Enabled React server page in Frontend BETA

Various fixes to React frontend

- Implemented Extension DataTables table support
- Fixed colors in night mode
- Fixed DataTables night mode
- Fixed chart opacity in night mode (HighCharts doesn't like hsl values)
- Fixed color selector buttons in night mode
- Translated Login button
- Added license to package.json
- Changed Extension endpoint table representation to objects to allow value formatting
This commit is contained in:
Aurora Lahtela 2022-08-27 22:26:30 +03:00
parent c824994cb9
commit 4c53a9a406
25 changed files with 251 additions and 78 deletions

View File

@ -0,0 +1,71 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.delivery.domain.datatransfer.extension;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
/**
* @author AuroraLS3
*/
public class TableCellDto {
private final String value;
@Nullable
private final Object valueUnformatted;
public TableCellDto(String value) {
this.value = value;
this.valueUnformatted = null;
}
public TableCellDto(String value, @Nullable Object valueUnformatted) {
this.value = value;
this.valueUnformatted = valueUnformatted;
}
public String getValue() {
return value;
}
@Nullable
public Object getValueUnformatted() {
return valueUnformatted;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TableCellDto that = (TableCellDto) o;
return Objects.equals(getValue(), that.getValue()) && Objects.equals(getValueUnformatted(), that.getValueUnformatted());
}
@Override
public int hashCode() {
return Objects.hash(getValue(), getValueUnformatted());
}
@Override
public String toString() {
return "TableCellDto{" +
"value='" + value + '\'' +
", valueUnformatted=" + valueUnformatted +
'}';
}
}

View File

@ -30,7 +30,7 @@ public class TableDto {
private final List<String> columns; private final List<String> columns;
private final List<IconDto> icons; private final List<IconDto> icons;
private final List<List<Object>> rows; private final List<List<TableCellDto>> rows;
public TableDto(Table table) { public TableDto(Table table) {
columns = Arrays.stream(table.getColumns()) columns = Arrays.stream(table.getColumns())
@ -46,17 +46,17 @@ public class TableDto {
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
private List<Object> constructRow(List<String> columns, Object[] row) { private List<TableCellDto> constructRow(List<String> columns, TableCellDto[] row) {
List<Object> constructedRow = new ArrayList<>(); List<TableCellDto> constructedRow = new ArrayList<>();
int headerLength = row.length - 1; int headerLength = row.length - 1;
int columnCount = columns.size(); int columnCount = columns.size();
for (int i = 0; i < columnCount; i++) { for (int i = 0; i < columnCount; i++) {
if (i > headerLength) { if (i > headerLength) {
constructedRow.add("-"); constructedRow.add(new TableCellDto("-"));
} else { } else {
Object value = row[i]; TableCellDto cell = row[i];
constructedRow.add(value != null ? value : '-'); constructedRow.add(cell != null ? cell : new TableCellDto("-"));
} }
} }
return constructedRow; return constructedRow;
@ -70,7 +70,7 @@ public class TableDto {
return icons; return icons;
} }
public List<List<Object>> getRows() { public List<List<TableCellDto>> getRows() {
return rows; return rows;
} }

View File

@ -23,6 +23,7 @@ import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.List; import java.util.List;
@Deprecated
public class DynamicHtmlTable implements HtmlTable { public class DynamicHtmlTable implements HtmlTable {
private final Header[] headers; private final Header[] headers;
private final List<Object[]> rows; private final List<Object[]> rows;

View File

@ -16,6 +16,7 @@
*/ */
package com.djrapitops.plan.delivery.rendering.html.structure; package com.djrapitops.plan.delivery.rendering.html.structure;
import com.djrapitops.plan.delivery.domain.datatransfer.extension.TableCellDto;
import com.djrapitops.plan.delivery.formatting.Formatters; import com.djrapitops.plan.delivery.formatting.Formatters;
import com.djrapitops.plan.delivery.rendering.html.Html; import com.djrapitops.plan.delivery.rendering.html.Html;
import com.djrapitops.plan.delivery.rendering.html.icon.Color; import com.djrapitops.plan.delivery.rendering.html.icon.Color;
@ -25,8 +26,10 @@ import com.djrapitops.plan.extension.table.TableColumnFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Deprecated
public interface HtmlTable { public interface HtmlTable {
static HtmlTable fromExtensionTable(Table table, com.djrapitops.plan.extension.icon.Color tableColor) { static HtmlTable fromExtensionTable(Table table, com.djrapitops.plan.extension.icon.Color tableColor) {
@ -57,25 +60,25 @@ public interface HtmlTable {
return headers.toArray(new Header[0]); return headers.toArray(new Header[0]);
} }
static List<Object[]> mapToRows(List<Object[]> rows, TableColumnFormat[] tableColumnFormats) { static List<TableCellDto[]> mapToRows(List<Object[]> rows, TableColumnFormat[] tableColumnFormats) {
return rows.stream() return rows.stream()
.map(row -> { .map(row -> {
List<Object> mapped = new ArrayList<>(row.length); List<TableCellDto> mapped = new ArrayList<>(row.length);
for (int i = 0; i < row.length; i++) { for (int i = 0; i < row.length; i++) {
Object value = row[i]; Object value = row[i];
if (value == null) { if (value == null) {
mapped.add(null); mapped.add(null);
} else { } else {
TableColumnFormat format = tableColumnFormats[i]; TableColumnFormat format = tableColumnFormats[i];
mapped.add(applyFormat(format, value)); mapped.add(new TableCellDto(applyFormat(format, value), value));
} }
} }
return mapped.toArray(); return mapped.toArray(new TableCellDto[0]);
}) })
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
static Object applyFormat(TableColumnFormat format, Object value) { static String applyFormat(TableColumnFormat format, Object value) {
try { try {
switch (format) { switch (format) {
case TIME_MILLISECONDS: case TIME_MILLISECONDS:
@ -90,7 +93,7 @@ public interface HtmlTable {
return Html.swapColorCodesToSpan(value.toString()); return Html.swapColorCodesToSpan(value.toString());
} }
} catch (Exception e) { } catch (Exception e) {
return value; return Objects.toString(value);
} }
} }

View File

@ -16,17 +16,19 @@
*/ */
package com.djrapitops.plan.delivery.rendering.html.structure; package com.djrapitops.plan.delivery.rendering.html.structure;
import com.djrapitops.plan.delivery.domain.datatransfer.extension.TableCellDto;
import com.djrapitops.plan.delivery.rendering.html.icon.Color; import com.djrapitops.plan.delivery.rendering.html.icon.Color;
import com.djrapitops.plan.extension.table.Table; import com.djrapitops.plan.extension.table.Table;
import java.util.List; import java.util.List;
@Deprecated
public class HtmlTableWithColoredHeader implements HtmlTable { public class HtmlTableWithColoredHeader implements HtmlTable {
private final Header[] headers; private final Header[] headers;
private final Color headerColor; private final Color headerColor;
private final List<Object[]> rows; private final List<TableCellDto[]> rows;
public HtmlTableWithColoredHeader(Header[] headers, Color headerColor, List<Object[]> rows) { public HtmlTableWithColoredHeader(Header[] headers, Color headerColor, List<TableCellDto[]> rows) {
this.headers = headers; this.headers = headers;
this.headerColor = headerColor; this.headerColor = headerColor;
this.rows = rows; this.rows = rows;
@ -63,15 +65,15 @@ public class HtmlTableWithColoredHeader implements HtmlTable {
StringBuilder builtBody = new StringBuilder(); StringBuilder builtBody = new StringBuilder();
builtBody.append("<tbody>"); builtBody.append("<tbody>");
if (rows.isEmpty()) { if (rows.isEmpty()) {
appendRow(builtBody, "No Data"); appendRow(builtBody, new TableCellDto("No Data"));
} }
for (Object[] row : rows) { for (TableCellDto[] row : rows) {
appendRow(builtBody, row); appendRow(builtBody, row);
} }
return builtBody.append("</tbody>").toString(); return builtBody.append("</tbody>").toString();
} }
private void appendRow(StringBuilder builtBody, Object... row) { private void appendRow(StringBuilder builtBody, TableCellDto... row) {
int headerLength = row.length - 1; int headerLength = row.length - 1;
builtBody.append("<tr>"); builtBody.append("<tr>");
for (int i = 0; i < headers.length; i++) { for (int i = 0; i < headers.length; i++) {
@ -80,8 +82,8 @@ public class HtmlTableWithColoredHeader implements HtmlTable {
builtBody.append("<td>-"); builtBody.append("<td>-");
} else { } else {
builtBody.append("<td>"); builtBody.append("<td>");
Object value = row[i]; TableCellDto cell = row[i];
builtBody.append(value != null ? value : '-'); builtBody.append(cell != null ? cell.getValue() : '-');
} }
builtBody.append("</td>"); builtBody.append("</td>");
} catch (ClassCastException | ArrayIndexOutOfBoundsException e) { } catch (ClassCastException | ArrayIndexOutOfBoundsException e) {

View File

@ -107,6 +107,12 @@ public class PageFactory {
public Page serverPage(ServerUUID serverUUID) throws IOException { public Page serverPage(ServerUUID serverUUID) throws IOException {
Server server = dbSystem.get().getDatabase().query(ServerQueries.fetchServerMatchingIdentifier(serverUUID)) Server server = dbSystem.get().getDatabase().query(ServerQueries.fetchServerMatchingIdentifier(serverUUID))
.orElseThrow(() -> new NotFoundException("Server not found in the database")); .orElseThrow(() -> new NotFoundException("Server not found in the database"));
if (config.get().isTrue(PluginSettings.FRONTEND_BETA)) {
String reactHtml = getResource("index.html");
return () -> reactHtml;
}
return new ServerPage( return new ServerPage(
getResource("server.html"), getResource("server.html"),
server, server,

View File

@ -1,4 +1,5 @@
{ {
"license": "LGPL-3.0-or-later",
"name": "dashboard", "name": "dashboard",
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,

View File

@ -9,6 +9,7 @@ import KillsTable from "../table/KillsTable";
import Accordion from "./Accordion"; import Accordion from "./Accordion";
import {useTranslation} from "react-i18next"; import {useTranslation} from "react-i18next";
import {baseAddress} from "../../service/backendConfiguration"; import {baseAddress} from "../../service/backendConfiguration";
import {ChartLoader} from "../navigation/Loader";
const SessionHeader = ({session}) => { const SessionHeader = ({session}) => {
return ( return (
@ -88,7 +89,7 @@ const SessionAccordion = (
) => { ) => {
const {t} = useTranslation(); const {t} = useTranslation();
if (!sessions) return <></> if (!sessions) return <ChartLoader/>
const firstColumn = isPlayer ? (<><Fa icon={faUser}/> {t('html.label.player')}</>) const firstColumn = isPlayer ? (<><Fa icon={faUser}/> {t('html.label.player')}</>)
: (<><Fa icon={faServer}/> {t('html.label.server')}</>) : (<><Fa icon={faServer}/> {t('html.label.server')}</>)

View File

@ -3,8 +3,7 @@ import {Card, Col} from "react-bootstrap-v5";
import ExtensionIcon from "./ExtensionIcon"; import ExtensionIcon from "./ExtensionIcon";
import Datapoint from "../Datapoint"; import Datapoint from "../Datapoint";
import Masonry from 'masonry-layout' import Masonry from 'masonry-layout'
import {useTheme} from "../../hooks/themeHook"; import ExtensionTable from "./ExtensionTable";
import {useTranslation} from "react-i18next";
export const ExtensionCardWrapper = ({extension, children}) => { export const ExtensionCardWrapper = ({extension, children}) => {
const [windowWidth, setWindowWidth] = useState(window.innerWidth); const [windowWidth, setWindowWidth] = useState(window.innerWidth);
@ -65,32 +64,6 @@ const ExtensionValues = ({tab}) => {
) )
} }
const ExtensionTable = ({table}) => {
const {nightModeEnabled} = useTheme();
const {t} = useTranslation();
const columns = table.table.rows.length ? table.table.rows.map((row, i) => <tr key={i}>{row.map((value, j) => <td
key={i + '' + j}>{value}</td>)}</tr>) :
<tr>{table.table.columns.map((column, i) =>
<td key={i}>{i === 0 ? t('generic.noData') : '-'}</td>)}
</tr>
return (
<table className={"table table-striped" + (nightModeEnabled ? " table-dark" : '')}>
<thead className={table.tableColorClass}>
<tr>
{table.table.columns.map((column, i) => <th key={i}><ExtensionIcon
icon={table.table.icons[i]}/> {column}
</th>)}
</tr>
</thead>
<tbody>
{columns}
</tbody>
</table>
)
}
const ExtensionTables = ({tab}) => { const ExtensionTables = ({tab}) => {
return (<> return (<>
{tab.tableData.map((table, i) => ( {tab.tableData.map((table, i) => (

View File

@ -11,4 +11,8 @@ const ExtensionIcon = ({icon}) => (
/> />
) )
export const toExtensionIconHtmlString = ({icon}) => {
return icon ? `<i class="${iconTypeToFontAwesomeClass(icon.family)} ${icon.iconName} ${icon.colorClass}"></i>` : '';
}
export default ExtensionIcon; export default ExtensionIcon;

View File

@ -0,0 +1,77 @@
import React, {useState} from 'react';
import {useTheme} from "../../hooks/themeHook";
import {useTranslation} from "react-i18next";
import ExtensionIcon, {toExtensionIconHtmlString} from "./ExtensionIcon";
import DataTablesTable from "../table/DataTablesTable";
const ExtensionDataTable = ({table}) => {
const [id] = useState("extension-table-" + new Date().getTime() + "-" + (Math.floor(Math.random() * 100000)));
const data = {
columns: table.table.columns.map((column, i) => {
return {
title: toExtensionIconHtmlString(table.table.icons[i]) + ' ' + column,
data: {
"_": `col${i}.v`,
display: `col${i}.d`
},
};
}),
data: table.table.rows.map((row) => {
const dataRow = {};
row.forEach((cell, j) => dataRow[`col${j}`] = {
v: cell['valueUnformatted'] || cell.value || cell,
d: cell.value || cell
});
return dataRow;
})
};
const options = {
responsive: true,
deferRender: true,
columns: data.columns,
data: data.data,
order: [[1, "desc"]]
}
return (
<DataTablesTable id={id} options={options}/>
)
}
const ExtensionColoredTable = ({table}) => {
const {nightModeEnabled} = useTheme();
const {t} = useTranslation();
const rows = table.table.rows.length ? table.table.rows.map((row, i) => <tr key={i}>{row.map((value, j) => <td
key={i + '' + j}>{value.value || String(value)}</td>)}</tr>) :
<tr>{table.table.columns.map((column, i) =>
<td key={i}>{i === 0 ? t('generic.noData') : '-'}</td>)}
</tr>
return (
<table className={"table table-striped" + (nightModeEnabled ? " table-dark" : '')}>
<thead className={table.tableColorClass}>
<tr>
{table.table.columns.map((column, i) => <th key={i}><ExtensionIcon
icon={table.table.icons[i]}/> {column}
</th>)}
</tr>
</thead>
<tbody>
{rows}
</tbody>
</table>
)
}
const ExtensionTable = ({table}) => {
const tableLength = table.table.rows.length;
if (tableLength > 25) {
return <ExtensionDataTable table={table}/>
} else {
return <ExtensionColoredTable table={table}/>
}
}
export default ExtensionTable

View File

@ -8,7 +8,7 @@ import {useTranslation} from "react-i18next";
const LineGraph = ({id, series}) => { const LineGraph = ({id, series}) => {
const {t} = useTranslation() const {t} = useTranslation()
const {graphTheming} = useTheme(); const {graphTheming, nightModeEnabled} = useTheme();
useEffect(() => { useEffect(() => {
NoDataDisplay(Highcharts); NoDataDisplay(Highcharts);
@ -27,12 +27,12 @@ const LineGraph = ({id, series}) => {
title: {text: ''}, title: {text: ''},
plotOptions: { plotOptions: {
areaspline: { areaspline: {
fillOpacity: 0.4 fillOpacity: nightModeEnabled ? 0.2 : 0.4
} }
}, },
series: series series: series
}) })
}, [series, graphTheming, id, t]) }, [series, graphTheming, id, t, nightModeEnabled])
return ( return (
<div className="chart-area" id={id}> <div className="chart-area" id={id}>

View File

@ -2,10 +2,13 @@ import React, {useEffect, useState} from "react";
import {useTranslation} from "react-i18next"; import {useTranslation} from "react-i18next";
import {tooltip} from "../../util/graphs"; import {tooltip} from "../../util/graphs";
import LineGraph from "./LineGraph"; import LineGraph from "./LineGraph";
import {useTheme} from "../../hooks/themeHook";
import {withReducedSaturation} from "../../util/colors";
const PlayerPingGraph = ({data}) => { const PlayerPingGraph = ({data}) => {
const {t} = useTranslation(); const {t} = useTranslation();
const [series, setSeries] = useState([]); const [series, setSeries] = useState([]);
const {nightModeEnabled} = useTheme();
useEffect(() => { useEffect(() => {
const avgPingSeries = { const avgPingSeries = {
@ -13,23 +16,23 @@ const PlayerPingGraph = ({data}) => {
type: 'spline', type: 'spline',
tooltip: tooltip.twoDecimals, tooltip: tooltip.twoDecimals,
data: data.avg_ping_series, data: data.avg_ping_series,
color: data.colors.avg color: nightModeEnabled ? withReducedSaturation(data.colors.avg) : data.colors.avg
} }
const maxPingSeries = { const maxPingSeries = {
name: t('html.label.worstPing'), name: t('html.label.worstPing'),
type: 'spline', type: 'spline',
tooltip: tooltip.twoDecimals, tooltip: tooltip.twoDecimals,
data: data.max_ping_series, data: data.max_ping_series,
color: data.colors.max color: nightModeEnabled ? withReducedSaturation(data.colors.max) : data.colors.max
} }
const minPingSeries = { const minPingSeries = {
name: t('html.label.bestPing'), name: t('html.label.bestPing'),
type: 'spline', type: 'spline',
tooltip: tooltip.twoDecimals, tooltip: tooltip.twoDecimals,
data: data.min_ping_series, data: data.min_ping_series,
color: data.colors.min color: nightModeEnabled ? withReducedSaturation(data.colors.min) : data.colors.min
} }
setSeries([avgPingSeries, maxPingSeries, minPingSeries]); setSeries([avgPingSeries, maxPingSeries, minPingSeries, nightModeEnabled]);
}, [data, t]) }, [data, t])
return ( return (

View File

@ -3,14 +3,15 @@ import Highcharts from 'highcharts';
import {useTheme} from "../../hooks/themeHook"; import {useTheme} from "../../hooks/themeHook";
import {useTranslation} from "react-i18next"; import {useTranslation} from "react-i18next";
import Accessibility from "highcharts/modules/accessibility"; import Accessibility from "highcharts/modules/accessibility";
import {withReducedSaturation} from "../../util/colors";
const PunchCard = ({series}) => { const PunchCard = ({series}) => {
const {t} = useTranslation(); const {t} = useTranslation();
const {graphTheming} = useTheme(); const {graphTheming, nightModeEnabled} = useTheme();
useEffect(() => { useEffect(() => {
const punchCard = { const punchCard = {
name: t('html.label.relativeJoinActivity'), name: t('html.label.relativeJoinActivity'),
color: '#222', color: nightModeEnabled ? withReducedSaturation('#222') : '#222',
data: series data: series
}; };
Accessibility(Highcharts); Accessibility(Highcharts);
@ -46,7 +47,7 @@ const PunchCard = ({series}) => {
}, },
series: [punchCard] series: [punchCard]
}), 25) }), 25)
}, [series, graphTheming, t]) }, [series, graphTheming, t, nightModeEnabled])
return ( return (
<div className="chart-area" id="punchcard"> <div className="chart-area" id="punchcard">

View File

@ -2,10 +2,13 @@ import {useTranslation} from "react-i18next";
import React, {useEffect, useState} from "react"; import React, {useEffect, useState} from "react";
import {tooltip} from "../../util/graphs"; import {tooltip} from "../../util/graphs";
import LineGraph from "./LineGraph"; import LineGraph from "./LineGraph";
import {useTheme} from "../../hooks/themeHook";
import {withReducedSaturation} from "../../util/colors";
const TimeByTimeGraph = ({data}) => { const TimeByTimeGraph = ({data}) => {
const {t} = useTranslation(); const {t} = useTranslation();
const [series, setSeries] = useState([]); const [series, setSeries] = useState([]);
const {nightModeEnabled} = useTheme();
useEffect(() => { useEffect(() => {
const uniquePlayers = { const uniquePlayers = {
@ -13,16 +16,16 @@ const TimeByTimeGraph = ({data}) => {
type: 'spline', type: 'spline',
tooltip: tooltip.zeroDecimals, tooltip: tooltip.zeroDecimals,
data: data.uniquePlayers, data: data.uniquePlayers,
color: data.colors.playersOnline color: nightModeEnabled ? withReducedSaturation(data.colors.playersOnline) : data.colors.playersOnline
}; };
const newPlayers = { const newPlayers = {
name: t('html.label.newPlayers'), name: t('html.label.newPlayers'),
type: 'spline', type: 'spline',
tooltip: tooltip.zeroDecimals, tooltip: tooltip.zeroDecimals,
data: data.newPlayers, data: data.newPlayers,
color: data.colors.newPlayers color: nightModeEnabled ? withReducedSaturation(data.colors.newPlayers) : data.colors.newPlayers
}; };
setSeries([uniquePlayers, newPlayers]); setSeries([uniquePlayers, newPlayers, nightModeEnabled]);
}, [data, t]) }, [data, t])
return ( return (

View File

@ -110,7 +110,7 @@ const AllPerformanceGraph = ({id, data, dataSeries}) => {
type: 'areaspline', type: 'areaspline',
tooltip: tooltip.zeroDecimals, tooltip: tooltip.zeroDecimals,
data: dataSeries.playersOnline, data: dataSeries.playersOnline,
color: nightModeEnabled ? withReducedSaturation(data.colors.playersOnline) : data.colors.playersOnline, color: data.colors.playersOnline,
yAxis: 0 yAxis: 0
}, tps: { }, tps: {
name: t('html.label.tps'), name: t('html.label.tps'),
@ -164,7 +164,7 @@ const AllPerformanceGraph = ({id, data, dataSeries}) => {
title: {text: ''}, title: {text: ''},
plotOptions: { plotOptions: {
areaspline: { areaspline: {
fillOpacity: 0.4 fillOpacity: nightModeEnabled ? 0.2 : 0.4
} }
}, },
legend: { legend: {

View File

@ -21,7 +21,7 @@ const CpuRamPerformanceGraph = ({id, data, dataSeries}) => {
type: 'areaspline', type: 'areaspline',
tooltip: tooltip.zeroDecimals, tooltip: tooltip.zeroDecimals,
data: dataSeries.playersOnline, data: dataSeries.playersOnline,
color: nightModeEnabled ? withReducedSaturation(data.colors.playersOnline) : data.colors.playersOnline, color: data.colors.playersOnline,
yAxis: 0 yAxis: 0
}, cpu: { }, cpu: {
name: t('html.label.cpu'), name: t('html.label.cpu'),
@ -76,7 +76,7 @@ const CpuRamPerformanceGraph = ({id, data, dataSeries}) => {
title: {text: ''}, title: {text: ''},
plotOptions: { plotOptions: {
areaspline: { areaspline: {
fillOpacity: 0.4 fillOpacity: nightModeEnabled ? 0.2 : 0.4
} }
}, },
legend: { legend: {

View File

@ -16,13 +16,13 @@ const DiskPerformanceGraph = ({id, data, dataSeries}) => {
const zones = { const zones = {
disk: [{ disk: [{
value: data.zones.diskThresholdMed, value: data.zones.diskThresholdMed,
color: nightModeEnabled ? withReducedSaturation(data.colors.low) : data.colors.low color: data.colors.low
}, { }, {
value: data.zones.diskThresholdHigh, value: data.zones.diskThresholdHigh,
color: nightModeEnabled ? withReducedSaturation(data.colors.med) : data.colors.med color: data.colors.med
}, { }, {
value: Number.MAX_VALUE, value: Number.MAX_VALUE,
color: nightModeEnabled ? withReducedSaturation(data.colors.high) : data.colors.high color: data.colors.high
}] }]
}; };
@ -57,7 +57,7 @@ const DiskPerformanceGraph = ({id, data, dataSeries}) => {
title: {text: ''}, title: {text: ''},
plotOptions: { plotOptions: {
areaspline: { areaspline: {
fillOpacity: 0.4 fillOpacity: nightModeEnabled ? 0.2 : 0.4
} }
}, },
legend: { legend: {

View File

@ -33,7 +33,7 @@ const TpsPerformanceGraph = ({id, data, dataSeries}) => {
type: 'areaspline', type: 'areaspline',
tooltip: tooltip.zeroDecimals, tooltip: tooltip.zeroDecimals,
data: dataSeries.playersOnline, data: dataSeries.playersOnline,
color: nightModeEnabled ? withReducedSaturation(data.colors.playersOnline) : data.colors.playersOnline, color: data.colors.playersOnline,
yAxis: 0 yAxis: 0
}, tps: { }, tps: {
name: t('html.label.tps'), name: t('html.label.tps'),
@ -73,7 +73,7 @@ const TpsPerformanceGraph = ({id, data, dataSeries}) => {
title: {text: ''}, title: {text: ''},
plotOptions: { plotOptions: {
areaspline: { areaspline: {
fillOpacity: 0.4 fillOpacity: nightModeEnabled ? 0.2 : 0.4
} }
}, },
legend: { legend: {

View File

@ -21,7 +21,7 @@ const WorldPerformanceGraph = ({id, data, dataSeries}) => {
type: 'areaspline', type: 'areaspline',
tooltip: tooltip.zeroDecimals, tooltip: tooltip.zeroDecimals,
data: dataSeries.playersOnline, data: dataSeries.playersOnline,
color: nightModeEnabled ? withReducedSaturation(data.colors.playersOnline) : data.colors.playersOnline, color: data.colors.playersOnline,
yAxis: 0 yAxis: 0
}, entities: { }, entities: {
name: t('html.label.loadedEntities'), name: t('html.label.loadedEntities'),
@ -75,7 +75,7 @@ const WorldPerformanceGraph = ({id, data, dataSeries}) => {
title: {text: ''}, title: {text: ''},
plotOptions: { plotOptions: {
areaspline: { areaspline: {
fillOpacity: 0.4 fillOpacity: nightModeEnabled ? 0.2 : 0.4
} }
}, },
legend: { legend: {

View File

@ -7,7 +7,7 @@ import {Modal} from "react-bootstrap-v5";
import {useTranslation} from "react-i18next"; import {useTranslation} from "react-i18next";
const ColorSelectorButton = ({color, setColor, disabled}) => const ColorSelectorButton = ({color, setColor, disabled}) =>
<button className={"btn color-chooser " + colorEnumToBgClass(color)} <button className={"btn color-chooser " + colorEnumToBgClass(color) + (disabled ? " disabled" : '')}
id={"choose-" + color} id={"choose-" + color}
disabled={disabled} disabled={disabled}
onClick={() => setColor(color)} onClick={() => setColor(color)}

View File

@ -4,9 +4,11 @@ import 'datatables.net-bs5'
import 'datatables.net-responsive-bs5' import 'datatables.net-responsive-bs5'
import 'datatables.net-bs5/css/dataTables.bootstrap5.min.css'; import 'datatables.net-bs5/css/dataTables.bootstrap5.min.css';
import 'datatables.net-responsive-bs5/css/responsive.bootstrap5.min.css'; import 'datatables.net-responsive-bs5/css/responsive.bootstrap5.min.css';
import {useTheme} from "../../hooks/themeHook";
const DataTablesTable = ({id, options}) => { const DataTablesTable = ({id, options}) => {
const dataTableRef = useRef(null); const dataTableRef = useRef(null);
const {nightModeEnabled} = useTheme();
useEffect(() => { useEffect(() => {
const idSelector = `#${id}`; const idSelector = `#${id}`;
@ -24,7 +26,8 @@ const DataTablesTable = ({id, options}) => {
}, [id, options, dataTableRef]); }, [id, options, dataTableRef]);
return ( return (
<table id={id} className="table table-bordered table-striped" style={{width: "100%"}}/> <table id={id} className={"table table-bordered table-striped" + (nightModeEnabled ? " table-dark" : '')}
style={{width: "100%"}}/>
) )
}; };

View File

@ -181,6 +181,7 @@ div.scrollbar {
.color-chooser { .color-chooser {
margin-right: 0.15rem; margin-right: 0.15rem;
margin-bottom: 0.2rem; margin-bottom: 0.2rem;
border-color: transparent !important;
} }
/* Navbar ====================================== */ /* Navbar ====================================== */
@ -767,111 +768,133 @@ div#navSrvContainer::-webkit-scrollbar-thumb {
.bg-red, body.theme-red .fc-toolbar-chunk .btn.btn-primary { .bg-red, body.theme-red .fc-toolbar-chunk .btn.btn-primary {
background-color: #F44336; background-color: #F44336;
--bs-btn-disabled-bg: #F44336;
color: #fff; color: #fff;
} }
.bg-pink, body.theme-pink .fc-toolbar-chunk .btn.btn-primary { .bg-pink, body.theme-pink .fc-toolbar-chunk .btn.btn-primary {
background-color: #E91E63; background-color: #E91E63;
--bs-btn-disabled-bg: #E91E63;
color: #fff; color: #fff;
} }
.bg-purple, body.theme-purple .fc-toolbar-chunk .btn.btn-primary { .bg-purple, body.theme-purple .fc-toolbar-chunk .btn.btn-primary {
background-color: #9C27B0; background-color: #9C27B0;
--bs-btn-disabled-bg: #9C27B0;
color: #fff; color: #fff;
} }
.bg-deep-purple, body.theme-deep-purple .fc-toolbar-chunk .btn.btn-primary { .bg-deep-purple, body.theme-deep-purple .fc-toolbar-chunk .btn.btn-primary {
background-color: #673AB7; background-color: #673AB7;
--bs-btn-disabled-bg: #673AB7;
color: #fff; color: #fff;
} }
.bg-indigo, body.theme-indigo .fc-toolbar-chunk .btn.btn-primary { .bg-indigo, body.theme-indigo .fc-toolbar-chunk .btn.btn-primary {
background-color: #3F51B5; background-color: #3F51B5;
--bs-btn-disabled-bg: #3F51B5;
color: #fff; color: #fff;
} }
.bg-blue, body.theme-blue .fc-toolbar-chunk .btn.btn-primary { .bg-blue, body.theme-blue .fc-toolbar-chunk .btn.btn-primary {
background-color: #2196F3; background-color: #2196F3;
--bs-btn-disabled-bg: #2196F3;
color: #fff; color: #fff;
} }
.bg-light-blue, body.theme-light-blue .fc-toolbar-chunk .btn.btn-primary { .bg-light-blue, body.theme-light-blue .fc-toolbar-chunk .btn.btn-primary {
background-color: #03A9F4; background-color: #03A9F4;
--bs-btn-disabled-bg: #03A9F4;
color: #fff; color: #fff;
} }
.bg-cyan, body.theme-cyan .fc-toolbar-chunk .btn.btn-primary { .bg-cyan, body.theme-cyan .fc-toolbar-chunk .btn.btn-primary {
background-color: #00BCD4; background-color: #00BCD4;
--bs-btn-disabled-bg: #00BCD4;
color: #fff; color: #fff;
} }
.bg-teal, body.theme-teal .fc-toolbar-chunk .btn.btn-primary { .bg-teal, body.theme-teal .fc-toolbar-chunk .btn.btn-primary {
background-color: #009688; background-color: #009688;
--bs-btn-disabled-bg: #009688;
color: #fff; color: #fff;
} }
.bg-green, body.theme-green .fc-toolbar-chunk .btn.btn-primary { .bg-green, body.theme-green .fc-toolbar-chunk .btn.btn-primary {
background-color: #4CAF50; background-color: #4CAF50;
--bs-btn-disabled-bg: #4CAF50;
color: #fff; color: #fff;
} }
.bg-light-green, body.theme-light-green .fc-toolbar-chunk .btn.btn-primary { .bg-light-green, body.theme-light-green .fc-toolbar-chunk .btn.btn-primary {
background-color: #8BC34A; background-color: #8BC34A;
--bs-btn-disabled-bg: #8BC34A;
color: #fff; color: #fff;
} }
.bg-lime, body.theme-lime .fc-toolbar-chunk .btn.btn-primary { .bg-lime, body.theme-lime .fc-toolbar-chunk .btn.btn-primary {
background-color: #CDDC39; background-color: #CDDC39;
--bs-btn-disabled-bg: #CDDC39;
color: #fff; color: #fff;
} }
.bg-yellow, body.theme-yellow .fc-toolbar-chunk .btn.btn-primary { .bg-yellow, body.theme-yellow .fc-toolbar-chunk .btn.btn-primary {
background-color: #ffe821; background-color: #ffe821;
--bs-btn-disabled-bg: #ffe821;
color: #fff; color: #fff;
} }
.bg-amber, body.theme-amber .fc-toolbar-chunk .btn.btn-primary { .bg-amber, body.theme-amber .fc-toolbar-chunk .btn.btn-primary {
background-color: #FFC107; background-color: #FFC107;
--bs-btn-disabled-bg: #FFC107;
color: #fff; color: #fff;
} }
.bg-orange, body.theme-orange .fc-toolbar-chunk .btn.btn-primary { .bg-orange, body.theme-orange .fc-toolbar-chunk .btn.btn-primary {
background-color: #FF9800; background-color: #FF9800;
--bs-btn-disabled-bg: #FF9800;
color: #fff; color: #fff;
} }
.bg-deep-orange, body.theme-deep-orange .fc-toolbar-chunk .btn.btn-primary { .bg-deep-orange, body.theme-deep-orange .fc-toolbar-chunk .btn.btn-primary {
background-color: #FF5722; background-color: #FF5722;
--bs-btn-disabled-bg: #FF5722;
color: #fff; color: #fff;
} }
.bg-brown, body.theme-brown .fc-toolbar-chunk .btn.btn-primary { .bg-brown, body.theme-brown .fc-toolbar-chunk .btn.btn-primary {
background-color: #795548; background-color: #795548;
--bs-btn-disabled-bg: #795548;
color: #fff; color: #fff;
} }
.bg-grey, body.theme-grey .fc-toolbar-chunk .btn.btn-primary { .bg-grey, body.theme-grey .fc-toolbar-chunk .btn.btn-primary {
background-color: #9E9E9E; background-color: #9E9E9E;
--bs-btn-disabled-bg: #9E9E9E;
color: #fff; color: #fff;
} }
.bg-blue-grey, body.theme-blue-grey .fc-toolbar-chunk .btn.btn-primary { .bg-blue-grey, body.theme-blue-grey .fc-toolbar-chunk .btn.btn-primary {
background-color: #607D8B; background-color: #607D8B;
--bs-btn-disabled-bg: #607D8B;
color: #fff; color: #fff;
} }
.bg-black, body.theme-black .fc-toolbar-chunk .btn.btn-primary { .bg-black, body.theme-black .fc-toolbar-chunk .btn.btn-primary {
background-color: #555555; background-color: #555555;
--bs-btn-disabled-bg: #555555;
color: #fff; color: #fff;
} }
.bg-white, body.theme-white .fc-toolbar-chunk .btn.btn-primary { .bg-white, body.theme-white .fc-toolbar-chunk .btn.btn-primary {
background-color: #ffffff; background-color: #ffffff;
--bs-btn-disabled-bg: #ffffff;
color: #333; color: #333;
} }
.bg-plan, body.theme-plan .fc-toolbar-chunk .btn.btn-primary { .bg-plan, body.theme-plan .fc-toolbar-chunk .btn.btn-primary {
background-color: #368F17; background-color: #368F17;
--bs-btn-disabled-bg: #368F17;
color: #fff; color: #fff;
} }
@ -902,6 +925,7 @@ div#navSrvContainer::-webkit-scrollbar-thumb {
.bg-night, body.theme-night .fc-toolbar-chunk .btn.btn-primary { .bg-night, body.theme-night .fc-toolbar-chunk .btn.btn-primary {
background-color: #44475a; background-color: #44475a;
--bs-btn-disabled-bg: #44475a;
color: #eee8d5; color: #eee8d5;
} }

View File

@ -181,7 +181,7 @@ const createNightModeColorCss = () => {
return `.bg-${color.name}{background-color: ${desaturatedColor} !important;color: ${nightColors.yellow};}` + return `.bg-${color.name}{background-color: ${desaturatedColor} !important;color: ${nightColors.yellow};}` +
`.bg-${color.name}-outline{outline-color: ${desaturatedColor};border-color: ${desaturatedColor};}` + `.bg-${color.name}-outline{outline-color: ${desaturatedColor};border-color: ${desaturatedColor};}` +
`.col-${color.name}{color: ${desaturatedColor} !important;}` `.col-${color.name}{color: ${desaturatedColor} !important;}`
}).join(); }).join('');
} }
export const createNightModeCss = () => { export const createNightModeCss = () => {

View File

@ -66,7 +66,7 @@ const LoginForm = ({login}) => {
value={password} onChange={event => setPassword(event.target.value)}/> value={password} onChange={event => setPassword(event.target.value)}/>
</div> </div>
<button className="btn bg-plan btn-user w-100" id="login-button" onClick={onLogin}> <button className="btn bg-plan btn-user w-100" id="login-button" onClick={onLogin}>
Login {t('html.login.login')}
</button> </button>
</form> </form>
); );