@expofp/debug 3.0.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/lib/debugUi/DebugOverlay.d.ts +7 -0
- package/dist/lib/debugUi/DebugOverlay.js +7 -0
- package/dist/lib/debugUi/DebugTabs.d.ts +6 -0
- package/dist/lib/debugUi/DebugTabs.js +6 -0
- package/dist/lib/debugUi/DebugUi.d.ts +5 -0
- package/dist/lib/debugUi/DebugUi.js +31 -0
- package/dist/lib/debugUi/LogTab.d.ts +2 -0
- package/dist/lib/debugUi/LogTab.js +6 -0
- package/dist/lib/debugUi/Settings.d.ts +2 -0
- package/dist/lib/debugUi/Settings.js +7 -0
- package/dist/lib/debugUi/SettingsItem.d.ts +6 -0
- package/dist/lib/debugUi/SettingsItem.js +5 -0
- package/dist/lib/debugUi/SettingsTab.d.ts +2 -0
- package/dist/lib/debugUi/SettingsTab.js +13 -0
- package/dist/lib/debugUi/SideButton.d.ts +4 -0
- package/dist/lib/debugUi/SideButton.js +37 -0
- package/dist/lib/debugUi/index.d.ts +2 -0
- package/dist/lib/debugUi/index.js +1 -0
- package/dist/lib/debugUi/renderDebugUi.d.ts +2 -0
- package/dist/lib/debugUi/renderDebugUi.js +24 -0
- package/dist/lib/editors/BooleanEditor.d.ts +3 -0
- package/dist/lib/editors/BooleanEditor.js +6 -0
- package/dist/lib/editors/EditorProps.d.ts +7 -0
- package/dist/lib/editors/EditorProps.js +1 -0
- package/dist/lib/editors/EnumEditor.d.ts +5 -0
- package/dist/lib/editors/EnumEditor.js +16 -0
- package/dist/lib/init-debug.d.ts +2 -0
- package/dist/lib/init-debug.js +19 -0
- package/dist/lib/initDebug.d.ts +2 -0
- package/dist/lib/initDebug.js +15 -0
- package/dist/lib/settings/editor-props.d.ts +7 -0
- package/dist/lib/settings/editor-props.js +1 -0
- package/dist/lib/settings/index.d.ts +5 -0
- package/dist/lib/settings/index.js +4 -0
- package/dist/lib/settings/register-boolean-setting.d.ts +3 -0
- package/dist/lib/settings/register-boolean-setting.js +8 -0
- package/dist/lib/settings/register-enum-setting.d.ts +3 -0
- package/dist/lib/settings/register-enum-setting.js +10 -0
- package/dist/lib/settings/register-setting.d.ts +9 -0
- package/dist/lib/settings/register-setting.js +65 -0
- package/dist/lib/settings/registerBooleanSetting.d.ts +3 -0
- package/dist/lib/settings/registerBooleanSetting.js +8 -0
- package/dist/lib/settings/registerEnumSetting.d.ts +3 -0
- package/dist/lib/settings/registerEnumSetting.js +10 -0
- package/dist/lib/settings/registerSetting.d.ts +9 -0
- package/dist/lib/settings/registerSetting.js +64 -0
- package/dist/lib/settings/registeredSettings.d.ts +3 -0
- package/dist/lib/settings/registeredSettings.js +2 -0
- package/dist/lib/settings/reset-all-settings.d.ts +2 -0
- package/dist/lib/settings/reset-all-settings.js +4 -0
- package/dist/lib/settings/resetAllSettings.d.ts +2 -0
- package/dist/lib/settings/resetAllSettings.js +4 -0
- package/dist/lib/settings/setting-registry.d.ts +4 -0
- package/dist/lib/settings/setting-registry.js +2 -0
- package/dist/lib/ui/boolean-editor.d.ts +3 -0
- package/dist/lib/ui/boolean-editor.js +6 -0
- package/dist/lib/ui/debug-overlay.d.ts +7 -0
- package/dist/lib/ui/debug-overlay.js +7 -0
- package/dist/lib/ui/debug-tabs.d.ts +6 -0
- package/dist/lib/ui/debug-tabs.js +6 -0
- package/dist/lib/ui/debug-ui.d.ts +7 -0
- package/dist/lib/ui/debug-ui.js +34 -0
- package/dist/lib/ui/enum-editor.d.ts +5 -0
- package/dist/lib/ui/enum-editor.js +16 -0
- package/dist/lib/ui/index.d.ts +2 -0
- package/dist/lib/ui/index.js +1 -0
- package/dist/lib/ui/log-tab.d.ts +2 -0
- package/dist/lib/ui/log-tab.js +6 -0
- package/dist/lib/ui/render-debug-ui.d.ts +2 -0
- package/dist/lib/ui/render-debug-ui.js +29 -0
- package/dist/lib/ui/settings-item.d.ts +6 -0
- package/dist/lib/ui/settings-item.js +5 -0
- package/dist/lib/ui/settings-list.d.ts +3 -0
- package/dist/lib/ui/settings-list.js +7 -0
- package/dist/lib/ui/settings-tab.d.ts +2 -0
- package/dist/lib/ui/settings-tab.js +13 -0
- package/dist/lib/ui/side-button.d.ts +5 -0
- package/dist/lib/ui/side-button.js +37 -0
- package/dist/lib/utils/add-debug-secret-listener.d.ts +2 -0
- package/dist/lib/utils/add-debug-secret-listener.js +38 -0
- package/dist/lib/utils/addDebugSecretListener.d.ts +2 -0
- package/dist/lib/utils/addDebugSecretListener.js +31 -0
- package/package.json +36 -0
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import '@radix-ui/themes/styles.css';
|
|
3
|
+
import { Box, Container, Theme } from '@radix-ui/themes';
|
|
4
|
+
import { DebugTabs } from './DebugTabs.js';
|
|
5
|
+
export const DebugOverlay = ({ onClose }) => {
|
|
6
|
+
return (_jsx(Theme, { appearance: "dark", children: _jsx(Box, { position: "fixed", top: "0", right: "0", width: "100vw", height: "100dvh", overflowY: "auto", style: { backgroundColor: 'black' }, children: _jsx(Container, { size: "4", px: "2", children: _jsx(DebugTabs, { onClose: onClose }) }) }) }));
|
|
7
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Cross1Icon } from '@radix-ui/react-icons';
|
|
3
|
+
import { Button, Tabs } from '@radix-ui/themes';
|
|
4
|
+
import { LogTab } from './LogTab.js';
|
|
5
|
+
import { SettingsTab } from './SettingsTab.js';
|
|
6
|
+
export const DebugTabs = ({ onClose }) => (_jsxs(Tabs.Root, { defaultValue: "settingsTab", children: [_jsxs(Tabs.List, { children: [_jsx(Tabs.Trigger, { value: "settingsTab", children: "Settings" }), _jsx(Tabs.Trigger, { value: "logTab", children: "Log" }), _jsxs(Button, { ml: "auto", variant: "soft", mt: "3px", color: "gray", onClick: onClose, children: [_jsx(Cross1Icon, { width: "16", height: "16" }), " Close"] })] }), _jsx(Tabs.Content, { value: "settingsTab", children: _jsx(SettingsTab, {}) }), _jsx(Tabs.Content, { value: "logTab", children: _jsx(LogTab, {}) })] }));
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { lazy, Suspense, useEffect, useState } from 'react';
|
|
3
|
+
import { addDebugSecretListener } from '../utils/addDebugSecretListener.js';
|
|
4
|
+
import { SideButton } from './SideButton.js';
|
|
5
|
+
const LazyDebugOverlay = lazy(async () => ({
|
|
6
|
+
default: (await import('./DebugOverlay.js')).DebugOverlay,
|
|
7
|
+
}));
|
|
8
|
+
export const DebugUi = (props) => {
|
|
9
|
+
const [isOpen, setIsOpen] = useState(props.open);
|
|
10
|
+
const [debugButtonEnabled, setDebugButtonEnabled] = props.useDebugButtonState();
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
return addDebugSecretListener(() => {
|
|
13
|
+
setDebugButtonEnabled(true);
|
|
14
|
+
setIsOpen(true);
|
|
15
|
+
});
|
|
16
|
+
}, []);
|
|
17
|
+
// set visibility of efp floorplan to none when debug ui is open
|
|
18
|
+
// otherwise, z-index issues occur
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
const efpFloorplan = document.getElementsByClassName('expofp-floorplan-default')[0];
|
|
21
|
+
if (efpFloorplan) {
|
|
22
|
+
efpFloorplan.style.visibility = isOpen ? 'hidden' : 'visible';
|
|
23
|
+
}
|
|
24
|
+
}, [isOpen]);
|
|
25
|
+
if (isOpen) {
|
|
26
|
+
return (_jsx(Suspense, { fallback: null, children: _jsx(LazyDebugOverlay, { onClose: () => {
|
|
27
|
+
setIsOpen(false);
|
|
28
|
+
} }) }));
|
|
29
|
+
}
|
|
30
|
+
return debugButtonEnabled ? _jsx(SideButton, { onClick: () => setIsOpen(true) }) : null;
|
|
31
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { InfoCircledIcon } from '@radix-ui/react-icons';
|
|
3
|
+
import { Box, Callout } from '@radix-ui/themes';
|
|
4
|
+
export const LogTab = () => {
|
|
5
|
+
return (_jsx(Box, { pt: "20px", children: _jsx(Box, { mb: "20px", children: _jsxs(Callout.Root, { children: [_jsx(Callout.Icon, { children: _jsx(InfoCircledIcon, {}) }), _jsx(Callout.Text, { children: "Implement to intercept console.info, console.warn and console.error and output them here when logging enabled." })] }) }) }));
|
|
6
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Skeleton } from '@radix-ui/themes';
|
|
3
|
+
import { Suspense } from 'react';
|
|
4
|
+
import { editors } from '../settings/registeredSettings.js';
|
|
5
|
+
export const Settings = () => {
|
|
6
|
+
return editors.map((Editor, index) => (_jsx(Suspense, { fallback: _jsx(Skeleton, { height: "60px" }), children: _jsx(Editor, {}) }, index)));
|
|
7
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Heading, Separator } from '@radix-ui/themes';
|
|
3
|
+
export const SettingsItem = ({ label, children }) => {
|
|
4
|
+
return (_jsxs(_Fragment, { children: [_jsxs(Box, { children: [_jsx(Heading, { size: "4", children: label }), _jsx(Box, { mt: "10px", children: children })] }), _jsx(Separator, { size: "4", my: "20px" })] }));
|
|
5
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { InfoCircledIcon } from '@radix-ui/react-icons';
|
|
3
|
+
import { AlertDialog, Box, Button, Callout, Flex } from '@radix-ui/themes';
|
|
4
|
+
import { resetAllSettings } from '../settings/resetAllSettings.js';
|
|
5
|
+
import { Settings } from './Settings.js';
|
|
6
|
+
export const SettingsTab = () => {
|
|
7
|
+
return (_jsxs(Box, { pt: "20px", children: [_jsx(Box, { mb: "20px", children: _jsxs(Callout.Root, { children: [_jsx(Callout.Icon, { children: _jsx(InfoCircledIcon, {}) }), _jsx(Callout.Text, { children: "These settings will persist across page reloads in your browser." })] }) }), _jsx(Settings, {}), _jsxs(Flex, { gap: "3", mt: "40px", justify: "center", children: [_jsx(Button, { onClick: () => {
|
|
8
|
+
window.location.reload();
|
|
9
|
+
}, children: "Reload with the applied changes" }), _jsxs(AlertDialog.Root, { children: [_jsx(AlertDialog.Trigger, { children: _jsx(Button, { color: "red", children: "Reset all and reload" }) }), _jsxs(AlertDialog.Content, { maxWidth: "450px", children: [_jsx(AlertDialog.Title, { children: "Reset all and reload" }), _jsx(AlertDialog.Description, { size: "2", children: "This will reset all settings, disable debug mode, and reload the page." }), _jsxs(Flex, { gap: "3", mt: "4", justify: "end", children: [_jsx(AlertDialog.Cancel, { children: _jsx(Button, { variant: "soft", color: "gray", children: "Cancel" }) }), _jsx(AlertDialog.Action, { children: _jsx(Button, { variant: "solid", color: "red", onClick: () => {
|
|
10
|
+
resetAllSettings();
|
|
11
|
+
window.location.reload();
|
|
12
|
+
}, children: "Reset all and reload" }) })] })] })] })] })] }));
|
|
13
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
export const SideButton = ({ onClick }) => {
|
|
3
|
+
const size = 64;
|
|
4
|
+
const width = 20;
|
|
5
|
+
const radius = 14;
|
|
6
|
+
return (_jsx("button", { type: "button", style: {
|
|
7
|
+
position: 'fixed',
|
|
8
|
+
right: 0,
|
|
9
|
+
top: '50%',
|
|
10
|
+
transform: 'translateY(-50%)',
|
|
11
|
+
padding: 0,
|
|
12
|
+
border: 'none',
|
|
13
|
+
background: 'transparent',
|
|
14
|
+
cursor: 'pointer',
|
|
15
|
+
zIndex: 9999,
|
|
16
|
+
boxSizing: 'border-box',
|
|
17
|
+
}, onClick: onClick, children: _jsx("div", { style: {
|
|
18
|
+
width,
|
|
19
|
+
height: size,
|
|
20
|
+
paddingLeft: 4,
|
|
21
|
+
display: 'flex',
|
|
22
|
+
alignItems: 'center',
|
|
23
|
+
justifyContent: 'center',
|
|
24
|
+
background: 'var(--side-tab-bg, rgba(0,0,0,0.5))',
|
|
25
|
+
color: 'white',
|
|
26
|
+
borderRadius: `${radius}px 0 0 ${radius}px`,
|
|
27
|
+
fontSize: Math.round(size * 0.25),
|
|
28
|
+
lineHeight: 1,
|
|
29
|
+
userSelect: 'none',
|
|
30
|
+
cursor: 'pointer',
|
|
31
|
+
filter: 'grayscale(0.5)',
|
|
32
|
+
boxSizing: 'border-box',
|
|
33
|
+
}, children: _jsx(ChevronLeftSvg, { size: size * 0.7 }) }) }));
|
|
34
|
+
};
|
|
35
|
+
function ChevronLeftSvg({ size = 18, strokeWidth = 3, ...props }) {
|
|
36
|
+
return (_jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", ...props, children: _jsx("path", { fill: "none", d: "M14.5 5.5L8.5 12l6 6.5", stroke: "currentColor", strokeWidth: strokeWidth, strokeLinecap: "round", strokeLinejoin: "round" }) }));
|
|
37
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { renderDebugUi } from './renderDebugUi.js';
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { registerBooleanSetting } from '../settings/index.js';
|
|
3
|
+
import { addDebugSecretListener } from '../utils/addDebugSecretListener.js';
|
|
4
|
+
export function renderDebugUi() {
|
|
5
|
+
const showDebugButtonSetting = registerBooleanSetting('Show Debug Button Overlay');
|
|
6
|
+
const render = async (open) => {
|
|
7
|
+
const { DebugUi } = await import('./DebugUi.js');
|
|
8
|
+
const { createRoot } = await import('react-dom/client');
|
|
9
|
+
const rootElement = document.createElement('div');
|
|
10
|
+
document.body.appendChild(rootElement);
|
|
11
|
+
const root = createRoot(rootElement);
|
|
12
|
+
root.render(_jsx(DebugUi, { useDebugButtonState: showDebugButtonSetting.useState, open: open }));
|
|
13
|
+
};
|
|
14
|
+
if (showDebugButtonSetting.get()) {
|
|
15
|
+
render(false);
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
const unsuscribe = addDebugSecretListener(() => {
|
|
19
|
+
unsuscribe();
|
|
20
|
+
showDebugButtonSetting.set(true);
|
|
21
|
+
render(true);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Flex, Switch } from '@radix-ui/themes';
|
|
3
|
+
import { SettingsItem } from '../debugUi/SettingsItem.js';
|
|
4
|
+
export const BooleanEditor = (props) => {
|
|
5
|
+
return (_jsx(SettingsItem, { label: props.label, children: _jsx(Flex, { children: _jsx(Switch, { checked: props.value, onCheckedChange: (checked) => props.onChange(checked) }) }) }));
|
|
6
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Select } from '@radix-ui/themes';
|
|
3
|
+
import { SettingsItem } from '../debugUi/SettingsItem.js';
|
|
4
|
+
export function EnumEditor(props) {
|
|
5
|
+
const stringMode = props.context.values.every((v) => typeof v === 'string');
|
|
6
|
+
const stringify = stringMode ? (v) => v : (v) => JSON.stringify(v);
|
|
7
|
+
const parse = stringMode ? (v) => v : (v) => JSON.parse(v);
|
|
8
|
+
const value = stringify(props.value);
|
|
9
|
+
const values = props.context.values.map((v) => ({
|
|
10
|
+
value: stringify(v),
|
|
11
|
+
label: stringify(v),
|
|
12
|
+
})) || [];
|
|
13
|
+
return (_jsx(SettingsItem, { label: "Data.js validation", children: _jsxs(Select.Root, { value: value, onValueChange: (value) => {
|
|
14
|
+
props.onChange(parse(value));
|
|
15
|
+
}, children: [_jsx(Select.Trigger, {}), _jsx(Select.Content, { children: values.map((v) => (_jsx(Select.Item, { value: v.value, children: v.label }, v.value))) })] }) }));
|
|
16
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
let debugInitialized = false;
|
|
2
|
+
export function initDebug() {
|
|
3
|
+
if (debugInitialized) {
|
|
4
|
+
return;
|
|
5
|
+
}
|
|
6
|
+
debugInitialized = true;
|
|
7
|
+
// if not browser, exit
|
|
8
|
+
if (typeof window === 'undefined' && typeof document === 'undefined') {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
// add a simple debug button to the page (right, center, fixed position)
|
|
12
|
+
import('./ui/index.js')
|
|
13
|
+
.then(({ renderDebugUi }) => {
|
|
14
|
+
renderDebugUi();
|
|
15
|
+
})
|
|
16
|
+
.catch(() => {
|
|
17
|
+
/* debug UI failed to load */
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
let debugInitialized = false;
|
|
2
|
+
export function initDebug() {
|
|
3
|
+
if (debugInitialized) {
|
|
4
|
+
return;
|
|
5
|
+
}
|
|
6
|
+
debugInitialized = true;
|
|
7
|
+
// if not browser, exit
|
|
8
|
+
if (typeof window === 'undefined' && typeof document === 'undefined') {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
// add a simple debug button to the page (right, center, fixed position)
|
|
12
|
+
import('./debugUi/index.js').then(({ renderDebugUi }) => {
|
|
13
|
+
renderDebugUi();
|
|
14
|
+
});
|
|
15
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { registerSetting } from './register-setting.js';
|
|
3
|
+
export function registerBooleanSetting(label) {
|
|
4
|
+
const BooleanEditorLazy = React.lazy(() => import('../ui/boolean-editor.js').then((mod) => ({
|
|
5
|
+
default: mod.BooleanEditor,
|
|
6
|
+
})));
|
|
7
|
+
return registerSetting(label, false, BooleanEditorLazy);
|
|
8
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { registerSetting } from './register-setting.js';
|
|
3
|
+
export function registerEnumSetting(label, values, defaultValue) {
|
|
4
|
+
const EnumEditorLazy = React.lazy(() => import('../ui/enum-editor.js').then((mod) => ({
|
|
5
|
+
default: mod.EnumEditor,
|
|
6
|
+
})));
|
|
7
|
+
return registerSetting(label, defaultValue, EnumEditorLazy, {
|
|
8
|
+
values,
|
|
9
|
+
});
|
|
10
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { EditorProps } from './editor-props.js';
|
|
2
|
+
export interface DebugSetting<T> {
|
|
3
|
+
get: () => T;
|
|
4
|
+
set: (value: T) => void;
|
|
5
|
+
reset: () => void;
|
|
6
|
+
useState: () => [T, (value: T) => void];
|
|
7
|
+
}
|
|
8
|
+
export declare function registerSetting<T, K = void>(label: string, defaultValue: T, Editor: React.FC<EditorProps<T, K>>, context?: K): DebugSetting<T>;
|
|
9
|
+
//# sourceMappingURL=register-setting.d.ts.map
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import { editors, resets } from './setting-registry.js';
|
|
4
|
+
const LOCAL_STORAGE_EVENT = 'efp-local-storage';
|
|
5
|
+
export function registerSetting(label, defaultValue, Editor, context) {
|
|
6
|
+
const key = `efp-debug-setting-"${label}"`;
|
|
7
|
+
const get = () => tryJsonParse(localStorage.getItem(key), defaultValue);
|
|
8
|
+
const set = (value) => {
|
|
9
|
+
localStorage.setItem(key, JSON.stringify(value));
|
|
10
|
+
emitLocalStorageChange(key);
|
|
11
|
+
};
|
|
12
|
+
const reset = () => {
|
|
13
|
+
localStorage.removeItem(key);
|
|
14
|
+
emitLocalStorageChange(key);
|
|
15
|
+
};
|
|
16
|
+
resets.push(reset);
|
|
17
|
+
function useLocalStorageState() {
|
|
18
|
+
const [state, setState] = useState(get());
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
const update = () => setState(get());
|
|
21
|
+
const onStorage = (e) => {
|
|
22
|
+
if (e.key === key)
|
|
23
|
+
update();
|
|
24
|
+
};
|
|
25
|
+
const onLocal = (e) => {
|
|
26
|
+
const { detail } = e;
|
|
27
|
+
if (detail?.key === key)
|
|
28
|
+
update();
|
|
29
|
+
};
|
|
30
|
+
window.addEventListener('storage', onStorage);
|
|
31
|
+
window.addEventListener(LOCAL_STORAGE_EVENT, onLocal);
|
|
32
|
+
return () => {
|
|
33
|
+
window.removeEventListener('storage', onStorage);
|
|
34
|
+
window.removeEventListener(LOCAL_STORAGE_EVENT, onLocal);
|
|
35
|
+
};
|
|
36
|
+
}, []);
|
|
37
|
+
return [state, set];
|
|
38
|
+
}
|
|
39
|
+
const EditorImpl = () => {
|
|
40
|
+
const [state, setState] = useLocalStorageState();
|
|
41
|
+
// context is always provided by callers that register an Editor expecting it
|
|
42
|
+
return _jsx(Editor, { label: label, value: state, context: context, onChange: setState });
|
|
43
|
+
};
|
|
44
|
+
EditorImpl.displayName = `DebugSetting(${label})`;
|
|
45
|
+
editors.push(EditorImpl);
|
|
46
|
+
return {
|
|
47
|
+
get,
|
|
48
|
+
set,
|
|
49
|
+
reset,
|
|
50
|
+
useState: useLocalStorageState,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function tryJsonParse(str, fallback) {
|
|
54
|
+
if (str === null)
|
|
55
|
+
return fallback;
|
|
56
|
+
try {
|
|
57
|
+
return JSON.parse(str);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return fallback;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function emitLocalStorageChange(key) {
|
|
64
|
+
window.dispatchEvent(new CustomEvent(LOCAL_STORAGE_EVENT, { detail: { key } }));
|
|
65
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { registerSetting } from './registerSetting.js';
|
|
3
|
+
export function registerBooleanSetting(label) {
|
|
4
|
+
const BooleanEditorLazy = React.lazy(() => import('../editors/BooleanEditor.js').then((mod) => ({
|
|
5
|
+
default: mod.BooleanEditor,
|
|
6
|
+
})));
|
|
7
|
+
return registerSetting(label, false, BooleanEditorLazy);
|
|
8
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { registerSetting } from './registerSetting.js';
|
|
3
|
+
export function registerEnumSetting(label, values, defaultValue) {
|
|
4
|
+
const EnumEditorLazy = React.lazy(() => import('../editors/EnumEditor.js').then((mod) => ({
|
|
5
|
+
default: mod.EnumEditor,
|
|
6
|
+
})));
|
|
7
|
+
return registerSetting(label, defaultValue, EnumEditorLazy, {
|
|
8
|
+
values,
|
|
9
|
+
});
|
|
10
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { EditorProps } from '../editors/EditorProps.js';
|
|
2
|
+
export interface DebugSetting<T> {
|
|
3
|
+
get: () => T;
|
|
4
|
+
set: (value: T) => void;
|
|
5
|
+
reset: () => void;
|
|
6
|
+
useState: () => [T, (value: T) => void];
|
|
7
|
+
}
|
|
8
|
+
export declare function registerSetting<T, K = void>(label: string, defaultValue: T, Editor: React.FC<EditorProps<T, K>>, context?: K): DebugSetting<T>;
|
|
9
|
+
//# sourceMappingURL=registerSetting.d.ts.map
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
3
|
+
import { editors, resets } from './registeredSettings.js';
|
|
4
|
+
const LOCAL_STORAGE_EVENT = 'efp-local-storage';
|
|
5
|
+
export function registerSetting(label, defaultValue, Editor, context) {
|
|
6
|
+
const key = `efp-debug-setting-"${label}"`;
|
|
7
|
+
const get = () => tryJsonParse(localStorage.getItem(key), defaultValue);
|
|
8
|
+
const set = (value) => {
|
|
9
|
+
localStorage.setItem(key, JSON.stringify(value));
|
|
10
|
+
emitLocalStorageChange(key);
|
|
11
|
+
};
|
|
12
|
+
const reset = () => {
|
|
13
|
+
localStorage.removeItem(key);
|
|
14
|
+
emitLocalStorageChange(key);
|
|
15
|
+
};
|
|
16
|
+
resets.push(reset);
|
|
17
|
+
function useLocalStorageState() {
|
|
18
|
+
const [state, setState] = useState(get());
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
const update = () => setState(get());
|
|
21
|
+
const onStorage = (e) => {
|
|
22
|
+
if (e.key === key)
|
|
23
|
+
update();
|
|
24
|
+
};
|
|
25
|
+
const onLocal = (e) => {
|
|
26
|
+
const { detail } = e;
|
|
27
|
+
if (detail?.key === key)
|
|
28
|
+
update();
|
|
29
|
+
};
|
|
30
|
+
window.addEventListener('storage', onStorage);
|
|
31
|
+
window.addEventListener(LOCAL_STORAGE_EVENT, onLocal);
|
|
32
|
+
return () => {
|
|
33
|
+
window.removeEventListener('storage', onStorage);
|
|
34
|
+
window.removeEventListener(LOCAL_STORAGE_EVENT, onLocal);
|
|
35
|
+
};
|
|
36
|
+
}, []);
|
|
37
|
+
const setValueCallback = useCallback(set, []);
|
|
38
|
+
return [state, setValueCallback];
|
|
39
|
+
}
|
|
40
|
+
const EditorImpl = () => {
|
|
41
|
+
const [state, setState] = useLocalStorageState();
|
|
42
|
+
return _jsx(Editor, { label: label, value: state, context: context, onChange: setState });
|
|
43
|
+
};
|
|
44
|
+
editors.push(EditorImpl);
|
|
45
|
+
return {
|
|
46
|
+
get,
|
|
47
|
+
set,
|
|
48
|
+
reset,
|
|
49
|
+
useState: useLocalStorageState,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function tryJsonParse(str, fallback) {
|
|
53
|
+
if (str === null)
|
|
54
|
+
return fallback;
|
|
55
|
+
try {
|
|
56
|
+
return JSON.parse(str);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return fallback;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function emitLocalStorageChange(key) {
|
|
63
|
+
window.dispatchEvent(new CustomEvent(LOCAL_STORAGE_EVENT, { detail: { key } }));
|
|
64
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Flex, Switch } from '@radix-ui/themes';
|
|
3
|
+
import { SettingsItem } from './settings-item.js';
|
|
4
|
+
export const BooleanEditor = (props) => {
|
|
5
|
+
return (_jsx(SettingsItem, { label: props.label, children: _jsx(Flex, { children: _jsx(Switch, { checked: props.value, onCheckedChange: (checked) => props.onChange(checked) }) }) }));
|
|
6
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import '@radix-ui/themes/styles.css';
|
|
3
|
+
import { Box, Container, Theme } from '@radix-ui/themes';
|
|
4
|
+
import { DebugTabs } from './debug-tabs.js';
|
|
5
|
+
export const DebugOverlay = ({ onClose }) => {
|
|
6
|
+
return (_jsx(Theme, { appearance: "dark", children: _jsx(Box, { position: "fixed", top: "0", right: "0", width: "100vw", height: "100dvh", overflowY: "auto", style: { backgroundColor: 'black' }, children: _jsx(Container, { size: "4", px: "2", children: _jsx(DebugTabs, { onClose: onClose }) }) }) }));
|
|
7
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Cross1Icon } from '@radix-ui/react-icons';
|
|
3
|
+
import { Button, Tabs } from '@radix-ui/themes';
|
|
4
|
+
import { LogTab } from './log-tab.js';
|
|
5
|
+
import { SettingsTab } from './settings-tab.js';
|
|
6
|
+
export const DebugTabs = ({ onClose }) => (_jsxs(Tabs.Root, { defaultValue: "settingsTab", children: [_jsxs(Tabs.List, { children: [_jsx(Tabs.Trigger, { value: "settingsTab", children: "Settings" }), _jsx(Tabs.Trigger, { value: "logTab", children: "Log" }), _jsxs(Button, { ml: "auto", variant: "soft", mt: "3px", color: "gray", onClick: onClose, children: [_jsx(Cross1Icon, { width: "16", height: "16" }), " Close"] })] }), _jsx(Tabs.Content, { value: "settingsTab", children: _jsx(SettingsTab, {}) }), _jsx(Tabs.Content, { value: "logTab", children: _jsx(LogTab, {}) })] }));
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { lazy, Suspense, useEffect, useState } from 'react';
|
|
3
|
+
import { addDebugSecretListener } from '../utils/add-debug-secret-listener.js';
|
|
4
|
+
import { SideButton } from './side-button.js';
|
|
5
|
+
const LazyDebugOverlay = lazy(async () => ({
|
|
6
|
+
default: (await import('./debug-overlay.js')).DebugOverlay,
|
|
7
|
+
}));
|
|
8
|
+
const DEFAULT_HOST_SELECTOR = '.expofp-floorplan-default';
|
|
9
|
+
export const DebugUi = (props) => {
|
|
10
|
+
const [isOpen, setIsOpen] = useState(props.open);
|
|
11
|
+
const [debugButtonEnabled, setDebugButtonEnabled] = props.useDebugButtonState();
|
|
12
|
+
const selector = props.hostSelector ?? DEFAULT_HOST_SELECTOR;
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
return addDebugSecretListener(() => {
|
|
15
|
+
setDebugButtonEnabled(true);
|
|
16
|
+
setIsOpen(true);
|
|
17
|
+
});
|
|
18
|
+
}, []);
|
|
19
|
+
// set visibility of efp floorplan to none when debug ui is open
|
|
20
|
+
// otherwise, z-index issues occur
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
const efpFloorplan = document.querySelector(selector);
|
|
23
|
+
if (efpFloorplan) {
|
|
24
|
+
efpFloorplan.style.visibility = isOpen ? 'hidden' : 'visible';
|
|
25
|
+
}
|
|
26
|
+
}, [isOpen, selector]);
|
|
27
|
+
if (isOpen) {
|
|
28
|
+
return (_jsx(Suspense, { fallback: null, children: _jsx(LazyDebugOverlay, { onClose: () => {
|
|
29
|
+
setIsOpen(false);
|
|
30
|
+
} }) }));
|
|
31
|
+
}
|
|
32
|
+
return debugButtonEnabled ? _jsx(SideButton, { onClick: () => setIsOpen(true) }) : null;
|
|
33
|
+
};
|
|
34
|
+
DebugUi.displayName = 'DebugUi';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Select } from '@radix-ui/themes';
|
|
3
|
+
import { SettingsItem } from './settings-item.js';
|
|
4
|
+
export function EnumEditor(props) {
|
|
5
|
+
const stringMode = props.context.values.every((v) => typeof v === 'string');
|
|
6
|
+
const stringify = stringMode ? (v) => v : (v) => JSON.stringify(v);
|
|
7
|
+
const parse = stringMode ? (v) => v : (v) => JSON.parse(v);
|
|
8
|
+
const value = stringify(props.value);
|
|
9
|
+
const values = props.context.values.map((v) => ({
|
|
10
|
+
value: stringify(v),
|
|
11
|
+
label: stringify(v),
|
|
12
|
+
}));
|
|
13
|
+
return (_jsx(SettingsItem, { label: props.label, children: _jsxs(Select.Root, { value: value, onValueChange: (value) => {
|
|
14
|
+
props.onChange(parse(value));
|
|
15
|
+
}, children: [_jsx(Select.Trigger, {}), _jsx(Select.Content, { children: values.map((v) => (_jsx(Select.Item, { value: v.value, children: v.label }, v.value))) })] }) }));
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { renderDebugUi } from './render-debug-ui.js';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { InfoCircledIcon } from '@radix-ui/react-icons';
|
|
3
|
+
import { Box, Callout } from '@radix-ui/themes';
|
|
4
|
+
export const LogTab = () => {
|
|
5
|
+
return (_jsx(Box, { pt: "20px", children: _jsx(Box, { mb: "20px", children: _jsxs(Callout.Root, { children: [_jsx(Callout.Icon, { children: _jsx(InfoCircledIcon, {}) }), _jsx(Callout.Text, { children: "Implement to intercept console.info, console.warn and console.error and output them here when logging enabled." })] }) }) }));
|
|
6
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { registerBooleanSetting } from '../settings/index.js';
|
|
3
|
+
import { addDebugSecretListener } from '../utils/add-debug-secret-listener.js';
|
|
4
|
+
let rendered = false;
|
|
5
|
+
export function renderDebugUi() {
|
|
6
|
+
if (rendered) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
rendered = true;
|
|
10
|
+
const showDebugButtonSetting = registerBooleanSetting('Show Debug Button Overlay');
|
|
11
|
+
const render = async (open) => {
|
|
12
|
+
const { DebugUi } = await import('./debug-ui.js');
|
|
13
|
+
const { createRoot } = await import('react-dom/client');
|
|
14
|
+
const rootElement = document.createElement('div');
|
|
15
|
+
document.body.appendChild(rootElement);
|
|
16
|
+
const root = createRoot(rootElement);
|
|
17
|
+
root.render(_jsx(DebugUi, { useDebugButtonState: showDebugButtonSetting.useState, open: open }));
|
|
18
|
+
};
|
|
19
|
+
if (showDebugButtonSetting.get()) {
|
|
20
|
+
render(false);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
const unsubscribe = addDebugSecretListener(() => {
|
|
24
|
+
unsubscribe();
|
|
25
|
+
showDebugButtonSetting.set(true);
|
|
26
|
+
render(true);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Heading, Separator } from '@radix-ui/themes';
|
|
3
|
+
export const SettingsItem = ({ label, children }) => {
|
|
4
|
+
return (_jsxs(_Fragment, { children: [_jsxs(Box, { children: [_jsx(Heading, { size: "4", children: label }), _jsx(Box, { mt: "10px", children: children })] }), _jsx(Separator, { size: "4", my: "20px" })] }));
|
|
5
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Skeleton } from '@radix-ui/themes';
|
|
3
|
+
import { Suspense } from 'react';
|
|
4
|
+
import { editors } from '../settings/setting-registry.js';
|
|
5
|
+
export const SettingsList = () => {
|
|
6
|
+
return editors.map((Editor, index) => (_jsx(Suspense, { fallback: _jsx(Skeleton, { height: "60px" }), children: _jsx(Editor, {}) }, index)));
|
|
7
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { InfoCircledIcon } from '@radix-ui/react-icons';
|
|
3
|
+
import { AlertDialog, Box, Button, Callout, Flex } from '@radix-ui/themes';
|
|
4
|
+
import { resetAllSettings } from '../settings/reset-all-settings.js';
|
|
5
|
+
import { SettingsList } from './settings-list.js';
|
|
6
|
+
export const SettingsTab = () => {
|
|
7
|
+
return (_jsxs(Box, { pt: "20px", children: [_jsx(Box, { mb: "20px", children: _jsxs(Callout.Root, { children: [_jsx(Callout.Icon, { children: _jsx(InfoCircledIcon, {}) }), _jsx(Callout.Text, { children: "These settings will persist across page reloads in your browser." })] }) }), _jsx(SettingsList, {}), _jsxs(Flex, { gap: "3", mt: "40px", justify: "center", children: [_jsx(Button, { onClick: () => {
|
|
8
|
+
window.location.reload();
|
|
9
|
+
}, children: "Reload with the applied changes" }), _jsxs(AlertDialog.Root, { children: [_jsx(AlertDialog.Trigger, { children: _jsx(Button, { color: "red", children: "Reset all and reload" }) }), _jsxs(AlertDialog.Content, { maxWidth: "450px", children: [_jsx(AlertDialog.Title, { children: "Reset all and reload" }), _jsx(AlertDialog.Description, { size: "2", children: "This will reset all settings, disable debug mode, and reload the page." }), _jsxs(Flex, { gap: "3", mt: "4", justify: "end", children: [_jsx(AlertDialog.Cancel, { children: _jsx(Button, { variant: "soft", color: "gray", children: "Cancel" }) }), _jsx(AlertDialog.Action, { children: _jsx(Button, { variant: "solid", color: "red", onClick: () => {
|
|
10
|
+
resetAllSettings();
|
|
11
|
+
window.location.reload();
|
|
12
|
+
}, children: "Reset all and reload" }) })] })] })] })] })] }));
|
|
13
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
export const SideButton = ({ onClick }) => {
|
|
3
|
+
const size = 64;
|
|
4
|
+
const width = 20;
|
|
5
|
+
const radius = 14;
|
|
6
|
+
return (_jsx("button", { type: "button", style: {
|
|
7
|
+
position: 'fixed',
|
|
8
|
+
right: 0,
|
|
9
|
+
top: '50%',
|
|
10
|
+
transform: 'translateY(-50%)',
|
|
11
|
+
padding: 0,
|
|
12
|
+
border: 'none',
|
|
13
|
+
background: 'transparent',
|
|
14
|
+
cursor: 'pointer',
|
|
15
|
+
zIndex: 9999,
|
|
16
|
+
boxSizing: 'border-box',
|
|
17
|
+
}, onClick: onClick, children: _jsx("div", { style: {
|
|
18
|
+
width,
|
|
19
|
+
height: size,
|
|
20
|
+
paddingLeft: 4,
|
|
21
|
+
display: 'flex',
|
|
22
|
+
alignItems: 'center',
|
|
23
|
+
justifyContent: 'center',
|
|
24
|
+
background: 'var(--side-tab-bg, rgba(0,0,0,0.5))',
|
|
25
|
+
color: 'white',
|
|
26
|
+
borderRadius: `${radius}px 0 0 ${radius}px`,
|
|
27
|
+
fontSize: Math.round(size * 0.25),
|
|
28
|
+
lineHeight: 1,
|
|
29
|
+
userSelect: 'none',
|
|
30
|
+
cursor: 'pointer',
|
|
31
|
+
filter: 'grayscale(0.5)',
|
|
32
|
+
boxSizing: 'border-box',
|
|
33
|
+
}, children: _jsx(ChevronLeftSvg, { size: size * 0.7 }) }) }));
|
|
34
|
+
};
|
|
35
|
+
function ChevronLeftSvg({ size = 18, strokeWidth = 3, ...props }) {
|
|
36
|
+
return (_jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", ...props, children: _jsx("path", { fill: "none", d: "M14.5 5.5L8.5 12l6 6.5", stroke: "currentColor", strokeWidth: strokeWidth, strokeLinecap: "round", strokeLinejoin: "round" }) }));
|
|
37
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const SECRET_PHRASE = 'dbg123';
|
|
2
|
+
const callbacks = [];
|
|
3
|
+
let initialized = false;
|
|
4
|
+
let listenerRef = null;
|
|
5
|
+
export function addDebugSecretListener(callback) {
|
|
6
|
+
callbacks.push(callback);
|
|
7
|
+
if (!initialized) {
|
|
8
|
+
initialized = true;
|
|
9
|
+
let buffer = '';
|
|
10
|
+
function onKeyDown(e) {
|
|
11
|
+
// Ignore modifier keys
|
|
12
|
+
if (e.ctrlKey || e.metaKey || e.altKey)
|
|
13
|
+
return;
|
|
14
|
+
if (e.key.length !== 1)
|
|
15
|
+
return;
|
|
16
|
+
buffer += e.key.toLowerCase();
|
|
17
|
+
// Keep buffer length sane
|
|
18
|
+
buffer = buffer.slice(-SECRET_PHRASE.length);
|
|
19
|
+
if (buffer === SECRET_PHRASE) {
|
|
20
|
+
buffer = '';
|
|
21
|
+
callbacks.forEach((cb) => cb());
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
listenerRef = onKeyDown;
|
|
25
|
+
document.addEventListener('keydown', onKeyDown);
|
|
26
|
+
}
|
|
27
|
+
return () => {
|
|
28
|
+
const index = callbacks.indexOf(callback);
|
|
29
|
+
if (index !== -1) {
|
|
30
|
+
callbacks.splice(index, 1);
|
|
31
|
+
}
|
|
32
|
+
if (callbacks.length === 0 && listenerRef) {
|
|
33
|
+
document.removeEventListener('keydown', listenerRef);
|
|
34
|
+
listenerRef = null;
|
|
35
|
+
initialized = false;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const SECRET_PHRASE = 'dbg123';
|
|
2
|
+
const callbacks = [];
|
|
3
|
+
let initialized = false;
|
|
4
|
+
export function addDebugSecretListener(callback) {
|
|
5
|
+
callbacks.push(callback);
|
|
6
|
+
if (!initialized) {
|
|
7
|
+
initialized = true;
|
|
8
|
+
let buffer = '';
|
|
9
|
+
function onKeyDown(e) {
|
|
10
|
+
// Ignore modifier keys
|
|
11
|
+
if (e.ctrlKey || e.metaKey || e.altKey)
|
|
12
|
+
return;
|
|
13
|
+
if (e.key.length !== 1)
|
|
14
|
+
return;
|
|
15
|
+
buffer += e.key.toLowerCase();
|
|
16
|
+
// Keep buffer length sane
|
|
17
|
+
buffer = buffer.slice(-SECRET_PHRASE.length);
|
|
18
|
+
if (buffer === SECRET_PHRASE) {
|
|
19
|
+
buffer = '';
|
|
20
|
+
callbacks.forEach((cb) => cb());
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
document.addEventListener('keydown', onKeyDown);
|
|
24
|
+
}
|
|
25
|
+
return () => {
|
|
26
|
+
const index = callbacks.indexOf(callback);
|
|
27
|
+
if (index !== -1) {
|
|
28
|
+
callbacks.splice(index, 1);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@expofp/debug",
|
|
3
|
+
"version": "3.0.0-alpha.3",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "ExpoFP SDK internal: debug utilities",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public"
|
|
9
|
+
},
|
|
10
|
+
"main": "./dist/index.js",
|
|
11
|
+
"module": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"exports": {
|
|
14
|
+
"./package.json": "./package.json",
|
|
15
|
+
".": {
|
|
16
|
+
"@expofp/source": "./src/index.ts",
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"import": "./dist/index.js",
|
|
19
|
+
"default": "./dist/index.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"!**/*.tsbuildinfo",
|
|
25
|
+
"!**/*.map"
|
|
26
|
+
],
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@radix-ui/react-icons": "^1.3.2",
|
|
29
|
+
"@radix-ui/themes": "^3.2.1",
|
|
30
|
+
"tslib": "^2.3.0"
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"react": "^19.2.4",
|
|
34
|
+
"react-dom": "^19.2.4"
|
|
35
|
+
}
|
|
36
|
+
}
|