@mosetta/editor 0.0.0-stage → 0.0.28

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 (115) hide show
  1. package/CHANGELOG.md +185 -0
  2. package/LICENSE +21 -0
  3. package/README.md +10 -2
  4. package/mosetta.json +6 -0
  5. package/package.json +35 -3
  6. package/prefabs/workbench/prefab.json +11 -0
  7. package/prefabs/workbench/versions/1.json +120 -0
  8. package/prefabs/workbench/versions/2.json +13 -0
  9. package/prefabs/workbench/versions/3.json +19 -0
  10. package/prefabs/workbench/versions/4.json +29 -0
  11. package/prefabs/workbench/versions/5.json +29 -0
  12. package/tiles/canvas/canvas.component.tsx +2038 -0
  13. package/tiles/canvas/canvas.css +973 -0
  14. package/tiles/canvas/canvasTools.ts +66 -0
  15. package/tiles/canvas/chromeNodes.ts +35 -0
  16. package/tiles/canvas/ghosts.ts +33 -0
  17. package/tiles/canvas/insertPopover.tsx +56 -0
  18. package/tiles/canvas/popoverPlacement.ts +104 -0
  19. package/tiles/canvas/popoverSize.ts +87 -0
  20. package/tiles/canvas/providerChrome.tsx +191 -0
  21. package/tiles/canvas/scrollWatch.ts +43 -0
  22. package/tiles/canvas/settingsPopover.tsx +257 -0
  23. package/tiles/canvas/settingsTabs.ts +78 -0
  24. package/tiles/canvas/toolPanel.tsx +86 -0
  25. package/tiles/content/autoBind.ts +61 -0
  26. package/tiles/content/blockPage.css +390 -0
  27. package/tiles/content/blockPage.tsx +379 -0
  28. package/tiles/content/blockPicker.css +215 -0
  29. package/tiles/content/blockPicker.tsx +501 -0
  30. package/tiles/content/blockUpgrades.ts +145 -0
  31. package/tiles/content/blockVariants.ts +185 -0
  32. package/tiles/content/blockVocabulary.ts +77 -0
  33. package/tiles/content/catalogVersions.ts +106 -0
  34. package/tiles/content/componentPreview.css +82 -0
  35. package/tiles/content/componentPreview.tsx +249 -0
  36. package/tiles/content/content.server.ts +593 -0
  37. package/tiles/content/contentCommon.ts +133 -0
  38. package/tiles/content/disconnected.ts +56 -0
  39. package/tiles/content/distributions.css +256 -0
  40. package/tiles/content/distributions.tsx +271 -0
  41. package/tiles/content/editingAutosave.ts +125 -0
  42. package/tiles/content/editingClipboard.ts +185 -0
  43. package/tiles/content/editingConflicts.ts +223 -0
  44. package/tiles/content/editingHotkeys.ts +59 -0
  45. package/tiles/content/editingNotices.ts +105 -0
  46. package/tiles/content/formRoot.ts +18 -0
  47. package/tiles/content/fuzzySearch.ts +121 -0
  48. package/tiles/content/mediaUpload.ts +55 -0
  49. package/tiles/content/moduleSiblings.ts +9 -0
  50. package/tiles/content/mountedTree.ts +136 -0
  51. package/tiles/content/nodeSettings.css +483 -0
  52. package/tiles/content/nodeSettings.tsx +963 -0
  53. package/tiles/content/pasteText.ts +131 -0
  54. package/tiles/content/prefabFields.tsx +158 -0
  55. package/tiles/content/prefabParts.ts +105 -0
  56. package/tiles/content/previewEnvironment.ts +46 -0
  57. package/tiles/content/projectConfig.ts +46 -0
  58. package/tiles/content/projectPrefabs.ts +57 -0
  59. package/tiles/content/publishModal.css +109 -0
  60. package/tiles/content/publishModal.tsx +92 -0
  61. package/tiles/content/refsEditing.ts +412 -0
  62. package/tiles/content/scaledStage.ts +63 -0
  63. package/tiles/content/searchField.css +32 -0
  64. package/tiles/content/searchField.tsx +32 -0
  65. package/tiles/content/settingsDraft.ts +178 -0
  66. package/tiles/content/settingsIssues.tsx +124 -0
  67. package/tiles/content/themeScope.ts +72 -0
  68. package/tiles/content/themeVariables.ts +47 -0
  69. package/tiles/content/tooltipPlacement.ts +17 -0
  70. package/tiles/content/tooltips.tsx +131 -0
  71. package/tiles/content/treeMounter.ts +33 -0
  72. package/tiles/content/updatesModal.css +124 -0
  73. package/tiles/content/updatesModal.tsx +110 -0
  74. package/tiles/content/useCtrlHeld.ts +24 -0
  75. package/tiles/content/useEditingSession.ts +115 -0
  76. package/tiles/content/useMountedTree.ts +47 -0
  77. package/tiles/content/workbenchScale.css +27 -0
  78. package/tiles/hostFallback/hostFallback.component.tsx +32 -0
  79. package/tiles/hostFallback/hostFallback.css +12 -0
  80. package/tiles/hostFallback/hostFallback.editor.tsx +10 -0
  81. package/tiles/page/page.component.tsx +7 -0
  82. package/tiles/page/page.script.tsx +5 -0
  83. package/tiles/pageModules/pageModules.component.tsx +740 -0
  84. package/tiles/pageModules/pageModules.css +383 -0
  85. package/tiles/pageModules/pageModules.icon.svg +1 -0
  86. package/tiles/pageModules/servedPage.ts +465 -0
  87. package/tiles/pagesPanel/pagesPanel.component.tsx +1645 -0
  88. package/tiles/pagesPanel/pagesPanel.css +713 -0
  89. package/tiles/pagesPanel/pagesPanel.editor.tsx +23 -0
  90. package/tiles/pagesPanel/pagesSearch.ts +194 -0
  91. package/tiles/pagesPanel/routeTree.ts +98 -0
  92. package/tiles/previewBridge/landing.ts +66 -0
  93. package/tiles/previewBridge/previewAgent.ts +457 -0
  94. package/tiles/previewBridge/previewBridge.component.tsx +137 -0
  95. package/tiles/previewBridge/previewBridge.editor.tsx +10 -0
  96. package/tiles/previewBridge/previewBridgeCommon.ts +172 -0
  97. package/tiles/previewBridge/standIn.ts +22 -0
  98. package/tiles/treeView/treeView.component.tsx +514 -0
  99. package/tiles/treeView/treeView.css +303 -0
  100. package/tiles/treeView/treeView.editor.tsx +12 -0
  101. package/tiles/workbench/assistant.css +401 -0
  102. package/tiles/workbench/assistant.tsx +516 -0
  103. package/tiles/workbench/assistantChat.ts +165 -0
  104. package/tiles/workbench/dictation.ts +354 -0
  105. package/tiles/workbench/projectInfo.ts +72 -0
  106. package/tiles/workbench/recent.ts +67 -0
  107. package/tiles/workbench/workbench.component.tsx +476 -0
  108. package/tiles/workbench/workbench.css +376 -0
  109. package/tiles/workbench/workbenchAddress.ts +202 -0
  110. package/tiles/workbench/workspace.ts +775 -0
  111. package/tiles/workbenchEditor/workbenchEditor.component.tsx +461 -0
  112. package/tiles/workbenchEditor/workbenchEditor.css +370 -0
  113. package/tiles/workbenchHome/workbenchHome.component.tsx +196 -0
  114. package/tiles/workbenchHome/workbenchHome.css +271 -0
  115. package/tsconfig.json +3 -0
