@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,370 @@
1
+ /* The editor page of the workbench: the tab row, the quick picker, the tree, the handle, the canvases. */
2
+
3
+ /* a pane kept mounted but out of sight: a canvas behind another tab */
4
+ .workbench__pane--hidden {
5
+ display: none !important;
6
+ }
7
+
8
+ /* ---- the editor ------------------------------------------------------------------------------ */
9
+
10
+ .workbench__editor {
11
+ display: flex;
12
+ flex: 1;
13
+ flex-direction: column;
14
+ min-height: 0;
15
+ }
16
+
17
+ .workbench__desk {
18
+ position: relative;
19
+ display: flex;
20
+ flex: 1;
21
+ min-height: 0;
22
+ }
23
+
24
+ /* the quick picker: a card under the tab row, over the desk's left edge, the compact list inside;
25
+ a clear backdrop over the whole desk takes the click that closes it */
26
+ .workbench__picker-backdrop {
27
+ position: absolute;
28
+ inset: 0;
29
+ z-index: 29;
30
+ }
31
+
32
+ .workbench__picker {
33
+ position: absolute;
34
+ top: 8px;
35
+ left: 8px;
36
+ z-index: 30;
37
+ display: flex;
38
+ flex-direction: column;
39
+ width: min(380px, calc(100% - 16px));
40
+ max-height: calc(100% - 16px);
41
+ border: 1px solid var(--wb-border);
42
+ border-radius: 10px;
43
+ background: #ffffff;
44
+ box-shadow: 0 12px 32px rgb(0 0 0 / 14%), 0 2px 6px rgb(0 0 0 / 8%);
45
+ overflow: hidden;
46
+ }
47
+
48
+ .workbench__picker-switch {
49
+ display: flex;
50
+ gap: 2px;
51
+ padding: 6px 8px 0;
52
+ border-bottom: 1px solid var(--wb-border);
53
+ }
54
+
55
+ .workbench__picker-choice {
56
+ padding: 5px 10px 7px;
57
+ border: 0;
58
+ border-bottom: 2px solid transparent;
59
+ background: none;
60
+ font: inherit;
61
+ font-size: var(--mosetta-wb-font-sm);
62
+ color: var(--wb-muted);
63
+ cursor: pointer;
64
+ }
65
+
66
+ .workbench__picker-choice:hover {
67
+ color: var(--wb-ink);
68
+ }
69
+
70
+ .workbench__picker-choice--on {
71
+ color: var(--wb-ink);
72
+ font-weight: 600;
73
+ border-bottom-color: var(--wb-blue);
74
+ }
75
+
76
+ .workbench__picker-list {
77
+ display: flex;
78
+ flex-direction: column;
79
+ min-height: 160px;
80
+ max-height: 60vh;
81
+ overflow: hidden;
82
+ }
83
+
84
+ .workbench__picker-list > *,
85
+ .workbench__picker-list > * > *,
86
+ .workbench__picker-list > * > * > * {
87
+ flex: 1;
88
+ min-height: 0;
89
+ }
90
+
91
+ .workbench__tab-new--on {
92
+ background: #d4d4d8;
93
+ color: var(--wb-ink);
94
+ }
95
+
96
+ .workbench__tree {
97
+ flex: none;
98
+ width: 280px; /* the document's own width is set inline */
99
+ max-width: calc(100% - 320px); /* the canvas keeps at least this much */
100
+ min-height: 0;
101
+ overflow-y: auto;
102
+ background: #ffffff;
103
+ }
104
+
105
+ /* the handle between the tree and the canvas: a hairline that thickens and turns blue under the
106
+ pointer; the grab area is wider than the line */
107
+ .workbench__splitter {
108
+ position: relative;
109
+ flex: none;
110
+ width: 6px;
111
+ cursor: col-resize;
112
+ z-index: 2;
113
+ touch-action: none;
114
+ }
115
+
116
+ .workbench__splitter::before {
117
+ content: '';
118
+ position: absolute;
119
+ top: 0;
120
+ bottom: 0;
121
+ left: 0;
122
+ width: 1px;
123
+ background: var(--wb-border);
124
+ transition: width 80ms ease, background-color 80ms ease;
125
+ }
126
+
127
+ .workbench__splitter:hover::before,
128
+ .workbench__desk--resizing .workbench__splitter::before {
129
+ width: 3px;
130
+ background: var(--wb-blue);
131
+ }
132
+
133
+ /* while the handle is dragged the canvases take no pointer events (an iframe would swallow the drag) */
134
+ .workbench__desk--resizing {
135
+ cursor: col-resize;
136
+ user-select: none;
137
+ }
138
+
139
+ .workbench__desk--resizing .workbench__canvases {
140
+ pointer-events: none;
141
+ }
142
+
143
+ .workbench__canvases {
144
+ position: relative;
145
+ display: flex;
146
+ flex: 1;
147
+ min-width: 0;
148
+ min-height: 0;
149
+ }
150
+
151
+ .workbench__canvas {
152
+ display: flex;
153
+ flex: 1;
154
+ min-width: 0;
155
+ min-height: 0;
156
+ }
157
+
158
+ .workbench__canvas > *,
159
+ .workbench__canvas > * > *,
160
+ .workbench__canvas > * > * > * {
161
+ flex: 1;
162
+ min-width: 0;
163
+ min-height: 0;
164
+ }
165
+
166
+ .workbench__blank {
167
+ display: flex;
168
+ flex: 1;
169
+ flex-direction: column;
170
+ align-items: center;
171
+ justify-content: center;
172
+ gap: 12px;
173
+ color: var(--wb-muted);
174
+ }
175
+
176
+ .workbench__blank p {
177
+ margin: 0;
178
+ }
179
+
180
+ /* ---- the tabs: a browser's tab row — the one in front joined to the content below it --------- */
181
+
182
+ .workbench__tabs {
183
+ display: flex;
184
+ align-items: flex-end;
185
+ flex: none;
186
+ gap: 0;
187
+ height: 38px;
188
+ padding: 6px 8px 0;
189
+ background: var(--wb-frame);
190
+ overflow-x: auto;
191
+ overflow-y: hidden;
192
+ scrollbar-width: none;
193
+ }
194
+
195
+ .workbench__tabs::-webkit-scrollbar {
196
+ display: none;
197
+ }
198
+
199
+ .workbench__tab {
200
+ position: relative;
201
+ display: flex;
202
+ align-items: center;
203
+ gap: 6px;
204
+ flex: 0 1 220px;
205
+ min-width: 96px;
206
+ height: 32px;
207
+ padding: 0 8px 0 12px;
208
+ border-radius: var(--wb-tab-radius) var(--wb-tab-radius) 0 0;
209
+ color: #52525b;
210
+ cursor: pointer;
211
+ user-select: none;
212
+ outline: none;
213
+ }
214
+
215
+ /* a hairline between tabs at rest, none beside the front one */
216
+ .workbench__tab + .workbench__tab::after {
217
+ content: '';
218
+ position: absolute;
219
+ top: 8px;
220
+ bottom: 8px;
221
+ left: -1px;
222
+ width: 1px;
223
+ background: #c9c9cf;
224
+ }
225
+
226
+ .workbench__tab--front + .workbench__tab::after,
227
+ .workbench__tab + .workbench__tab--front::after {
228
+ display: none;
229
+ }
230
+
231
+ .workbench__tab:hover {
232
+ background: #f1f1f3;
233
+ color: var(--wb-ink);
234
+ }
235
+
236
+ .workbench__tab:focus-visible {
237
+ box-shadow: inset 0 0 0 2px var(--wb-blue);
238
+ }
239
+
240
+ /* the front tab: the content's own white, and the two curves that make it one with the row below —
241
+ a square at each foot, painted with a circle of the frame's colour cut out of the tab's white */
242
+ .workbench__tab--front,
243
+ .workbench__tab--front:hover {
244
+ background: #ffffff;
245
+ color: var(--wb-ink);
246
+ }
247
+
248
+ .workbench__tab--front::before {
249
+ content: '';
250
+ position: absolute;
251
+ right: 100%;
252
+ bottom: 0;
253
+ width: var(--wb-tab-radius);
254
+ height: var(--wb-tab-radius);
255
+ background: radial-gradient(circle at 0 0, transparent calc(var(--wb-tab-radius) - 0.5px), #ffffff var(--wb-tab-radius));
256
+ pointer-events: none;
257
+ }
258
+
259
+ /* the right curve: the tab's own ::after is taken by the hairline, so the close button (static,
260
+ positioned against the tab) lends its ::before — `left: 100%` is the TAB's right edge */
261
+ .workbench__tab--front > .workbench__tab-close::before {
262
+ content: '';
263
+ position: absolute;
264
+ bottom: 0;
265
+ left: 100%;
266
+ width: var(--wb-tab-radius);
267
+ height: var(--wb-tab-radius);
268
+ background: radial-gradient(circle at 100% 0, transparent calc(var(--wb-tab-radius) - 0.5px), #ffffff var(--wb-tab-radius));
269
+ pointer-events: none;
270
+ }
271
+
272
+ .workbench__tab-kind {
273
+ display: flex;
274
+ flex: none;
275
+ color: #a1a1aa;
276
+ }
277
+
278
+ .workbench__tab--front .workbench__tab-kind {
279
+ color: var(--wb-blue);
280
+ }
281
+
282
+ .workbench__tab-kind svg {
283
+ width: var(--mosetta-wb-icon-sm);
284
+ height: var(--mosetta-wb-icon-sm);
285
+ }
286
+
287
+ .workbench__tab-label {
288
+ flex: 1;
289
+ min-width: 0;
290
+ overflow: hidden;
291
+ text-overflow: ellipsis;
292
+ white-space: nowrap;
293
+ font-size: var(--mosetta-wb-font-sm);
294
+ }
295
+
296
+ .workbench__tab--front .workbench__tab-label {
297
+ font-weight: 500;
298
+ }
299
+
300
+ .workbench__tab-loading {
301
+ flex: none;
302
+ width: 8px;
303
+ height: 8px;
304
+ border-radius: 50%;
305
+ background: var(--wb-blue);
306
+ animation: workbench-pulse 1s ease-in-out infinite;
307
+ }
308
+
309
+ @keyframes workbench-pulse {
310
+ 50% { opacity: 0.25; }
311
+ }
312
+
313
+ .workbench__tab-close {
314
+ position: static;
315
+ display: flex;
316
+ align-items: center;
317
+ justify-content: center;
318
+ flex: none;
319
+ width: 18px;
320
+ height: 18px;
321
+ padding: 0;
322
+ border: 0;
323
+ border-radius: 50%;
324
+ background: none;
325
+ color: #71717a;
326
+ cursor: pointer;
327
+ opacity: 0;
328
+ }
329
+
330
+ .workbench__tab:hover .workbench__tab-close,
331
+ .workbench__tab--front .workbench__tab-close,
332
+ .workbench__tab-close:focus-visible {
333
+ opacity: 1;
334
+ }
335
+
336
+ .workbench__tab-close:hover {
337
+ background: #d4d4d8;
338
+ color: var(--wb-ink);
339
+ }
340
+
341
+ .workbench__tab-close svg {
342
+ width: 10px;
343
+ height: 10px;
344
+ }
345
+
346
+ .workbench__tab-new {
347
+ display: flex;
348
+ align-items: center;
349
+ justify-content: center;
350
+ flex: none;
351
+ width: 28px;
352
+ height: 28px;
353
+ margin: 0 0 2px 6px;
354
+ padding: 0;
355
+ border: 0;
356
+ border-radius: 50%;
357
+ background: none;
358
+ color: #52525b;
359
+ cursor: pointer;
360
+ }
361
+
362
+ .workbench__tab-new:hover {
363
+ background: #d4d4d8;
364
+ color: var(--wb-ink);
365
+ }
366
+
367
+ .workbench__tab-new svg {
368
+ width: var(--mosetta-wb-icon-sm);
369
+ height: var(--mosetta-wb-icon-sm);
370
+ }
@@ -0,0 +1,196 @@
1
+ // HOME of the workbench (ADR-0051 §4): the project — its name and addresses, the site itself as
2
+ // it is published, and beside it what is worth knowing at a glance. One page under
3
+ // `/workbench/{...rest}`: whatever the sidebar's links name that no other page answers lands here.
4
+ import { defineComponentSide, Link, markInternalTile } from '@mosetta/react';
5
+ import { useIsPreviewMode, useRoute } from '@mosetta/react';
6
+ import { Mosaic } from '@mosetta/ui';
7
+ import { useEffect, useState } from 'react';
8
+ import { useServerAction } from '@mosetta/react/server-action';
9
+ import { listPagesToken } from '../content/contentCommon';
10
+ import '../content/workbenchScale.css';
11
+ import './workbenchHome.css';
12
+ import { basePathOf } from '../workbench/workbenchAddress';
13
+ import { IProjectInfo, useProjectInfo } from '../workbench/projectInfo';
14
+
15
+ const OutIcon = (
16
+ <svg viewBox="0 0 16 16"
17
+ aria-hidden="true">
18
+ <path d="M6.5 3.5H3.5v9h9V9.5M9 3.5h3.5V7M12.5 3.5L7.5 8.5"
19
+ fill="none"
20
+ stroke="currentColor"
21
+ strokeWidth="1.4"
22
+ strokeLinecap="round" />
23
+ </svg>
24
+ );
25
+
26
+ const projectNameOf = (host: string): string => host.split(':')[0].split('.')[0] || host;
27
+
28
+ /** the screen the Home preview shows the site on: a common desktop, 16:10 */
29
+ const DESKTOP_WIDTH = 1440;
30
+ const DESKTOP_HEIGHT = 900;
31
+
32
+ /**
33
+ * Home: the project — its name and addresses, the site itself as it is published (half the
34
+ * width: a window, not a wall), and beside it what is worth knowing at a glance: how many pages
35
+ * are live and how many drafts are in progress, the prefabs, the public address, the live routes
36
+ * each a step into the editor. The site's frame loads behind a blur and a spinner — a blank
37
+ * white card while a page loads reads as broken.
38
+ */
39
+ function HomeView({ project, base }: { project: IProjectInfo; base: string }) {
40
+ const origin = typeof window === 'undefined' ? '' : window.location.origin;
41
+ const addresses = [origin, ...(project.publicOrigin && project.publicOrigin !== origin ? [project.publicOrigin] : [])];
42
+ // the listings the pages views hold already (the same cache entries) — counted here
43
+ const pages = useServerAction({ token: listPagesToken, input: { kind: 'pages' } });
44
+ const prefabs = useServerAction({ token: listPagesToken, input: { kind: 'prefabs' } });
45
+ const listed = pages.isSuccess() && pages.serverData.ok ? pages.serverData.pages : null;
46
+ const prefabList = prefabs.isSuccess() && prefabs.serverData.ok ? prefabs.serverData.pages : null;
47
+ const live = listed?.filter((page) => page.meta.live != null && !page.meta.locked) ?? [];
48
+ const drafts = listed?.reduce((sum, page) => sum + page.drafts.length, 0) ?? 0;
49
+ const [frameLoaded, setFrameLoaded] = useState(false);
50
+ // the site believes it is on a desktop screen (DESKTOP_WIDTH wide) and is scaled down into its
51
+ // half of the page: a page honestly squeezed into half a screen looks like a phone's, which is
52
+ // not what the author published
53
+ const [previewBox, setPreviewBox] = useState<HTMLDivElement | null>(null);
54
+ const [previewSize, setPreviewSize] = useState<{ width: number; height: number }>({ width: 0, height: 0 });
55
+ useEffect(() => {
56
+ if (!previewBox) return undefined;
57
+ const measure = () => setPreviewSize({ width: previewBox.clientWidth, height: previewBox.clientHeight });
58
+ measure();
59
+ const Observer = previewBox.ownerDocument.defaultView?.ResizeObserver;
60
+ if (!Observer) return undefined;
61
+ const observer = new Observer(measure);
62
+ observer.observe(previewBox);
63
+ return () => observer.disconnect();
64
+ }, [previewBox]);
65
+ const previewScale = previewSize.width ? Math.min(previewSize.width / DESKTOP_WIDTH, previewSize.height / DESKTOP_HEIGHT) : 1;
66
+ const bare = (address: string) => address.replace(/^https?:\/\//, '');
67
+ return (
68
+ <div className="workbench__home"
69
+ data-test-id="workbench-home">
70
+ <header className="workbench__home-head">
71
+ <Mosaic seed={projectNameOf(project.host)}
72
+ size={48}
73
+ className="workbench__home-mosaic" />
74
+ <div>
75
+ <h1 className="workbench__home-name">{projectNameOf(project.host)}</h1>
76
+ <div className="workbench__home-addresses">
77
+ {addresses.map((address) => (
78
+ <a key={address}
79
+ className="workbench__home-address"
80
+ href={address}
81
+ target="_blank"
82
+ rel="noreferrer">
83
+ {bare(address)}
84
+ {OutIcon}
85
+ </a>
86
+ ))}
87
+ </div>
88
+ </div>
89
+ </header>
90
+ <div className="workbench__home-body">
91
+ <section className="workbench__home-card">
92
+ <div className="workbench__home-card-head">
93
+ <span className="workbench__home-card-title">The home page, as published</span>
94
+ <span className="workbench__home-card-actions">
95
+ <a className="workbench__button workbench__button--ghost"
96
+ href="/"
97
+ target="_blank"
98
+ rel="noreferrer">
99
+ Open the site
100
+ </a>
101
+ <Link className="workbench__button"
102
+ href={`${base}/pages`}>
103
+ Edit the pages
104
+ </Link>
105
+ </span>
106
+ </div>
107
+ {/* the live site, as a reader gets it: the editor's own window keeps nothing of it */}
108
+ <div ref={setPreviewBox}
109
+ className={`workbench__home-preview${frameLoaded ? '' : ' workbench__home-preview--loading'}`}
110
+ data-test-id="workbench-home-preview"
111
+ data-loaded={frameLoaded || undefined}
112
+ data-preview-scale={previewScale.toFixed(3)}>
113
+ <iframe className="workbench__home-frame"
114
+ title="The site's home page"
115
+ src="/"
116
+ loading="lazy"
117
+ style={{ width: `${DESKTOP_WIDTH}px`, height: `${DESKTOP_HEIGHT}px`, transform: `scale(${previewScale})` }}
118
+ onLoad={() => setFrameLoaded(true)} />
119
+ {frameLoaded ? null : (
120
+ <div className="workbench__home-veil"
121
+ aria-label="Loading the home page">
122
+ <span className="workbench__spinner" />
123
+ </div>
124
+ )}
125
+ </div>
126
+ </section>
127
+ <aside className="workbench__home-facts"
128
+ data-test-id="workbench-home-facts">
129
+ <div className="workbench__home-fact-row">
130
+ <Link className="workbench__home-fact workbench__home-fact--button"
131
+ href={`${base}/pages`}>
132
+ <span className="workbench__home-fact-value">{listed ? live.length : '…'}</span>
133
+ <span className="workbench__home-fact-label">published {live.length === 1 ? 'page' : 'pages'}{listed && drafts ? ` · ${drafts} ${drafts === 1 ? 'draft' : 'drafts'} in progress` : ''}</span>
134
+ </Link>
135
+ <Link className="workbench__home-fact workbench__home-fact--button"
136
+ href={`${base}/prefabs`}>
137
+ <span className="workbench__home-fact-value">{prefabList ? prefabList.length : '…'}</span>
138
+ <span className="workbench__home-fact-label">{prefabList?.length === 1 ? 'prefab' : 'prefabs'}</span>
139
+ </Link>
140
+ </div>
141
+ <div className="workbench__home-fact">
142
+ <span className="workbench__home-fact-value workbench__home-fact-value--address">
143
+ {project.publicOrigin ? bare(project.publicOrigin) : bare(origin)}
144
+ </span>
145
+ <span className="workbench__home-fact-label">
146
+ {project.publicOrigin ? 'the public address — readers get the pages there' : 'the address readers get the pages at'}
147
+ </span>
148
+ </div>
149
+ {live.length > 0 ? (
150
+ <div className="workbench__home-fact workbench__home-routes">
151
+ <span className="workbench__home-fact-label">live routes</span>
152
+ <ul className="workbench__home-route-list">
153
+ {live.slice(0, 10).map((page) => (
154
+ <li key={page.name}>
155
+ <Link className="workbench__home-route"
156
+ data-test-id="workbench-home-route"
157
+ data-page={page.name}
158
+ data-tooltip={`Open ${page.livePattern ?? page.name} in the editor`}
159
+ href={`${base}/pages/${encodeURIComponent(page.name)}`}>
160
+ <span className="workbench__home-route-path">{page.livePattern ?? page.name}</span>
161
+ <span className="workbench__home-route-rev">r{page.meta.live}</span>
162
+ </Link>
163
+ </li>
164
+ ))}
165
+ {live.length > 10 ? (
166
+ <li className="workbench__home-route-more">…and {live.length - 10} more — see the pages</li>
167
+ ) : null}
168
+ </ul>
169
+ </div>
170
+ ) : null}
171
+ </aside>
172
+ </div>
173
+ </div>
174
+ );
175
+ }
176
+
177
+
178
+ function HomeBlock() {
179
+ const route = useRoute();
180
+ const project = useProjectInfo();
181
+ const preview = useIsPreviewMode();
182
+ if (preview) {
183
+ return <div className="workbench__home workbench__home--bare"
184
+ data-workbench-preview-stub>Home inside a preview does not load the site</div>;
185
+ }
186
+ if (typeof window === 'undefined') return null; // the facts and the frame are the browser's
187
+ return <HomeView project={project}
188
+ base={basePathOf(route?.pattern ?? null, window.location.pathname)} />;
189
+ }
190
+
191
+ export default markInternalTile(defineComponentSide({
192
+ Component: HomeBlock,
193
+ defaultSettings() {
194
+ return {};
195
+ },
196
+ }));