107 lines
3.2 KiB
JavaScript
107 lines
3.2 KiB
JavaScript
// Single mutable state object + a plain pub/sub. No framework: components
|
|
// mutate `state` directly (or via the small helpers below) and then call
|
|
// notify() — every subscriber re-renders its own slice of the DOM from
|
|
// scratch. That's cheap at file-manager list sizes and keeps the mental
|
|
// model identical to the old egui immediate-mode AppState.
|
|
export const state = {
|
|
// Local pane
|
|
local: {
|
|
path: "",
|
|
entries: [],
|
|
selected: [], // names of selected entries, in no particular order
|
|
selectAnchor: null, // name shift-click range selection extends from
|
|
sort: { col: "name", dir: "asc" },
|
|
},
|
|
|
|
// One entry per open remote connection tab.
|
|
// { id, label, params, status, remotePath, remoteEntries, remoteSelected,
|
|
// remoteSelectAnchor, loading, sort: { col, dir } }
|
|
tabs: [],
|
|
activeTabId: null, // null = Local pane currently displayed, otherwise the displayed remote tab's id
|
|
// The remote connection toolbar actions (Upload, refresh, New Folder, ...)
|
|
// target. Unlike activeTabId this is NOT cleared when the Local pane is
|
|
// displayed -- switching to Local to pick a file to upload shouldn't lose
|
|
// track of which server it's going to, mirroring the old egui AppState
|
|
// where `active_tab` persisted across the Local dock tab gaining focus.
|
|
lastRemoteTabId: null,
|
|
activePane: "local", // 'local' | 'remote' — who last received a click (toolbar targets this)
|
|
|
|
// Split-pane layout. Each pane slot holds a tabId (null = Local, otherwise
|
|
// a remote connection id). The user can rearrange panes by dragging tabs.
|
|
layout: {
|
|
dir: "row", // 'row' | 'column' — direction of the split
|
|
panes: [
|
|
{ id: "pane-0", tabId: null }, // null = Local
|
|
],
|
|
},
|
|
|
|
sidebar: {
|
|
quickAccess: [
|
|
{ name: "Home", path: "" },
|
|
{ name: "Desktop", path: "" },
|
|
{ name: "Documents", path: "" },
|
|
{ name: "Downloads", path: "" },
|
|
{ name: "Pictures", path: "" },
|
|
{ name: "Music", path: "" },
|
|
{ name: "Videos", path: "" },
|
|
],
|
|
bookmarks: [],
|
|
},
|
|
|
|
history: [],
|
|
sites: [],
|
|
|
|
queueTasks: [],
|
|
showQueue: false,
|
|
|
|
statusMessage: "",
|
|
|
|
showConnectDialog: false,
|
|
connectLoading: false,
|
|
connectError: "",
|
|
|
|
showHistoryPopup: false,
|
|
|
|
opTarget: "local", // which side New Folder / Rename / Delete apply to
|
|
dialog: null, // { kind: 'mkdir'|'rename'|'delete', ... }
|
|
|
|
showSettingsDialog: false,
|
|
// Persisted (via get_pref/set_pref) + session-only (panel visibility)
|
|
// preferences -- see js/components/settingsDialog.js.
|
|
settings: {
|
|
language: "en",
|
|
theme: "ember",
|
|
confirmBeforeDelete: true,
|
|
defaultLocalFolder: "",
|
|
autoClearCompletedSecs: 0,
|
|
maxConcurrent: 2,
|
|
showToolbar: true,
|
|
showSidebar: true,
|
|
showStatusBar: true,
|
|
showQueuePanel: true,
|
|
},
|
|
};
|
|
|
|
const listeners = new Set();
|
|
|
|
export function notify() {
|
|
listeners.forEach((fn) => fn(state));
|
|
}
|
|
|
|
export function subscribe(fn) {
|
|
listeners.add(fn);
|
|
return () => listeners.delete(fn);
|
|
}
|
|
|
|
export function activeTab() {
|
|
return state.tabs.find((t) => t.id === state.lastRemoteTabId) ?? null;
|
|
}
|
|
|
|
export function isWelcome() {
|
|
return state.tabs.length === 0;
|
|
}
|
|
|
|
export function setStatus(message) {
|
|
state.statusMessage = message;
|
|
notify();
|
|
}
|