From 11757325eeb485844e0b090791a7a0261800763d Mon Sep 17 00:00:00 2001 From: Nick Grato Date: Wed, 22 Feb 2023 16:44:26 -0800 Subject: [PATCH 01/19] staching progress --- admin/.prettierrc.json | 6 + admin/src/react-components/system-editor.js | 370 ---------------- admin/src/react-components/system-editor.tsx | 427 +++++++++++++++++++ admin/types.d.ts | 43 ++ 4 files changed, 476 insertions(+), 370 deletions(-) create mode 100644 admin/.prettierrc.json delete mode 100644 admin/src/react-components/system-editor.js create mode 100644 admin/src/react-components/system-editor.tsx create mode 100644 admin/types.d.ts diff --git a/admin/.prettierrc.json b/admin/.prettierrc.json new file mode 100644 index 0000000000..47e113111d --- /dev/null +++ b/admin/.prettierrc.json @@ -0,0 +1,6 @@ +{ + "singleQuote": true, + "jsxSingleQuote": false, + "tabWidth": 2, + "semi": true +} diff --git a/admin/src/react-components/system-editor.js b/admin/src/react-components/system-editor.js deleted file mode 100644 index c1f4976df9..0000000000 --- a/admin/src/react-components/system-editor.js +++ /dev/null @@ -1,370 +0,0 @@ -/* eslint-disable react/prop-types */ -/* eslint-disable @calm/react-intl/missing-formatted-message*/ -import React, { Component } from "react"; -import { Title } from "react-admin"; -import Card from "@material-ui/core/Card"; -import CardContent from "@material-ui/core/CardContent"; -import Typography from "@material-ui/core/Typography"; -import { withStyles } from "@material-ui/core/styles"; -import List from "@material-ui/core/List"; -import ListItem from "@material-ui/core/ListItem"; -import ListItemText from "@material-ui/core/ListItemText"; -import ListItemIcon from "@material-ui/core/ListItemIcon"; - -import LockIcon from "@material-ui/icons/Lock"; -import PaletteIcon from "@material-ui/icons/Palette"; -import VpnKeyIcon from "@material-ui/icons/VpnKey"; -import CodeIcon from "@material-ui/icons/Code"; -import DeveloperModeIcon from "@material-ui/icons/DeveloperMode"; - -import Warning from "@material-ui/icons/Warning"; -import Info from "@material-ui/icons/Info"; -import { fetchReticulumAuthenticated } from "hubs/src/utils/phoenix-utils"; -import withCommonStyles from "../utils/with-common-styles"; -import { getAdminInfo, getEditableConfig } from "../utils/ita"; -import configs from "../utils/configs"; - -// Send quota to use as heuristic for checking if in SES sandbox -// https://forums.aws.amazon.com/thread.jspa?threadID=61090 -const MAX_AWS_SES_QUOTA_FOR_SANDBOX = 200; - -const styles = withCommonStyles(() => ({})); - -class SystemEditorComponent extends Component { - state = { - reticulumMeta: {} - }; - - async componentDidMount() { - const adminInfo = await getAdminInfo(); - const retConfig = await getEditableConfig("reticulum"); - - this.setState({ adminInfo, retConfig }); - this.updateReticulumMeta(); - } - - async updateReticulumMeta() { - const reticulumMeta = await fetchReticulumAuthenticated(`/api/v1/meta?include_repo`); - this.setState({ reticulumMeta }); - } - - render() { - const needsAvatars = this.state.reticulumMeta.repo && !this.state.reticulumMeta.repo.avatar_listings.any; - const needsScenes = this.state.reticulumMeta.repo && !this.state.reticulumMeta.repo.scene_listings.any; - const exceededStorageQuota = this.state.reticulumMeta.repo && !this.state.reticulumMeta.repo.storage.in_quota; - - const isInSESSandbox = - this.state.adminInfo && - this.state.adminInfo.using_ses && - this.state.adminInfo.ses_max_24_hour_send <= MAX_AWS_SES_QUOTA_FOR_SANDBOX; - - const isUsingCloudflare = - this.state.adminInfo && - this.state.retConfig && - this.state.retConfig.phx && - this.state.retConfig.phx.cors_proxy_url_host === `cors-proxy.${this.state.adminInfo.worker_domain}`; - - return ( - <> - - - <CardContent className={this.props.classes.info}> - <Typography variant="title" gutterBottom> - 🐣 Hubs Cloud is live - </Typography> - <Typography variant="body1" gutterBottom> - Need help? Check out the{" "} - <a - href="https://hubs.mozilla.com/docs/hubs-cloud-getting-started.html" - target="_blank" - rel="noopener noreferrer" - > - Getting Started - </a>{" "} - guide. - </Typography> - <Typography variant="body1" gutterBottom> - Hubs Cloud updates automatically, see the{" "} - <a - href="https://github.com/mozilla/hubs-cloud/blob/master/CHANGELOG.md" - target="_blank" - rel="noopener noreferrer" - > - Hubs Cloud Changelog - </a> - . - </Typography> - <Typography variant="body1" gutterBottom> - <b>Questions or issues?</b> Visit the{" "} - <a href="https://hubs.mozilla.com/docs/welcome.html" target="_blank" rel="noopener noreferrer"> - Hubs Docs - </a>{" "} - or create a{" "} - <a href="https://github.com/mozilla/hubs/discussions" target="_blank" rel="noopener noreferrer"> - question in discussions - </a>{" "} - or{" "} - <a href="https://github.com/mozilla/hubs" target="_blank" rel="noopener noreferrer"> - issue in github - </a> - . - </Typography> - {this.state.reticulumMeta && - this.state.adminInfo && - (needsAvatars || needsScenes || isInSESSandbox || exceededStorageQuota) && ( - <List> - {isInSESSandbox && ( - <ListItem> - <ListItemIcon className={this.props.classes.warningIcon}> - <Warning /> - </ListItemIcon> - <ListItemText - inset - primary={ - <span> - Your AWS account is in the{" "} - <a - href="https://docs.aws.amazon.com/ses/latest/DeveloperGuide/request-production-access.html" - target="_blank" - rel="noopener noreferrer" - > - AWS Simple Email Service Sandbox. - </a>{" "} - Follow instructions in{" "} - <a - href="https://hubs.mozilla.com/docs/hubs-cloud-aws-troubleshooting.html#youre-in-the-aws-sandbox-and-people-dont-receive-magic-link-emails" - target="_blank" - rel="noopener noreferrer" - > - {" "} - You're in the AWS SES Sandbox and people don't receive magic link emails: - </a> - Solution #1, #2, #3, or{" "} - <a - href="https://hubs.mozilla.com/docs/hubs-cloud-aws-existing-email-provider.html" - target="_blank" - rel="noopener noreferrer" - > - Using an Existing Email Provider - </a> - </span> - } - secondary={ - <span> - Users will not be able to log in until the system can send email. You'll need to either{" "} - <a - href="https://docs.aws.amazon.com/ses/latest/DeveloperGuide/request-production-access.html" - target="_blank" - rel="noopener noreferrer" - > - follow the instructions - </a>{" "} - to request a limit increase, or set custom email settings in{" "} - <a href="/admin#/server-setup">Server Settings</a> - </span> - } - /> - </ListItem> - )} - {exceededStorageQuota && ( - <ListItem> - <ListItemIcon className={this.props.classes.warningIcon}> - <Warning /> - </ListItemIcon> - <ListItemText - inset - primary={<span>You have exceeded your specified storage limit.</span>} - secondary={ - <span> - Visitors will not be able to upload new scenes, avatars, or files until you increase the - 'Storage Limit' in your stack settings. - </span> - } - /> - </ListItem> - )} - {needsAvatars && ( - <ListItem> - <ListItemIcon className={this.props.classes.warningIcon}> - <Warning /> - </ListItemIcon> - <ListItemText - inset - primary="Your system has no avatars." - secondary="Choose 'Import Content' on the left to load avatars." - /> - </ListItem> - )} - {needsScenes && ( - <ListItem> - <ListItemIcon className={this.props.classes.warningIcon}> - <Warning /> - </ListItemIcon> - <ListItemText - inset - primary="Your system has no scenes." - secondary="Choose 'Import Content' on the left to load scenes." - /> - </ListItem> - )} - {!isUsingCloudflare && ( - <ListItem> - <ListItemIcon className={this.props.classes.infoIcon}> - <Info /> - </ListItemIcon> - <ListItemText - inset - primary={ - this.state.adminInfo.provider === "arbortect" - ? "You are not using a CDN." - : "You are using your cloud provider to serve content." - } - secondary="You can reduce costs and improve performance by using Cloudflare's CDN to serve content. Choose 'Content CDN' on the left for more info." - /> - </ListItem> - )} - </List> - )} - </CardContent> - </Card> - <Card className={this.props.classes.container}> - <Typography variant="title" gutterBottom> - In the Admin Panel, you can: - </Typography> - <List> - <ListItem> - <ListItemIcon> - <PaletteIcon /> - </ListItemIcon> - <ListItemText - primary={ - <span> - Customize the{" "} - <a - href="https://hubs.mozilla.com/docs/hubs-cloud-customizing-look-and-feel.html" - target="_blank" - rel="noopener noreferrer" - > - look and feel of your hub - </a>{" "} - in the <i>App Settings menu</i> - </span> - } - /> - </ListItem> - <ListItem style={{ paddingLeft: "100px", paddingTop: "0px" }}> - <ListItemText - primary={ - <span> - Change images, favicon, and logos - <i>Images tab</i> - </span> - } - /> - </ListItem> - <ListItem style={{ paddingLeft: "100px", paddingTop: "0px" }}> - <ListItemText - primary={ - <span> - Set the theme colors - <i>Themes tab</i> - </span> - } - /> - </ListItem> - <ListItem> - <ListItemIcon> - <LockIcon /> - </ListItemIcon> - <ListItemText - primary={ - <span> - Lockdown your instance to specific users via the  - <a - href="https://hubs.mozilla.com/docs/hubs-cloud-limiting-user-access.html" - target="_blank" - rel="noopener noreferrer" - > - Limiting Access Guide - </a> - </span> - } - /> - </ListItem> - <ListItem> - <ListItemIcon> - <VpnKeyIcon /> - </ListItemIcon> - <ListItemText - primary={ - <span> - Add your API keys for Google Analytics, Sketchfab, Discord, etc. -   - <i>Server Settings menu  > API Keys tab</i> - </span> - } - /> - </ListItem> - <ListItem> - <ListItemIcon> - <DeveloperModeIcon /> - </ListItemIcon> - - <ListItemText - primary={ - <span> - Add extra Javascript, CSS, Headers, HTML, Cors origins -   - <i>Server Settings menu  > Advanced tab</i> - </span> - } - /> - </ListItem> - <ListItem> - <ListItemIcon> - <CodeIcon /> - </ListItemIcon> - <ListItemText - primary={ - <span> - Not enough customizations? You can modify the client code directly by  - <a - href="https://hubs.mozilla.com/docs/hubs-cloud-custom-clients.html" - target="_blank" - rel="noopener noreferrer" - > - deploying a custom client - </a> - </span> - } - /> - </ListItem> - </List> - </Card> - <Card className={this.props.classes.container}> - <Title title="Hubs Cloud" /> - <CardContent className={this.props.classes.info}> - <Typography variant="title" gutterBottom> - Your hub version: - </Typography> - {configs.IS_LOCAL_OR_CUSTOM_CLIENT ? ( - <> - <Typography variant="body1" gutterBottom> - App client: Custom client - </Typography> - <Typography variant="body1" gutterBottom> - {`(Undeploy custom client to run build ${process.env.BUILD_VERSION || "?"})`} - </Typography> - <Typography variant="body1" gutterBottom> - {`(Remember to regularly pull in upstream changes from the "hubs-cloud" branch: https://github.com/mozilla/hubs)`} - </Typography> - </> - ) : ( - <Typography variant="body1" gutterBottom> - {`App client: ${process.env.BUILD_VERSION || "?"}`} - </Typography> - )} - </CardContent> - </Card> - </> - ); - } -} - -export const SystemEditor = withStyles(styles)(SystemEditorComponent); diff --git a/admin/src/react-components/system-editor.tsx b/admin/src/react-components/system-editor.tsx new file mode 100644 index 0000000000..67c1bed5ae --- /dev/null +++ b/admin/src/react-components/system-editor.tsx @@ -0,0 +1,427 @@ +/* eslint-disable react/prop-types */ +/* eslint-disable @calm/react-intl/missing-formatted-message*/ +import React, { useState, useEffect } from 'react'; +import Card from '@material-ui/core/Card'; +import CardContent from '@material-ui/core/CardContent'; +import Typography from '@material-ui/core/Typography'; +import { withStyles } from '@material-ui/core/styles'; +import List from '@material-ui/core/List'; +import ListItem from '@material-ui/core/ListItem'; +import ListItemText from '@material-ui/core/ListItemText'; +import ListItemIcon from '@material-ui/core/ListItemIcon'; +import LockIcon from '@material-ui/icons/Lock'; +import PaletteIcon from '@material-ui/icons/Palette'; +import VpnKeyIcon from '@material-ui/icons/VpnKey'; +import CodeIcon from '@material-ui/icons/Code'; +import DeveloperModeIcon from '@material-ui/icons/DeveloperMode'; +import Warning from '@material-ui/icons/Warning'; +import Info from '@material-ui/icons/Info'; +import { fetchReticulumAuthenticated } from 'hubs/src/utils/phoenix-utils'; +import withCommonStyles from '../utils/with-common-styles'; +import { getAdminInfo, getEditableConfig } from '../utils/ita'; +import configs from '../utils/configs'; +import { ReticulumMetaT, AdminInfoT, RetConfigT } from '../../types'; + +const styles = withCommonStyles(() => ({})); + +const SystemEditorComponent = ({ classes }) => { + const [adminInfo, setAdminInfo] = useState<AdminInfoT>({} as AdminInfoT); + const [retConfig, setRetConfig] = useState<RetConfigT>({} as RetConfigT); + const [reticulumMeta, setReticulumMeta] = useState<ReticulumMetaT>( + {} as ReticulumMetaT + ); + // Send quota to use as heuristic for checking if in SES sandbox + // https://forums.aws.amazon.com/thread.jspa?threadID=61090 + const MAX_AWS_SES_QUOTA_FOR_SANDBOX = 200; + + /** + * Init Component + */ + useEffect(() => { + const init = async () => { + try { + const adminInfo = await getAdminInfo(); + setAdminInfo(adminInfo); + } catch (error) { + console.log('error', error); + } + + try { + const retConfig = await getEditableConfig('reticulum'); + setRetConfig(retConfig); + } catch (error) { + console.log('error', error); + } + + try { + updateReticulumMeta(); + } catch (error) { + console.log('error', error); + } + }; + init(); + }, []); + + /** + * Update Reticulum + */ + const updateReticulumMeta = async () => { + const path = `/api/v1/meta?include_repo`; + const reticulumMeta = await fetchReticulumAuthenticated(path); + setReticulumMeta(reticulumMeta); + }; + + const needsAvatars = + reticulumMeta.repo && !reticulumMeta.repo.avatar_listings.any; + const needsScenes = + reticulumMeta.repo && !reticulumMeta.repo.scene_listings.any; + const exceededStorageQuota = + reticulumMeta.repo && !reticulumMeta.repo.storage.in_quota; + + const isInSESSandbox = + adminInfo && + adminInfo.using_ses && + adminInfo.ses_max_24_hour_send <= MAX_AWS_SES_QUOTA_FOR_SANDBOX; + + const isUsingCloudflare = + adminInfo && + retConfig && + retConfig.phx && + retConfig.phx.cors_proxy_url_host === + `cors-proxy.${adminInfo.worker_domain}`; + + return ( + <> + <Card className={classes.container}> + <CardContent className={classes.info}> + {reticulumMeta && + adminInfo && + (needsAvatars || + needsScenes || + isInSESSandbox || + exceededStorageQuota) && ( + <List> + {isInSESSandbox && ( + <ListItem> + <ListItemIcon className={classes.warningIcon}> + <Warning /> + </ListItemIcon> + <ListItemText + inset + primary={ + <span> + Your AWS account is in the{' '} + <a + href="https://docs.aws.amazon.com/ses/latest/DeveloperGuide/request-production-access.html" + target="_blank" + rel="noopener noreferrer" + > + AWS Simple Email Service Sandbox. + </a>{' '} + Follow instructions in{' '} + <a + href="https://hubs.mozilla.com/docs/hubs-cloud-aws-troubleshooting.html#youre-in-the-aws-sandbox-and-people-dont-receive-magic-link-emails" + target="_blank" + rel="noopener noreferrer" + > + {' '} + You're in the AWS SES Sandbox and people + don't receive magic link emails: + </a> + Solution #1, #2, #3, or{' '} + <a + href="https://hubs.mozilla.com/docs/hubs-cloud-aws-existing-email-provider.html" + target="_blank" + rel="noopener noreferrer" + > + Using an Existing Email Provider + </a> + </span> + } + secondary={ + <span> + Users will not be able to log in until the system can + send email. You'll need to either{' '} + <a + href="https://docs.aws.amazon.com/ses/latest/DeveloperGuide/request-production-access.html" + target="_blank" + rel="noopener noreferrer" + > + follow the instructions + </a>{' '} + to request a limit increase, or set custom email + settings in{' '} + <a href="/admin#/server-setup">Server Settings</a> + </span> + } + /> + </ListItem> + )} + {exceededStorageQuota && ( + <ListItem> + <ListItemIcon className={classes.warningIcon}> + <Warning /> + </ListItemIcon> + <ListItemText + inset + primary={ + <span> + You have exceeded your specified storage limit. + </span> + } + secondary={ + <span> + Visitors will not be able to upload new scenes, + avatars, or files until you increase the 'Storage + Limit' in your stack settings. + </span> + } + /> + </ListItem> + )} + {needsAvatars && ( + <ListItem> + <ListItemIcon className={classes.warningIcon}> + <Warning /> + </ListItemIcon> + <ListItemText + inset + primary="Your system has no avatars." + secondary="Choose 'Import Content' on the left to load avatars." + /> + </ListItem> + )} + {needsScenes && ( + <ListItem> + <ListItemIcon className={classes.warningIcon}> + <Warning /> + </ListItemIcon> + <ListItemText + inset + primary="Your system has no scenes." + secondary="Choose 'Import Content' on the left to load scenes." + /> + </ListItem> + )} + {!isUsingCloudflare && ( + <ListItem> + <ListItemIcon className={classes.infoIcon}> + <Info /> + </ListItemIcon> + <ListItemText + inset + primary={ + adminInfo.provider === 'arbortect' + ? 'You are not using a CDN.' + : 'You are using your cloud provider to serve content.' + } + secondary="You can reduce costs and improve performance by using Cloudflare's CDN to serve content. Choose 'Content CDN' on the left for more info." + /> + </ListItem> + )} + </List> + )} + </CardContent> + </Card> + + <Card className={classes.container}> + <CardContent className={classes.info}> + <Typography variant="title" gutterBottom> + 🐣 Hubs Cloud is live + </Typography> + <Typography variant="body1" gutterBottom> + Need help? Check out the{' '} + <a + href="https://hubs.mozilla.com/docs/hubs-cloud-getting-started.html" + target="_blank" + rel="noopener noreferrer" + > + Getting Started + </a>{' '} + guide. + </Typography> + <Typography variant="body1" gutterBottom> + Hubs Cloud updates automatically, see the{' '} + <a + href="https://github.com/mozilla/hubs-cloud/blob/master/CHANGELOG.md" + target="_blank" + rel="noopener noreferrer" + > + Hubs Cloud Changelog + </a> + . + </Typography> + <Typography variant="body1" gutterBottom> + <b>Questions or issues?</b> Visit the{' '} + <a + href="https://hubs.mozilla.com/docs/welcome.html" + target="_blank" + rel="noopener noreferrer" + > + Hubs Docs + </a>{' '} + or create a{' '} + <a + href="https://github.com/mozilla/hubs/discussions" + target="_blank" + rel="noopener noreferrer" + > + question in discussions + </a>{' '} + or{' '} + <a + href="https://github.com/mozilla/hubs" + target="_blank" + rel="noopener noreferrer" + > + issue in github + </a> + . + </Typography> + </CardContent> + </Card> + + <Card className={classes.container}> + <Typography variant="title" gutterBottom> + In the Admin Panel, you can: + </Typography> + <List> + <ListItem> + <ListItemIcon> + <PaletteIcon /> + </ListItemIcon> + <ListItemText + primary={ + <span> + Customize the{' '} + <a + href="https://hubs.mozilla.com/docs/hubs-cloud-customizing-look-and-feel.html" + target="_blank" + rel="noopener noreferrer" + > + look and feel of your hub + </a>{' '} + in the <i>App Settings menu</i> + </span> + } + /> + </ListItem> + <ListItem style={{ paddingLeft: '100px', paddingTop: '0px' }}> + <ListItemText + primary={ + <span> + Change images, favicon, and logos - <i>Images tab</i> + </span> + } + /> + </ListItem> + <ListItem style={{ paddingLeft: '100px', paddingTop: '0px' }}> + <ListItemText + primary={ + <span> + Set the theme colors - <i>Themes tab</i> + </span> + } + /> + </ListItem> + <ListItem> + <ListItemIcon> + <LockIcon /> + </ListItemIcon> + <ListItemText + primary={ + <span> + Lockdown your instance to specific users via the  + <a + href="https://hubs.mozilla.com/docs/hubs-cloud-limiting-user-access.html" + target="_blank" + rel="noopener noreferrer" + > + Limiting Access Guide + </a> + </span> + } + /> + </ListItem> + <ListItem> + <ListItemIcon> + <VpnKeyIcon /> + </ListItemIcon> + <ListItemText + primary={ + <span> + Add your API keys for Google Analytics, Sketchfab, Discord, + etc. -   + <i>Server Settings menu  > API Keys tab</i> + </span> + } + /> + </ListItem> + <ListItem> + <ListItemIcon> + <DeveloperModeIcon /> + </ListItemIcon> + + <ListItemText + primary={ + <span> + Add extra Javascript, CSS, Headers, HTML, Cors origins - +   + <i>Server Settings menu  > Advanced tab</i> + </span> + } + /> + </ListItem> + <ListItem> + <ListItemIcon> + <CodeIcon /> + </ListItemIcon> + <ListItemText + primary={ + <span> + Not enough customizations? You can modify the client code + directly by  + <a + href="https://hubs.mozilla.com/docs/hubs-cloud-custom-clients.html" + target="_blank" + rel="noopener noreferrer" + > + deploying a custom client + </a> + </span> + } + /> + </ListItem> + </List> + </Card> + <Card className={classes.container}> + <CardContent className={classes.info}> + <Typography variant="title" gutterBottom> + Your hub version: + </Typography> + {configs.IS_LOCAL_OR_CUSTOM_CLIENT ? ( + <> + <Typography variant="body1" gutterBottom> + App client: Custom client + </Typography> + <Typography variant="body1" gutterBottom> + {`(Undeploy custom client to run build ${ + process.env.BUILD_VERSION || '?' + })`} + </Typography> + <Typography variant="body1" gutterBottom> + {`(Remember to regularly pull in upstream changes from the "hubs-cloud" branch: https://github.com/mozilla/hubs)`} + </Typography> + </> + ) : ( + <Typography variant="body1" gutterBottom> + {`App client: ${process.env.BUILD_VERSION || '?'}`} + </Typography> + )} + </CardContent> + </Card> + </> + ); +}; + +export const SystemEditor = withStyles(styles)(SystemEditorComponent); diff --git a/admin/types.d.ts b/admin/types.d.ts new file mode 100644 index 0000000000..542e5b87d2 --- /dev/null +++ b/admin/types.d.ts @@ -0,0 +1,43 @@ +export type ReticulumStorageT = { + in_quota: boolean; +}; + +export type ReticulumSceneListingT = { + any: boolean; + default: boolean; + featured: boolean; +}; + +export type ReticulumAvatarListingT = { + any: boolean; + base: boolean; + default: boolean; + featured: boolean; +}; + +export type ReticulumRepoT = { + storage: ReticulumStorageT; + scene_listings: ReticulumSceneListingT; + avatar_listings: ReticulumAvatarListingT; +}; + +export type ReticulumMetaT = { + phx_host: string; + phx_port: string; + pool: string | null; + version: string; + repo: ReticulumRepoT; +}; + +export type AdminInfoT = { + using_ses: any; + ses_max_24_hour_send: number; + provider: string; + worker_domain: string; +}; + +export type RetConfigT = { + phx: { + cors_proxy_url_host: string; + }; +}; From 1f407df355be6ae50294b086569cda50fdb109dc Mon Sep 17 00:00:00 2001 From: Nick Grato <nickgrato@ngrato-27545.local> Date: Thu, 23 Feb 2023 16:44:00 -0800 Subject: [PATCH 02/19] adding some styles --- admin/package-lock.json | 115 +++-- admin/package.json | 1 + admin/src/admin.html | 10 + admin/src/admin.js | 214 +++++--- admin/src/assets/images/hubs_logo.png | Bin 0 -> 9744 bytes admin/src/react-components/admin-menu.js | 6 +- .../{ => pages}/system-editor.tsx | 88 +++- .../react-components/shared/CardSection.tsx | 34 ++ admin/src/styles/core/boilerplate.scss | 21 + admin/src/styles/core/theme.scss | 207 ++++++++ admin/src/styles/core/variables.scss | 162 ++++++ admin/src/styles/globals.scss | 39 ++ admin/src/styles/readme.md | 35 ++ admin/src/styles/tools/flex.scss | 36 ++ admin/src/styles/tools/functions.scss | 4 + admin/src/styles/tools/media-queries.scss | 109 ++++ admin/src/styles/tools/mixins.scss | 152 ++++++ admin/src/styles/tools/utility.scss | 467 ++++++++++++++++++ admin/src/styles/ui/dropdown.scss | 22 + admin/src/styles/ui/page.scss | 13 + admin/src/utils/with-updated-styles.js | 80 +++ 21 files changed, 1667 insertions(+), 148 deletions(-) create mode 100644 admin/src/assets/images/hubs_logo.png rename admin/src/react-components/{ => pages}/system-editor.tsx (84%) create mode 100644 admin/src/react-components/shared/CardSection.tsx create mode 100644 admin/src/styles/core/boilerplate.scss create mode 100644 admin/src/styles/core/theme.scss create mode 100644 admin/src/styles/core/variables.scss create mode 100644 admin/src/styles/globals.scss create mode 100644 admin/src/styles/readme.md create mode 100644 admin/src/styles/tools/flex.scss create mode 100644 admin/src/styles/tools/functions.scss create mode 100644 admin/src/styles/tools/media-queries.scss create mode 100644 admin/src/styles/tools/mixins.scss create mode 100644 admin/src/styles/tools/utility.scss create mode 100644 admin/src/styles/ui/dropdown.scss create mode 100644 admin/src/styles/ui/page.scss create mode 100644 admin/src/utils/with-updated-styles.js diff --git a/admin/package-lock.json b/admin/package-lock.json index 39a1a2fbc2..6f4c8a3422 100644 --- a/admin/package-lock.json +++ b/admin/package-lock.json @@ -10,6 +10,7 @@ "license": "MPL-2.0", "dependencies": { "@iarna/toml": "^2.2.3", + "@mozilla/lilypad-ui": "^1.7.7", "aframe": "github:mozillareality/aframe#hubs/master", "bitecs": "github:mozilla/bitECS#hubs-patches", "classnames": "^2.2.5", @@ -62,10 +63,11 @@ } }, "..": { + "name": "hubs", "version": "0.0.1", "license": "MPL-2.0", "dependencies": { - "@braintree/sanitize-url": "^5.0.0", + "@braintree/sanitize-url": "^6.0.0", "@formatjs/intl-locale": "^2.4.13", "@formatjs/intl-pluralrules": "^4.0.3", "@formatjs/intl-relativetimeformat": "^8.0.1", @@ -83,7 +85,7 @@ "classnames": "^2.2.5", "color": "^3.1.2", "copy-to-clipboard": "^3.0.8", - "core-js": "^3.6.5", + "core-js": "^3.24.1", "dashjs": "^3.1.0", "deepmerge": "^2.1.1", "detect-browser": "^3.0.1", @@ -106,14 +108,15 @@ "jwt-decode": "^2.2.0", "lib-hubs": "github:mozillareality/lib-hubs#master", "linkify-it": "^2.0.3", - "markdown-it": "^8.4.2", + "markdown-it": "^12.3.2", "moving-average": "^1.0.0", - "networked-aframe": "github:mozillareality/networked-aframe#master", + "networked-aframe": "github:mozillareality/networked-aframe#6093c3a0b2867a9e141cd5c19f7d13dfa7c38479", "nipplejs": "github:mozillareality/nipplejs#mr-social-client/master", "node-ensure": "0.0.0", "normalize.css": "^8.0.1", "pdfjs-dist": "^2.14.305", "phoenix": "github:gfodor/phoenix-js#master", + "postprocessing": "^6.28.7", "prop-types": "^15.7.2", "raven-js": "^3.20.1", "react": "^16.13.1", @@ -132,8 +135,9 @@ "screenfull": "^4.0.1", "sdp-transform": "^2.14.1", "semver": "^7.3.2", - "three": "github:mozillareality/three.js#d96f3cf69c7303db807cabaa2acba09d648e5e96", - "three-ammo": "github:infinitelee/three-ammo", + "three": "github:mozillareality/three.js#56e7c46d991cc16bff82bdbb03c7bfba4620567f", + "three-ammo": "github:mozillareality/three-ammo", + "three-gltf-extensions": "^0.0.14", "three-mesh-bvh": "^0.3.7", "three-pathfinding": "^1.1.0", "three-to-ammo": "github:infinitelee/three-to-ammo", @@ -145,17 +149,18 @@ "zip-loader": "^1.1.0" }, "devDependencies": { - "@babel/core": "^7.18.9", + "@babel/core": "^7.18.13", "@babel/eslint-parser": "^7.18.9", "@babel/plugin-proposal-class-properties": "^7.18.6", "@babel/plugin-proposal-object-rest-spread": "^7.18.9", "@babel/plugin-proposal-optional-chaining": "7.18.9", - "@babel/polyfill": "^7.4.4", - "@babel/preset-env": "^7.18.9", + "@babel/preset-env": "^7.18.10", "@babel/preset-react": "^7.18.6", + "@babel/preset-typescript": "^7.18.6", "@babel/register": "^7.18.9", "@calm/eslint-plugin-react-intl": "^1.4.1", "@formatjs/cli": "^5.0.6", + "@formatjs/cli-lib": "^5.1.0", "@iarna/toml": "^2.2.5", "@storybook/addon-actions": "^6.5.9", "@storybook/addon-essentials": "^6.5.9", @@ -165,11 +170,12 @@ "@storybook/react": "^6.5.9", "@storybook/storybook-deployer": "^2.8.12", "@svgr/webpack": "^6.3.1", + "@types/three": "^0.141.0", + "@types/webxr": "^0.5.0", "acorn": "^8.8.0", "ava": "^4.3.1", "babel-loader": "^8.2.5", "babel-plugin-react-intl": "^8.2.21", - "babel-plugin-transform-react-jsx-img-import": "^0.1.4", "copy-webpack-plugin": "^11.0.0", "cors": "^2.8.5", "css-loader": "^6.7.1", @@ -183,6 +189,7 @@ "esm": "^3.2.25", "fast-plural-rules": "1.0.2", "file-loader": "^6.2.0", + "fork-ts-checker-webpack-plugin": "^7.2.13", "fs-extra": "^10.1.0", "glob": "^8.0.3", "html-loader": "^4.1.0", @@ -193,7 +200,7 @@ "localstorage-memory": "^1.0.3", "mediasoup-client": "^3.6.54", "mini-css-extract-plugin": "^2.6.1", - "node-fetch": "^3.2.9", + "node-fetch": "^2.6.7", "npm-scripts-info": "0.3.9", "ora": "^6.1.2", "phoenix-channels": "^1.0.0", @@ -215,6 +222,8 @@ "stylelint-config-recommended-scss": "^7.0.0", "stylelint-scss": "^4.3.0", "tar": "^6.1.11", + "ts-loader": "^9.3.1", + "typescript": "^4.7.4", "url-loader": "^4.1.1", "webpack": "^5.74.0", "webpack-bundle-analyzer": "^4.5.0", @@ -2234,6 +2243,14 @@ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" }, + "node_modules/@mozilla/lilypad-ui": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/@mozilla/lilypad-ui/-/lilypad-ui-1.7.7.tgz", + "integrity": "sha512-Ubhw29bwotBg+yR6eg4C0CJH+54pDgxOBtPAjcT9jo9eG8IgsCLAGdI8su9mFYdgl+M562ovV/3XBqy5SHC9bw==", + "peerDependencies": { + "react": "^18.1.0" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -11597,6 +11614,11 @@ } } }, + "@mozilla/lilypad-ui": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/@mozilla/lilypad-ui/-/lilypad-ui-1.7.7.tgz", + "integrity": "sha512-Ubhw29bwotBg+yR6eg4C0CJH+54pDgxOBtPAjcT9jo9eG8IgsCLAGdI8su9mFYdgl+M562ovV/3XBqy5SHC9bw==" + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -12033,8 +12055,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz", "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==", - "dev": true, - "requires": {} + "dev": true }, "@webpack-cli/info": { "version": "1.5.0", @@ -12049,8 +12070,7 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz", "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==", - "dev": true, - "requires": {} + "dev": true }, "@xtuc/ieee754": { "version": "1.2.0", @@ -12084,15 +12104,13 @@ "version": "1.8.0", "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", - "dev": true, - "requires": {} + "dev": true }, "acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} + "dev": true }, "aframe": { "version": "git+ssh://git@github.com/mozillareality/aframe.git#3155ad3df9632ee84d93d3cd8250a4c44767e70e", @@ -12160,8 +12178,7 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "requires": {} + "dev": true }, "ansi-html-community": { "version": "0.0.8", @@ -13464,8 +13481,7 @@ "version": "8.5.0", "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", - "dev": true, - "requires": {} + "dev": true }, "eslint-scope": { "version": "7.1.1", @@ -14336,18 +14352,19 @@ "hubs": { "version": "file:..", "requires": { - "@babel/core": "^7.18.9", + "@babel/core": "^7.18.13", "@babel/eslint-parser": "^7.18.9", "@babel/plugin-proposal-class-properties": "^7.18.6", "@babel/plugin-proposal-object-rest-spread": "^7.18.9", "@babel/plugin-proposal-optional-chaining": "7.18.9", - "@babel/polyfill": "^7.4.4", - "@babel/preset-env": "^7.18.9", + "@babel/preset-env": "^7.18.10", "@babel/preset-react": "^7.18.6", + "@babel/preset-typescript": "^7.18.6", "@babel/register": "^7.18.9", - "@braintree/sanitize-url": "^5.0.0", + "@braintree/sanitize-url": "^6.0.0", "@calm/eslint-plugin-react-intl": "^1.4.1", "@formatjs/cli": "^5.0.6", + "@formatjs/cli-lib": "^5.1.0", "@formatjs/intl-locale": "^2.4.13", "@formatjs/intl-pluralrules": "^4.0.3", "@formatjs/intl-relativetimeformat": "^8.0.1", @@ -14365,6 +14382,8 @@ "@storybook/react": "^6.5.9", "@storybook/storybook-deployer": "^2.8.12", "@svgr/webpack": "^6.3.1", + "@types/three": "^0.141.0", + "@types/webxr": "^0.5.0", "acorn": "^8.8.0", "aframe": "github:mozillareality/aframe#hubs/master", "ammo-debug-drawer": "github:infinitelee/ammo-debug-drawer", @@ -14373,14 +14392,13 @@ "ava": "^4.3.1", "babel-loader": "^8.2.5", "babel-plugin-react-intl": "^8.2.21", - "babel-plugin-transform-react-jsx-img-import": "^0.1.4", "bitecs": "github:mozilla/bitECS#hubs-patches", "buffered-interpolation": "github:Infinitelee/buffered-interpolation", "classnames": "^2.2.5", "color": "^3.1.2", "copy-to-clipboard": "^3.0.8", "copy-webpack-plugin": "^11.0.0", - "core-js": "^3.6.5", + "core-js": "^3.24.1", "cors": "^2.8.5", "css-loader": "^6.7.1", "dashjs": "^3.1.0", @@ -14405,6 +14423,7 @@ "event-target-shim": "^3.0.1", "fast-plural-rules": "1.0.2", "file-loader": "^6.2.0", + "fork-ts-checker-webpack-plugin": "^7.2.13", "form-data": "^3.0.0", "form-urlencoded": "^2.0.4", "fs-extra": "^10.1.0", @@ -14424,20 +14443,21 @@ "lib-hubs": "github:mozillareality/lib-hubs#master", "linkify-it": "^2.0.3", "localstorage-memory": "^1.0.3", - "markdown-it": "^8.4.2", + "markdown-it": "^12.3.2", "mediasoup-client": "^3.6.54", "mini-css-extract-plugin": "^2.6.1", "moving-average": "^1.0.0", - "networked-aframe": "github:mozillareality/networked-aframe#master", + "networked-aframe": "github:mozillareality/networked-aframe#6093c3a0b2867a9e141cd5c19f7d13dfa7c38479", "nipplejs": "github:mozillareality/nipplejs#mr-social-client/master", "node-ensure": "0.0.0", - "node-fetch": "^3.2.9", + "node-fetch": "^2.6.7", "normalize.css": "^8.0.1", "npm-scripts-info": "0.3.9", "ora": "^6.1.2", "pdfjs-dist": "^2.14.305", "phoenix": "github:gfodor/phoenix-js#master", "phoenix-channels": "^1.0.0", + "postprocessing": "^6.28.7", "prettier": "^2.7.1", "process": "^0.11.10", "prop-types": "^15.7.2", @@ -14474,12 +14494,15 @@ "stylelint-config-recommended-scss": "^7.0.0", "stylelint-scss": "^4.3.0", "tar": "^6.1.11", - "three": "github:mozillareality/three.js#d96f3cf69c7303db807cabaa2acba09d648e5e96", - "three-ammo": "github:infinitelee/three-ammo", + "three": "github:mozillareality/three.js#56e7c46d991cc16bff82bdbb03c7bfba4620567f", + "three-ammo": "github:mozillareality/three-ammo", + "three-gltf-extensions": "^0.0.14", "three-mesh-bvh": "^0.3.7", "three-pathfinding": "^1.1.0", "three-to-ammo": "github:infinitelee/three-to-ammo", "troika-three-text": "^0.45.0", + "ts-loader": "^9.3.1", + "typescript": "^4.7.4", "url-loader": "^4.1.1", "use-clipboard-copy": "^0.1.2", "uuid": "^3.2.1", @@ -14517,8 +14540,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", - "dev": true, - "requires": {} + "dev": true }, "ieee754": { "version": "1.2.1", @@ -15051,14 +15073,12 @@ "jss-default-unit": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/jss-default-unit/-/jss-default-unit-8.0.2.tgz", - "integrity": "sha512-WxNHrF/18CdoAGw2H0FqOEvJdREXVXLazn7PQYU7V6/BWkCV0GkmWsppNiExdw8dP4TU1ma1dT9zBNJ95feLmg==", - "requires": {} + "integrity": "sha512-WxNHrF/18CdoAGw2H0FqOEvJdREXVXLazn7PQYU7V6/BWkCV0GkmWsppNiExdw8dP4TU1ma1dT9zBNJ95feLmg==" }, "jss-expand": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/jss-expand/-/jss-expand-5.3.0.tgz", - "integrity": "sha512-NiM4TbDVE0ykXSAw6dfFmB1LIqXP/jdd0ZMnlvlGgEMkMt+weJIl8Ynq1DsuBY9WwkNyzWktdqcEW2VN0RAtQg==", - "requires": {} + "integrity": "sha512-NiM4TbDVE0ykXSAw6dfFmB1LIqXP/jdd0ZMnlvlGgEMkMt+weJIl8Ynq1DsuBY9WwkNyzWktdqcEW2VN0RAtQg==" }, "jss-extend": { "version": "6.2.0", @@ -15081,8 +15101,7 @@ "jss-global": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/jss-global/-/jss-global-3.0.0.tgz", - "integrity": "sha512-wxYn7vL+TImyQYGAfdplg7yaxnPQ9RaXY/cIA8hawaVnmmWxDHzBK32u1y+RAvWboa3lW83ya3nVZ/C+jyjZ5Q==", - "requires": {} + "integrity": "sha512-wxYn7vL+TImyQYGAfdplg7yaxnPQ9RaXY/cIA8hawaVnmmWxDHzBK32u1y+RAvWboa3lW83ya3nVZ/C+jyjZ5Q==" }, "jss-nested": { "version": "6.0.1", @@ -15122,8 +15141,7 @@ "jss-props-sort": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/jss-props-sort/-/jss-props-sort-6.0.0.tgz", - "integrity": "sha512-E89UDcrphmI0LzmvYk25Hp4aE5ZBsXqMWlkFXS0EtPkunJkRr+WXdCNYbXbksIPnKlBenGB9OxzQY+mVc70S+g==", - "requires": {} + "integrity": "sha512-E89UDcrphmI0LzmvYk25Hp4aE5ZBsXqMWlkFXS0EtPkunJkRr+WXdCNYbXbksIPnKlBenGB9OxzQY+mVc70S+g==" }, "jss-template": { "version": "1.0.1", @@ -15817,8 +15835,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", - "dev": true, - "requires": {} + "dev": true }, "postcss-modules-local-by-default": { "version": "4.0.0", @@ -17043,8 +17060,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.1.tgz", "integrity": "sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ==", - "dev": true, - "requires": {} + "dev": true }, "super-animejs": { "version": "3.1.0", @@ -17754,8 +17770,7 @@ "version": "8.8.1", "resolved": "https://registry.npmjs.org/ws/-/ws-8.8.1.tgz", "integrity": "sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==", - "dev": true, - "requires": {} + "dev": true }, "xml": { "version": "1.0.1", diff --git a/admin/package.json b/admin/package.json index 672d9ccb80..980cac26a8 100644 --- a/admin/package.json +++ b/admin/package.json @@ -21,6 +21,7 @@ }, "dependencies": { "@iarna/toml": "^2.2.3", + "@mozilla/lilypad-ui": "^1.7.7", "aframe": "github:mozillareality/aframe#hubs/master", "bitecs": "github:mozilla/bitECS#hubs-patches", "classnames": "^2.2.5", diff --git a/admin/src/admin.html b/admin/src/admin.html index de14edfcba..b342e21f48 100644 --- a/admin/src/admin.html +++ b/admin/src/admin.html @@ -11,6 +11,16 @@ <!-- webpackIgnore: true --> <link rel="shortcut icon" type="image/png" href="/favicon.ico"> <title>Admin | App by Company + + + diff --git a/admin/src/admin.js b/admin/src/admin.js index ed9866d7f4..da083907ab 100644 --- a/admin/src/admin.js +++ b/admin/src/admin.js @@ -1,48 +1,67 @@ /* eslint-disable react/prop-types */ -import "./webxr-bypass-hacks"; -import configs from "./utils/configs"; -import ReactDOM from "react-dom"; -import React, { Component } from "react"; -import { Route } from "react-router-dom"; -import PropTypes from "prop-types"; -import toml from "@iarna/toml"; +import './webxr-bypass-hacks'; +import configs from './utils/configs'; +import ReactDOM from 'react-dom'; +import React, { Component } from 'react'; +import { Route } from 'react-router-dom'; +import PropTypes from 'prop-types'; +import toml from '@iarna/toml'; import { schemaByCategories, schemaCategories, getSchemas as getItaSchemas, setAuthToken as setItaAuthToken, - getAdminInfo -} from "./utils/ita"; -import { detectIdle } from "./utils/idle-detector"; -import { connectToReticulum } from "hubs/src/utils/phoenix-utils"; -import { AppBar, Admin, Layout, Resource } from "react-admin"; -import { postgrestClient, postgrestAuthenticatior } from "./utils/postgrest-data-provider"; -import { AdminMenu } from "./react-components/admin-menu"; -import { SceneList, SceneEdit } from "./react-components/scenes"; -import { SceneListingList, SceneListingEdit } from "./react-components/scene-listings"; -import { AvatarList, AvatarEdit } from "./react-components/avatars"; -import { IdentityList, IdentityCreate, IdentityEdit } from "./react-components/identities"; -import { AvatarListingList, AvatarListingEdit } from "./react-components/avatar-listings"; -import { FeaturedSceneListingList, FeaturedSceneListingEdit } from "./react-components/featured-scene-listings"; -import { PendingSceneList } from "./react-components/pending-scenes"; -import { AccountList, AccountEdit } from "./react-components/accounts"; -import { ProjectList, ProjectShow } from "./react-components/projects"; -import { SystemEditor } from "./react-components/system-editor"; -import { ServiceEditor, AppConfigEditor } from "./react-components/service-editor"; -import { ServerAccess } from "./react-components/server-access"; -import { ContentCDN } from "./react-components/content-cdn"; -import { ImportContent } from "./react-components/import-content"; -import { AutoEndSessionDialog } from "./react-components/auto-end-session-dialog"; -import registerTelemetry from "hubs/src/telemetry"; -import { createMuiTheme, withStyles } from "@material-ui/core/styles"; -import { UnauthorizedPage } from "./react-components/unauthorized"; -import { store } from "hubs/src/utils/store-instance"; - -const qs = new URLSearchParams(location.hash.split("?")[1]); + getAdminInfo, +} from './utils/ita'; +import { detectIdle } from './utils/idle-detector'; +import { connectToReticulum } from 'hubs/src/utils/phoenix-utils'; +import { AppBar, Admin, Layout, Resource } from 'react-admin'; +import { + postgrestClient, + postgrestAuthenticatior, +} from './utils/postgrest-data-provider'; +import { AdminMenu } from './react-components/admin-menu'; +import { SceneList, SceneEdit } from './react-components/scenes'; +import { + SceneListingList, + SceneListingEdit, +} from './react-components/scene-listings'; +import { AvatarList, AvatarEdit } from './react-components/avatars'; +import { + IdentityList, + IdentityCreate, + IdentityEdit, +} from './react-components/identities'; +import { + AvatarListingList, + AvatarListingEdit, +} from './react-components/avatar-listings'; +import { + FeaturedSceneListingList, + FeaturedSceneListingEdit, +} from './react-components/featured-scene-listings'; +import { PendingSceneList } from './react-components/pending-scenes'; +import { AccountList, AccountEdit } from './react-components/accounts'; +import { ProjectList, ProjectShow } from './react-components/projects'; +import { SystemEditor } from './react-components/pages/system-editor'; +import { + ServiceEditor, + AppConfigEditor, +} from './react-components/service-editor'; +import { ServerAccess } from './react-components/server-access'; +import { ContentCDN } from './react-components/content-cdn'; +import { ImportContent } from './react-components/import-content'; +import { AutoEndSessionDialog } from './react-components/auto-end-session-dialog'; +import registerTelemetry from 'hubs/src/telemetry'; +import { createMuiTheme, withStyles } from '@material-ui/core/styles'; +import { UnauthorizedPage } from './react-components/unauthorized'; +import { store } from 'hubs/src/utils/store-instance'; + +const qs = new URLSearchParams(location.hash.split('?')[1]); window.APP = { store }; -registerTelemetry("/admin", "Hubs Admin"); +registerTelemetry('/admin', 'Hubs Admin'); let itaSchemas; @@ -50,48 +69,50 @@ const theme = createMuiTheme({ overrides: { MuiDrawer: { docked: { - background: "#222222", - minHeight: "100vh" - } - } + background: '#222222', + minHeight: '100vh', + }, + }, }, palette: { primary: { - main: "#FF3464" + main: '#FF3464', }, secondary: { - main: "#000000" - } + main: '#000000', + }, }, typography: { - fontFamily: "Open Sans, sans-serif" - } + fontFamily: 'Open Sans, sans-serif', + }, }); class AdminUI extends Component { static propTypes = { dataProvider: PropTypes.func, authProvider: PropTypes.func, - onEndSession: PropTypes.func + onEndSession: PropTypes.func, }; state = { showAutoEndSessionDialog: false, - isAdmin: true + isAdmin: true, }; async componentDidMount() { - if (process.env.NODE_ENV !== "development" || qs.get("idle_timeout")) detectIdle(); - window.addEventListener("idle_detected", this.onIdleDetected); - window.addEventListener("activity_detected", this.onActivityDetected); + if (process.env.NODE_ENV !== 'development' || qs.get('idle_timeout')) + detectIdle(); + window.addEventListener('idle_detected', this.onIdleDetected); + window.addEventListener('activity_detected', this.onActivityDetected); const adminInfo = await getAdminInfo(); // Unauthorized account - if (adminInfo.error && adminInfo.code === 401) this.setState({ isAdmin: false }); + if (adminInfo.error && adminInfo.code === 401) + this.setState({ isAdmin: false }); } componentWillUnmount() { - window.removeEventListener("idle_detected", this.onIdleDetected); - window.removeEventListener("activity_detected", this.onActivityDetected); + window.removeEventListener('idle_detected', this.onIdleDetected); + window.removeEventListener('activity_detected', this.onActivityDetected); } onIdleDetected = () => { @@ -124,13 +145,13 @@ class AdminUI extends Component { name="scene_listings" list={SceneListingList} edit={SceneListingEdit} - options={{ label: "Approved scenes" }} + options={{ label: 'Approved scenes' }} /> @@ -138,17 +159,22 @@ class AdminUI extends Component { name="avatar_listings" list={AvatarListingList} edit={AvatarListingEdit} - options={{ label: "Approved avatars" }} + options={{ label: 'Approved avatars' }} /> - + @@ -157,7 +183,9 @@ class AdminUI extends Component { {this.state.showAutoEndSessionDialog && ( this.setState({ showAutoEndSessionDialog: false })} + onCancel={() => + this.setState({ showAutoEndSessionDialog: false }) + } onEndSession={() => { this.props.onEndSession(); this.setState({ sessionEnded: true }); @@ -173,8 +201,8 @@ class AdminUI extends Component { } } -import { IntlProvider } from "react-intl"; -import { lang, messages } from "./utils/i18n"; +import { IntlProvider } from 'react-intl'; +import { lang, messages } from './utils/i18n'; const mountUI = async (retPhxChannel, customRoutes, layout) => { let dataProvider; @@ -191,10 +219,13 @@ const mountUI = async (retPhxChannel, customRoutes, layout) => { await postgrestAuthenticatior.refreshPermsToken(); // Refresh perms regularly - permsTokenRefreshInterval = setInterval(() => postgrestAuthenticatior.refreshPermsToken(), 60000); + permsTokenRefreshInterval = setInterval( + () => postgrestAuthenticatior.refreshPermsToken(), + 60000 + ); } else { const server = configs.RETICULUM_SERVER || document.location.host; - dataProvider = postgrestClient("//" + server + "/api/postgrest"); + dataProvider = postgrestClient('//' + server + '/api/postgrest'); authProvider = postgrestAuthenticatior.createAuthProvider(); postgrestAuthenticatior.setAuthToken(store.state.credentials.token); } @@ -217,21 +248,21 @@ const mountUI = async (retPhxChannel, customRoutes, layout) => { onEndSession={onEndSession} /> , - document.getElementById("ui-root") + document.getElementById('ui-root') ); }; const HiddenAppBar = withStyles({ hideOnDesktop: { - "@media (min-width: 768px) and (min-height: 480px)": { - display: "none" - } - } -})(props => { + '@media (min-width: 768px) and (min-height: 480px)': { + display: 'none', + }, + }, +})((props) => { const { classes, ...other } = props; return ; }); -document.addEventListener("DOMContentLoaded", async () => { +document.addEventListener('DOMContentLoaded', async () => { const socket = await connectToReticulum(); if (store.state && store.state.credentials && store.state.credentials.token) { @@ -245,45 +276,64 @@ document.addEventListener("DOMContentLoaded", async () => { const homeRoute = ; const importRoute = ; - const accessRoute = ; + const accessRoute = ( + + ); const cdnRoute = ; const customRoutes = [homeRoute, importRoute, accessRoute, cdnRoute]; try { const appConfigSchema = schemaByCategories({ - hubs: toml.parse(await fetch("/hubs/schema.toml").then(r => r.text())) + hubs: toml.parse(await fetch('/hubs/schema.toml').then((r) => r.text())), }); const appConfigRoute = ( - } /> + ( + + )} + /> ); customRoutes.push(appConfigRoute); } catch (e) { - console.error("Could not initialize app config.", e); + console.error('Could not initialize app config.', e); } if (itaSchemas) { customRoutes.push( - } /> + } + /> ); } - const layout = props => ( - } /> + const layout = (props) => ( + } + /> ); - const redirectToLogin = () => (document.location = "/?sign_in&sign_in_destination=admin"); + const redirectToLogin = () => + (document.location = '/?sign_in&sign_in_destination=admin'); if (store.state.credentials && store.state.credentials.token) { // Reticulum global channel - const retPhxChannel = socket.channel(`ret`, { hub_id: "admin", token: store.state.credentials.token }); + const retPhxChannel = socket.channel(`ret`, { + hub_id: 'admin', + token: store.state.credentials.token, + }); retPhxChannel .join() - .receive("ok", async () => { + .receive('ok', async () => { mountUI(retPhxChannel, customRoutes, layout); }) - .receive("error", res => { - document.location = "/?sign_in&sign_in_destination=admin"; + .receive('error', (res) => { + document.location = '/?sign_in&sign_in_destination=admin'; console.error(res); }); } else { diff --git a/admin/src/assets/images/hubs_logo.png b/admin/src/assets/images/hubs_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..dee705c1ef1d56d2833e280a9fd412de59880c69 GIT binary patch literal 9744 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 ({ @@ -125,7 +125,7 @@ class Menu extends Component { return ( - + - + ({})); +const styles = withUpdatedStyles(() => ({})); const SystemEditorComponent = ({ classes }) => { const [adminInfo, setAdminInfo] = useState({} as AdminInfoT); @@ -91,8 +94,9 @@ const SystemEditorComponent = ({ classes }) => { `cors-proxy.${adminInfo.worker_domain}`; return ( - <> - +
+ {/* WARNING */} + {/* {reticulumMeta && adminInfo && @@ -189,6 +193,7 @@ const SystemEditorComponent = ({ classes }) => { primary="Your system has no avatars." secondary="Choose 'Import Content' on the left to load avatars." /> +

)} {needsScenes && ( @@ -222,14 +227,71 @@ const SystemEditorComponent = ({ classes }) => { )}
+
*/} + + + +

