From d616838eb59e7eb228a44c03ec24f56a9782b4c8 Mon Sep 17 00:00:00 2001 From: Manuel Martin Date: Tue, 21 May 2024 15:08:36 +0200 Subject: [PATCH] Add add-on preferences update support --- src/addons.ts | 158 ++++++++++++++++++++- src/app.ts | 9 +- src/cloud.js | 3 +- src/discord.js | 3 +- src/hub.js | 11 +- src/hubs.js | 1 + src/index.js | 3 +- src/link.js | 3 +- src/react-components/preferences-screen.js | 35 ++++- src/scene.js | 3 +- src/signin.js | 3 +- src/systems/single-action-button-system.js | 9 +- src/tokens.js | 3 +- src/types.ts | 85 +++++++++++ src/utils/phoenix-utils.js | 4 +- src/utils/store-instance.js | 11 ++ src/utils/store-instance.ts | 3 - src/utils/theme.js | 4 +- src/verify.js | 3 +- src/whats-new.js | 3 +- webpack.config.js | 3 + 21 files changed, 328 insertions(+), 32 deletions(-) create mode 100644 src/utils/store-instance.js delete mode 100644 src/utils/store-instance.ts diff --git a/src/addons.ts b/src/addons.ts index 286ca27c27..e8f13b5ec4 100644 --- a/src/addons.ts +++ b/src/addons.ts @@ -8,7 +8,14 @@ import { SystemOrderE, PrefabConfigT, NetworkSchemaConfigT, - ChatCommandConfigT + ChatCommandConfigT, + PreferenceConfigT, + PreferencePrefsScreenItemT, + PreferencePrefsScreenCategory, + PreferenceScreenLabelT, + PreferenceScreenDefT, + PreferenceDefConfigT, + PostProcessOrderE } from "./types"; import configs from "./utils/configs"; import { commonInflators, gltfInflators, jsxInflators } from "./utils/jsx-entity"; @@ -18,6 +25,8 @@ import { GLTFLinkResolverFn, gltfLinkResolvers } from "./inflators/model"; import { Object3D } from "three"; import { extraSections } from "./react-components/debug-panel/ECSSidebar"; import { shouldUseNewLoader } from "./hubs"; +import { SCHEMA } from "./storage/store"; +import { Pass } from "postprocessing"; function getNextIdx(slot: Array, system: SystemConfigT) { return slot.findIndex(item => { @@ -81,17 +90,20 @@ function registerChatCommand(command: ChatCommandConfigT) { export type AddonIdT = string; export type AddonNameT = string; export type AddonDescriptionT = string; +export type AddonOnLoadedFn = () => void; export type AddonOnReadyFn = (app: App, config?: JSON) => void; export interface InternalAddonConfigT { name: AddonNameT; description?: AddonDescriptionT; + onLoaded?: AddonOnLoadedFn; onReady?: AddonOnReadyFn; system?: SystemConfigT | SystemConfigT[]; inflator?: InflatorConfigT | InflatorConfigT[]; prefab?: PrefabConfigT | PrefabConfigT[]; networkSchema?: NetworkSchemaConfigT | NetworkSchemaConfigT[]; chatCommand?: ChatCommandConfigT | ChatCommandConfigT[]; + preference?: PreferenceConfigT | PreferenceConfigT[]; enabled?: boolean; config?: JSON | undefined; } @@ -107,6 +119,10 @@ export type AddonRegisterCallbackT = (app: App) => void; export function registerAddon(id: AddonIdT, config: AddonConfigT) { console.log(`Add-on ${id} registered`); pendingAddons.set(id, config); + registerPreferences(id, config); + if (config.onLoaded) { + config.onLoaded(); + } } export type GLTFParserCallbackFn = (parser: GLTFParser) => GLTFLoaderPlugin; @@ -120,6 +136,146 @@ export function registerECSSidebarSection(section: (world: HubsWorld, selectedOb extraSections.push(section); } +const screenPreferencesDefs = new Map(); +export function getAddonsPreferencesDefs(): PreferenceScreenDefT { + return screenPreferencesDefs; +} + +const screenPreferencesLabels = new Map(); +export function getAddonsPreferencesLabels(): PreferenceScreenLabelT { + return screenPreferencesLabels; +} + +let xFormedScreenPreferencesCategories: Map; +const screenPreferencesCategories = new Map(); +export function getAddonsPreferencesCategories(app: App): PreferencePrefsScreenCategory { + // We need to transform on the spot as when the preferences are added we don't yet have the hub user_data + // to know what addons are enabled in the room + if (!xFormedScreenPreferencesCategories) { + xFormedScreenPreferencesCategories = new Map(); + screenPreferencesCategories.forEach((categories, addonId) => { + if (isAddonEnabled(app, addonId)) { + const config = addons.get(addonId); + xFormedScreenPreferencesCategories.set(config?.name || addonId, categories); + } + }); + return xFormedScreenPreferencesCategories; + } else { + return xFormedScreenPreferencesCategories; + } +} + +function registerPreferences(addonId: string, addonConfig: AddonConfigT) { + const prefSchema = SCHEMA.definitions.preferences.properties; + function register(preference: PreferenceConfigT) { + for (const key in preference) { + if (!(key in prefSchema)) { + const prefDef = preference[key].prefDefinition; + if (key in prefSchema) { + throw new Error(`Preference ${key} already exists`); + } + (prefSchema as any)[key] = prefDef; + screenPreferencesDefs.set(key, prefDef); + + const prefConfig = preference[key]; + let categoryPrefs: PreferencePrefsScreenItemT[]; + if (screenPreferencesCategories.has(addonId)) { + categoryPrefs = screenPreferencesCategories.get(addonId)!; + } else { + categoryPrefs = new Array(); + screenPreferencesCategories.set(addonId, categoryPrefs); + } + categoryPrefs.push({ key, ...prefConfig.prefConfig }); + screenPreferencesLabels.set(key, prefConfig.prefConfig.description); + } else { + throw new Error("Preference already exists"); + } + } + } + + if (addonConfig.preference) { + if (Array.isArray(addonConfig.preference)) { + addonConfig.preference.forEach(preference => { + register(preference); + }); + } else { + register(addonConfig.preference); + } + } +} + +const fxOrder2Passes = { + [PostProcessOrderE.AfterScene]: 0, + [PostProcessOrderE.AfterBloom]: 0, + [PostProcessOrderE.AfterUI]: 0, + [PostProcessOrderE.AfterAA]: 0 +}; +const fx2Order = new Map(); +function afterSceneIdx() { + return 2 + fxOrder2Passes[PostProcessOrderE.AfterScene]; +} +function afterBloomIdx(app: App) { + let idx = afterSceneIdx(); + idx += fxOrder2Passes[PostProcessOrderE.AfterBloom]; + if (app.fx.bloomAndTonemapPass) { + idx++; + } + return idx; +} +function afterUIIdx(app: App) { + let idx = afterBloomIdx(app); + idx += fxOrder2Passes[PostProcessOrderE.AfterUI]; + return idx; +} +function afterAAIdx(app: App) { + let idx = afterUIIdx(app); + idx += fxOrder2Passes[PostProcessOrderE.AfterAA]; + return idx; +} +function getPassIdx(app: App, order: PostProcessOrderE) { + switch (order) { + case PostProcessOrderE.AfterScene: + return afterSceneIdx(); + case PostProcessOrderE.AfterBloom: + return afterBloomIdx(app); + case PostProcessOrderE.AfterUI: + return afterUIIdx(app); + case PostProcessOrderE.AfterAA: + return afterAAIdx(app); + } +} +export function registerPass(app: App, pass: Pass | Pass[], order: PostProcessOrderE) { + function register(pass: Pass) { + const nextIdx = getPassIdx(app, order) + 1; + fx2Order.set(pass, order); + fxOrder2Passes[order]++; + app.fx.composer?.addPass(pass, nextIdx); + } + + if (Array.isArray(pass)) { + pass.every(pass => register(pass)); + } else { + register(pass); + } +} + +export function unregisterPass(app: App, pass: Pass | Pass[]) { + function unregister(pass: Pass) { + if (fx2Order.has(pass)) { + const order = fx2Order.get(pass)!; + fxOrder2Passes[order]--; + app.fx.composer?.removePass(pass); + fx2Order.delete(pass); + } + } + + if (Array.isArray(pass)) { + pass.every(pass => unregister(pass)); + } else { + unregister(pass); + } +} + export function getAddonConfig(id: string): AdminAddonConfig { const adminAddonsConfig = configs.feature("addons_config"); let adminAddonConfig = { diff --git a/src/app.ts b/src/app.ts index 4a927ef10a..f238dcb685 100644 --- a/src/app.ts +++ b/src/app.ts @@ -2,7 +2,6 @@ import { addEntity, createWorld, IWorld } from "bitecs"; import "./aframe-to-bit-components"; import { AEntity, Networked, Object3DTag, Owned } from "./bit-components"; import MediaSearchStore from "./storage/media-search-store"; -import Store from "./storage/store"; import qsTruthy from "./utils/qs_truthy"; import type { AComponent, AScene } from "aframe"; @@ -28,7 +27,7 @@ import { DialogAdapter } from "./naf-dialog-adapter"; import { mainTick } from "./systems/hubs-systems"; import { waitForPreloads } from "./utils/preload"; import SceneEntryManager from "./scene-entry-manager"; -import { store } from "./utils/store-instance"; +import { getStore } from "./utils/store-instance"; import { addObject3DComponent } from "./utils/jsx-entity"; import { ElOrEid } from "./utils/bit-utils"; import { onAddonsInit } from "./addons"; @@ -79,7 +78,6 @@ export class App { mediaDevicesManager?: MediaDevicesManager; entryManager?: SceneEntryManager; messageDispatch?: any; - store: Store; componentRegistry: { [key: string]: AComponent[] }; mediaSearchStore = new MediaSearchStore(); @@ -130,7 +128,6 @@ export class App { } = {}; constructor() { - this.store = store; // TODO: Create accessor / update methods for these maps / set this.world.eid2obj = new Map(); this.world.eid2mat = new Map(); @@ -177,6 +174,10 @@ export class App { onAddonsInit(this); } + get store() { + return getStore(); + } + getSystem(id: SystemKeyT) { const systems = this.scene?.systems!; if (id in systems) { diff --git a/src/cloud.js b/src/cloud.js index 74817e46a9..5f1d3fb2fa 100644 --- a/src/cloud.js +++ b/src/cloud.js @@ -9,7 +9,7 @@ import { PageContainer } from "./react-components/layout/PageContainer"; import { AuthContextProvider } from "./react-components/auth/AuthContext"; import { Container } from "./react-components/layout/Container"; import { Button } from "./react-components/input/Button"; -import { store } from "./utils/store-instance"; +import { getStore } from "./utils/store-instance"; import registerTelemetry from "./telemetry"; import { FormattedMessage } from "react-intl"; @@ -117,6 +117,7 @@ function HubsCloudPage() { ); } +const store = getStore(); window.APP = { store }; function CloudRoot() { diff --git a/src/discord.js b/src/discord.js index ab4e3e03d1..437d7f8f44 100644 --- a/src/discord.js +++ b/src/discord.js @@ -11,7 +11,7 @@ import discordBotVideoWebM from "./assets/video/discord.webm"; import registerTelemetry from "./telemetry"; import { ThemeProvider } from "./react-components/styles/theme"; -import { store } from "./utils/store-instance"; +import { getStore } from "./utils/store-instance"; registerTelemetry("/discord", "Discord Landing Page"); @@ -21,6 +21,7 @@ class DiscordPage extends Component { componentDidMount() {} render() { + const store = getStore(); return ( diff --git a/src/hub.js b/src/hub.js index 66a29bf348..bd2eb1a554 100644 --- a/src/hub.js +++ b/src/hub.js @@ -183,6 +183,7 @@ import "./systems/audio-debug-system"; import "./systems/audio-gain-system"; import "./gltf-component-mappings"; +import { addons } from "./addons"; import { App, getScene } from "./app"; import MediaDevicesManager from "./utils/media-devices-manager"; import PinningHelper from "./utils/pinning-helper"; @@ -219,8 +220,6 @@ preload( }) ); -const store = window.APP.store; -store.update({ preferences: { shouldPromptForRefresh: false } }); // Clear flag that prompts for refresh from preference screen const mediaSearchStore = window.APP.mediaSearchStore; const OAUTH_FLOW_PERMS_TOKEN_KEY = "ret-oauth-flow-perms-token"; const NOISY_OCCUPANT_COUNT = 30; // Above this # of occupants, we stop posting join/leaves/renames @@ -273,7 +272,7 @@ import { exposeBitECSDebugHelpers } from "./bitecs-debug-helpers"; import { loadLegacyRoomObjects } from "./utils/load-legacy-room-objects"; import { loadSavedEntityStates } from "./utils/entity-state-utils"; import { shouldUseNewLoader } from "./utils/bit-utils"; -import { addons } from "./addons"; +import { getStore } from "./utils/store-instance"; const PHOENIX_RELIABLE_NAF = "phx-reliable"; NAF.options.firstSyncSource = PHOENIX_RELIABLE_NAF; @@ -354,6 +353,7 @@ function mountUI(props = {}) { qsTruthy("allow_idle") || (process.env.NODE_ENV === "development" && !qs.get("idle_timeout")); const forcedVREntryType = qsVREntryType; + const store = getStore(); root.render( @@ -607,7 +607,7 @@ function handleHubChannelJoined(entryManager, hubChannel, messageDispatch, data) onSendMessage: messageDispatch.dispatch, onLoaded: () => { audioSystem.setMediaGainOverride(1); - store.executeOnLoadActions(scene); + getStore().executeOnLoadActions(scene); }, onMediaSearchResultEntrySelected: (entry, selectAction) => scene.emit("action_selected_media_result_entry", { entry, selectAction }), @@ -739,6 +739,9 @@ async function runBotMode(scene, entryManager) { } document.addEventListener("DOMContentLoaded", async () => { + const store = getStore(); + store.update({ preferences: { shouldPromptForRefresh: false } }); // Clear flag that prompts for refresh from preference screen + if (!root) { const container = document.getElementById("ui-root"); root = createRoot(container); diff --git a/src/hubs.js b/src/hubs.js index 965e5b907d..48dc23a122 100644 --- a/src/hubs.js +++ b/src/hubs.js @@ -22,6 +22,7 @@ export * from "./utils/take-soft-ownership"; export * from "./utils/component-utils"; export * from "./utils/projection-mode"; export * from "./utils/assign-network-ids"; +export * from "./utils/store-instance"; export * from "./components/gltf-model-plus"; export * from "./inflators/model"; export * from "./inflators/physics-shape"; diff --git a/src/index.js b/src/index.js index 896c491f7a..414578772c 100644 --- a/src/index.js +++ b/src/index.js @@ -7,10 +7,11 @@ import { HomePage } from "./react-components/home/HomePage"; import { AuthContextProvider } from "./react-components/auth/AuthContext"; import "./react-components/styles/global.scss"; import { ThemeProvider } from "./react-components/styles/theme"; -import { store } from "./utils/store-instance"; +import { getStore } from "./utils/store-instance"; registerTelemetry("/home", "Hubs Home Page"); +const store = getStore(); window.APP = { store }; function HomeRoot() { diff --git a/src/link.js b/src/link.js index c6fc89f052..beb641e3f7 100644 --- a/src/link.js +++ b/src/link.js @@ -10,10 +10,11 @@ import LinkRoot from "./react-components/link-root"; import LinkChannel from "./utils/link-channel"; import { connectToReticulum } from "./utils/phoenix-utils"; import { ThemeProvider } from "./react-components/styles/theme"; -import { store } from "./utils/store-instance"; +import { getStore } from "./utils/store-instance"; registerTelemetry("/link", "Hubs Device Link"); +const store = getStore(); const linkChannel = new LinkChannel(store); (async () => { diff --git a/src/react-components/preferences-screen.js b/src/react-components/preferences-screen.js index 9d577c57c0..507568ce6b 100644 --- a/src/react-components/preferences-screen.js +++ b/src/react-components/preferences-screen.js @@ -27,6 +27,7 @@ import { isLockedDownDemoRoom } from "../utils/hub-utils"; import dropdownArrowUrl from "../assets/images/dropdown_arrow.png"; import dropdownArrow2xUrl from "../assets/images/dropdown_arrow@2x.png"; import { PermissionNotification } from "./room/PermissionNotifications"; +import { getAddonsPreferencesCategories, getAddonsPreferencesLabels } from "../addons"; export const CLIPPING_THRESHOLD_MIN = 0.0; export const CLIPPING_THRESHOLD_MAX = 0.1; @@ -582,12 +583,26 @@ class PreferenceListItem extends Component { const isCheckbox = this.props.itemProps.prefType === PREFERENCE_LIST_ITEM_TYPE.CHECK_BOX; const isCustomComponent = this.props.itemProps.prefType === PREFERENCE_LIST_ITEM_TYPE.CUSTOM_COMPONENT; const isSmallScreen = window.innerWidth < 600; - const label = preferenceLabels[this.props.storeKey] && ( + let labelText; + if (preferenceLabels[this.props.storeKey]) { + labelText = intl.formatMessage(preferenceLabels[this.props.storeKey]); + } else { + const addonLabels = getAddonsPreferencesLabels(); + labelText = addonLabels.get(this.props.storeKey); + } + let labelTooltip; + if (this.props.itemProps.tooltipKey) { + labelTooltip = intl.formatMessage(preferenceLabels[this.props.itemProps.tooltipKey]); + } else { + const addonLabels = getAddonsPreferencesLabels(); + labelTooltip = addonLabels.get(this.props.storeKey); + } + const label = ( - {intl.formatMessage(preferenceLabels[this.props.storeKey])} + {labelText} ); const prefSchema = this.props.store.schema.definitions.preferences.properties; @@ -695,7 +710,8 @@ const CATEGORY_MOVEMENT = 3; const CATEGORY_TOUCHSCREEN = 4; const CATEGORY_ACCESSIBILITY = 5; const CATEGORY_GRAPHICS = 6; -const TOP_LEVEL_CATEGORIES = [CATEGORY_AUDIO, CATEGORY_CONTROLS, CATEGORY_MISC]; +const CATEGORY_ADDONS = 7; +const TOP_LEVEL_CATEGORIES = [CATEGORY_AUDIO, CATEGORY_CONTROLS, CATEGORY_MISC, CATEGORY_ADDONS]; const categoryNames = defineMessages({ [CATEGORY_AUDIO]: { id: "preferences-screen.category.audio", defaultMessage: "Audio" }, [CATEGORY_CONTROLS]: { id: "preferences-screen.category.controls", defaultMessage: "Controls" }, @@ -703,7 +719,8 @@ const categoryNames = defineMessages({ [CATEGORY_MOVEMENT]: { id: "preferences-screen.category.movement", defaultMessage: "Movement" }, [CATEGORY_TOUCHSCREEN]: { id: "preferences-screen.category.touchscreen", defaultMessage: "Touchscreen" }, [CATEGORY_ACCESSIBILITY]: { id: "preferences-screen.category.accessibility", defaultMessage: "Accessibility" }, - [CATEGORY_GRAPHICS]: { id: "preferences-screen.category.graphics", defaultMessage: "Graphics" } + [CATEGORY_GRAPHICS]: { id: "preferences-screen.category.graphics", defaultMessage: "Graphics" }, + [CATEGORY_ADDONS]: { id: "preferences-screen.category.add-ons", defaultMessage: "Add-Ons" } }); function NavItem({ ariaLabel, title, onClick, selected }) { @@ -1427,6 +1444,11 @@ class PreferencesScreen extends Component { ); } + const addOnCategories = []; + getAddonsPreferencesCategories(APP).forEach((prefItems, prefCatName) => { + addOnCategories.push({ name: prefCatName, items: prefItems.map(toItem).filter(item => !!item) }); + }); + return new Map([ [ CATEGORY_AUDIO, @@ -1460,7 +1482,8 @@ class PreferencesScreen extends Component { items: items.get(CATEGORY_GRAPHICS) } ] - ] + ], + [CATEGORY_ADDONS, addOnCategories] ]); } diff --git a/src/scene.js b/src/scene.js index acd784692b..aab334b4e9 100644 --- a/src/scene.js +++ b/src/scene.js @@ -10,7 +10,7 @@ import registerTelemetry from "./telemetry"; import { disableiOSZoom } from "./utils/disable-ios-zoom"; import { connectToReticulum, fetchReticulumAuthenticatedWithToken } from "./utils/phoenix-utils"; import "./utils/theme"; -import { store } from "./utils/store-instance"; +import { getStore } from "./utils/store-instance"; function mountUI(props = {}) { const container = document.getElementById("ui-root"); @@ -89,6 +89,7 @@ function onReady() { disableiOSZoom(); + const store = getStore(); const sceneId = parseSceneId(document.location); console.log(`Scene ID: ${sceneId}`); remountUI({ sceneId, store }); diff --git a/src/signin.js b/src/signin.js index 2337e18c5b..122abed7cc 100644 --- a/src/signin.js +++ b/src/signin.js @@ -10,10 +10,11 @@ import "./react-components/styles/global.scss"; import "./assets/stylesheets/globals.scss"; import { Center } from "./react-components/layout/Center"; import { ThemeProvider } from "./react-components/styles/theme"; -import { store } from "./utils/store-instance"; +import { getStore } from "./utils/store-instance"; registerTelemetry("/signin", "Hubs Sign In Page"); +const store = getStore(); window.APP = { store }; function SignInRoot() { diff --git a/src/systems/single-action-button-system.js b/src/systems/single-action-button-system.js index 850b94eeca..28e389c724 100644 --- a/src/systems/single-action-button-system.js +++ b/src/systems/single-action-button-system.js @@ -1,4 +1,4 @@ -import { addComponent, defineQuery, hasComponent, removeComponent } from "bitecs"; +import { addComponent, defineQuery, enterQuery, hasComponent, removeComponent } from "bitecs"; import { HoverButton, HoveredHandLeft, @@ -80,13 +80,16 @@ function applyTheme() { textHoverColor: new THREE.Color(0xffffff) }; } -onThemeChanged(applyTheme); -applyTheme(); const hoverComponents = [HoveredRemoteRight, HoveredRemoteLeft, HoveredHandRight, HoveredHandLeft]; const hoverButtonsQuery = defineQuery([HoverButton]); +const hoverButtonsEnterQuery = enterQuery(hoverButtonsQuery); function hoverButtonSystem(world) { + if (hoverButtonsEnterQuery(world).length > 0) { + onThemeChanged(applyTheme); + applyTheme(); + } hoverButtonsQuery(world).forEach(function (eid) { const obj = world.eid2obj.get(eid); const isHovered = hasAnyComponent(world, hoverComponents, eid); diff --git a/src/tokens.js b/src/tokens.js index b300dcd13b..236e614f13 100644 --- a/src/tokens.js +++ b/src/tokens.js @@ -10,10 +10,11 @@ import "./react-components/styles/global.scss"; import { TokenPageLayout } from "./react-components/tokens/TokenPageLayout"; import configs from "./utils/configs"; import { ThemeProvider } from "./react-components/styles/theme"; -import { store } from "./utils/store-instance"; +import { getStore } from "./utils/store-instance"; registerTelemetry("/tokens", "Backend API Tokens Page"); +const store = getStore(); window.APP = { store }; function TokensRoot() { diff --git a/src/types.ts b/src/types.ts index f2e595eaa5..db174a5ff0 100644 --- a/src/types.ts +++ b/src/types.ts @@ -15,6 +15,13 @@ export enum SystemOrderE { AfterRender = 500 } +export enum PostProcessOrderE { + AfterScene = 0, + AfterBloom = 1, + AfterUI = 2, + AfterAA = 3 +} + export type CoreSystemKeyT = keyof AScene["systems"]; export type HubsSystemKeyT = keyof HubsSystems; export type SystemKeyT = CoreSystemKeyT | HubsSystemKeyT; @@ -93,6 +100,84 @@ export interface ChatCommandConfigT { command: ChatCommandCallbackFn; } +/** + * This has to be kept in sync with the preferences-screen PREFERENCE_LIST_ITEM_TYPE + */ +export enum PREFERENCE_LIST_ITEM_TYPE { + CHECK_BOX = 1, + SELECT = 2, + NUMBER_WITH_RANGE = 3, + MAX_RESOLUTION = 4, + MAP_COUNT = 5, + CUSTOM_COMPONENT = 6 +} + +export type PreferenceDefConfigT = { + type: "string" | "number" | "bool" | "object"; + default: string | number | boolean | object | undefined; +}; + +export type PreferenceSelectT = { + value: string | number; + text: string; +}; + +export type PreferenceRangeT = { + min: number; + max: number; + step: number; + digits: number; +}; + +export type PreferenceUIConfigT = + | { + prefType: PREFERENCE_LIST_ITEM_TYPE.CHECK_BOX; + description: string; + promptForRefresh?: boolean; + hidden?: () => boolean; + disableIfFalse?: string; + } + | { + prefType: PREFERENCE_LIST_ITEM_TYPE.NUMBER_WITH_RANGE; + description: string; + promptForRefresh?: boolean; + hidden?: () => boolean; + disableIfFalse?: string; + min: number; + max: number; + step: number; + digits: number; + } + | { + prefType: PREFERENCE_LIST_ITEM_TYPE.SELECT; + description: string; + promptForRefresh?: boolean; + hidden?: () => boolean; + disableIfFalse?: string; + options?: PreferenceSelectT[]; + } + | { + prefType: PREFERENCE_LIST_ITEM_TYPE.MAP_COUNT; + description: string; + promptForRefresh?: boolean; + hidden?: () => boolean; + disableIfFalse?: string; + defaultValue?: number; + text?: string; + }; + +export type PreferencePrefsScreenItemT = { key: string | PreferenceUIConfigT }; +export type PreferencePrefsScreenCategory = Map; +export type PreferenceScreenLabelT = Map; +export type PreferenceScreenDefT = Map; + +export type PreferenceConfigT = { + [key: string]: { + prefDefinition: PreferenceDefConfigT; + prefConfig: PreferenceUIConfigT; + }; +}; + export type SoundDefT = { id: number; url: string; diff --git a/src/utils/phoenix-utils.js b/src/utils/phoenix-utils.js index df1d4bbff9..e484780b45 100644 --- a/src/utils/phoenix-utils.js +++ b/src/utils/phoenix-utils.js @@ -2,7 +2,7 @@ import { Socket } from "phoenix"; import { generateHubName } from "../utils/name-generation"; import configs from "../utils/configs"; import { sleep } from "../utils/async-utils"; -import { store } from "../utils/store-instance"; +import { getStore } from "../utils/store-instance"; export function hasReticulumServer() { return !!configs.RETICULUM_SERVER; @@ -198,6 +198,7 @@ export function fetchReticulumAuthenticatedWithToken(token, url, method = "GET", }); } export function fetchReticulumAuthenticated(url, method = "GET", payload) { + const store = getStore(); return fetchReticulumAuthenticatedWithToken(store.state.credentials.token, url, method, payload); } @@ -210,6 +211,7 @@ export async function createAndRedirectToNewHub(name, sceneId, replace, qs) { } const headers = { "content-type": "application/json" }; + const store = getStore(); if (store.state && store.state.credentials.token) { headers.authorization = `bearer ${store.state.credentials.token}`; } diff --git a/src/utils/store-instance.js b/src/utils/store-instance.js new file mode 100644 index 0000000000..6332f6c8e4 --- /dev/null +++ b/src/utils/store-instance.js @@ -0,0 +1,11 @@ +import Store from "../storage/store"; + +let store; +export function getStore() { + if (store) { + return store; + } else { + store = new Store(); + return store; + } +} diff --git a/src/utils/store-instance.ts b/src/utils/store-instance.ts deleted file mode 100644 index bb7c358f31..0000000000 --- a/src/utils/store-instance.ts +++ /dev/null @@ -1,3 +0,0 @@ -import Store from "../storage/store"; - -export const store = new Store(); diff --git a/src/utils/theme.js b/src/utils/theme.js index 654865114f..9597716f05 100644 --- a/src/utils/theme.js +++ b/src/utils/theme.js @@ -1,5 +1,5 @@ import { waitForDOMContentLoaded } from "./async-utils"; -import { store } from "./store-instance"; +import { getStore } from "./store-instance"; // NOTE these should be synchronized with the top of shared.scss const DEFAULT_ACTION_COLOR = "#FF3464"; @@ -99,6 +99,7 @@ function tryGetTheme(themeId) { } function getCurrentTheme() { + const store = getStore(); const preferredThemeId = store.state?.preferences?.theme; return tryGetTheme(preferredThemeId); } @@ -153,6 +154,7 @@ function applyThemeToBody() { } function onThemeChanged(listener) { + const store = getStore(); store.addEventListener("themechanged", listener); const [_darkModeQuery, removeDarkModeListener] = registerDarkModeQuery(listener); diff --git a/src/verify.js b/src/verify.js index 1546d6c562..03719c9aad 100644 --- a/src/verify.js +++ b/src/verify.js @@ -10,10 +10,11 @@ import "./assets/stylesheets/globals.scss"; import { PageContainer } from "./react-components/layout/PageContainer"; import { Center } from "./react-components/layout/Center"; import { ThemeProvider } from "./react-components/styles/theme"; -import { store } from "./utils/store-instance"; +import { getStore } from "./utils/store-instance"; registerTelemetry("/verify", "Hubs Verify Email Page"); +const store = getStore(); window.APP = { store }; function VerifyRoot() { diff --git a/src/whats-new.js b/src/whats-new.js index de1887e296..af9d68342e 100644 --- a/src/whats-new.js +++ b/src/whats-new.js @@ -5,8 +5,9 @@ import markdownit from "markdown-it"; import { FormattedMessage } from "react-intl"; import { WrappedIntlProvider } from "./react-components/wrapped-intl-provider"; import { AuthContextProvider } from "./react-components/auth/AuthContext"; -import { store } from "./utils/store-instance"; +import { getStore } from "./utils/store-instance"; +const store = getStore(); window.APP = { store }; import registerTelemetry from "./telemetry"; diff --git a/webpack.config.js b/webpack.config.js index c52b924df0..0a3f660491 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -341,6 +341,9 @@ module.exports = async (env, argv) => { filename: "assets/js/[name]-[chunkhash].js", publicPath: process.env.BASE_ASSETS_PATH || "" }, + optimization: { + minimize: argv.mode === "production" ? true : false + }, target: ["web", "es5"], // use es5 for webpack runtime to maximize compatibility devtool: argv.mode === "production" ? "source-map" : "inline-source-map", devServer: {