Move settings into a standalone Settings window
The tab-bar popover had outgrown itself. Settings now open in their own native window (a fourth window kind, built like the connection manager but not modal): a sidebar with Appearance, Transfers, Editor, File list and About, holding every popover row plus the visible-columns choice. Each control is an ordinary settings save, so the other windows follow live while it stays open. The gear button in the tab bar, Ctrl+, and the macOS app menu's "Settings..." (Cmd+,) open it. On macOS Cmd+W arrives as the main window's close-tab menu action, so bun closes the focused Settings window first instead of a main-window tab. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+34
-8
@@ -10,6 +10,7 @@ import { ManagerWindow } from "./manager-window";
|
||||
import { setLocale as setBackendLocale } from "./i18n";
|
||||
import { applyApplicationMenu } from "./menu";
|
||||
import { createAppRPC } from "./rpc-handlers";
|
||||
import { SettingsWindow } from "./settings-window";
|
||||
import { ConnectionStore } from "./store/connections";
|
||||
import { migrateLegacyDataDir } from "./store/paths";
|
||||
import { SettingsStore } from "./store/settings";
|
||||
@@ -52,6 +53,7 @@ const events = {
|
||||
openEditor: (_ctx: EditorOpenContext) => {},
|
||||
settingsChanged: (_settings: AppSettings) => {},
|
||||
openConnectionManager: () => {},
|
||||
openSettings: () => {},
|
||||
showAbout: () => {},
|
||||
};
|
||||
const rpc = createAppRPC({ registry, engine, store, settings, edits, watcher, events });
|
||||
@@ -108,29 +110,43 @@ const manager = new ManagerWindow({
|
||||
rebuildMenu: () => void applyApplicationMenu(store),
|
||||
onSettingsSaved: (s) => settingsSaved(s, "manager"),
|
||||
});
|
||||
const settingsWin = new SettingsWindow({
|
||||
settings,
|
||||
url,
|
||||
currentSettings: () => currentSettings,
|
||||
mainFrame: () => win.getFrame(),
|
||||
onSettingsSaved: (s) => settingsSaved(s, "settings"),
|
||||
showAbout: () => showAbout(),
|
||||
});
|
||||
|
||||
events.openEditor = (ctx) => editors.open(ctx);
|
||||
/**
|
||||
* Every settings save lands here, whichever window made it (editor and manager
|
||||
* windows save too — the scale shortcuts): side effects run once, then every
|
||||
* OTHER window follows. The sender is skipped — it already shows the new
|
||||
* values, and an echo arriving behind a newer local change (a held Cmd+) would
|
||||
* drag it backwards.
|
||||
* windows save too — the scale shortcuts — and the Settings window is nothing
|
||||
* but saves): side effects run once, then every OTHER window follows. The
|
||||
* sender is skipped — it already shows the new values, and an echo arriving
|
||||
* behind a newer local change (a held Cmd+) would drag it backwards.
|
||||
*/
|
||||
function settingsSaved(s: AppSettings, sender: "main" | "manager" | { editor: string }): void {
|
||||
function settingsSaved(
|
||||
s: AppSettings,
|
||||
sender: "main" | "manager" | "settings" | { editor: string },
|
||||
): void {
|
||||
engine.setConcurrency(effectiveConcurrency(s));
|
||||
const localeChanged = s.locale !== currentSettings.locale;
|
||||
currentSettings = s; // future windows' preloads bake the fresh theme
|
||||
if (localeChanged) {
|
||||
setBackendLocale(s.locale);
|
||||
void applyApplicationMenu(store); // relabel the native menu in the new language
|
||||
settingsWin.retitle();
|
||||
}
|
||||
if (sender !== "main") rpc.send.settingsChanged({ settings: s });
|
||||
if (sender !== "manager") manager.broadcastSettings(s);
|
||||
if (sender !== "settings") settingsWin.broadcastSettings(s);
|
||||
editors.broadcastSettings(s, typeof sender === "object" ? sender.editor : undefined);
|
||||
}
|
||||
events.settingsChanged = (s) => settingsSaved(s, "main");
|
||||
events.openConnectionManager = () => manager.open();
|
||||
events.openSettings = () => settingsWin.open();
|
||||
|
||||
function showAbout(): void {
|
||||
const { build } = BRANDING;
|
||||
@@ -149,6 +165,7 @@ events.showAbout = showAbout;
|
||||
// and the modal manager has nothing to guard
|
||||
win.on("close", () => {
|
||||
manager.close();
|
||||
settingsWin.close();
|
||||
editors.closeAllForAppExit();
|
||||
});
|
||||
|
||||
@@ -173,6 +190,7 @@ engine.onUpdate = (snapshot) => {
|
||||
if (
|
||||
!windowFocused &&
|
||||
!editors.anyFocused() &&
|
||||
!settingsWin.isFocused() &&
|
||||
(snapshot.state === "completed" || snapshot.state === "failed")
|
||||
) {
|
||||
Utils.showNotification({
|
||||
@@ -230,10 +248,14 @@ void applyApplicationMenu(store);
|
||||
ApplicationMenu.on("application-menu-clicked", (event) => {
|
||||
const action = (event as { data?: { action?: string } }).data?.action;
|
||||
if (!action) return;
|
||||
// ⌘W while an editor window is focused closes that editor (with its dirty
|
||||
// prompt), not a main-window tab. Other menu actions deliberately still
|
||||
// target the main window.
|
||||
// ⌘W while the Settings window or an editor window is focused closes THAT
|
||||
// window (an editor with its dirty prompt), not a main-window tab. Other
|
||||
// menu actions deliberately still target the main window.
|
||||
if (action === "close-tab") {
|
||||
if (settingsWin.isFocused()) {
|
||||
settingsWin.close();
|
||||
return;
|
||||
}
|
||||
const focusedEditor = editors.focusedKey();
|
||||
if (focusedEditor) {
|
||||
editors.requestClose(focusedEditor);
|
||||
@@ -244,6 +266,10 @@ ApplicationMenu.on("application-menu-clicked", (event) => {
|
||||
manager.open();
|
||||
return;
|
||||
}
|
||||
if (action === "settings") {
|
||||
settingsWin.open();
|
||||
return;
|
||||
}
|
||||
if (action === "about") {
|
||||
showAbout();
|
||||
return;
|
||||
|
||||
@@ -36,6 +36,8 @@ export async function applyApplicationMenu(store: ConnectionStore): Promise<void
|
||||
submenu: [
|
||||
{ label: t("menu.about", { app: BRANDING.appName }), action: "about" },
|
||||
{ type: "separator" },
|
||||
{ label: t("menu.settings"), action: "settings", accelerator: "," },
|
||||
{ type: "separator" },
|
||||
{ label: t("menu.checkForUpdates"), action: "check-for-updates" },
|
||||
{ type: "separator" },
|
||||
{ role: "hide" },
|
||||
|
||||
+56
-20
@@ -31,6 +31,7 @@ export interface AppDeps {
|
||||
openEditor: (ctx: EditorOpenContext) => void;
|
||||
settingsChanged: (settings: AppSettings) => void;
|
||||
openConnectionManager: () => void;
|
||||
openSettings: () => void;
|
||||
showAbout: () => void;
|
||||
};
|
||||
}
|
||||
@@ -130,6 +131,28 @@ const pickKeyFileHandler = () =>
|
||||
return { path };
|
||||
});
|
||||
|
||||
/** External-editor picker, shared by the main and settings window rpcs. */
|
||||
const pickEditorAppHandler = () =>
|
||||
wrap(async () => {
|
||||
// PORTABILITY: apps live in different places per OS
|
||||
const { homedir } = await import("node:os");
|
||||
const [startingFolder, allowedFileTypes] =
|
||||
process.platform === "darwin"
|
||||
? ["/Applications", "app"]
|
||||
: process.platform === "win32"
|
||||
? [process.env.ProgramFiles ?? "C:\\Program Files", "exe"]
|
||||
: [homedir(), "*"];
|
||||
const picked = await Utils.openFileDialog({
|
||||
startingFolder,
|
||||
allowedFileTypes,
|
||||
canChooseFiles: true,
|
||||
canChooseDirectory: false,
|
||||
allowsMultipleSelection: false,
|
||||
});
|
||||
const path = picked.find((p) => p.trim().length > 0) ?? null;
|
||||
return { path };
|
||||
});
|
||||
|
||||
export function createAppRPC({
|
||||
registry,
|
||||
engine,
|
||||
@@ -257,26 +280,7 @@ export function createAppRPC({
|
||||
editExternal: ({ sessionId, path }) =>
|
||||
wrap(() => edits.editExternal(sessionId, path)),
|
||||
|
||||
pickEditorApp: () =>
|
||||
wrap(async () => {
|
||||
// PORTABILITY: apps live in different places per OS
|
||||
const { homedir } = await import("node:os");
|
||||
const [startingFolder, allowedFileTypes] =
|
||||
process.platform === "darwin"
|
||||
? ["/Applications", "app"]
|
||||
: process.platform === "win32"
|
||||
? [process.env.ProgramFiles ?? "C:\\Program Files", "exe"]
|
||||
: [homedir(), "*"];
|
||||
const picked = await Utils.openFileDialog({
|
||||
startingFolder,
|
||||
allowedFileTypes,
|
||||
canChooseFiles: true,
|
||||
canChooseDirectory: false,
|
||||
allowsMultipleSelection: false,
|
||||
});
|
||||
const path = picked.find((p) => p.trim().length > 0) ?? null;
|
||||
return { path };
|
||||
}),
|
||||
pickEditorApp: pickEditorAppHandler,
|
||||
|
||||
pickKeyFile: pickKeyFileHandler,
|
||||
|
||||
@@ -318,6 +322,13 @@ export function createAppRPC({
|
||||
return {};
|
||||
}),
|
||||
|
||||
// ---- settings window ----
|
||||
openSettings: () =>
|
||||
wrap(async () => {
|
||||
events.openSettings();
|
||||
return {};
|
||||
}),
|
||||
|
||||
showAbout: () =>
|
||||
wrap(async () => {
|
||||
events.showAbout();
|
||||
@@ -449,3 +460,28 @@ export function createManagerRPC(
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export interface SettingsRpcHooks {
|
||||
/** Every edit here is a settings save — rebroadcast it to the other windows. */
|
||||
onSettingsSaved: (next: AppSettings) => void;
|
||||
showAbout: () => void;
|
||||
}
|
||||
|
||||
/** RPC for the single Settings window (per-window instance). */
|
||||
export function createSettingsRPC(deps: { settings: SettingsStore }, hooks: SettingsRpcHooks) {
|
||||
return BrowserView.defineRPC<AppRPC>({
|
||||
maxRequestTime: MAX_REQUEST_TIME_MS,
|
||||
handlers: {
|
||||
requests: {
|
||||
getSettings: () => wrap(async () => ({ settings: await deps.settings.load() })),
|
||||
saveSettings: saveSettingsHandler(deps.settings, hooks.onSettingsSaved),
|
||||
pickEditorApp: pickEditorAppHandler,
|
||||
showAbout: () =>
|
||||
wrap(async () => {
|
||||
hooks.showAbout();
|
||||
return {};
|
||||
}),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import type { BrowserWindow } from "electrobun/bun";
|
||||
import type { AppSettings } from "../shared/types";
|
||||
import { t } from "./i18n";
|
||||
import { createSettingsRPC } from "./rpc-handlers";
|
||||
import type { SettingsStore } from "./store/settings";
|
||||
import { openSecondaryWindow } from "./windows";
|
||||
|
||||
const SETTINGS_WIDTH = 680;
|
||||
const SETTINGS_HEIGHT = 480;
|
||||
|
||||
export interface SettingsWindowDeps {
|
||||
settings: SettingsStore;
|
||||
/** The once-resolved dev/prod view url. */
|
||||
url: string;
|
||||
/** Live settings snapshot — baked into the window's preload (theme flash). */
|
||||
currentSettings: () => AppSettings;
|
||||
mainFrame: () => { x: number; y: number; width: number; height: number };
|
||||
/** This window saved settings — rebroadcast them to every other window. */
|
||||
onSettingsSaved: (settings: AppSettings) => void;
|
||||
showAbout: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* The single Settings window. Unlike the connection manager it is NOT modal:
|
||||
* every edit is an ordinary settings save, so the other windows follow live
|
||||
* while it stays open. On macOS ⌘W reaches bun as the main window's close-tab
|
||||
* menu action, so index.ts asks isFocused() first and closes this window
|
||||
* instead of a main-window tab.
|
||||
*/
|
||||
export class SettingsWindow {
|
||||
private win: BrowserWindow | null = null;
|
||||
private rpc: ReturnType<typeof createSettingsRPC> | null = null;
|
||||
private focused = false;
|
||||
|
||||
constructor(private deps: SettingsWindowDeps) {}
|
||||
|
||||
isFocused(): boolean {
|
||||
return this.focused;
|
||||
}
|
||||
|
||||
open(): void {
|
||||
if (this.win) {
|
||||
this.win.activate();
|
||||
return;
|
||||
}
|
||||
|
||||
this.rpc = createSettingsRPC(
|
||||
{ settings: this.deps.settings },
|
||||
{
|
||||
onSettingsSaved: (next) => this.deps.onSettingsSaved(next),
|
||||
showAbout: () => this.deps.showAbout(),
|
||||
},
|
||||
);
|
||||
|
||||
const main = this.deps.mainFrame();
|
||||
this.win = openSecondaryWindow({
|
||||
title: t("options.title"),
|
||||
url: this.deps.url,
|
||||
mode: "settings",
|
||||
settings: this.deps.currentSettings(),
|
||||
frame: {
|
||||
x: Math.max(0, main.x + (main.width - SETTINGS_WIDTH) / 2),
|
||||
y: Math.max(0, main.y + (main.height - SETTINGS_HEIGHT) / 2),
|
||||
width: SETTINGS_WIDTH,
|
||||
height: SETTINGS_HEIGHT,
|
||||
},
|
||||
rpc: this.rpc,
|
||||
});
|
||||
this.win.on("focus", () => (this.focused = true));
|
||||
this.win.on("blur", () => (this.focused = false));
|
||||
this.win.on("close", () => {
|
||||
this.win = null;
|
||||
this.rpc = null;
|
||||
this.focused = false;
|
||||
});
|
||||
console.log("[settings] window opened");
|
||||
}
|
||||
|
||||
close(): void {
|
||||
this.win?.close(); // the close event does the cleanup
|
||||
}
|
||||
|
||||
/** The native title follows a language switch (the header row is the UI's own). */
|
||||
retitle(): void {
|
||||
this.win?.setTitle(t("options.title"));
|
||||
}
|
||||
|
||||
broadcastSettings(settings: AppSettings): void {
|
||||
this.rpc?.send.settingsChanged({ settings });
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -13,7 +13,7 @@ import type { AppSettings } from "../shared/types";
|
||||
* windows flash the wrong background.
|
||||
*/
|
||||
export function buildPreload(
|
||||
mode: "main" | "editor" | "manager",
|
||||
mode: "main" | "editor" | "manager" | "settings",
|
||||
settings: AppSettings,
|
||||
ctx?: unknown,
|
||||
): string {
|
||||
@@ -76,7 +76,7 @@ export interface SecondaryWindowOptions {
|
||||
title: string;
|
||||
/** The once-resolved dev/prod url (see getMainViewUrl in index.ts). */
|
||||
url: string;
|
||||
mode: "editor" | "manager";
|
||||
mode: "editor" | "manager" | "settings";
|
||||
/** Current persisted settings — baked into the preload for flash-free paint. */
|
||||
settings: AppSettings;
|
||||
ctx?: unknown;
|
||||
|
||||
+11
-2
@@ -23,14 +23,22 @@ export const de = {
|
||||
manageConnections: "Verbindungen verwalten",
|
||||
newTab: "Neuer Tab ({mod}T)",
|
||||
closeTab: "Tab schließen ({mod}W)",
|
||||
options: "Optionen",
|
||||
options: "Einstellungen",
|
||||
update: "Update verfügbar",
|
||||
updating: "Aktualisiere",
|
||||
restartToUpdate: "Zum Anwenden neu starten",
|
||||
updateTooltip: "Update verfügbar: miniSCP {version}",
|
||||
},
|
||||
options: {
|
||||
title: "Optionen",
|
||||
title: "Einstellungen",
|
||||
sections: {
|
||||
appearance: "Darstellung",
|
||||
transfers: "Übertragungen",
|
||||
editor: "Editor",
|
||||
fileList: "Dateiliste",
|
||||
about: "Über",
|
||||
},
|
||||
columns: "Sichtbare Spalten",
|
||||
language: "Sprache",
|
||||
system: "System",
|
||||
theme: { label: "Design", system: "System", light: "Hell", dark: "Dunkel" },
|
||||
@@ -248,6 +256,7 @@ export const de = {
|
||||
},
|
||||
menu: {
|
||||
about: "Über {app}",
|
||||
settings: "Einstellungen…",
|
||||
checkForUpdates: "Nach Updates suchen…",
|
||||
quit: "Beenden",
|
||||
file: "Datei",
|
||||
|
||||
+11
-2
@@ -27,14 +27,22 @@ export const en = {
|
||||
manageConnections: "Manage connections",
|
||||
newTab: "New tab ({mod}T)",
|
||||
closeTab: "Close tab ({mod}W)",
|
||||
options: "Options",
|
||||
options: "Settings",
|
||||
update: "Update available",
|
||||
updating: "Updating",
|
||||
restartToUpdate: "Restart to apply",
|
||||
updateTooltip: "Update available: miniSCP {version}",
|
||||
},
|
||||
options: {
|
||||
title: "Options",
|
||||
title: "Settings",
|
||||
sections: {
|
||||
appearance: "Appearance",
|
||||
transfers: "Transfers",
|
||||
editor: "Editor",
|
||||
fileList: "File list",
|
||||
about: "About",
|
||||
},
|
||||
columns: "Visible columns",
|
||||
language: "Language",
|
||||
system: "System",
|
||||
theme: { label: "Theme", system: "System", light: "Light", dark: "Dark" },
|
||||
@@ -252,6 +260,7 @@ export const en = {
|
||||
},
|
||||
menu: {
|
||||
about: "About {app}",
|
||||
settings: "Settings…",
|
||||
checkForUpdates: "Check for Updates…",
|
||||
quit: "Quit",
|
||||
file: "File",
|
||||
|
||||
+11
-2
@@ -23,14 +23,22 @@ export const es = {
|
||||
manageConnections: "Gestionar conexiones",
|
||||
newTab: "Nueva pestaña ({mod}T)",
|
||||
closeTab: "Cerrar pestaña ({mod}W)",
|
||||
options: "Opciones",
|
||||
options: "Ajustes",
|
||||
update: "Actualización disponible",
|
||||
updating: "Actualizando",
|
||||
restartToUpdate: "Reiniciar para aplicar",
|
||||
updateTooltip: "Actualización disponible: miniSCP {version}",
|
||||
},
|
||||
options: {
|
||||
title: "Opciones",
|
||||
title: "Ajustes",
|
||||
sections: {
|
||||
appearance: "Apariencia",
|
||||
transfers: "Transferencias",
|
||||
editor: "Editor",
|
||||
fileList: "Lista de archivos",
|
||||
about: "Acerca de",
|
||||
},
|
||||
columns: "Columnas visibles",
|
||||
language: "Idioma",
|
||||
system: "Sistema",
|
||||
theme: { label: "Tema", system: "Sistema", light: "Claro", dark: "Oscuro" },
|
||||
@@ -248,6 +256,7 @@ export const es = {
|
||||
},
|
||||
menu: {
|
||||
about: "Acerca de {app}",
|
||||
settings: "Ajustes…",
|
||||
checkForUpdates: "Buscar actualizaciones…",
|
||||
quit: "Salir",
|
||||
file: "Archivo",
|
||||
|
||||
+11
-2
@@ -25,14 +25,22 @@ export const eu = {
|
||||
manageConnections: "Kudeatu konexioak",
|
||||
newTab: "Fitxa berria ({mod}T)",
|
||||
closeTab: "Itxi fitxa ({mod}W)",
|
||||
options: "Aukerak",
|
||||
options: "Ezarpenak",
|
||||
update: "Eguneratzea eskuragarri",
|
||||
updating: "Eguneratzen",
|
||||
restartToUpdate: "Berrabiarazi aplikatzeko",
|
||||
updateTooltip: "Eguneratzea eskuragarri: miniSCP {version}",
|
||||
},
|
||||
options: {
|
||||
title: "Aukerak",
|
||||
title: "Ezarpenak",
|
||||
sections: {
|
||||
appearance: "Itxura",
|
||||
transfers: "Transferentziak",
|
||||
editor: "Editorea",
|
||||
fileList: "Fitxategi-zerrenda",
|
||||
about: "Honi buruz",
|
||||
},
|
||||
columns: "Zutabe ikusgaiak",
|
||||
language: "Hizkuntza",
|
||||
system: "Sistema",
|
||||
theme: { label: "Gaia", system: "Sistema", light: "Argia", dark: "Iluna" },
|
||||
@@ -250,6 +258,7 @@ export const eu = {
|
||||
},
|
||||
menu: {
|
||||
about: "{app} (r)i buruz",
|
||||
settings: "Ezarpenak…",
|
||||
checkForUpdates: "Bilatu eguneratzeak…",
|
||||
quit: "Irten",
|
||||
file: "Fitxategia",
|
||||
|
||||
+11
-2
@@ -23,14 +23,22 @@ export const fr = {
|
||||
manageConnections: "Gérer les connexions",
|
||||
newTab: "Nouvel onglet ({mod}T)",
|
||||
closeTab: "Fermer l'onglet ({mod}W)",
|
||||
options: "Options",
|
||||
options: "Réglages",
|
||||
update: "Mise à jour disponible",
|
||||
updating: "Mise à jour",
|
||||
restartToUpdate: "Redémarrer pour appliquer",
|
||||
updateTooltip: "Mise à jour disponible : miniSCP {version}",
|
||||
},
|
||||
options: {
|
||||
title: "Options",
|
||||
title: "Réglages",
|
||||
sections: {
|
||||
appearance: "Apparence",
|
||||
transfers: "Transferts",
|
||||
editor: "Éditeur",
|
||||
fileList: "Liste des fichiers",
|
||||
about: "À propos",
|
||||
},
|
||||
columns: "Colonnes visibles",
|
||||
language: "Langue",
|
||||
system: "Système",
|
||||
theme: { label: "Thème", system: "Système", light: "Clair", dark: "Sombre" },
|
||||
@@ -248,6 +256,7 @@ export const fr = {
|
||||
},
|
||||
menu: {
|
||||
about: "À propos de {app}",
|
||||
settings: "Réglages…",
|
||||
checkForUpdates: "Rechercher des mises à jour…",
|
||||
quit: "Quitter",
|
||||
file: "Fichier",
|
||||
|
||||
+11
-2
@@ -24,14 +24,22 @@ export const ja = {
|
||||
manageConnections: "接続を管理",
|
||||
newTab: "新しいタブ ({mod}T)",
|
||||
closeTab: "タブを閉じる ({mod}W)",
|
||||
options: "オプション",
|
||||
options: "設定",
|
||||
update: "アップデートあり",
|
||||
updating: "更新中",
|
||||
restartToUpdate: "再起動して適用",
|
||||
updateTooltip: "アップデートがあります: miniSCP {version}",
|
||||
},
|
||||
options: {
|
||||
title: "オプション",
|
||||
title: "設定",
|
||||
sections: {
|
||||
appearance: "外観",
|
||||
transfers: "転送",
|
||||
editor: "エディタ",
|
||||
fileList: "ファイル一覧",
|
||||
about: "情報",
|
||||
},
|
||||
columns: "表示する列",
|
||||
language: "言語",
|
||||
system: "システム",
|
||||
theme: { label: "テーマ", system: "システム", light: "ライト", dark: "ダーク" },
|
||||
@@ -248,6 +256,7 @@ export const ja = {
|
||||
},
|
||||
menu: {
|
||||
about: "{app} について",
|
||||
settings: "設定…",
|
||||
checkForUpdates: "アップデートを確認…",
|
||||
quit: "終了",
|
||||
file: "ファイル",
|
||||
|
||||
+11
-2
@@ -24,14 +24,22 @@ export const zh = {
|
||||
manageConnections: "管理连接",
|
||||
newTab: "新建标签页 ({mod}T)",
|
||||
closeTab: "关闭标签页 ({mod}W)",
|
||||
options: "选项",
|
||||
options: "设置",
|
||||
update: "有可用更新",
|
||||
updating: "正在更新",
|
||||
restartToUpdate: "重启以应用",
|
||||
updateTooltip: "有可用更新:miniSCP {version}",
|
||||
},
|
||||
options: {
|
||||
title: "选项",
|
||||
title: "设置",
|
||||
sections: {
|
||||
appearance: "外观",
|
||||
transfers: "传输",
|
||||
editor: "编辑器",
|
||||
fileList: "文件列表",
|
||||
about: "关于",
|
||||
},
|
||||
columns: "显示的列",
|
||||
language: "语言",
|
||||
system: "系统",
|
||||
theme: { label: "主题", system: "系统", light: "浅色", dark: "深色" },
|
||||
@@ -248,6 +256,7 @@ export const zh = {
|
||||
},
|
||||
menu: {
|
||||
about: "关于 {app}",
|
||||
settings: "设置…",
|
||||
checkForUpdates: "检查更新…",
|
||||
quit: "退出",
|
||||
file: "文件",
|
||||
|
||||
+6
-3
@@ -26,9 +26,9 @@ export interface EditorOpenContext {
|
||||
// bun.requests = calls the webview makes, handled in src/bun/rpc-handlers.ts
|
||||
// webview.messages = push events from bun, handled in ui rpc.service.ts
|
||||
//
|
||||
// ONE schema serves both window kinds (main + editor): electrobun allows
|
||||
// partial handler sets, and messages nobody listens to are silently dropped,
|
||||
// so each side only implements/hears what applies to it.
|
||||
// ONE schema serves every window kind (main, editor, manager, settings):
|
||||
// electrobun allows partial handler sets, and messages nobody listens to are
|
||||
// silently dropped, so each side only implements/hears what applies to it.
|
||||
export type AppRPC = {
|
||||
bun: {
|
||||
requests: {
|
||||
@@ -154,6 +154,9 @@ export type AppRPC = {
|
||||
managerConnect: { params: { id: string }; response: Result };
|
||||
managerOpenStack: { params: { id: string }; response: Result };
|
||||
managerClose: { params: Record<string, never>; response: Result };
|
||||
// settings window
|
||||
/** Main window: open (or focus) the single Settings window. */
|
||||
openSettings: { params: Record<string, never>; response: Result };
|
||||
// auto-updater (electrobun Updater)
|
||||
/** Main window: re-run the update check on demand (menu item). */
|
||||
checkForUpdate: {
|
||||
|
||||
Reference in New Issue
Block a user