@dimina-kit/devtools 0.4.0-dev.20260615070430 → 0.4.0-dev.20260616024534
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 +0 -82
- package/dist/main/api.d.ts +1 -1
- package/dist/main/app/app.d.ts +0 -4
- package/dist/main/app/app.js +11 -6
- package/dist/main/app/lifecycle.d.ts +1 -0
- package/dist/main/app/lifecycle.js +10 -0
- package/dist/main/index.bundle.js +111 -143
- package/dist/main/ipc/app.d.ts +1 -3
- package/dist/main/ipc/app.js +0 -80
- package/dist/main/runtime/miniapp-runtime.d.ts +4 -2
- package/dist/main/services/notifications/renderer-notifier.d.ts +0 -4
- package/dist/main/services/notifications/renderer-notifier.js +1 -7
- package/dist/main/services/safe-area/index.d.ts +6 -4
- package/dist/main/services/safe-area/index.js +20 -14
- package/dist/main/services/simulator-storage/index.js +4 -2
- package/dist/main/services/views/view-manager.js +40 -8
- package/dist/main/services/workbench-context.d.ts +10 -13
- package/dist/main/services/workbench-context.js +1 -4
- package/dist/main/services/workspace/workspace-service.js +13 -0
- package/dist/main/utils/theme.d.ts +15 -0
- package/dist/main/utils/theme.js +34 -1
- package/dist/main/windows/main-window/create.d.ts +6 -0
- package/dist/main/windows/main-window/create.js +5 -1
- package/dist/preload/runtime/native-host.d.ts +3 -0
- package/dist/preload/runtime/native-host.js +2 -0
- package/dist/preload/windows/host-toolbar-runtime.cjs.map +2 -2
- package/dist/preload/windows/main.cjs +3 -3
- package/dist/preload/windows/main.cjs.map +2 -2
- package/dist/preload/windows/simulator.cjs +1 -0
- package/dist/preload/windows/simulator.cjs.map +2 -2
- package/dist/preload/windows/simulator.js +1 -0
- package/dist/renderer/assets/index-BFwsvw4p.js +50 -0
- package/dist/renderer/assets/{input-J1MQ5fra.js → input-hGF71bn3.js} +2 -2
- package/dist/renderer/assets/{ipc-transport-21qDpt9Y.js → ipc-transport-BKWkvwKK.js} +2 -2
- package/dist/renderer/assets/ipc-transport-Cg1q1gwD.css +1 -0
- package/dist/renderer/assets/{popover-ChIUUEdj.js → popover-NNQNyjgI.js} +2 -2
- package/dist/renderer/assets/select-Bb1AKeSq.js +2 -0
- package/dist/renderer/assets/{settings-CGea5k_R.js → settings-7uwimev-.js} +2 -2
- package/dist/renderer/assets/settings-api-DkrPHroq.js +2 -0
- package/dist/renderer/assets/{workbenchSettings-CFrdVOiP.js → workbenchSettings-EaMYvKHG.js} +2 -2
- package/dist/renderer/entries/main/index.html +6 -6
- package/dist/renderer/entries/popover/index.html +5 -5
- package/dist/renderer/entries/settings/index.html +5 -5
- package/dist/renderer/entries/workbench-settings/index.html +4 -4
- package/dist/shared/ipc-channels.d.ts +1 -7
- package/dist/shared/ipc-channels.js +7 -6
- package/dist/shared/ipc-schemas.d.ts +0 -1
- package/dist/shared/ipc-schemas.js +0 -1
- package/dist/shared/types.d.ts +17 -61
- package/dist/simulator/assets/device-shell-BWX7Yopg.js +2 -0
- package/dist/simulator/assets/{device-shell-DsdcNEkt.css → device-shell-BiFE9Otv.css} +1 -1
- package/dist/simulator/assets/{simulator-mini-app-CYtW6bMd.js → simulator-mini-app-BfNuDF3R.js} +2 -2
- package/dist/simulator/assets/{simulator-cV1PERi6.js → simulator-sf-D0mhw.js} +3 -3
- package/dist/simulator/simulator.html +1 -1
- package/package.json +4 -4
- package/dist/renderer/assets/index-Bx4XqFQ2.js +0 -50
- package/dist/renderer/assets/ipc-transport-CVp38I_M.css +0 -1
- package/dist/renderer/assets/select-BgWAbMot.js +0 -2
- package/dist/renderer/assets/settings-api-Dit4oL6S.js +0 -2
- package/dist/simulator/assets/device-shell-BEnc2k-W.js +0 -2
package/dist/main/ipc/app.js
CHANGED
|
@@ -1,91 +1,11 @@
|
|
|
1
1
|
import { AppChannel } from '../../shared/ipc-channels.js';
|
|
2
2
|
import { IpcRegistry } from '../utils/ipc-registry.js';
|
|
3
|
-
function readString(value) {
|
|
4
|
-
if (typeof value !== 'string')
|
|
5
|
-
return undefined;
|
|
6
|
-
const trimmed = value.trim();
|
|
7
|
-
return trimmed.length > 0 ? trimmed : undefined;
|
|
8
|
-
}
|
|
9
|
-
function normalizeHeaderAvatar(value) {
|
|
10
|
-
if (!value || typeof value !== 'object')
|
|
11
|
-
return null;
|
|
12
|
-
const raw = value;
|
|
13
|
-
const avatar = {};
|
|
14
|
-
const displayName = readString(raw.displayName);
|
|
15
|
-
const displayInitial = readString(raw.displayInitial);
|
|
16
|
-
const avatarUrl = readString(raw.avatarUrl);
|
|
17
|
-
const tooltip = readString(raw.tooltip);
|
|
18
|
-
if (displayName)
|
|
19
|
-
avatar.displayName = displayName;
|
|
20
|
-
if (displayInitial)
|
|
21
|
-
avatar.displayInitial = displayInitial;
|
|
22
|
-
if (avatarUrl)
|
|
23
|
-
avatar.avatarUrl = avatarUrl;
|
|
24
|
-
if (tooltip)
|
|
25
|
-
avatar.tooltip = tooltip;
|
|
26
|
-
return avatar.displayName || avatar.displayInitial || avatar.avatarUrl ? avatar : null;
|
|
27
|
-
}
|
|
28
|
-
function readPlacement(value) {
|
|
29
|
-
return value === 'left' || value === 'center' || value === 'right' ? value : undefined;
|
|
30
|
-
}
|
|
31
|
-
function normalizeHeaderAction(value) {
|
|
32
|
-
if (!value || typeof value !== 'object')
|
|
33
|
-
return null;
|
|
34
|
-
const raw = value;
|
|
35
|
-
const id = readString(raw.id);
|
|
36
|
-
const label = readString(raw.label);
|
|
37
|
-
if (!id || !label)
|
|
38
|
-
return null;
|
|
39
|
-
const action = { id, label };
|
|
40
|
-
const tooltip = readString(raw.tooltip);
|
|
41
|
-
const placement = readPlacement(raw.placement);
|
|
42
|
-
if (tooltip)
|
|
43
|
-
action.tooltip = tooltip;
|
|
44
|
-
if (placement)
|
|
45
|
-
action.placement = placement;
|
|
46
|
-
if (typeof raw.disabled === 'boolean')
|
|
47
|
-
action.disabled = raw.disabled;
|
|
48
|
-
return action;
|
|
49
|
-
}
|
|
50
|
-
function normalizeHeaderActions(value) {
|
|
51
|
-
if (!Array.isArray(value))
|
|
52
|
-
return [];
|
|
53
|
-
const actions = [];
|
|
54
|
-
const seen = new Set();
|
|
55
|
-
for (const item of value) {
|
|
56
|
-
const action = normalizeHeaderAction(item);
|
|
57
|
-
if (!action || seen.has(action.id))
|
|
58
|
-
continue;
|
|
59
|
-
seen.add(action.id);
|
|
60
|
-
actions.push(action);
|
|
61
|
-
}
|
|
62
|
-
return actions.slice(0, 8);
|
|
63
|
-
}
|
|
64
3
|
export function registerAppIpc(ctx) {
|
|
65
4
|
return new IpcRegistry(ctx.senderPolicy)
|
|
66
5
|
.handle(AppChannel.GetBranding, async () => {
|
|
67
6
|
if (ctx.brandingProvider)
|
|
68
7
|
return ctx.brandingProvider();
|
|
69
8
|
return { appName: ctx.appName };
|
|
70
|
-
})
|
|
71
|
-
.handle(AppChannel.GetHeaderAvatar, async () => {
|
|
72
|
-
if (!ctx.headerAvatarProvider)
|
|
73
|
-
return null;
|
|
74
|
-
return normalizeHeaderAvatar(await ctx.headerAvatarProvider());
|
|
75
|
-
})
|
|
76
|
-
.handle(AppChannel.InvokeHeaderAvatar, async () => {
|
|
77
|
-
await ctx.headerAvatarActionHandler?.();
|
|
78
|
-
})
|
|
79
|
-
.handle(AppChannel.GetHeaderActions, async () => {
|
|
80
|
-
if (!ctx.headerActionsProvider)
|
|
81
|
-
return [];
|
|
82
|
-
return normalizeHeaderActions(await ctx.headerActionsProvider());
|
|
83
|
-
})
|
|
84
|
-
.handle(AppChannel.InvokeHeaderAction, async (_event, id) => {
|
|
85
|
-
const actionId = readString(id);
|
|
86
|
-
if (!actionId)
|
|
87
|
-
return;
|
|
88
|
-
await ctx.headerActionHandler?.(actionId);
|
|
89
9
|
});
|
|
90
10
|
}
|
|
91
11
|
//# sourceMappingURL=app.js.map
|
|
@@ -15,8 +15,10 @@
|
|
|
15
15
|
* syntax: under strictFunctionTypes method signatures compare bivariantly,
|
|
16
16
|
* which would let a wrongly-narrowed implementation or host override slip
|
|
17
17
|
* past the drift sentinel.
|
|
18
|
-
* - `workspace.openProject` stays writable (no `readonly`)
|
|
19
|
-
*
|
|
18
|
+
* - `workspace.openProject` stays writable (no `readonly`) for backward
|
|
19
|
+
* compat: hosts MAY still gate permissions by reassigning it. The preferred
|
|
20
|
+
* path is now the declarative `WorkbenchAppConfig.onBeforeOpenProject` hook
|
|
21
|
+
* (runs before any side effect, throw to veto) — see host-migration.md §7.
|
|
20
22
|
*
|
|
21
23
|
* `asMiniappRuntime(ctx)` is an identity return — the contract is a typed
|
|
22
24
|
* VIEW onto the live context, not a snapshot/projection. That is what makes
|
|
@@ -65,10 +65,6 @@ export interface RendererNotifier {
|
|
|
65
65
|
* placeholder div resizes (closes the host-toolbar dynamic-height loop).
|
|
66
66
|
*/
|
|
67
67
|
hostToolbarHeightChanged(height: number): void;
|
|
68
|
-
/** Ask the main renderer to re-fetch the host-provided header avatar DTO. */
|
|
69
|
-
headerAvatarChanged(): void;
|
|
70
|
-
/** Ask the main renderer to re-fetch the host-provided header actions. */
|
|
71
|
-
headerActionsChanged(): void;
|
|
72
68
|
/**
|
|
73
69
|
* Ask the main renderer's Monaco editor to open a project file at a position.
|
|
74
70
|
* Drives the "click a console file link → open in editor" pipeline.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ProjectChannel, WindowChannel, SettingsChannel, PopoverChannel, WorkbenchSettingsChannel, EditorChannel, ViewChannel,
|
|
1
|
+
import { ProjectChannel, WindowChannel, SettingsChannel, PopoverChannel, WorkbenchSettingsChannel, EditorChannel, ViewChannel, } from '../../../shared/ipc-channels.js';
|
|
2
2
|
/** Safely resolve a WebContents, skipping destroyed / missing targets. */
|
|
3
3
|
function liveWebContents(wc) {
|
|
4
4
|
if (!wc)
|
|
@@ -39,12 +39,6 @@ export function createRendererNotifier(ctx) {
|
|
|
39
39
|
hostToolbarHeightChanged(height) {
|
|
40
40
|
sendToMain(ViewChannel.HostToolbarHeightChanged, height);
|
|
41
41
|
},
|
|
42
|
-
headerAvatarChanged() {
|
|
43
|
-
sendToMain(AppChannel.HeaderAvatarChanged);
|
|
44
|
-
},
|
|
45
|
-
headerActionsChanged() {
|
|
46
|
-
sendToMain(AppChannel.HeaderActionsChanged);
|
|
47
|
-
},
|
|
48
42
|
editorOpenFile(payload) {
|
|
49
43
|
sendToMain(EditorChannel.OpenFile, payload);
|
|
50
44
|
},
|
|
@@ -3,10 +3,12 @@ import type { ConnectionRegistry } from '@dimina-kit/electron-deck/main';
|
|
|
3
3
|
import type { NativeDeviceInfo } from '../../../shared/ipc-channels.js';
|
|
4
4
|
export interface SafeAreaController {
|
|
5
5
|
/** Attach the debugger to a freshly-attached render-host guest and push the
|
|
6
|
-
* current device's insets.
|
|
7
|
-
*
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
* current device's insets. `isTabPage` selects the bottom-inset policy (0 for
|
|
7
|
+
* tab pages, the real inset for full-bleed non-tab pages). No-op (warn) if the
|
|
8
|
+
* guest is already claimed by an external CDP client — env then stays 0. */
|
|
9
|
+
applyToGuest(guestWc: WebContents, device: NativeDeviceInfo | null, isTabPage: boolean): void;
|
|
10
|
+
/** Re-push insets to every still-attached guest after a device change (each
|
|
11
|
+
* guest keeps the page type it attached with). */
|
|
10
12
|
reapplyAll(device: NativeDeviceInfo | null): void;
|
|
11
13
|
/** Detach from all guests (teardown). */
|
|
12
14
|
dispose(): void;
|
|
@@ -1,25 +1,31 @@
|
|
|
1
|
-
function guestInsets(device) {
|
|
1
|
+
function guestInsets(device, isTabPage) {
|
|
2
2
|
const top = device?.safeAreaInsets.top ?? 0;
|
|
3
|
-
|
|
3
|
+
// A tab page's content sits above the shell-drawn tabBar (which fills the
|
|
4
|
+
// bottom safe area), so it never borders the bottom unsafe zone. A non-tab
|
|
5
|
+
// page is full-bleed to the device bottom, so surface the real inset for its
|
|
6
|
+
// own `env(safe-area-inset-bottom)` opt-in.
|
|
7
|
+
const bottom = isTabPage ? 0 : (device?.safeAreaInsets.bottom ?? 0);
|
|
8
|
+
return { top, topMax: top, right: 0, rightMax: 0, bottom, bottomMax: bottom, left: 0, leftMax: 0 };
|
|
4
9
|
}
|
|
5
10
|
export function createSafeAreaController(options = {}) {
|
|
6
|
-
// Guests we successfully attached `wc.debugger` to (
|
|
7
|
-
//
|
|
8
|
-
|
|
9
|
-
|
|
11
|
+
// Guests we successfully attached `wc.debugger` to (value = the page's
|
|
12
|
+
// `isTabPage`, fixed for the guest's life — it's one page). So we don't
|
|
13
|
+
// re-attach (throws), and a device-change reapply reuses the same policy.
|
|
14
|
+
const attached = new Map();
|
|
15
|
+
function override(wc, device, isTabPage) {
|
|
10
16
|
if (wc.isDestroyed())
|
|
11
17
|
return;
|
|
12
18
|
void wc.debugger
|
|
13
|
-
.sendCommand('Emulation.setSafeAreaInsetsOverride', { insets: guestInsets(device) })
|
|
19
|
+
.sendCommand('Emulation.setSafeAreaInsetsOverride', { insets: guestInsets(device, isTabPage) })
|
|
14
20
|
.catch((err) => {
|
|
15
21
|
console.warn('[safe-area] setSafeAreaInsetsOverride failed:', err instanceof Error ? err.message : err);
|
|
16
22
|
});
|
|
17
23
|
}
|
|
18
24
|
return {
|
|
19
|
-
applyToGuest: (wc, device) => {
|
|
25
|
+
applyToGuest: (wc, device, isTabPage) => {
|
|
20
26
|
if (!wc || wc.isDestroyed() || attached.has(wc)) {
|
|
21
27
|
if (wc && !wc.isDestroyed() && attached.has(wc))
|
|
22
|
-
override(wc, device);
|
|
28
|
+
override(wc, device, isTabPage);
|
|
23
29
|
return;
|
|
24
30
|
}
|
|
25
31
|
try {
|
|
@@ -31,21 +37,21 @@ export function createSafeAreaController(options = {}) {
|
|
|
31
37
|
console.warn('[safe-area] debugger.attach failed; env(safe-area-inset-*) stays 0:', err instanceof Error ? err.message : err);
|
|
32
38
|
return;
|
|
33
39
|
}
|
|
34
|
-
attached.
|
|
40
|
+
attached.set(wc, isTabPage);
|
|
35
41
|
if (options.connections) {
|
|
36
42
|
options.connections.acquire(wc).own(() => attached.delete(wc));
|
|
37
43
|
}
|
|
38
44
|
else {
|
|
39
45
|
wc.once('destroyed', () => attached.delete(wc));
|
|
40
46
|
}
|
|
41
|
-
override(wc, device);
|
|
47
|
+
override(wc, device, isTabPage);
|
|
42
48
|
},
|
|
43
49
|
reapplyAll: (device) => {
|
|
44
|
-
for (const wc of attached)
|
|
45
|
-
override(wc, device);
|
|
50
|
+
for (const [wc, isTabPage] of attached)
|
|
51
|
+
override(wc, device, isTabPage);
|
|
46
52
|
},
|
|
47
53
|
dispose: () => {
|
|
48
|
-
for (const wc of attached) {
|
|
54
|
+
for (const wc of attached.keys()) {
|
|
49
55
|
try {
|
|
50
56
|
if (!wc.isDestroyed())
|
|
51
57
|
wc.debugger.detach();
|
|
@@ -403,8 +403,10 @@ export function setupSimulatorStorage(host, options) {
|
|
|
403
403
|
const filtered = prefix
|
|
404
404
|
? result.entries.filter(([key]) => key.startsWith(prefix))
|
|
405
405
|
: result.entries;
|
|
406
|
-
// Keys are returned with their `${appId}_` prefix intact
|
|
407
|
-
//
|
|
406
|
+
// Keys are returned with their `${appId}_` prefix intact: every write
|
|
407
|
+
// path (Set/Remove) keys off the raw storage key. The Storage panel
|
|
408
|
+
// display-strips the prefix for readability (storage-panel.tsx), so the
|
|
409
|
+
// wire keys stay raw while the UI shows clean, Chrome-like keys.
|
|
408
410
|
return filtered.map(([key, value]) => ({ key, value }));
|
|
409
411
|
});
|
|
410
412
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { ipcMain, shell, WebContentsView, webContents } from 'electron';
|
|
1
|
+
import { ipcMain, nativeTheme, shell, WebContentsView, webContents } from 'electron';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { cjsSiblingPreloadPath, mainPreloadPath } from '../../utils/paths.js';
|
|
4
|
+
import { simDeskBg } from '../../utils/theme.js';
|
|
4
5
|
import { applyNavigationHardening, handleWindowOpenExternal, } from '../../windows/navigation-hardening.js';
|
|
5
6
|
import { SimulatorCustomApiBridgeChannel } from '../../../shared/ipc-channels.js';
|
|
6
7
|
import { OPEN_IN_EDITOR_SCHEME, decodeOpenInEditorUrl, resourceUrlToProjectRelativePath, } from '../../../shared/open-in-editor.js';
|
|
@@ -806,12 +807,27 @@ export function createViewManager(ctx) {
|
|
|
806
807
|
},
|
|
807
808
|
});
|
|
808
809
|
nativeSimulatorView = view;
|
|
809
|
-
// Paint the WCV surface the
|
|
810
|
-
//
|
|
811
|
-
//
|
|
812
|
-
//
|
|
813
|
-
view.setBackgroundColor(
|
|
810
|
+
// Paint the WCV surface the themed desk color (simDeskBg(): dark #121212 /
|
|
811
|
+
// light #e8e8e8) so a height-resize that grows the region never flashes a
|
|
812
|
+
// mismatched strip before DeviceShell's desk repaints — the WCV, the desk,
|
|
813
|
+
// and the renderer placeholder behind it are all the same color.
|
|
814
|
+
view.setBackgroundColor(simDeskBg());
|
|
814
815
|
const simWc = view.webContents;
|
|
816
|
+
// Keep the WCV surface in sync with the active color scheme. The
|
|
817
|
+
// process-wide installThemeBackgroundSync() re-syncs BrowserWindows on a
|
|
818
|
+
// theme switch, but this top-level WebContentsView is not a window, so its
|
|
819
|
+
// creation-time backgroundColor would otherwise freeze. Mirror simDeskBg()
|
|
820
|
+
// here on every nativeTheme `updated`; the listener is owned by the wc's
|
|
821
|
+
// connection so it detaches when the simulator view is torn down.
|
|
822
|
+
const syncDeskBg = () => {
|
|
823
|
+
try {
|
|
824
|
+
if (!simWc.isDestroyed())
|
|
825
|
+
view.setBackgroundColor(simDeskBg());
|
|
826
|
+
}
|
|
827
|
+
catch { /* view/wc gone */ }
|
|
828
|
+
};
|
|
829
|
+
nativeTheme.on('updated', syncDeskBg);
|
|
830
|
+
ctx.connections.acquire(simWc).own(() => nativeTheme.removeListener('updated', syncDeskBg));
|
|
815
831
|
// Service the simulator-side `__diminaCustomApis` bridge: this top-level
|
|
816
832
|
// WebContentsView has no embedder renderer to proxy through, so dispatch its
|
|
817
833
|
// `sendToHost` requests straight to `ctx.simulatorApis` from main.
|
|
@@ -822,12 +838,23 @@ export function createViewManager(ctx) {
|
|
|
822
838
|
// them with contextIsolation/sandbox off so the render runtime + its preload
|
|
823
839
|
// share the page realm. (A top-level WebContentsView can host these guests; a
|
|
824
840
|
// `<webview>` guest cannot — that's the whole point of Option A.)
|
|
841
|
+
// Page type (`isTab`) of each attaching guest, captured from its render-host
|
|
842
|
+
// URL in will-attach (where `params.src` carries the full URL) and consumed
|
|
843
|
+
// FIFO in the matching did-attach — `guestWc.getURL()` is still empty there.
|
|
844
|
+
// Per-attach scope: a fresh simWc + handlers are built on every (re)attach.
|
|
845
|
+
const pendingGuestIsTab = [];
|
|
825
846
|
simWc.on('will-attach-webview', (_event, webPreferences, params) => {
|
|
826
847
|
;
|
|
827
848
|
webPreferences.partition = partition;
|
|
828
849
|
params.partition = partition;
|
|
829
850
|
webPreferences.contextIsolation = false;
|
|
830
851
|
webPreferences.sandbox = false;
|
|
852
|
+
let isTab = false;
|
|
853
|
+
try {
|
|
854
|
+
isTab = new URL(params.src).searchParams.get('isTab') === '1';
|
|
855
|
+
}
|
|
856
|
+
catch { /* keep false */ }
|
|
857
|
+
pendingGuestIsTab.push(isTab);
|
|
831
858
|
});
|
|
832
859
|
simWc.on('did-attach-webview', (_event, guestWc) => {
|
|
833
860
|
// Scale the nested render-host page with the device zoom. The host WCV is
|
|
@@ -841,8 +868,13 @@ export function createViewManager(ctx) {
|
|
|
841
868
|
}
|
|
842
869
|
catch { /* guest not ready; setNativeSimulatorViewBounds re-applies */ }
|
|
843
870
|
// Simulate this device's CSS env(safe-area-inset-*) on the fresh guest
|
|
844
|
-
// before it paints, so notch-aware page layout resolves correctly.
|
|
845
|
-
|
|
871
|
+
// before it paints, so notch-aware page layout resolves correctly. The
|
|
872
|
+
// bottom inset is page-type-dependent (see services/safe-area): a tab
|
|
873
|
+
// page's content sits above the shell tabBar (bottom 0); a non-tab page
|
|
874
|
+
// is full-bleed (real bottom inset). The page type was captured from the
|
|
875
|
+
// render-host URL in will-attach (FIFO).
|
|
876
|
+
const isTabGuest = pendingGuestIsTab.shift() ?? false;
|
|
877
|
+
safeArea.applyToGuest(guestWc, ctx.bridge?.getDevice() ?? null, isTabGuest);
|
|
846
878
|
guestWc.setWindowOpenHandler(({ url }) => handleWindowOpenExternal(url));
|
|
847
879
|
guestWc.on('will-navigate', (e, url) => {
|
|
848
880
|
try {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { BrowserWindow } from 'electron';
|
|
2
|
-
import type { CompilationAdapter,
|
|
2
|
+
import type { CompilationAdapter, WorkbenchConfig } from '../../shared/types.js';
|
|
3
3
|
import type { BridgeRouterHandle } from '../ipc/bridge-router.js';
|
|
4
4
|
import type { ConsoleForwarder } from './console-forward/index.js';
|
|
5
5
|
import type { NetworkForwarder } from './network-forward/index.js';
|
|
@@ -35,14 +35,6 @@ export interface WorkbenchContext {
|
|
|
35
35
|
}> | {
|
|
36
36
|
appName: string;
|
|
37
37
|
};
|
|
38
|
-
/** Host-injected profile provider for the built-in header avatar slot. */
|
|
39
|
-
headerAvatarProvider?: HeaderAvatarProvider;
|
|
40
|
-
/** Host-injected click handler for the built-in header avatar slot. */
|
|
41
|
-
headerAvatarActionHandler?: () => void | Promise<void>;
|
|
42
|
-
/** Host-injected compact action provider for the built-in project header. */
|
|
43
|
-
headerActionsProvider?: HeaderActionsProvider;
|
|
44
|
-
/** Host-injected click handler for the built-in project header actions. */
|
|
45
|
-
headerActionHandler?: (id: string) => void | Promise<void>;
|
|
46
38
|
/** Unified lifecycle manager for all overlay WebContentsViews */
|
|
47
39
|
views: ViewManager;
|
|
48
40
|
/** Owns BrowserWindow instances (main + settings) and their sender-id checks. */
|
|
@@ -82,6 +74,13 @@ export interface WorkbenchContext {
|
|
|
82
74
|
parentWindow: BrowserWindow;
|
|
83
75
|
templates: ProjectTemplate[];
|
|
84
76
|
}) => Promise<CustomCreateProjectDialogResult>;
|
|
77
|
+
/**
|
|
78
|
+
* Host permission gate consulted by `workspace.openProject` BEFORE any side
|
|
79
|
+
* effect. Throwing vetoes the open (see `WorkbenchAppConfig.onBeforeOpenProject`).
|
|
80
|
+
* Wired from the config by `createDevtoolsRuntime`; undefined for the
|
|
81
|
+
* single-tenant default.
|
|
82
|
+
*/
|
|
83
|
+
onBeforeOpenProject?: (projectPath: string) => void | Promise<void>;
|
|
85
84
|
/**
|
|
86
85
|
* Trust predicate consulted by `IpcRegistry` for every incoming IPC.
|
|
87
86
|
* Resolves the currently-trusted senders (main renderer + overlays)
|
|
@@ -183,10 +182,6 @@ export interface CreateContextOptions extends Pick<WorkbenchConfig, 'adapter' |
|
|
|
183
182
|
preloadPath: string;
|
|
184
183
|
rendererDir: string;
|
|
185
184
|
brandingProvider?: WorkbenchContext['brandingProvider'];
|
|
186
|
-
headerAvatarProvider?: WorkbenchContext['headerAvatarProvider'];
|
|
187
|
-
headerAvatarActionHandler?: WorkbenchContext['headerAvatarActionHandler'];
|
|
188
|
-
headerActionsProvider?: WorkbenchContext['headerActionsProvider'];
|
|
189
|
-
headerActionHandler?: WorkbenchContext['headerActionHandler'];
|
|
190
185
|
/** Host-supplied project list backend. Defaults to LocalProjectsProvider. */
|
|
191
186
|
projectsProvider?: ProjectsProvider;
|
|
192
187
|
/** Templates injected by the host; same-id overrides a built-in. */
|
|
@@ -195,6 +190,8 @@ export interface CreateContextOptions extends Pick<WorkbenchConfig, 'adapter' |
|
|
|
195
190
|
builtinTemplates?: 'all' | 'none' | readonly string[];
|
|
196
191
|
/** Host-supplied "新建项目" dialog hook. */
|
|
197
192
|
customCreateProjectDialog?: WorkbenchContext['customCreateProjectDialog'];
|
|
193
|
+
/** Host permission gate run before a project opens (throw to veto). */
|
|
194
|
+
onBeforeOpenProject?: WorkbenchContext['onBeforeOpenProject'];
|
|
198
195
|
}
|
|
199
196
|
export declare function createWorkbenchContext(opts: CreateContextOptions): WorkbenchContext;
|
|
200
197
|
export { sanitizeTemplates };
|
|
@@ -18,10 +18,6 @@ export function createWorkbenchContext(opts) {
|
|
|
18
18
|
apiNamespaces: opts.apiNamespaces ?? [],
|
|
19
19
|
appName: opts.appName ?? 'Dimina DevTools',
|
|
20
20
|
brandingProvider: opts.brandingProvider,
|
|
21
|
-
headerAvatarProvider: opts.headerAvatarProvider,
|
|
22
|
-
headerAvatarActionHandler: opts.headerAvatarActionHandler,
|
|
23
|
-
headerActionsProvider: opts.headerActionsProvider,
|
|
24
|
-
headerActionHandler: opts.headerActionHandler,
|
|
25
21
|
};
|
|
26
22
|
ctx.registry = new DisposableRegistry();
|
|
27
23
|
// Empty connection registry as a first-class context field. Connections are
|
|
@@ -41,6 +37,7 @@ export function createWorkbenchContext(opts) {
|
|
|
41
37
|
ctx.projectsProvider = opts.projectsProvider ?? createLocalProjectsProvider();
|
|
42
38
|
ctx.projectTemplates = resolveTemplates(BUILTIN_TEMPLATES, opts.projectTemplates ?? [], opts.builtinTemplates ?? 'all');
|
|
43
39
|
ctx.customCreateProjectDialog = opts.customCreateProjectDialog;
|
|
40
|
+
ctx.onBeforeOpenProject = opts.onBeforeOpenProject;
|
|
44
41
|
ctx.workspace = createWorkspaceService(ctx);
|
|
45
42
|
ctx.senderPolicy = createWorkbenchSenderPolicy(ctx);
|
|
46
43
|
return ctx;
|
|
@@ -80,6 +80,19 @@ export function createWorkspaceService(ctx) {
|
|
|
80
80
|
? provider.validateProjectDir(dirPath)
|
|
81
81
|
: null,
|
|
82
82
|
async openProject(projectPath) {
|
|
83
|
+
// Host permission gate — runs BEFORE any side effect (referer reset,
|
|
84
|
+
// session teardown, compile/dev-server spin-up). A throwing hook vetoes
|
|
85
|
+
// the open: return early with the error and leave the currently-active
|
|
86
|
+
// session fully intact. The declarative replacement for monkey-patching
|
|
87
|
+
// this method. See WorkbenchAppConfig.onBeforeOpenProject.
|
|
88
|
+
if (ctx.onBeforeOpenProject) {
|
|
89
|
+
try {
|
|
90
|
+
await ctx.onBeforeOpenProject(projectPath);
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
return { success: false, error: err instanceof Error ? err.message : String(err) };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
83
96
|
clearSimulatorServicewechatReferer();
|
|
84
97
|
// Invalidate the outgoing session's onLog BEFORE teardown starts (same
|
|
85
98
|
// order as closeProject below): the dying compile worker flushes
|
|
@@ -8,6 +8,21 @@ import { type Disposable } from '@dimina-kit/electron-deck/main';
|
|
|
8
8
|
* Light: hsl(0 0% 98%) ≈ #fafafa (--color-bg)
|
|
9
9
|
*/
|
|
10
10
|
export declare function themeBg(): string;
|
|
11
|
+
/**
|
|
12
|
+
* Backdrop ("desk") color for the native simulator WebContentsView — the
|
|
13
|
+
* surface the simulated phone sits on. Unlike {@link themeBg} (the window bg),
|
|
14
|
+
* the desk is a neutral grey kept a touch off the window so the light-colored
|
|
15
|
+
* phone keeps contrast against it in BOTH schemes.
|
|
16
|
+
*
|
|
17
|
+
* Dark: hsl(0 0% 7%) ≈ #121212 (the long-standing desk color — unchanged)
|
|
18
|
+
* Light: hsl(0 0% 91%) ≈ #e8e8e8 (neutral grey; the white phone reads on it)
|
|
19
|
+
*
|
|
20
|
+
* MUST stay equal to the renderer's `--color-sim-bg` (design.css) and the
|
|
21
|
+
* simulator page's `.device-shell-root` background (device-shell.css): the WCV,
|
|
22
|
+
* the desk, and the placeholder behind it are the same color so a height-resize
|
|
23
|
+
* never flashes a mismatched strip. Update all three together.
|
|
24
|
+
*/
|
|
25
|
+
export declare function simDeskBg(): string;
|
|
11
26
|
/**
|
|
12
27
|
* Install a single process-wide listener that keeps every window's native
|
|
13
28
|
* `backgroundColor` in sync with the active color scheme.
|
package/dist/main/utils/theme.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { BrowserWindow, nativeTheme } from 'electron';
|
|
2
2
|
import { toDisposable } from '@dimina-kit/electron-deck/main';
|
|
3
|
+
import { WorkbenchSettingsChannel } from '../../shared/ipc-channels.js';
|
|
3
4
|
/**
|
|
4
5
|
* Background color that matches the current system color scheme.
|
|
5
6
|
* Pass as `backgroundColor` when creating BrowserWindows to prevent
|
|
@@ -11,6 +12,23 @@ import { toDisposable } from '@dimina-kit/electron-deck/main';
|
|
|
11
12
|
export function themeBg() {
|
|
12
13
|
return nativeTheme.shouldUseDarkColors ? '#1a1a1a' : '#fafafa';
|
|
13
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Backdrop ("desk") color for the native simulator WebContentsView — the
|
|
17
|
+
* surface the simulated phone sits on. Unlike {@link themeBg} (the window bg),
|
|
18
|
+
* the desk is a neutral grey kept a touch off the window so the light-colored
|
|
19
|
+
* phone keeps contrast against it in BOTH schemes.
|
|
20
|
+
*
|
|
21
|
+
* Dark: hsl(0 0% 7%) ≈ #121212 (the long-standing desk color — unchanged)
|
|
22
|
+
* Light: hsl(0 0% 91%) ≈ #e8e8e8 (neutral grey; the white phone reads on it)
|
|
23
|
+
*
|
|
24
|
+
* MUST stay equal to the renderer's `--color-sim-bg` (design.css) and the
|
|
25
|
+
* simulator page's `.device-shell-root` background (device-shell.css): the WCV,
|
|
26
|
+
* the desk, and the placeholder behind it are the same color so a height-resize
|
|
27
|
+
* never flashes a mismatched strip. Update all three together.
|
|
28
|
+
*/
|
|
29
|
+
export function simDeskBg() {
|
|
30
|
+
return nativeTheme.shouldUseDarkColors ? '#121212' : '#e8e8e8';
|
|
31
|
+
}
|
|
14
32
|
/**
|
|
15
33
|
* Install a single process-wide listener that keeps every window's native
|
|
16
34
|
* `backgroundColor` in sync with the active color scheme.
|
|
@@ -36,10 +54,25 @@ export function themeBg() {
|
|
|
36
54
|
export function installThemeBackgroundSync() {
|
|
37
55
|
const apply = () => {
|
|
38
56
|
const bg = themeBg();
|
|
57
|
+
const isDark = nativeTheme.shouldUseDarkColors;
|
|
39
58
|
for (const win of BrowserWindow.getAllWindows()) {
|
|
40
|
-
if (
|
|
59
|
+
if (win.isDestroyed())
|
|
60
|
+
continue;
|
|
61
|
+
// Isolate each window: a window closing mid-loop (or a renderer torn down
|
|
62
|
+
// between the isDestroyed() check and the send) must not abort the sync
|
|
63
|
+
// for the remaining windows.
|
|
64
|
+
try {
|
|
41
65
|
win.setBackgroundColor(bg);
|
|
66
|
+
// Notify renderer JS consumers that can't observe the CSS
|
|
67
|
+
// `prefers-color-scheme` change (Monaco's theme). Electron does not
|
|
68
|
+
// dispatch the renderer's matchMedia change event for programmatic
|
|
69
|
+
// `nativeTheme.themeSource` flips, so push it from here — the one place
|
|
70
|
+
// that already centralizes color-scheme reactions.
|
|
71
|
+
if (!win.webContents.isDestroyed()) {
|
|
72
|
+
win.webContents.send(WorkbenchSettingsChannel.ThemeChanged, isDark);
|
|
73
|
+
}
|
|
42
74
|
}
|
|
75
|
+
catch { /* window/webContents gone mid-loop */ }
|
|
43
76
|
}
|
|
44
77
|
};
|
|
45
78
|
nativeTheme.on('updated', apply);
|
|
@@ -6,6 +6,12 @@ export interface WindowOptions {
|
|
|
6
6
|
minWidth?: number;
|
|
7
7
|
minHeight?: number;
|
|
8
8
|
indexHtml: string;
|
|
9
|
+
/**
|
|
10
|
+
* Auto-show the window on `ready-to-show` in non-test envs. Defaults to
|
|
11
|
+
* `true`. `false` lets a login-gating host keep the window hidden and call
|
|
12
|
+
* `show()` itself. The test env always uses `showInactive()` regardless.
|
|
13
|
+
*/
|
|
14
|
+
autoShow?: boolean;
|
|
9
15
|
}
|
|
10
16
|
export declare function createMainWindow(opts: WindowOptions): BrowserWindow;
|
|
11
17
|
//# sourceMappingURL=create.d.ts.map
|
|
@@ -26,7 +26,11 @@ export function createMainWindow(opts) {
|
|
|
26
26
|
mainWindow.showInactive();
|
|
27
27
|
}
|
|
28
28
|
else {
|
|
29
|
-
|
|
29
|
+
// A login-gating host opts out via `autoShow: false` and shows the
|
|
30
|
+
// window itself once auth passes — don't flash an un-authed window.
|
|
31
|
+
if (opts.autoShow !== false) {
|
|
32
|
+
mainWindow.show();
|
|
33
|
+
}
|
|
30
34
|
// Don't auto-open a detached DevTools for the devtools UI shell itself —
|
|
31
35
|
// it's noise for normal use (the mini-app's Console lives in the embedded
|
|
32
36
|
// right-panel DevTools, not here). Opt in via env for debugging the shell.
|
|
@@ -4,6 +4,9 @@ export interface RenderHostUrlOptions {
|
|
|
4
4
|
bridgeId: string;
|
|
5
5
|
appId: string;
|
|
6
6
|
pagePath: string;
|
|
7
|
+
/** Whether this page is a tabBar page. Surfaced on the URL so main can pick
|
|
8
|
+
* the bottom safe-area policy at `did-attach-webview` (services/safe-area). */
|
|
9
|
+
isTab?: boolean;
|
|
7
10
|
}
|
|
8
11
|
export interface DiminaNativeHostBridge {
|
|
9
12
|
enabled: boolean;
|
|
@@ -55,6 +55,8 @@ function buildBridge(cfg) {
|
|
|
55
55
|
url.searchParams.set('bridgeId', opts.bridgeId);
|
|
56
56
|
url.searchParams.set('appId', opts.appId);
|
|
57
57
|
url.searchParams.set('pagePath', opts.pagePath);
|
|
58
|
+
if (opts.isTab)
|
|
59
|
+
url.searchParams.set('isTab', '1');
|
|
58
60
|
return url.toString();
|
|
59
61
|
},
|
|
60
62
|
renderPreloadUrl: cfg.renderPreloadUrl,
|