Getting Stared

+ + {/* AVATARS / SCENES */} +
+

Add avatars and scenes

+ + {}} + cta="get more avatars and scenes" + body="Give your hub visitors more scenes to explore and a wider + selection of avatars to choose from. Install your new assets + on the Import Content page." + /> +
+ + {/* CUSTOMIZE HUB */} +
+

Customize the look of your hub

+ + {}} + cta="apply my window" + body="Apply your branding to the hub’s website and lobby." + /> + + {}} + cta="Edit hub’s text and details" + body="Edit your hub’s name, description and other text content for + improved search engines results." + /> +
+ + {/* CHANGE ROOM */} +
+

Change room settings

+ + {}} + cta="Change room settings" + body="Specify the default room size and how they are accessed and created." + /> +
+ + {/* CHANGE ROOM */} +
+

Limit whoe can access your hub

+ + {}} + cta="Limit access guide" + body="Learn how to control who can enter your hub’s rooms." + /> +
+
- - 🐣 Hubs Cloud is live - - +

🐣 Hubs Cloud is live

+

Need help? Check out the{' '} { Getting Started {' '} guide. - +

Hubs Cloud updates automatically, see the{' '} { )}
- +
); }; diff --git a/admin/src/react-components/shared/CardSection.tsx b/admin/src/react-components/shared/CardSection.tsx new file mode 100644 index 0000000000..aff03fae73 --- /dev/null +++ b/admin/src/react-components/shared/CardSection.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { Icon, Button } from '@mozilla/lilypad-ui'; + +type CardSectionPropsT = { + cta: string; + ctaLabel?: string; + ctaCallback: () => void; + body: string; +}; + +const CardSection = ({ + cta, + ctaLabel, + ctaCallback, + body, +}: CardSectionPropsT) => { + return ( +
+
+
+ +
+

{body}

+
+
+ ); +}; + +export default CardSection; diff --git a/admin/src/styles/core/boilerplate.scss b/admin/src/styles/core/boilerplate.scss new file mode 100644 index 0000000000..90cf185289 --- /dev/null +++ b/admin/src/styles/core/boilerplate.scss @@ -0,0 +1,21 @@ +/* + + Boilerplate is a collection of all the SCSS partials + that do not output CSS when compiled. + + Include this file in components to pull in theme + variables, functions, and mixins for use in scoped + styling. + + TODO : Break this up better so that variables/mixins/function are being imported to css modules only + and utility classes and styles are being imported to the global style sheet so that in + the TSX code we can use utilities there... + +*/ + +// tools +@forward './variables'; +@forward '../tools/flex'; +@forward '../tools/functions'; +@forward '../tools/media-queries'; +@forward '../tools/mixins'; diff --git a/admin/src/styles/core/theme.scss b/admin/src/styles/core/theme.scss new file mode 100644 index 0000000000..852b30c7f7 --- /dev/null +++ b/admin/src/styles/core/theme.scss @@ -0,0 +1,207 @@ +main[data-theme='light'], +:root { + /** + PRIMARY INTERACTION + **/ + --color-interaction-primary: #1700c7; + --color-interaction-primary-hover: #170696; + --color-interaction-primary-active: #170696; + --color-interaction-primary-disabled: rgba(176, 176, 176, 0.5); + --color-interaction-primary-alt: #ffffff; + --color-interaction-primary-alt-hover: #e8effd; + --color-interaction-primary-alt-active: #cbd8f0; + --color-interaction-primary-alt-disabled: #e7e7e7; + + /** + SECONDARY INTERACTION + **/ + --color-interaction-secondary: #ffffff; + --color-interaction-secondary-hover: #d3d3d3; + --color-interaction-secondary-active: #d3d3d3; + --color-interaction-secondary-disabled: rgba(176, 176, 176, 0.5); + --color-interaction-secondary-alt: #ffffff; + --color-interaction-secondary-alt-hover: #e8e8e8; + --color-interaction-secondary-alt-active: #e8e8e8; + --color-interaction-secondary-alt-disabled: #e7e7e7; + + /** + SEMANTIC + **/ + --color-semantic-info: #262626; + --color-semantic-info-hover: #616161; + --color-semantic-info-active: #616161; + --color-semantic-disabled: #6f6f6fcc; + --color-semantic-success: #009465; + --color-semantic-success-hover: #188651; + --color-semantic-success-active: #188651; + --color-semantic-success-disabled: #6f6f6fcc; + --color-semantic-warning: #f9ab15; + --color-semantic-warning-hover: #d08a03; + --color-semantic-warning-active: #d08a03; + --color-semantic-warning-disabled: #6f6f6fcc; + --color-semantic-critical: #e90738; + --color-semantic-critical-hover: #d1003b; + --color-semantic-critical-active: #d1003b; + --color-semantic-critical-disabled: #6f6f6fcc; + --color-semantic-critical-bg-alt: #f9e1e5; + --color-semantic-neutral: #262626; + --color-semantic-neutral-hover: #616161; + --color-semantic-neutral-active: #616161; + --color-semantic-neutral-inactive: #6f6f6fcc; + + /** + TEXT + **/ + --color-text-main: #000000; + --color-text-subtle: #757575; + --color-text-reverse: #ffffff; + --color-text-reverse-subtle: #e7e7e7; + --color-text-disabled: #b0b0b0; + --color-text-info: #000000; + --color-text-success: #000000; + --color-text-warning: #000000; + --color-text-critical: #000000; + + /** + BORDER + **/ + --color-border-1: #e6e6e6; + --color-border-2: #cacaca; + --color-border-3: rgba(202, 202, 202, 0.2); + + /** + NEUTRALS + **/ + --color-neutral-0: #ffffff; + --color-neutral-0-reverse: #000000; + --color-neutral-1: #f5f5f5; + --color-neutral-2: #ededed; + --color-neutral-3: #e7e7e7; + + /** + STATUS + **/ + --color-status-ready: #7ed320; + --color-status-offline: #ada7a8; + --color-status-busy: #f19b53; + + /** + BACKGROUNDS + **/ + --color-background-overlay: rgba(23, 10, 122, 0.4); + --color-background-subtle-callout: #e8effd; + --color-background-modal-overlay: rgba(0, 0, 0, 0.3); + --color-background-critical: #f9e1e5; + --color-background-neutral-0: #ffffff; + + /** + MENU + **/ + --color-interactions-menu: #000000; + --color-interactions-menu-hover: #2f2f2f; + --color-interactions-menu-inactive: #757575; +} + +// PLACE HOLDER FOR DARK VALUES +main[data-theme='dark'] { + /** + PRIMARY INTERACTION + **/ + --color-interaction-primary: #5282ff; + --color-interaction-primary-hover: #7092e4; + --color-interaction-primary-active: #7092e4; + --color-interaction-primary-disabled: rgba(176, 176, 176, 0.5); + --color-interaction-primary-alt: #ffffff; + --color-interaction-primary-alt-hover: #e8effd; + --color-interaction-primary-alt-active: #cbd8f0; + --color-interaction-primary-alt-disabled: #e7e7e7; + + /** + SECONDARY INTERACTION + **/ + --color-interaction-secondary: #696e79; + --color-interaction-secondary-hover: #515764; + --color-interaction-secondary-active: #515764; + --color-interaction-secondary-disabled: rgba(176, 176, 176, 0.5); + --color-interaction-secondary-alt: #ffffff; + --color-interaction-secondary-alt-hover: #e8e8e8; + --color-interaction-secondary-alt-active: #d9d9d9; + --color-interaction-secondary-alt-disabled: #e7e7e7; + + /** + SEMANTIC + **/ + --color-semantic-info: #0073bd; + --color-semantic-info-hover: #0061a1; + --color-semantic-info-active: #004e84; + --color-semantic-disabled: #6f6f6f; + --color-semantic-success: #0c884c; + --color-semantic-success-hover: #08743f; + --color-semantic-success-active: #045f33; + --color-semantic-success-disabled: #767676; + --color-semantic-warning: #a86500; + --color-semantic-warning-hover: #915200; + --color-semantic-warning-active: #783f00; + --color-semantic-warning-disabled: #747474; + --color-semantic-critical: #d32947; + --color-semantic-critical-hover: #de3a57; + --color-semantic-critical-active: #e84d68; + --color-semantic-critical-disabled: #6e6e6e; + --color-semantic-critical-bg-alt: #f9e1e5; + --color-semantic-neutral: #222222; + --color-semantic-neutral-hover: #3a3a3a; + --color-semantic-neutral-active: #535353; + --color-semantic-neutral-disabled: #6e6e6e; + + /** + TEXT + **/ + --color-text-main: #cdcdcd; + --color-text-subtle: #767676; + --color-text-reverse: #000000; + --color-text-reverse-subtle: #212121; + --color-text-disabled: #4a4a4a; + --color-text-info: #cdcdcd; + --color-text-success: #cdcdcd; + --color-text-warning: #cdcdcd; + --color-text-critical: #cdcdcd; + + /** + BORDER + **/ + --color-border-1: #2d2d2d; + --color-border-2: #1a1a1a; + + /** + NEUTRALS + **/ + --color-neutral-0: #1a1a1a; + --color-neutral-0-reverse: #ffffff; + --color-neutral-1: #262626; + --color-neutral-2: #2e2e2e; + --color-neutral-3: #363636; + + /** + STATUS + **/ + --color-status-ready: #7ed320; + --color-status-offline: #ada7a8; + --color-status-busy: #f19b53; + + /** + BACKGROUNDS + **/ + --color-background-overlay: rgba(23, 10, 122, 0.4); + --color-background-overlay-2: rgba(23, 10, 122, 0.6); + --color-background-subtle-callout: #e8effd; + --color-background-modal-overlay: rgba(0, 0, 0, 0.3); + --color-background-critical: #f9e1e5; + --color-background-neutral-0: #ffffff; + + /** + MENU + **/ + --color-interactions-menu: #000000; + --color-interactions-menu-hover: #2f2f2f; + --color-interactions-menu-inactive: #757575; +} diff --git a/admin/src/styles/core/variables.scss b/admin/src/styles/core/variables.scss new file mode 100644 index 0000000000..24ad3ee280 --- /dev/null +++ b/admin/src/styles/core/variables.scss @@ -0,0 +1,162 @@ +// ----------------- +// Font Setup +// ----------------- +$font-size-base: 16; // base size in px +$primary-font: 'Inter'; +$secondary-font: 'Space Grotesk'; +$backup-font: 'Arial'; + +// --------------------------------- +// Colors +// --------------------------------- +/** + PRIMARY INTERACTION + **/ +$color-interaction-primary: var(--color-interaction-primary); +$color-interaction-primary-hover: var(--color-interaction-primary-hover); +$color-interaction-primary-active: var(--color-interaction-primary-active); +$color-interaction-primary-disabled: var(--color-interaction-primary-disabled); +$color-interaction-primary-alt: var(--color-interaction-primary-alt); +$color-interaction-primary-alt-hover: var( + --color-interaction-primary-alt-hover +); +$color-interaction-primary-alt-active: var( + --color-interaction-primary-alt-active +); +$color-interaction-primary-alt-disabled: var( + --color-interaction-primary-alt-disabled +); + +/** + SECONDARY INTERACTION + **/ +$color-interaction-secondary: var(--color-interaction-secondary); +$color-interaction-secondary-hover: var(--color-interaction-secondary-hover); +$color-interaction-secondary-active: var(--color-interaction-secondary-active); +$color-interaction-secondary-disabled: var( + --color-interaction-secondary-disabled +); +$color-interaction-secondary-alt: var(--color-interaction-secondary-alt); +$color-interaction-secondary-alt-hover: var( + --color-interaction-secondary-alt-hover +); +$color-interaction-secondary-alt-active: var( + --color-interaction-secondary-alt-active +); +$color-interaction-secondary-alt-disabled: var( + --color-interaction-secondary-alt-disabled +); + +/** + SEMANTIC + **/ + +$color-semantic-info: var(--color-semantic-info); +$color-semantic-info-hover: var(--color-semantic-info-hover); +$color-semantic-info-active: var(--color-semantic-info-active); +$color-semantic-disabled: var(--color-semantic-disabled); +$color-semantic-success: var(--color-semantic-success); +$color-semantic-success-hover: var(--color-semantic-success-hover); +$color-semantic-success-active: var(--color-semantic-success-active); +$color-semantic-success-disabled: var(--color-semantic-success-disabled); +$color-semantic-warning: var(--color-semantic-warning); +$color-semantic-warning-hover: var(--color-semantic-warning-hover); +$color-semantic-warning-active: var(--color-semantic-warning-active); +$color-semantic-warning-disabled: var(--color-semantic-warning-disabled); +$color-semantic-critical: var(--color-semantic-critical); +$color-semantic-critical-hover: var(--color-semantic-critical-hover); +$color-semantic-critical-active: var(--color-semantic-critical-active); +$color-semantic-critical-disabled: var(--color-semantic-critical-disabled); +$color-semantic-critical-bg-alt: var(--color-semantic-critical-bg-alt); +$color-semantic-neutral: var(--color-semantic-neutral); +$color-semantic-neutral-hover: var(--color-semantic-neutral-hover); +$color-semantic-neutral-active: var(--color-semantic-neutral-active); +$color-semantic-neutral-inactive: var(--color-semantic-neutral-inactive); + +/** + TEXT + **/ +$color-text-main: var(--color-text-main); +$color-text-subtle: var(--color-text-subtle); +$color-text-reverse: var(--color-text-reverse); +$color-text-reverse-subtle: var(--color-text-reverse-subtle); +$color-text-disabled: var(--color-text-disabled); +$color-text-info: var(--color-text-info); +$color-text-success: var(--color-text-success); +$color-text-warning: var(--color-text-warning); +$color-text-critical: var(--color-text-critical); + +/** + BORDER + **/ +$color-border-1: var(--color-border-1); +$color-border-2: var(--color-border-2); +$color-border-3: var(--color-border-3); + +/** + NEUTRALS + **/ +$color-neutral-0: var(--color-neutral-0); +$color-neutral-1: var(--color-neutral-1); +$color-neutral-2: var(--color-neutral-2); +$color-neutral-3: var(--color-neutral-3); + +/** + STATUS + **/ +$color-status-ready: var(--color-status-ready); +$color-status-busy: var(--color-status-busy); +$color-status-offline: var(--color-status-offline); + +/** + BACKGROUNDS + **/ +$color-background-overlay: var(--color-background-overlay); +$color-background-modal-overlay: var(--color-background-modal-overlay); +$color-interactions-menu: var(--color-interactions-menu); +$color-background-neutral-0: var(--color-background-neutral-0); +$color-background-critical: var(--color-background-critical); +$color-background-subtle-callout: var(--color-background-subtle-callout); + +/** + MENU + **/ +$color-interactions-menu: var(--color-interactions-menu); +$color-interactions-menu-hover: var(--color-interactions-menu-hover); +$color-interactions-menu-inactive: var(--color-interactions-menu-inactive); + +// ---- Primary Brand Colors --------------------- +$black: #231f20; +$blue: #0d73a6; +$gradient-warm: radial-gradient( + 147.61% 1186.51% at 110.24% -15.63%, + #ff806f 36.82%, + #ffff98 100% +); +$gradient-rainbow: linear-gradient( + 107.97deg, + #489cbe 6.73%, + #5427c9 39.4%, + #a8527c 77.18%, + #a67878 104.75% +); + +// ---- Utility Colors --------------------- +$white: #ffffff; +$off-white: #ddd; +$grey: #868686; +$accent-grey: #e0e0e0; + +// ---- Break Points --------------------- +$breakpoint-mobile: 580px; +$breakpoint-tablet: 768px; +$breakpoint-tablet-large: 992px; +$breakpoint-desktop: 1024px; +$breakpoint-desktop-large: 1220px; +$breakpoint-hd: 1312px; +$container-max-width: 1440px; + +// ---- Misc --------------------- +$drop-shadow: 0 3px 8px rgba($black, 0.3); +$drop-shadow-right: 3px 3px 8px rgba($black, 0.3); +$drop-shadow-light: 0 3px 8px rgba($black, 0.05); diff --git a/admin/src/styles/globals.scss b/admin/src/styles/globals.scss new file mode 100644 index 0000000000..63924ffaf8 --- /dev/null +++ b/admin/src/styles/globals.scss @@ -0,0 +1,39 @@ +@use './core/boilerplate.scss' as *; +@use './tools/utility.scss' as *; +@use './ui/dropdown.scss' as *; +@use './core/theme.scss' as *; +@use './ui/page.scss' as *; + +html, +body { + @include primary-font(); + padding: 0; + margin: 0; + font-size: 16px; +} + +a { + color: inherit; + text-decoration: none; + cursor: pointer; +} + +h1, +h2, +h3, +h4, +h5, +h6, +p { + margin: 0px; +} + +button { + &:hover { + cursor: pointer; + } +} + +* { + box-sizing: border-box; +} diff --git a/admin/src/styles/readme.md b/admin/src/styles/readme.md new file mode 100644 index 0000000000..9f22ba186b --- /dev/null +++ b/admin/src/styles/readme.md @@ -0,0 +1,35 @@ + + +## Getting Started + +This is a document describing how to use some of the features in the style folder. Including utility classes, mixins etc.. + +### Utility Classes + +The utility classes provided here should not dominate the JSX className. These are intended to be used sparingly and/or when only a few utility classes are needed and the provided classes do the trick. + +#### Margin and Padding + +These classes are used for fast margin or padding with additional mobile or desktop responsive behavior. Any size from 0-100 is available to use in the following pattern: + +#### Key + +- m = margin +- p = padding + +- y = vertical ( top & bottom ) +- x = horizontal { right & left )} +- t = top +- r = right +- b = bottom +- l = left + +- mb = show only on mobile +- dt = show only on desktop + +#### Pattern + +{ attribute (p,m) }{ direction (x,y,t,r,b,l) note: this is optional, leave off to have attribute applied to all sides }-{ size (numbers 1-100) }-{ mobile or desktop (mb,dt) note: this is optional, leave off to apply style to all media sizes.} + +Say you want a class with margin right 20px, the class would look like 'mr-20'. Let's say you wanted +that 20px to only display on mobile the class would be 'mr-20-mb'. diff --git a/admin/src/styles/tools/flex.scss b/admin/src/styles/tools/flex.scss new file mode 100644 index 0000000000..4b01b5b456 --- /dev/null +++ b/admin/src/styles/tools/flex.scss @@ -0,0 +1,36 @@ +// FLEX QUICKIES +.flex { + display: flex; +} +.flex-align-start { + display: flex; + align-items: flex-start; +} +.flex-align-items-center { + display: flex; + align-items: center; +} +.flex-align-end { + display: flex; + align-items: flex-end; +} +.flex-justify-start { + display: flex; + justify-content: flex-start; +} +.flex-justify-center { + display: flex; + justify-content: center; +} +.flex-justify-end { + display: flex; + justify-content: flex-end; +} +.flex-justify-between { + display: flex; + justify-content: space-between; +} +.flex-justify-around { + display: flex; + justify-content: space-around; +} diff --git a/admin/src/styles/tools/functions.scss b/admin/src/styles/tools/functions.scss new file mode 100644 index 0000000000..9181afe417 --- /dev/null +++ b/admin/src/styles/tools/functions.scss @@ -0,0 +1,4 @@ +@function rem($size) { + $rem-size: $size / 16; + @return #{$rem-size}rem; +} diff --git a/admin/src/styles/tools/media-queries.scss b/admin/src/styles/tools/media-queries.scss new file mode 100644 index 0000000000..e6a2be4b49 --- /dev/null +++ b/admin/src/styles/tools/media-queries.scss @@ -0,0 +1,109 @@ +@use '../core/variables' as *; + +@mixin mobile-to-tablet { + @media (min-width: 600px) and (max-width: 1000px) { + @content; + } +} + +@mixin mobile-down { + @media (max-width: $breakpoint-mobile) { + @content; + } +} + +@mixin mobile-up { + @media (min-width: $breakpoint-mobile) { + @content; + } +} + +@mixin mobile-only { + @media (max-width: $breakpoint-tablet - 1) { + @content; + } +} + +@mixin mobile-tablet { + @media (max-width: $breakpoint-desktop - 1) { + @content; + } +} + +@mixin tablet-up { + @media (min-width: $breakpoint-tablet) { + @content; + } +} + +@mixin tablet-down { + @media (max-width: $breakpoint-tablet) { + @content; + } +} + +@mixin tablet-large-up { + @media (min-width: $breakpoint-tablet-large) { + @content; + } +} + +@mixin tablet-large-down { + @media (max-width: $breakpoint-tablet-large) { + @content; + } +} + +@mixin tablet-only { + @media (min-width: $breakpoint-tablet) and (max-width: $breakpoint-desktop - 1) { + @content; + } +} + +@mixin desktop-up { + @media (min-width: $breakpoint-desktop) { + @content; + } +} + +@mixin desktop-only { + @media (min-width: $breakpoint-desktop) and (max-width: $breakpoint-hd - 1) { + @content; + } +} + +@mixin desktop-down { + @media (max-width: $breakpoint-desktop) { + @content; + } +} + +@mixin desktop-large-up { + @media (min-width: $breakpoint-desktop-large) { + @content; + } +} + +@mixin desktop-large-only { + @media (min-width: $breakpoint-desktop-large) and (max-width: $breakpoint-hd - 1) { + @content; + } +} + +@mixin desktop-large-down { + @media (max-width: $breakpoint-desktop-large) { + @content; + } +} + +@mixin hd-up { + @media (min-width: $breakpoint-hd) { + @content; + } +} + +@mixin max-width-up { + @media (min-width: $container-max-width) { + @content; + } +} diff --git a/admin/src/styles/tools/mixins.scss b/admin/src/styles/tools/mixins.scss new file mode 100644 index 0000000000..399fcbe087 --- /dev/null +++ b/admin/src/styles/tools/mixins.scss @@ -0,0 +1,152 @@ +@use '../core/variables.scss' as *; +@use '../tools/media-queries.scss' as *; +@use '../tools/functions.scss' as *; + +/*------------------------------- + TYPOGRAPHY STYLES +-------------------------------*/ +@mixin font-size($size: 16) { + -font-size-: $size; + font-size: rem($size); +} + +@mixin primary-font($weight: 600) { + font-family: $primary-font, $backup-font; + font-weight: $weight; +} + +@mixin secondary-font($weight: 700) { + font-family: $secondary-font, $backup-font; + font-weight: $weight; +} + +/*------------------------------- + HEADLINE STYLES +-------------------------------*/ +@mixin heading-xxl { + @include font-size(40); + @include secondary-font(700); + line-height: 51px; + + @include tablet-down { + @include font-size(28.8); + line-height: 37px; + } +} + +@mixin heading-xl { + @include font-size(36); + @include secondary-font(700); + line-height: 46px; + + @include tablet-down { + @include font-size(25.6); + line-height: 33px; + } +} + +@mixin heading-lg { + @include font-size(32); + @include secondary-font(700); + line-height: 41px; + + @include tablet-down { + @include font-size(24); + line-height: 31px; + } +} + +@mixin heading-md { + @include font-size(28); + @include secondary-font(700); + line-height: 36px; + + @include tablet-down { + @include font-size(21); + line-height: 27px; + } +} + +@mixin heading-sm { + @include font-size(24); + @include primary-font(700); + line-height: 31px; + + @include tablet-down { + @include font-size(19.2); + line-height: 24px; + } +} + +@mixin heading-xs { + @include font-size(20); + @include primary-font(700); + line-height: 26px; +} + +@mixin heading-xxs { + @include font-size(16); + @include primary-font(700); + line-height: 20px; +} + +/*------------------------------- + BODY STYLES +-------------------------------*/ +@mixin body-lg($weight: 700) { + @include font-size(14); + @include primary-font($weight); + line-height: 21px; + letter-spacing: 0; + + @include tablet-up { + @include font-size(16); + line-height: 24px; + letter-spacing: 0; + } +} + +@mixin body-md { + @include primary-font(500); + @include font-size(16); + line-height: 24px; + + @include tablet-down { + @include font-size(14); + } +} + +@mixin body-md-bold { + @include primary-font(700); + @include font-size(16); + line-height: 24px; +} + +@mixin body-sm { + @include font-size(12); + @include primary-font(400); + letter-spacing: 0; + line-height: 25px; +} + +@mixin body-xs { + @include font-size(10); + @include primary-font(300); + line-height: 13px; +} + +/*------------------------------- + UTILITY MIXINS +-------------------------------*/ +@mixin size($size) { + height: #{$size}px; + width: #{$size}px; +} + +@mixin webkit($attr, $time, $type) { + -webkit-transition: $attr $time $type; + -moz-transition: $attr $time $type; + -o-transition: $attr $time $type; + -ms-transition: $attr $time $type; + transition: $attr $time $type; +} diff --git a/admin/src/styles/tools/utility.scss b/admin/src/styles/tools/utility.scss new file mode 100644 index 0000000000..88a8f98224 --- /dev/null +++ b/admin/src/styles/tools/utility.scss @@ -0,0 +1,467 @@ +@use '../tools/media-queries.scss' as *; +@use '../tools/mixins.scss' as *; +@use '../core/variables.scss' as *; + +// --------------------------------- +// Quick Typograpjhy +// --------------------------------- +$fontSizes: 12, 14, 16, 18, 20; + +@each $fontSize in $fontSizes { + .font-#{$fontSize} { + @include font-size($fontSize); + } +} + +// Body Text +.body-md { + @include body-md(); +} + +.body-md-bold { + @include body-md-bold(); +} + +// Heading +.heading-xxl { + @include heading-xxl(); +} + +.heading-xl { + @include heading-xl(); +} + +.heading-lg { + @include heading-lg(); +} + +.heading-md { + @include heading-md(); +} + +.heading-sm { + @include heading-sm(); +} + +.heading-xs { + @include heading-xs(); +} + +.heading-xxs { + @include heading-xxs(); +} + +.capitalize { + text-transform: capitalize; +} + +// --------------------------------- +// Quick Media-Q +// --------------------------------- +.no-mobile { + @include mobile-down { + display: none !important; + } +} + +// --------------------------------- +// Colors +// --------------------------------- +.color-text-main { + color: $color-text-main; +} + +// --------------------------------- +// Visibility +// --------------------------------- + +.hidden { + display: none !important; +} + +.hidden-mobile-only { + @include mobile-only { + display: none !important; + } +} + +.hidden-tablet-up { + @include tablet-up { + display: none !important; + } +} + +.hidden-tablet-only { + @include tablet-only { + display: none !important; + } +} + +.hidden-desktop-up { + @include desktop-up { + display: none !important; + } +} + +.hidden-desktop-only { + @include desktop-only { + display: none !important; + } +} + +.hidden-hd-up { + @include hd-up { + display: none !important; + } +} + +// --------------------------------- +// Misc +// --------------------------------- + +.sr-only { + border: 0 !important; + clip: rect(1px, 1px, 1px, 1px) !important; + -webkit-clip-path: inset(50%) !important; + clip-path: inset(50%) !important; + height: 1px !important; + margin: -1px !important; + overflow: hidden !important; + padding: 0 !important; + position: absolute !important; + width: 1px !important; + white-space: nowrap !important; +} + +.clear-button { + cursor: pointer; + border: none; + background-color: transparent; + display: inline-block; + margin: 0; + padding: 0; +} + +.block { + display: block; +} + +.pointer { + cursor: pointer; +} + +.no-link { + &, + &:hover { + text-decoration: none; + color: inherit; + } +} + +.overflow-x-hidden { + overflow-x: hidden; +} + +.text-center { + text-align: center; +} + +.text-right { + text-align: right; +} + +.text-left { + text-align: left; +} + +.uppercase { + text-transform: uppercase; +} + +.title-case { + text-transform: capitalize; +} + +.u-left-absolute { + position: absolute; + left: 0; +} + +.u-right-absolute { + position: absolute; + right: 0; +} + +.left-absolute { + position: absolute; + left: 0; +} + +.right-absolute { + position: absolute; + right: 0; +} + +.width-100 { + width: 100%; +} + +.content-box { + &, + & * { + box-sizing: content-box; + } +} + +.button-wrapper { + border: 0; + background: transparent; + padding: 0; +} + +// --------------------------------- +// Layout +// --------------------------------- + +// Create Size Value Array +$start: 0; +$end: 100; +$array: ''; +$unit: 'px'; + +@for $i from $start + 1 through $end { + $array: append($array, $i, comma); + $array: set-nth($array, 1, $start); +} + +@each $size in $array { + /** + PADDING + **/ + + // Padding: General + .p-#{$size} { + padding: #{$size}#{$unit}; + &-dt { + @include mobile-up { + padding: #{$size}#{$unit}; + } + } + &-mb { + @include mobile-down { + padding: #{$size}#{$unit}; + } + } + } + + // Padding: Horizontal + .px-#{$size} { + padding-left: #{$size}#{$unit}; + padding-right: #{$size}#{$unit}; + &-dt { + @include mobile-up { + padding-left: #{$size}#{$unit}; + padding-right: #{$size}#{$unit}; + } + } + &-mb { + @include mobile-down { + padding-left: #{$size}#{$unit}; + padding-right: #{$size}#{$unit}; + } + } + } + + // Padding: Vertical + .py-#{$size} { + padding-top: #{$size}#{$unit}; + padding-bottom: #{$size}#{$unit}; + &-dt { + @include mobile-up { + padding-top: #{$size}#{$unit}; + padding-bottom: #{$size}#{$unit}; + } + } + &-mb { + @include mobile-down { + padding-top: #{$size}#{$unit}; + padding-bottom: #{$size}#{$unit}; + } + } + } + + // Padding: Top + .pt-#{$size} { + padding-top: #{$size}#{$unit}; + &-dt { + @include mobile-up { + padding-top: #{$size}#{$unit}; + } + } + &-mb { + @include mobile-down { + padding-top: #{$size}#{$unit}; + } + } + } + + // Padding: Top + .pb-#{$size} { + padding-bottom: #{$size}#{$unit}; + &-dt { + @include mobile-up { + padding-bottom: #{$size}#{$unit}; + } + } + &-mb { + @include mobile-down { + padding-bottom: #{$size}#{$unit}; + } + } + } + + // Margin: Right + .pr-#{$size} { + padding-right: #{$size}#{$unit}; + &-dt { + @include mobile-up { + padding-right: #{$size}#{$unit}; + } + } + &-mb { + @include mobile-down { + padding-right: #{$size}#{$unit}; + } + } + } + + // Margin: Left + .pl-#{$size} { + padding-left: #{$size}#{$unit}; + &-dt { + @include mobile-up { + padding-left: #{$size}#{$unit}; + } + } + &-mb { + @include mobile-down { + padding-left: #{$size}#{$unit}; + } + } + } + + /** + MARGIN + **/ + + // Margin: General + .m-#{$size} { + margin: #{$size}#{$unit}; + &-dt { + @include mobile-up { + margin: #{$size}#{$unit}; + } + } + &-mb { + @include mobile-down { + margin: #{$size}#{$unit}; + } + } + } + + // Margin: Horizontal + .mx-#{$size} { + margin-left: #{$size}#{$unit}; + margin-right: #{$size}#{$unit}; + &-dt { + @include mobile-up { + margin-left: #{$size}#{$unit}; + margin-right: #{$size}#{$unit}; + } + } + &-mb { + @include mobile-down { + margin-left: #{$size}#{$unit}; + margin-right: #{$size}#{$unit}; + } + } + } + + // Margin: Vertical + .my-#{$size} { + margin-top: #{$size}#{$unit}; + margin-bottom: #{$size}#{$unit}; + &-dt { + @include mobile-up { + margin-top: #{$size}#{$unit}; + margin-bottom: #{$size}#{$unit}; + } + } + &-mb { + @include mobile-down { + margin-top: #{$size}#{$unit}; + margin-bottom: #{$size}#{$unit}; + } + } + } + + // Margin: Top + .mt-#{$size} { + margin-top: #{$size}#{$unit}; + &-dt { + @include mobile-up { + margin-top: #{$size}#{$unit}; + } + } + &-mb { + @include mobile-down { + margin-top: #{$size}#{$unit}; + } + } + } + + // Margin: Top + .mb-#{$size} { + margin-bottom: #{$size}#{$unit}; + &-dt { + @include mobile-up { + margin-bottom: #{$size}#{$unit}; + } + } + &-mb { + @include mobile-down { + margin-bottom: #{$size}#{$unit}; + } + } + } + + // Margin: Right + .mr-#{$size} { + margin-right: #{$size}#{$unit}; + &-dt { + @include mobile-up { + margin-right: #{$size}#{$unit}; + } + } + &-mb { + @include mobile-down { + margin-right: #{$size}#{$unit}; + } + } + } + + // Margin: Left + .ml-#{$size} { + margin-left: #{$size}#{$unit}; + &-dt { + @include mobile-up { + margin-left: #{$size}#{$unit}; + } + } + &-mb { + @include mobile-down { + margin-left: #{$size}#{$unit}; + } + } + } +} diff --git a/admin/src/styles/ui/dropdown.scss b/admin/src/styles/ui/dropdown.scss new file mode 100644 index 0000000000..3b0ed39300 --- /dev/null +++ b/admin/src/styles/ui/dropdown.scss @@ -0,0 +1,22 @@ +@use '../core/boilerplate.scss' as *; + +.dropdown-hr { + border: 0; + height: 2px; + background: $color-border-3; + margin: 24px 0; +} + +.dropdown-link { + @include body-md; + display: flex; + align-items: center; + color: $color-text-reverse; + background-color: transparent; + border: 0px; + padding: 0; + + &:first-child { + margin-bottom: 24px; + } +} diff --git a/admin/src/styles/ui/page.scss b/admin/src/styles/ui/page.scss new file mode 100644 index 0000000000..413e55cbe9 --- /dev/null +++ b/admin/src/styles/ui/page.scss @@ -0,0 +1,13 @@ +@use '../core/boilerplate.scss' as *; + +.page_wrapper { + @include primary-font(); + padding: 0; + margin: 0; + font-size: 16px; +} + +.global_background { + // need to override material + background-color: $color-neutral-1 !important; +} diff --git a/admin/src/utils/with-updated-styles.js b/admin/src/utils/with-updated-styles.js new file mode 100644 index 0000000000..3e35df1aca --- /dev/null +++ b/admin/src/utils/with-updated-styles.js @@ -0,0 +1,80 @@ +import { blue, green, amber } from '@material-ui/core/colors'; + +const getCommon = (theme) => ({ + container: { + ...theme.mixins.gutters(), + display: 'flex', + flexWrap: 'wrap', + padding: '36px', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'flex-start', + margin: '12px', + }, + info: { + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + width: '100%', + }, + + infoIcon: { + color: blue[200], + }, + + warningIcon: { + color: amber[700], + }, + + successIcon: { + color: green[700], + }, + + button: { + margin: '10px 10px 0 0', + width: 'max-content', + }, + success: { + backgroundColor: green[600], + }, + warning: { + backgroundColor: amber[700], + }, + icon: { + fontSize: 20, + }, + message: { + display: 'flex', + alignItems: 'center', + }, + snackContents: { + '& a': { + color: 'white', + }, + }, + command: { + fontFamily: 'monospace', + padding: '12px', + margin: '12px', + borderRadius: '4px', + backgroundColor: 'whitesmoke', + }, + steps: { + '& li': { + margin: '6px 0px', + }, + }, + section: { + '&:not(:first-child)': { + marginTop: '12px', + }, + }, +}); + +export default function withUpdatedStyles(f) { + return (theme) => { + const computed = f(theme); + const common = getCommon(theme); + return { ...computed, ...common }; + }; +} From aef6b87483be262e7212a6e2e201532caf79bb7f Mon Sep 17 00:00:00 2001 From: Nick Grato Date: Fri, 24 Feb 2023 14:05:45 -0800 Subject: [PATCH 03/19] updating admin.js --- admin/src/admin.js | 214 +++++++----------- admin/src/react-components/admin-menu.js | 4 +- .../react-components/shared/CardSection.tsx | 3 +- 3 files changed, 87 insertions(+), 134 deletions(-) diff --git a/admin/src/admin.js b/admin/src/admin.js index da083907ab..c1829af380 100644 --- a/admin/src/admin.js +++ b/admin/src/admin.js @@ -1,67 +1,48 @@ /* eslint-disable react/prop-types */ -import './webxr-bypass-hacks'; -import configs from './utils/configs'; -import ReactDOM from 'react-dom'; -import React, { Component } from 'react'; -import { Route } from 'react-router-dom'; -import PropTypes from 'prop-types'; -import toml from '@iarna/toml'; +import "./webxr-bypass-hacks"; +import configs from "./utils/configs"; +import ReactDOM from "react-dom"; +import React, { Component } from "react"; +import { Route } from "react-router-dom"; +import PropTypes from "prop-types"; +import toml from "@iarna/toml"; import { schemaByCategories, schemaCategories, getSchemas as getItaSchemas, setAuthToken as setItaAuthToken, - getAdminInfo, -} from './utils/ita'; -import { detectIdle } from './utils/idle-detector'; -import { connectToReticulum } from 'hubs/src/utils/phoenix-utils'; -import { AppBar, Admin, Layout, Resource } from 'react-admin'; -import { - postgrestClient, - postgrestAuthenticatior, -} from './utils/postgrest-data-provider'; -import { AdminMenu } from './react-components/admin-menu'; -import { SceneList, SceneEdit } from './react-components/scenes'; -import { - SceneListingList, - SceneListingEdit, -} from './react-components/scene-listings'; -import { AvatarList, AvatarEdit } from './react-components/avatars'; -import { - IdentityList, - IdentityCreate, - IdentityEdit, -} from './react-components/identities'; -import { - AvatarListingList, - AvatarListingEdit, -} from './react-components/avatar-listings'; -import { - FeaturedSceneListingList, - FeaturedSceneListingEdit, -} from './react-components/featured-scene-listings'; -import { PendingSceneList } from './react-components/pending-scenes'; -import { AccountList, AccountEdit } from './react-components/accounts'; -import { ProjectList, ProjectShow } from './react-components/projects'; -import { SystemEditor } from './react-components/pages/system-editor'; -import { - ServiceEditor, - AppConfigEditor, -} from './react-components/service-editor'; -import { ServerAccess } from './react-components/server-access'; -import { ContentCDN } from './react-components/content-cdn'; -import { ImportContent } from './react-components/import-content'; -import { AutoEndSessionDialog } from './react-components/auto-end-session-dialog'; -import registerTelemetry from 'hubs/src/telemetry'; -import { createMuiTheme, withStyles } from '@material-ui/core/styles'; -import { UnauthorizedPage } from './react-components/unauthorized'; -import { store } from 'hubs/src/utils/store-instance'; - -const qs = new URLSearchParams(location.hash.split('?')[1]); + getAdminInfo +} from "./utils/ita"; +import { detectIdle } from "./utils/idle-detector"; +import { connectToReticulum } from "hubs/src/utils/phoenix-utils"; +import { AppBar, Admin, Layout, Resource } from "react-admin"; +import { postgrestClient, postgrestAuthenticatior } from "./utils/postgrest-data-provider"; +import { AdminMenu } from "./react-components/admin-menu"; +import { SceneList, SceneEdit } from "./react-components/scenes"; +import { SceneListingList, SceneListingEdit } from "./react-components/scene-listings"; +import { AvatarList, AvatarEdit } from "./react-components/avatars"; +import { IdentityList, IdentityCreate, IdentityEdit } from "./react-components/identities"; +import { AvatarListingList, AvatarListingEdit } from "./react-components/avatar-listings"; +import { FeaturedSceneListingList, FeaturedSceneListingEdit } from "./react-components/featured-scene-listings"; +import { PendingSceneList } from "./react-components/pending-scenes"; +import { AccountList, AccountEdit } from "./react-components/accounts"; +import { ProjectList, ProjectShow } from "./react-components/projects"; +import { SystemEditor } from "./react-components/pages/system-editor"; +import { ServiceEditor, AppConfigEditor } from "./react-components/service-editor"; +import { ServerAccess } from "./react-components/server-access"; +import { ContentCDN } from "./react-components/content-cdn"; +import { ImportContent } from "./react-components/import-content"; +import { AutoEndSessionDialog } from "./react-components/auto-end-session-dialog"; +import registerTelemetry from "hubs/src/telemetry"; +import { createMuiTheme, withStyles } from "@material-ui/core/styles"; +import { UnauthorizedPage } from "./react-components/unauthorized"; +import { store } from "hubs/src/utils/store-instance"; + +const qs = new URLSearchParams(location.hash.split("?")[1]); window.APP = { store }; -registerTelemetry('/admin', 'Hubs Admin'); +registerTelemetry("/admin", "Hubs Admin"); let itaSchemas; @@ -69,50 +50,48 @@ const theme = createMuiTheme({ overrides: { MuiDrawer: { docked: { - background: '#222222', - minHeight: '100vh', - }, - }, + background: "#222222", + minHeight: "100vh" + } + } }, palette: { primary: { - main: '#FF3464', + main: "#FF3464" }, secondary: { - main: '#000000', - }, + main: "#000000" + } }, typography: { - fontFamily: 'Open Sans, sans-serif', - }, + fontFamily: "Open Sans, sans-serif" + } }); class AdminUI extends Component { static propTypes = { dataProvider: PropTypes.func, authProvider: PropTypes.func, - onEndSession: PropTypes.func, + onEndSession: PropTypes.func }; state = { showAutoEndSessionDialog: false, - isAdmin: true, + isAdmin: true }; async componentDidMount() { - if (process.env.NODE_ENV !== 'development' || qs.get('idle_timeout')) - detectIdle(); - window.addEventListener('idle_detected', this.onIdleDetected); - window.addEventListener('activity_detected', this.onActivityDetected); + if (process.env.NODE_ENV !== "development" || qs.get("idle_timeout")) detectIdle(); + window.addEventListener("idle_detected", this.onIdleDetected); + window.addEventListener("activity_detected", this.onActivityDetected); const adminInfo = await getAdminInfo(); // Unauthorized account - if (adminInfo.error && adminInfo.code === 401) - this.setState({ isAdmin: false }); + if (adminInfo.error && adminInfo.code === 401) this.setState({ isAdmin: false }); } componentWillUnmount() { - window.removeEventListener('idle_detected', this.onIdleDetected); - window.removeEventListener('activity_detected', this.onActivityDetected); + window.removeEventListener("idle_detected", this.onIdleDetected); + window.removeEventListener("activity_detected", this.onActivityDetected); } onIdleDetected = () => { @@ -145,13 +124,13 @@ class AdminUI extends Component { name="scene_listings" list={SceneListingList} edit={SceneListingEdit} - options={{ label: 'Approved scenes' }} + options={{ label: "Approved scenes" }} /> @@ -159,22 +138,17 @@ class AdminUI extends Component { name="avatar_listings" list={AvatarListingList} edit={AvatarListingEdit} - options={{ label: 'Approved avatars' }} + options={{ label: "Approved avatars" }} /> - + @@ -183,9 +157,7 @@ class AdminUI extends Component { {this.state.showAutoEndSessionDialog && ( - this.setState({ showAutoEndSessionDialog: false }) - } + onCancel={() => this.setState({ showAutoEndSessionDialog: false })} onEndSession={() => { this.props.onEndSession(); this.setState({ sessionEnded: true }); @@ -201,8 +173,8 @@ class AdminUI extends Component { } } -import { IntlProvider } from 'react-intl'; -import { lang, messages } from './utils/i18n'; +import { IntlProvider } from "react-intl"; +import { lang, messages } from "./utils/i18n"; const mountUI = async (retPhxChannel, customRoutes, layout) => { let dataProvider; @@ -219,13 +191,10 @@ const mountUI = async (retPhxChannel, customRoutes, layout) => { await postgrestAuthenticatior.refreshPermsToken(); // Refresh perms regularly - permsTokenRefreshInterval = setInterval( - () => postgrestAuthenticatior.refreshPermsToken(), - 60000 - ); + permsTokenRefreshInterval = setInterval(() => postgrestAuthenticatior.refreshPermsToken(), 60000); } else { const server = configs.RETICULUM_SERVER || document.location.host; - dataProvider = postgrestClient('//' + server + '/api/postgrest'); + dataProvider = postgrestClient("//" + server + "/api/postgrest"); authProvider = postgrestAuthenticatior.createAuthProvider(); postgrestAuthenticatior.setAuthToken(store.state.credentials.token); } @@ -248,21 +217,21 @@ const mountUI = async (retPhxChannel, customRoutes, layout) => { onEndSession={onEndSession} /> , - document.getElementById('ui-root') + document.getElementById("ui-root") ); }; const HiddenAppBar = withStyles({ hideOnDesktop: { - '@media (min-width: 768px) and (min-height: 480px)': { - display: 'none', - }, - }, -})((props) => { + "@media (min-width: 768px) and (min-height: 480px)": { + display: "none" + } + } +})(props => { const { classes, ...other } = props; return ; }); -document.addEventListener('DOMContentLoaded', async () => { +document.addEventListener("DOMContentLoaded", async () => { const socket = await connectToReticulum(); if (store.state && store.state.credentials && store.state.credentials.token) { @@ -276,64 +245,45 @@ document.addEventListener('DOMContentLoaded', async () => { const homeRoute = ; const importRoute = ; - const accessRoute = ( - - ); + const accessRoute = ; const cdnRoute = ; const customRoutes = [homeRoute, importRoute, accessRoute, cdnRoute]; try { const appConfigSchema = schemaByCategories({ - hubs: toml.parse(await fetch('/hubs/schema.toml').then((r) => r.text())), + hubs: toml.parse(await fetch("/hubs/schema.toml").then(r => r.text())) }); const appConfigRoute = ( - ( - - )} - /> + } /> ); customRoutes.push(appConfigRoute); } catch (e) { - console.error('Could not initialize app config.', e); + console.error("Could not initialize app config.", e); } if (itaSchemas) { customRoutes.push( - } - /> + } /> ); } - const layout = (props) => ( - } - /> + const layout = props => ( + } /> ); - const redirectToLogin = () => - (document.location = '/?sign_in&sign_in_destination=admin'); + const redirectToLogin = () => (document.location = "/?sign_in&sign_in_destination=admin"); if (store.state.credentials && store.state.credentials.token) { // Reticulum global channel - const retPhxChannel = socket.channel(`ret`, { - hub_id: 'admin', - token: store.state.credentials.token, - }); + const retPhxChannel = socket.channel(`ret`, { hub_id: "admin", token: store.state.credentials.token }); retPhxChannel .join() - .receive('ok', async () => { + .receive("ok", async () => { mountUI(retPhxChannel, customRoutes, layout); }) - .receive('error', (res) => { - document.location = '/?sign_in&sign_in_destination=admin'; + .receive("error", res => { + document.location = "/?sign_in&sign_in_destination=admin"; console.error(res); }); } else { diff --git a/admin/src/react-components/admin-menu.js b/admin/src/react-components/admin-menu.js index b363b9a267..8bf1250c61 100644 --- a/admin/src/react-components/admin-menu.js +++ b/admin/src/react-components/admin-menu.js @@ -17,8 +17,10 @@ import ViewIcon from "@material-ui/icons/ViewList"; import SettingsIcon from "@material-ui/icons/Settings"; import Collapse from "@material-ui/core/Collapse"; import { getServiceDisplayName } from "../utils/ita"; -import HubsLogo from "../assets/images/hubs_logo.png" +import HubsCloudLogo from "../assets/images/hubs_cloud_dark.png"; import configs from "../utils/configs"; +import HubsLogo from "../assets/images/hubs_logo.png" + const mapStateToProps = state => ({ resources: getResources(state) diff --git a/admin/src/react-components/shared/CardSection.tsx b/admin/src/react-components/shared/CardSection.tsx index aff03fae73..9129ae61c4 100644 --- a/admin/src/react-components/shared/CardSection.tsx +++ b/admin/src/react-components/shared/CardSection.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Icon, Button } from '@mozilla/lilypad-ui'; +import { Icon, Button, ButtonSizesE } from '@mozilla/lilypad-ui'; type CardSectionPropsT = { cta: string; @@ -25,6 +25,7 @@ const CardSection = ({