@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.
- package/CHANGELOG.md +185 -0
- package/LICENSE +21 -0
- package/README.md +10 -2
- package/mosetta.json +6 -0
- package/package.json +35 -3
- package/prefabs/workbench/prefab.json +11 -0
- package/prefabs/workbench/versions/1.json +120 -0
- package/prefabs/workbench/versions/2.json +13 -0
- package/prefabs/workbench/versions/3.json +19 -0
- package/prefabs/workbench/versions/4.json +29 -0
- package/prefabs/workbench/versions/5.json +29 -0
- package/tiles/canvas/canvas.component.tsx +2038 -0
- package/tiles/canvas/canvas.css +973 -0
- package/tiles/canvas/canvasTools.ts +66 -0
- package/tiles/canvas/chromeNodes.ts +35 -0
- package/tiles/canvas/ghosts.ts +33 -0
- package/tiles/canvas/insertPopover.tsx +56 -0
- package/tiles/canvas/popoverPlacement.ts +104 -0
- package/tiles/canvas/popoverSize.ts +87 -0
- package/tiles/canvas/providerChrome.tsx +191 -0
- package/tiles/canvas/scrollWatch.ts +43 -0
- package/tiles/canvas/settingsPopover.tsx +257 -0
- package/tiles/canvas/settingsTabs.ts +78 -0
- package/tiles/canvas/toolPanel.tsx +86 -0
- package/tiles/content/autoBind.ts +61 -0
- package/tiles/content/blockPage.css +390 -0
- package/tiles/content/blockPage.tsx +379 -0
- package/tiles/content/blockPicker.css +215 -0
- package/tiles/content/blockPicker.tsx +501 -0
- package/tiles/content/blockUpgrades.ts +145 -0
- package/tiles/content/blockVariants.ts +185 -0
- package/tiles/content/blockVocabulary.ts +77 -0
- package/tiles/content/catalogVersions.ts +106 -0
- package/tiles/content/componentPreview.css +82 -0
- package/tiles/content/componentPreview.tsx +249 -0
- package/tiles/content/content.server.ts +593 -0
- package/tiles/content/contentCommon.ts +133 -0
- package/tiles/content/disconnected.ts +56 -0
- package/tiles/content/distributions.css +256 -0
- package/tiles/content/distributions.tsx +271 -0
- package/tiles/content/editingAutosave.ts +125 -0
- package/tiles/content/editingClipboard.ts +185 -0
- package/tiles/content/editingConflicts.ts +223 -0
- package/tiles/content/editingHotkeys.ts +59 -0
- package/tiles/content/editingNotices.ts +105 -0
- package/tiles/content/formRoot.ts +18 -0
- package/tiles/content/fuzzySearch.ts +121 -0
- package/tiles/content/mediaUpload.ts +55 -0
- package/tiles/content/moduleSiblings.ts +9 -0
- package/tiles/content/mountedTree.ts +136 -0
- package/tiles/content/nodeSettings.css +483 -0
- package/tiles/content/nodeSettings.tsx +963 -0
- package/tiles/content/pasteText.ts +131 -0
- package/tiles/content/prefabFields.tsx +158 -0
- package/tiles/content/prefabParts.ts +105 -0
- package/tiles/content/previewEnvironment.ts +46 -0
- package/tiles/content/projectConfig.ts +46 -0
- package/tiles/content/projectPrefabs.ts +57 -0
- package/tiles/content/publishModal.css +109 -0
- package/tiles/content/publishModal.tsx +92 -0
- package/tiles/content/refsEditing.ts +412 -0
- package/tiles/content/scaledStage.ts +63 -0
- package/tiles/content/searchField.css +32 -0
- package/tiles/content/searchField.tsx +32 -0
- package/tiles/content/settingsDraft.ts +178 -0
- package/tiles/content/settingsIssues.tsx +124 -0
- package/tiles/content/themeScope.ts +72 -0
- package/tiles/content/themeVariables.ts +47 -0
- package/tiles/content/tooltipPlacement.ts +17 -0
- package/tiles/content/tooltips.tsx +131 -0
- package/tiles/content/treeMounter.ts +33 -0
- package/tiles/content/updatesModal.css +124 -0
- package/tiles/content/updatesModal.tsx +110 -0
- package/tiles/content/useCtrlHeld.ts +24 -0
- package/tiles/content/useEditingSession.ts +115 -0
- package/tiles/content/useMountedTree.ts +47 -0
- package/tiles/content/workbenchScale.css +27 -0
- package/tiles/hostFallback/hostFallback.component.tsx +32 -0
- package/tiles/hostFallback/hostFallback.css +12 -0
- package/tiles/hostFallback/hostFallback.editor.tsx +10 -0
- package/tiles/page/page.component.tsx +7 -0
- package/tiles/page/page.script.tsx +5 -0
- package/tiles/pageModules/pageModules.component.tsx +740 -0
- package/tiles/pageModules/pageModules.css +383 -0
- package/tiles/pageModules/pageModules.icon.svg +1 -0
- package/tiles/pageModules/servedPage.ts +465 -0
- package/tiles/pagesPanel/pagesPanel.component.tsx +1645 -0
- package/tiles/pagesPanel/pagesPanel.css +713 -0
- package/tiles/pagesPanel/pagesPanel.editor.tsx +23 -0
- package/tiles/pagesPanel/pagesSearch.ts +194 -0
- package/tiles/pagesPanel/routeTree.ts +98 -0
- package/tiles/previewBridge/landing.ts +66 -0
- package/tiles/previewBridge/previewAgent.ts +457 -0
- package/tiles/previewBridge/previewBridge.component.tsx +137 -0
- package/tiles/previewBridge/previewBridge.editor.tsx +10 -0
- package/tiles/previewBridge/previewBridgeCommon.ts +172 -0
- package/tiles/previewBridge/standIn.ts +22 -0
- package/tiles/treeView/treeView.component.tsx +514 -0
- package/tiles/treeView/treeView.css +303 -0
- package/tiles/treeView/treeView.editor.tsx +12 -0
- package/tiles/workbench/assistant.css +401 -0
- package/tiles/workbench/assistant.tsx +516 -0
- package/tiles/workbench/assistantChat.ts +165 -0
- package/tiles/workbench/dictation.ts +354 -0
- package/tiles/workbench/projectInfo.ts +72 -0
- package/tiles/workbench/recent.ts +67 -0
- package/tiles/workbench/workbench.component.tsx +476 -0
- package/tiles/workbench/workbench.css +376 -0
- package/tiles/workbench/workbenchAddress.ts +202 -0
- package/tiles/workbench/workspace.ts +775 -0
- package/tiles/workbenchEditor/workbenchEditor.component.tsx +461 -0
- package/tiles/workbenchEditor/workbenchEditor.css +370 -0
- package/tiles/workbenchHome/workbenchHome.component.tsx +196 -0
- package/tiles/workbenchHome/workbenchHome.css +271 -0
- 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
|
+
}));
|