@dxos/plugin-debug 0.6.8-main.046e6cf

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.
Files changed (80) hide show
  1. package/LICENSE +8 -0
  2. package/README.md +15 -0
  3. package/dist/lib/browser/DebugGlobal-H37KGFEG.mjs +179 -0
  4. package/dist/lib/browser/DebugGlobal-H37KGFEG.mjs.map +7 -0
  5. package/dist/lib/browser/DebugSpace-ZZ2JLU6F.mjs +407 -0
  6. package/dist/lib/browser/DebugSpace-ZZ2JLU6F.mjs.map +7 -0
  7. package/dist/lib/browser/DevtoolsMain-DTAWVF22.mjs +16 -0
  8. package/dist/lib/browser/DevtoolsMain-DTAWVF22.mjs.map +7 -0
  9. package/dist/lib/browser/chunk-ED5L5YYI.mjs +27 -0
  10. package/dist/lib/browser/chunk-ED5L5YYI.mjs.map +7 -0
  11. package/dist/lib/browser/chunk-RYK3J66D.mjs +24 -0
  12. package/dist/lib/browser/chunk-RYK3J66D.mjs.map +7 -0
  13. package/dist/lib/browser/chunk-WEGYHXMB.mjs +21 -0
  14. package/dist/lib/browser/chunk-WEGYHXMB.mjs.map +7 -0
  15. package/dist/lib/browser/index.mjs +658 -0
  16. package/dist/lib/browser/index.mjs.map +7 -0
  17. package/dist/lib/browser/meta.json +1 -0
  18. package/dist/lib/browser/meta.mjs +9 -0
  19. package/dist/lib/browser/meta.mjs.map +7 -0
  20. package/dist/types/src/DebugPlugin.d.ts +5 -0
  21. package/dist/types/src/DebugPlugin.d.ts.map +1 -0
  22. package/dist/types/src/components/DebugGlobal.d.ts +7 -0
  23. package/dist/types/src/components/DebugGlobal.d.ts.map +1 -0
  24. package/dist/types/src/components/DebugPanel.d.ts +5 -0
  25. package/dist/types/src/components/DebugPanel.d.ts.map +1 -0
  26. package/dist/types/src/components/DebugSettings.d.ts +6 -0
  27. package/dist/types/src/components/DebugSettings.d.ts.map +1 -0
  28. package/dist/types/src/components/DebugSpace.d.ts +9 -0
  29. package/dist/types/src/components/DebugSpace.d.ts.map +1 -0
  30. package/dist/types/src/components/DebugSpace.stories.d.ts +16 -0
  31. package/dist/types/src/components/DebugSpace.stories.d.ts.map +1 -0
  32. package/dist/types/src/components/DebugStatus.d.ts +3 -0
  33. package/dist/types/src/components/DebugStatus.d.ts.map +1 -0
  34. package/dist/types/src/components/DevtoolsMain.d.ts +4 -0
  35. package/dist/types/src/components/DevtoolsMain.d.ts.map +1 -0
  36. package/dist/types/src/components/ObjectCreator.d.ts +17 -0
  37. package/dist/types/src/components/ObjectCreator.d.ts.map +1 -0
  38. package/dist/types/src/components/ObjectCreator.stories.d.ts +17 -0
  39. package/dist/types/src/components/ObjectCreator.stories.d.ts.map +1 -0
  40. package/dist/types/src/components/Tree.d.ts +20 -0
  41. package/dist/types/src/components/Tree.d.ts.map +1 -0
  42. package/dist/types/src/components/Wireframe.d.ts +7 -0
  43. package/dist/types/src/components/Wireframe.d.ts.map +1 -0
  44. package/dist/types/src/components/index.d.ts +13 -0
  45. package/dist/types/src/components/index.d.ts.map +1 -0
  46. package/dist/types/src/index.d.ts +4 -0
  47. package/dist/types/src/index.d.ts.map +1 -0
  48. package/dist/types/src/meta.d.ts +15 -0
  49. package/dist/types/src/meta.d.ts.map +1 -0
  50. package/dist/types/src/scaffolding/generator.d.ts +11 -0
  51. package/dist/types/src/scaffolding/generator.d.ts.map +1 -0
  52. package/dist/types/src/scaffolding/index.d.ts +2 -0
  53. package/dist/types/src/scaffolding/index.d.ts.map +1 -0
  54. package/dist/types/src/translations.d.ts +29 -0
  55. package/dist/types/src/translations.d.ts.map +1 -0
  56. package/dist/types/src/types.d.ts +19 -0
  57. package/dist/types/src/types.d.ts.map +1 -0
  58. package/dist/types/src/types.test.d.ts +2 -0
  59. package/dist/types/src/types.test.d.ts.map +1 -0
  60. package/package.json +92 -0
  61. package/src/DebugPlugin.tsx +270 -0
  62. package/src/components/DebugGlobal.tsx +80 -0
  63. package/src/components/DebugPanel.tsx +34 -0
  64. package/src/components/DebugSettings.tsx +159 -0
  65. package/src/components/DebugSpace.stories.tsx +40 -0
  66. package/src/components/DebugSpace.tsx +195 -0
  67. package/src/components/DebugStatus.tsx +221 -0
  68. package/src/components/DevtoolsMain.tsx +16 -0
  69. package/src/components/ObjectCreator.stories.tsx +44 -0
  70. package/src/components/ObjectCreator.tsx +100 -0
  71. package/src/components/Tree.tsx +113 -0
  72. package/src/components/Wireframe.tsx +35 -0
  73. package/src/components/index.ts +14 -0
  74. package/src/index.ts +9 -0
  75. package/src/meta.tsx +19 -0
  76. package/src/scaffolding/generator.ts +143 -0
  77. package/src/scaffolding/index.ts +5 -0
  78. package/src/translations.ts +36 -0
  79. package/src/types.test.ts +13 -0
  80. package/src/types.ts +45 -0
