The dual-skin arrangement existed to let one build toggle between the modern
shadcn look and 7.css. Keeping both correct is what forced the cascade tricks
that fight 7.css, so the branch drops the toggle: this build IS the Windows 7
build.
- `skin` is gone from AppSettings/DEFAULT_SETTINGS, the settings store, the
SettingsService (signal, applySkin, persist payload), the options popover
control, and the i18n keys.
- buildPreload adds `.win7` unconditionally; `chromeOptions()` no longer takes a
skin and always returns frameless `titleBarStyle: "hidden"`.
- `IS_WIN7_CHROME` and `IS_MAC_CHROME` are deleted along with every
`isMacChrome` branch in the tab bar, editor window and connection manager.
Those reserved `pl-24` for macOS traffic lights, which frameless chrome
removes on every platform, so the branches were dead by construction.
- The title bar is no longer conditional — it is the window's only chrome.
- index.html keeps just the win7 anti-flash arm (four theme states).
Not done here, deliberately: stripping Tailwind's element styling. The
neutralizers in styles.css (notably `.win7 button { min-width: 0 }`) are still
load-bearing until each component's markup is converted to 7.css's real
contract, so they come out per component rather than in one sweep.
Verified: backend `tsc --noEmit` clean, `bun run build:ui` clean; bundle
490.50 kB, down from 492.52 kB.
Co-Authored-By: Claude <noreply@anthropic.com>
156 lines
4.6 KiB
TypeScript
156 lines
4.6 KiB
TypeScript
export type EntryType = "file" | "dir" | "symlink";
|
|
|
|
export interface FileEntry {
|
|
name: string;
|
|
path: string; // absolute path on its filesystem
|
|
type: EntryType;
|
|
size: number; // bytes
|
|
mtime: number; // epoch milliseconds
|
|
mode: number; // permission bits only (masked & 0o7777)
|
|
owner?: string; // resolved name when the provider knows it
|
|
group?: string;
|
|
}
|
|
|
|
/** Optional file-list columns (Name is always shown). */
|
|
export type ColumnKey = "size" | "mtime" | "kind" | "rights" | "owner" | "group";
|
|
|
|
/** Every RPC response uses this envelope; errors never cross the bridge as exceptions. */
|
|
export type Result<T = object> = ({ ok: true } & T) | { ok: false; error: string };
|
|
|
|
export type AuthConfig =
|
|
| { kind: "password"; password: string }
|
|
| { kind: "key"; privateKeyPath: string; passphrase?: string }
|
|
| { kind: "agent" };
|
|
|
|
export interface ConnectionConfig {
|
|
host: string;
|
|
port: number;
|
|
username: string;
|
|
auth: AuthConfig;
|
|
initialPath?: string | null;
|
|
}
|
|
|
|
export interface SavedConnection extends ConnectionConfig {
|
|
id: string;
|
|
stackId: string | null;
|
|
name: string;
|
|
/** Other-panel local dir applied when that panel is local and untouched. */
|
|
localDir?: string | null;
|
|
}
|
|
|
|
/** A named group of connections that can be opened together as tabs. */
|
|
export interface Stack {
|
|
id: string;
|
|
name: string;
|
|
/** Collapsed in the connection-manager list (UI state, persisted). */
|
|
collapsed?: boolean;
|
|
}
|
|
|
|
/** A concrete host alias resolved from ~/.ssh/config (read-only). */
|
|
export interface SshConfigHost {
|
|
name: string; // the Host alias as written
|
|
hostname: string; // resolved HostName (defaults to alias)
|
|
port: number;
|
|
username: string;
|
|
identityFile?: string; // first existing IdentityFile, absolute path
|
|
unsupported?: "proxy"; // ProxyJump/ProxyCommand present — direct connect can't work
|
|
}
|
|
|
|
export interface ConnectionsFile {
|
|
version: 2;
|
|
stacks: Stack[];
|
|
connections: SavedConnection[];
|
|
}
|
|
|
|
/** UI languages the app ships translations for. */
|
|
export type SupportedLocale = "en" | "es" | "eu" | "fr" | "de" | "zh" | "ja";
|
|
|
|
export type AccentTone =
|
|
| "graphite"
|
|
| "blue"
|
|
| "indigo"
|
|
| "purple"
|
|
| "rose"
|
|
| "orange"
|
|
| "green"
|
|
| "teal";
|
|
|
|
export interface AppSettings {
|
|
/** UI language; "system" resolves to the OS locale (fallback English). */
|
|
locale: "system" | SupportedLocale;
|
|
theme: "system" | "light" | "dark";
|
|
/** Dark appearance: grey ("dark") or near-black AMOLED ("darker"). */
|
|
darkTone: "dark" | "darker";
|
|
/** Accent tone for chrome and selection tint; graphite = neutral zinc. */
|
|
accent: AccentTone;
|
|
/** File-list icons: plain neutral glyphs, or per-category glyphs and colors. */
|
|
iconColors: "plain" | "multi";
|
|
/** Visible optional file-list columns (both panels; Name always shows). */
|
|
columns: ColumnKey[];
|
|
speedUnit: "MBps" | "Mbps";
|
|
/** Size of the panel interface: file list, path bar, header controls. */
|
|
textSize: "compact" | "default" | "large";
|
|
/** Run transfer jobs one at a time, or several in parallel. */
|
|
transferMode: "single" | "multi";
|
|
/** How many jobs run at once when transferMode is "multi". */
|
|
transferConcurrency: number;
|
|
/** Remove completed transfers from the list automatically. */
|
|
autoClearCompleted: boolean;
|
|
/** Edit files with the built-in editor or an external application. */
|
|
editor: "builtin" | "custom";
|
|
/** Full path to the external editor .app; empty = OS default application. */
|
|
editorApp: string;
|
|
// Auto-refresh the active tab's panels (local watched, remote polled). Always
|
|
// on for now; uncomment (+ the settings.service and tab-bar breadcrumbs, and
|
|
// gate the poll/watch in panels.service) to expose a toggle later.
|
|
// autoRefresh: boolean;
|
|
}
|
|
|
|
export const DEFAULT_SETTINGS: AppSettings = {
|
|
locale: "system",
|
|
theme: "system",
|
|
darkTone: "dark",
|
|
accent: "blue",
|
|
iconColors: "multi",
|
|
columns: ["size", "mtime"],
|
|
speedUnit: "MBps",
|
|
textSize: "default",
|
|
transferMode: "single",
|
|
transferConcurrency: 3,
|
|
autoClearCompleted: true,
|
|
editor: "builtin",
|
|
editorApp: "",
|
|
// autoRefresh: true,
|
|
};
|
|
|
|
export function effectiveConcurrency(settings: AppSettings): number {
|
|
if (settings.transferMode === "single") return 1;
|
|
return Math.max(1, Math.min(8, Math.round(settings.transferConcurrency) || 1));
|
|
}
|
|
|
|
export type TransferState =
|
|
| "queued"
|
|
| "scanning"
|
|
| "running"
|
|
| "completed"
|
|
| "failed"
|
|
| "cancelled";
|
|
|
|
export interface TransferSnapshot {
|
|
id: string;
|
|
name: string; // display name (basename of source)
|
|
srcSessionId: string;
|
|
srcPath: string;
|
|
dstSessionId: string;
|
|
dstDir: string;
|
|
state: TransferState;
|
|
totalFiles: number;
|
|
totalBytes: number;
|
|
doneFiles: number;
|
|
doneBytes: number;
|
|
currentFile: string;
|
|
currentFileBytes: number;
|
|
currentFileTotal: number;
|
|
error?: string;
|
|
}
|