Remove the Windows 7 (7.css) skin
The win7 skin is dropped. Everything else the branch built stays: Linux support and the HTML context menus, the workspace-tab menu, the continuous interface scale (rem everywhere, stepper, Ctrl/Cmd +/-/0) and the core/ service layer. The modern tree already carried all of them, and nothing outside skins/7like/ imported the win7 tree except main.ts and styles.css. Removed: the skins/7like/ tree (5.1k lines, fonts), the 7.css dependency, the skin setting and its options row + strings in all 7 locales, the frameless-window plumbing (window-control RPCs, windowState push, trackMaximizeState, the synthesized resize grip and its postinstall watchdog), the session-skin pinning in buildPreload/chromeOptions, the skin/skinfix cascade layers, and the win7-only probes and screenshot harness (the harness cannot render the modern tree: plain Firefox has no RPC bridge). chromeOptions() stays as the one shared frame decision. Fixed on the way out: - index.html's per-theme pre-paint background is restored. The branch had replaced it with a win7-only transparent rule, so modern dark windows had silently lost one of the three flash guards. - editor-windows.ts wrote its window-key separator as a literal NUL byte, which made git and grep treat the file as binary; it is `\0` now (same string at runtime). No settings migration: a stale "skin" key is carried harmlessly by the store's spread-merge and dropped on the next save. UI bundle: 616.7 kB -> 405.4 kB, back under the 500 kB budget. Docs: architecture, gotchas and platform-notes lose the win7 material but keep what stays true (construction-time preloads/frames, drag regions ignoring preventDefault, electrobun's missing resize/maximize primitives); flash-prevention, dev-URL and Bun-panic notes move back under Electrobun; stale context-menu/tab-bar links fixed; README documents the scale keys. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Binary file not shown.
+3
-29
@@ -15,13 +15,7 @@ import { migrateLegacyDataDir } from "./store/paths";
|
||||
import { SettingsStore } from "./store/settings";
|
||||
import { AppTray } from "./tray";
|
||||
import { TransferEngine } from "./transfers/engine";
|
||||
import {
|
||||
buildPreload,
|
||||
chromeOptions,
|
||||
initSessionSkin,
|
||||
showWhenReady,
|
||||
trackMaximizeState,
|
||||
} from "./windows";
|
||||
import { buildPreload, chromeOptions, showWhenReady } from "./windows";
|
||||
|
||||
const PROD_URL = "views://app/index.html";
|
||||
|
||||
@@ -60,19 +54,7 @@ const events = {
|
||||
openConnectionManager: () => {},
|
||||
showAbout: () => {},
|
||||
};
|
||||
// Late-bound for the same reason events is: `win` below takes this rpc in its
|
||||
// constructor, so it cannot exist yet. Only the win7 title bar reads it.
|
||||
let mainWin: BrowserWindow | undefined;
|
||||
const rpc = createAppRPC({
|
||||
registry,
|
||||
engine,
|
||||
store,
|
||||
settings,
|
||||
edits,
|
||||
watcher,
|
||||
mainWindow: () => mainWin,
|
||||
events,
|
||||
});
|
||||
const rpc = createAppRPC({ registry, engine, store, settings, edits, watcher, events });
|
||||
// late-bound like events: a watched local dir changed -> tell the main webview
|
||||
watcher.onChange = (dir) => rpc.send.pathRefresh({ sessionId: "local", dir });
|
||||
const url = await getMainViewUrl();
|
||||
@@ -82,11 +64,6 @@ const url = await getMainViewUrl();
|
||||
// fresh via the saveSettings flow below.
|
||||
let currentSettings = await settings.load();
|
||||
setBackendLocale(currentSettings.locale); // native menus/errors follow the UI language
|
||||
// Freeze the skin for this process BEFORE any window is constructed. It picks
|
||||
// a component tree and a native frame, neither of which can change on a live
|
||||
// window, so the persisted value may only take effect at the next start — see
|
||||
// initSessionSkin.
|
||||
initSessionSkin(currentSettings.skin);
|
||||
|
||||
const win = new BrowserWindow({
|
||||
title: BRANDING.appName,
|
||||
@@ -97,8 +74,7 @@ const win = new BrowserWindow({
|
||||
x: 120,
|
||||
y: 120,
|
||||
},
|
||||
// Frame decision lives in chromeOptions — see windows.ts. Constructor-only,
|
||||
// and read from the pinned session skin, not from currentSettings.
|
||||
// frame decision lives in chromeOptions (windows.ts); constructor-only
|
||||
...chromeOptions(),
|
||||
// lets the UI pick the fused layout + theme before first paint
|
||||
preload: buildPreload("main", currentSettings),
|
||||
@@ -107,8 +83,6 @@ const win = new BrowserWindow({
|
||||
hidden: true,
|
||||
});
|
||||
showWhenReady(win);
|
||||
mainWin = win;
|
||||
trackMaximizeState(win, (maximized) => rpc.send.windowState({ maximized }));
|
||||
|
||||
const editors = new EditorWindows({
|
||||
registry,
|
||||
|
||||
@@ -70,8 +70,6 @@ export class ManagerWindow {
|
||||
this.close();
|
||||
},
|
||||
onClose: () => this.close(),
|
||||
// late-bound: the window takes this rpc in its constructor
|
||||
window: () => this.win ?? undefined,
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
+1
-97
@@ -1,4 +1,4 @@
|
||||
import { BrowserView, type BrowserWindow, Updater, Utils } from "electrobun/bun";
|
||||
import { BrowserView, Updater, Utils } from "electrobun/bun";
|
||||
import type { AppRPC, EditorOpenContext } from "../shared/rpc";
|
||||
import { effectiveConcurrency, type AppSettings, type Result } from "../shared/types";
|
||||
import { readFileText, writeFileText, type EditManager } from "./edits";
|
||||
@@ -24,12 +24,6 @@ export interface AppDeps {
|
||||
settings: SettingsStore;
|
||||
edits: EditManager;
|
||||
watcher: LocalDirWatcher;
|
||||
/**
|
||||
* The main BrowserWindow, for the win7 skin's custom title-bar controls.
|
||||
* Lazy for the same reason events is late-bound: the window takes this rpc
|
||||
* instance in its constructor, so it does not exist yet at createAppRPC time.
|
||||
*/
|
||||
mainWindow: () => BrowserWindow | undefined;
|
||||
/** Assigned after the rpc object exists (it needs rpc.send). */
|
||||
events: {
|
||||
sessionClosed: (info: { sessionId: string; reason: string }) => void;
|
||||
@@ -49,78 +43,6 @@ async function wrap<T extends object>(fn: () => Promise<T> | T): Promise<Result<
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Window-control requests for the win7 skin's custom title bar. Shared by all
|
||||
* three window kinds; each passes a getter for ITS OWN window.
|
||||
*
|
||||
* The getter is lazy because the main window's rpc instance is constructed
|
||||
* before the BrowserWindow exists (the window takes the rpc in its
|
||||
* constructor) — same late-binding reason as AppDeps.events. Under the modern
|
||||
* skin nothing calls these; they are harmless no-ops if the window is gone.
|
||||
*/
|
||||
export function windowControlHandlers(
|
||||
getWindow: () => BrowserWindow | undefined,
|
||||
/**
|
||||
* What the title bar's X does. Defaults to a raw `win.close()`. Editor
|
||||
* windows MUST pass their confirmed-close hook instead — the native close
|
||||
* is not cancellable, so a raw close there would skip the dirty prompt
|
||||
* (see gotchas.md "Window `close` is not cancellable").
|
||||
*/
|
||||
onClose: (win: BrowserWindow | undefined) => void = (win) => win?.close(),
|
||||
) {
|
||||
return {
|
||||
windowMinimize: () =>
|
||||
wrap(() => {
|
||||
getWindow()?.minimize();
|
||||
return {};
|
||||
}),
|
||||
|
||||
windowToggleMaximize: () =>
|
||||
wrap(() => {
|
||||
const win = getWindow();
|
||||
if (!win) return { maximized: false };
|
||||
// isMaximized is a synchronous FFI read, so this stays one round trip
|
||||
const next = !win.isMaximized();
|
||||
if (next) win.maximize();
|
||||
else win.unmaximize();
|
||||
return { maximized: next };
|
||||
}),
|
||||
|
||||
windowClose: () =>
|
||||
wrap(() => {
|
||||
onClose(getWindow());
|
||||
return {};
|
||||
}),
|
||||
|
||||
windowIsMaximized: () => wrap(() => ({ maximized: getWindow()?.isMaximized() ?? false })),
|
||||
|
||||
// ---- STOPGAP: synthesized resize ----
|
||||
// A frameless GTK window has no resize borders, and electrobun exposes no
|
||||
// native resize gesture (it has startWindowMove and nothing else — the
|
||||
// shipped libNativeWrapper does not even link gtk_window_begin_resize_drag).
|
||||
// So the webview drags its own handles and moves the frame itself. Both are
|
||||
// synchronous FFI reads/writes, like isMaximized above, so each drag tick
|
||||
// stays one round trip. DELETE WITH THE REST OF THE HACK — the checklist is
|
||||
// in ui/src/app/skins/7like/window-resize/window-resize.ts.
|
||||
windowGetFrame: () =>
|
||||
wrap(() => getWindow()?.getFrame() ?? { x: 0, y: 0, width: 0, height: 0 }),
|
||||
|
||||
// Params are annotated from the schema rather than inferred: this factory
|
||||
// returns a bare object literal, so it gets none of the contextual typing
|
||||
// the inline handler sets inside defineRPC<AppRPC> enjoy.
|
||||
windowSetFrame: ({
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
}: AppRPC["bun"]["requests"]["windowSetFrame"]["params"]) =>
|
||||
wrap(() => {
|
||||
getWindow()?.setFrame(x, y, width, height);
|
||||
return {};
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
/** Connection-store requests, shared by the main and manager window rpcs. */
|
||||
function connectionStoreHandlers(store: ConnectionStore, onChanged: () => void) {
|
||||
return {
|
||||
@@ -199,7 +121,6 @@ export function createAppRPC({
|
||||
settings,
|
||||
edits,
|
||||
watcher,
|
||||
mainWindow,
|
||||
events,
|
||||
}: AppDeps) {
|
||||
return BrowserView.defineRPC<AppRPC>({
|
||||
@@ -297,9 +218,6 @@ export function createAppRPC({
|
||||
return {};
|
||||
}),
|
||||
|
||||
// ---- custom window chrome (win7 skin) ----
|
||||
...windowControlHandlers(mainWindow),
|
||||
|
||||
// ---- connection store ----
|
||||
...connectionStoreHandlers(store, () => events.connectionsChanged()),
|
||||
|
||||
@@ -429,8 +347,6 @@ export interface EditorRpcHooks {
|
||||
onSaved: () => void;
|
||||
/** Webview resolved its dirty prompt: close without the native fallback. */
|
||||
onCloseConfirmed: () => void;
|
||||
/** This window, for the win7 title-bar controls (late-bound like the rest). */
|
||||
window: () => BrowserWindow | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -466,12 +382,6 @@ export function createEditorRPC(
|
||||
hooks.onCloseConfirmed();
|
||||
return {};
|
||||
}),
|
||||
|
||||
// win7 skin: this window is frameless, so the webview draws the
|
||||
// chrome. The X routes through onCloseConfirmed (suppressPrompt +
|
||||
// close), exactly like editorClose — the editor UI runs its dirty
|
||||
// prompt BEFORE calling this, same as the ⌘W path.
|
||||
...windowControlHandlers(hooks.window, () => hooks.onCloseConfirmed()),
|
||||
},
|
||||
messages: {
|
||||
editorState: (state) => hooks.onDraft(state),
|
||||
@@ -487,8 +397,6 @@ export interface ManagerRpcHooks {
|
||||
onConnect: (id: string) => void;
|
||||
onOpenStack: (id: string) => void;
|
||||
onClose: () => void;
|
||||
/** This window, for the win7 title-bar controls (late-bound like the rest). */
|
||||
window: () => BrowserWindow | undefined;
|
||||
}
|
||||
|
||||
/** RPC for the single connection-manager window (per-window instance). */
|
||||
@@ -521,10 +429,6 @@ export function createManagerRPC(
|
||||
hooks.onClose();
|
||||
return {};
|
||||
}),
|
||||
|
||||
// win7 skin: frameless window, webview-drawn chrome. The X reuses
|
||||
// the managerClose path so the modal bookkeeping still runs.
|
||||
...windowControlHandlers(hooks.window, () => hooks.onClose()),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
+10
-89
@@ -1,31 +1,9 @@
|
||||
import { BrowserWindow } from "electrobun/bun";
|
||||
import { DEFAULT_SETTINGS, type AppSettings } from "../shared/types";
|
||||
|
||||
/**
|
||||
* The skin THIS PROCESS booted with, frozen at startup by `initSessionSkin`.
|
||||
*
|
||||
* Every window must be built from this, never from the live settings snapshot.
|
||||
* The skin picks a whole component tree (bootstrapped from the preload-baked
|
||||
* __APP_SKIN) and the native frame (constructor-only), so it cannot change
|
||||
* without a restart — but the persisted setting CAN change mid-session. Reading
|
||||
* the live value would boot a newly opened editor or manager into the pending
|
||||
* skin while the main window still runs the old one: two windows, two trees,
|
||||
* two frames.
|
||||
*
|
||||
* Pinning it in one place makes that impossible rather than making each call
|
||||
* site remember. Everything else in the preload keeps using the live snapshot,
|
||||
* because theme/tone/accent/text-size genuinely do apply live.
|
||||
*/
|
||||
let sessionSkin: AppSettings["skin"] = DEFAULT_SETTINGS.skin;
|
||||
|
||||
/** Call once, at startup, before any window is constructed. */
|
||||
export function initSessionSkin(skin: AppSettings["skin"]): void {
|
||||
sessionSkin = skin;
|
||||
}
|
||||
import type { AppSettings } from "../shared/types";
|
||||
|
||||
/**
|
||||
* The preload globals every window gets. Single composition point: the UI
|
||||
* reads them in ui/src/app/platform.ts before first paint (window chrome and
|
||||
* reads them in ui/src/app/core/platform.ts before first paint (window chrome and
|
||||
* root-component selection both depend on them).
|
||||
*
|
||||
* The persisted theme attributes are baked in here — mirroring the
|
||||
@@ -39,22 +17,11 @@ export function buildPreload(
|
||||
settings: AppSettings,
|
||||
ctx?: unknown,
|
||||
): string {
|
||||
// sessionSkin, NOT settings.skin — a pending skin change must not leak into
|
||||
// a window opened before the restart. See initSessionSkin.
|
||||
let script =
|
||||
`window.__APP_PLATFORM=${JSON.stringify(process.platform)};` +
|
||||
`window.__APP_MODE=${JSON.stringify(mode)};` +
|
||||
`window.__APP_SKIN=${JSON.stringify(sessionSkin)};`;
|
||||
`window.__APP_MODE=${JSON.stringify(mode)};`;
|
||||
if (ctx !== undefined) script += `window.__APP_CTX=${JSON.stringify(ctx)};`;
|
||||
|
||||
// A class, not a data-* attribute: 7.scoped.css is precompiled to require a
|
||||
// literal `.win7` ancestor, so the name is not ours to choose. This is the
|
||||
// ONLY place it is ever written — the UI must never toggle it live, because
|
||||
// each skin's CSS is written against its own tree's markup.
|
||||
if (sessionSkin === "win7") {
|
||||
script += `document.documentElement.classList.add("win7");`;
|
||||
}
|
||||
|
||||
const dataset: Record<string, string> = {};
|
||||
if (settings.theme !== "system") dataset["theme"] = settings.theme;
|
||||
if (settings.darkTone !== "dark") dataset["darkTone"] = settings.darkTone;
|
||||
@@ -72,32 +39,14 @@ export function buildPreload(
|
||||
}
|
||||
|
||||
/**
|
||||
* Native chrome options for a window. The ONE place the frame decision is made —
|
||||
* both the main window (src/bun/index.ts) and openSecondaryWindow spread this.
|
||||
*
|
||||
* Both values are BrowserWindow CONSTRUCTOR options — there is no API to
|
||||
* re-decorate a live window — which is half of why the skin is a launch-time
|
||||
* choice. Takes no argument on purpose: it reads the pinned sessionSkin, so no
|
||||
* call site can accidentally frame a window from a pending setting.
|
||||
* Native chrome for every window — the ONE place the frame is decided; the main
|
||||
* window (src/bun/index.ts) and openSecondaryWindow both spread this. macOS
|
||||
* fuses the titlebar with the app's own header row (hiddenInset: the traffic
|
||||
* lights overlay it, which is what IS_MAC_CHROME reserves pl-24 for); other
|
||||
* platforms keep the native titlebar. Constructor-only: there is no API to
|
||||
* re-decorate a live window.
|
||||
*/
|
||||
export function chromeOptions() {
|
||||
const skin = sessionSkin;
|
||||
// win7 draws its own Aero title bar (drag region + our minimize/maximize/
|
||||
// close), so the window is frameless on EVERY platform. `titleBarStyle:
|
||||
// "hidden"` clears `Titled` from the Cocoa style mask, which is also what
|
||||
// removes the macOS traffic lights — there is no separate
|
||||
// hide-traffic-lights API. Linux honours it too: the GTK wrapper calls
|
||||
// gtk_window_set_decorated(false).
|
||||
//
|
||||
// `transparent` is what lets the Aero frame be a real frame: .window's 6px
|
||||
// radius is drawn by the webview OUTSIDE the window's own rectangle, so on
|
||||
// an opaque surface the corners are simply clipped square.
|
||||
// PORTABILITY: on Linux this needs a compositor granting an RGBA visual.
|
||||
if (skin === "win7") return { titleBarStyle: "hidden" as const, transparent: true };
|
||||
|
||||
// modern: the native frame stays. On macOS it fuses with the app's own
|
||||
// header row (hiddenInset), and the traffic lights overlay it — which is
|
||||
// what IS_MAC_CHROME reserves pl-24 for in the shadlike tree.
|
||||
return process.platform === "darwin"
|
||||
? { titleBarStyle: "hiddenInset" as const, trafficLightOffset: { x: 12, y: 13 } }
|
||||
: {};
|
||||
@@ -123,31 +72,6 @@ export function showWhenReady(win: BrowserWindow): void {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Push maximize-state changes to a window's webview so the win7 title bar can
|
||||
* swap its maximize/restore glyph.
|
||||
*
|
||||
* electrobun emits NO maximize/unmaximize event (only close/resize/move/focus/
|
||||
* blur/keyDown/keyUp), so the state is derived from `resize` — which is also
|
||||
* what makes OS-initiated changes work: double-click zoom, Win+Up, Aero Snap
|
||||
* all resize the window. `isMaximized()` is a synchronous FFI read, so polling
|
||||
* it per resize is cheap; the last-value guard keeps the wire quiet during a
|
||||
* drag-resize. The emitter has no off(), so this closure must not capture
|
||||
* anything that needs to be garbage collected (see WindowRegistry.track).
|
||||
*/
|
||||
export function trackMaximizeState(
|
||||
win: BrowserWindow,
|
||||
push: (maximized: boolean) => void,
|
||||
): void {
|
||||
let last = win.isMaximized();
|
||||
win.on("resize", () => {
|
||||
const now = win.isMaximized();
|
||||
if (now === last) return;
|
||||
last = now;
|
||||
push(now);
|
||||
});
|
||||
}
|
||||
|
||||
export interface SecondaryWindowOptions {
|
||||
title: string;
|
||||
/** The once-resolved dev/prod url (see getMainViewUrl in index.ts). */
|
||||
@@ -162,10 +86,7 @@ export interface SecondaryWindowOptions {
|
||||
rpc: NonNullable<ConstructorParameters<typeof BrowserWindow>[0]>["rpc"];
|
||||
}
|
||||
|
||||
/**
|
||||
* App-chromed secondary window. The frame decision is delegated to
|
||||
* chromeOptions (the single frame decision).
|
||||
*/
|
||||
/** App-chromed secondary window; its frame comes from chromeOptions. */
|
||||
export function openSecondaryWindow(opts: SecondaryWindowOptions): BrowserWindow {
|
||||
const win = new BrowserWindow({
|
||||
title: opts.title,
|
||||
|
||||
@@ -33,12 +33,6 @@ export const de = {
|
||||
title: "Optionen",
|
||||
language: "Sprache",
|
||||
system: "System",
|
||||
skin: {
|
||||
label: "Optik",
|
||||
modern: "Modern",
|
||||
win7: "Windows 7",
|
||||
restartHint: "Wird beim nächsten Start von miniSCP übernommen",
|
||||
},
|
||||
theme: { label: "Design", system: "System", light: "Hell", dark: "Dunkel" },
|
||||
tone: { label: "Ton", dark: "Dunkel", darker: "Dunkler" },
|
||||
accent: { label: "Akzent" },
|
||||
|
||||
@@ -37,12 +37,6 @@ export const en = {
|
||||
title: "Options",
|
||||
language: "Language",
|
||||
system: "System",
|
||||
skin: {
|
||||
label: "Skin",
|
||||
modern: "Modern",
|
||||
win7: "Windows 7",
|
||||
restartHint: "Applies next time you start miniSCP",
|
||||
},
|
||||
theme: { label: "Theme", system: "System", light: "Light", dark: "Dark" },
|
||||
tone: { label: "Tone", dark: "Dark", darker: "Darker" },
|
||||
accent: { label: "Accent" },
|
||||
|
||||
@@ -33,12 +33,6 @@ export const es = {
|
||||
title: "Opciones",
|
||||
language: "Idioma",
|
||||
system: "Sistema",
|
||||
skin: {
|
||||
label: "Aspecto",
|
||||
modern: "Moderno",
|
||||
win7: "Windows 7",
|
||||
restartHint: "Se aplica la próxima vez que abras miniSCP",
|
||||
},
|
||||
theme: { label: "Tema", system: "Sistema", light: "Claro", dark: "Oscuro" },
|
||||
tone: { label: "Tono", dark: "Oscuro", darker: "Más oscuro" },
|
||||
accent: { label: "Acento" },
|
||||
|
||||
@@ -35,12 +35,6 @@ export const eu = {
|
||||
title: "Aukerak",
|
||||
language: "Hizkuntza",
|
||||
system: "Sistema",
|
||||
skin: {
|
||||
label: "Itxura",
|
||||
modern: "Modernoa",
|
||||
win7: "Windows 7",
|
||||
restartHint: "Hurrengoan miniSCP abiaraztean aplikatuko da",
|
||||
},
|
||||
theme: { label: "Gaia", system: "Sistema", light: "Argia", dark: "Iluna" },
|
||||
tone: { label: "Tonua", dark: "Iluna", darker: "Ilunagoa" },
|
||||
accent: { label: "Azentua" },
|
||||
|
||||
@@ -33,12 +33,6 @@ export const fr = {
|
||||
title: "Options",
|
||||
language: "Langue",
|
||||
system: "Système",
|
||||
skin: {
|
||||
label: "Apparence",
|
||||
modern: "Moderne",
|
||||
win7: "Windows 7",
|
||||
restartHint: "Prend effet au prochain démarrage de miniSCP",
|
||||
},
|
||||
theme: { label: "Thème", system: "Système", light: "Clair", dark: "Sombre" },
|
||||
tone: { label: "Ton", dark: "Sombre", darker: "Plus sombre" },
|
||||
accent: { label: "Accent" },
|
||||
|
||||
@@ -34,12 +34,6 @@ export const ja = {
|
||||
title: "オプション",
|
||||
language: "言語",
|
||||
system: "システム",
|
||||
skin: {
|
||||
label: "外観",
|
||||
modern: "モダン",
|
||||
win7: "Windows 7",
|
||||
restartHint: "次回 miniSCP を起動したときに適用されます",
|
||||
},
|
||||
theme: { label: "テーマ", system: "システム", light: "ライト", dark: "ダーク" },
|
||||
tone: { label: "トーン", dark: "ダーク", darker: "より暗く" },
|
||||
accent: { label: "アクセント" },
|
||||
|
||||
@@ -34,12 +34,6 @@ export const zh = {
|
||||
title: "选项",
|
||||
language: "语言",
|
||||
system: "系统",
|
||||
skin: {
|
||||
label: "外观",
|
||||
modern: "现代",
|
||||
win7: "Windows 7",
|
||||
restartHint: "下次启动 miniSCP 时生效",
|
||||
},
|
||||
theme: { label: "主题", system: "系统", light: "浅色", dark: "深色" },
|
||||
tone: { label: "色调", dark: "深色", darker: "更深" },
|
||||
accent: { label: "强调色" },
|
||||
|
||||
+1
-37
@@ -128,7 +128,7 @@ export type AppRPC = {
|
||||
// local shell integration
|
||||
/**
|
||||
* Shell actions the context menu triggers on a LOCAL path. The menu
|
||||
* itself is HTML (ui/src/app/context-menu.ts) — electrobun's native
|
||||
* itself is HTML (ui/src/app/core/context-menu.service.ts) — electrobun's native
|
||||
* context menu is a no-op on Linux — but these three still need the
|
||||
* bun side's Utils.
|
||||
*/
|
||||
@@ -154,35 +154,6 @@ export type AppRPC = {
|
||||
managerConnect: { params: { id: string }; response: Result };
|
||||
managerOpenStack: { params: { id: string }; response: Result };
|
||||
managerClose: { params: Record<string, never>; response: Result };
|
||||
// custom window chrome (win7 skin — the window is frameless, so the
|
||||
// webview's title bar owns the controls). Served by every window kind.
|
||||
windowMinimize: { params: Record<string, never>; response: Result };
|
||||
/** Maximize/restore in one call; responds with the resulting state. */
|
||||
windowToggleMaximize: {
|
||||
params: Record<string, never>;
|
||||
response: Result<{ maximized: boolean }>;
|
||||
};
|
||||
windowClose: { params: Record<string, never>; response: Result };
|
||||
/** First paint: the title bar needs the state before any resize fires. */
|
||||
windowIsMaximized: {
|
||||
params: Record<string, never>;
|
||||
response: Result<{ maximized: boolean }>;
|
||||
};
|
||||
/**
|
||||
* STOPGAP — the two halves of the synthesized resize. A frameless GTK
|
||||
* window has no resize borders and electrobun exposes no native resize
|
||||
* gesture, so the webview drags its own handles and moves the frame
|
||||
* itself. Delete both with the rest of the hack; see the removal
|
||||
* checklist in ui/src/app/skins/7like/window-resize/window-resize.ts.
|
||||
*/
|
||||
windowGetFrame: {
|
||||
params: Record<string, never>;
|
||||
response: Result<{ x: number; y: number; width: number; height: number }>;
|
||||
};
|
||||
windowSetFrame: {
|
||||
params: { x: number; y: number; width: number; height: number };
|
||||
response: Result;
|
||||
};
|
||||
// auto-updater (electrobun Updater)
|
||||
/** Main window: re-run the update check on demand (menu item). */
|
||||
checkForUpdate: {
|
||||
@@ -213,13 +184,6 @@ export type AppRPC = {
|
||||
editorCloseRequest: Record<string, never>;
|
||||
/** bun → main: the modal connection manager opened/closed. */
|
||||
managerState: { open: boolean };
|
||||
/**
|
||||
* bun → any window: maximize state changed, so the custom title bar can
|
||||
* swap its maximize/restore glyph. electrobun has no maximize event —
|
||||
* this is derived from the window's `resize` event (see windows.ts
|
||||
* trackMaximizeState), which also covers OS-initiated Aero Snap.
|
||||
*/
|
||||
windowState: { maximized: boolean };
|
||||
/** bun → main: the manager window changed the connection store. */
|
||||
connectionsChanged: Record<string, never>;
|
||||
/**
|
||||
|
||||
+1
-14
@@ -78,18 +78,6 @@ export type AccentTone =
|
||||
export interface AppSettings {
|
||||
/** UI language; "system" resolves to the OS locale (fallback English). */
|
||||
locale: "system" | SupportedLocale;
|
||||
/**
|
||||
* Visual skin. "modern" is the shadcn-style look; "win7" swaps in 7.css
|
||||
* (Windows 7 Aero) and switches the window to custom frameless chrome.
|
||||
* Each skin is a whole component tree under ui/src/app/skins/ — "modern" is
|
||||
* shadlike/, "win7" is 7like/ — selected at bootstrap in ui/src/main.ts.
|
||||
* The value is NOT named after its folder on purpose: it is persisted in
|
||||
* user settings, and "win7" is also the literal class 7.scoped.css is
|
||||
* precompiled to require, so neither can be renamed freely.
|
||||
* Changing this needs a window reopen to take effect on the frame itself —
|
||||
* the CSS half applies live.
|
||||
*/
|
||||
skin: "modern" | "win7";
|
||||
theme: "system" | "light" | "dark";
|
||||
/** Dark appearance: grey ("dark") or near-black AMOLED ("darker"). */
|
||||
darkTone: "dark" | "darker";
|
||||
@@ -103,7 +91,7 @@ export interface AppSettings {
|
||||
/**
|
||||
* Interface scale factor, 0.5–4 in 0.1 steps (shown as 50%–400%). Works by
|
||||
* setting the root font-size — every size in the UI is rem, so one lever
|
||||
* scales BOTH skins whole. Replaced the 3-tier `textSize`
|
||||
* scales the whole UI. Replaced the 3-tier `textSize`
|
||||
* (compact|default|large); SettingsStore.load migrates old files.
|
||||
*/
|
||||
uiScale: number;
|
||||
@@ -125,7 +113,6 @@ export interface AppSettings {
|
||||
|
||||
export const DEFAULT_SETTINGS: AppSettings = {
|
||||
locale: "system",
|
||||
skin: "modern",
|
||||
theme: "system",
|
||||
darkTone: "dark",
|
||||
accent: "blue",
|
||||
|
||||
Reference in New Issue
Block a user