@@ -0,0 +1,249 @@
1
+ // The block HOVER PREVIEW: a bubble beside a block row (the palette, the canvas's insert
2
+ // popover), showing what a block looks like.
3
+ //
4
+ // DECLARED AS DATA. A preview exists where the module's editor facet says so — `preview: 'self'`
5
+ // for a block that stands on its own, or a tree for anything richer. Nothing to declare, nothing
6
+ // shown: a block rendered alone out of any composition is a guess, and a guess presented as "this
7
+ // is what you are about to place" is worse than no answer.
8
+ //
9
+ // MOUNTED, NOT RENDERED (ADR-0034). The editor does not render the specimen with its own react —
10
+ // the hosting itself is `useMountedTree`, the same one the settings panel's form goes through.
11
+ // What this file owns is the PLAN: reading the declaration and turning it into a tree for a ROOT —
12
+ // in order, the one the preview tree names, the one the block's own library ships, and failing
13
+ // both the editor's.
14
+ //
15
+ // ONE MOUNT, MANY PREVIEWS. The application lives as long as the panel; a new row hands it a new
16
+ // tree (IMountHandle.render → the page manager). No mounting on hover, no empty box while a
17
+ // module loads.
18
+ //
19
+ // SIZED BY WHAT IT SHOWS, up to a ceiling, never shrinking while one preview is open.
20
+ import { createPortal } from 'react-dom';
21
+ import { useEffect, useState } from 'react';
22
+ import { TILES_HOLDER_TOKEN } from '@mosetta/runtime/client';
23
+ import type { IMountPreview } from '@mosetta/runtime/client';
24
+ import type { IBlueprintNode } from '@mosetta/core';
25
+ import { PALETTE_PREVIEW, useComponent } from '@mosetta/react';
26
+ import { useDi } from '@mosetta/react';
27
+ import { tileScopeClass } from '@mosetta/react';
28
+ import type { IEditorSide, IPreviewTree, TPreviewSpec } from '@mosetta/react';
29
+ import { hostedPage, hostFallbackOf, loadModule } from './mountedTree';
30
+ import type { IMountSpec } from './mountedTree';
31
+ import { useMountedTree } from './useMountedTree';
32
+ import { atOwnVersion } from './blockVariants';
33
+ import './componentPreview.css';
34
+
35
+ /** the bubble grows with its content, between these */
36
+ const MIN_WIDTH = 160;
37
+ const MIN_HEIGHT = 80;
38
+ const MAX_WIDTH = 420;
39
+ const MAX_HEIGHT = 320;
40
+ /** distance from the row, and the margin the bubble keeps from the window's edges */
41
+ const GAP = 12;
42
+ const EDGE = 8;
43
+ /** the frame between the border and the specimen, so they never touch */
44
+ const PAD = 12;
45
+ /** what the frame adds to the measured content: the padding and the 1px borders */
46
+ const FRAME = PAD * 2 + 2;
47
+
48
+ export interface IPreviewTarget {
49
+ readonly reference: string;
50
+ /** from the catalog: no editor facet, no declaration — nothing to load and nothing to ask */
51
+ readonly hasEditor: boolean;
52
+ /** a root module of the block's OWN library, when it ships one (catalog `isRoot`) */
53
+ readonly libraryRoot?: string;
54
+ /** the hovered row, in window coordinates */
55
+ readonly anchor: { readonly top: number; readonly left: number; readonly right: number };
56
+ /** a PREFAB's own tree — shown as it is, under its own root, no declaration asked for */
57
+ readonly tree?: { readonly rootReference: string; readonly children: readonly IBlueprintNode[] };
58
+ }
59
+
60
+ export const editorReferenceOf = (reference: string) => reference.replace('_t_component_', '_t_editor_');
61
+
62
+ /** the mount option every specimen gets: "you are a palette preview" (IMountOptions.preview) */
63
+ const PALETTE_MOUNT: IMountPreview = { type: PALETTE_PREVIEW };
64
+
65
+ /**
66
+ * ONE declaration object per facet: its identity is what keeps the specimen application alive
67
+ * across rows (MountedTree remounts when the declaration changes) — facets with the same (empty)
68
+ * environment share one application, a facet with services of its own gets one arranged for it.
69
+ */
70
+ const previewByFacet = new WeakMap<object, IMountPreview>();
71
+
72
+ export function previewMountFor(facet: object | undefined, services: IEditorSide['previewServices']): IMountPreview {
73
+ if (!facet || !services?.length) return PALETTE_MOUNT;
74
+ let declared = previewByFacet.get(facet);
75
+ if (!declared) {
76
+ declared = { type: PALETTE_PREVIEW, services };
77
+ previewByFacet.set(facet, declared);
78
+ }
79
+ return declared;
80
+ }
81
+
82
+ /** the editor's own root — every root can host, so this is simply ours (ADR-0034 §5) */
83
+ export const previewRootOf = ({ namespace, version }: { namespace: string; version: string }) => `_ns_${namespace}_t_component_n_page_v_${version}`;
84
+
85
+ export interface IComponentFacet {
86
+ defaultSettings?: () => Record<string, unknown>;
87
+ }
88
+
89
+ export interface IEditorFacet {
90
+ preview?: TPreviewSpec;
91
+ previewServices?: IEditorSide['previewServices'];
92
+ variants?: IEditorSide['variants'];
93
+ }
94
+
95
+ type TPlan
96
+ = | { readonly status: 'looking' }
97
+ /** the module says nothing about how it looks alone — then the palette says nothing either */
98
+ | { readonly status: 'none' }
99
+ | { readonly status: 'failed' }
100
+ | { readonly status: 'ready'; readonly spec: IMountSpec };
101
+
102
+ /**
103
+ * Reads the declaration and turns it into a mount spec — what to show, and whose root shows it.
104
+ * Everything here is data and module loading — no rendering of anyone else's UI, no mounting:
105
+ * the spec goes to `useMountedTree`, which owns the application.
106
+ */
107
+ export function usePreviewPlan(target: IPreviewTarget | null, self: { namespace: string; version: string }): TPlan {
108
+ const holder = useDi(TILES_HOLDER_TOKEN);
109
+ const [plan, setPlan] = useState<TPlan>({ status: 'looking' });
110
+
111
+ useEffect(() => {
112
+ let alive = true;
113
+ if (!target) return undefined;
114
+ if (target.tree) {
115
+ setPlan({ status: 'ready', spec: { rootReference: target.tree.rootReference, tree: hostedPage(target.tree.rootReference, target.tree.children, hostFallbackOf(self)), preview: PALETTE_MOUNT } });
116
+ return undefined;
117
+ }
118
+ if (!target.hasEditor) {
119
+ setPlan({ status: 'none' });
120
+ return undefined;
121
+ }
122
+ setPlan({ status: 'looking' });
123
+ void (async () => {
124
+ try {
125
+ const [editor, component] = await Promise.all([
126
+ loadModule(holder, editorReferenceOf(target.reference)),
127
+ loadModule(holder, target.reference),
128
+ ]);
129
+ const loaded = editor?.module as IEditorFacet | undefined;
130
+ // the facet's own blocks at the facet's own version — the preview was written at some earlier one
131
+ const facet = loaded ? atOwnVersion(loaded, target.reference) : undefined;
132
+ const spec = facet?.preview;
133
+ if (!spec) {
134
+ if (alive) setPlan({ status: 'none' });
135
+ return;
136
+ }
137
+ const declared: IPreviewTree = spec === 'self'
138
+ ? {
139
+ children: [{
140
+ id: '__preview-node',
141
+ reference: target.reference,
142
+ settings: (component?.module as IComponentFacet | undefined)?.defaultSettings?.() ?? {},
143
+ children: [],
144
+ }],
145
+ }
146
+ : spec;
147
+ // whose root: the one the declaration names, the block's own library, or ours
148
+ const rootReference = declared.reference ?? target.libraryRoot ?? previewRootOf(self);
149
+ if (alive) {
150
+ setPlan({
151
+ status: 'ready',
152
+ spec: {
153
+ rootReference,
154
+ tree: hostedPage(rootReference, declared.children, hostFallbackOf(self)),
155
+ // keyed by the LOADED facet: its identity is what keeps one application across rows
156
+ preview: previewMountFor(loaded, loaded?.previewServices),
157
+ },
158
+ });
159
+ }
160
+ } catch {
161
+ if (alive) setPlan({ status: 'failed' });
162
+ }
163
+ })();
164
+ return () => {
165
+ alive = false;
166
+ };
167
+ }, [holder, target, self.namespace, self.version]);
168
+
169
+ return plan;
170
+ }
171
+
172
+ /** where the bubble goes: beside the row, clamped into the window, beak on the row's own line */
173
+ function place(anchor: IPreviewTarget['anchor'], size: { width: number; height: number }) {
174
+ const view = { width: window.innerWidth, height: window.innerHeight };
175
+ const flipped = anchor.right + GAP + size.width > view.width - EDGE;
176
+ const left = flipped
177
+ ? Math.max(EDGE, anchor.left - GAP - size.width)
178
+ : Math.min(anchor.right + GAP, view.width - EDGE - size.width);
179
+ // grows DOWN from the row — until there is no down left, and then it slides up while the beak
180
+ // stays on the row it belongs to
181
+ const top = Math.max(EDGE, Math.min(anchor.top - GAP, view.height - EDGE - size.height));
182
+ return { top, left, beak: Math.max(6, Math.min(anchor.top - top, size.height - 16)), flipped };
183
+ }
184
+
185
+ /**
186
+ * The bubble — in a PORTAL, because the panel it is anchored to scrolls and clips its own content.
187
+ * Wrapped in a scope host of the palette's module: css is built with that class as a prefix, and
188
+ * markup portalled onto the body would otherwise arrive with no styles at all. Pointer-transparent:
189
+ * something to look at, never to reach, so it can never steal the hover that opened it.
190
+ */
191
+ export function ComponentPreview({ target }: { target: IPreviewTarget | null }) {
192
+ const self = useComponent().reference;
193
+ const plan = usePreviewPlan(target, self);
194
+ // a null spec leaves the application as it is — the bubble outlives the hover on purpose
195
+ const { ref: stage, failed } = useMountedTree(plan.status === 'ready' ? plan.spec : null);
196
+ const [size, setSize] = useState({ width: MIN_WIDTH, height: MIN_HEIGHT });
197
+
198
+ // measured again for each new specimen
199
+ useEffect(() => {
200
+ if (plan.status === 'ready') setSize({ width: MIN_WIDTH, height: MIN_HEIGHT });
201
+ }, [plan]);
202
+
203
+ // measured after the specimen renders, and never allowed to shrink while it is shown: a box
204
+ // that followed its content downwards would twitch as data arrived
205
+ useEffect(() => {
206
+ const element = stage.current;
207
+ if (!element || typeof ResizeObserver === 'undefined') return undefined;
208
+ const grow = () => setSize((seen) => ({
209
+ width: Math.min(MAX_WIDTH, Math.max(seen.width, element.scrollWidth)),
210
+ height: Math.min(MAX_HEIGHT, Math.max(seen.height, element.scrollHeight)),
211
+ }));
212
+ const observer = new ResizeObserver(grow);
213
+ observer.observe(element);
214
+ grow();
215
+ return () => observer.disconnect();
216
+ }, [plan.status]);
217
+
218
+ // `failed` counts only against the plan that asked for the mount — not the one being replaced
219
+ const broken = plan.status === 'failed' || (plan.status === 'ready' && failed);
220
+ const shown = target !== null && (plan.status === 'ready' || broken);
221
+ // the window sees the FULL box — the frame included — while the styles carry the content's size
222
+ const outer = { width: size.width + FRAME, height: size.height + FRAME };
223
+ const at = place(target?.anchor ?? { top: 0, left: 0, right: 0 }, outer);
224
+ return createPortal(
225
+ <div className={tileScopeClass(self)}
226
+ style={{ display: 'contents' }}>
227
+ {/* the application outlives the hover, so the bubble is always in the document and merely
228
+ hidden — unmounting it on every pointer-out would defeat the point of mounting once */}
229
+ <div className={`palette-preview${at.flipped ? ' palette-preview--flipped' : ''}`}
230
+ data-component-preview={shown ? target.reference : undefined}
231
+ hidden={!shown}
232
+ style={{ top: `${at.top}px`, left: `${at.left}px`, width: `${size.width}px`, height: `${size.height}px`, padding: `${PAD}px` }}>
233
+ <span className="palette-preview__beak"
234
+ style={{ top: `${at.beak}px` }} />
235
+ {broken && (
236
+ <div className="palette-preview__failed"
237
+ data-preview-failed>Could not render this preview</div>
238
+ )}
239
+ <div className="palette-preview__viewport">
240
+ {/* capped at the box ceiling so long text WRAPS there instead of leaving as one line */}
241
+ <div className="palette-preview__content"
242
+ style={{ maxWidth: `${MAX_WIDTH}px` }}
243
+ ref={stage} />
244
+ </div>
245
+ </div>
246
+ </div>,
247
+ document.body,
248
+ );
249
+ }