From b92e886a3905b861f77ec1a6ac70ced33986b74b Mon Sep 17 00:00:00 2001 From: Aurora Lahtela <24460436+AuroraLS3@users.noreply.github.com> Date: Sat, 25 Feb 2023 13:57:12 +0200 Subject: [PATCH] React PageExtension Javascript APIs (#2894) * Page extension api javascript file * Add support for extending rows with pageExtensionApi Tested it * Support javascript snippets in react index.html * Add context to pageExtensionApi calls * Fix redirect text overlapping with sidebar * Add new API for registering custom javascript and css --- .../plan/capability/Capability.java | 9 ++- .../plan/delivery/web/ResourceService.java | 80 ++++++++++++++++++- .../plan/delivery/export/ReactExporter.java | 5 +- .../delivery/rendering/pages/PageFactory.java | 23 +++--- .../delivery/rendering/pages/ReactPage.java | 5 +- .../plan/delivery/web/ResourceSvc.java | 44 ++++++---- .../delivery/webserver/ResponseResolver.java | 1 + .../delivery/webserver/AccessControlTest.java | 4 + Plan/react/dashboard/public/index.html | 46 ++++++----- .../dashboard/public/pageExtensionApi.js | 69 ++++++++++++++++ Plan/react/dashboard/src/App.js | 5 +- .../cards/common/GeolocationsCard.js | 7 +- .../server/values/ServerAsNumbersCard.js | 5 +- .../layout/extension/ExtendableCardBody.js | 38 +++++++++ .../layout/extension/ExtendableRow.js | 38 +++++++++ .../components/navigation/MainPageRedirect.js | 8 +- .../dashboard/src/hooks/pageExtensionHook.js | 61 ++++++++++++++ .../src/views/common/Geolocations.js | 7 +- .../src/views/network/NetworkGeolocations.js | 2 +- .../src/views/network/NetworkJoinAddresses.js | 9 ++- .../src/views/network/NetworkOverview.js | 16 ++-- .../src/views/network/NetworkPerformance.js | 13 +-- .../network/NetworkPlayerbaseOverview.js | 13 +-- .../src/views/network/NetworkServers.js | 7 +- .../src/views/network/NetworkSessions.js | 9 ++- .../src/views/player/PlayerOverview.js | 22 ++--- .../src/views/player/PlayerPvpPve.js | 13 +-- .../src/views/player/PlayerServers.js | 13 +-- .../src/views/player/PlayerSessions.js | 9 ++- .../dashboard/src/views/players/AllPlayers.js | 7 +- .../src/views/server/OnlineActivity.js | 13 +-- .../src/views/server/PlayerbaseOverview.js | 13 +-- .../src/views/server/ServerGeolocations.js | 2 +- .../src/views/server/ServerJoinAddresses.js | 9 ++- .../src/views/server/ServerOverview.js | 13 +-- .../src/views/server/ServerPerformance.js | 13 +-- .../src/views/server/ServerPlayers.js | 9 ++- .../src/views/server/ServerPvpPve.js | 13 +-- .../src/views/server/ServerSessions.js | 9 ++- 39 files changed, 517 insertions(+), 165 deletions(-) create mode 100644 Plan/react/dashboard/public/pageExtensionApi.js create mode 100644 Plan/react/dashboard/src/components/layout/extension/ExtendableCardBody.js create mode 100644 Plan/react/dashboard/src/components/layout/extension/ExtendableRow.js create mode 100644 Plan/react/dashboard/src/hooks/pageExtensionHook.js diff --git a/Plan/api/src/main/java/com/djrapitops/plan/capability/Capability.java b/Plan/api/src/main/java/com/djrapitops/plan/capability/Capability.java index 3607f6675..2f7629a47 100644 --- a/Plan/api/src/main/java/com/djrapitops/plan/capability/Capability.java +++ b/Plan/api/src/main/java/com/djrapitops/plan/capability/Capability.java @@ -16,6 +16,8 @@ */ package com.djrapitops.plan.capability; +import com.djrapitops.plan.delivery.web.ResourceService; + import java.util.Optional; import java.util.UUID; @@ -83,7 +85,12 @@ enum Capability { /** * {@link com.djrapitops.plan.delivery.web.ResourceService} */ - PAGE_EXTENSION_RESOURCES; + PAGE_EXTENSION_RESOURCES, + /** + * {@link com.djrapitops.plan.delivery.web.ResourceService#addJavascriptToResource(String, String, ResourceService.Position, String, String)} + * {@link com.djrapitops.plan.delivery.web.ResourceService#addStyleToResource(String, String, ResourceService.Position, String, String)} + */ + PAGE_EXTENSION_RESOURCES_REGISTER_DIRECT_CUSTOMIZATION; static Optional getByName(String name) { if (name == null) { diff --git a/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/ResourceService.java b/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/ResourceService.java index d85eeda8c..6b1a7ba51 100644 --- a/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/ResourceService.java +++ b/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/ResourceService.java @@ -16,6 +16,9 @@ */ package com.djrapitops.plan.delivery.web; +import com.djrapitops.plan.delivery.web.resolver.MimeType; +import com.djrapitops.plan.delivery.web.resolver.NoAuthResolver; +import com.djrapitops.plan.delivery.web.resolver.Response; import com.djrapitops.plan.delivery.web.resource.WebResource; import java.util.Optional; @@ -48,7 +51,7 @@ public interface ResourceService { WebResource getResource(String pluginName, String fileName, Supplier source); /** - * Add javascript to load in an existing html resource. + * Add javascript to load in a html resource. *

* Adds {@code } or multiple to the resource. * @@ -63,6 +66,40 @@ public interface ResourceService { */ void addScriptsToResource(String pluginName, String fileName, Position position, String... jsSources); + /** + * Add javascript to load in a html resource. + *

+ * Requires PAGE_EXTENSION_RESOURCES_REGISTER_DIRECT_CUSTOMIZATION Capability. + * + * @param pluginName Name of your plugin (for config purposes) + * @param fileName Name of the .html file being modified + * @param position Where to place the script tag on the page. + * @param scriptName Name of your javascript file (used on the page) + * @param javascriptAsString Javascript file contents in UTF-8 + * @throws IllegalArgumentException If fileName is null, empty or does not end with .html + * @throws IllegalArgumentException If anything is empty or null + */ + default void addJavascriptToResource(String pluginName, String fileName, Position position, String scriptName, String javascriptAsString) { + if (javascriptAsString == null || javascriptAsString.isEmpty()) { + throw new IllegalArgumentException("null or empty 'javascriptAsString' given."); + } + if (scriptName == null || scriptName.isEmpty()) { + throw new IllegalArgumentException("null or empty 'scriptName' given."); + } + String actualScriptName = scriptName.endsWith(".js") ? scriptName : scriptName + ".js"; + + addScriptsToResource(pluginName, fileName, position, pluginName + "/" + actualScriptName); + ResolverService.getInstance().registerResolver(pluginName, pluginName + "/" + actualScriptName, (NoAuthResolver) request -> { + if (request.getPath().asString().equals(actualScriptName)) { + return Optional.of(Response.builder() + .setContent(javascriptAsString) + .setMimeType(MimeType.JS) + .build()); + } + return Optional.empty(); + }); + } + /** * Add css to load in an existing html resource. *

@@ -79,9 +116,46 @@ public interface ResourceService { */ void addStylesToResource(String pluginName, String fileName, Position position, String... cssSources); + + /** + * Add javascript to load in a html resource. + *

+ * Requires PAGE_EXTENSION_RESOURCES_REGISTER_DIRECT_CUSTOMIZATION Capability. + * + * @param pluginName Name of your plugin (for config purposes) + * @param fileName Name of the .html file being modified + * @param position Where to place the script tag on the page. + * @param cssFileName Name of your css file (used on the page) + * @param cssAsString CSS file contents in UTF-8 + * @throws IllegalArgumentException If fileName is null, empty or does not end with .html + * @throws IllegalArgumentException If anything is empty or null + */ + default void addStyleToResource(String pluginName, String fileName, Position position, String cssFileName, String cssAsString) { + if (cssAsString == null || cssAsString.isEmpty()) { + throw new IllegalArgumentException("null or empty 'cssAsString' given."); + } + if (cssFileName == null || cssFileName.isEmpty()) { + throw new IllegalArgumentException("null or empty 'cssFileName' given."); + } + String actualCssFileName = cssFileName.endsWith(".js") ? cssFileName : cssFileName + ".js"; + + addStylesToResource(pluginName, fileName, position, pluginName + "/" + actualCssFileName); + ResolverService.getInstance().registerResolver(pluginName, pluginName + "/" + actualCssFileName, (NoAuthResolver) request -> { + if (request.getPath().asString().equals(actualCssFileName)) { + return Optional.of(Response.builder() + .setContent(cssAsString) + .setMimeType(MimeType.CSS) + .build()); + } + return Optional.empty(); + }); + } + enum Position { /** * Loaded before page contents. + *

+ * Recommended for loading style sheets. */ PRE_CONTENT, /** @@ -94,7 +168,11 @@ public interface ResourceService { * Loaded after script execution. *

* Recommended for loading data to custom structure on the page. + * + * @see Javascript API + * @deprecated No longer supported on React pages, use the javascript API in PRE_MAIN_SCRIPT. */ + @Deprecated AFTER_MAIN_SCRIPT; public String cleanName() { diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/export/ReactExporter.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/export/ReactExporter.java index 1f84da496..4ed952f01 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/export/ReactExporter.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/export/ReactExporter.java @@ -74,6 +74,7 @@ public class ReactExporter extends FileExporter { exportAsset(toDirectory, "logo512.png"); exportAsset(toDirectory, "manifest.json"); exportAsset(toDirectory, "robots.txt"); + exportAsset(toDirectory, "pageExtensionApi.js"); exportStaticBundle(toDirectory); exportLocaleJson(toDirectory.resolve("locale")); exportMetadataJson(toDirectory.resolve("metadata")); @@ -140,7 +141,9 @@ public class ReactExporter extends FileExporter { String contents = files.getResourceFromJar("web/index.html") .asString(); String basePath = getBasePath(); - contents = StringUtils.replace(contents, "/static", basePath + "/static"); + contents = StringUtils.replaceEach(contents, + new String[]{"/static", "/pageExtensionApi.js"}, + new String[]{basePath + "/static", basePath + "/pageExtensionApi.js"}); export(toDirectory.resolve("index.html"), contents); } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/pages/PageFactory.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/pages/PageFactory.java index 7992fdbe2..599591475 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/pages/PageFactory.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/pages/PageFactory.java @@ -109,8 +109,15 @@ public class PageFactory { } public Page reactPage() throws IOException { - // TODO use ResourceService to apply snippets to the React index.html - return new ReactPage(getBasePath(), getPublicHtmlOrJarResource("index.html")); + try { + String fileName = "index.html"; + WebResource resource = ResourceService.getInstance().getResource( + "Plan", fileName, () -> getPublicHtmlOrJarResource(fileName) + ); + return new ReactPage(getBasePath(), resource); + } catch (UncheckedIOException readFail) { + throw readFail.getCause(); + } } private String getBasePath() { @@ -258,14 +265,10 @@ public class PageFactory { } } - public WebResource getPublicHtmlOrJarResource(String resourceName) throws IOException { - try { - return publicHtmlFiles.get().findPublicHtmlResource(resourceName) - .orElseGet(() -> files.get().getResourceFromJar("web/" + resourceName)) - .asWebResource(); - } catch (UncheckedIOException readFail) { - throw readFail.getCause(); - } + public WebResource getPublicHtmlOrJarResource(String resourceName) { + return publicHtmlFiles.get().findPublicHtmlResource(resourceName) + .orElseGet(() -> files.get().getResourceFromJar("web/" + resourceName)) + .asWebResource(); } public Page loginPage() throws IOException { diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/pages/ReactPage.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/pages/ReactPage.java index bab588e31..db76e700b 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/pages/ReactPage.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/pages/ReactPage.java @@ -36,9 +36,10 @@ public class ReactPage implements Page { @Override public String toHtml() { - return StringUtils.replace( + return StringUtils.replaceEach( reactHtml.asString(), - "/static", basePath + "/static"); + new String[]{"/static", "/pageExtensionApi.js"}, + new String[]{basePath + "/static", basePath + "/pageExtensionApi.js"}); } @Override diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/web/ResourceSvc.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/web/ResourceSvc.java index 5e5afe5ad..98d990240 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/web/ResourceSvc.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/web/ResourceSvc.java @@ -29,6 +29,7 @@ import com.djrapitops.plan.utilities.logging.ErrorLogger; import net.playeranalytics.plugin.server.PluginLogger; import org.apache.commons.lang3.StringUtils; import org.apache.commons.text.TextStringBuilder; +import org.jetbrains.annotations.Nullable; import javax.inject.Inject; import javax.inject.Singleton; @@ -94,20 +95,8 @@ public class ResourceSvc implements ResourceService { } } - private WebResource applySnippets(String pluginName, @Untrusted String fileName, WebResource resource) { - Map byPosition = calculateSnippets(fileName); - if (byPosition.isEmpty()) return resource; - - String html = applySnippets(resource, byPosition); - return WebResource.create(html); - } - - private String applySnippets(WebResource resource, Map byPosition) { - String html = resource.asString(); - if (html == null) { - return "Error: Given resource did not support WebResource#asString method properly and returned 'null'"; - } - + @Nullable + private static String applyLegacy(Map byPosition, String html) { StringBuilder toHead = byPosition.get(Position.PRE_CONTENT); if (toHead != null) { html = StringUtils.replaceOnce(html, "", toHead.append("").toString()); @@ -130,6 +119,33 @@ public class ResourceSvc implements ResourceService { return html; } + private WebResource applySnippets(String pluginName, @Untrusted String fileName, WebResource resource) { + Map byPosition = calculateSnippets(fileName); + if (byPosition.isEmpty()) return resource; + + String html = applySnippets(fileName, resource, byPosition); + return WebResource.create(html); + } + + private String applySnippets(@Untrusted String fileName, WebResource resource, Map byPosition) { + String html = resource.asString(); + if (html == null) { + return "Error: Given resource did not support WebResource#asString method properly and returned 'null'"; + } + + if ("index.html".equals(fileName)) { + StringBuilder toHead = byPosition.get(Position.PRE_CONTENT); + if (toHead != null) { + html = StringUtils.replaceOnce(html, "", toHead.append("").toString()); + } + StringBuilder toBody = byPosition.get(Position.PRE_MAIN_SCRIPT); + html = StringUtils.replaceOnce(html, "", toBody.toString()); + return html; + } else { + return applyLegacy(byPosition, html); + } + } + private Map calculateSnippets(@Untrusted String fileName) { Map byPosition = new EnumMap<>(Position.class); for (Snippet snippet : snippets) { diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/ResponseResolver.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/ResponseResolver.java index 16c78e64a..b18a75367 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/ResponseResolver.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/ResponseResolver.java @@ -143,6 +143,7 @@ public class ResponseResolver { resolverService.registerResolver(plugin, "/manifest.json", fileResolver(() -> responseFactory.jsonFileResponse("manifest.json"))); resolverService.registerResolver(plugin, "/asset-manifest.json", fileResolver(() -> responseFactory.jsonFileResponse("asset-manifest.json"))); resolverService.registerResolver(plugin, "/favicon.ico", fileResolver(responseFactory::faviconResponse)); + resolverService.registerResolver(plugin, "/pageExtensionApi.js", fileResolver(() -> responseFactory.javaScriptResponse("pageExtensionApi.js"))); resolverService.registerResolver(plugin, "/query", queryPageResolver); resolverService.registerResolver(plugin, "/players", playersPageResolver); diff --git a/Plan/common/src/test/java/com/djrapitops/plan/delivery/webserver/AccessControlTest.java b/Plan/common/src/test/java/com/djrapitops/plan/delivery/webserver/AccessControlTest.java index f516066b6..926b4f6af 100644 --- a/Plan/common/src/test/java/com/djrapitops/plan/delivery/webserver/AccessControlTest.java +++ b/Plan/common/src/test/java/com/djrapitops/plan/delivery/webserver/AccessControlTest.java @@ -219,6 +219,7 @@ class AccessControlTest { "/v1/locale/NonexistingLanguage,404", "/docs/swagger.json,500", // swagger.json not available during tests "/docs,200", + "/pageExtensionApi.js,200", }) void levelZeroCanAccess(String resource, String expectedResponseCode) throws NoSuchAlgorithmException, IOException, KeyManagementException { int responseCode = access(resource, cookieLevel0); @@ -293,6 +294,7 @@ class AccessControlTest { "/v1/locale/NonexistingLanguage,404", "/docs/swagger.json,403", "/docs,403", + "/pageExtensionApi.js,200", }) void levelOneCanAccess(String resource, String expectedResponseCode) throws NoSuchAlgorithmException, IOException, KeyManagementException { int responseCode = access(resource, cookieLevel1); @@ -367,6 +369,7 @@ class AccessControlTest { "/v1/locale/NonexistingLanguage,404", "/docs/swagger.json,403", "/docs,403", + "/pageExtensionApi.js,200", }) void levelTwoCanAccess(String resource, String expectedResponseCode) throws NoSuchAlgorithmException, IOException, KeyManagementException { int responseCode = access(resource, cookieLevel2); @@ -439,6 +442,7 @@ class AccessControlTest { "/v1/locale/NonexistingLanguage,404", "/docs/swagger.json,403", "/docs,403", + "/pageExtensionApi.js,200", }) void levelHundredCanNotAccess(String resource, String expectedResponseCode) throws NoSuchAlgorithmException, IOException, KeyManagementException { int responseCode = access(resource, cookieLevel100); diff --git a/Plan/react/dashboard/public/index.html b/Plan/react/dashboard/public/index.html index 06737c7a4..64299a0b8 100644 --- a/Plan/react/dashboard/public/index.html +++ b/Plan/react/dashboard/public/index.html @@ -1,34 +1,36 @@ - - - - - + + + + + - - - + + - Plan | Player Analytics + Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will + work correctly both with client-side routing and a non-root public URL. + Learn how to configure a non-root public URL by running `npm run build`. + --> + Plan | Player Analytics - + + +

diff --git a/Plan/react/dashboard/public/pageExtensionApi.js b/Plan/react/dashboard/public/pageExtensionApi.js new file mode 100644 index 000000000..f57723ec4 --- /dev/null +++ b/Plan/react/dashboard/public/pageExtensionApi.js @@ -0,0 +1,69 @@ +/* +* Usage: +* - Look for any element with 'extendable' class name and use its id. +* +* // Html-string +* const render = async (context) => { +* // Any code that needs to run before the element is added to DOM +* return '

Hello world

'; +* } +* const unmount = async () => { +* // Any code that needs to run when the element is removed from DOM +* }; +* // 'server-overview-card' is the ID of the element +* global.pageExtensionApi.registerElement('beforeElement', 'server-overview-card', render, unmount); +* +* // HtmlElement +* global.pageExtensionApi.registerElement('afterElement', 'server-overview-card', () => { +* const para = document.createElement("p"); +* para.innerText = "Hello world"; +* return para; +* }, unmount); +*/ +class PageExtensionApi { + beforeElementRenders = []; + afterElementRenders = []; + + registerElement(position, id, renderCallback, unmountCallback) { + const renderers = position === 'beforeElement' ? this.beforeElementRenders : this.afterElementRenders; + renderers.push({id, renderCallback, unmountCallback}); + } + + onRender(id, position, context) { + const renderers = position === 'beforeElement' ? this.beforeElementRenders : this.afterElementRenders; + return Promise.all(renderers + .filter(renderer => renderer.id === id) + .filter(renderer => renderer.renderCallback) + .map(async renderer => { + try { + const rendered = await renderer.renderCallback(context); + if (rendered instanceof HTMLElement) { + return rendered.outerHTML; + } else { + return rendered; + } + } catch (e) { + console.warn("Error when rendering " + renderer + ": " + e); + return null; + } + }) + .filter(renderedElement => renderedElement)); + } + + onUnmount(className, position) { + const renderers = position === 'beforeElement' ? this.beforeElementRenders : this.afterElementRenders; + return renderers + .filter(renderer => renderer.className === className) + .forEach(renderer => { + try { + return renderer.unmountCallback() + } catch (e) { + console.warn("Error when unmounting " + renderer + ": " + e); + return null; + } + }); + } +} + +// Global +pageExtensionApi = new PageExtensionApi(); \ No newline at end of file diff --git a/Plan/react/dashboard/src/App.js b/Plan/react/dashboard/src/App.js index 005d096d4..43e40b987 100644 --- a/Plan/react/dashboard/src/App.js +++ b/Plan/react/dashboard/src/App.js @@ -14,6 +14,7 @@ import {AuthenticationContextProvider} from "./hooks/authenticationHook"; import {NavigationContextProvider} from "./hooks/navigationHook"; import MainPageRedirect from "./components/navigation/MainPageRedirect"; import {baseAddress, staticSite} from "./service/backendConfiguration"; +import {PageExtensionContextProvider} from "./hooks/pageExtensionHook"; const PlayerPage = React.lazy(() => import("./views/layout/PlayerPage")); const PlayerOverview = React.lazy(() => import("./views/player/PlayerOverview")); @@ -69,7 +70,9 @@ const ContextProviders = ({children}) => ( - {children} + + {children} + diff --git a/Plan/react/dashboard/src/components/cards/common/GeolocationsCard.js b/Plan/react/dashboard/src/components/cards/common/GeolocationsCard.js index a8d4ca020..617198582 100644 --- a/Plan/react/dashboard/src/components/cards/common/GeolocationsCard.js +++ b/Plan/react/dashboard/src/components/cards/common/GeolocationsCard.js @@ -1,11 +1,12 @@ import {useTranslation} from "react-i18next"; -import {Card, Col, Dropdown, Row} from "react-bootstrap"; +import {Card, Col, Dropdown} from "react-bootstrap"; import {FontAwesomeIcon as Fa} from "@fortawesome/react-fontawesome"; import React, {useState} from "react"; import {faExclamationTriangle, faGlobe, faLayerGroup} from "@fortawesome/free-solid-svg-icons"; import GeolocationBarGraph from "../../graphs/GeolocationBarGraph"; import GeolocationWorldMap, {ProjectionOptions} from "../../graphs/GeolocationWorldMap"; import {CardLoader} from "../../navigation/Loader"; +import ExtendableRow from "../../layout/extension/ExtendableRow"; const ProjectionDropDown = ({projection, setProjection}) => { const {t} = useTranslation(); @@ -54,7 +55,7 @@ const GeolocationsCard = ({data}) => { - + @@ -62,7 +63,7 @@ const GeolocationsCard = ({data}) => { - + ) diff --git a/Plan/react/dashboard/src/components/cards/server/values/ServerAsNumbersCard.js b/Plan/react/dashboard/src/components/cards/server/values/ServerAsNumbersCard.js index 0d378162b..3b83b2509 100644 --- a/Plan/react/dashboard/src/components/cards/server/values/ServerAsNumbersCard.js +++ b/Plan/react/dashboard/src/components/cards/server/values/ServerAsNumbersCard.js @@ -14,6 +14,7 @@ import Datapoint from "../../../Datapoint"; import {faCalendarCheck, faClock} from "@fortawesome/free-regular-svg-icons"; import React from "react"; import {CardLoader} from "../../../navigation/Loader"; +import ExtendableCardBody from "../../../layout/extension/ExtendableCardBody"; const ServerAsNumbersCard = ({data}) => { const {t} = useTranslation(); @@ -27,7 +28,7 @@ const ServerAsNumbersCard = ({data}) => { {data.player_kills ? t('html.label.serverAsNumberse') : t('html.label.networkAsNumbers')} - + @@ -71,7 +72,7 @@ const ServerAsNumbersCard = ({data}) => { - + ) } diff --git a/Plan/react/dashboard/src/components/layout/extension/ExtendableCardBody.js b/Plan/react/dashboard/src/components/layout/extension/ExtendableCardBody.js new file mode 100644 index 000000000..1aec07749 --- /dev/null +++ b/Plan/react/dashboard/src/components/layout/extension/ExtendableCardBody.js @@ -0,0 +1,38 @@ +import React, {useCallback, useEffect, useState} from 'react'; +import {Card} from "react-bootstrap"; +import {usePageExtension} from "../../../hooks/pageExtensionHook"; + +const ExtendableCardBody = ({id, className, children}) => { + const [elementsBefore, setElementsBefore] = useState([]); + const [elementsAfter, setElementsAfter] = useState([]); + const {onRender, onUnmount, context} = usePageExtension(); + + const render = useCallback(async () => { + if (!onRender) return; + setElementsBefore(await onRender(id, 'beforeElement', context)); + setElementsAfter(await onRender(id, 'afterElement', context)); + }, [setElementsBefore, setElementsAfter, id, onRender, context]) + useEffect(() => { + render(); + + return () => { + if (!onUnmount) return; + setElementsBefore([]) + setElementsAfter([]) + onUnmount(id, 'beforeElement'); + onUnmount(id, 'afterElement'); + } + }, [setElementsBefore, setElementsAfter, id, onUnmount, render]); + + return ( + <> +
+ + {children} + +
+ + ) +}; + +export default ExtendableCardBody \ No newline at end of file diff --git a/Plan/react/dashboard/src/components/layout/extension/ExtendableRow.js b/Plan/react/dashboard/src/components/layout/extension/ExtendableRow.js new file mode 100644 index 000000000..6aa007842 --- /dev/null +++ b/Plan/react/dashboard/src/components/layout/extension/ExtendableRow.js @@ -0,0 +1,38 @@ +import React, {useCallback, useEffect, useState} from 'react'; +import {Row} from "react-bootstrap"; +import {usePageExtension} from "../../../hooks/pageExtensionHook"; + +const ExtendableRow = ({id, className, children}) => { + const [elementsBefore, setElementsBefore] = useState([]); + const [elementsAfter, setElementsAfter] = useState([]); + const {onRender, onUnmount, context} = usePageExtension(); + + const render = useCallback(async () => { + if (!onRender) return; + setElementsBefore(await onRender(id, 'beforeElement', context)); + setElementsAfter(await onRender(id, 'afterElement', context)); + }, [setElementsBefore, setElementsAfter, id, onRender, context]) + useEffect(() => { + render(); + + return () => { + if (!onUnmount) return; + setElementsBefore([]) + setElementsAfter([]) + onUnmount(id, 'beforeElement'); + onUnmount(id, 'afterElement'); + } + }, [setElementsBefore, setElementsAfter, id, onUnmount, render]); + + return ( + <> +
+ + {children} + +
+ + ) +}; + +export default ExtendableRow \ No newline at end of file diff --git a/Plan/react/dashboard/src/components/navigation/MainPageRedirect.js b/Plan/react/dashboard/src/components/navigation/MainPageRedirect.js index ac9f25da9..7cd975f80 100644 --- a/Plan/react/dashboard/src/components/navigation/MainPageRedirect.js +++ b/Plan/react/dashboard/src/components/navigation/MainPageRedirect.js @@ -21,6 +21,7 @@ const RedirectPlaceholder = () => { if (dateDiff > 50) { return <> +

Redirecting..

@@ -32,7 +33,7 @@ const RedirectPlaceholder = () => {

If you are trying to set up a development environment, - change package.json "proxy" to your Plan webserver address. + change package.json "proxy" to address of your Plan webserver.

} else { - return

Redirecting..

+ return <> +

+

Redirecting..

+ } } diff --git a/Plan/react/dashboard/src/hooks/pageExtensionHook.js b/Plan/react/dashboard/src/hooks/pageExtensionHook.js new file mode 100644 index 000000000..a3ed6857b --- /dev/null +++ b/Plan/react/dashboard/src/hooks/pageExtensionHook.js @@ -0,0 +1,61 @@ +import {createContext, useCallback, useContext, useMemo} from "react"; +import {useAuth} from "./authenticationHook"; +import {useNavigation} from "./navigationHook"; +import {useTheme} from "./themeHook"; +import {useMetadata} from "./metadataHook"; +import {getColors, withReducedSaturation} from "../util/colors"; +import axios from "axios"; + +const PageExtensionContext = createContext({}); + +export const PageExtensionContextProvider = ({children}) => { + const onRender = useCallback(async (className, position) => { + return await global.pageExtensionApi.onRender(className, position); + }, []); + + const onUnmount = useCallback((className, position) => { + global.pageExtensionApi.onUnmount(className, position); + }, []); + + const authContext = useAuth(); + const navigationContext = useNavigation(); + const themeContext = useTheme(); + const metadata = useMetadata(); + const callContext = useMemo(() => { + return { + authentication: { + loggedIn: authContext.loggedIn, + user: authContext.user, + hasPermission: authContext.hasPermission + }, + navigation: { + currentTab: navigationContext.currentTab + }, + theme: { + currentThemeColor: themeContext.selectedColor, + colorMap: getColors(), + withReducedSaturation + }, + metadata: { + ...metadata + }, + utilities: { + axios + } + }; + }, [authContext, navigationContext, themeContext, metadata]); + + const sharedState = useMemo(() => { + return { + onRender, onUnmount, context: callContext + }; + }, [onRender, onUnmount, callContext]); + return ( + {children} + + ) +} + +export const usePageExtension = () => { + return useContext(PageExtensionContext); +} \ No newline at end of file diff --git a/Plan/react/dashboard/src/views/common/Geolocations.js b/Plan/react/dashboard/src/views/common/Geolocations.js index a6e3a65c6..e1f2cfca5 100644 --- a/Plan/react/dashboard/src/views/common/Geolocations.js +++ b/Plan/react/dashboard/src/views/common/Geolocations.js @@ -1,21 +1,22 @@ import React from 'react'; -import {Col, Row} from "react-bootstrap"; +import {Col} from "react-bootstrap"; import {ErrorViewCard} from "../ErrorView"; import GeolocationsCard from "../../components/cards/common/GeolocationsCard"; import PingTableCard from "../../components/cards/common/PingTableCard"; import LoadIn from "../../components/animation/LoadIn"; +import ExtendableRow from "../../components/layout/extension/ExtendableRow"; const Geolocations = ({className, geolocationData, pingData, geolocationError, pingError}) => { return (
- + {geolocationError ? : } {pingError ? : } - +
) diff --git a/Plan/react/dashboard/src/views/network/NetworkGeolocations.js b/Plan/react/dashboard/src/views/network/NetworkGeolocations.js index 0b6bb884a..2fec08487 100644 --- a/Plan/react/dashboard/src/views/network/NetworkGeolocations.js +++ b/Plan/react/dashboard/src/views/network/NetworkGeolocations.js @@ -9,7 +9,7 @@ const NetworkGeolocations = () => { const {data: pingData, loadingError: pingLoadingError} = useDataRequest(fetchNetworkPingTable, []); return ( - diff --git a/Plan/react/dashboard/src/views/network/NetworkJoinAddresses.js b/Plan/react/dashboard/src/views/network/NetworkJoinAddresses.js index c229d2832..7557964cd 100644 --- a/Plan/react/dashboard/src/views/network/NetworkJoinAddresses.js +++ b/Plan/react/dashboard/src/views/network/NetworkJoinAddresses.js @@ -1,21 +1,22 @@ import React from 'react'; -import {Col, Row} from "react-bootstrap"; +import {Col} from "react-bootstrap"; import JoinAddressGroupCard from "../../components/cards/server/graphs/JoinAddressGroupCard"; import JoinAddressGraphCard from "../../components/cards/server/graphs/JoinAddressGraphCard"; import LoadIn from "../../components/animation/LoadIn"; +import ExtendableRow from "../../components/layout/extension/ExtendableRow"; const NetworkJoinAddresses = () => { return ( -
- +
+ - +
) diff --git a/Plan/react/dashboard/src/views/network/NetworkOverview.js b/Plan/react/dashboard/src/views/network/NetworkOverview.js index 8e09b6b66..835d16ab9 100644 --- a/Plan/react/dashboard/src/views/network/NetworkOverview.js +++ b/Plan/react/dashboard/src/views/network/NetworkOverview.js @@ -2,7 +2,7 @@ import React from 'react'; import {useDataRequest} from "../../hooks/dataFetchHook"; import ErrorView from "../ErrorView"; import LoadIn from "../../components/animation/LoadIn"; -import {Card, Col, Row} from "react-bootstrap"; +import {Card, Col} from "react-bootstrap"; import ServerAsNumbersCard from "../../components/cards/server/values/ServerAsNumbersCard"; import ServerWeekComparisonCard from "../../components/cards/server/tables/ServerWeekComparisonCard"; import {fetchNetworkOverview} from "../../service/networkService"; @@ -11,6 +11,8 @@ import {CardLoader} from "../../components/navigation/Loader"; import Datapoint from "../../components/Datapoint"; import {faUsers} from "@fortawesome/free-solid-svg-icons"; import NetworkOnlineActivityGraphsCard from "../../components/cards/server/graphs/NetworkOnlineActivityGraphsCard"; +import ExtendableRow from "../../components/layout/extension/ExtendableRow"; +import ExtendableCardBody from "../../components/layout/extension/ExtendableCardBody"; const RecentPlayersCard = ({data}) => { @@ -25,7 +27,7 @@ const RecentPlayersCard = ({data}) => { {t('html.label.players')} - +

{t('html.label.last24hours')}

@@ -41,7 +43,7 @@ const RecentPlayersCard = ({data}) => { name={t('html.label.uniquePlayers')} value={data.unique_players_30d}/> -
+ ) } @@ -56,22 +58,22 @@ const NetworkOverview = () => { return (
- + - - + + - +
) diff --git a/Plan/react/dashboard/src/views/network/NetworkPerformance.js b/Plan/react/dashboard/src/views/network/NetworkPerformance.js index 4590598d5..7c6b13ff4 100644 --- a/Plan/react/dashboard/src/views/network/NetworkPerformance.js +++ b/Plan/react/dashboard/src/views/network/NetworkPerformance.js @@ -1,6 +1,6 @@ import React, {useCallback, useEffect, useState} from 'react'; import LoadIn from "../../components/animation/LoadIn"; -import {Card, Col, Row} from "react-bootstrap"; +import {Card, Col} from "react-bootstrap"; import {useMetadata} from "../../hooks/metadataHook"; import CardHeader from "../../components/cards/CardHeader"; import {faServer} from "@fortawesome/free-solid-svg-icons"; @@ -12,6 +12,7 @@ import PerformanceAsNumbersCard from "../../components/cards/server/tables/Perfo import {useNavigation} from "../../hooks/navigationHook"; import {mapPerformanceDataToSeries} from "../../util/graphs"; import PerformanceGraphsCard from "../../components/cards/network/PerformanceGraphsCard"; +import ExtendableRow from "../../components/layout/extension/ExtendableRow"; const NetworkPerformance = () => { const {t} = useTranslation(); @@ -87,13 +88,13 @@ const NetworkPerformance = () => { const isUpToDate = visualizedServers.every((s, i) => s === selectedOptions[i]); return ( -
- +
+ - - + + @@ -108,7 +109,7 @@ const NetworkPerformance = () => { - +
) diff --git a/Plan/react/dashboard/src/views/network/NetworkPlayerbaseOverview.js b/Plan/react/dashboard/src/views/network/NetworkPlayerbaseOverview.js index 48019cfaa..a65fb3f61 100644 --- a/Plan/react/dashboard/src/views/network/NetworkPlayerbaseOverview.js +++ b/Plan/react/dashboard/src/views/network/NetworkPlayerbaseOverview.js @@ -1,4 +1,4 @@ -import {Col, Row} from "react-bootstrap"; +import {Col} from "react-bootstrap"; import React from "react"; import PlayerbaseDevelopmentCard from "../../components/cards/server/graphs/PlayerbaseDevelopmentCard"; import CurrentPlayerbaseCard from "../../components/cards/server/graphs/CurrentPlayerbaseCard"; @@ -8,22 +8,23 @@ import PlayerbaseTrendsCard from "../../components/cards/server/tables/Playerbas import PlayerbaseInsightsCard from "../../components/cards/server/insights/PlayerbaseInsightsCard"; import LoadIn from "../../components/animation/LoadIn"; import {fetchNetworkPlayerbaseOverview} from "../../service/networkService"; +import ExtendableRow from "../../components/layout/extension/ExtendableRow"; const NetworkPlayerbaseOverview = () => { const {data, loadingError} = useDataRequest(fetchNetworkPlayerbaseOverview, []); return ( -
- +
+ - - + + {loadingError && } {!loadingError && <> @@ -33,7 +34,7 @@ const NetworkPlayerbaseOverview = () => { } - +
) diff --git a/Plan/react/dashboard/src/views/network/NetworkServers.js b/Plan/react/dashboard/src/views/network/NetworkServers.js index 64588f8ba..c694db4ec 100644 --- a/Plan/react/dashboard/src/views/network/NetworkServers.js +++ b/Plan/react/dashboard/src/views/network/NetworkServers.js @@ -1,11 +1,12 @@ import React, {useState} from 'react'; -import {Col, Row} from "react-bootstrap"; +import {Col} from "react-bootstrap"; import {useDataRequest} from "../../hooks/dataFetchHook"; import {fetchServersOverview} from "../../service/networkService"; import ErrorView from "../ErrorView"; import ServersTableCard from "../../components/cards/network/ServersTableCard"; import QuickViewGraphCard from "../../components/cards/network/QuickViewGraphCard"; import QuickViewDataCard from "../../components/cards/network/QuickViewDataCard"; +import ExtendableRow from "../../components/layout/extension/ExtendableRow"; const NetworkServers = () => { const [selectedServer, setSelectedServer] = useState(0); @@ -17,7 +18,7 @@ const NetworkServers = () => { } return ( - + setSelectedServer(index)}/> @@ -26,7 +27,7 @@ const NetworkServers = () => { {data?.servers.length && } {data?.servers.length && } - + ) }; diff --git a/Plan/react/dashboard/src/views/network/NetworkSessions.js b/Plan/react/dashboard/src/views/network/NetworkSessions.js index 036a400ea..4beb5e420 100644 --- a/Plan/react/dashboard/src/views/network/NetworkSessions.js +++ b/Plan/react/dashboard/src/views/network/NetworkSessions.js @@ -1,15 +1,16 @@ -import {Col, Row} from "react-bootstrap"; +import {Col} from "react-bootstrap"; import React from "react"; import ServerRecentSessionsCard from "../../components/cards/server/tables/ServerRecentSessionsCard"; import SessionInsightsCard from "../../components/cards/server/insights/SessionInsightsCard"; import LoadIn from "../../components/animation/LoadIn"; import ServerPieCard from "../../components/cards/common/ServerPieCard"; +import ExtendableRow from "../../components/layout/extension/ExtendableRow"; const NetworkSessions = () => { return ( -
- +
+ @@ -17,7 +18,7 @@ const NetworkSessions = () => { - +
) diff --git a/Plan/react/dashboard/src/views/player/PlayerOverview.js b/Plan/react/dashboard/src/views/player/PlayerOverview.js index d837405c6..ee96d459f 100644 --- a/Plan/react/dashboard/src/views/player/PlayerOverview.js +++ b/Plan/react/dashboard/src/views/player/PlayerOverview.js @@ -1,5 +1,5 @@ import React from "react"; -import {Card, Col, Row} from "react-bootstrap"; +import {Card, Col} from "react-bootstrap"; import {FontAwesomeIcon as Fa} from "@fortawesome/react-fontawesome"; import {faAddressBook, faCalendar, faCalendarCheck, faClock} from "@fortawesome/free-regular-svg-icons"; import { @@ -29,6 +29,8 @@ import {useTranslation} from "react-i18next"; import NicknamesCard from "../../components/cards/player/NicknamesCard"; import {TableRow} from "../../components/table/TableRow"; import LoadIn from "../../components/animation/LoadIn"; +import ExtendableCardBody from "../../components/layout/extension/ExtendableCardBody"; +import ExtendableRow from "../../components/layout/extension/ExtendableRow"; const PlayerOverviewCard = ({player}) => { const {t} = useTranslation(); @@ -42,8 +44,8 @@ const PlayerOverviewCard = ({player}) => { {player.info.name} - - + +

@@ -67,9 +69,9 @@ const PlayerOverviewCard = ({player}) => { className="col-green"/> {t('html.label.mobKills')}: {player.info.mob_kill_count}

{t('html.label.deaths')}: {player.info.death_count}

-
+
- + { name={t('html.label.lastSeen')} value={player.info.last_seen} boldTitle /> - -
+ + ); } @@ -222,8 +224,8 @@ const PlayerOverview = () => { return ( -
- +
+ @@ -233,7 +235,7 @@ const PlayerOverview = () => { - +
) diff --git a/Plan/react/dashboard/src/views/player/PlayerPvpPve.js b/Plan/react/dashboard/src/views/player/PlayerPvpPve.js index 95b26f6b0..548029442 100644 --- a/Plan/react/dashboard/src/views/player/PlayerPvpPve.js +++ b/Plan/react/dashboard/src/views/player/PlayerPvpPve.js @@ -1,5 +1,5 @@ import React from "react"; -import {Card, Col, Row} from "react-bootstrap"; +import {Card, Col} from "react-bootstrap"; import {FontAwesomeIcon as Fa} from "@fortawesome/react-fontawesome"; import {faLifeRing} from "@fortawesome/free-regular-svg-icons"; import {faKhanda, faSkull} from "@fortawesome/free-solid-svg-icons"; @@ -10,6 +10,7 @@ import {useTranslation} from "react-i18next"; import PvpPveAsNumbersCard from "../../components/cards/player/PvpPveAsNumbersCard"; import PvpKillsTableCard from "../../components/cards/common/PvpKillsTableCard"; import LoadIn from "../../components/animation/LoadIn"; +import ExtendableRow from "../../components/layout/extension/ExtendableRow"; const InsightsCard = ({player}) => { const {t} = useTranslation(); @@ -50,23 +51,23 @@ const PlayerPvpPve = () => { const {player} = usePlayer(); return ( -
- +
+ - - + + - +
) diff --git a/Plan/react/dashboard/src/views/player/PlayerServers.js b/Plan/react/dashboard/src/views/player/PlayerServers.js index 551c4ec65..38031f0d6 100644 --- a/Plan/react/dashboard/src/views/player/PlayerServers.js +++ b/Plan/react/dashboard/src/views/player/PlayerServers.js @@ -1,5 +1,5 @@ import React from "react"; -import {Card, Col, Row} from "react-bootstrap"; +import {Card, Col} from "react-bootstrap"; import {FontAwesomeIcon as Fa} from "@fortawesome/react-fontawesome"; import {faHandPointer} from "@fortawesome/free-regular-svg-icons"; import Scrollable from "../../components/Scrollable"; @@ -10,6 +10,7 @@ import {usePlayer} from "../layout/PlayerPage"; import {useTranslation} from "react-i18next"; import PlayerPingGraph from "../../components/graphs/PlayerPingGraph"; import LoadIn from "../../components/animation/LoadIn"; +import ExtendableRow from "../../components/layout/extension/ExtendableRow"; const PingGraphCard = ({player}) => { const {t} = useTranslation(); @@ -70,20 +71,20 @@ const PlayerServers = () => { const {player} = usePlayer(); return ( -
- +
+ - - + + - +
) diff --git a/Plan/react/dashboard/src/views/player/PlayerSessions.js b/Plan/react/dashboard/src/views/player/PlayerSessions.js index 91180cb80..8397248d0 100644 --- a/Plan/react/dashboard/src/views/player/PlayerSessions.js +++ b/Plan/react/dashboard/src/views/player/PlayerSessions.js @@ -1,5 +1,5 @@ import React from "react"; -import {Card, Col, Row} from "react-bootstrap"; +import {Card, Col} from "react-bootstrap"; import {FontAwesomeIcon as Fa} from "@fortawesome/react-fontawesome"; import {faCalendarAlt} from "@fortawesome/free-regular-svg-icons"; import PlayerSessionCalendar from "../../components/calendar/PlayerSessionCalendar"; @@ -8,6 +8,7 @@ import {useTranslation} from "react-i18next"; import PlayerWorldPieCard from "../../components/cards/player/PlayerWorldPieCard"; import PlayerRecentSessionsCard from "../../components/cards/player/PlayerRecentSessionsCard"; import LoadIn from "../../components/animation/LoadIn"; +import ExtendableRow from "../../components/layout/extension/ExtendableRow"; const SessionCalendarCard = ({player}) => { const {t} = useTranslation(); @@ -27,8 +28,8 @@ const PlayerSessions = () => { const {player} = usePlayer(); return ( -
- +
+ @@ -36,7 +37,7 @@ const PlayerSessions = () => { - +
) diff --git a/Plan/react/dashboard/src/views/players/AllPlayers.js b/Plan/react/dashboard/src/views/players/AllPlayers.js index ae2ac0114..0fd00fd23 100644 --- a/Plan/react/dashboard/src/views/players/AllPlayers.js +++ b/Plan/react/dashboard/src/views/players/AllPlayers.js @@ -2,10 +2,11 @@ import React from 'react'; import {useDataRequest} from "../../hooks/dataFetchHook"; import {fetchPlayers} from "../../service/serverService"; import ErrorView from "../ErrorView"; -import {Col, Row} from "react-bootstrap"; +import {Col} from "react-bootstrap"; import PlayerListCard from "../../components/cards/common/PlayerListCard"; import LoadIn from "../../components/animation/LoadIn"; import {CardLoader} from "../../components/navigation/Loader"; +import ExtendableRow from "../../components/layout/extension/ExtendableRow"; const AllPlayers = () => { const {data, loadingError} = useDataRequest(fetchPlayers, [null]); @@ -14,11 +15,11 @@ const AllPlayers = () => { return ( - + {data ? : } - + ) }; diff --git a/Plan/react/dashboard/src/views/server/OnlineActivity.js b/Plan/react/dashboard/src/views/server/OnlineActivity.js index 0dfca9d2a..0f85ab127 100644 --- a/Plan/react/dashboard/src/views/server/OnlineActivity.js +++ b/Plan/react/dashboard/src/views/server/OnlineActivity.js @@ -1,5 +1,5 @@ import React from "react"; -import {Col, Row} from "react-bootstrap"; +import {Col} from "react-bootstrap"; import OnlineActivityGraphsCard from "../../components/cards/server/graphs/OnlineActivityGraphsCard"; import OnlineActivityAsNumbersCard from "../../components/cards/server/tables/OnlineActivityAsNumbersCard"; import {useParams} from "react-router-dom"; @@ -8,6 +8,7 @@ import {fetchOnlineActivityOverview} from "../../service/serverService"; import ErrorView from "../ErrorView"; import OnlineActivityInsightsCard from "../../components/cards/server/insights/OnlineActivityInsightsCard"; import LoadIn from "../../components/animation/LoadIn"; +import ExtendableRow from "../../components/layout/extension/ExtendableRow"; const OnlineActivity = () => { const {identifier} = useParams(); @@ -18,20 +19,20 @@ const OnlineActivity = () => { return ( -
- +
+ - - + + - +
) diff --git a/Plan/react/dashboard/src/views/server/PlayerbaseOverview.js b/Plan/react/dashboard/src/views/server/PlayerbaseOverview.js index 83731980d..58a425a81 100644 --- a/Plan/react/dashboard/src/views/server/PlayerbaseOverview.js +++ b/Plan/react/dashboard/src/views/server/PlayerbaseOverview.js @@ -1,4 +1,4 @@ -import {Col, Row} from "react-bootstrap"; +import {Col} from "react-bootstrap"; import React from "react"; import PlayerbaseDevelopmentCard from "../../components/cards/server/graphs/PlayerbaseDevelopmentCard"; import CurrentPlayerbaseCard from "../../components/cards/server/graphs/CurrentPlayerbaseCard"; @@ -9,6 +9,7 @@ import {ErrorViewCard} from "../ErrorView"; import PlayerbaseTrendsCard from "../../components/cards/server/tables/PlayerbaseTrendsCard"; import PlayerbaseInsightsCard from "../../components/cards/server/insights/PlayerbaseInsightsCard"; import LoadIn from "../../components/animation/LoadIn"; +import ExtendableRow from "../../components/layout/extension/ExtendableRow"; const PlayerbaseOverview = () => { const {identifier} = useParams(); @@ -17,16 +18,16 @@ const PlayerbaseOverview = () => { return ( -
- +
+ - - + + {loadingError && } {!loadingError && <> @@ -36,7 +37,7 @@ const PlayerbaseOverview = () => { } - +
) diff --git a/Plan/react/dashboard/src/views/server/ServerGeolocations.js b/Plan/react/dashboard/src/views/server/ServerGeolocations.js index 8656bc643..6f336027e 100644 --- a/Plan/react/dashboard/src/views/server/ServerGeolocations.js +++ b/Plan/react/dashboard/src/views/server/ServerGeolocations.js @@ -11,7 +11,7 @@ const ServerGeolocations = () => { const {pingData, pingLoadingError} = useDataRequest(fetchPingTable, [identifier]); return ( - diff --git a/Plan/react/dashboard/src/views/server/ServerJoinAddresses.js b/Plan/react/dashboard/src/views/server/ServerJoinAddresses.js index 316c5443f..58bb5c139 100644 --- a/Plan/react/dashboard/src/views/server/ServerJoinAddresses.js +++ b/Plan/react/dashboard/src/views/server/ServerJoinAddresses.js @@ -1,23 +1,24 @@ import React from 'react'; -import {Col, Row} from "react-bootstrap"; +import {Col} from "react-bootstrap"; import JoinAddressGroupCard from "../../components/cards/server/graphs/JoinAddressGroupCard"; import JoinAddressGraphCard from "../../components/cards/server/graphs/JoinAddressGraphCard"; import {useParams} from "react-router-dom"; import LoadIn from "../../components/animation/LoadIn"; +import ExtendableRow from "../../components/layout/extension/ExtendableRow"; const ServerJoinAddresses = () => { const {identifier} = useParams(); return ( -
- +
+ - +
) diff --git a/Plan/react/dashboard/src/views/server/ServerOverview.js b/Plan/react/dashboard/src/views/server/ServerOverview.js index 8f6b44a5a..b465e0501 100644 --- a/Plan/react/dashboard/src/views/server/ServerOverview.js +++ b/Plan/react/dashboard/src/views/server/ServerOverview.js @@ -1,6 +1,6 @@ import React from "react"; -import {Card, Col, Row} from "react-bootstrap"; +import {Card, Col} from "react-bootstrap"; import {faExclamationCircle, faPowerOff, faTachometerAlt, faUser, faUsers} from "@fortawesome/free-solid-svg-icons"; import Datapoint from "../../components/Datapoint"; import {useTranslation} from "react-i18next"; @@ -13,6 +13,7 @@ import ServerAsNumbersCard from "../../components/cards/server/values/ServerAsNu import ServerWeekComparisonCard from "../../components/cards/server/tables/ServerWeekComparisonCard"; import LoadIn from "../../components/animation/LoadIn"; import {CardLoader} from "../../components/navigation/Loader"; +import ExtendableRow from "../../components/layout/extension/ExtendableRow"; const Last7DaysCard = ({data}) => { const {t} = useTranslation(); @@ -68,23 +69,23 @@ const ServerOverview = () => { return ( -
- +
+ - - + + - +
) diff --git a/Plan/react/dashboard/src/views/server/ServerPerformance.js b/Plan/react/dashboard/src/views/server/ServerPerformance.js index 065b29d97..51c1a8dcc 100644 --- a/Plan/react/dashboard/src/views/server/ServerPerformance.js +++ b/Plan/react/dashboard/src/views/server/ServerPerformance.js @@ -1,6 +1,6 @@ import React from 'react'; import LoadIn from "../../components/animation/LoadIn"; -import {Col, Row} from "react-bootstrap"; +import {Col} from "react-bootstrap"; import {useParams} from "react-router-dom"; import {useDataRequest} from "../../hooks/dataFetchHook"; import {fetchPerformanceOverview} from "../../service/serverService"; @@ -8,6 +8,7 @@ import PerformanceGraphsCard from "../../components/cards/server/graphs/Performa import PerformanceInsightsCard from "../../components/cards/server/insights/PerformanceInsightsCard"; import {ErrorViewCard} from "../ErrorView"; import PerformanceAsNumbersCard from "../../components/cards/server/tables/PerformanceAsNumbersCard"; +import ExtendableRow from "../../components/layout/extension/ExtendableRow"; const ServerPerformance = () => { const {identifier} = useParams(); @@ -16,13 +17,13 @@ const ServerPerformance = () => { return ( -
- +
+ - - + + {loadingError ? : } @@ -31,7 +32,7 @@ const ServerPerformance = () => { {loadingError ? : } - +
) diff --git a/Plan/react/dashboard/src/views/server/ServerPlayers.js b/Plan/react/dashboard/src/views/server/ServerPlayers.js index 930f1f61b..df2b0010d 100644 --- a/Plan/react/dashboard/src/views/server/ServerPlayers.js +++ b/Plan/react/dashboard/src/views/server/ServerPlayers.js @@ -3,9 +3,10 @@ import {useDataRequest} from "../../hooks/dataFetchHook"; import {useParams} from "react-router-dom"; import {fetchPlayers} from "../../service/serverService"; import ErrorView from "../ErrorView"; -import {Col, Row} from "react-bootstrap"; +import {Col} from "react-bootstrap"; import PlayerListCard from "../../components/cards/common/PlayerListCard"; import LoadIn from "../../components/animation/LoadIn"; +import ExtendableRow from "../../components/layout/extension/ExtendableRow"; const ServerPlayers = () => { const {identifier} = useParams(); @@ -16,12 +17,12 @@ const ServerPlayers = () => { return ( -
- +
+ - +
) diff --git a/Plan/react/dashboard/src/views/server/ServerPvpPve.js b/Plan/react/dashboard/src/views/server/ServerPvpPve.js index 34ba39d52..9168a56fe 100644 --- a/Plan/react/dashboard/src/views/server/ServerPvpPve.js +++ b/Plan/react/dashboard/src/views/server/ServerPvpPve.js @@ -1,6 +1,6 @@ import React from "react"; import PvpPveAsNumbersCard from "../../components/cards/server/tables/PvpPveAsNumbersCard"; -import {Col, Row} from "react-bootstrap"; +import {Col} from "react-bootstrap"; import PvpKillsTableCard from "../../components/cards/common/PvpKillsTableCard"; import PvpPveInsightsCard from "../../components/cards/server/insights/PvpPveInsightsCard"; import {useParams} from "react-router-dom"; @@ -8,6 +8,7 @@ import {useDataRequest} from "../../hooks/dataFetchHook"; import {fetchKills, fetchPvpPve} from "../../service/serverService"; import ErrorView from "../ErrorView"; import LoadIn from "../../components/animation/LoadIn"; +import ExtendableRow from "../../components/layout/extension/ExtendableRow"; const ServerPvpPve = () => { const {identifier} = useParams(); @@ -20,20 +21,20 @@ const ServerPvpPve = () => { return ( -
- +
+ - - + + - +
) diff --git a/Plan/react/dashboard/src/views/server/ServerSessions.js b/Plan/react/dashboard/src/views/server/ServerSessions.js index 5fd9ed6d6..4641d4b5f 100644 --- a/Plan/react/dashboard/src/views/server/ServerSessions.js +++ b/Plan/react/dashboard/src/views/server/ServerSessions.js @@ -1,17 +1,18 @@ -import {Col, Row} from "react-bootstrap"; +import {Col} from "react-bootstrap"; import React from "react"; import ServerWorldPieCard from "../../components/cards/server/graphs/ServerWorldPieCard"; import ServerRecentSessionsCard from "../../components/cards/server/tables/ServerRecentSessionsCard"; import SessionInsightsCard from "../../components/cards/server/insights/SessionInsightsCard"; import LoadIn from "../../components/animation/LoadIn"; import {useParams} from "react-router-dom"; +import ExtendableRow from "../../components/layout/extension/ExtendableRow"; const ServerSessions = () => { const {identifier} = useParams(); return ( -
- +
+ @@ -19,7 +20,7 @@ const ServerSessions = () => { - +
)