termdock 1.4.109 → 1.4.110
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/.desktop-dist/main.js +200 -27
- package/dist/client/assets/{ModelPreview-Cxk4IZLv.js → ModelPreview-B40SLVdu.js} +1 -1
- package/dist/client/assets/{abnfDiagram-VRR7QNED-Bz2HWRRX.js → abnfDiagram-VRR7QNED-D7p11Ssd.js} +1 -1
- package/dist/client/assets/{arc-D3vf35f_.js → arc-CsmLIp7i.js} +1 -1
- package/dist/client/assets/{architectureDiagram-ZJ3FMSHR-LI-OXsDp.js → architectureDiagram-ZJ3FMSHR-BjYttOye.js} +1 -1
- package/dist/client/assets/{blockDiagram-677ZJIJ3-Dm1JtLIo.js → blockDiagram-677ZJIJ3-BnWi8IVO.js} +1 -1
- package/dist/client/assets/{c4Diagram-LMCZKHZV-DSkxlBnU.js → c4Diagram-LMCZKHZV-BFm2RcKX.js} +1 -1
- package/dist/client/assets/channel-ifcSKqN3.js +1 -0
- package/dist/client/assets/{chunk-2Q5K7J3B-Wd2AO6yQ.js → chunk-2Q5K7J3B-D_OKw60X.js} +1 -1
- package/dist/client/assets/{chunk-32BRIVSS-CIBkFIAg.js → chunk-32BRIVSS-D6k1lZV9.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-9veDV0NJ.js → chunk-5VM5RSS4-CEqvSDkW.js} +1 -1
- package/dist/client/assets/{chunk-EX3LRPZG-DVi5mqrV.js → chunk-EX3LRPZG-Ca2Gmbvd.js} +1 -1
- package/dist/client/assets/{chunk-JWPE2WC7-DZc_3_5i.js → chunk-JWPE2WC7-CRRvRUQB.js} +1 -1
- package/dist/client/assets/{chunk-MOJQB5TN-Czq1YGEA.js → chunk-MOJQB5TN-CeZ5qCFm.js} +1 -1
- package/dist/client/assets/{chunk-RYQCIY6F-BN3UqH_7.js → chunk-RYQCIY6F-Cx7sW5Rr.js} +1 -1
- package/dist/client/assets/{chunk-V7JOEXUC-BgDCEp_z.js → chunk-V7JOEXUC-B3_grzN8.js} +1 -1
- package/dist/client/assets/{chunk-VR4S4FIN-BnyRVZSn.js → chunk-VR4S4FIN-g73M8USU.js} +1 -1
- package/dist/client/assets/{chunk-XXDRQBXY-KOa0rQIz.js → chunk-XXDRQBXY-9i-NV13r.js} +1 -1
- package/dist/client/assets/classDiagram-OUVF2IWQ-CVu2gyed.js +1 -0
- package/dist/client/assets/classDiagram-v2-EOCWNBFH-CVu2gyed.js +1 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-B-P2BstB.js → cose-bilkent-JH36ORCC-DGKauMxg.js} +1 -1
- package/dist/client/assets/{cynefin-VYW2F7L2-DOSg0Q6x.js → cynefin-VYW2F7L2-DKvEKvGd.js} +1 -1
- package/dist/client/assets/{cynefinDiagram-TSTJHNR4-Bm8Ct13U.js → cynefinDiagram-TSTJHNR4-CLXYMIpB.js} +1 -1
- package/dist/client/assets/{dagre-VKFMJZFB-Cnw99n0y.js → dagre-VKFMJZFB-DFAR5nAp.js} +1 -1
- package/dist/client/assets/{diagram-FQU43EPY-B742HZhK.js → diagram-FQU43EPY-DeRBoyeq.js} +1 -1
- package/dist/client/assets/{diagram-G47NLZAW-BGXKr2u9.js → diagram-G47NLZAW-CDEAgMuP.js} +1 -1
- package/dist/client/assets/{diagram-NH7WQ7WH-DIyEYN64.js → diagram-NH7WQ7WH-4VFR2ZsJ.js} +1 -1
- package/dist/client/assets/{diagram-OA4YK3LP-BdRMWU80.js → diagram-OA4YK3LP-DMKIgZlI.js} +1 -1
- package/dist/client/assets/{diagram-WEI45ONY-Bu-180k8.js → diagram-WEI45ONY-zEjxP4io.js} +1 -1
- package/dist/client/assets/{ebnfDiagram-CCIWWBDH-Djcej7Pq.js → ebnfDiagram-CCIWWBDH-BuErqR1l.js} +1 -1
- package/dist/client/assets/{erDiagram-Q63AITRT-D70JxkGr.js → erDiagram-Q63AITRT-D4nbc2T3.js} +1 -1
- package/dist/client/assets/{flowDiagram-23GEKE2U-DovWUM1i.js → flowDiagram-23GEKE2U-B2MiQAI4.js} +1 -1
- package/dist/client/assets/{ganttDiagram-NO4QXBWP-Izsfqq5i.js → ganttDiagram-NO4QXBWP-hGTX0vsS.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-IHSO6WYX-BTZo0N08.js → gitGraphDiagram-IHSO6WYX-BHHSVtvH.js} +1 -1
- package/dist/client/assets/index-CFXqRKcJ.css +1 -0
- package/dist/client/assets/index-D3kro0zp.js +456 -0
- package/dist/client/assets/{infoDiagram-FWYZ7A6U-Ina5uVvZ.js → infoDiagram-FWYZ7A6U-BZuNpsCG.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-FXEZZL3T-iUdzuACl.js → ishikawaDiagram-FXEZZL3T-C0KwjZIv.js} +1 -1
- package/dist/client/assets/{journeyDiagram-5HDEW3XC-lh15NShH.js → journeyDiagram-5HDEW3XC-mxy7-Irj.js} +1 -1
- package/dist/client/assets/{kanban-definition-HUTT4EX6-CKZooxMa.js → kanban-definition-HUTT4EX6-DslZKv6b.js} +1 -1
- package/dist/client/assets/{linear-BrmrQdSX.js → linear-BJXz3Iqq.js} +1 -1
- package/dist/client/assets/{mermaid.core-fbY4nFyi.js → mermaid.core-Btv5hFOv.js} +4 -4
- package/dist/client/assets/{mindmap-definition-LN4V7U3C-BHUhjMUf.js → mindmap-definition-LN4V7U3C-BSbqyDyq.js} +1 -1
- package/dist/client/assets/{pegDiagram-2B236MQR-8qcHqxsE.js → pegDiagram-2B236MQR-L2cCu4W1.js} +1 -1
- package/dist/client/assets/{pieDiagram-ENE6RG2P-CpIkkExH.js → pieDiagram-ENE6RG2P-CXdawbl4.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-ABIIQ3AL-hWzvYsWG.js → quadrantDiagram-ABIIQ3AL-CwKwBctE.js} +1 -1
- package/dist/client/assets/{railroadDiagram-RFXS5EU6-2nhrE9bK.js → railroadDiagram-RFXS5EU6-B6MBOnSp.js} +1 -1
- package/dist/client/assets/{requirementDiagram-TGXJPOKE-B05mEN7w.js → requirementDiagram-TGXJPOKE-CR9lns0Z.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-HTMAVEWB-CXHkBpDN.js → sankeyDiagram-HTMAVEWB-DLuhNvTd.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-DBY2YBRQ-C4wG1r_I.js → sequenceDiagram-DBY2YBRQ-BorPhsA5.js} +1 -1
- package/dist/client/assets/{sizeCapture-X5ZJPWSS-D_3i42tQ.js → sizeCapture-X5ZJPWSS-DPXho3_K.js} +1 -1
- package/dist/client/assets/{stateDiagram-2N3HPSRC-DhEgczRA.js → stateDiagram-2N3HPSRC-DSbrRqVk.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-6OUMAXLB--bVRWeRL.js +1 -0
- package/dist/client/assets/{swimlanes-5IMT3BWC-DTrpFviv.js → swimlanes-5IMT3BWC-D4A4axIk.js} +2 -2
- package/dist/client/assets/swimlanesDiagram-G3AALYLV-CqWtgk0W.js +8 -0
- package/dist/client/assets/{timeline-definition-FHXFAJF6-DLpl_8Za.js → timeline-definition-FHXFAJF6-D4ObRKT8.js} +1 -1
- package/dist/client/assets/{vennDiagram-L72KCM5P-CgnCFHj7.js → vennDiagram-L72KCM5P-Bj_4-RtP.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-EHGQE667-BwNfD-cz.js → wardleyDiagram-EHGQE667-BNvcAUoQ.js} +1 -1
- package/dist/client/assets/{xychartDiagram-FW5EYKEG--sZgo_Fw.js → xychartDiagram-FW5EYKEG-BfGV9sTq.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/sw.js +1 -1
- package/package.json +1 -1
- package/runtime-manifest.json +1 -1
- package/dist/client/assets/channel-qZtjoGxW.js +0 -1
- package/dist/client/assets/classDiagram-OUVF2IWQ-CnWXW1tk.js +0 -1
- package/dist/client/assets/classDiagram-v2-EOCWNBFH-CnWXW1tk.js +0 -1
- package/dist/client/assets/index-BE4Js_ss.js +0 -456
- package/dist/client/assets/index-CRIZk117.css +0 -1
- package/dist/client/assets/stateDiagram-v2-6OUMAXLB-D9gqRttI.js +0 -1
- package/dist/client/assets/swimlanesDiagram-G3AALYLV-CNOSFfLB.js +0 -8
package/.desktop-dist/main.js
CHANGED
|
@@ -53,13 +53,17 @@ function triggerLocalNetworkPermission() {
|
|
|
53
53
|
let mainWindow = null;
|
|
54
54
|
const serviceWindows = new Map();
|
|
55
55
|
const windowServiceOrigins = new WeakMap();
|
|
56
|
+
const restorableServiceWindows = new WeakSet();
|
|
56
57
|
const reportedServiceActivity = new Map();
|
|
57
58
|
const observedServiceActivity = new Map();
|
|
58
59
|
let lastFocusedServiceWindow = null;
|
|
59
60
|
let menuBarStatus = null;
|
|
61
|
+
let menuBarStatusWidth = 0;
|
|
60
62
|
let floatingWidgetWindow = null;
|
|
61
63
|
let floatingPositionTimer = null;
|
|
62
64
|
let isQuitting = false;
|
|
65
|
+
const FLOATING_WIDGET_WIDTHS = [64, 108, 152];
|
|
66
|
+
const FLOATING_WIDGET_HEIGHT = 40;
|
|
63
67
|
function focusedWorkspaceWindow() {
|
|
64
68
|
const focused = BrowserWindow.getFocusedWindow();
|
|
65
69
|
if (focused && windowServiceOrigins.has(focused))
|
|
@@ -182,25 +186,90 @@ function menuBarContextMenu() {
|
|
|
182
186
|
{ role: 'quit' },
|
|
183
187
|
]);
|
|
184
188
|
}
|
|
189
|
+
function menuBarStatusImage(status) {
|
|
190
|
+
const size = 18;
|
|
191
|
+
const scale = 2;
|
|
192
|
+
const bitmap = Buffer.alloc(size * scale * size * scale * 4);
|
|
193
|
+
const glyphs = {
|
|
194
|
+
'0': ['111', '101', '101', '101', '111'],
|
|
195
|
+
'1': ['010', '110', '010', '010', '111'],
|
|
196
|
+
'2': ['111', '001', '111', '100', '111'],
|
|
197
|
+
'3': ['111', '001', '111', '001', '111'],
|
|
198
|
+
'4': ['101', '101', '111', '001', '001'],
|
|
199
|
+
'5': ['111', '100', '111', '001', '111'],
|
|
200
|
+
'6': ['111', '100', '111', '101', '111'],
|
|
201
|
+
'7': ['111', '001', '010', '010', '010'],
|
|
202
|
+
'8': ['111', '101', '111', '101', '111'],
|
|
203
|
+
'9': ['111', '101', '111', '001', '111'],
|
|
204
|
+
'+': ['000', '010', '111', '010', '000'],
|
|
205
|
+
};
|
|
206
|
+
const drawPixel = (x, y, alpha = 255) => {
|
|
207
|
+
for (let offsetY = 0; offsetY < scale; offsetY += 1) {
|
|
208
|
+
for (let offsetX = 0; offsetX < scale; offsetX += 1) {
|
|
209
|
+
const pixelX = x * scale + offsetX;
|
|
210
|
+
const pixelY = y * scale + offsetY;
|
|
211
|
+
if (pixelX < 0 || pixelX >= size * scale || pixelY < 0 || pixelY >= size * scale)
|
|
212
|
+
continue;
|
|
213
|
+
bitmap[(pixelY * size * scale + pixelX) * 4 + 3] = alpha;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
const drawGlyph = (value, x, y, alpha) => {
|
|
218
|
+
const glyph = glyphs[value > 9 ? '+' : String(value)] ?? glyphs['0'];
|
|
219
|
+
glyph.forEach((row, rowIndex) => {
|
|
220
|
+
[...row].forEach((cell, columnIndex) => {
|
|
221
|
+
if (cell === '1')
|
|
222
|
+
drawPixel(x + columnIndex, y + rowIndex, alpha);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
};
|
|
226
|
+
// Three quiet, consistently aligned rows read much better at menu-bar size
|
|
227
|
+
// than squeezing two metrics beside each other. The ordering mirrors the
|
|
228
|
+
// floating widget: running, review, then connected services.
|
|
229
|
+
const rows = [
|
|
230
|
+
{ value: status.runningCount, y: 0, icon: [[0, 0], [0, 1], [1, 1], [0, 2], [1, 2], [2, 2], [3, 2], [0, 3], [1, 3], [0, 4]] },
|
|
231
|
+
{ value: status.reviewCount, y: 6, icon: [[2, 0], [1, 1], [3, 1], [0, 2], [4, 2], [1, 3], [3, 3], [2, 4]] },
|
|
232
|
+
{ value: status.serviceCount, y: 12, icon: [[0, 0], [1, 0], [3, 0], [4, 0], [1, 1], [3, 1], [2, 2], [2, 3], [1, 4], [2, 4], [3, 4]] },
|
|
233
|
+
];
|
|
234
|
+
rows.forEach(({ value, y, icon }) => {
|
|
235
|
+
const alpha = value > 0 ? 255 : 96;
|
|
236
|
+
icon.forEach(([offsetX, offsetY]) => drawPixel(1 + offsetX, y + offsetY, alpha));
|
|
237
|
+
drawGlyph(value, 11, y, alpha);
|
|
238
|
+
});
|
|
239
|
+
// Template images are intentionally monochrome; macOS supplies the correct
|
|
240
|
+
// menu-bar color for the active appearance and accessibility state.
|
|
241
|
+
const image = nativeImage.createFromBitmap(bitmap, {
|
|
242
|
+
width: size * scale,
|
|
243
|
+
height: size * scale,
|
|
244
|
+
scaleFactor: scale,
|
|
245
|
+
});
|
|
246
|
+
image.setTemplateImage(true);
|
|
247
|
+
return image;
|
|
248
|
+
}
|
|
185
249
|
function refreshMenuBarStatus(status) {
|
|
186
250
|
if (process.platform !== 'darwin' || !status.preferences.menuBarStatusEnabled) {
|
|
187
251
|
menuBarStatus?.destroy();
|
|
188
252
|
menuBarStatus = null;
|
|
253
|
+
menuBarStatusWidth = 0;
|
|
189
254
|
return;
|
|
190
255
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
menuBarStatus
|
|
256
|
+
const image = menuBarStatusImage(status);
|
|
257
|
+
const imageWidth = image.getSize().width;
|
|
258
|
+
if (!menuBarStatus || menuBarStatus.isDestroyed() || menuBarStatusWidth !== imageWidth) {
|
|
259
|
+
menuBarStatus?.destroy();
|
|
260
|
+
menuBarStatus = new Tray(image);
|
|
261
|
+
menuBarStatusWidth = imageWidth;
|
|
195
262
|
menuBarStatus.on('click', () => focusNextService('attention'));
|
|
196
263
|
menuBarStatus.on('right-click', () => menuBarStatus?.popUpContextMenu(menuBarContextMenu()));
|
|
197
264
|
}
|
|
198
|
-
|
|
265
|
+
else {
|
|
266
|
+
menuBarStatus.setImage(image);
|
|
267
|
+
}
|
|
268
|
+
menuBarStatus.setTitle('');
|
|
199
269
|
menuBarStatus.setToolTip(`${status.tooltip}\n点击循环切换需关注的服务`);
|
|
200
270
|
}
|
|
201
|
-
function constrainFloatingPosition(position) {
|
|
202
|
-
const
|
|
203
|
-
const height = 50;
|
|
271
|
+
function constrainFloatingPosition(position, width = FLOATING_WIDGET_WIDTHS[2]) {
|
|
272
|
+
const height = FLOATING_WIDGET_HEIGHT;
|
|
204
273
|
const display = position
|
|
205
274
|
? screen.getDisplayMatching({ x: position.x, y: position.y, width, height })
|
|
206
275
|
: screen.getPrimaryDisplay();
|
|
@@ -219,20 +288,21 @@ function createFloatingWidget(position) {
|
|
|
219
288
|
const window = new BrowserWindow({
|
|
220
289
|
title: 'Termdock Status',
|
|
221
290
|
type: 'panel',
|
|
222
|
-
width:
|
|
223
|
-
height:
|
|
291
|
+
width: FLOATING_WIDGET_WIDTHS[2],
|
|
292
|
+
height: FLOATING_WIDGET_HEIGHT,
|
|
224
293
|
x: bounds.x,
|
|
225
294
|
y: bounds.y,
|
|
226
295
|
show: false,
|
|
227
296
|
frame: false,
|
|
228
297
|
transparent: true,
|
|
229
298
|
backgroundColor: 'rgba(0, 0, 0, 0)',
|
|
230
|
-
vibrancy: '
|
|
299
|
+
vibrancy: 'popover',
|
|
231
300
|
visualEffectState: 'active',
|
|
232
301
|
resizable: false,
|
|
233
302
|
maximizable: false,
|
|
234
303
|
minimizable: false,
|
|
235
304
|
fullscreenable: false,
|
|
305
|
+
focusable: false,
|
|
236
306
|
skipTaskbar: true,
|
|
237
307
|
alwaysOnTop: true,
|
|
238
308
|
acceptFirstMouse: true,
|
|
@@ -246,7 +316,13 @@ function createFloatingWidget(position) {
|
|
|
246
316
|
},
|
|
247
317
|
});
|
|
248
318
|
window.setAlwaysOnTop(true, 'floating');
|
|
249
|
-
window.setVisibleOnAllWorkspaces(true, {
|
|
319
|
+
window.setVisibleOnAllWorkspaces(true, {
|
|
320
|
+
visibleOnFullScreen: true,
|
|
321
|
+
// Electron otherwise temporarily transforms the whole process into a UI
|
|
322
|
+
// element, which can hide the app menu and remove normal windows from App
|
|
323
|
+
// Expose while this panel exists.
|
|
324
|
+
skipTransformProcessType: true,
|
|
325
|
+
});
|
|
250
326
|
// Keep the auxiliary status panel out of App Expose / Mission Control so it
|
|
251
327
|
// cannot disrupt macOS's three-finger application-window gesture.
|
|
252
328
|
window.setHiddenInMissionControl(true);
|
|
@@ -255,7 +331,8 @@ function createFloatingWidget(position) {
|
|
|
255
331
|
if (constrainingMove || window.isDestroyed())
|
|
256
332
|
return;
|
|
257
333
|
const [x, y] = window.getPosition();
|
|
258
|
-
const
|
|
334
|
+
const [width] = window.getSize();
|
|
335
|
+
const constrained = constrainFloatingPosition({ x, y }, width);
|
|
259
336
|
if (constrained.x === x && constrained.y === y)
|
|
260
337
|
return;
|
|
261
338
|
constrainingMove = true;
|
|
@@ -285,6 +362,20 @@ function createFloatingWidget(position) {
|
|
|
285
362
|
void window.loadFile(rendererPath).then(() => window.showInactive());
|
|
286
363
|
return window;
|
|
287
364
|
}
|
|
365
|
+
function resizeFloatingWidget(metricCount) {
|
|
366
|
+
const window = floatingWidgetWindow;
|
|
367
|
+
if (!window || window.isDestroyed())
|
|
368
|
+
return;
|
|
369
|
+
const safeCount = Math.min(3, Math.max(1, Math.round(metricCount)));
|
|
370
|
+
const width = FLOATING_WIDGET_WIDTHS[safeCount - 1];
|
|
371
|
+
const [currentWidth] = window.getSize();
|
|
372
|
+
if (currentWidth === width)
|
|
373
|
+
return;
|
|
374
|
+
const [x, y] = window.getPosition();
|
|
375
|
+
window.setSize(width, FLOATING_WIDGET_HEIGHT, false);
|
|
376
|
+
const constrained = constrainFloatingPosition({ x: x + currentWidth - width, y }, width);
|
|
377
|
+
window.setPosition(constrained.x, constrained.y, false);
|
|
378
|
+
}
|
|
288
379
|
function refreshFloatingWidget(status) {
|
|
289
380
|
if (process.platform !== 'darwin' || !status.preferences.floatingWidgetEnabled) {
|
|
290
381
|
if (floatingWidgetWindow && !floatingWidgetWindow.isDestroyed())
|
|
@@ -300,7 +391,8 @@ function keepFloatingWidgetOnScreen() {
|
|
|
300
391
|
if (!floatingWidgetWindow || floatingWidgetWindow.isDestroyed())
|
|
301
392
|
return;
|
|
302
393
|
const [x, y] = floatingWidgetWindow.getPosition();
|
|
303
|
-
const
|
|
394
|
+
const [width] = floatingWidgetWindow.getSize();
|
|
395
|
+
const constrained = constrainFloatingPosition({ x, y }, width);
|
|
304
396
|
if (constrained.x !== x || constrained.y !== y) {
|
|
305
397
|
floatingWidgetWindow.setPosition(constrained.x, constrained.y, false);
|
|
306
398
|
}
|
|
@@ -379,6 +471,7 @@ function defaultConfig() {
|
|
|
379
471
|
version: 1,
|
|
380
472
|
connections: [],
|
|
381
473
|
lastConnectionUrl: null,
|
|
474
|
+
openConnectionUrls: [],
|
|
382
475
|
trustedCertificateAuthorities: [],
|
|
383
476
|
desktopPreferences: {
|
|
384
477
|
menuBarStatusEnabled: true,
|
|
@@ -517,13 +610,20 @@ function readDesktopConfig() {
|
|
|
517
610
|
const parsed = JSON.parse(fs.readFileSync(desktopConfigPath, 'utf8'));
|
|
518
611
|
if (parsed.version !== 1 || !Array.isArray(parsed.connections))
|
|
519
612
|
return defaultConfig();
|
|
613
|
+
const lastConnectionUrl = typeof parsed.lastConnectionUrl === 'string' ? parsed.lastConnectionUrl : null;
|
|
520
614
|
return {
|
|
521
615
|
version: 1,
|
|
522
616
|
connections: parsed.connections.filter((entry) => Boolean(entry)
|
|
523
617
|
&& typeof entry.id === 'string'
|
|
524
618
|
&& typeof entry.label === 'string'
|
|
525
619
|
&& typeof entry.url === 'string'),
|
|
526
|
-
lastConnectionUrl
|
|
620
|
+
lastConnectionUrl,
|
|
621
|
+
// Older desktop.json files only remembered the most recent window. Use
|
|
622
|
+
// that value once as their restore list, while preserving an explicit
|
|
623
|
+
// empty list when the user intentionally closed every service window.
|
|
624
|
+
openConnectionUrls: Array.isArray(parsed.openConnectionUrls)
|
|
625
|
+
? parsed.openConnectionUrls.filter((url) => typeof url === 'string')
|
|
626
|
+
: lastConnectionUrl ? [lastConnectionUrl] : [],
|
|
527
627
|
trustedCertificateAuthorities: Array.isArray(parsed.trustedCertificateAuthorities)
|
|
528
628
|
? parsed.trustedCertificateAuthorities.filter((entry) => Boolean(entry)
|
|
529
629
|
&& typeof entry.origin === 'string'
|
|
@@ -1057,7 +1157,7 @@ async function installCli() {
|
|
|
1057
1157
|
], { timeout: 120_000, maxBuffer: 256 * 1024 });
|
|
1058
1158
|
return snapshot();
|
|
1059
1159
|
}
|
|
1060
|
-
async function connectWindow(rawUrl) {
|
|
1160
|
+
async function connectWindow(rawUrl, options = {}) {
|
|
1061
1161
|
let probe = await probeServiceWithLocalNetworkPermission(rawUrl);
|
|
1062
1162
|
if (!probe.ok)
|
|
1063
1163
|
return probe;
|
|
@@ -1066,13 +1166,30 @@ async function connectWindow(rawUrl) {
|
|
|
1066
1166
|
const existingWindow = serviceWindows.get(key);
|
|
1067
1167
|
if (existingWindow && !existingWindow.isDestroyed()) {
|
|
1068
1168
|
const config = readDesktopConfig();
|
|
1069
|
-
|
|
1169
|
+
if (options.updateLastConnection !== false)
|
|
1170
|
+
config.lastConnectionUrl = probe.url;
|
|
1171
|
+
config.openConnectionUrls = [
|
|
1172
|
+
...config.openConnectionUrls.filter((url) => {
|
|
1173
|
+
try {
|
|
1174
|
+
return new URL(url).origin !== key;
|
|
1175
|
+
}
|
|
1176
|
+
catch {
|
|
1177
|
+
return false;
|
|
1178
|
+
}
|
|
1179
|
+
}),
|
|
1180
|
+
probe.url,
|
|
1181
|
+
];
|
|
1070
1182
|
const existing = config.connections.find((entry) => entry.url === probe.url);
|
|
1071
1183
|
if (existing)
|
|
1072
1184
|
existing.lastConnectedAt = Date.now();
|
|
1073
1185
|
writeDesktopConfig(config);
|
|
1074
|
-
|
|
1075
|
-
|
|
1186
|
+
if (options.focus !== false) {
|
|
1187
|
+
mainWindow?.hide();
|
|
1188
|
+
showAndFocusWindow(existingWindow);
|
|
1189
|
+
}
|
|
1190
|
+
else {
|
|
1191
|
+
existingWindow.showInactive();
|
|
1192
|
+
}
|
|
1076
1193
|
return probe;
|
|
1077
1194
|
}
|
|
1078
1195
|
const workspaceWindow = createDesktopWindow({ serviceOrigin: key, label: serviceLabel(probe.url) });
|
|
@@ -1082,13 +1199,34 @@ async function connectWindow(rawUrl) {
|
|
|
1082
1199
|
try {
|
|
1083
1200
|
await workspaceWindow.loadURL(probe.url);
|
|
1084
1201
|
const config = readDesktopConfig();
|
|
1085
|
-
|
|
1202
|
+
if (options.updateLastConnection !== false)
|
|
1203
|
+
config.lastConnectionUrl = probe.url;
|
|
1204
|
+
config.openConnectionUrls = [
|
|
1205
|
+
...config.openConnectionUrls.filter((url) => {
|
|
1206
|
+
try {
|
|
1207
|
+
return new URL(url).origin !== key;
|
|
1208
|
+
}
|
|
1209
|
+
catch {
|
|
1210
|
+
return false;
|
|
1211
|
+
}
|
|
1212
|
+
}),
|
|
1213
|
+
probe.url,
|
|
1214
|
+
];
|
|
1086
1215
|
const existing = config.connections.find((entry) => entry.url === probe.url);
|
|
1087
1216
|
if (existing)
|
|
1088
1217
|
existing.lastConnectedAt = Date.now();
|
|
1089
1218
|
writeDesktopConfig(config);
|
|
1090
|
-
|
|
1091
|
-
|
|
1219
|
+
restorableServiceWindows.add(workspaceWindow);
|
|
1220
|
+
if (options.focus !== false) {
|
|
1221
|
+
mainWindow?.hide();
|
|
1222
|
+
showAndFocusWindow(workspaceWindow);
|
|
1223
|
+
}
|
|
1224
|
+
else {
|
|
1225
|
+
// Restored workspaces must be real visible macOS windows so App Expose
|
|
1226
|
+
// can select them, but opening several at launch must not steal focus
|
|
1227
|
+
// from each other.
|
|
1228
|
+
workspaceWindow.showInactive();
|
|
1229
|
+
}
|
|
1092
1230
|
return probe;
|
|
1093
1231
|
}
|
|
1094
1232
|
catch (error) {
|
|
@@ -1291,6 +1429,19 @@ function createDesktopWindow(options) {
|
|
|
1291
1429
|
clearUnreadNotifications();
|
|
1292
1430
|
});
|
|
1293
1431
|
window.on('close', (event) => {
|
|
1432
|
+
const serviceOrigin = windowServiceOrigins.get(window);
|
|
1433
|
+
if (serviceOrigin && !isQuitting && restorableServiceWindows.has(window)) {
|
|
1434
|
+
const config = readDesktopConfig();
|
|
1435
|
+
config.openConnectionUrls = config.openConnectionUrls.filter((url) => {
|
|
1436
|
+
try {
|
|
1437
|
+
return new URL(url).origin !== serviceOrigin;
|
|
1438
|
+
}
|
|
1439
|
+
catch {
|
|
1440
|
+
return false;
|
|
1441
|
+
}
|
|
1442
|
+
});
|
|
1443
|
+
writeDesktopConfig(config);
|
|
1444
|
+
}
|
|
1294
1445
|
if (window === mainWindow && process.platform === 'darwin' && !isQuitting) {
|
|
1295
1446
|
event.preventDefault();
|
|
1296
1447
|
window.hide();
|
|
@@ -1390,6 +1541,12 @@ function installIpcHandlers() {
|
|
|
1390
1541
|
: 'attention';
|
|
1391
1542
|
focusNextService(normalized);
|
|
1392
1543
|
});
|
|
1544
|
+
ipcMain.handle('desktop:set-floating-metric-count', (event, count) => {
|
|
1545
|
+
const sourceWindow = BrowserWindow.fromWebContents(event.sender);
|
|
1546
|
+
if (sourceWindow !== floatingWidgetWindow || typeof count !== 'number' || !Number.isFinite(count))
|
|
1547
|
+
return;
|
|
1548
|
+
resizeFloatingWidget(count);
|
|
1549
|
+
});
|
|
1393
1550
|
ipcMain.handle('desktop:disable-floating-widget', async () => {
|
|
1394
1551
|
updateDesktopPreferences({ floatingWidgetEnabled: false });
|
|
1395
1552
|
return snapshot();
|
|
@@ -1681,11 +1838,27 @@ app.whenReady().then(async () => {
|
|
|
1681
1838
|
window.webContents.send('desktop:update-state-changed', state);
|
|
1682
1839
|
}
|
|
1683
1840
|
});
|
|
1684
|
-
const
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1841
|
+
const startupConfig = readDesktopConfig();
|
|
1842
|
+
const restoreUrls = startupConfig.openConnectionUrls;
|
|
1843
|
+
let restoredWindowCount = 0;
|
|
1844
|
+
for (const url of restoreUrls) {
|
|
1845
|
+
const probe = await connectWindow(url, { focus: false, updateLastConnection: false });
|
|
1846
|
+
if (probe.ok)
|
|
1847
|
+
restoredWindowCount += 1;
|
|
1848
|
+
}
|
|
1849
|
+
if (restoredWindowCount > 0) {
|
|
1850
|
+
let preferredOrigin = null;
|
|
1851
|
+
try {
|
|
1852
|
+
preferredOrigin = startupConfig.lastConnectionUrl
|
|
1853
|
+
? new URL(startupConfig.lastConnectionUrl).origin
|
|
1854
|
+
: null;
|
|
1855
|
+
}
|
|
1856
|
+
catch {
|
|
1857
|
+
// A malformed legacy value must not prevent the other saved windows
|
|
1858
|
+
// from being restored.
|
|
1859
|
+
}
|
|
1860
|
+
const preferredWindow = preferredOrigin ? serviceWindows.get(preferredOrigin) : null;
|
|
1861
|
+
showAndFocusWindow(preferredWindow ?? [...serviceWindows.values()][0]);
|
|
1689
1862
|
}
|
|
1690
1863
|
else {
|
|
1691
1864
|
await showConnectionCenter();
|