@dimina-kit/devtools 0.4.0-dev.20260612152115 → 0.4.0-dev.20260615083030
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main/index.bundle.js +49 -17
- 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/utils/theme.d.ts +15 -0
- package/dist/main/utils/theme.js +34 -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-DgqX2UvN.js → input-hGF71bn3.js} +2 -2
- package/dist/renderer/assets/{ipc-transport-CsFhDJ7q.js → ipc-transport-BKWkvwKK.js} +2 -2
- package/dist/renderer/assets/ipc-transport-Cg1q1gwD.css +1 -0
- package/dist/renderer/assets/{popover-D_2o_52M.js → popover-NNQNyjgI.js} +2 -2
- package/dist/renderer/assets/select-Bb1AKeSq.js +2 -0
- package/dist/renderer/assets/{settings-CivkfizP.js → settings-7uwimev-.js} +2 -2
- package/dist/renderer/assets/settings-api-DkrPHroq.js +2 -0
- package/dist/renderer/assets/{workbenchSettings-BwpwxuzM.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 -1
- package/dist/shared/ipc-channels.js +7 -0
- package/dist/shared/ipc-schemas.d.ts +0 -1
- package/dist/shared/ipc-schemas.js +0 -1
- 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-Bi_6SS-L.js +0 -50
- package/dist/renderer/assets/ipc-transport-SnOAk0Jq.css +0 -1
- package/dist/renderer/assets/select-BJYsdghn.js +0 -2
- package/dist/renderer/assets/settings-api-7mXkBy5-.js +0 -2
- package/dist/simulator/assets/device-shell-BEnc2k-W.js +0 -2
|
@@ -365,7 +365,14 @@ var WorkbenchSettingsChannel = {
|
|
|
365
365
|
SetTheme: "workbenchSettings:setTheme",
|
|
366
366
|
GetCdpStatus: "workbenchSettings:getCdpStatus",
|
|
367
367
|
GetMcpStatus: "workbenchSettings:getMcpStatus",
|
|
368
|
-
Init: "workbenchSettings:init"
|
|
368
|
+
Init: "workbenchSettings:init",
|
|
369
|
+
// Main → renderer push: the active color scheme flipped (OS change or in-app
|
|
370
|
+
// SetTheme). Payload is `isDark: boolean`. The app's CSS reacts to
|
|
371
|
+
// `prefers-color-scheme` automatically; this exists for the few JS consumers
|
|
372
|
+
// (Monaco's theme) that can't observe that media change — Electron does NOT
|
|
373
|
+
// dispatch the renderer's `matchMedia('(prefers-color-scheme)')` change event
|
|
374
|
+
// for programmatic `nativeTheme.themeSource` assignments.
|
|
375
|
+
ThemeChanged: "workbenchSettings:themeChanged"
|
|
369
376
|
};
|
|
370
377
|
var ProjectChannel = {
|
|
371
378
|
Open: "project:open",
|
|
@@ -795,12 +802,21 @@ import { toDisposable } from "@dimina-kit/electron-deck/main";
|
|
|
795
802
|
function themeBg() {
|
|
796
803
|
return nativeTheme2.shouldUseDarkColors ? "#1a1a1a" : "#fafafa";
|
|
797
804
|
}
|
|
805
|
+
function simDeskBg() {
|
|
806
|
+
return nativeTheme2.shouldUseDarkColors ? "#121212" : "#e8e8e8";
|
|
807
|
+
}
|
|
798
808
|
function installThemeBackgroundSync() {
|
|
799
809
|
const apply = () => {
|
|
800
810
|
const bg = themeBg();
|
|
811
|
+
const isDark = nativeTheme2.shouldUseDarkColors;
|
|
801
812
|
for (const win of BrowserWindow.getAllWindows()) {
|
|
802
|
-
if (
|
|
813
|
+
if (win.isDestroyed()) continue;
|
|
814
|
+
try {
|
|
803
815
|
win.setBackgroundColor(bg);
|
|
816
|
+
if (!win.webContents.isDestroyed()) {
|
|
817
|
+
win.webContents.send(WorkbenchSettingsChannel.ThemeChanged, isDark);
|
|
818
|
+
}
|
|
819
|
+
} catch {
|
|
804
820
|
}
|
|
805
821
|
}
|
|
806
822
|
};
|
|
@@ -1018,7 +1034,7 @@ function createRendererNotifier(ctx) {
|
|
|
1018
1034
|
}
|
|
1019
1035
|
|
|
1020
1036
|
// src/main/services/views/view-manager.ts
|
|
1021
|
-
import { ipcMain as ipcMain2, shell as shell2, WebContentsView as WebContentsView2, webContents } from "electron";
|
|
1037
|
+
import { ipcMain as ipcMain2, nativeTheme as nativeTheme3, shell as shell2, WebContentsView as WebContentsView2, webContents } from "electron";
|
|
1022
1038
|
import path7 from "path";
|
|
1023
1039
|
|
|
1024
1040
|
// src/shared/open-in-editor.ts
|
|
@@ -1075,22 +1091,23 @@ function resourceUrlToProjectRelativePath(resourceUrl, expectedOrigin) {
|
|
|
1075
1091
|
}
|
|
1076
1092
|
|
|
1077
1093
|
// src/main/services/safe-area/index.ts
|
|
1078
|
-
function guestInsets(device) {
|
|
1094
|
+
function guestInsets(device, isTabPage2) {
|
|
1079
1095
|
const top = device?.safeAreaInsets.top ?? 0;
|
|
1080
|
-
|
|
1096
|
+
const bottom = isTabPage2 ? 0 : device?.safeAreaInsets.bottom ?? 0;
|
|
1097
|
+
return { top, topMax: top, right: 0, rightMax: 0, bottom, bottomMax: bottom, left: 0, leftMax: 0 };
|
|
1081
1098
|
}
|
|
1082
1099
|
function createSafeAreaController(options = {}) {
|
|
1083
|
-
const attached = /* @__PURE__ */ new
|
|
1084
|
-
function override(wc, device) {
|
|
1100
|
+
const attached = /* @__PURE__ */ new Map();
|
|
1101
|
+
function override(wc, device, isTabPage2) {
|
|
1085
1102
|
if (wc.isDestroyed()) return;
|
|
1086
|
-
void wc.debugger.sendCommand("Emulation.setSafeAreaInsetsOverride", { insets: guestInsets(device) }).catch((err) => {
|
|
1103
|
+
void wc.debugger.sendCommand("Emulation.setSafeAreaInsetsOverride", { insets: guestInsets(device, isTabPage2) }).catch((err) => {
|
|
1087
1104
|
console.warn("[safe-area] setSafeAreaInsetsOverride failed:", err instanceof Error ? err.message : err);
|
|
1088
1105
|
});
|
|
1089
1106
|
}
|
|
1090
1107
|
return {
|
|
1091
|
-
applyToGuest: (wc, device) => {
|
|
1108
|
+
applyToGuest: (wc, device, isTabPage2) => {
|
|
1092
1109
|
if (!wc || wc.isDestroyed() || attached.has(wc)) {
|
|
1093
|
-
if (wc && !wc.isDestroyed() && attached.has(wc)) override(wc, device);
|
|
1110
|
+
if (wc && !wc.isDestroyed() && attached.has(wc)) override(wc, device, isTabPage2);
|
|
1094
1111
|
return;
|
|
1095
1112
|
}
|
|
1096
1113
|
try {
|
|
@@ -1099,19 +1116,19 @@ function createSafeAreaController(options = {}) {
|
|
|
1099
1116
|
console.warn("[safe-area] debugger.attach failed; env(safe-area-inset-*) stays 0:", err instanceof Error ? err.message : err);
|
|
1100
1117
|
return;
|
|
1101
1118
|
}
|
|
1102
|
-
attached.
|
|
1119
|
+
attached.set(wc, isTabPage2);
|
|
1103
1120
|
if (options.connections) {
|
|
1104
1121
|
options.connections.acquire(wc).own(() => attached.delete(wc));
|
|
1105
1122
|
} else {
|
|
1106
1123
|
wc.once("destroyed", () => attached.delete(wc));
|
|
1107
1124
|
}
|
|
1108
|
-
override(wc, device);
|
|
1125
|
+
override(wc, device, isTabPage2);
|
|
1109
1126
|
},
|
|
1110
1127
|
reapplyAll: (device) => {
|
|
1111
|
-
for (const wc of attached) override(wc, device);
|
|
1128
|
+
for (const [wc, isTabPage2] of attached) override(wc, device, isTabPage2);
|
|
1112
1129
|
},
|
|
1113
1130
|
dispose: () => {
|
|
1114
|
-
for (const wc of attached) {
|
|
1131
|
+
for (const wc of attached.keys()) {
|
|
1115
1132
|
try {
|
|
1116
1133
|
if (!wc.isDestroyed()) wc.debugger.detach();
|
|
1117
1134
|
} catch {
|
|
@@ -2313,22 +2330,38 @@ function createViewManager(ctx) {
|
|
|
2313
2330
|
}
|
|
2314
2331
|
});
|
|
2315
2332
|
nativeSimulatorView = view;
|
|
2316
|
-
view.setBackgroundColor(
|
|
2333
|
+
view.setBackgroundColor(simDeskBg());
|
|
2317
2334
|
const simWc = view.webContents;
|
|
2335
|
+
const syncDeskBg = () => {
|
|
2336
|
+
try {
|
|
2337
|
+
if (!simWc.isDestroyed()) view.setBackgroundColor(simDeskBg());
|
|
2338
|
+
} catch {
|
|
2339
|
+
}
|
|
2340
|
+
};
|
|
2341
|
+
nativeTheme3.on("updated", syncDeskBg);
|
|
2342
|
+
ctx.connections.acquire(simWc).own(() => nativeTheme3.removeListener("updated", syncDeskBg));
|
|
2318
2343
|
attachNativeCustomApiBridge(simWc);
|
|
2344
|
+
const pendingGuestIsTab = [];
|
|
2319
2345
|
simWc.on("will-attach-webview", (_event, webPreferences, params) => {
|
|
2320
2346
|
;
|
|
2321
2347
|
webPreferences.partition = partition;
|
|
2322
2348
|
params.partition = partition;
|
|
2323
2349
|
webPreferences.contextIsolation = false;
|
|
2324
2350
|
webPreferences.sandbox = false;
|
|
2351
|
+
let isTab = false;
|
|
2352
|
+
try {
|
|
2353
|
+
isTab = new URL(params.src).searchParams.get("isTab") === "1";
|
|
2354
|
+
} catch {
|
|
2355
|
+
}
|
|
2356
|
+
pendingGuestIsTab.push(isTab);
|
|
2325
2357
|
});
|
|
2326
2358
|
simWc.on("did-attach-webview", (_event, guestWc) => {
|
|
2327
2359
|
try {
|
|
2328
2360
|
guestWc.setZoomFactor(currentZoomFactor);
|
|
2329
2361
|
} catch {
|
|
2330
2362
|
}
|
|
2331
|
-
|
|
2363
|
+
const isTabGuest = pendingGuestIsTab.shift() ?? false;
|
|
2364
|
+
safeArea.applyToGuest(guestWc, ctx.bridge?.getDevice() ?? null, isTabGuest);
|
|
2332
2365
|
guestWc.setWindowOpenHandler(({ url }) => handleWindowOpenExternal(url));
|
|
2333
2366
|
guestWc.on("will-navigate", (e, url) => {
|
|
2334
2367
|
try {
|
|
@@ -3258,7 +3291,6 @@ var SimulatorSetDeviceInfoSchema = z3.tuple([
|
|
|
3258
3291
|
screenWidth: z3.number().int().min(100).max(4e3),
|
|
3259
3292
|
screenHeight: z3.number().int().min(100).max(4e3),
|
|
3260
3293
|
statusBarHeight: z3.number().finite().min(0).max(400),
|
|
3261
|
-
safeAreaBottom: z3.number().finite().min(0).max(400),
|
|
3262
3294
|
notchType: z3.enum(["none", "notch", "dynamic-island"]),
|
|
3263
3295
|
safeAreaInsets: z3.object({
|
|
3264
3296
|
top: z3.number().finite().min(0).max(400),
|
|
@@ -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 {
|
|
@@ -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);
|
|
@@ -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,
|