diff --git a/src/app/App.tsx b/src/app/App.tsx index 6e15a95..ab622ce 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -7,7 +7,7 @@ import { ReactComponent as MoonIcon } from "../assets/moon.svg"; import { ReactComponent as PadlockUnlockedIcon } from "../assets/padlock-unlocked.svg"; import { ReactComponent as PadlockIcon } from "../assets/padlock.svg"; import { ReactComponent as PeersIcon } from "../assets/peers.svg"; -import { ReactComponent as SettingsIcon } from "../assets/settings.svg"; +import { ReactComponent as PluginsIcon } from "../assets/plugins.svg"; import { ReactComponent as SunIcon } from "../assets/sun.svg"; import { ReactComponent as VisualizerIcon } from "../assets/visualizer.svg"; import { ServiceFactory } from "../factories/serviceFactory"; @@ -44,9 +44,9 @@ import Login from "./routes/Login"; import Peer from "./routes/Peer"; import { PeerRouteProps } from "./routes/PeerRouteProps"; import Peers from "./routes/Peers"; +import Plugins from "./routes/Plugins"; import Search from "./routes/Search"; import { SearchRouteProps } from "./routes/SearchRouteProps"; -import Settings from "./routes/Settings"; import Unavailable from "./routes/Unavailable"; import Visualizer from "./routes/Visualizer"; @@ -267,9 +267,9 @@ class App extends AsyncComponent { route: "/visualizer" }, { - label: "Settings", - icon: , - route: "/settings", + label: "Plugins", + icon: , + route: "/plugins", hidden: !this.state.isLoggedIn }, { @@ -414,8 +414,8 @@ class App extends AsyncComponent { component={(props: RouteComponentProps) => ()} /> ()} + path="/plugins" + component={() => ()} /> { * @returns The plugin details if available. */ public static pluginDetails(): { - icon: ReactNode; title: string; description: string; settings: ReactNode; } | undefined { if (Participation._isAvailable) { return { - icon: , title: Participation.PLUGIN_TITLE, description: Participation.PLUGIN_DESCRIPTION, settings: @@ -139,21 +136,18 @@ class Participation extends AsyncComponent { return (
-
-

{Participation.PLUGIN_TITLE}

-
- -
+ > + Add Event +
{this.state.eventIds.length === 0 && ( diff --git a/src/app/components/plugins/Spammer.tsx b/src/app/components/plugins/Spammer.tsx index ea5173f..6a2d52c 100644 --- a/src/app/components/plugins/Spammer.tsx +++ b/src/app/components/plugins/Spammer.tsx @@ -1,5 +1,4 @@ import React, { ReactNode } from "react"; -import { ReactComponent as SpammerIcon } from "../../../assets/plugins/spammer.svg"; import { ServiceFactory } from "../../../factories/serviceFactory"; import { ISpammerSettings } from "../../../models/plugins/ISpammerSettings"; import { AuthService } from "../../../services/authService"; @@ -81,14 +80,12 @@ class Spammer extends AsyncComponent { * @returns The plugin details if available. */ public static pluginDetails(): { - icon: ReactNode; title: string; description: string; settings: ReactNode; } | undefined { if (Spammer._isAvailable) { return { - icon: , title: Spammer.PLUGIN_TITLE, description: Spammer.PLUGIN_DESCRIPTION, settings: diff --git a/src/app/routes/Settings.scss b/src/app/routes/Plugins.scss similarity index 83% rename from src/app/routes/Settings.scss rename to src/app/routes/Plugins.scss index 6144956..1f6f07d 100644 --- a/src/app/routes/Settings.scss +++ b/src/app/routes/Plugins.scss @@ -2,7 +2,7 @@ @import '../../scss/fonts'; @import '../../scss/media-queries'; -.settings { +.plugins { display: flex; flex: 1; justify-content: center; @@ -37,4 +37,13 @@ } } } + + a { + color: var(--accent-primary); + text-decoration: none; + + &:hover { + text-decoration: underline; + } + } } diff --git a/src/app/routes/Plugins.tsx b/src/app/routes/Plugins.tsx new file mode 100644 index 0000000..925ecad --- /dev/null +++ b/src/app/routes/Plugins.tsx @@ -0,0 +1,108 @@ +import React, { ReactNode } from "react"; +import { ServiceFactory } from "../../factories/serviceFactory"; +import { AuthService } from "../../services/authService"; +import AsyncComponent from "../components/layout/AsyncComponent"; +import TabPanel from "../components/layout/TabPanel"; +import Participation from "../components/plugins/Participation"; +import Spammer from "../components/plugins/Spammer"; +import "./Plugins.scss"; +import { PluginsState } from "./PluginsState"; + +/** + * Plugins panel. + */ +class Plugins extends AsyncComponent { + /** + * The auth service. + */ + private readonly _authService: AuthService; + + /** + * Create a new instance of Plugins. + * @param props The props. + */ + constructor(props: unknown) { + super(props); + + this._authService = ServiceFactory.get("auth"); + + this.state = { + plugins: [] + }; + } + + /** + * The component did mount. + */ + public async componentDidMount(): Promise { + super.componentDidMount(); + + const plugins = []; + + if (this._authService.isLoggedIn()) { + const pluginDetailsSpammer = Spammer.pluginDetails(); + if (pluginDetailsSpammer) { + plugins.push(pluginDetailsSpammer); + } + const pluginDetailsParticipation = Participation.pluginDetails(); + if (pluginDetailsParticipation) { + plugins.push(pluginDetailsParticipation); + } + } + + if (plugins.length > 0) { + this.setState({ + activeTab: plugins[0].title + }); + } + + this.setState({ + plugins + }); + } + + /** + * Render the component. + * @returns The node to render. + */ + public render(): ReactNode { + return ( +
+
+ {this.state.plugins.length === 0 && ( +

+ No plugins enabled which are supported by the dashboard.
+ More information about managing plugins can be found on the + {" "} + + Hornet Developer Documentation. + +

+ )} + p.title)} + activeTab={this.state.activeTab ? this.state.activeTab : ""} + onTabChanged={activeTab => { + this.setState({ + activeTab + }); + }} + > + {this.state.plugins.map((p, idx) => ( +
+ {p.settings} +
+ ))} + +
+
+
+ ); + } +} + +export default Plugins; diff --git a/src/app/routes/PluginsState.ts b/src/app/routes/PluginsState.ts new file mode 100644 index 0000000..204c4be --- /dev/null +++ b/src/app/routes/PluginsState.ts @@ -0,0 +1,18 @@ +import { ReactNode } from "react"; + +export interface PluginsState { + + /** + * The active tab. + */ + activeTab?: string; + + /** + * Plugins. + */ + plugins: { + title: string; + description: string; + settings: ReactNode; + }[]; +} diff --git a/src/app/routes/Settings.tsx b/src/app/routes/Settings.tsx deleted file mode 100644 index e1d229a..0000000 --- a/src/app/routes/Settings.tsx +++ /dev/null @@ -1,165 +0,0 @@ -import React, { ReactNode } from "react"; -import { ReactComponent as ChevronDownIcon } from "../../assets/chevron-down.svg"; -import { ReactComponent as EllipsisIcon } from "../../assets/ellipsis.svg"; -import { ServiceFactory } from "../../factories/serviceFactory"; -import { AuthService } from "../../services/authService"; -import { ThemeService } from "../../services/themeService"; -import AsyncComponent from "../components/layout/AsyncComponent"; -import TabPanel from "../components/layout/TabPanel"; -import Participation from "../components/plugins/Participation"; -import Spammer from "../components/plugins/Spammer"; -import "./Settings.scss"; -import { SettingsState } from "./SettingsState"; - -/** - * Settings panel. - */ -class Settings extends AsyncComponent { - /** - * The theme service. - */ - private readonly _themeService: ThemeService; - - /** - * The auth service. - */ - private readonly _authService: AuthService; - - /** - * The standard sections that are not plugins. - */ - private readonly _standardSections: string[]; - - /** - * Create a new instance of Settings. - * @param props The props. - */ - constructor(props: unknown) { - super(props); - - this._themeService = ServiceFactory.get("theme"); - this._authService = ServiceFactory.get("auth"); - this._standardSections = ["General"]; - - this.state = { - theme: this._themeService.get(), - sections: this._standardSections, - activeSection: this._standardSections[0], - plugins: [] - }; - } - - /** - * The component did mount. - */ - public async componentDidMount(): Promise { - super.componentDidMount(); - - const plugins = []; - - if (this._authService.isLoggedIn()) { - const pluginDetailsSpammer = Spammer.pluginDetails(); - if (pluginDetailsSpammer) { - plugins.push(pluginDetailsSpammer); - } - const pluginDetailsParticipation = Participation.pluginDetails(); - if (pluginDetailsParticipation) { - plugins.push(pluginDetailsParticipation); - } - } - - if (plugins.length > 0) { - this._standardSections.push("Plugins"); - } - - this.setState({ - sections: this._standardSections, - plugins - }); - } - - /** - * Render the component. - * @returns The node to render. - */ - public render(): ReactNode { - return ( -
-
- { - this.setState({ - activeSection: activeTab - }); - }} - > -
-

General

-
- Theme -
-
-
- - -
-
-
-
- {this.state.plugins.map((p, idx) => ( -
-
-
- {p.icon} -

{p.title}

-
- -
-

{p.description}

-
- ))} -
- {this.state.plugins.map((p, idx) => ( -
- {p.settings} -
- ))} -
-
-
- ); - } - - /** - * Open new plugin tab - * @param title The title of the plugin. - */ - private addTab(title: string): void { - if (!this.state.sections.includes(title)) { - this.setState({ - sections: [...this.state.sections, title] - }); - } - this.setState({ - activeSection: title - }); - } -} - -export default Settings; diff --git a/src/app/routes/SettingsState.ts b/src/app/routes/SettingsState.ts deleted file mode 100644 index a9c02bb..0000000 --- a/src/app/routes/SettingsState.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { ReactNode } from "react"; - -export interface SettingsState { - /** - * The current theme. - */ - theme: string; - - /** - * Sections. - */ - sections: string[]; - - /** - * The active section. - */ - activeSection: string; - - /** - * Plugins. - */ - plugins: { - icon: ReactNode; - title: string; - description: string; - settings: ReactNode; - }[]; -} diff --git a/src/assets/plugins/spammer.svg b/src/assets/plugins.svg similarity index 87% rename from src/assets/plugins/spammer.svg rename to src/assets/plugins.svg index 6ac6f3d..de5b0cc 100644 --- a/src/assets/plugins/spammer.svg +++ b/src/assets/plugins.svg @@ -1,4 +1,3 @@ - - - + + diff --git a/src/scss/layout.scss b/src/scss/layout.scss index 20a1d21..e8adbf6 100644 --- a/src/scss/layout.scss +++ b/src/scss/layout.scss @@ -13,7 +13,7 @@ align-items: center; } - &.right { + &.end { justify-content: flex-end; }