package/package.json ADDED
@@ -0,0 +1,92 @@
1
+ {
2
+ "name": "@dxos/plugin-debug",
3
+ "version": "0.6.8-main.046e6cf",
4
+ "description": "DXOS Surface plugin for testing.",
5
+ "homepage": "https://dxos.org",
6
+ "bugs": "https://github.com/dxos/dxos/issues",
7
+ "license": "MIT",
8
+ "author": "DXOS.org",
9
+ "exports": {
10
+ ".": {
11
+ "browser": "./dist/lib/browser/index.mjs",
12
+ "types": "./dist/types/src/index.d.ts"
13
+ },
14
+ "./meta": {
15
+ "browser": "./dist/lib/browser/meta.mjs",
16
+ "types": "./dist/types/src/meta.d.ts"
17
+ }
18
+ },
19
+ "types": "dist/types/src/index.d.ts",
20
+ "typesVersions": {
21
+ "*": {
22
+ "meta": [
23
+ "dist/types/src/meta.d.ts"
24
+ ]
25
+ }
26
+ },
27
+ "files": [
28
+ "dist",
29
+ "src"
30
+ ],
31
+ "dependencies": {
32
+ "@preact/signals-core": "^1.6.0",
33
+ "@tldraw/tldraw": "^2.3.0",
34
+ "date-fns": "^3.3.1",
35
+ "lodash.get": "^4.4.2",
36
+ "react-json-tree": "^0.18.0",
37
+ "react-resize-detector": "^11.0.1",
38
+ "react-syntax-highlighter": "^15.5.0",
39
+ "@dxos/app-framework": "0.6.8-main.046e6cf",
40
+ "@dxos/async": "0.6.8-main.046e6cf",
41
+ "@dxos/client-services": "0.6.8-main.046e6cf",
42
+ "@dxos/config": "0.6.8-main.046e6cf",
43
+ "@dxos/echo-generator": "0.6.8-main.046e6cf",
44
+ "@dxos/automerge": "0.6.8-main.046e6cf",
45
+ "@dxos/devtools": "0.6.8-main.046e6cf",
46
+ "@dxos/echo-pipeline": "0.6.8-main.046e6cf",
47
+ "@dxos/echo-schema": "0.6.8-main.046e6cf",
48
+ "@dxos/keys": "0.6.8-main.046e6cf",
49
+ "@dxos/invariant": "0.6.8-main.046e6cf",
50
+ "@dxos/local-storage": "0.6.8-main.046e6cf",
51
+ "@dxos/log": "0.6.8-main.046e6cf",
52
+ "@dxos/plugin-graph": "0.6.8-main.046e6cf",
53
+ "@dxos/plugin-client": "0.6.8-main.046e6cf",
54
+ "@dxos/plugin-markdown": "0.6.8-main.046e6cf",
55
+ "@dxos/plugin-settings": "0.6.8-main.046e6cf",
56
+ "@dxos/plugin-sketch": "0.6.8-main.046e6cf",
57
+ "@dxos/plugin-space": "0.6.8-main.046e6cf",
58
+ "@dxos/plugin-theme": "0.6.8-main.046e6cf",
59
+ "@dxos/plugin-status-bar": "0.6.8-main.046e6cf",
60
+ "@dxos/protocols": "0.6.8-main.046e6cf",
61
+ "@dxos/random": "0.6.8-main.046e6cf",
62
+ "@dxos/react-async": "0.6.8-main.046e6cf",
63
+ "@dxos/react-client": "0.6.8-main.046e6cf",
64
+ "@dxos/react-ui-attention": "0.6.8-main.046e6cf",
65
+ "@dxos/react-ui-table": "0.6.8-main.046e6cf",
66
+ "@dxos/util": "0.6.8-main.046e6cf"
67
+ },
68
+ "devDependencies": {
69
+ "@phosphor-icons/react": "^2.1.5",
70
+ "@types/lodash.get": "^4.4.7",
71
+ "@types/react": "~18.2.0",
72
+ "@types/react-dom": "~18.2.0",
73
+ "@types/react-syntax-highlighter": "^15.5.5",
74
+ "react": "~18.2.0",
75
+ "react-dom": "~18.2.0",
76
+ "vite": "^5.3.4",
77
+ "@dxos/random": "0.6.8-main.046e6cf",
78
+ "@dxos/storybook-utils": "0.6.8-main.046e6cf",
79
+ "@dxos/react-ui": "0.6.8-main.046e6cf",
80
+ "@dxos/react-ui-theme": "0.6.8-main.046e6cf"
81
+ },
82
+ "peerDependencies": {
83
+ "@phosphor-icons/react": "^2.1.5",
84
+ "react": "^18.0.0",
85
+ "react-dom": "^18.0.0",
86
+ "@dxos/react-ui": "0.6.8-main.046e6cf",
87
+ "@dxos/react-ui-theme": "0.6.8-main.046e6cf"
88
+ },
89
+ "publishConfig": {
90
+ "access": "public"
91
+ }
92
+ }
@@ -0,0 +1,270 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { Bug, Hammer, type IconProps } from '@phosphor-icons/react';
6
+ import React, { type ReactNode, useEffect, useState } from 'react';
7
+
8
+ import {
9
+ getPlugin,
10
+ parseGraphPlugin,
11
+ parseIntentPlugin,
12
+ resolvePlugin,
13
+ type IntentPluginProvides,
14
+ type Plugin,
15
+ type PluginDefinition,
16
+ } from '@dxos/app-framework';
17
+ import { Timer } from '@dxos/async';
18
+ import { LocalStorageStore } from '@dxos/local-storage';
19
+ import { type ClientPluginProvides } from '@dxos/plugin-client';
20
+ import { createExtension, Graph, type Node } from '@dxos/plugin-graph';
21
+ import { SpaceAction } from '@dxos/plugin-space';
22
+ import { CollectionType } from '@dxos/plugin-space/types';
23
+ import { type Client } from '@dxos/react-client';
24
+ import { type Space, SpaceState, isSpace } from '@dxos/react-client/echo';
25
+ import { Main } from '@dxos/react-ui';
26
+ import {
27
+ baseSurface,
28
+ topbarBlockPaddingStart,
29
+ fixedInsetFlexLayout,
30
+ bottombarBlockPaddingEnd,
31
+ } from '@dxos/react-ui-theme';
32
+
33
+ import { DebugGlobal, DebugSettings, DebugSpace, DebugStatus, DevtoolsMain, Wireframe } from './components';
34
+ import meta, { DEBUG_PLUGIN } from './meta';
35
+ import translations from './translations';
36
+ import { DebugContext, type DebugSettingsProps, type DebugPluginProvides, DebugAction } from './types';
37
+
38
+ export const SETTINGS_KEY = DEBUG_PLUGIN + '/settings';
39
+
40
+ export const DebugPlugin = (): PluginDefinition<DebugPluginProvides> => {
41
+ const settings = new LocalStorageStore<DebugSettingsProps>(DEBUG_PLUGIN, { debug: true, devtools: true });
42
+ let intentPlugin: Plugin<IntentPluginProvides>;
43
+
44
+ return {
45
+ meta,
46
+ ready: async (plugins) => {
47
+ intentPlugin = resolvePlugin(plugins, parseIntentPlugin)!;
48
+ settings
49
+ .prop({ key: 'debug', type: LocalStorageStore.bool({ allowUndefined: true }) })
50
+ .prop({ key: 'devtools', type: LocalStorageStore.bool({ allowUndefined: true }) })
51
+ .prop({ key: 'wireframe', type: LocalStorageStore.bool({ allowUndefined: true }) });
52
+
53
+ // TODO(burdon): Remove hacky dependency on global variable.
54
+ // Used to test how composer handles breaking protocol changes.
55
+ const composer = (window as any).composer;
56
+ composer.changeStorageVersionInMetadata = async (version: number) => {
57
+ const { changeStorageVersionInMetadata } = await import('@dxos/echo-pipeline/testing');
58
+ const { createStorageObjects } = await import('@dxos/client-services');
59
+ const client: Client = (window as any).dxos.client;
60
+ const config = client.config;
61
+ await client.destroy();
62
+ const { storage } = createStorageObjects(config.values?.runtime?.client?.storage ?? {});
63
+ await changeStorageVersionInMetadata(storage, version);
64
+ location.pathname = '/';
65
+ };
66
+ },
67
+ unload: async () => {
68
+ settings.close();
69
+ },
70
+ provides: {
71
+ settings: settings.values,
72
+ translations,
73
+ context: ({ children }) => {
74
+ const [timer, setTimer] = useState<Timer>();
75
+ useEffect(() => timer?.state.on((value) => !value && setTimer(undefined)), [timer]);
76
+ useEffect(() => {
77
+ timer?.stop();
78
+ }, []);
79
+
80
+ return (
81
+ <DebugContext.Provider
82
+ value={{
83
+ running: !!timer,
84
+ start: (cb, options) => {
85
+ timer?.stop();
86
+ setTimer(new Timer(cb).start(options));
87
+ },
88
+ stop: () => timer?.stop(),
89
+ }}
90
+ >
91
+ {children}
92
+ </DebugContext.Provider>
93
+ );
94
+ },
95
+ graph: {
96
+ builder: (plugins) => {
97
+ const graphPlugin = resolvePlugin(plugins, parseGraphPlugin);
98
+
99
+ // TODO(burdon): Combine nodes into single subtree.
100
+
101
+ return [
102
+ // Devtools node.
103
+ createExtension({
104
+ id: 'dxos.org/plugin/debug/devtools',
105
+ filter: (node): node is Node<null> => !!settings.values.devtools && node.id === 'root',
106
+ connector: () => [
107
+ {
108
+ // TODO(zan): Removed `/` because it breaks deck layout reload. Fix?
109
+ id: 'dxos.org.plugin.debug.devtools',
110
+ data: 'devtools',
111
+ type: 'dxos.org/plugin/debug/devtools',
112
+ properties: {
113
+ label: ['devtools label', { ns: DEBUG_PLUGIN }],
114
+ icon: (props: IconProps) => <Hammer {...props} />,
115
+ iconSymbol: 'ph--hammer--regular',
116
+ },
117
+ },
118
+ ],
119
+ }),
120
+
121
+ // Debug node.
122
+ createExtension({
123
+ id: 'dxos.org/plugin/debug/debug',
124
+ filter: (node): node is Node<null> => !!settings.values.debug && node.id === 'root',
125
+ connector: () => [
126
+ {
127
+ id: 'dxos.org/plugin/debug/debug',
128
+ type: 'dxos.org/plugin/debug/debug',
129
+ data: { graph: graphPlugin?.provides.graph },
130
+ properties: {
131
+ label: ['debug label', { ns: DEBUG_PLUGIN }],
132
+ icon: (props: IconProps) => <Bug {...props} />,
133
+ iconSymbol: 'ph--bug--regular',
134
+ },
135
+ },
136
+ ],
137
+ }),
138
+
139
+ // Space debug nodes.
140
+ createExtension({
141
+ id: 'dxos.org/plugin/debug/spaces',
142
+ filter: (node): node is Node<Space> => !!settings.values.debug && isSpace(node.data),
143
+ connector: ({ node }) => {
144
+ const space = node.data;
145
+ return [
146
+ {
147
+ id: `${space.id}-debug`,
148
+ type: 'dxos.org/plugin/debug/space',
149
+ data: { space },
150
+ properties: {
151
+ label: ['debug label', { ns: DEBUG_PLUGIN }],
152
+ icon: (props: IconProps) => <Bug {...props} />,
153
+ iconSymbol: 'ph--bug--regular',
154
+ },
155
+ },
156
+ ];
157
+ },
158
+ }),
159
+ ];
160
+ },
161
+ },
162
+ intent: {
163
+ resolver: async (intent, plugins) => {
164
+ switch (intent.action) {
165
+ case DebugAction.OPEN_DEVTOOLS: {
166
+ const clientPlugin = getPlugin<ClientPluginProvides>(plugins, 'dxos.org/plugin/client');
167
+ const client = clientPlugin.provides.client;
168
+ const vaultUrl = client.config.values?.runtime?.client?.remoteSource ?? 'https://halo.dxos.org';
169
+
170
+ // Check if we're serving devtools locally on the usual port.
171
+ let devtoolsUrl = 'http://localhost:5174';
172
+ try {
173
+ // TODO(burdon): Test header to see if this is actually devtools.
174
+ await fetch(devtoolsUrl);
175
+ } catch {
176
+ // Match devtools to running app.
177
+ const isDev = window.location.href.includes('.dev.') || window.location.href.includes('localhost');
178
+ devtoolsUrl = `https://devtools${isDev ? '.dev.' : '.'}dxos.org`;
179
+ }
180
+
181
+ window.open(`${devtoolsUrl}?target=${vaultUrl}`, '_blank');
182
+ return { data: true };
183
+ }
184
+ }
185
+ },
186
+ },
187
+ surface: {
188
+ component: ({ name, data, role }) => {
189
+ switch (role) {
190
+ case 'settings':
191
+ return data.plugin === meta.id ? <DebugSettings settings={settings.values} /> : null;
192
+ case 'status':
193
+ return <DebugStatus />;
194
+ }
195
+
196
+ const primary = data.active ?? data.object;
197
+ let component: ReactNode;
198
+ if (role === 'main' || role === 'article') {
199
+ if (primary === 'devtools' && settings.values.devtools) {
200
+ component = <DevtoolsMain />;
201
+ } else if (!primary || typeof primary !== 'object' || !settings.values.debug) {
202
+ component = null;
203
+ } else if ('space' in primary && isSpace(primary.space)) {
204
+ component = (
205
+ <DebugSpace
206
+ space={primary.space}
207
+ onAddObjects={(objects) => {
208
+ if (!isSpace(primary.space)) {
209
+ return;
210
+ }
211
+
212
+ const collection =
213
+ primary.space.state.get() === SpaceState.SPACE_READY &&
214
+ primary.space.properties[CollectionType.typename];
215
+ if (!(collection instanceof CollectionType)) {
216
+ return;
217
+ }
218
+
219
+ void intentPlugin?.provides.intent.dispatch(
220
+ objects.map((object) => ({
221
+ action: SpaceAction.ADD_OBJECT,
222
+ data: { target: collection, object },
223
+ })),
224
+ );
225
+ }}
226
+ />
227
+ );
228
+ } else if ('graph' in primary && primary.graph instanceof Graph) {
229
+ component = <DebugGlobal graph={primary.graph} />;
230
+ } else {
231
+ component = null;
232
+ }
233
+ }
234
+
235
+ if (!component) {
236
+ if (settings.values.wireframe) {
237
+ if (role === 'main' || role === 'article' || role === 'section') {
238
+ const primary = data.active ?? data.object;
239
+ if (!(primary instanceof CollectionType)) {
240
+ return <Wireframe label={role} data={data} className='row-span-2 overflow-hidden' />;
241
+ }
242
+ }
243
+ }
244
+
245
+ return null;
246
+ }
247
+
248
+ switch (role) {
249
+ case 'article':
250
+ return (
251
+ <div role='none' className='row-span-2 rounded-t-md overflow-x-auto'>
252
+ {component}
253
+ </div>
254
+ );
255
+ case 'main':
256
+ return (
257
+ <Main.Content
258
+ classNames={[baseSurface, fixedInsetFlexLayout, topbarBlockPaddingStart, bottombarBlockPaddingEnd]}
259
+ >
260
+ {component}
261
+ </Main.Content>
262
+ );
263
+ }
264
+
265
+ return null;
266
+ },
267
+ },
268
+ },
269
+ };
270
+ };
@@ -0,0 +1,80 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { Gauge, Graph as GraphIcon, Gear, Toolbox, Warning } from '@phosphor-icons/react';
6
+ import React, { type FC, useEffect, useState } from 'react';
7
+
8
+ import { type Graph } from '@dxos/plugin-graph';
9
+ import { useClient, useConfig } from '@dxos/react-client';
10
+ import { Button, ToggleGroup, ToggleGroupItem, useThemeContext } from '@dxos/react-ui';
11
+ import { getSize, mx } from '@dxos/react-ui-theme';
12
+
13
+ import { DebugPanel } from './DebugPanel';
14
+ import { Json, Tree } from './Tree';
15
+
16
+ const DebugGlobal: FC<{ graph: Graph }> = ({ graph }) => {
17
+ const { themeMode } = useThemeContext();
18
+ const [view, setView] = useState<'config' | 'diagnostics' | 'graph'>('graph');
19
+ const [data, setData] = useState<any>({});
20
+ const client = useClient();
21
+ const config = useConfig();
22
+ const handleRefresh = async () => {
23
+ const data = await client.diagnostics({ truncate: true });
24
+ setData(data);
25
+ };
26
+ useEffect(() => {
27
+ void handleRefresh();
28
+ }, []);
29
+
30
+ const handleResetClient = async (force = false) => {
31
+ if (!force && !window.confirm('Reset storage?')) {
32
+ return;
33
+ }
34
+
35
+ // TODO(burdon): Throws exception.
36
+ await client.reset();
37
+ window.location.href = window.location.origin;
38
+ };
39
+
40
+ const handleOpenDevtools = () => {
41
+ const vaultUrl = config.values?.runtime?.client?.remoteSource;
42
+ if (vaultUrl) {
43
+ window.open(`https://devtools.dev.dxos.org/?target=${vaultUrl}`);
44
+ }
45
+ };
46
+
47
+ return (
48
+ <DebugPanel
49
+ menu={
50
+ <>
51
+ <ToggleGroup type='single' value={view}>
52
+ <ToggleGroupItem value={'graph'} onClick={() => setView('graph')} title={'Plugin graph'}>
53
+ <GraphIcon className={getSize(5)} />
54
+ </ToggleGroupItem>
55
+ <ToggleGroupItem value={'diagnostics'} onClick={() => setView('diagnostics')} title={'Diagnostics'}>
56
+ <Gauge className={getSize(5)} />
57
+ </ToggleGroupItem>
58
+ <ToggleGroupItem value={'config'} onClick={() => setView('config')} title={'Config'}>
59
+ <Gear className={getSize(5)} />
60
+ </ToggleGroupItem>
61
+ </ToggleGroup>
62
+
63
+ <div className='grow' />
64
+ <Button onClick={(event) => handleResetClient(event.shiftKey)} title='Reset client'>
65
+ <Warning className={mx(getSize(5), 'text-red-700')} />
66
+ </Button>
67
+ <Button onClick={handleOpenDevtools} title='Open Devtools'>
68
+ <Toolbox weight='duotone' className={mx(getSize(5), 'text-700')} />
69
+ </Button>
70
+ </>
71
+ }
72
+ >
73
+ {view === 'graph' && <Tree data={graph.toJSON()} />}
74
+ {view === 'config' && <Json theme={themeMode} data={data.diagnostics?.config} />}
75
+ {view === 'diagnostics' && <Json theme={themeMode} data={data} />}
76
+ </DebugPanel>
77
+ );
78
+ };
79
+
80
+ export default DebugGlobal;
@@ -0,0 +1,34 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { formatDistance } from 'date-fns';
6
+ import React, { type FC, type PropsWithChildren, type ReactNode } from 'react';
7
+
8
+ import { useConfig } from '@dxos/react-client';
9
+ import { DensityProvider } from '@dxos/react-ui';
10
+
11
+ export const DebugPanel: FC<PropsWithChildren<{ menu: ReactNode }>> = ({ menu, children }) => {
12
+ const config = useConfig();
13
+ return (
14
+ <>
15
+ <div className='flex shrink-0 p-2 space-x-2'>
16
+ <DensityProvider density='fine'>{menu}</DensityProvider>
17
+ </div>
18
+ <div className='flex flex-col grow px-2 overflow-hidden'>
19
+ <div className='flex flex-col grow overflow-auto'>{children}</div>
20
+
21
+ {config.values?.runtime?.app?.build?.timestamp && (
22
+ <div className='p-2 text-sm font-mono'>
23
+ {config.values?.runtime?.app?.build?.version} (
24
+ {formatDistance(new Date(config.values?.runtime?.app?.build?.timestamp), new Date(), {
25
+ addSuffix: true,
26
+ includeSeconds: true,
27
+ })}
28
+ )
29
+ </div>
30
+ )}
31
+ </div>
32
+ </>
33
+ );
34
+ };
@@ -0,0 +1,159 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { Gift, DownloadSimple, FirstAidKit } from '@phosphor-icons/react';
6
+ import React, { useEffect, useState } from 'react';
7
+
8
+ import { parseFileManagerPlugin, useResolvePlugin } from '@dxos/app-framework';
9
+ import { type ConfigProto, defs, SaveConfig, Storage } from '@dxos/config';
10
+ import { log } from '@dxos/log';
11
+ import { SettingsValue } from '@dxos/plugin-settings';
12
+ import { useClient } from '@dxos/react-client';
13
+ import { useTranslation, Button, Toast, Input, useFileDownload, Select } from '@dxos/react-ui';
14
+ import { getSize, mx } from '@dxos/react-ui-theme';
15
+ import { setDeep } from '@dxos/util';
16
+
17
+ import { DEBUG_PLUGIN } from '../meta';
18
+ import { type DebugSettingsProps } from '../types';
19
+
20
+ type Toast = {
21
+ title: string;
22
+ description?: string;
23
+ };
24
+
25
+ const StorageAdapters = {
26
+ opfs: defs.Runtime.Client.Storage.StorageDriver.WEBFS,
27
+ idb: defs.Runtime.Client.Storage.StorageDriver.IDB,
28
+ } as const;
29
+
30
+ export const DebugSettings = ({ settings }: { settings: DebugSettingsProps }) => {
31
+ const { t } = useTranslation(DEBUG_PLUGIN);
32
+ const [toast, setToast] = useState<Toast>();
33
+ const client = useClient();
34
+ const download = useFileDownload();
35
+ // TODO(mykola): Get updates from other places that change Config.
36
+ const [storageConfig, setStorageConfig] = useState<ConfigProto>({});
37
+ const fileManagerPlugin = useResolvePlugin(parseFileManagerPlugin);
38
+
39
+ useEffect(() => {
40
+ void Storage().then((config) => setStorageConfig(config));
41
+ }, []);
42
+
43
+ const handleToast = (toast: Toast) => {
44
+ setToast(toast);
45
+ const t = setTimeout(() => setToast(undefined), 5_000);
46
+ return () => clearTimeout(t);
47
+ };
48
+
49
+ const handleDownload = async () => {
50
+ const data = await client.diagnostics();
51
+ const file = new Blob([JSON.stringify(data, undefined, 2)], { type: 'text/plain' });
52
+ const fileName = `composer-${new Date().toISOString().replace(/\W/g, '-')}.json`;
53
+ download(file, fileName);
54
+
55
+ if (fileManagerPlugin?.provides.file.upload) {
56
+ const info = await fileManagerPlugin.provides.file.upload(new File([file], fileName), client.spaces.default);
57
+ if (!info) {
58
+ log.error('diagnostics failed to upload to IPFS');
59
+ return;
60
+ }
61
+ handleToast({ title: t('settings uploaded'), description: t('settings uploaded to clipboard') });
62
+
63
+ // TODO(nf): move to IpfsPlugin?
64
+ const url = client.config.values.runtime!.services!.ipfs!.gateway + '/' + info.cid;
65
+ void navigator.clipboard.writeText(url);
66
+ handleToast({ title: t('settings uploaded'), description: t('settings uploaded to clipboard') });
67
+ log.info('diagnostics', { url });
68
+ }
69
+ };
70
+
71
+ const handleRepair = async () => {
72
+ try {
73
+ const info = await client.repair();
74
+ setStorageConfig(await Storage());
75
+ handleToast({ title: t('settings repair success'), description: JSON.stringify(info, undefined, 2) });
76
+ } catch (err: any) {
77
+ handleToast({ title: t('settings repair failed'), description: err.message });
78
+ }
79
+ };
80
+
81
+ return (
82
+ <>
83
+ <SettingsValue label={t('settings show debug panel')}>
84
+ <Input.Switch checked={settings.debug} onCheckedChange={(checked) => (settings.debug = !!checked)} />
85
+ </SettingsValue>
86
+ <SettingsValue label={t('settings show devtools panel')}>
87
+ <Input.Switch checked={settings.devtools} onCheckedChange={(checked) => (settings.devtools = !!checked)} />
88
+ </SettingsValue>
89
+ <SettingsValue label={t('settings wireframe')}>
90
+ <Input.Switch checked={settings.wireframe} onCheckedChange={(checked) => (settings.wireframe = !!checked)} />
91
+ </SettingsValue>
92
+ <SettingsValue label={t('settings download diagnostics')}>
93
+ <Button onClick={handleDownload}>
94
+ <DownloadSimple className={getSize(5)} />
95
+ </Button>
96
+ </SettingsValue>
97
+ <SettingsValue label={t('settings repair')}>
98
+ <Button onClick={handleRepair}>
99
+ <FirstAidKit className={getSize(5)} />
100
+ </Button>
101
+ </SettingsValue>
102
+
103
+ {/* TODO(burdon): Move to layout? */}
104
+ {toast && (
105
+ <Toast.Root>
106
+ <Toast.Body>
107
+ <Toast.Title>
108
+ <Gift className={mx(getSize(5), 'inline mr-1')} weight='duotone' />
109
+ <span>{toast.title}</span>
110
+ </Toast.Title>
111
+ {toast.description && <Toast.Description>{toast.description}</Toast.Description>}
112
+ </Toast.Body>
113
+ </Toast.Root>
114
+ )}
115
+
116
+ <SettingsValue label={t('settings choose storage adaptor')}>
117
+ <Select.Root
118
+ value={
119
+ Object.entries(StorageAdapters).find(
120
+ ([name, value]) => value === storageConfig?.runtime?.client?.storage?.dataStore,
121
+ )?.[0]
122
+ }
123
+ onValueChange={(value) => {
124
+ if (confirm(t('settings storage adapter changed alert'))) {
125
+ updateConfig(
126
+ storageConfig,
127
+ setStorageConfig,
128
+ ['runtime', 'client', 'storage', 'dataStore'],
129
+ StorageAdapters[value as keyof typeof StorageAdapters],
130
+ );
131
+ }
132
+ }}
133
+ >
134
+ <Select.TriggerButton placeholder={t('settings data store label')} />
135
+ <Select.Portal>
136
+ <Select.Content>
137
+ <Select.Viewport>
138
+ {Object.keys(StorageAdapters).map((key) => (
139
+ <Select.Option key={key} value={key}>
140
+ {t(`settings storage adaptor ${key} label`)}
141
+ </Select.Option>
142
+ ))}
143
+ </Select.Viewport>
144
+ </Select.Content>
145
+ </Select.Portal>
146
+ </Select.Root>
147
+ </SettingsValue>
148
+ </>
149
+ );
150
+ };
151
+
152
+ const updateConfig = (config: ConfigProto, setConfig: (newConfig: ConfigProto) => void, path: string[], value: any) => {
153
+ const storageConfigCopy = JSON.parse(JSON.stringify(config ?? {}));
154
+ setDeep(storageConfigCopy, path, value);
155
+ setConfig(storageConfigCopy);
156
+ queueMicrotask(async () => {
157
+ await SaveConfig(storageConfigCopy);
158
+ });
159
+ };
@@ -0,0 +1,40 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import '@dxosTheme';
6
+
7
+ import React, { type FC, useEffect } from 'react';
8
+
9
+ import { createSpaceObjectGenerator } from '@dxos/echo-generator';
10
+ import { useSpaces } from '@dxos/react-client/echo';
11
+ import { ClientRepeater } from '@dxos/react-client/testing';
12
+
13
+ import DebugSpace from './DebugSpace';
14
+
15
+ const Story: FC = () => {
16
+ const [space] = useSpaces();
17
+ useEffect(() => {
18
+ if (space) {
19
+ const generator = createSpaceObjectGenerator(space);
20
+ generator.addSchemas();
21
+ }
22
+ }, [space]);
23
+
24
+ if (!space) {
25
+ return null;
26
+ }
27
+
28
+ return <DebugSpace space={space} />;
29
+ };
30
+
31
+ export default {
32
+ title: 'plugin-debug/DebugSpace',
33
+ component: DebugSpace,
34
+ render: () => <ClientRepeater component={Story} createSpace />,
35
+ parameters: {
36
+ layout: 'fullscreen',
37
+ },
38
+ };
39
+
40
+ export const Default = {};