brep-io-kernel 1.0.26 → 1.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/README.md +8 -0
- package/dist-kernel/brep-kernel.js +20819 -19752
- package/package.json +6 -5
- package/src/BREP/SolidMethods/fillet copy.js +1532 -0
- package/src/UI/fileManagerWidget.js +1 -10
- package/src/UI/startupTour.js +659 -0
- package/src/UI/toolbarButtons/newButton.js +21 -0
- package/src/UI/toolbarButtons/registerDefaultButtons.js +2 -0
- package/src/UI/toolbarButtons/saveButton.js +1 -1
- package/src/UI/viewer.js +4 -0
- package/src/fs.proxy.js +119 -25
- package/src/generated/licenseBundle.js +2 -0
- package/src/idbStorage.js +275 -105
- package/src/index.js +7 -0
- package/src/licenseInfo.js +71 -0
- package/src/services/componentLibrary.js +1 -1
|
@@ -169,7 +169,7 @@ export class FileManagerWidget {
|
|
|
169
169
|
}
|
|
170
170
|
|
|
171
171
|
_buildUI() {
|
|
172
|
-
// Header: name input + Save
|
|
172
|
+
// Header: name input + Save
|
|
173
173
|
const header = document.createElement('div');
|
|
174
174
|
header.className = 'fm-row header';
|
|
175
175
|
|
|
@@ -191,15 +191,6 @@ export class FileManagerWidget {
|
|
|
191
191
|
saveBtn.className = 'fm-btn';
|
|
192
192
|
saveBtn.addEventListener('click', () => this.saveCurrent());
|
|
193
193
|
header.appendChild(saveBtn);
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
const newBtn = document.createElement('button');
|
|
198
|
-
newBtn.textContent = 'New';
|
|
199
|
-
newBtn.className = 'fm-btn';
|
|
200
|
-
newBtn.addEventListener('click', () => this.newModel());
|
|
201
|
-
header.appendChild(newBtn);
|
|
202
|
-
|
|
203
194
|
this.uiElement.appendChild(header);
|
|
204
195
|
|
|
205
196
|
// List container
|
|
@@ -0,0 +1,659 @@
|
|
|
1
|
+
import { localStorage as LS } from '../idbStorage.js';
|
|
2
|
+
|
|
3
|
+
const TOUR_STORAGE_KEY = '__BREP_STARTUP_TOUR_DONE__';
|
|
4
|
+
const TOUR_STORAGE_VALUE = '1';
|
|
5
|
+
|
|
6
|
+
const DEFAULT_PADDING = 8;
|
|
7
|
+
const CARD_MARGIN = 14;
|
|
8
|
+
const MIN_CARD_GAP = 12;
|
|
9
|
+
|
|
10
|
+
function ensureTourStyles() {
|
|
11
|
+
if (typeof document === 'undefined') return;
|
|
12
|
+
if (document.getElementById('startup-tour-styles')) return;
|
|
13
|
+
const style = document.createElement('style');
|
|
14
|
+
style.id = 'startup-tour-styles';
|
|
15
|
+
style.textContent = `
|
|
16
|
+
.brep-tour-overlay {
|
|
17
|
+
position: fixed;
|
|
18
|
+
inset: 0;
|
|
19
|
+
z-index: 20000;
|
|
20
|
+
pointer-events: none;
|
|
21
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
|
22
|
+
}
|
|
23
|
+
.brep-tour-highlight {
|
|
24
|
+
position: fixed;
|
|
25
|
+
border: 2px solid #6ea8fe;
|
|
26
|
+
border-radius: 10px;
|
|
27
|
+
box-shadow: 0 0 0 9999px rgba(6, 10, 18, 0.7), 0 0 18px rgba(110, 168, 254, 0.4);
|
|
28
|
+
pointer-events: none;
|
|
29
|
+
transition: opacity 0.18s ease, transform 0.18s ease, width 0.18s ease, height 0.18s ease;
|
|
30
|
+
opacity: 0;
|
|
31
|
+
}
|
|
32
|
+
.brep-tour-card {
|
|
33
|
+
position: fixed;
|
|
34
|
+
width: min(360px, calc(100vw - 32px));
|
|
35
|
+
background: #0b0e14;
|
|
36
|
+
color: #e5e7eb;
|
|
37
|
+
border: 1px solid #1f2937;
|
|
38
|
+
border-radius: 12px;
|
|
39
|
+
padding: 12px;
|
|
40
|
+
box-shadow: 0 18px 50px rgba(0,0,0,0.5);
|
|
41
|
+
font-size: 12px;
|
|
42
|
+
line-height: 1.4;
|
|
43
|
+
pointer-events: auto;
|
|
44
|
+
}
|
|
45
|
+
.brep-tour-card.is-center {
|
|
46
|
+
top: 50%;
|
|
47
|
+
left: 50%;
|
|
48
|
+
transform: translate(-50%, -50%);
|
|
49
|
+
}
|
|
50
|
+
.brep-tour-title {
|
|
51
|
+
font-size: 13px;
|
|
52
|
+
font-weight: 700;
|
|
53
|
+
margin-bottom: 6px;
|
|
54
|
+
}
|
|
55
|
+
.brep-tour-body {
|
|
56
|
+
color: #c7cdd7;
|
|
57
|
+
margin-bottom: 10px;
|
|
58
|
+
}
|
|
59
|
+
.brep-tour-progress {
|
|
60
|
+
font-size: 11px;
|
|
61
|
+
color: #9aa4b2;
|
|
62
|
+
margin-bottom: 10px;
|
|
63
|
+
}
|
|
64
|
+
.brep-tour-skipnext {
|
|
65
|
+
display: flex;
|
|
66
|
+
align-items: center;
|
|
67
|
+
gap: 6px;
|
|
68
|
+
font-size: 12px;
|
|
69
|
+
color: #c7cdd7;
|
|
70
|
+
margin-bottom: 10px;
|
|
71
|
+
user-select: none;
|
|
72
|
+
}
|
|
73
|
+
.brep-tour-actions {
|
|
74
|
+
display: flex;
|
|
75
|
+
align-items: center;
|
|
76
|
+
justify-content: space-between;
|
|
77
|
+
gap: 8px;
|
|
78
|
+
}
|
|
79
|
+
.brep-tour-action-group {
|
|
80
|
+
display: flex;
|
|
81
|
+
gap: 8px;
|
|
82
|
+
align-items: center;
|
|
83
|
+
}
|
|
84
|
+
.brep-tour-btn {
|
|
85
|
+
border: 1px solid #364053;
|
|
86
|
+
background: rgba(255,255,255,0.04);
|
|
87
|
+
color: #e5e7eb;
|
|
88
|
+
border-radius: 8px;
|
|
89
|
+
padding: 6px 10px;
|
|
90
|
+
cursor: pointer;
|
|
91
|
+
font-size: 12px;
|
|
92
|
+
font-weight: 700;
|
|
93
|
+
line-height: 1;
|
|
94
|
+
transition: border-color .15s ease, background-color .15s ease, transform .05s ease;
|
|
95
|
+
}
|
|
96
|
+
.brep-tour-btn:hover { border-color: #6ea8fe; background: rgba(110,168,254,0.12); }
|
|
97
|
+
.brep-tour-btn:active { transform: translateY(1px); }
|
|
98
|
+
.brep-tour-btn.primary {
|
|
99
|
+
border-color: #6ea8fe;
|
|
100
|
+
background: linear-gradient(180deg, rgba(110,168,254,.35), rgba(110,168,254,.15));
|
|
101
|
+
color: #e9f0ff;
|
|
102
|
+
box-shadow: 0 0 0 1px rgba(110,168,254,.25) inset;
|
|
103
|
+
}
|
|
104
|
+
.brep-tour-btn[disabled] {
|
|
105
|
+
opacity: 0.45;
|
|
106
|
+
cursor: default;
|
|
107
|
+
}
|
|
108
|
+
.brep-tour-skip {
|
|
109
|
+
border: none;
|
|
110
|
+
background: transparent;
|
|
111
|
+
color: #9aa4b2;
|
|
112
|
+
text-decoration: underline;
|
|
113
|
+
cursor: pointer;
|
|
114
|
+
font-size: 11px;
|
|
115
|
+
padding: 0;
|
|
116
|
+
}
|
|
117
|
+
.brep-tour-close {
|
|
118
|
+
position: absolute;
|
|
119
|
+
top: 8px;
|
|
120
|
+
right: 8px;
|
|
121
|
+
width: 22px;
|
|
122
|
+
height: 22px;
|
|
123
|
+
border-radius: 6px;
|
|
124
|
+
border: 1px solid #364053;
|
|
125
|
+
background: rgba(255,255,255,0.04);
|
|
126
|
+
color: #e5e7eb;
|
|
127
|
+
cursor: pointer;
|
|
128
|
+
font-weight: 700;
|
|
129
|
+
line-height: 1;
|
|
130
|
+
}
|
|
131
|
+
.brep-tour-close:hover { border-color: #6ea8fe; background: rgba(110,168,254,0.12); }
|
|
132
|
+
`;
|
|
133
|
+
document.head.appendChild(style);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function clamp(value, min, max) {
|
|
137
|
+
return Math.min(Math.max(value, min), max);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function getViewportRect() {
|
|
141
|
+
return {
|
|
142
|
+
left: 0,
|
|
143
|
+
top: 0,
|
|
144
|
+
right: window.innerWidth,
|
|
145
|
+
bottom: window.innerHeight,
|
|
146
|
+
width: window.innerWidth,
|
|
147
|
+
height: window.innerHeight,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function isDialogOpen() {
|
|
152
|
+
try {
|
|
153
|
+
if (typeof window === 'undefined') return false;
|
|
154
|
+
if (typeof window.isDialogOpen === 'function') return window.isDialogOpen();
|
|
155
|
+
return !!window.__BREPDialogOpen;
|
|
156
|
+
} catch {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function waitForDialogsToClose(timeoutMs = 12000) {
|
|
162
|
+
const start = Date.now();
|
|
163
|
+
while (isDialogOpen()) {
|
|
164
|
+
if (Date.now() - start > timeoutMs) break;
|
|
165
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function getDefaultSteps() {
|
|
170
|
+
return [
|
|
171
|
+
{
|
|
172
|
+
id: 'welcome',
|
|
173
|
+
title: 'Welcome to BREP CAD',
|
|
174
|
+
body: 'This quick tour highlights the main areas. Use Next/Back or the Left/Right arrow keys. Press Esc to exit.',
|
|
175
|
+
target: null,
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
id: 'toolbar',
|
|
179
|
+
title: 'Main toolbar',
|
|
180
|
+
body: 'Import, export, save, and view tools live here. Buttons update based on selection.',
|
|
181
|
+
target: () => document.getElementById('main-toolbar'),
|
|
182
|
+
padding: 6,
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
id: 'sidebar',
|
|
186
|
+
title: 'Sidebar panels',
|
|
187
|
+
body: 'These panels hold File Manager, History, Scene Manager, and Display Settings. Click a header to expand or collapse.',
|
|
188
|
+
target: () => document.getElementById('sidebar'),
|
|
189
|
+
padding: 6,
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
id: 'file-manager',
|
|
193
|
+
title: 'File Manager',
|
|
194
|
+
body: 'Save and load models from browser storage. Use New to clear the scene.',
|
|
195
|
+
onEnter: (viewer) => viewer?.accordion?.expandSection?.('File Manager'),
|
|
196
|
+
target: () =>
|
|
197
|
+
document.querySelector('#accordion-content-File\\ Manager .fm-row.header') ||
|
|
198
|
+
document.querySelector('#accordion-content-File\\ Manager') ||
|
|
199
|
+
document.querySelector('[name="accordion-title-File Manager"]'),
|
|
200
|
+
padding: 6,
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
id: 'history',
|
|
204
|
+
title: 'History',
|
|
205
|
+
body: 'Your feature history appears here. Use it to reorder, edit, or roll back steps.',
|
|
206
|
+
onEnter: (viewer) => viewer?.accordion?.expandSection?.('History'),
|
|
207
|
+
target: () =>
|
|
208
|
+
document.querySelector('#accordion-content-History') ||
|
|
209
|
+
document.querySelector('[name="accordion-title-History"]'),
|
|
210
|
+
padding: 6,
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
id: 'viewport',
|
|
214
|
+
title: '3D viewport',
|
|
215
|
+
body: 'Orbit with left-drag, pan with right-drag, zoom with the wheel. Click geometry to select.',
|
|
216
|
+
target: () => document.getElementById('viewport'),
|
|
217
|
+
padding: 2,
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
id: 'done',
|
|
221
|
+
title: 'All set',
|
|
222
|
+
body: 'You are ready to model. Enjoy building.',
|
|
223
|
+
target: null,
|
|
224
|
+
},
|
|
225
|
+
];
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export class StartupTour {
|
|
229
|
+
constructor(viewer, { steps = null } = {}) {
|
|
230
|
+
this.viewer = viewer || null;
|
|
231
|
+
this.steps = Array.isArray(steps) && steps.length ? steps : getDefaultSteps();
|
|
232
|
+
this.index = 0;
|
|
233
|
+
this.active = false;
|
|
234
|
+
this._overlay = null;
|
|
235
|
+
this._highlight = null;
|
|
236
|
+
this._card = null;
|
|
237
|
+
this._titleEl = null;
|
|
238
|
+
this._bodyEl = null;
|
|
239
|
+
this._progressEl = null;
|
|
240
|
+
this._skipNextRow = null;
|
|
241
|
+
this._skipNextCheckbox = null;
|
|
242
|
+
this._nextBtn = null;
|
|
243
|
+
this._backBtn = null;
|
|
244
|
+
this._skipBtn = null;
|
|
245
|
+
this._closeBtn = null;
|
|
246
|
+
this._onKeyDown = null;
|
|
247
|
+
this._onReposition = null;
|
|
248
|
+
this._onSkipNextChange = null;
|
|
249
|
+
this._positionRaf = null;
|
|
250
|
+
this._currentTarget = null;
|
|
251
|
+
this._prevSidebarPinned = null;
|
|
252
|
+
this._prevSidebarSuspended = null;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
static isDone() {
|
|
256
|
+
try {
|
|
257
|
+
return LS.getItem(TOUR_STORAGE_KEY) === TOUR_STORAGE_VALUE;
|
|
258
|
+
} catch {
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
static markDone() {
|
|
264
|
+
try { LS.setItem(TOUR_STORAGE_KEY, TOUR_STORAGE_VALUE); } catch { }
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async maybeStart() {
|
|
268
|
+
try { await LS.ready(); } catch { }
|
|
269
|
+
if (StartupTour.isDone()) return false;
|
|
270
|
+
await waitForDialogsToClose();
|
|
271
|
+
this.start();
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
start() {
|
|
276
|
+
if (this.active) return;
|
|
277
|
+
if (typeof document === 'undefined') return;
|
|
278
|
+
if (document.getElementById('startup-tour-overlay')) return;
|
|
279
|
+
|
|
280
|
+
ensureTourStyles();
|
|
281
|
+
this.active = true;
|
|
282
|
+
this.index = 0;
|
|
283
|
+
|
|
284
|
+
this._suspendSidebar();
|
|
285
|
+
this._buildUI();
|
|
286
|
+
this._attachEvents();
|
|
287
|
+
this._showStep(this.index);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
_suspendSidebar() {
|
|
291
|
+
const v = this.viewer;
|
|
292
|
+
if (!v) return;
|
|
293
|
+
try {
|
|
294
|
+
if (typeof v._sidebarPinned === 'boolean') this._prevSidebarPinned = v._sidebarPinned;
|
|
295
|
+
if (typeof v._sidebarAutoHideSuspended === 'boolean') this._prevSidebarSuspended = v._sidebarAutoHideSuspended;
|
|
296
|
+
if (typeof v._setSidebarPinned === 'function') v._setSidebarPinned(true);
|
|
297
|
+
if (typeof v._setSidebarAutoHideSuspended === 'function') v._setSidebarAutoHideSuspended(true);
|
|
298
|
+
} catch { }
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
_restoreSidebar() {
|
|
302
|
+
const v = this.viewer;
|
|
303
|
+
if (!v) return;
|
|
304
|
+
try {
|
|
305
|
+
if (typeof v._setSidebarPinned === 'function' && this._prevSidebarPinned !== null) {
|
|
306
|
+
v._setSidebarPinned(!!this._prevSidebarPinned);
|
|
307
|
+
}
|
|
308
|
+
if (typeof v._setSidebarAutoHideSuspended === 'function' && this._prevSidebarSuspended !== null) {
|
|
309
|
+
v._setSidebarAutoHideSuspended(!!this._prevSidebarSuspended);
|
|
310
|
+
}
|
|
311
|
+
} catch { }
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
_buildUI() {
|
|
315
|
+
const overlay = document.createElement('div');
|
|
316
|
+
overlay.id = 'startup-tour-overlay';
|
|
317
|
+
overlay.className = 'brep-tour-overlay';
|
|
318
|
+
|
|
319
|
+
const highlight = document.createElement('div');
|
|
320
|
+
highlight.className = 'brep-tour-highlight';
|
|
321
|
+
|
|
322
|
+
const card = document.createElement('div');
|
|
323
|
+
card.className = 'brep-tour-card';
|
|
324
|
+
|
|
325
|
+
const closeBtn = document.createElement('button');
|
|
326
|
+
closeBtn.className = 'brep-tour-close';
|
|
327
|
+
closeBtn.type = 'button';
|
|
328
|
+
closeBtn.textContent = 'x';
|
|
329
|
+
|
|
330
|
+
const title = document.createElement('div');
|
|
331
|
+
title.className = 'brep-tour-title';
|
|
332
|
+
|
|
333
|
+
const body = document.createElement('div');
|
|
334
|
+
body.className = 'brep-tour-body';
|
|
335
|
+
|
|
336
|
+
const progress = document.createElement('div');
|
|
337
|
+
progress.className = 'brep-tour-progress';
|
|
338
|
+
|
|
339
|
+
const skipNextRow = document.createElement('label');
|
|
340
|
+
skipNextRow.className = 'brep-tour-skipnext';
|
|
341
|
+
const skipNextCheckbox = document.createElement('input');
|
|
342
|
+
skipNextCheckbox.type = 'checkbox';
|
|
343
|
+
skipNextCheckbox.checked = false;
|
|
344
|
+
skipNextCheckbox.style.marginRight = '6px';
|
|
345
|
+
const skipNextText = document.createElement('span');
|
|
346
|
+
skipNextText.textContent = 'Skip tour next time';
|
|
347
|
+
skipNextRow.appendChild(skipNextCheckbox);
|
|
348
|
+
skipNextRow.appendChild(skipNextText);
|
|
349
|
+
|
|
350
|
+
const actions = document.createElement('div');
|
|
351
|
+
actions.className = 'brep-tour-actions';
|
|
352
|
+
|
|
353
|
+
const leftGroup = document.createElement('div');
|
|
354
|
+
leftGroup.className = 'brep-tour-action-group';
|
|
355
|
+
|
|
356
|
+
const backBtn = document.createElement('button');
|
|
357
|
+
backBtn.type = 'button';
|
|
358
|
+
backBtn.className = 'brep-tour-btn';
|
|
359
|
+
backBtn.textContent = 'Back';
|
|
360
|
+
|
|
361
|
+
const nextBtn = document.createElement('button');
|
|
362
|
+
nextBtn.type = 'button';
|
|
363
|
+
nextBtn.className = 'brep-tour-btn primary';
|
|
364
|
+
nextBtn.textContent = 'Next';
|
|
365
|
+
|
|
366
|
+
leftGroup.appendChild(backBtn);
|
|
367
|
+
leftGroup.appendChild(nextBtn);
|
|
368
|
+
|
|
369
|
+
const skipBtn = document.createElement('button');
|
|
370
|
+
skipBtn.type = 'button';
|
|
371
|
+
skipBtn.className = 'brep-tour-skip';
|
|
372
|
+
skipBtn.textContent = 'Skip tour';
|
|
373
|
+
|
|
374
|
+
actions.appendChild(leftGroup);
|
|
375
|
+
actions.appendChild(skipBtn);
|
|
376
|
+
|
|
377
|
+
card.appendChild(closeBtn);
|
|
378
|
+
card.appendChild(title);
|
|
379
|
+
card.appendChild(body);
|
|
380
|
+
card.appendChild(progress);
|
|
381
|
+
card.appendChild(skipNextRow);
|
|
382
|
+
card.appendChild(actions);
|
|
383
|
+
|
|
384
|
+
overlay.appendChild(highlight);
|
|
385
|
+
overlay.appendChild(card);
|
|
386
|
+
document.body.appendChild(overlay);
|
|
387
|
+
|
|
388
|
+
this._overlay = overlay;
|
|
389
|
+
this._highlight = highlight;
|
|
390
|
+
this._card = card;
|
|
391
|
+
this._titleEl = title;
|
|
392
|
+
this._bodyEl = body;
|
|
393
|
+
this._progressEl = progress;
|
|
394
|
+
this._skipNextRow = skipNextRow;
|
|
395
|
+
this._skipNextCheckbox = skipNextCheckbox;
|
|
396
|
+
this._nextBtn = nextBtn;
|
|
397
|
+
this._backBtn = backBtn;
|
|
398
|
+
this._skipBtn = skipBtn;
|
|
399
|
+
this._closeBtn = closeBtn;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
_attachEvents() {
|
|
403
|
+
if (!this._overlay) return;
|
|
404
|
+
|
|
405
|
+
this._onKeyDown = (ev) => {
|
|
406
|
+
if (!this.active) return;
|
|
407
|
+
const key = ev.key;
|
|
408
|
+
if (key === 'Escape') {
|
|
409
|
+
ev.preventDefault();
|
|
410
|
+
ev.stopPropagation();
|
|
411
|
+
this.exit();
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
if (key === 'ArrowRight' || key === 'Enter') {
|
|
415
|
+
ev.preventDefault();
|
|
416
|
+
ev.stopPropagation();
|
|
417
|
+
this.next();
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
if (key === 'ArrowLeft') {
|
|
421
|
+
ev.preventDefault();
|
|
422
|
+
ev.stopPropagation();
|
|
423
|
+
this.prev();
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
this._onReposition = () => this._schedulePosition();
|
|
428
|
+
this._onSkipNextChange = () => {
|
|
429
|
+
if (!this._skipNextCheckbox) return;
|
|
430
|
+
if (this._skipNextCheckbox.checked) StartupTour.markDone();
|
|
431
|
+
else resetStartupTourFlag();
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
window.addEventListener('keydown', this._onKeyDown, true);
|
|
435
|
+
window.addEventListener('resize', this._onReposition);
|
|
436
|
+
window.addEventListener('scroll', this._onReposition, true);
|
|
437
|
+
|
|
438
|
+
this._nextBtn?.addEventListener('click', () => this.next());
|
|
439
|
+
this._backBtn?.addEventListener('click', () => this.prev());
|
|
440
|
+
this._skipBtn?.addEventListener('click', () => this.exit());
|
|
441
|
+
this._closeBtn?.addEventListener('click', () => this.exit());
|
|
442
|
+
this._skipNextCheckbox?.addEventListener('change', this._onSkipNextChange);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
_detachEvents() {
|
|
446
|
+
if (this._onKeyDown) window.removeEventListener('keydown', this._onKeyDown, true);
|
|
447
|
+
if (this._onReposition) {
|
|
448
|
+
window.removeEventListener('resize', this._onReposition);
|
|
449
|
+
window.removeEventListener('scroll', this._onReposition, true);
|
|
450
|
+
}
|
|
451
|
+
if (this._skipNextCheckbox && this._onSkipNextChange) {
|
|
452
|
+
this._skipNextCheckbox.removeEventListener('change', this._onSkipNextChange);
|
|
453
|
+
}
|
|
454
|
+
this._onKeyDown = null;
|
|
455
|
+
this._onReposition = null;
|
|
456
|
+
this._onSkipNextChange = null;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
_resolveTarget(step) {
|
|
460
|
+
if (!step || !step.target) return null;
|
|
461
|
+
try {
|
|
462
|
+
if (typeof step.target === 'function') return step.target(this.viewer) || null;
|
|
463
|
+
if (typeof step.target === 'string') return document.querySelector(step.target);
|
|
464
|
+
if (step.target instanceof HTMLElement) return step.target;
|
|
465
|
+
} catch { }
|
|
466
|
+
return null;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
_runStepEnter(step) {
|
|
470
|
+
if (!step || typeof step.onEnter !== 'function') return null;
|
|
471
|
+
try { return step.onEnter(this.viewer, step); } catch { return null; }
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
_scrollTargetIntoView(target) {
|
|
475
|
+
if (!target || !target.scrollIntoView) return;
|
|
476
|
+
try {
|
|
477
|
+
target.scrollIntoView({ block: 'center', inline: 'center', behavior: 'smooth' });
|
|
478
|
+
} catch { }
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
_showStep(index) {
|
|
482
|
+
if (!this.active) return;
|
|
483
|
+
const step = this.steps[index];
|
|
484
|
+
if (!step) return;
|
|
485
|
+
this.index = index;
|
|
486
|
+
|
|
487
|
+
if (this._titleEl) this._titleEl.textContent = step.title || '';
|
|
488
|
+
if (this._bodyEl) this._bodyEl.textContent = step.body || '';
|
|
489
|
+
if (this._progressEl) this._progressEl.textContent = `Step ${index + 1} of ${this.steps.length}`;
|
|
490
|
+
|
|
491
|
+
if (this._backBtn) this._backBtn.disabled = index === 0;
|
|
492
|
+
if (this._nextBtn) this._nextBtn.textContent = index === this.steps.length - 1 ? 'Finish' : 'Next';
|
|
493
|
+
|
|
494
|
+
const finalize = () => {
|
|
495
|
+
const target = this._resolveTarget(step);
|
|
496
|
+
this._currentTarget = target;
|
|
497
|
+
if (target) this._scrollTargetIntoView(target);
|
|
498
|
+
this._schedulePosition(true);
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
const enterResult = this._runStepEnter(step);
|
|
502
|
+
if (enterResult && typeof enterResult.then === 'function') {
|
|
503
|
+
enterResult.then(() => requestAnimationFrame(finalize)).catch(() => requestAnimationFrame(finalize));
|
|
504
|
+
} else {
|
|
505
|
+
requestAnimationFrame(finalize);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
_schedulePosition(force = false) {
|
|
510
|
+
if (!this.active) return;
|
|
511
|
+
if (this._positionRaf && !force) return;
|
|
512
|
+
if (this._positionRaf) cancelAnimationFrame(this._positionRaf);
|
|
513
|
+
this._positionRaf = requestAnimationFrame(() => {
|
|
514
|
+
this._positionRaf = null;
|
|
515
|
+
this._positionCurrent();
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
_positionCurrent() {
|
|
520
|
+
if (!this.active || !this._card || !this._highlight) return;
|
|
521
|
+
|
|
522
|
+
const step = this.steps[this.index];
|
|
523
|
+
const target = this._currentTarget;
|
|
524
|
+
const viewport = getViewportRect();
|
|
525
|
+
const padding = Number(step?.padding);
|
|
526
|
+
const pad = Number.isFinite(padding) ? padding : DEFAULT_PADDING;
|
|
527
|
+
|
|
528
|
+
if (!target || !target.getBoundingClientRect) {
|
|
529
|
+
this._highlight.style.opacity = '0';
|
|
530
|
+
this._highlight.style.width = '0px';
|
|
531
|
+
this._highlight.style.height = '0px';
|
|
532
|
+
this._highlight.style.left = '0px';
|
|
533
|
+
this._highlight.style.top = '0px';
|
|
534
|
+
this._card.classList.add('is-center');
|
|
535
|
+
this._card.style.left = '';
|
|
536
|
+
this._card.style.top = '';
|
|
537
|
+
this._card.style.transform = '';
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
const rect = target.getBoundingClientRect();
|
|
542
|
+
if (!rect || rect.width <= 0 || rect.height <= 0) {
|
|
543
|
+
this._highlight.style.opacity = '0';
|
|
544
|
+
this._highlight.style.width = '0px';
|
|
545
|
+
this._highlight.style.height = '0px';
|
|
546
|
+
this._highlight.style.left = '0px';
|
|
547
|
+
this._highlight.style.top = '0px';
|
|
548
|
+
this._card.classList.add('is-center');
|
|
549
|
+
this._card.style.left = '';
|
|
550
|
+
this._card.style.top = '';
|
|
551
|
+
this._card.style.transform = '';
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
const left = clamp(rect.left - pad, viewport.left + 6, viewport.right - 6);
|
|
556
|
+
const top = clamp(rect.top - pad, viewport.top + 6, viewport.bottom - 6);
|
|
557
|
+
const right = clamp(rect.right + pad, viewport.left + 6, viewport.right - 6);
|
|
558
|
+
const bottom = clamp(rect.bottom + pad, viewport.top + 6, viewport.bottom - 6);
|
|
559
|
+
const width = Math.max(0, right - left);
|
|
560
|
+
const height = Math.max(0, bottom - top);
|
|
561
|
+
|
|
562
|
+
this._highlight.style.opacity = '1';
|
|
563
|
+
this._highlight.style.left = `${left}px`;
|
|
564
|
+
this._highlight.style.top = `${top}px`;
|
|
565
|
+
this._highlight.style.width = `${width}px`;
|
|
566
|
+
this._highlight.style.height = `${height}px`;
|
|
567
|
+
|
|
568
|
+
this._card.classList.remove('is-center');
|
|
569
|
+
|
|
570
|
+
const cardRect = this._card.getBoundingClientRect();
|
|
571
|
+
let cardLeft = left;
|
|
572
|
+
let cardTop = bottom + MIN_CARD_GAP;
|
|
573
|
+
|
|
574
|
+
if (cardTop + cardRect.height + CARD_MARGIN > viewport.bottom) {
|
|
575
|
+
const above = top - cardRect.height - MIN_CARD_GAP;
|
|
576
|
+
if (above >= CARD_MARGIN) {
|
|
577
|
+
cardTop = above;
|
|
578
|
+
} else {
|
|
579
|
+
cardTop = clamp(viewport.bottom - cardRect.height - CARD_MARGIN, CARD_MARGIN, viewport.bottom - CARD_MARGIN);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
if (cardLeft + cardRect.width + CARD_MARGIN > viewport.right) {
|
|
584
|
+
cardLeft = clamp(viewport.right - cardRect.width - CARD_MARGIN, CARD_MARGIN, viewport.right - CARD_MARGIN);
|
|
585
|
+
}
|
|
586
|
+
if (cardLeft < CARD_MARGIN) cardLeft = CARD_MARGIN;
|
|
587
|
+
if (cardTop < CARD_MARGIN) cardTop = CARD_MARGIN;
|
|
588
|
+
|
|
589
|
+
this._card.style.left = `${cardLeft}px`;
|
|
590
|
+
this._card.style.top = `${cardTop}px`;
|
|
591
|
+
this._card.style.transform = 'none';
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
next() {
|
|
595
|
+
if (!this.active) return;
|
|
596
|
+
if (this.index >= this.steps.length - 1) {
|
|
597
|
+
this.complete();
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
this._showStep(this.index + 1);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
prev() {
|
|
604
|
+
if (!this.active) return;
|
|
605
|
+
if (this.index <= 0) return;
|
|
606
|
+
this._showStep(this.index - 1);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
exit() {
|
|
610
|
+
if (!this.active) return;
|
|
611
|
+
if (this._skipNextCheckbox?.checked) StartupTour.markDone();
|
|
612
|
+
else resetStartupTourFlag();
|
|
613
|
+
this.destroy();
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
complete() {
|
|
617
|
+
if (!this.active) return;
|
|
618
|
+
if (this._skipNextCheckbox?.checked) StartupTour.markDone();
|
|
619
|
+
else resetStartupTourFlag();
|
|
620
|
+
this.destroy();
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
destroy() {
|
|
624
|
+
if (!this.active) return;
|
|
625
|
+
this.active = false;
|
|
626
|
+
|
|
627
|
+
if (this._positionRaf) cancelAnimationFrame(this._positionRaf);
|
|
628
|
+
this._positionRaf = null;
|
|
629
|
+
|
|
630
|
+
this._detachEvents();
|
|
631
|
+
|
|
632
|
+
try { this._overlay?.remove(); } catch { }
|
|
633
|
+
this._overlay = null;
|
|
634
|
+
this._highlight = null;
|
|
635
|
+
this._card = null;
|
|
636
|
+
this._titleEl = null;
|
|
637
|
+
this._bodyEl = null;
|
|
638
|
+
this._progressEl = null;
|
|
639
|
+
this._skipNextRow = null;
|
|
640
|
+
this._skipNextCheckbox = null;
|
|
641
|
+
this._nextBtn = null;
|
|
642
|
+
this._backBtn = null;
|
|
643
|
+
this._skipBtn = null;
|
|
644
|
+
this._closeBtn = null;
|
|
645
|
+
|
|
646
|
+
this._restoreSidebar();
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
export async function maybeStartStartupTour(viewer, options = {}) {
|
|
651
|
+
const tour = new StartupTour(viewer, options);
|
|
652
|
+
const started = await tour.maybeStart();
|
|
653
|
+
if (!started) return null;
|
|
654
|
+
return tour;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
export function resetStartupTourFlag() {
|
|
658
|
+
try { LS.setItem(TOUR_STORAGE_KEY, ''); } catch { }
|
|
659
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export function createNewButton(viewer) {
|
|
2
|
+
async function onClick() {
|
|
3
|
+
try {
|
|
4
|
+
if (viewer?.fileManagerWidget?.newModel) {
|
|
5
|
+
await viewer.fileManagerWidget.newModel();
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
} catch { }
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
if (!viewer?.partHistory) return;
|
|
12
|
+
const proceed = await confirm('Clear current model and start a new one?');
|
|
13
|
+
if (!proceed) return;
|
|
14
|
+
await viewer.partHistory.reset?.();
|
|
15
|
+
try { viewer.partHistory.currentHistoryStepId = null; } catch { }
|
|
16
|
+
await viewer.partHistory.runHistory?.();
|
|
17
|
+
} catch { }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return { label: '🆕', title: 'New model', onClick };
|
|
21
|
+
}
|