@ytspar/devbar 1.0.0-canary.cdf7fa2 → 1.0.0-canary.db7454c
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/dist/GlobalDevBar.d.ts +7 -0
- package/dist/GlobalDevBar.js +135 -34
- package/dist/constants.d.ts +11 -1
- package/dist/constants.js +11 -1
- package/dist/ui/modals.d.ts +4 -0
- package/dist/ui/modals.js +48 -4
- package/package.json +1 -1
package/dist/GlobalDevBar.d.ts
CHANGED
|
@@ -46,6 +46,8 @@ export declare class GlobalDevBar {
|
|
|
46
46
|
private apiKeyStatus;
|
|
47
47
|
private lastOutline;
|
|
48
48
|
private lastSchema;
|
|
49
|
+
private savingOutline;
|
|
50
|
+
private savingSchema;
|
|
49
51
|
private consoleFilter;
|
|
50
52
|
private showOutlineModal;
|
|
51
53
|
private showSchemaModal;
|
|
@@ -55,6 +57,11 @@ export declare class GlobalDevBar {
|
|
|
55
57
|
private clsValue;
|
|
56
58
|
private inpValue;
|
|
57
59
|
private reconnectAttempts;
|
|
60
|
+
private readonly currentAppPort;
|
|
61
|
+
private readonly baseWsPort;
|
|
62
|
+
private wsVerified;
|
|
63
|
+
private serverProjectDir;
|
|
64
|
+
private verificationTimeout;
|
|
58
65
|
private lastDotPosition;
|
|
59
66
|
private reconnectTimeout;
|
|
60
67
|
private screenshotTimeout;
|
package/dist/GlobalDevBar.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* to avoid React dependency conflicts in host applications.
|
|
9
9
|
*/
|
|
10
10
|
import * as html2canvasModule from 'html2canvas-pro';
|
|
11
|
-
import { BASE_RECONNECT_DELAY_MS, BUTTON_COLORS, CATEGORY_COLORS, CLIPBOARD_NOTIFICATION_MS, COLORS, DESIGN_REVIEW_NOTIFICATION_MS, DEVBAR_SCREENSHOT_QUALITY, FONT_MONO, getEffectiveTheme, getThemeColors, MAX_CONSOLE_LOGS, MAX_RECONNECT_ATTEMPTS, MAX_RECONNECT_DELAY_MS, SCREENSHOT_BLUR_DELAY_MS, SCREENSHOT_NOTIFICATION_MS, SCREENSHOT_SCALE, TAILWIND_BREAKPOINTS, TOOLTIP_STYLES, WS_PORT, } from './constants.js';
|
|
11
|
+
import { BASE_RECONNECT_DELAY_MS, BUTTON_COLORS, CATEGORY_COLORS, CLIPBOARD_NOTIFICATION_MS, COLORS, DESIGN_REVIEW_NOTIFICATION_MS, DEVBAR_SCREENSHOT_QUALITY, FONT_MONO, getEffectiveTheme, getTheme, getThemeColors, injectThemeCSS, MAX_CONSOLE_LOGS, MAX_PORT_RETRIES, MAX_RECONNECT_ATTEMPTS, MAX_RECONNECT_DELAY_MS, PORT_RETRY_DELAY_MS, PORT_SCAN_RESTART_DELAY_MS, SCREENSHOT_BLUR_DELAY_MS, SCREENSHOT_NOTIFICATION_MS, SCREENSHOT_SCALE, TAILWIND_BREAKPOINTS, TOOLTIP_STYLES, VERIFICATION_TIMEOUT_MS, WS_PORT, WS_PORT_OFFSET, } from './constants.js';
|
|
12
12
|
import { DebugLogger, normalizeDebugConfig } from './debug.js';
|
|
13
13
|
import { extractDocumentOutline, outlineToMarkdown } from './outline.js';
|
|
14
14
|
import { extractPageSchema, schemaToMarkdown } from './schema.js';
|
|
@@ -91,6 +91,8 @@ export class GlobalDevBar {
|
|
|
91
91
|
this.apiKeyStatus = null;
|
|
92
92
|
this.lastOutline = null;
|
|
93
93
|
this.lastSchema = null;
|
|
94
|
+
this.savingOutline = false;
|
|
95
|
+
this.savingSchema = false;
|
|
94
96
|
this.consoleFilter = null;
|
|
95
97
|
// Modal states
|
|
96
98
|
this.showOutlineModal = false;
|
|
@@ -101,6 +103,9 @@ export class GlobalDevBar {
|
|
|
101
103
|
this.clsValue = 0;
|
|
102
104
|
this.inpValue = 0;
|
|
103
105
|
this.reconnectAttempts = 0;
|
|
106
|
+
this.wsVerified = false;
|
|
107
|
+
this.serverProjectDir = null;
|
|
108
|
+
this.verificationTimeout = null;
|
|
104
109
|
// Track the position of the connection indicator dot for smooth collapse
|
|
105
110
|
this.lastDotPosition = null;
|
|
106
111
|
this.reconnectTimeout = null;
|
|
@@ -132,6 +137,17 @@ export class GlobalDevBar {
|
|
|
132
137
|
this.debug = new DebugLogger(this.debugConfig);
|
|
133
138
|
// Initialize settings manager
|
|
134
139
|
this.settingsManager = getSettingsManager();
|
|
140
|
+
// Calculate app port from URL for multi-instance support
|
|
141
|
+
if (typeof window !== 'undefined') {
|
|
142
|
+
this.currentAppPort =
|
|
143
|
+
parseInt(window.location.port, 10) || (window.location.protocol === 'https:' ? 443 : 80);
|
|
144
|
+
// Calculate expected WS port (appPort + port offset) like SweetlinkBridge does
|
|
145
|
+
this.baseWsPort = this.currentAppPort > 0 ? this.currentAppPort + WS_PORT_OFFSET : WS_PORT;
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
this.currentAppPort = 0;
|
|
149
|
+
this.baseWsPort = WS_PORT;
|
|
150
|
+
}
|
|
135
151
|
this.options = {
|
|
136
152
|
position: options.position ?? 'bottom-left',
|
|
137
153
|
accentColor: options.accentColor ?? COLORS.primary,
|
|
@@ -287,6 +303,8 @@ export class GlobalDevBar {
|
|
|
287
303
|
this.reconnectAttempts = MAX_RECONNECT_ATTEMPTS; // Prevent reconnection
|
|
288
304
|
if (this.reconnectTimeout)
|
|
289
305
|
clearTimeout(this.reconnectTimeout);
|
|
306
|
+
if (this.verificationTimeout)
|
|
307
|
+
clearTimeout(this.verificationTimeout);
|
|
290
308
|
if (this.ws)
|
|
291
309
|
this.ws.close();
|
|
292
310
|
// Clear timeouts
|
|
@@ -345,27 +363,82 @@ export class GlobalDevBar {
|
|
|
345
363
|
document.head.appendChild(style);
|
|
346
364
|
}
|
|
347
365
|
}
|
|
348
|
-
connectWebSocket() {
|
|
366
|
+
connectWebSocket(port) {
|
|
349
367
|
if (this.destroyed)
|
|
350
368
|
return;
|
|
351
|
-
|
|
352
|
-
|
|
369
|
+
const targetPort = port ?? this.baseWsPort;
|
|
370
|
+
this.debug.ws('Connecting to WebSocket', { port: targetPort, appPort: this.currentAppPort });
|
|
371
|
+
const ws = new WebSocket(`ws://localhost:${targetPort}`);
|
|
353
372
|
this.ws = ws;
|
|
373
|
+
this.wsVerified = false;
|
|
374
|
+
// Timeout for server-info verification
|
|
375
|
+
this.verificationTimeout = setTimeout(() => {
|
|
376
|
+
if (!this.wsVerified && ws.readyState === WebSocket.OPEN) {
|
|
377
|
+
// Server didn't send server-info (old version) - accept for backwards compatibility
|
|
378
|
+
this.debug.ws('Server is old version (no server-info), accepting for backwards compat');
|
|
379
|
+
this.wsVerified = true;
|
|
380
|
+
this.sweetlinkConnected = true;
|
|
381
|
+
this.reconnectAttempts = 0;
|
|
382
|
+
this.settingsManager.setWebSocket(ws);
|
|
383
|
+
this.settingsManager.setConnected(true);
|
|
384
|
+
ws.send(JSON.stringify({ type: 'load-settings' }));
|
|
385
|
+
this.render();
|
|
386
|
+
}
|
|
387
|
+
}, VERIFICATION_TIMEOUT_MS);
|
|
354
388
|
ws.onopen = () => {
|
|
355
|
-
this.
|
|
356
|
-
this.reconnectAttempts = 0;
|
|
357
|
-
this.debug.ws('WebSocket connected');
|
|
358
|
-
// Update settings manager with WebSocket connection
|
|
359
|
-
this.settingsManager.setWebSocket(ws);
|
|
360
|
-
this.settingsManager.setConnected(true);
|
|
389
|
+
this.debug.ws('WebSocket socket opened, awaiting server-info');
|
|
361
390
|
ws.send(JSON.stringify({ type: 'browser-client-ready' }));
|
|
362
|
-
// Request settings from server
|
|
363
|
-
ws.send(JSON.stringify({ type: 'load-settings' }));
|
|
364
|
-
this.render();
|
|
365
391
|
};
|
|
366
392
|
ws.onmessage = async (event) => {
|
|
367
393
|
try {
|
|
368
|
-
const
|
|
394
|
+
const message = JSON.parse(event.data);
|
|
395
|
+
// Handle server-info for port matching
|
|
396
|
+
if (message.type === 'server-info') {
|
|
397
|
+
if (this.verificationTimeout) {
|
|
398
|
+
clearTimeout(this.verificationTimeout);
|
|
399
|
+
this.verificationTimeout = null;
|
|
400
|
+
}
|
|
401
|
+
const serverAppPort = message.appPort;
|
|
402
|
+
const serverMatchesApp = serverAppPort === null || serverAppPort === this.currentAppPort;
|
|
403
|
+
if (!serverMatchesApp) {
|
|
404
|
+
this.debug.ws('Server mismatch', {
|
|
405
|
+
serverAppPort,
|
|
406
|
+
currentAppPort: this.currentAppPort,
|
|
407
|
+
tryingNextPort: targetPort + 1,
|
|
408
|
+
});
|
|
409
|
+
ws.close();
|
|
410
|
+
// Try next port
|
|
411
|
+
const nextPort = targetPort + 1;
|
|
412
|
+
if (nextPort < this.baseWsPort + MAX_PORT_RETRIES) {
|
|
413
|
+
setTimeout(() => this.connectWebSocket(nextPort), PORT_RETRY_DELAY_MS);
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
this.debug.ws('No matching server found, will retry from base port');
|
|
417
|
+
setTimeout(() => this.connectWebSocket(this.baseWsPort), PORT_SCAN_RESTART_DELAY_MS);
|
|
418
|
+
}
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
// Server matches - mark as verified and connected
|
|
422
|
+
this.wsVerified = true;
|
|
423
|
+
this.sweetlinkConnected = true;
|
|
424
|
+
this.reconnectAttempts = 0;
|
|
425
|
+
this.serverProjectDir = message.projectDir ?? null;
|
|
426
|
+
this.debug.ws('Server verified', {
|
|
427
|
+
appPort: serverAppPort ?? 'any',
|
|
428
|
+
projectDir: this.serverProjectDir,
|
|
429
|
+
});
|
|
430
|
+
this.settingsManager.setWebSocket(ws);
|
|
431
|
+
this.settingsManager.setConnected(true);
|
|
432
|
+
ws.send(JSON.stringify({ type: 'load-settings' }));
|
|
433
|
+
this.render();
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
// Ignore other commands until verified
|
|
437
|
+
if (!this.wsVerified) {
|
|
438
|
+
this.debug.ws('Ignoring command before verification', { type: message.type });
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
const command = message;
|
|
369
442
|
this.debug.ws('Received command', { type: command.type });
|
|
370
443
|
await this.handleSweetlinkCommand(command);
|
|
371
444
|
}
|
|
@@ -374,16 +447,25 @@ export class GlobalDevBar {
|
|
|
374
447
|
}
|
|
375
448
|
};
|
|
376
449
|
ws.onclose = () => {
|
|
377
|
-
this.
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
//
|
|
382
|
-
if (
|
|
383
|
-
|
|
384
|
-
this.
|
|
385
|
-
this.
|
|
386
|
-
this.
|
|
450
|
+
if (this.verificationTimeout) {
|
|
451
|
+
clearTimeout(this.verificationTimeout);
|
|
452
|
+
this.verificationTimeout = null;
|
|
453
|
+
}
|
|
454
|
+
// Only reset connection state if we were actually verified/connected
|
|
455
|
+
if (this.wsVerified) {
|
|
456
|
+
this.sweetlinkConnected = false;
|
|
457
|
+
this.wsVerified = false;
|
|
458
|
+
this.serverProjectDir = null;
|
|
459
|
+
this.settingsManager.setConnected(false);
|
|
460
|
+
this.debug.ws('WebSocket disconnected');
|
|
461
|
+
this.render();
|
|
462
|
+
// Auto-reconnect with exponential backoff (start from base port)
|
|
463
|
+
if (!this.destroyed && this.reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
|
|
464
|
+
const delayMs = BASE_RECONNECT_DELAY_MS * 2 ** this.reconnectAttempts;
|
|
465
|
+
this.reconnectAttempts++;
|
|
466
|
+
this.debug.ws('Scheduling reconnect', { attempt: this.reconnectAttempts, delayMs });
|
|
467
|
+
this.reconnectTimeout = setTimeout(() => this.connectWebSocket(this.baseWsPort), Math.min(delayMs, MAX_RECONNECT_DELAY_MS));
|
|
468
|
+
}
|
|
387
469
|
}
|
|
388
470
|
};
|
|
389
471
|
ws.onerror = () => {
|
|
@@ -579,6 +661,7 @@ export class GlobalDevBar {
|
|
|
579
661
|
}, durationMs);
|
|
580
662
|
break;
|
|
581
663
|
case 'outline':
|
|
664
|
+
this.savingOutline = false;
|
|
582
665
|
this.lastOutline = path;
|
|
583
666
|
if (this.outlineTimeout)
|
|
584
667
|
clearTimeout(this.outlineTimeout);
|
|
@@ -588,6 +671,7 @@ export class GlobalDevBar {
|
|
|
588
671
|
}, durationMs);
|
|
589
672
|
break;
|
|
590
673
|
case 'schema':
|
|
674
|
+
this.savingSchema = false;
|
|
591
675
|
this.lastSchema = path;
|
|
592
676
|
if (this.schemaTimeout)
|
|
593
677
|
clearTimeout(this.schemaTimeout);
|
|
@@ -787,6 +871,8 @@ export class GlobalDevBar {
|
|
|
787
871
|
this.themeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
|
788
872
|
this.themeMediaHandler = () => {
|
|
789
873
|
if (this.themeMode === 'system') {
|
|
874
|
+
// Re-inject theme CSS when system preference changes
|
|
875
|
+
injectThemeCSS(getTheme(this.themeMode));
|
|
790
876
|
this.debug.state('System theme changed', {
|
|
791
877
|
effectiveTheme: getEffectiveTheme(this.themeMode),
|
|
792
878
|
});
|
|
@@ -813,6 +899,8 @@ export class GlobalDevBar {
|
|
|
813
899
|
setThemeMode(mode) {
|
|
814
900
|
this.themeMode = mode;
|
|
815
901
|
this.settingsManager.saveSettings({ themeMode: mode });
|
|
902
|
+
// Inject the appropriate theme CSS variables
|
|
903
|
+
injectThemeCSS(getTheme(mode));
|
|
816
904
|
this.debug.state('Theme mode changed', { mode, effectiveTheme: getEffectiveTheme(mode) });
|
|
817
905
|
this.render();
|
|
818
906
|
}
|
|
@@ -1066,9 +1154,13 @@ export class GlobalDevBar {
|
|
|
1066
1154
|
this.render();
|
|
1067
1155
|
}
|
|
1068
1156
|
handleSaveOutline() {
|
|
1157
|
+
if (this.savingOutline)
|
|
1158
|
+
return; // Prevent repeated clicks
|
|
1069
1159
|
const outline = extractDocumentOutline();
|
|
1070
1160
|
const markdown = outlineToMarkdown(outline);
|
|
1071
1161
|
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
1162
|
+
this.savingOutline = true;
|
|
1163
|
+
this.render();
|
|
1072
1164
|
this.ws.send(JSON.stringify({
|
|
1073
1165
|
type: 'save-outline',
|
|
1074
1166
|
data: {
|
|
@@ -1082,9 +1174,13 @@ export class GlobalDevBar {
|
|
|
1082
1174
|
}
|
|
1083
1175
|
}
|
|
1084
1176
|
handleSaveSchema() {
|
|
1177
|
+
if (this.savingSchema)
|
|
1178
|
+
return; // Prevent repeated clicks
|
|
1085
1179
|
const schema = extractPageSchema();
|
|
1086
1180
|
const markdown = schemaToMarkdown(schema);
|
|
1087
1181
|
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
1182
|
+
this.savingSchema = true;
|
|
1183
|
+
this.render();
|
|
1088
1184
|
this.ws.send(JSON.stringify({
|
|
1089
1185
|
type: 'save-schema',
|
|
1090
1186
|
data: {
|
|
@@ -1462,6 +1558,8 @@ export class GlobalDevBar {
|
|
|
1462
1558
|
},
|
|
1463
1559
|
onSave: () => this.handleSaveOutline(),
|
|
1464
1560
|
sweetlinkConnected: this.sweetlinkConnected,
|
|
1561
|
+
isSaving: this.savingOutline,
|
|
1562
|
+
savedPath: this.lastOutline,
|
|
1465
1563
|
});
|
|
1466
1564
|
modal.appendChild(header);
|
|
1467
1565
|
const content = createModalContent();
|
|
@@ -1548,6 +1646,8 @@ export class GlobalDevBar {
|
|
|
1548
1646
|
},
|
|
1549
1647
|
onSave: () => this.handleSaveSchema(),
|
|
1550
1648
|
sweetlinkConnected: this.sweetlinkConnected,
|
|
1649
|
+
isSaving: this.savingSchema,
|
|
1650
|
+
savedPath: this.lastSchema,
|
|
1551
1651
|
});
|
|
1552
1652
|
modal.appendChild(header);
|
|
1553
1653
|
const content = createModalContent();
|
|
@@ -1915,7 +2015,8 @@ export class GlobalDevBar {
|
|
|
1915
2015
|
const isCompact = this.compactMode;
|
|
1916
2016
|
const tooltip = isCompact ? 'Expand (Cmd+Shift+M)' : 'Compact (Cmd+Shift+M)';
|
|
1917
2017
|
btn.setAttribute('data-tooltip', tooltip);
|
|
1918
|
-
const
|
|
2018
|
+
const { accentColor } = this.options;
|
|
2019
|
+
const iconColor = COLORS.textSecondary;
|
|
1919
2020
|
Object.assign(btn.style, {
|
|
1920
2021
|
display: 'flex',
|
|
1921
2022
|
alignItems: 'center',
|
|
@@ -1926,21 +2027,21 @@ export class GlobalDevBar {
|
|
|
1926
2027
|
minHeight: '22px',
|
|
1927
2028
|
flexShrink: '0',
|
|
1928
2029
|
borderRadius: '50%',
|
|
1929
|
-
border: `1px solid ${
|
|
2030
|
+
border: `1px solid ${accentColor}60`,
|
|
1930
2031
|
backgroundColor: 'transparent',
|
|
1931
|
-
color: `${
|
|
2032
|
+
color: `${iconColor}99`,
|
|
1932
2033
|
cursor: 'pointer',
|
|
1933
2034
|
transition: 'all 150ms',
|
|
1934
2035
|
});
|
|
1935
2036
|
btn.onmouseenter = () => {
|
|
1936
|
-
btn.style.borderColor =
|
|
1937
|
-
btn.style.backgroundColor = `${
|
|
1938
|
-
btn.style.color =
|
|
2037
|
+
btn.style.borderColor = accentColor;
|
|
2038
|
+
btn.style.backgroundColor = `${accentColor}20`;
|
|
2039
|
+
btn.style.color = iconColor;
|
|
1939
2040
|
};
|
|
1940
2041
|
btn.onmouseleave = () => {
|
|
1941
|
-
btn.style.borderColor = `${
|
|
2042
|
+
btn.style.borderColor = `${accentColor}60`;
|
|
1942
2043
|
btn.style.backgroundColor = 'transparent';
|
|
1943
|
-
btn.style.color = `${
|
|
2044
|
+
btn.style.color = `${iconColor}99`;
|
|
1944
2045
|
};
|
|
1945
2046
|
btn.onclick = () => {
|
|
1946
2047
|
this.toggleCompactMode();
|
|
@@ -1956,8 +2057,8 @@ export class GlobalDevBar {
|
|
|
1956
2057
|
svg.setAttribute('stroke-linecap', 'round');
|
|
1957
2058
|
svg.setAttribute('stroke-linejoin', 'round');
|
|
1958
2059
|
const path = document.createElementNS('http://www.w3.org/2000/svg', 'polyline');
|
|
1959
|
-
//
|
|
1960
|
-
path.setAttribute('points', isCompact ? '
|
|
2060
|
+
// Left chevron (<) when expanded to shrink, right chevron (>) when compact to expand
|
|
2061
|
+
path.setAttribute('points', isCompact ? '9 18 15 12 9 6' : '15 18 9 12 15 6');
|
|
1961
2062
|
svg.appendChild(path);
|
|
1962
2063
|
btn.appendChild(svg);
|
|
1963
2064
|
return btn;
|
package/dist/constants.d.ts
CHANGED
|
@@ -11,8 +11,18 @@ export declare const MAX_RECONNECT_ATTEMPTS = 10;
|
|
|
11
11
|
export declare const BASE_RECONNECT_DELAY_MS = 1000;
|
|
12
12
|
/** Maximum delay between reconnection attempts (ms) */
|
|
13
13
|
export declare const MAX_RECONNECT_DELAY_MS = 30000;
|
|
14
|
-
/** Default WebSocket port for Sweetlink connection */
|
|
14
|
+
/** Default WebSocket port for Sweetlink connection (legacy fallback) */
|
|
15
15
|
export declare const WS_PORT = 9223;
|
|
16
|
+
/** Port offset from app port to calculate WebSocket port (matches SweetlinkBridge) */
|
|
17
|
+
export declare const WS_PORT_OFFSET = 6223;
|
|
18
|
+
/** Maximum ports to try when scanning for matching server */
|
|
19
|
+
export declare const MAX_PORT_RETRIES = 10;
|
|
20
|
+
/** Delay between port scan attempts (ms) */
|
|
21
|
+
export declare const PORT_RETRY_DELAY_MS = 100;
|
|
22
|
+
/** Timeout for server-info verification (ms) */
|
|
23
|
+
export declare const VERIFICATION_TIMEOUT_MS = 1000;
|
|
24
|
+
/** Delay before restarting port scan from base after all ports fail (ms) */
|
|
25
|
+
export declare const PORT_SCAN_RESTART_DELAY_MS = 3000;
|
|
16
26
|
/** Duration to show screenshot notification (ms) */
|
|
17
27
|
export declare const SCREENSHOT_NOTIFICATION_MS = 3000;
|
|
18
28
|
/** Duration to show clipboard notification (ms) */
|
package/dist/constants.js
CHANGED
|
@@ -18,8 +18,18 @@ export const MAX_RECONNECT_DELAY_MS = 30000;
|
|
|
18
18
|
// ============================================================================
|
|
19
19
|
// WebSocket Settings
|
|
20
20
|
// ============================================================================
|
|
21
|
-
/** Default WebSocket port for Sweetlink connection */
|
|
21
|
+
/** Default WebSocket port for Sweetlink connection (legacy fallback) */
|
|
22
22
|
export const WS_PORT = 9223;
|
|
23
|
+
/** Port offset from app port to calculate WebSocket port (matches SweetlinkBridge) */
|
|
24
|
+
export const WS_PORT_OFFSET = 6223;
|
|
25
|
+
/** Maximum ports to try when scanning for matching server */
|
|
26
|
+
export const MAX_PORT_RETRIES = 10;
|
|
27
|
+
/** Delay between port scan attempts (ms) */
|
|
28
|
+
export const PORT_RETRY_DELAY_MS = 100;
|
|
29
|
+
/** Timeout for server-info verification (ms) */
|
|
30
|
+
export const VERIFICATION_TIMEOUT_MS = 1000;
|
|
31
|
+
/** Delay before restarting port scan from base after all ports fail (ms) */
|
|
32
|
+
export const PORT_SCAN_RESTART_DELAY_MS = 3000;
|
|
23
33
|
// ============================================================================
|
|
24
34
|
// Notification Durations
|
|
25
35
|
// ============================================================================
|
package/dist/ui/modals.d.ts
CHANGED
|
@@ -13,6 +13,10 @@ export interface ModalConfig {
|
|
|
13
13
|
onCopyMd: () => Promise<void>;
|
|
14
14
|
onSave?: () => void;
|
|
15
15
|
sweetlinkConnected: boolean;
|
|
16
|
+
/** Whether a save operation is in progress */
|
|
17
|
+
isSaving?: boolean;
|
|
18
|
+
/** Path where data was saved (shows confirmation) */
|
|
19
|
+
savedPath?: string | null;
|
|
16
20
|
}
|
|
17
21
|
/**
|
|
18
22
|
* Create modal overlay with click-outside-to-close behavior
|
package/dist/ui/modals.js
CHANGED
|
@@ -34,7 +34,7 @@ export function createModalBox(color) {
|
|
|
34
34
|
* Create modal header with title, copy/save/close buttons
|
|
35
35
|
*/
|
|
36
36
|
export function createModalHeader(config) {
|
|
37
|
-
const { color, title, onClose, onCopyMd, onSave, sweetlinkConnected } = config;
|
|
37
|
+
const { color, title, onClose, onCopyMd, onSave, sweetlinkConnected, isSaving, savedPath } = config;
|
|
38
38
|
const header = document.createElement('div');
|
|
39
39
|
Object.assign(header.style, {
|
|
40
40
|
display: 'flex',
|
|
@@ -42,6 +42,8 @@ export function createModalHeader(config) {
|
|
|
42
42
|
justifyContent: 'space-between',
|
|
43
43
|
padding: '16px 20px',
|
|
44
44
|
borderBottom: `1px solid ${color}40`,
|
|
45
|
+
flexWrap: 'wrap',
|
|
46
|
+
gap: '8px',
|
|
45
47
|
});
|
|
46
48
|
const titleEl = document.createElement('h2');
|
|
47
49
|
Object.assign(titleEl.style, {
|
|
@@ -53,7 +55,7 @@ export function createModalHeader(config) {
|
|
|
53
55
|
titleEl.textContent = title;
|
|
54
56
|
header.appendChild(titleEl);
|
|
55
57
|
const headerButtons = document.createElement('div');
|
|
56
|
-
Object.assign(headerButtons.style, { display: 'flex', gap: '10px' });
|
|
58
|
+
Object.assign(headerButtons.style, { display: 'flex', gap: '10px', alignItems: 'center' });
|
|
57
59
|
// Copy MD button
|
|
58
60
|
const copyBtn = createStyledButton({ color, text: 'Copy MD' });
|
|
59
61
|
copyBtn.onclick = async () => {
|
|
@@ -71,8 +73,17 @@ export function createModalHeader(config) {
|
|
|
71
73
|
headerButtons.appendChild(copyBtn);
|
|
72
74
|
// Save button (if Sweetlink connected)
|
|
73
75
|
if (sweetlinkConnected && onSave) {
|
|
74
|
-
const saveBtn = createStyledButton({
|
|
75
|
-
|
|
76
|
+
const saveBtn = createStyledButton({
|
|
77
|
+
color,
|
|
78
|
+
text: isSaving ? 'Saving...' : 'Save',
|
|
79
|
+
});
|
|
80
|
+
if (isSaving) {
|
|
81
|
+
saveBtn.style.opacity = '0.6';
|
|
82
|
+
saveBtn.style.cursor = 'not-allowed';
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
saveBtn.onclick = onSave;
|
|
86
|
+
}
|
|
76
87
|
headerButtons.appendChild(saveBtn);
|
|
77
88
|
}
|
|
78
89
|
// Close button - use same padding as other buttons for consistent height
|
|
@@ -85,6 +96,39 @@ export function createModalHeader(config) {
|
|
|
85
96
|
closeBtn.onclick = onClose;
|
|
86
97
|
headerButtons.appendChild(closeBtn);
|
|
87
98
|
header.appendChild(headerButtons);
|
|
99
|
+
// Show saved path confirmation below buttons
|
|
100
|
+
if (savedPath) {
|
|
101
|
+
const savedConfirm = document.createElement('div');
|
|
102
|
+
Object.assign(savedConfirm.style, {
|
|
103
|
+
width: '100%',
|
|
104
|
+
marginTop: '4px',
|
|
105
|
+
padding: '8px 12px',
|
|
106
|
+
backgroundColor: `${color}15`,
|
|
107
|
+
border: `1px solid ${color}30`,
|
|
108
|
+
borderRadius: '6px',
|
|
109
|
+
fontSize: '0.75rem',
|
|
110
|
+
color: color,
|
|
111
|
+
display: 'flex',
|
|
112
|
+
alignItems: 'center',
|
|
113
|
+
gap: '6px',
|
|
114
|
+
});
|
|
115
|
+
// Checkmark icon
|
|
116
|
+
const checkmark = document.createElement('span');
|
|
117
|
+
checkmark.textContent = '✓';
|
|
118
|
+
Object.assign(checkmark.style, { fontWeight: '600' });
|
|
119
|
+
savedConfirm.appendChild(checkmark);
|
|
120
|
+
// Path text
|
|
121
|
+
const pathText = document.createElement('span');
|
|
122
|
+
Object.assign(pathText.style, {
|
|
123
|
+
color: '#9ca3af',
|
|
124
|
+
fontFamily: 'monospace',
|
|
125
|
+
fontSize: '0.6875rem',
|
|
126
|
+
wordBreak: 'break-all',
|
|
127
|
+
});
|
|
128
|
+
pathText.textContent = `Saved to ${savedPath}`;
|
|
129
|
+
savedConfirm.appendChild(pathText);
|
|
130
|
+
header.appendChild(savedConfirm);
|
|
131
|
+
}
|
|
88
132
|
return header;
|
|
89
133
|
}
|
|
90
134
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ytspar/devbar",
|
|
3
|
-
"version": "1.0.0-canary.
|
|
3
|
+
"version": "1.0.0-canary.db7454c",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "Development toolbar and utilities with Sweetlink integration - pure vanilla JS, no framework dependencies",
|