Skip to content

Commit

Permalink
Add add-on preferences update support
Browse files Browse the repository at this point in the history
  • Loading branch information
keianhzo committed May 21, 2024
1 parent 644d101 commit d616838
Show file tree
Hide file tree
Showing 21 changed files with 328 additions and 32 deletions.
158 changes: 157 additions & 1 deletion src/addons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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<SystemConfigT>, system: SystemConfigT) {
return slot.findIndex(item => {
Expand Down Expand Up @@ -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;
}
Expand All @@ -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;
Expand All @@ -120,6 +136,146 @@ export function registerECSSidebarSection(section: (world: HubsWorld, selectedOb
extraSections.push(section);
}

const screenPreferencesDefs = new Map<string, PreferenceDefConfigT>();
export function getAddonsPreferencesDefs(): PreferenceScreenDefT {
return screenPreferencesDefs;
}

const screenPreferencesLabels = new Map<string, string>();
export function getAddonsPreferencesLabels(): PreferenceScreenLabelT {
return screenPreferencesLabels;
}

let xFormedScreenPreferencesCategories: Map<string, PreferencePrefsScreenItemT[]>;
const screenPreferencesCategories = new Map<string, PreferencePrefsScreenItemT[]>();
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<string, PreferencePrefsScreenItemT[]>();
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<PreferencePrefsScreenItemT>();
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<Pass, PostProcessOrderE>();
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 = {
Expand Down
9 changes: 5 additions & 4 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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";
Expand Down Expand Up @@ -79,7 +78,6 @@ export class App {
mediaDevicesManager?: MediaDevicesManager;
entryManager?: SceneEntryManager;
messageDispatch?: any;
store: Store;
componentRegistry: { [key: string]: AComponent[] };

mediaSearchStore = new MediaSearchStore();
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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) {
Expand Down
3 changes: 2 additions & 1 deletion src/cloud.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -117,6 +117,7 @@ function HubsCloudPage() {
);
}

const store = getStore();
window.APP = { store };

function CloudRoot() {
Expand Down
3 changes: 2 additions & 1 deletion src/discord.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand All @@ -21,6 +21,7 @@ class DiscordPage extends Component {
componentDidMount() {}

render() {
const store = getStore();
return (
<WrappedIntlProvider>
<ThemeProvider store={store}>
Expand Down
11 changes: 7 additions & 4 deletions src/hub.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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(
<WrappedIntlProvider>
<ThemeProvider store={store}>
Expand Down Expand Up @@ -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 }),
Expand Down Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions src/hubs.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
3 changes: 2 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
3 changes: 2 additions & 1 deletion src/link.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down

0 comments on commit d616838

Please sign in to comment.