@design.estate/wcctools 6.1.0 → 7.0.0
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 +41 -0
- package/dist_shell/bundle.js +405 -368
- package/dist_shell/bundle.js.map +1 -1
- package/dist_shell/bundle.js.third-party-notices.json +52 -0
- package/dist_shell/bundle.js.third-party-notices.txt +107 -1
- package/dist_shell/index.html +1 -1
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/capture/classes.capturebrowser.d.ts +65 -0
- package/dist_ts/capture/classes.capturebrowser.js +251 -0
- package/dist_ts/capture/classes.captureservice.d.ts +39 -0
- package/dist_ts/capture/classes.captureservice.js +119 -0
- package/dist_ts/capture/classes.screencast.d.ts +47 -0
- package/dist_ts/capture/classes.screencast.js +127 -0
- package/dist_ts/capture/errors.d.ts +14 -0
- package/dist_ts/capture/errors.js +23 -0
- package/dist_ts/capture/images.d.ts +3 -0
- package/dist_ts/capture/images.js +58 -0
- package/dist_ts/capture/index.d.ts +4 -0
- package/dist_ts/capture/index.js +5 -0
- package/dist_ts/capture/interaction.d.ts +11 -0
- package/dist_ts/capture/interaction.js +138 -0
- package/dist_ts/capture/navigationguard.d.ts +44 -0
- package/dist_ts/capture/navigationguard.js +129 -0
- package/dist_ts/capture/previewpage.d.ts +29 -0
- package/dist_ts/capture/previewpage.js +96 -0
- package/dist_ts/capture/request.d.ts +69 -0
- package/dist_ts/capture/request.js +239 -0
- package/dist_ts/classes.bundlestatus.d.ts +36 -0
- package/dist_ts/classes.bundlestatus.js +110 -0
- package/dist_ts/classes.devapi.d.ts +3 -0
- package/dist_ts/classes.devapi.js +8 -1
- package/dist_ts/classes.devserver.d.ts +65 -14
- package/dist_ts/classes.devserver.js +157 -52
- package/dist_ts/classes.hostpolicy.d.ts +63 -0
- package/dist_ts/classes.hostpolicy.js +171 -0
- package/dist_ts/cli.js +28 -3
- package/dist_ts/cli.screenshot.d.ts +6 -0
- package/dist_ts/cli.screenshot.js +89 -0
- package/dist_ts/index.d.ts +0 -2
- package/dist_ts/index.js +1 -3
- package/dist_ts/plugins.d.ts +6 -2
- package/dist_ts/plugins.js +8 -3
- package/dist_ts_interfaces/capture.d.ts +151 -0
- package/dist_ts_interfaces/capture.js +2 -0
- package/dist_ts_interfaces/index.d.ts +1 -0
- package/dist_ts_interfaces/index.js +2 -1
- package/dist_ts_interfaces/requests.d.ts +59 -0
- package/dist_ts_shell/bundlestatus.d.ts +15 -0
- package/dist_ts_shell/bundlestatus.js +64 -0
- package/dist_ts_shell/elements/wcc-contextmenu.d.ts +3 -0
- package/dist_ts_shell/elements/wcc-contextmenu.js +18 -3
- package/dist_ts_shell/elements/wcc-preview-frame.d.ts +6 -0
- package/dist_ts_shell/elements/wcc-preview-frame.js +50 -2
- package/dist_ts_shell/elements/wcc-recording-panel.d.ts +9 -0
- package/dist_ts_shell/elements/wcc-recording-panel.js +12 -1
- package/dist_ts_shell/elements/wcc-shell.d.ts +15 -0
- package/dist_ts_shell/elements/wcc-shell.js +45 -3
- package/dist_ts_shell/plugins.d.ts +2 -1
- package/dist_ts_shell/plugins.js +3 -2
- package/dist_ts_shell/services/framesampler.service.d.ts +23 -0
- package/dist_ts_shell/services/framesampler.service.js +101 -0
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/package.json +6 -2
- package/readme.md +108 -6
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/capture/classes.capturebrowser.ts +290 -0
- package/ts/capture/classes.captureservice.ts +152 -0
- package/ts/capture/classes.screencast.ts +152 -0
- package/ts/capture/errors.ts +25 -0
- package/ts/capture/images.ts +61 -0
- package/ts/capture/index.ts +4 -0
- package/ts/capture/interaction.ts +146 -0
- package/ts/capture/navigationguard.ts +147 -0
- package/ts/capture/previewpage.ts +130 -0
- package/ts/capture/request.ts +304 -0
- package/ts/classes.bundlestatus.ts +123 -0
- package/ts/classes.devapi.ts +9 -0
- package/ts/classes.devserver.ts +172 -50
- package/ts/classes.hostpolicy.ts +201 -0
- package/ts/cli.screenshot.ts +95 -0
- package/ts/cli.ts +28 -2
- package/ts/index.ts +0 -2
- package/ts/plugins.ts +9 -2
- package/ts_interfaces/bridge.ts +95 -0
- package/ts_interfaces/capture.ts +144 -0
- package/ts_interfaces/catalog.ts +37 -0
- package/ts_interfaces/index.ts +5 -0
- package/ts_interfaces/plugins.ts +3 -0
- package/ts_interfaces/requests.ts +104 -0
- package/ts_interfaces/standard.ts +103 -0
- package/ts_shared/index.ts +1 -0
- package/ts_shared/plugins.ts +3 -0
- package/ts_shared/previewroute.ts +58 -0
- package/ts_shell/bundlestatus.ts +68 -0
- package/ts_shell/elements/wcc-contextmenu.ts +306 -0
- package/ts_shell/elements/wcc-preview-frame.ts +167 -0
- package/ts_shell/elements/wcc-properties.ts +998 -0
- package/ts_shell/elements/wcc-record-button.ts +108 -0
- package/ts_shell/elements/wcc-recording-panel.ts +1017 -0
- package/ts_shell/elements/wcc-shell.ts +485 -0
- package/ts_shell/elements/wcc-sidebar.ts +1419 -0
- package/ts_shell/index.html +21 -0
- package/ts_shell/index.ts +4 -0
- package/ts_shell/plugins.ts +12 -0
- package/ts_shell/previewconnection.ts +103 -0
- package/ts_shell/services/framesampler.service.ts +112 -0
- package/ts_shell/services/recorder.service.ts +451 -0
- package/ts_shell/types/dom-mediacapture-stub/index.d.ts +12 -0
- package/ts_shell/types/dom-mediacapture-stub/package.json +6 -0
- package/ts_shell/types/dom-webcodecs-stub/index.d.ts +2 -0
- package/ts_shell/types/dom-webcodecs-stub/package.json +6 -0
- package/ts_web/00_commitinfo_data.ts +1 -1
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
import { DeesElement, property, state, html, customElement, type TemplateResult } from '@design.estate/dees-element';
|
|
2
|
+
import * as plugins from '../plugins.js';
|
|
3
|
+
import { WccPreviewConnection } from '../previewconnection.js';
|
|
4
|
+
import { WccBundleStatusClient } from '../bundlestatus.js';
|
|
5
|
+
import type { TWccViewport, WccPreviewFrame } from './wcc-preview-frame.js';
|
|
6
|
+
import type { WccSidebar } from './wcc-sidebar.js';
|
|
7
|
+
import './wcc-preview-frame.js';
|
|
8
|
+
import './wcc-sidebar.js';
|
|
9
|
+
import './wcc-properties.js';
|
|
10
|
+
|
|
11
|
+
/** Parameters of a `/wcctools-route/...` URL. */
|
|
12
|
+
export interface IWccShellRouteParams {
|
|
13
|
+
sectionName: string;
|
|
14
|
+
itemName: string;
|
|
15
|
+
demoIndex?: string;
|
|
16
|
+
viewport: string;
|
|
17
|
+
theme: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const viewports: TWccViewport[] = ['native', 'desktop', 'tablet', 'phablet', 'phone'];
|
|
21
|
+
const defaultSidebarWidth = 200;
|
|
22
|
+
|
|
23
|
+
const selectionKey = (selectionArg: plugins.interfaces.IWccSelection | null): string | null =>
|
|
24
|
+
selectionArg ? `${selectionArg.sectionName}::${selectionArg.itemName}::${selectionArg.demoIndex}` : null;
|
|
25
|
+
|
|
26
|
+
declare global {
|
|
27
|
+
interface HTMLElementTagNameMap {
|
|
28
|
+
'wcc-shell': WccShell;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The wcctools UI: catalog sidebar, properties and preview controls, and the preview frame
|
|
34
|
+
* that shows the catalog's own document. The shell owns the `/wcctools-route/...` URL and
|
|
35
|
+
* drives the preview through its bridge; a reloaded preview reconnects without the shell
|
|
36
|
+
* reloading.
|
|
37
|
+
*/
|
|
38
|
+
@customElement('wcc-shell')
|
|
39
|
+
export class WccShell extends DeesElement {
|
|
40
|
+
/** The path the server serves the preview document on. */
|
|
41
|
+
@property({ attribute: 'preview-path' })
|
|
42
|
+
accessor previewPath: string = plugins.shared.previewRoutePath;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The dev server's typed API. With it, the shell follows the bundle status and shows a failed
|
|
46
|
+
* catalog bundle over the preview; without it (the shell outside `wcctools dev`), it does not.
|
|
47
|
+
* Read when the shell connects.
|
|
48
|
+
*/
|
|
49
|
+
@property({ attribute: 'api-path' })
|
|
50
|
+
accessor apiPath: string = '';
|
|
51
|
+
|
|
52
|
+
@property({ attribute: false })
|
|
53
|
+
accessor selection: plugins.interfaces.IWccSelection | null = null;
|
|
54
|
+
|
|
55
|
+
@property()
|
|
56
|
+
accessor selectedViewport: TWccViewport = 'desktop';
|
|
57
|
+
|
|
58
|
+
@property()
|
|
59
|
+
accessor selectedTheme: plugins.interfaces.TWccTheme = 'dark';
|
|
60
|
+
|
|
61
|
+
@property()
|
|
62
|
+
accessor searchQuery: string = '';
|
|
63
|
+
|
|
64
|
+
// Pinned items as Set of "sectionName::itemName"
|
|
65
|
+
@property({ attribute: false })
|
|
66
|
+
accessor pinnedItems: Set<string> = new Set();
|
|
67
|
+
|
|
68
|
+
@property({ type: Number })
|
|
69
|
+
accessor sidebarWidth: number = defaultSidebarWidth;
|
|
70
|
+
|
|
71
|
+
/** The catalog as the connected preview describes it; kept while the preview reloads. */
|
|
72
|
+
@state()
|
|
73
|
+
accessor manifest: plugins.interfaces.IWccCatalogManifest | null = null;
|
|
74
|
+
|
|
75
|
+
/** The connected preview's bridge; null while the preview loads. */
|
|
76
|
+
@state()
|
|
77
|
+
accessor bridge: plugins.interfaces.IWccPreviewBridge | null = null;
|
|
78
|
+
|
|
79
|
+
/** Counts the renders of the preview, so the properties panel reads them. */
|
|
80
|
+
@state()
|
|
81
|
+
accessor renderCount: number = 0;
|
|
82
|
+
|
|
83
|
+
@state()
|
|
84
|
+
accessor previewSrc: string = '';
|
|
85
|
+
|
|
86
|
+
@state()
|
|
87
|
+
accessor advancedEditorOpen: boolean = false;
|
|
88
|
+
|
|
89
|
+
@state()
|
|
90
|
+
accessor isResizing: boolean = false;
|
|
91
|
+
|
|
92
|
+
@state()
|
|
93
|
+
accessor captureTarget: HTMLElement | null = null;
|
|
94
|
+
|
|
95
|
+
/** The catalog bundles whose latest completed run failed, as the dev server reports them. */
|
|
96
|
+
@state()
|
|
97
|
+
accessor bundleFailures: plugins.interfaces.IWccBundleStatus[] = [];
|
|
98
|
+
|
|
99
|
+
public get isNative(): boolean {
|
|
100
|
+
return this.selectedViewport === 'native';
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private frameScrollY: number = 0;
|
|
104
|
+
private sidebarScrollY: number = 0;
|
|
105
|
+
private scrollPositionsRestored: boolean = false;
|
|
106
|
+
/** The selection the preview shows, as far as the shell asked it to. */
|
|
107
|
+
private previewSelectionKey: string | null = null;
|
|
108
|
+
|
|
109
|
+
private connection = new WccPreviewConnection(
|
|
110
|
+
() => this.shadowRoot?.querySelector<WccPreviewFrame>('wcc-preview-frame')?.iframe ?? null,
|
|
111
|
+
{
|
|
112
|
+
onReady: (bridgeArg) => {
|
|
113
|
+
this.handlePreviewReady(bridgeArg).catch((error) => {
|
|
114
|
+
console.error('wcctools: connecting the preview failed', error);
|
|
115
|
+
});
|
|
116
|
+
},
|
|
117
|
+
onDisconnect: () => {
|
|
118
|
+
this.bridge = null;
|
|
119
|
+
},
|
|
120
|
+
onEscape: () => this.leaveNative(),
|
|
121
|
+
onScroll: (scrollYArg) => {
|
|
122
|
+
this.frameScrollY = scrollYArg;
|
|
123
|
+
this.debouncedScrollUpdate();
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
);
|
|
127
|
+
private bundleStatusClient: WccBundleStatusClient | null = null;
|
|
128
|
+
private routeDisposers: Array<() => void> = [];
|
|
129
|
+
private scrollDisposers: Array<() => void> = [];
|
|
130
|
+
private scrollUpdateTimeout: ReturnType<typeof setTimeout> | undefined;
|
|
131
|
+
|
|
132
|
+
public render(): TemplateResult {
|
|
133
|
+
return html`
|
|
134
|
+
<style>
|
|
135
|
+
:host {
|
|
136
|
+
background: #fcfcfc;
|
|
137
|
+
display: block;
|
|
138
|
+
box-sizing: border-box;
|
|
139
|
+
position: fixed;
|
|
140
|
+
inset: 0;
|
|
141
|
+
min-width: 0;
|
|
142
|
+
overflow: hidden;
|
|
143
|
+
container-type: inline-size;
|
|
144
|
+
container-name: wccShell;
|
|
145
|
+
}
|
|
146
|
+
:host([hidden]) {
|
|
147
|
+
display: none;
|
|
148
|
+
}
|
|
149
|
+
</style>
|
|
150
|
+
<wcc-sidebar
|
|
151
|
+
.manifest=${this.manifest}
|
|
152
|
+
.selection=${this.selection}
|
|
153
|
+
.searchQuery=${this.searchQuery}
|
|
154
|
+
.pinnedItems=${this.pinnedItems}
|
|
155
|
+
.sidebarWidth=${this.sidebarWidth}
|
|
156
|
+
.isNative=${this.isNative}
|
|
157
|
+
@selectionChanged=${(eventArg: CustomEvent<plugins.interfaces.IWccSelection>) => {
|
|
158
|
+
this.selection = eventArg.detail;
|
|
159
|
+
this.buildUrl();
|
|
160
|
+
}}
|
|
161
|
+
@searchChanged=${(eventArg: CustomEvent<string>) => {
|
|
162
|
+
this.searchQuery = eventArg.detail;
|
|
163
|
+
this.updateUrlWithScrollState();
|
|
164
|
+
}}
|
|
165
|
+
@pinnedChanged=${(eventArg: CustomEvent<Set<string>>) => {
|
|
166
|
+
this.pinnedItems = eventArg.detail;
|
|
167
|
+
this.updateUrlWithScrollState();
|
|
168
|
+
}}
|
|
169
|
+
@widthChanged=${(eventArg: CustomEvent<number>) => {
|
|
170
|
+
this.sidebarWidth = eventArg.detail;
|
|
171
|
+
}}
|
|
172
|
+
@widthCommitted=${() => this.updateUrlWithScrollState()}
|
|
173
|
+
@resizingChanged=${(eventArg: CustomEvent<boolean>) => {
|
|
174
|
+
this.isResizing = eventArg.detail;
|
|
175
|
+
}}
|
|
176
|
+
></wcc-sidebar>
|
|
177
|
+
<wcc-properties
|
|
178
|
+
role="region"
|
|
179
|
+
aria-label="Properties and preview controls"
|
|
180
|
+
.bridge=${this.bridge}
|
|
181
|
+
.renderCount=${this.renderCount}
|
|
182
|
+
.captureTarget=${this.captureTarget}
|
|
183
|
+
.selectedViewport=${this.selectedViewport}
|
|
184
|
+
.selectedTheme=${this.selectedTheme}
|
|
185
|
+
.isNative=${this.isNative}
|
|
186
|
+
.sidebarWidth=${this.sidebarWidth}
|
|
187
|
+
@selectedViewport=${(eventArg: CustomEvent<TWccViewport>) => {
|
|
188
|
+
this.selectedViewport = eventArg.detail;
|
|
189
|
+
this.buildUrl();
|
|
190
|
+
}}
|
|
191
|
+
@selectedTheme=${(eventArg: CustomEvent<plugins.interfaces.TWccTheme>) => {
|
|
192
|
+
this.selectedTheme = eventArg.detail;
|
|
193
|
+
this.buildUrl();
|
|
194
|
+
}}
|
|
195
|
+
@editorStateChanged=${(eventArg: CustomEvent<{ isOpen: boolean }>) => {
|
|
196
|
+
this.advancedEditorOpen = eventArg.detail.isOpen;
|
|
197
|
+
}}
|
|
198
|
+
></wcc-properties>
|
|
199
|
+
<wcc-preview-frame
|
|
200
|
+
role="main"
|
|
201
|
+
aria-label="Preview"
|
|
202
|
+
.viewport=${this.selectedViewport}
|
|
203
|
+
.previewSrc=${this.previewSrc}
|
|
204
|
+
.isNative=${this.isNative}
|
|
205
|
+
.sidebarWidth=${this.sidebarWidth}
|
|
206
|
+
.advancedEditorOpen=${this.advancedEditorOpen}
|
|
207
|
+
.isResizing=${this.isResizing}
|
|
208
|
+
.bundleFailures=${this.bundleFailures}
|
|
209
|
+
></wcc-preview-frame>
|
|
210
|
+
`;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private leaveNative() {
|
|
214
|
+
if (this.isNative) {
|
|
215
|
+
this.selectedViewport = 'desktop';
|
|
216
|
+
this.buildUrl();
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
private handleDocumentKeydown = (event: KeyboardEvent) => {
|
|
221
|
+
if (event.key === 'Escape') {
|
|
222
|
+
this.leaveNative();
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
public async connectedCallback() {
|
|
227
|
+
const connected = super.connectedCallback();
|
|
228
|
+
document.addEventListener('keydown', this.handleDocumentKeydown);
|
|
229
|
+
this.connection.start();
|
|
230
|
+
this.startBundleStatus();
|
|
231
|
+
if (this.hasUpdated) {
|
|
232
|
+
await this.setupSidebarScrollListener();
|
|
233
|
+
}
|
|
234
|
+
await this.connectRoutes();
|
|
235
|
+
return connected;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
public async disconnectedCallback() {
|
|
239
|
+
const disconnected = super.disconnectedCallback();
|
|
240
|
+
document.removeEventListener('keydown', this.handleDocumentKeydown);
|
|
241
|
+
this.connection.dispose();
|
|
242
|
+
this.bundleStatusClient?.dispose();
|
|
243
|
+
this.bundleStatusClient = null;
|
|
244
|
+
this.bridge = null;
|
|
245
|
+
for (const dispose of this.routeDisposers) {
|
|
246
|
+
dispose();
|
|
247
|
+
}
|
|
248
|
+
this.routeDisposers = [];
|
|
249
|
+
this.releaseScrollListeners();
|
|
250
|
+
clearTimeout(this.scrollUpdateTimeout);
|
|
251
|
+
return disconnected;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
private startBundleStatus() {
|
|
255
|
+
if (!this.apiPath || this.bundleStatusClient) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
this.bundleStatusClient = new WccBundleStatusClient(this.apiPath, (snapshotArg) => this.applyBundleStatus(snapshotArg));
|
|
259
|
+
this.bundleStatusClient.start();
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Apply the dev server's bundle status: a bundle whose latest completed run failed is shown
|
|
264
|
+
* over the preview until a run of it finishes, which also reloads the preview.
|
|
265
|
+
*/
|
|
266
|
+
public applyBundleStatus(snapshotArg: plugins.interfaces.IWccBundleStatusSnapshot) {
|
|
267
|
+
this.bundleFailures = snapshotArg.bundles.filter((bundleArg) => bundleArg.errorMessage !== undefined);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
public async firstUpdated() {
|
|
271
|
+
await this.setupSidebarScrollListener();
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
private async connectRoutes() {
|
|
275
|
+
const domtools = await this.domtoolsPromise;
|
|
276
|
+
if (!this.isConnected || this.routeDisposers.length > 0) {
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
this.domtools = domtools;
|
|
280
|
+
const handleRoute = async (routeInfo: { params: Record<string, string>; queryParams: Record<string, string> }) => {
|
|
281
|
+
const { sectionName, itemName, demoIndex, viewport, theme } = routeInfo.params;
|
|
282
|
+
this.applyRoute({ sectionName, itemName, demoIndex, viewport, theme }, routeInfo.queryParams);
|
|
283
|
+
};
|
|
284
|
+
this.routeDisposers = [
|
|
285
|
+
domtools.router.on('/wcctools-route/:sectionName/:itemName/:demoIndex/:viewport/:theme', handleRoute),
|
|
286
|
+
// Route without demo index (kept for existing links)
|
|
287
|
+
domtools.router.on('/wcctools-route/:sectionName/:itemName/:viewport/:theme', handleRoute),
|
|
288
|
+
];
|
|
289
|
+
await domtools.router._handleRouteState();
|
|
290
|
+
if (this.isConnected && !this.previewSrc) {
|
|
291
|
+
// Loaded once: later selections render through the bridge without reloading the preview
|
|
292
|
+
this.previewSrc = `${this.previewPath}${plugins.shared.formatPreviewQuery({ selection: this.selection, theme: this.selectedTheme })}`;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Apply a `/wcctools-route/...` location to the shell state.
|
|
298
|
+
*/
|
|
299
|
+
public applyRoute(routeArg: IWccShellRouteParams, queryParamsArg: Record<string, string> = {}) {
|
|
300
|
+
this.selection = {
|
|
301
|
+
sectionName: decodeURIComponent(routeArg.sectionName),
|
|
302
|
+
itemName: routeArg.itemName,
|
|
303
|
+
demoIndex: parseInt(routeArg.demoIndex ?? '0', 10) || 0,
|
|
304
|
+
};
|
|
305
|
+
this.selectedViewport = viewports.includes(routeArg.viewport as TWccViewport)
|
|
306
|
+
? routeArg.viewport as TWccViewport
|
|
307
|
+
: 'desktop';
|
|
308
|
+
this.selectedTheme = routeArg.theme === 'bright' ? 'bright' : 'dark';
|
|
309
|
+
|
|
310
|
+
// Restore state from query parameters
|
|
311
|
+
this.searchQuery = queryParamsArg.search || '';
|
|
312
|
+
if (queryParamsArg.frameScrollY) {
|
|
313
|
+
this.frameScrollY = parseInt(queryParamsArg.frameScrollY, 10);
|
|
314
|
+
}
|
|
315
|
+
if (queryParamsArg.sidebarScrollY) {
|
|
316
|
+
this.sidebarScrollY = parseInt(queryParamsArg.sidebarScrollY, 10);
|
|
317
|
+
}
|
|
318
|
+
if (queryParamsArg.pinned) {
|
|
319
|
+
const newPinned = new Set(queryParamsArg.pinned.split(',').filter(Boolean));
|
|
320
|
+
// Only update if actually different to avoid update loops
|
|
321
|
+
if (this.pinnedItems.size !== newPinned.size ||
|
|
322
|
+
![...newPinned].every(k => this.pinnedItems.has(k))) {
|
|
323
|
+
this.pinnedItems = newPinned;
|
|
324
|
+
}
|
|
325
|
+
} else if (this.pinnedItems.size > 0) {
|
|
326
|
+
this.pinnedItems = new Set();
|
|
327
|
+
}
|
|
328
|
+
this.sidebarWidth = queryParamsArg.sidebarWidth
|
|
329
|
+
? parseInt(queryParamsArg.sidebarWidth, 10)
|
|
330
|
+
: defaultSidebarWidth;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
public async updated(changedPropertiesArg: Map<string, unknown>) {
|
|
334
|
+
super.updated(changedPropertiesArg);
|
|
335
|
+
if (changedPropertiesArg.has('selection') && this.selection) {
|
|
336
|
+
document.title = this.selection.itemName;
|
|
337
|
+
}
|
|
338
|
+
if (changedPropertiesArg.has('selectedTheme')) {
|
|
339
|
+
await this.applyTheme();
|
|
340
|
+
}
|
|
341
|
+
if (changedPropertiesArg.has('selection')) {
|
|
342
|
+
await this.renderSelectionInPreview();
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/** The shell's own elements and the preview follow the selected theme. */
|
|
347
|
+
private async applyTheme() {
|
|
348
|
+
const theme = this.selectedTheme;
|
|
349
|
+
const domtools = await plugins.deesDomtools.elementBasic.setup();
|
|
350
|
+
theme === 'bright' ? domtools.themeManager.goBright() : domtools.themeManager.goDark();
|
|
351
|
+
if (this.bridge && this.bridge.getTheme() !== theme) {
|
|
352
|
+
await this.bridge.setTheme(theme);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Render the selection in the preview. Only a change of the selected entry or demo
|
|
358
|
+
* re-renders; theme, viewport, search, pins and sidebar width leave the rendered demo
|
|
359
|
+
* (and its internal state) untouched.
|
|
360
|
+
*/
|
|
361
|
+
private async renderSelectionInPreview() {
|
|
362
|
+
const bridge = this.bridge;
|
|
363
|
+
const key = selectionKey(this.selection);
|
|
364
|
+
if (!bridge || !this.selection || key === this.previewSelectionKey) {
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
this.previewSelectionKey = key;
|
|
368
|
+
await bridge.render(this.selection);
|
|
369
|
+
if (this.bridge === bridge) {
|
|
370
|
+
this.renderCount++;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/** A preview document is ready: read its catalog and bring it to the shell's state. */
|
|
375
|
+
private async handlePreviewReady(bridgeArg: plugins.interfaces.IWccPreviewBridge) {
|
|
376
|
+
this.bridge = bridgeArg;
|
|
377
|
+
this.manifest = bridgeArg.getManifest();
|
|
378
|
+
this.captureTarget = this.shadowRoot?.querySelector<WccPreviewFrame>('wcc-preview-frame')?.iframe ?? null;
|
|
379
|
+
if (bridgeArg.getTheme() !== this.selectedTheme) {
|
|
380
|
+
await bridgeArg.setTheme(this.selectedTheme);
|
|
381
|
+
}
|
|
382
|
+
// A reloaded preview restores what it showed from its own URL
|
|
383
|
+
this.previewSelectionKey = selectionKey(bridgeArg.getSelection());
|
|
384
|
+
await this.renderSelectionInPreview();
|
|
385
|
+
if (this.bridge !== bridgeArg) {
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
this.renderCount++;
|
|
389
|
+
await this.restoreScrollPositions();
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/** Scroll the sidebar and the preview to the positions of the loaded URL, once. */
|
|
393
|
+
private async restoreScrollPositions() {
|
|
394
|
+
if (this.scrollPositionsRestored) {
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
this.scrollPositionsRestored = true;
|
|
398
|
+
const sidebar = this.shadowRoot?.querySelector<WccSidebar>('wcc-sidebar');
|
|
399
|
+
await this.updateComplete;
|
|
400
|
+
await sidebar?.updateComplete;
|
|
401
|
+
if (sidebar?.scrollableContainer && this.sidebarScrollY > 0) {
|
|
402
|
+
sidebar.scrollableContainer.scrollTop = this.sidebarScrollY;
|
|
403
|
+
}
|
|
404
|
+
if (this.frameScrollY > 0) {
|
|
405
|
+
this.connection.scrollPreviewTo(this.frameScrollY);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
private getRouteUrl(): string | null {
|
|
410
|
+
if (!this.selection) {
|
|
411
|
+
return null;
|
|
412
|
+
}
|
|
413
|
+
const sectionName = encodeURIComponent(this.selection.sectionName);
|
|
414
|
+
const baseUrl = `/wcctools-route/${sectionName}/${this.selection.itemName}/${this.selection.demoIndex}/${this.selectedViewport}/${this.selectedTheme}`;
|
|
415
|
+
const queryParams = new URLSearchParams();
|
|
416
|
+
|
|
417
|
+
if (this.searchQuery) {
|
|
418
|
+
queryParams.set('search', this.searchQuery);
|
|
419
|
+
}
|
|
420
|
+
if (this.frameScrollY > 0) {
|
|
421
|
+
queryParams.set('frameScrollY', this.frameScrollY.toString());
|
|
422
|
+
}
|
|
423
|
+
if (this.sidebarScrollY > 0) {
|
|
424
|
+
queryParams.set('sidebarScrollY', this.sidebarScrollY.toString());
|
|
425
|
+
}
|
|
426
|
+
if (this.pinnedItems.size > 0) {
|
|
427
|
+
queryParams.set('pinned', Array.from(this.pinnedItems).join(','));
|
|
428
|
+
}
|
|
429
|
+
if (this.sidebarWidth !== defaultSidebarWidth) {
|
|
430
|
+
queryParams.set('sidebarWidth', this.sidebarWidth.toString());
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const queryString = queryParams.toString();
|
|
434
|
+
return queryString ? `${baseUrl}?${queryString}` : baseUrl;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/** Push the current state as a new history entry; without a selection there is no route to push. */
|
|
438
|
+
public buildUrl() {
|
|
439
|
+
const url = this.getRouteUrl();
|
|
440
|
+
if (url && this.domtools) {
|
|
441
|
+
this.domtools.router.pushUrl(url);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
private updateUrlWithScrollState() {
|
|
446
|
+
const url = this.getRouteUrl();
|
|
447
|
+
if (url) {
|
|
448
|
+
// Use replaceState to update URL without navigation
|
|
449
|
+
window.history.replaceState(null, '', url);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
private handleSidebarScroll = (event: Event) => {
|
|
454
|
+
this.sidebarScrollY = (event.currentTarget as HTMLElement).scrollTop;
|
|
455
|
+
this.debouncedScrollUpdate();
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
private async setupSidebarScrollListener() {
|
|
459
|
+
const sidebar = this.shadowRoot?.querySelector<WccSidebar>('wcc-sidebar');
|
|
460
|
+
await sidebar?.updateComplete;
|
|
461
|
+
// Prevent duplicate listeners and listeners on a removed shell
|
|
462
|
+
if (!this.isConnected || this.scrollDisposers.length > 0) {
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
const scrollContainer = sidebar?.scrollableContainer;
|
|
466
|
+
if (scrollContainer) {
|
|
467
|
+
scrollContainer.addEventListener('scroll', this.handleSidebarScroll);
|
|
468
|
+
this.scrollDisposers.push(() => scrollContainer.removeEventListener('scroll', this.handleSidebarScroll));
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
private releaseScrollListeners() {
|
|
473
|
+
for (const dispose of this.scrollDisposers) {
|
|
474
|
+
dispose();
|
|
475
|
+
}
|
|
476
|
+
this.scrollDisposers = [];
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
private debouncedScrollUpdate() {
|
|
480
|
+
clearTimeout(this.scrollUpdateTimeout);
|
|
481
|
+
this.scrollUpdateTimeout = setTimeout(() => {
|
|
482
|
+
this.updateUrlWithScrollState();
|
|
483
|
+
}, 300);
|
|
484
|
+
}
|
|
485
|
+
}
|