@dimina-kit/devtools 0.3.2-dev.20260524104239 → 0.3.2-dev.20260601115343
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 +20 -7
- package/dist/main/api.d.ts +1 -1
- package/dist/main/app/app.js +30 -2
- package/dist/main/app/bootstrap.d.ts +9 -0
- package/dist/main/app/bootstrap.js +28 -1
- package/dist/main/index.bundle.js +1468 -496
- package/dist/main/ipc/project-fs.d.ts +57 -0
- package/dist/main/ipc/project-fs.js +448 -0
- package/dist/main/ipc/simulator-module.js +2 -0
- package/dist/main/ipc/views.d.ts +19 -0
- package/dist/main/ipc/views.js +27 -0
- package/dist/main/services/layout/index.d.ts +1 -1
- package/dist/main/services/layout/index.js +3 -3
- package/dist/main/services/simulator-temp-files/disk.d.ts +55 -0
- package/dist/main/services/simulator-temp-files/disk.js +215 -0
- package/dist/main/services/simulator-temp-files/fs-channels.d.ts +69 -0
- package/dist/main/services/simulator-temp-files/fs-channels.js +108 -0
- package/dist/main/services/simulator-temp-files/index.d.ts +33 -0
- package/dist/main/services/simulator-temp-files/index.js +160 -0
- package/dist/main/services/simulator-temp-files/request-handler.d.ts +30 -0
- package/dist/main/services/simulator-temp-files/request-handler.js +178 -0
- package/dist/main/services/simulator-temp-files/resolver.d.ts +22 -0
- package/dist/main/services/simulator-temp-files/resolver.js +22 -0
- package/dist/main/services/simulator-temp-files/store.d.ts +14 -0
- package/dist/main/services/simulator-temp-files/store.js +24 -0
- package/dist/main/services/toolbar/toolbar-store.d.ts +3 -3
- package/dist/main/services/toolbar/toolbar-store.js +18 -1
- package/dist/main/services/update/update-manager.d.ts +10 -0
- package/dist/main/services/update/update-manager.js +16 -3
- package/dist/main/services/views/view-manager.d.ts +17 -4
- package/dist/main/services/views/view-manager.js +71 -14
- package/dist/main/services/workbench-context.d.ts +6 -1
- package/dist/main/services/workspace/workspace-service.d.ts +8 -0
- package/dist/main/services/workspace/workspace-service.js +22 -4
- package/dist/main/utils/ipc-registry.d.ts +10 -1
- package/dist/main/utils/ipc-registry.js +43 -0
- package/dist/preload/runtime/temp-files.d.ts +8 -0
- package/dist/preload/runtime/temp-files.js +34 -0
- package/dist/preload/shared/expose.js +2 -2
- package/dist/preload/windows/main.cjs +4 -0
- package/dist/preload/windows/main.cjs.map +2 -2
- package/dist/preload/windows/simulator.js +40 -8
- package/dist/renderer/assets/abap-DiwvWnMr.js +2 -0
- package/dist/renderer/assets/apex-CmtZjKlf.js +2 -0
- package/dist/renderer/assets/azcli-DL2My_i-.js +2 -0
- package/dist/renderer/assets/bat-B-nC98wG.js +2 -0
- package/dist/renderer/assets/bicep-Ju5MwOgh.js +3 -0
- package/dist/renderer/assets/cameligo-8Eu1TyBr.js +2 -0
- package/dist/renderer/assets/chunk-DPI0Y_ll.js +1 -0
- package/dist/renderer/assets/clojure-u-RpMkH3.js +2 -0
- package/dist/renderer/assets/codicon-ngg6Pgfi.ttf +0 -0
- package/dist/renderer/assets/coffee-CdA7bbTe.js +2 -0
- package/dist/renderer/assets/constants-CLAQX6VZ.js +2 -0
- package/dist/renderer/assets/cpp-CzNFP8ks.js +2 -0
- package/dist/renderer/assets/csharp-j1LThmcE.js +2 -0
- package/dist/renderer/assets/csp-CLRC61y6.js +2 -0
- package/dist/renderer/assets/css-r6rC_7P2.js +4 -0
- package/dist/renderer/assets/css.worker-DyDu5ynT.js +90 -0
- package/dist/renderer/assets/cssMode-BCBgxrnA.js +2 -0
- package/dist/renderer/assets/cypher-CW08XVUh.js +2 -0
- package/dist/renderer/assets/dart-Cs9aL5T_.js +2 -0
- package/dist/renderer/assets/dockerfile-BWM0M184.js +2 -0
- package/dist/renderer/assets/ecl-MJJuer5P.js +2 -0
- package/dist/renderer/assets/editor-CWI9TnMM.css +1 -0
- package/dist/renderer/assets/editor.api2-BTaYmRq7.js +873 -0
- package/dist/renderer/assets/editor.worker-Dpt_xPrs.js +27 -0
- package/dist/renderer/assets/elixir-D2AIuXqn.js +2 -0
- package/dist/renderer/assets/flow9-B2H24giC.js +2 -0
- package/dist/renderer/assets/freemarker2-DGDGzimW.js +4 -0
- package/dist/renderer/assets/fsharp-CMk2OIJN.js +2 -0
- package/dist/renderer/assets/go-BrMkuJg0.js +2 -0
- package/dist/renderer/assets/graphql-PSR1UKGv.js +2 -0
- package/dist/renderer/assets/handlebars-bOHhw3EG.js +2 -0
- package/dist/renderer/assets/hcl-DAQrbDOW.js +2 -0
- package/dist/renderer/assets/html-BMyuX0_c.js +2 -0
- package/dist/renderer/assets/html.worker-IWNXhnIC.js +503 -0
- package/dist/renderer/assets/htmlMode-DR4kXJ7l.js +2 -0
- package/dist/renderer/assets/index-BDMxXUfn.js +50 -0
- package/dist/renderer/assets/ini-0TG5BxW0.js +2 -0
- package/dist/renderer/assets/input-CCYS8Ave.js +2 -0
- package/dist/renderer/assets/{ipc-transport-BzPzxPwf.js → ipc-transport-Bn4KXvhu.js} +5 -5
- package/dist/renderer/assets/ipc-transport-Cu7tubyg.css +1 -0
- package/dist/renderer/assets/java-rgorz17v.js +2 -0
- package/dist/renderer/assets/javascript-Bu-tl1P7.js +2 -0
- package/dist/renderer/assets/json.worker-c4h5s80L.js +59 -0
- package/dist/renderer/assets/jsonMode-MQV_ffLR.js +8 -0
- package/dist/renderer/assets/julia-C8VMdHm8.js +2 -0
- package/dist/renderer/assets/kotlin-AhhC4Qhm.js +2 -0
- package/dist/renderer/assets/less-Cgca25AP.js +3 -0
- package/dist/renderer/assets/lexon-D0GHdBaw.js +2 -0
- package/dist/renderer/assets/liquid-ClkfOvhB.js +2 -0
- package/dist/renderer/assets/lspLanguageFeatures-Bke_4Ie9.js +5 -0
- package/dist/renderer/assets/lua-DmRsNG-P.js +2 -0
- package/dist/renderer/assets/m3-BgL5dNKT.js +2 -0
- package/dist/renderer/assets/markdown-BuJfycGS.js +2 -0
- package/dist/renderer/assets/mdx-RZdUskPu.js +2 -0
- package/dist/renderer/assets/mips-C9m_93PR.js +2 -0
- package/dist/renderer/assets/monaco.contribution-B7rUMaEv.js +3 -0
- package/dist/renderer/assets/msdax-CpFHC9OI.js +2 -0
- package/dist/renderer/assets/mysql-qFvltsqN.js +2 -0
- package/dist/renderer/assets/objective-c-Bnmr858J.js +2 -0
- package/dist/renderer/assets/pascal-WP0_D5AO.js +2 -0
- package/dist/renderer/assets/pascaligo-Blom4Rij.js +2 -0
- package/dist/renderer/assets/perl-B-vk8g64.js +2 -0
- package/dist/renderer/assets/pgsql-Cgvz6v67.js +2 -0
- package/dist/renderer/assets/php-8a3Lrw9m.js +2 -0
- package/dist/renderer/assets/pla-DuFqEZ8V.js +2 -0
- package/dist/renderer/assets/popover-CH4rwpdt.js +2 -0
- package/dist/renderer/assets/postiats-DkLtSgkp.js +2 -0
- package/dist/renderer/assets/powerquery-BJ1aNepW.js +2 -0
- package/dist/renderer/assets/powershell-rE98k687.js +2 -0
- package/dist/renderer/assets/protobuf-CUheFacr.js +3 -0
- package/dist/renderer/assets/pug-LDcAMD8w.js +2 -0
- package/dist/renderer/assets/python-Cobrm_-8.js +2 -0
- package/dist/renderer/assets/qsharp-DUKSQoR1.js +2 -0
- package/dist/renderer/assets/r-D-QApv87.js +2 -0
- package/dist/renderer/assets/razor-BEUIQFNJ.js +2 -0
- package/dist/renderer/assets/redis-SXdDyWR9.js +2 -0
- package/dist/renderer/assets/redshift-Y6lsCryn.js +2 -0
- package/dist/renderer/assets/restructuredtext-edObr9a8.js +2 -0
- package/dist/renderer/assets/ruby-CNnUfF-8.js +2 -0
- package/dist/renderer/assets/rust-IHUZWzBr.js +2 -0
- package/dist/renderer/assets/sb-DrUvY44N.js +2 -0
- package/dist/renderer/assets/scala-B4hbXGLM.js +2 -0
- package/dist/renderer/assets/scheme-BGrd12j3.js +2 -0
- package/dist/renderer/assets/scss-x5G1ES4U.js +4 -0
- package/dist/renderer/assets/select-Ct4-sijm.js +2 -0
- package/dist/renderer/assets/settings-CMTB-t0x.js +2 -0
- package/dist/renderer/assets/{settings-api-BXCw9vOZ.js → settings-api-Csdngf9Z.js} +2 -2
- package/dist/renderer/assets/shell-DOehe2Y8.js +2 -0
- package/dist/renderer/assets/solidity-BeRvcwWV.js +2 -0
- package/dist/renderer/assets/sophia-DZbkUNjy.js +2 -0
- package/dist/renderer/assets/sparql-B7_oi5-h.js +2 -0
- package/dist/renderer/assets/sql-CTlsFWVE.js +2 -0
- package/dist/renderer/assets/st-DJVEJdPE.js +2 -0
- package/dist/renderer/assets/swift-CwhT3fYa.js +2 -0
- package/dist/renderer/assets/systemverilog-BQN63pkN.js +2 -0
- package/dist/renderer/assets/tcl-DqwfpskA.js +2 -0
- package/dist/renderer/assets/ts.worker-BwM5Ha3L.js +67720 -0
- package/dist/renderer/assets/tsMode-BHa7dl9v.js +12 -0
- package/dist/renderer/assets/twig-BiyenUgc.js +2 -0
- package/dist/renderer/assets/typescript-15aT0Yj8.js +2 -0
- package/dist/renderer/assets/typespec-CWOJribt.js +2 -0
- package/dist/renderer/assets/vb-Cq5F87m3.js +2 -0
- package/dist/renderer/assets/wgsl-BAvW2lVr.js +299 -0
- package/dist/renderer/assets/workbenchSettings-CODFCzOg.js +8 -0
- package/dist/renderer/assets/workers-CRBS0W-v.js +2 -0
- package/dist/renderer/assets/xml-bR2Zh-HV.js +2 -0
- package/dist/renderer/assets/yaml-Bdk54Qrb.js +2 -0
- package/dist/renderer/entries/main/index.html +29 -6
- package/dist/renderer/entries/popover/index.html +7 -6
- package/dist/renderer/entries/settings/index.html +6 -5
- package/dist/renderer/entries/workbench-settings/index.html +6 -5
- package/dist/shared/constants.d.ts +0 -4
- package/dist/shared/constants.js +0 -4
- package/dist/shared/ipc-channels.d.ts +24 -0
- package/dist/shared/ipc-channels.js +37 -0
- package/dist/shared/ipc-schemas.d.ts +6 -0
- package/dist/shared/ipc-schemas.js +15 -0
- package/dist/shared/types.d.ts +37 -0
- package/dist/shared/vpath.d.ts +42 -0
- package/dist/shared/vpath.js +105 -0
- package/dist/simulator/assets/simulator-B4e3c6_O.js +9 -0
- package/dist/simulator/simulator.html +1 -1
- package/package.json +5 -4
- package/dist/renderer/assets/constants-DI382mTP.js +0 -2
- package/dist/renderer/assets/index-4lkyF644.js +0 -46
- package/dist/renderer/assets/input-D4byFOLS.js +0 -2
- package/dist/renderer/assets/ipc-transport-DqbRdZNO.css +0 -1
- package/dist/renderer/assets/popover-cxKrg_os.js +0 -2
- package/dist/renderer/assets/select-BgSkkdHW.js +0 -2
- package/dist/renderer/assets/settings-oD2K1jdT.js +0 -2
- package/dist/renderer/assets/workbenchSettings-CTNBpTft.js +0 -8
- package/dist/simulator/assets/simulator-DDRBJ6Wa.js +0 -9
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure dispatcher for `difile://` URL requests.
|
|
3
|
+
*
|
|
4
|
+
* Spec: `packages/devtools/docs/file-system.md` §4.3.
|
|
5
|
+
*
|
|
6
|
+
* The shipping implementation in `index.ts` registers a thin
|
|
7
|
+
* `simSession.protocol.handle('difile')` wrapper that delegates here; the race
|
|
8
|
+
* waiter on `_tmp/*` lives in `index.ts` because it owns the IPC lifecycle.
|
|
9
|
+
* For unit tests we assume the bytes are already in the store.
|
|
10
|
+
*
|
|
11
|
+
* Response shape:
|
|
12
|
+
* - 200: full body, with Content-Type, Cache-Control (immutable), ETag
|
|
13
|
+
* - 206: range slice, plus Content-Range
|
|
14
|
+
* - 304: empty body when `If-None-Match` matches the on-disk ETag. Per RFC
|
|
15
|
+
* 9110 §13.1.2 If-None-Match wins over Range.
|
|
16
|
+
* - 404: anything `resolveVPath` rejects, plus disk-side ENOENT and any
|
|
17
|
+
* other I/O error. The protocol handler in `index.ts` translates this
|
|
18
|
+
* into the renderer's network failure surface; we deliberately do not
|
|
19
|
+
* leak errno strings.
|
|
20
|
+
*/
|
|
21
|
+
import { resolveVPath } from '../../../shared/vpath.js';
|
|
22
|
+
import { readDiskFile } from './disk.js';
|
|
23
|
+
const CACHE_CONTROL = 'public, max-age=31536000, immutable';
|
|
24
|
+
function getHeader(headers, name) {
|
|
25
|
+
if (!headers)
|
|
26
|
+
return undefined;
|
|
27
|
+
const lc = name.toLowerCase();
|
|
28
|
+
for (const k of Object.keys(headers)) {
|
|
29
|
+
if (k.toLowerCase() === lc)
|
|
30
|
+
return headers[k];
|
|
31
|
+
}
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Weak ETag comparison per RFC 9110 §8.8.3.2: strip the optional `W/` prefix
|
|
36
|
+
* from each side before comparing. Both our generated ETags and conditional
|
|
37
|
+
* headers may or may not include the weak prefix; the comparison must accept
|
|
38
|
+
* `W/"abc"` and `"abc"` as equivalent.
|
|
39
|
+
*/
|
|
40
|
+
function etagsMatch(a, b) {
|
|
41
|
+
if (!a)
|
|
42
|
+
return false;
|
|
43
|
+
const stripped = (s) => (s.startsWith('W/') ? s.slice(2) : s);
|
|
44
|
+
return stripped(a.trim()) === stripped(b);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Parse a `Range: bytes=<start>-<end>` header into an inclusive `{start,end}`.
|
|
48
|
+
* Returns `null` if absent or malformed (callers should treat that as "no
|
|
49
|
+
* range" — a full 200 response, not a 416). `totalSize` clamps the end.
|
|
50
|
+
*/
|
|
51
|
+
function parseRange(header, totalSize) {
|
|
52
|
+
if (!header)
|
|
53
|
+
return null;
|
|
54
|
+
const m = /^bytes=(\d*)-(\d*)$/.exec(header.trim());
|
|
55
|
+
if (!m)
|
|
56
|
+
return null;
|
|
57
|
+
const startStr = m[1];
|
|
58
|
+
const endStr = m[2];
|
|
59
|
+
if (startStr === '' && endStr === '')
|
|
60
|
+
return null;
|
|
61
|
+
if (startStr === '') {
|
|
62
|
+
// Suffix range: last N bytes.
|
|
63
|
+
const n = Number(endStr);
|
|
64
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
65
|
+
return null;
|
|
66
|
+
const start = Math.max(0, totalSize - n);
|
|
67
|
+
return { start, end: totalSize - 1 };
|
|
68
|
+
}
|
|
69
|
+
const start = Number(startStr);
|
|
70
|
+
if (!Number.isFinite(start) || start < 0)
|
|
71
|
+
return null;
|
|
72
|
+
const end = endStr === '' ? totalSize - 1 : Number(endStr);
|
|
73
|
+
if (!Number.isFinite(end))
|
|
74
|
+
return null;
|
|
75
|
+
return { start, end };
|
|
76
|
+
}
|
|
77
|
+
function notFound() {
|
|
78
|
+
return new Response(null, { status: 404 });
|
|
79
|
+
}
|
|
80
|
+
function notModified(etag) {
|
|
81
|
+
return new Response(null, {
|
|
82
|
+
status: 304,
|
|
83
|
+
headers: {
|
|
84
|
+
ETag: etag,
|
|
85
|
+
'Cache-Control': CACHE_CONTROL,
|
|
86
|
+
'Access-Control-Allow-Origin': '*',
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
function bufferToBody(bytes) {
|
|
91
|
+
// Copy into a fresh ArrayBuffer so the result is definitely typed as
|
|
92
|
+
// `ArrayBuffer` (not `ArrayBufferLike` / `SharedArrayBuffer`) and is
|
|
93
|
+
// safe to hand to Response without aliasing the Node Buffer pool.
|
|
94
|
+
const out = new ArrayBuffer(bytes.byteLength);
|
|
95
|
+
new Uint8Array(out).set(bytes);
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
function tempBody(bytes, mime) {
|
|
99
|
+
return new Response(bufferToBody(bytes), {
|
|
100
|
+
status: 200,
|
|
101
|
+
headers: {
|
|
102
|
+
'Content-Type': mime,
|
|
103
|
+
'Cache-Control': CACHE_CONTROL,
|
|
104
|
+
'Access-Control-Allow-Origin': '*',
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
export async function handleDifileRequest(ctx, req) {
|
|
109
|
+
const v = resolveVPath(req.url);
|
|
110
|
+
if (!v)
|
|
111
|
+
return notFound();
|
|
112
|
+
if (v.kind === 'tmp') {
|
|
113
|
+
const record = ctx.tempStore.get(req.url);
|
|
114
|
+
if (!record)
|
|
115
|
+
return notFound();
|
|
116
|
+
return tempBody(record.bytes, record.mime || 'application/octet-stream');
|
|
117
|
+
}
|
|
118
|
+
if (!v.realPath)
|
|
119
|
+
return notFound();
|
|
120
|
+
// Disk-backed: _store or usr.
|
|
121
|
+
try {
|
|
122
|
+
// Probe first to learn the ETag/size so If-None-Match can short-circuit
|
|
123
|
+
// without reading the body. The probe also doubles as our existence /
|
|
124
|
+
// permission check, so anything that throws here (ENOENT, EACCES, ...)
|
|
125
|
+
// surfaces as a 404.
|
|
126
|
+
const probe = await readDiskFile(v.realPath);
|
|
127
|
+
const etag = probe.etag;
|
|
128
|
+
const totalSize = probe.totalSize;
|
|
129
|
+
const mime = probe.mime;
|
|
130
|
+
const ifNoneMatch = getHeader(req.headers, 'If-None-Match');
|
|
131
|
+
if (etagsMatch(ifNoneMatch, etag)) {
|
|
132
|
+
return notModified(etag);
|
|
133
|
+
}
|
|
134
|
+
const rangeHeader = getHeader(req.headers, 'Range');
|
|
135
|
+
const range = parseRange(rangeHeader, totalSize);
|
|
136
|
+
if (range) {
|
|
137
|
+
// Range out of bounds → 416 (Range Not Satisfiable) per RFC 9110 §15.4.
|
|
138
|
+
// Distinct from 404 so callers can tell "wrong file" from "wrong slice".
|
|
139
|
+
if (range.start < 0 || range.start >= totalSize || range.end < range.start) {
|
|
140
|
+
return new Response(null, {
|
|
141
|
+
status: 416,
|
|
142
|
+
headers: {
|
|
143
|
+
'Content-Range': `bytes */${totalSize}`,
|
|
144
|
+
'Access-Control-Allow-Origin': '*',
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
const clampedEnd = Math.min(range.end, totalSize - 1);
|
|
149
|
+
const sliced = await readDiskFile(v.realPath, { range: { start: range.start, end: clampedEnd } });
|
|
150
|
+
const { bytes } = sliced;
|
|
151
|
+
const lastByte = range.start + bytes.length - 1;
|
|
152
|
+
return new Response(bufferToBody(bytes), {
|
|
153
|
+
status: 206,
|
|
154
|
+
headers: {
|
|
155
|
+
'Content-Type': mime,
|
|
156
|
+
'Content-Length': String(bytes.length),
|
|
157
|
+
'Content-Range': `bytes ${range.start}-${lastByte}/${totalSize}`,
|
|
158
|
+
'Cache-Control': CACHE_CONTROL,
|
|
159
|
+
ETag: etag,
|
|
160
|
+
'Access-Control-Allow-Origin': '*',
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
return new Response(bufferToBody(probe.bytes), {
|
|
165
|
+
status: 200,
|
|
166
|
+
headers: {
|
|
167
|
+
'Content-Type': mime,
|
|
168
|
+
'Cache-Control': CACHE_CONTROL,
|
|
169
|
+
ETag: etag,
|
|
170
|
+
'Access-Control-Allow-Origin': '*',
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return notFound();
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
//# sourceMappingURL=request-handler.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure lookup helper backing the main-process `difile://_tmp/*` protocol
|
|
3
|
+
* handler. Receives the shared {@link TempFileStore} that the
|
|
4
|
+
* `simulator:temp-file:*` IPC channels populate from the renderer.
|
|
5
|
+
*
|
|
6
|
+
* The full URL (including scheme + host) is used as the Map key — the resolver
|
|
7
|
+
* never re-parses or normalises it, so any URL with a wrong scheme or host
|
|
8
|
+
* misses regardless of suffix similarity.
|
|
9
|
+
*/
|
|
10
|
+
export interface TempFileRecord {
|
|
11
|
+
bytes: Buffer;
|
|
12
|
+
mime: string;
|
|
13
|
+
}
|
|
14
|
+
export type TempFileStore = Map<string, TempFileRecord>;
|
|
15
|
+
export declare function resolveTempFile(store: TempFileStore, url: string): {
|
|
16
|
+
status: 200;
|
|
17
|
+
bytes: Buffer;
|
|
18
|
+
mime: string;
|
|
19
|
+
} | {
|
|
20
|
+
status: 404;
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=resolver.d.ts.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure lookup helper backing the main-process `difile://_tmp/*` protocol
|
|
3
|
+
* handler. Receives the shared {@link TempFileStore} that the
|
|
4
|
+
* `simulator:temp-file:*` IPC channels populate from the renderer.
|
|
5
|
+
*
|
|
6
|
+
* The full URL (including scheme + host) is used as the Map key — the resolver
|
|
7
|
+
* never re-parses or normalises it, so any URL with a wrong scheme or host
|
|
8
|
+
* misses regardless of suffix similarity.
|
|
9
|
+
*/
|
|
10
|
+
export function resolveTempFile(store, url) {
|
|
11
|
+
if (!url.startsWith('difile://_tmp/'))
|
|
12
|
+
return { status: 404 };
|
|
13
|
+
const record = store.get(url);
|
|
14
|
+
if (!record)
|
|
15
|
+
return { status: 404 };
|
|
16
|
+
return {
|
|
17
|
+
status: 200,
|
|
18
|
+
bytes: record.bytes,
|
|
19
|
+
mime: record.mime || 'application/octet-stream',
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=resolver.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mutators for the shared {@link TempFileStore}. The renderer-side bridge
|
|
3
|
+
* forwards every `createTempFilePath` / `registerTempFilePath` /
|
|
4
|
+
* `revokeTempFilePath` / `revokeAllTempFilePaths` call over IPC, and these
|
|
5
|
+
* helpers translate the payloads into Map writes.
|
|
6
|
+
*
|
|
7
|
+
* ArrayBuffer inputs are normalised to Buffer at the boundary so the
|
|
8
|
+
* resolver hot path can return the bytes directly without re-wrapping.
|
|
9
|
+
*/
|
|
10
|
+
import type { TempFileStore } from './resolver.js';
|
|
11
|
+
export declare function registerTempFile(store: TempFileStore, path: string, mime: string, bytes: ArrayBuffer | Buffer): void;
|
|
12
|
+
export declare function revokeTempFile(store: TempFileStore, path: string): void;
|
|
13
|
+
export declare function revokeAllTempFiles(store: TempFileStore): void;
|
|
14
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mutators for the shared {@link TempFileStore}. The renderer-side bridge
|
|
3
|
+
* forwards every `createTempFilePath` / `registerTempFilePath` /
|
|
4
|
+
* `revokeTempFilePath` / `revokeAllTempFilePaths` call over IPC, and these
|
|
5
|
+
* helpers translate the payloads into Map writes.
|
|
6
|
+
*
|
|
7
|
+
* ArrayBuffer inputs are normalised to Buffer at the boundary so the
|
|
8
|
+
* resolver hot path can return the bytes directly without re-wrapping.
|
|
9
|
+
*/
|
|
10
|
+
export function registerTempFile(store, path, mime, bytes) {
|
|
11
|
+
const buf = Buffer.isBuffer(bytes) ? bytes : Buffer.from(new Uint8Array(bytes));
|
|
12
|
+
// delete-then-set so an existing entry's insertion order is refreshed.
|
|
13
|
+
// Without this, a FIFO cap eviction over `store.keys()` could drop a
|
|
14
|
+
// recently re-written path.
|
|
15
|
+
store.delete(path);
|
|
16
|
+
store.set(path, { bytes: buf, mime });
|
|
17
|
+
}
|
|
18
|
+
export function revokeTempFile(store, path) {
|
|
19
|
+
store.delete(path);
|
|
20
|
+
}
|
|
21
|
+
export function revokeAllTempFiles(store) {
|
|
22
|
+
store.clear();
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -4,8 +4,8 @@ import type { ToolbarAction, ToolbarActionInput } from '../../../shared/types.js
|
|
|
4
4
|
*
|
|
5
5
|
* The toolbar is modelled as "one table the host recomputes from its current
|
|
6
6
|
* state": `set()` replaces the whole table atomically. The non-serialisable
|
|
7
|
-
* `handler` is kept main-process side; `list()` projects
|
|
8
|
-
*
|
|
7
|
+
* `handler` is kept main-process side; `list()` projects display metadata for
|
|
8
|
+
* the IPC boundary, and `getHandler()` resolves a handler by id.
|
|
9
9
|
*/
|
|
10
10
|
export interface ToolbarStore {
|
|
11
11
|
/**
|
|
@@ -14,7 +14,7 @@ export interface ToolbarStore {
|
|
|
14
14
|
* a rejected batch leaves the previous table intact.
|
|
15
15
|
*/
|
|
16
16
|
set(actions: ToolbarActionInput[]): void;
|
|
17
|
-
/** Project the current table to
|
|
17
|
+
/** Project the current table to serialisable display metadata — no `handler` leaks out. */
|
|
18
18
|
list(): ToolbarAction[];
|
|
19
19
|
/** Resolve the handler stored under `id`, or `undefined` if unknown. */
|
|
20
20
|
getHandler(id: string): (() => void | Promise<void>) | undefined;
|
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
function toPublicAction(action) {
|
|
2
|
+
const projected = {
|
|
3
|
+
id: action.id,
|
|
4
|
+
label: action.label,
|
|
5
|
+
};
|
|
6
|
+
if (action.kind)
|
|
7
|
+
projected.kind = action.kind;
|
|
8
|
+
if (action.placement)
|
|
9
|
+
projected.placement = action.placement;
|
|
10
|
+
if (action.icon)
|
|
11
|
+
projected.icon = action.icon;
|
|
12
|
+
if (action.displayInitial)
|
|
13
|
+
projected.displayInitial = action.displayInitial;
|
|
14
|
+
if (action.avatarUrl)
|
|
15
|
+
projected.avatarUrl = action.avatarUrl;
|
|
16
|
+
return projected;
|
|
17
|
+
}
|
|
1
18
|
export function createToolbarStore() {
|
|
2
19
|
let actions = [];
|
|
3
20
|
return {
|
|
@@ -13,7 +30,7 @@ export function createToolbarStore() {
|
|
|
13
30
|
actions = next.slice();
|
|
14
31
|
},
|
|
15
32
|
list() {
|
|
16
|
-
return actions.map(
|
|
33
|
+
return actions.map(toPublicAction);
|
|
17
34
|
},
|
|
18
35
|
getHandler(id) {
|
|
19
36
|
return actions.find((a) => a.id === id)?.handler;
|
|
@@ -24,6 +24,16 @@ export declare class UpdateManager {
|
|
|
24
24
|
private getCurrentVersion;
|
|
25
25
|
private latestUpdate;
|
|
26
26
|
private downloadedPath;
|
|
27
|
+
/**
|
|
28
|
+
* Whether this instance has already emitted `Available` once. The periodic
|
|
29
|
+
* check fires forever (interval default 1h), and downstream UI (qdmp shell
|
|
30
|
+
* renders a native toast / Notification) is not idempotent — every event
|
|
31
|
+
* stacks. We dedupe at the source: at most one Available per instance
|
|
32
|
+
* lifetime, regardless of how the upstream `info.version` shifts. The user
|
|
33
|
+
* already knows "an update exists"; tracking minor version bumps mid-
|
|
34
|
+
* session is not worth duplicating the toast.
|
|
35
|
+
*/
|
|
36
|
+
private hasNotified;
|
|
27
37
|
private timer;
|
|
28
38
|
private initialTimer;
|
|
29
39
|
private ipc;
|
|
@@ -7,6 +7,16 @@ export class UpdateManager {
|
|
|
7
7
|
getCurrentVersion;
|
|
8
8
|
latestUpdate = null;
|
|
9
9
|
downloadedPath = null;
|
|
10
|
+
/**
|
|
11
|
+
* Whether this instance has already emitted `Available` once. The periodic
|
|
12
|
+
* check fires forever (interval default 1h), and downstream UI (qdmp shell
|
|
13
|
+
* renders a native toast / Notification) is not idempotent — every event
|
|
14
|
+
* stacks. We dedupe at the source: at most one Available per instance
|
|
15
|
+
* lifetime, regardless of how the upstream `info.version` shifts. The user
|
|
16
|
+
* already knows "an update exists"; tracking minor version bumps mid-
|
|
17
|
+
* session is not worth duplicating the toast.
|
|
18
|
+
*/
|
|
19
|
+
hasNotified = false;
|
|
10
20
|
timer = null;
|
|
11
21
|
initialTimer = null;
|
|
12
22
|
ipc;
|
|
@@ -85,9 +95,12 @@ export class UpdateManager {
|
|
|
85
95
|
}
|
|
86
96
|
async checkAndNotify() {
|
|
87
97
|
const result = await this.check();
|
|
88
|
-
if (result.hasUpdate
|
|
89
|
-
|
|
90
|
-
|
|
98
|
+
if (!result.hasUpdate || !result.info)
|
|
99
|
+
return;
|
|
100
|
+
if (this.hasNotified)
|
|
101
|
+
return;
|
|
102
|
+
this.hasNotified = true;
|
|
103
|
+
this.mainWindow.webContents.send(UpdateChannel.Available, result.info);
|
|
91
104
|
}
|
|
92
105
|
}
|
|
93
106
|
//# sourceMappingURL=update-manager.js.map
|
|
@@ -20,13 +20,14 @@ export interface ViewManagerContext {
|
|
|
20
20
|
* Unified lifecycle manager for Electron WebContentsView overlays.
|
|
21
21
|
*
|
|
22
22
|
* Owns creation / attachment / detachment / positioning / destruction of
|
|
23
|
-
* every overlay view hung off the main window's contentView (simulator
|
|
24
|
-
* settings, popover). All `new WebContentsView`, `addChildView`,
|
|
23
|
+
* every overlay view hung off the main window's contentView (simulator
|
|
24
|
+
* DevTools, settings, popover). All `new WebContentsView`, `addChildView`,
|
|
25
25
|
* `removeChildView`, `webContents.destroy()` and overlay `setBounds` calls
|
|
26
26
|
* should live here — IPC handlers just call into the manager.
|
|
27
27
|
*
|
|
28
|
-
* The
|
|
29
|
-
*
|
|
28
|
+
* The code editor is NOT an overlay: it renders as an in-renderer
|
|
29
|
+
* `<MonacoEditor/>` React component inside the main window. The main
|
|
30
|
+
* window's own renderer is the content root and is not managed here.
|
|
30
31
|
*/
|
|
31
32
|
export interface ViewManager {
|
|
32
33
|
/**
|
|
@@ -78,6 +79,18 @@ export interface ViewManager {
|
|
|
78
79
|
resize(simWidth: number): void;
|
|
79
80
|
/** Show or hide the simulator overlay based on visibility flag. */
|
|
80
81
|
setVisible(visible: boolean, simWidth: number): void;
|
|
82
|
+
/**
|
|
83
|
+
* Apply a renderer-measured rectangle to the simulator's Chromium
|
|
84
|
+
* DevTools overlay view. `{ width: 0, height: 0 }` is treated as "hide" —
|
|
85
|
+
* the view is removed from the contentView but its WebContents is kept
|
|
86
|
+
* alive so re-showing it doesn't re-pay the DevTools bootstrap.
|
|
87
|
+
*/
|
|
88
|
+
setSimulatorDevtoolsBounds(bounds: {
|
|
89
|
+
x: number;
|
|
90
|
+
y: number;
|
|
91
|
+
width: number;
|
|
92
|
+
height: number;
|
|
93
|
+
}): void;
|
|
81
94
|
}
|
|
82
95
|
/**
|
|
83
96
|
* Build a ViewManager bound to the given context. The returned object is the
|
|
@@ -23,6 +23,11 @@ export function createViewManager(ctx) {
|
|
|
23
23
|
let popoverView = null;
|
|
24
24
|
let lastSimWidth = 375;
|
|
25
25
|
let simulatorWebContentsId = null;
|
|
26
|
+
// Renderer-driven overlay bounds for the simulator DevTools view. When
|
|
27
|
+
// non-null this takes precedence over the legacy layout computed from the
|
|
28
|
+
// window content size. A zero-area rectangle means "hide" — the overlay is
|
|
29
|
+
// removed from the contentView but its WebContents stays alive.
|
|
30
|
+
let simulatorBoundsOverride = null;
|
|
26
31
|
// ── Internal helpers ────────────────────────────────────────────────────
|
|
27
32
|
function destroyViewInternal(view) {
|
|
28
33
|
if (!view)
|
|
@@ -43,9 +48,42 @@ export function createViewManager(ctx) {
|
|
|
43
48
|
function applySimulatorBounds(simWidth) {
|
|
44
49
|
if (!simulatorView || ctx.windows.mainWindow.isDestroyed())
|
|
45
50
|
return;
|
|
51
|
+
if (simulatorBoundsOverride) {
|
|
52
|
+
simulatorView.setBounds(simulatorBoundsOverride);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
46
55
|
const [w = 0, h = 0] = ctx.windows.mainWindow.getContentSize();
|
|
47
56
|
simulatorView.setBounds(layout.computeSimulatorBounds(w, h, simWidth, headerHeight));
|
|
48
57
|
}
|
|
58
|
+
// Renderer publishes width/height = 0 to mean "hide overlay" — the
|
|
59
|
+
// surrounding React panel is unmounted/collapsed. We keep the cached
|
|
60
|
+
// value (so future republishes win over legacy layout) but remove the
|
|
61
|
+
// child view from the contentView until a non-empty rect arrives.
|
|
62
|
+
function isHidden(b) {
|
|
63
|
+
return b.width <= 0 || b.height <= 0;
|
|
64
|
+
}
|
|
65
|
+
function setSimulatorDevtoolsBounds(bounds) {
|
|
66
|
+
simulatorBoundsOverride = bounds;
|
|
67
|
+
if (!simulatorView || simulatorView.webContents.isDestroyed())
|
|
68
|
+
return;
|
|
69
|
+
if (ctx.windows.mainWindow.isDestroyed())
|
|
70
|
+
return;
|
|
71
|
+
if (isHidden(bounds)) {
|
|
72
|
+
if (simulatorViewAdded) {
|
|
73
|
+
try {
|
|
74
|
+
ctx.windows.mainWindow.contentView.removeChildView(simulatorView);
|
|
75
|
+
}
|
|
76
|
+
catch { /* already removed */ }
|
|
77
|
+
simulatorViewAdded = false;
|
|
78
|
+
}
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (!simulatorViewAdded) {
|
|
82
|
+
ctx.windows.mainWindow.contentView.addChildView(simulatorView);
|
|
83
|
+
simulatorViewAdded = true;
|
|
84
|
+
}
|
|
85
|
+
simulatorView.setBounds(bounds);
|
|
86
|
+
}
|
|
49
87
|
function applySettingsBounds() {
|
|
50
88
|
if (!settingsView || ctx.windows.mainWindow.isDestroyed())
|
|
51
89
|
return;
|
|
@@ -82,9 +120,13 @@ export function createViewManager(ctx) {
|
|
|
82
120
|
sim.setDevToolsWebContents(simulatorView.webContents);
|
|
83
121
|
sim.openDevTools();
|
|
84
122
|
// Default DevTools to Console panel (Chrome DevTools defaults to Elements).
|
|
85
|
-
// The DevTools UI
|
|
86
|
-
//
|
|
87
|
-
//
|
|
123
|
+
// The DevTools UI lives inside closed shadow roots, so a light-DOM
|
|
124
|
+
// querySelector('[role="tab"]') cannot reach the tab bar to click it.
|
|
125
|
+
// Instead drive the front-end's own view manager: the bundled DevTools
|
|
126
|
+
// exposes `UI.ViewManager.instance().showView(id)` on `globalThis.UI`
|
|
127
|
+
// once the front-end has finished bootstrapping. We poll for it and
|
|
128
|
+
// request the `console` view, and also persist the choice via the
|
|
129
|
+
// `panel-selectedTab` localStorage key so subsequent reloads honor it.
|
|
88
130
|
const devtoolsWc = simulatorView.webContents;
|
|
89
131
|
devtoolsWc.once('dom-ready', () => {
|
|
90
132
|
devtoolsWc.executeJavaScript(`
|
|
@@ -93,24 +135,35 @@ export function createViewManager(ctx) {
|
|
|
93
135
|
let tries = 0
|
|
94
136
|
const timer = setInterval(() => {
|
|
95
137
|
tries++
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
138
|
+
try {
|
|
139
|
+
const UI = globalThis.UI
|
|
140
|
+
const vm = UI && UI.ViewManager && typeof UI.ViewManager.instance === 'function'
|
|
141
|
+
? UI.ViewManager.instance()
|
|
142
|
+
: null
|
|
143
|
+
if (vm && typeof vm.showView === 'function') {
|
|
144
|
+
vm.showView('console')
|
|
101
145
|
clearInterval(timer)
|
|
102
146
|
return
|
|
103
147
|
}
|
|
104
|
-
}
|
|
105
|
-
if (tries >
|
|
106
|
-
},
|
|
148
|
+
} catch {}
|
|
149
|
+
if (tries > 80) clearInterval(timer)
|
|
150
|
+
}, 50)
|
|
107
151
|
})()
|
|
108
152
|
`).catch(() => { });
|
|
109
153
|
});
|
|
110
154
|
if (getDefaultTab(ctx) === 'simulator') {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
155
|
+
if (simulatorBoundsOverride) {
|
|
156
|
+
if (!isHidden(simulatorBoundsOverride)) {
|
|
157
|
+
ctx.windows.mainWindow.contentView.addChildView(simulatorView);
|
|
158
|
+
simulatorViewAdded = true;
|
|
159
|
+
simulatorView.setBounds(simulatorBoundsOverride);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
ctx.windows.mainWindow.contentView.addChildView(simulatorView);
|
|
164
|
+
simulatorViewAdded = true;
|
|
165
|
+
applySimulatorBounds(simWidth);
|
|
166
|
+
}
|
|
114
167
|
}
|
|
115
168
|
}
|
|
116
169
|
function detachSimulator() {
|
|
@@ -123,6 +176,9 @@ export function createViewManager(ctx) {
|
|
|
123
176
|
simulatorView = null;
|
|
124
177
|
simulatorViewAdded = false;
|
|
125
178
|
simulatorWebContentsId = null;
|
|
179
|
+
// Drop the renderer-published rect so a stale "hidden" override doesn't
|
|
180
|
+
// suppress the next view before its renderer republishes.
|
|
181
|
+
simulatorBoundsOverride = null;
|
|
126
182
|
}
|
|
127
183
|
function showSimulator(simWidth) {
|
|
128
184
|
lastSimWidth = simWidth;
|
|
@@ -276,6 +332,7 @@ export function createViewManager(ctx) {
|
|
|
276
332
|
},
|
|
277
333
|
resize,
|
|
278
334
|
setVisible,
|
|
335
|
+
setSimulatorDevtoolsBounds,
|
|
279
336
|
};
|
|
280
337
|
}
|
|
281
338
|
//# sourceMappingURL=view-manager.js.map
|
|
@@ -10,7 +10,7 @@ import { type SimulatorApiRegistry } from './simulator/custom-apis.js';
|
|
|
10
10
|
import { type ToolbarStore } from './toolbar/toolbar-store.js';
|
|
11
11
|
import { sanitizeTemplates } from './projects/templates.js';
|
|
12
12
|
import type { ProjectsProvider, ProjectTemplate } from './projects/types.js';
|
|
13
|
-
import type { CustomCreateProjectDialogResult } from '../../shared/types.js';
|
|
13
|
+
import type { BeforeOpenProjectResult, CustomCreateProjectDialogResult } from '../../shared/types.js';
|
|
14
14
|
/**
|
|
15
15
|
* Shared mutable state for the workbench application.
|
|
16
16
|
* Passed to each IPC module so they can read/write shared state without closures.
|
|
@@ -67,6 +67,11 @@ export interface WorkbenchContext {
|
|
|
67
67
|
parentWindow: BrowserWindow;
|
|
68
68
|
templates: ProjectTemplate[];
|
|
69
69
|
}) => Promise<CustomCreateProjectDialogResult>;
|
|
70
|
+
/**
|
|
71
|
+
* Optional host gate for opening projects. Runs after basic directory
|
|
72
|
+
* validation and before the current project session is closed.
|
|
73
|
+
*/
|
|
74
|
+
beforeOpenProject?: (projectPath: string) => BeforeOpenProjectResult | Promise<BeforeOpenProjectResult>;
|
|
70
75
|
/**
|
|
71
76
|
* Trust predicate consulted by `IpcRegistry` for every incoming IPC.
|
|
72
77
|
* Resolves the currently-trusted senders (main renderer + overlays)
|
|
@@ -38,6 +38,14 @@ export interface WorkspaceService {
|
|
|
38
38
|
appInfo: unknown;
|
|
39
39
|
} | null;
|
|
40
40
|
getProjectPath(): string;
|
|
41
|
+
/**
|
|
42
|
+
* The project path most recently torn down by `closeProject`, or '' if none
|
|
43
|
+
* since the last `openProject`. Lets the project-fs sandbox accept an
|
|
44
|
+
* in-flight write (debounced/teardown flush) that targets the just-closed
|
|
45
|
+
* project even though `getProjectPath()` has already been cleared. Reset to
|
|
46
|
+
* '' on the next `openProject` so it never accumulates stale roots.
|
|
47
|
+
*/
|
|
48
|
+
getLastClosedProjectPath(): string;
|
|
41
49
|
hasActiveSession(): boolean;
|
|
42
50
|
captureThumbnail(projectPath: string): Promise<string | null>;
|
|
43
51
|
getThumbnail(projectPath: string): Promise<string | null>;
|
|
@@ -6,6 +6,10 @@ import { loadWorkbenchSettings } from '../settings/index.js';
|
|
|
6
6
|
export function createWorkspaceService(ctx) {
|
|
7
7
|
let currentSession = null;
|
|
8
8
|
let currentProjectPath = '';
|
|
9
|
+
// The last project path cleared by `closeProject`; consumed by project-fs to
|
|
10
|
+
// accept an in-flight write that targets the just-closed project. Reset at
|
|
11
|
+
// the start of every `openProject` so it never accumulates a stale root.
|
|
12
|
+
let lastClosedProjectPath = '';
|
|
9
13
|
function sendStatus(status, message, hotReload) {
|
|
10
14
|
ctx.notify.projectStatus(hotReload ? { status, message, hotReload: true } : { status, message });
|
|
11
15
|
}
|
|
@@ -58,9 +62,6 @@ export function createWorkspaceService(ctx) {
|
|
|
58
62
|
? provider.validateProjectDir(dirPath)
|
|
59
63
|
: null,
|
|
60
64
|
async openProject(projectPath) {
|
|
61
|
-
clearSimulatorServicewechatReferer();
|
|
62
|
-
await disposeSession();
|
|
63
|
-
currentProjectPath = '';
|
|
64
65
|
// Reject obviously broken projects up front so we never spin up a
|
|
65
66
|
// compile/server that the simulator will then fetch asset-by-asset.
|
|
66
67
|
// Without this guard, deleted recent-list entries and typo'd CLI paths
|
|
@@ -75,6 +76,18 @@ export function createWorkspaceService(ctx) {
|
|
|
75
76
|
sendStatus('error', dirError);
|
|
76
77
|
return { success: false, error: dirError };
|
|
77
78
|
}
|
|
79
|
+
const gate = ctx.beforeOpenProject ? await ctx.beforeOpenProject(projectPath) : undefined;
|
|
80
|
+
if (gate && !gate.allow) {
|
|
81
|
+
const message = gate.userMessage ?? gate.reason;
|
|
82
|
+
sendStatus('error', message);
|
|
83
|
+
return { success: false, error: message };
|
|
84
|
+
}
|
|
85
|
+
clearSimulatorServicewechatReferer();
|
|
86
|
+
await disposeSession();
|
|
87
|
+
currentProjectPath = '';
|
|
88
|
+
// A fresh open starts a clean sandbox window — drop any previously
|
|
89
|
+
// recorded last-closed root so it can't accept writes after this point.
|
|
90
|
+
lastClosedProjectPath = '';
|
|
78
91
|
sendStatus('compiling', '正在编译...');
|
|
79
92
|
const { compile } = loadWorkbenchSettings();
|
|
80
93
|
let session;
|
|
@@ -98,7 +111,7 @@ export function createWorkspaceService(ctx) {
|
|
|
98
111
|
if (provider.updateLastOpened)
|
|
99
112
|
provider.updateLastOpened(projectPath);
|
|
100
113
|
});
|
|
101
|
-
bestEffort('sendStatus', () => sendStatus('ready', '编译完成'));
|
|
114
|
+
bestEffort('sendStatus', () => sendStatus('ready', gate?.statusMessage ?? '编译完成'));
|
|
102
115
|
bestEffort('applyReferer', () => applyRefererFromSession(session));
|
|
103
116
|
return {
|
|
104
117
|
success: true,
|
|
@@ -109,11 +122,16 @@ export function createWorkspaceService(ctx) {
|
|
|
109
122
|
async closeProject() {
|
|
110
123
|
clearSimulatorServicewechatReferer();
|
|
111
124
|
await disposeSession();
|
|
125
|
+
// Record the root being torn down BEFORE clearing it, so a teardown/
|
|
126
|
+
// debounced write already in flight can still be accepted against it.
|
|
127
|
+
if (currentProjectPath !== '')
|
|
128
|
+
lastClosedProjectPath = currentProjectPath;
|
|
112
129
|
currentProjectPath = '';
|
|
113
130
|
ctx.views.disposeAll();
|
|
114
131
|
},
|
|
115
132
|
getSession: () => currentSession,
|
|
116
133
|
getProjectPath: () => currentProjectPath,
|
|
134
|
+
getLastClosedProjectPath: () => lastClosedProjectPath,
|
|
117
135
|
hasActiveSession: () => currentSession !== null,
|
|
118
136
|
async captureThumbnail(projectPath) {
|
|
119
137
|
const wc = ctx.views.getSimulatorWebContents();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { WebContents } from 'electron';
|
|
1
|
+
import type { IpcMainEvent, WebContents } from 'electron';
|
|
2
2
|
import { ipcMain } from 'electron';
|
|
3
3
|
import { type Disposable } from './disposable.js';
|
|
4
4
|
type HandleFn = Parameters<typeof ipcMain.handle>[1];
|
|
@@ -32,6 +32,15 @@ export declare class IpcRegistry implements Disposable {
|
|
|
32
32
|
private registry;
|
|
33
33
|
constructor(policy?: SenderPolicy | undefined);
|
|
34
34
|
handle(channel: string, fn: HandleFn): this;
|
|
35
|
+
/**
|
|
36
|
+
* Register a SYNCHRONOUS `ipcRenderer.sendSync` handler. Unlike {@link on}
|
|
37
|
+
* (fire-and-forget), the renderer is blocked until we set `event.returnValue`,
|
|
38
|
+
* so `fn` MUST be synchronous and return the value to hand back. The sender
|
|
39
|
+
* policy gates it like {@link handle}; a rejected sender or a thrown error
|
|
40
|
+
* yields a structured `{ ok: false, code, message }` returnValue instead of
|
|
41
|
+
* the silent drop `on` uses, so the blocked renderer always gets an answer.
|
|
42
|
+
*/
|
|
43
|
+
handleSync(channel: string, fn: (event: IpcMainEvent, ...args: unknown[]) => unknown): this;
|
|
35
44
|
on(channel: string, fn: ListenerFn): this;
|
|
36
45
|
dispose(): Promise<void>;
|
|
37
46
|
}
|