@nexus-ai-fs/tui 0.9.18
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/README.md +30 -0
- package/package.json +48 -0
- package/src/app.tsx +349 -0
- package/src/index.tsx +137 -0
- package/src/opentui-env.d.ts +61 -0
- package/src/panels/access/access-panel.tsx +597 -0
- package/src/panels/access/alert-list.tsx +77 -0
- package/src/panels/access/constraint-creator.tsx +128 -0
- package/src/panels/access/constraint-list.tsx +72 -0
- package/src/panels/access/credential-list.tsx +68 -0
- package/src/panels/access/delegation-chain-view.tsx +110 -0
- package/src/panels/access/delegation-completer.tsx +120 -0
- package/src/panels/access/delegation-creator.tsx +237 -0
- package/src/panels/access/delegation-list.tsx +74 -0
- package/src/panels/access/fraud-score-view.tsx +94 -0
- package/src/panels/access/manifest-creator.tsx +167 -0
- package/src/panels/access/manifest-list.tsx +105 -0
- package/src/panels/access/namespace-config-view.tsx +525 -0
- package/src/panels/access/permission-checker.tsx +231 -0
- package/src/panels/agents/agent-status-view.tsx +196 -0
- package/src/panels/agents/agents-panel.tsx +493 -0
- package/src/panels/agents/delegation-list.tsx +154 -0
- package/src/panels/agents/inbox-view.tsx +96 -0
- package/src/panels/agents/trajectories-tab.tsx +40 -0
- package/src/panels/api-console/api-console-panel.tsx +189 -0
- package/src/panels/api-console/codegen-viewer.tsx +36 -0
- package/src/panels/api-console/codegen.ts +112 -0
- package/src/panels/api-console/endpoint-list.tsx +57 -0
- package/src/panels/api-console/request-builder.tsx +69 -0
- package/src/panels/api-console/response-viewer.tsx +54 -0
- package/src/panels/connectors/available-tab.tsx +357 -0
- package/src/panels/connectors/connector-row.tsx +121 -0
- package/src/panels/connectors/connectors-panel.tsx +88 -0
- package/src/panels/connectors/error-parser.ts +116 -0
- package/src/panels/connectors/mounted-tab.tsx +179 -0
- package/src/panels/connectors/skills-tab.tsx +235 -0
- package/src/panels/connectors/template-generator.ts +211 -0
- package/src/panels/connectors/write-tab.tsx +514 -0
- package/src/panels/events/audit-tab.tsx +69 -0
- package/src/panels/events/audit-trail.tsx +75 -0
- package/src/panels/events/connector-detail.tsx +49 -0
- package/src/panels/events/connector-list.tsx +73 -0
- package/src/panels/events/connectors-tab.tsx +92 -0
- package/src/panels/events/event-replay.tsx +80 -0
- package/src/panels/events/events-panel.tsx +414 -0
- package/src/panels/events/events-tab.tsx +212 -0
- package/src/panels/events/lock-list.tsx +54 -0
- package/src/panels/events/locks-tab.tsx +103 -0
- package/src/panels/events/mcl-replay.tsx +77 -0
- package/src/panels/events/mcl-tab.tsx +83 -0
- package/src/panels/events/operations-tab-wrapper.tsx +62 -0
- package/src/panels/events/operations-tab.tsx +41 -0
- package/src/panels/events/replay-tab.tsx +76 -0
- package/src/panels/events/secrets-audit.tsx +64 -0
- package/src/panels/events/secrets-tab.tsx +75 -0
- package/src/panels/events/subscription-list.tsx +54 -0
- package/src/panels/events/subscriptions-tab.tsx +82 -0
- package/src/panels/files/file-aspects.tsx +93 -0
- package/src/panels/files/file-editor.tsx +160 -0
- package/src/panels/files/file-explorer-keybindings.ts +468 -0
- package/src/panels/files/file-explorer-panel.tsx +545 -0
- package/src/panels/files/file-lineage.tsx +163 -0
- package/src/panels/files/file-list-item.tsx +28 -0
- package/src/panels/files/file-metadata.tsx +62 -0
- package/src/panels/files/file-preview.tsx +108 -0
- package/src/panels/files/file-schema.tsx +89 -0
- package/src/panels/files/file-tree-node.tsx +44 -0
- package/src/panels/files/file-tree.tsx +169 -0
- package/src/panels/files/share-links-tab.tsx +33 -0
- package/src/panels/files/uploads-tab.tsx +45 -0
- package/src/panels/payments/approval-list.tsx +83 -0
- package/src/panels/payments/balance-card.tsx +43 -0
- package/src/panels/payments/budget-card.tsx +70 -0
- package/src/panels/payments/payments-panel.tsx +451 -0
- package/src/panels/payments/policy-list.tsx +64 -0
- package/src/panels/payments/reservation-list.tsx +78 -0
- package/src/panels/payments/transaction-list.tsx +103 -0
- package/src/panels/payments/transfer-form.tsx +109 -0
- package/src/panels/search/column-search.tsx +79 -0
- package/src/panels/search/knowledge-view.tsx +100 -0
- package/src/panels/search/memory-list.tsx +197 -0
- package/src/panels/search/playbook-list.tsx +77 -0
- package/src/panels/search/rlm-answer-view.tsx +105 -0
- package/src/panels/search/search-panel.tsx +405 -0
- package/src/panels/search/search-results.tsx +116 -0
- package/src/panels/stack/stack-panel.tsx +474 -0
- package/src/panels/versions/conflicts-tab.tsx +59 -0
- package/src/panels/versions/entry-detail.tsx +89 -0
- package/src/panels/versions/transaction-actions.tsx +34 -0
- package/src/panels/versions/transaction-list.tsx +90 -0
- package/src/panels/versions/versions-panel.tsx +276 -0
- package/src/panels/workflows/execution-list.tsx +102 -0
- package/src/panels/workflows/scheduler-view.tsx +135 -0
- package/src/panels/workflows/workflow-list.tsx +88 -0
- package/src/panels/workflows/workflows-panel.tsx +295 -0
- package/src/panels/zones/brick-detail.tsx +136 -0
- package/src/panels/zones/brick-list.tsx +56 -0
- package/src/panels/zones/cache-tab.tsx +118 -0
- package/src/panels/zones/drift-view.tsx +97 -0
- package/src/panels/zones/mcp-mounts-tab.tsx +38 -0
- package/src/panels/zones/memories-tab.tsx +37 -0
- package/src/panels/zones/reindex-status.tsx +84 -0
- package/src/panels/zones/workspaces-tab.tsx +37 -0
- package/src/panels/zones/zone-list.tsx +73 -0
- package/src/panels/zones/zones-panel.tsx +559 -0
- package/src/services/command-runner.ts +303 -0
- package/src/shared/accessibility-announcements.ts +44 -0
- package/src/shared/action-registry.ts +466 -0
- package/src/shared/brick-states.ts +91 -0
- package/src/shared/command-palette.ts +35 -0
- package/src/shared/components/announcement-bar.tsx +30 -0
- package/src/shared/components/app-confirm-dialog.tsx +29 -0
- package/src/shared/components/breadcrumb.tsx +21 -0
- package/src/shared/components/brick-gate.tsx +60 -0
- package/src/shared/components/command-output.tsx +95 -0
- package/src/shared/components/command-palette.tsx +97 -0
- package/src/shared/components/confirm-dialog.tsx +61 -0
- package/src/shared/components/diff-viewer.tsx +219 -0
- package/src/shared/components/empty-state.tsx +36 -0
- package/src/shared/components/error-bar.tsx +60 -0
- package/src/shared/components/error-boundary.tsx +53 -0
- package/src/shared/components/help-overlay.tsx +99 -0
- package/src/shared/components/identity-switcher.tsx +168 -0
- package/src/shared/components/loading-indicator.tsx +40 -0
- package/src/shared/components/pagination-bar.tsx +68 -0
- package/src/shared/components/pre-connection-screen.tsx +398 -0
- package/src/shared/components/scroll-indicator.tsx +46 -0
- package/src/shared/components/side-nav-utils.ts +68 -0
- package/src/shared/components/side-nav.tsx +287 -0
- package/src/shared/components/spinner.tsx +26 -0
- package/src/shared/components/status-bar.tsx +117 -0
- package/src/shared/components/styled-text.tsx +72 -0
- package/src/shared/components/sub-tab-bar-utils.ts +100 -0
- package/src/shared/components/sub-tab-bar.tsx +40 -0
- package/src/shared/components/tab-bar-utils.ts +36 -0
- package/src/shared/components/tab-bar.tsx +50 -0
- package/src/shared/components/text-input.tsx +73 -0
- package/src/shared/components/tooltip.tsx +53 -0
- package/src/shared/components/virtual-list.tsx +93 -0
- package/src/shared/components/welcome-screen.tsx +111 -0
- package/src/shared/hooks/use-api.ts +10 -0
- package/src/shared/hooks/use-brick-available.ts +42 -0
- package/src/shared/hooks/use-confirm.ts +66 -0
- package/src/shared/hooks/use-connection-state.ts +67 -0
- package/src/shared/hooks/use-copy.ts +31 -0
- package/src/shared/hooks/use-fresh-server.ts +62 -0
- package/src/shared/hooks/use-keyboard.ts +58 -0
- package/src/shared/hooks/use-list-navigation.ts +106 -0
- package/src/shared/hooks/use-swr.ts +117 -0
- package/src/shared/hooks/use-tab-fallback.ts +32 -0
- package/src/shared/hooks/use-text-input.ts +113 -0
- package/src/shared/hooks/use-visible-tabs.ts +61 -0
- package/src/shared/lib/circular-buffer.ts +82 -0
- package/src/shared/lib/clipboard.ts +14 -0
- package/src/shared/nav-items.ts +73 -0
- package/src/shared/navigation.ts +110 -0
- package/src/shared/status-breadcrumb.ts +74 -0
- package/src/shared/syntax-style.ts +3 -0
- package/src/shared/tab-visibility.ts +15 -0
- package/src/shared/text-style.ts +23 -0
- package/src/shared/theme.ts +179 -0
- package/src/shared/utils/format-size.ts +20 -0
- package/src/shared/utils/format-text.ts +10 -0
- package/src/shared/utils/format-time.ts +72 -0
- package/src/shared/utils/lru-cache.ts +75 -0
- package/src/stores/access-store-types.ts +154 -0
- package/src/stores/access-store.ts +674 -0
- package/src/stores/agents-store.ts +404 -0
- package/src/stores/announcement-store.ts +46 -0
- package/src/stores/api-console-store.ts +476 -0
- package/src/stores/connectors-store.ts +434 -0
- package/src/stores/create-api-action.ts +140 -0
- package/src/stores/delegation-store.ts +300 -0
- package/src/stores/error-store.ts +102 -0
- package/src/stores/events-store.ts +163 -0
- package/src/stores/files-store.ts +630 -0
- package/src/stores/first-run-store.ts +34 -0
- package/src/stores/global-store.ts +255 -0
- package/src/stores/infra-store.ts +461 -0
- package/src/stores/knowledge-store.ts +358 -0
- package/src/stores/lineage-store.ts +126 -0
- package/src/stores/mcp-store.ts +147 -0
- package/src/stores/payments-store.ts +545 -0
- package/src/stores/search-store-types.ts +155 -0
- package/src/stores/search-store.ts +656 -0
- package/src/stores/share-link-store.ts +151 -0
- package/src/stores/stack-store.ts +352 -0
- package/src/stores/ui-store.ts +161 -0
- package/src/stores/upload-store.ts +131 -0
- package/src/stores/versions-store.ts +355 -0
- package/src/stores/workflows-store.ts +402 -0
- package/src/stores/workspace-store.ts +185 -0
- package/src/stores/zones-store.ts +378 -0
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keybinding builders and help-text helpers for the file explorer panel.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from file-explorer-panel.tsx to separate keybinding logic
|
|
5
|
+
* from component rendering (Decision 6A).
|
|
6
|
+
*
|
|
7
|
+
* @see Issue #3101 — filter/search, bulk ops, move/copy
|
|
8
|
+
* @see Issue #3591 — split oversized TUI modules
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
useFilesStore,
|
|
13
|
+
type FileItem,
|
|
14
|
+
type TreeNode,
|
|
15
|
+
getEffectiveSelection,
|
|
16
|
+
} from "../../stores/files-store.js";
|
|
17
|
+
import {
|
|
18
|
+
listNavigationBindings,
|
|
19
|
+
} from "../../shared/hooks/use-list-navigation.js";
|
|
20
|
+
import { subTabCycleBindings } from "../../shared/components/sub-tab-bar-utils.js";
|
|
21
|
+
import type { TabDef } from "../../shared/hooks/use-visible-tabs.js";
|
|
22
|
+
import type { useApi } from "../../shared/hooks/use-api.js";
|
|
23
|
+
|
|
24
|
+
// =============================================================================
|
|
25
|
+
// Panel-level tabs
|
|
26
|
+
// =============================================================================
|
|
27
|
+
|
|
28
|
+
export type FilesTab = "explorer" | "shareLinks" | "uploads";
|
|
29
|
+
|
|
30
|
+
// =============================================================================
|
|
31
|
+
// Input mode types
|
|
32
|
+
// =============================================================================
|
|
33
|
+
|
|
34
|
+
export type InputMode = "none" | "mkdir" | "rename" | "filter" | "search" | "paste-dest" | "create";
|
|
35
|
+
|
|
36
|
+
// =============================================================================
|
|
37
|
+
// Keybinding builders — one function per mode (Decision 6A)
|
|
38
|
+
// =============================================================================
|
|
39
|
+
|
|
40
|
+
export interface BindingContext {
|
|
41
|
+
// Active tab
|
|
42
|
+
readonly activeTab: FilesTab;
|
|
43
|
+
// Explorer
|
|
44
|
+
readonly cachedFiles: readonly FileItem[];
|
|
45
|
+
readonly selectedIndex: number;
|
|
46
|
+
readonly selectedItem: FileItem | null;
|
|
47
|
+
readonly selectedNode: TreeNode | null;
|
|
48
|
+
readonly isSentinel: boolean;
|
|
49
|
+
readonly visibleNodeCount: number;
|
|
50
|
+
readonly currentPath: string;
|
|
51
|
+
readonly client: ReturnType<typeof useApi>;
|
|
52
|
+
// Stores
|
|
53
|
+
readonly setSelectedIndex: (i: number) => void;
|
|
54
|
+
readonly toggleNode: (path: string, client: NonNullable<ReturnType<typeof useApi>>) => Promise<void>;
|
|
55
|
+
readonly collapseNode: (path: string) => void;
|
|
56
|
+
readonly fetchPreview: (path: string, client: NonNullable<ReturnType<typeof useApi>>) => Promise<void>;
|
|
57
|
+
readonly setMetadataTab: (tab: "metadata" | "aspects" | "schema" | "lineage") => void;
|
|
58
|
+
readonly catalogAvailable: boolean;
|
|
59
|
+
// Share links
|
|
60
|
+
readonly shareLinks: readonly { link_id: string; status: string }[];
|
|
61
|
+
readonly selectedLinkIndex: number;
|
|
62
|
+
readonly setSelectedLinkIndex: (i: number) => void;
|
|
63
|
+
readonly revokeLink: (id: string, client: NonNullable<ReturnType<typeof useApi>>) => void;
|
|
64
|
+
readonly fetchLinks: (client: NonNullable<ReturnType<typeof useApi>>) => void;
|
|
65
|
+
// Uploads
|
|
66
|
+
readonly uploadSessions: readonly unknown[];
|
|
67
|
+
readonly selectedSessionIndex: number;
|
|
68
|
+
readonly setSelectedSessionIndex: (i: number) => void;
|
|
69
|
+
// Tabs
|
|
70
|
+
readonly visibleTabs: readonly TabDef<FilesTab>[];
|
|
71
|
+
readonly setActiveTab: (tab: FilesTab) => void;
|
|
72
|
+
readonly toggleFocus: (panel: string) => void;
|
|
73
|
+
// Actions
|
|
74
|
+
readonly copy: (text: string) => void;
|
|
75
|
+
readonly setConfirmDelete: (v: boolean) => void;
|
|
76
|
+
readonly setInputMode: (mode: InputMode) => void;
|
|
77
|
+
readonly setInputBuffer: (v: string | ((prev: string) => string)) => void;
|
|
78
|
+
// Selection & clipboard
|
|
79
|
+
readonly selectedPaths: ReadonlySet<string>;
|
|
80
|
+
readonly visualModeAnchor: number | null;
|
|
81
|
+
readonly clipboard: { readonly paths: readonly string[]; readonly operation: "copy" | "cut" } | null;
|
|
82
|
+
readonly toggleSelect: (path: string) => void;
|
|
83
|
+
readonly clearSelection: () => void;
|
|
84
|
+
readonly enterVisualMode: (anchor: number) => void;
|
|
85
|
+
readonly exitVisualMode: () => void;
|
|
86
|
+
readonly yankToClipboard: (paths: readonly string[]) => void;
|
|
87
|
+
readonly cutToClipboard: (paths: readonly string[]) => void;
|
|
88
|
+
readonly clearClipboard: () => void;
|
|
89
|
+
readonly pasteFiles: (destinationDir: string, client: NonNullable<ReturnType<typeof useApi>>) => Promise<void>;
|
|
90
|
+
// Filter
|
|
91
|
+
readonly filterQuery: string;
|
|
92
|
+
readonly setFilterQuery: (v: string | ((prev: string) => string)) => void;
|
|
93
|
+
readonly searchQuery: string;
|
|
94
|
+
readonly setSearchQuery: (v: string | ((prev: string) => string)) => void;
|
|
95
|
+
readonly executeSearch: (query: string) => void;
|
|
96
|
+
// Search results
|
|
97
|
+
readonly searchResults: readonly { path: string; line?: number; content?: string }[] | null;
|
|
98
|
+
readonly setSearchResults: (v: readonly { path: string; line?: number; content?: string }[] | null) => void;
|
|
99
|
+
// Paste destination input
|
|
100
|
+
readonly setInputModeWithCallback: (mode: InputMode, onSubmit: (value: string) => void) => void;
|
|
101
|
+
// Editor
|
|
102
|
+
readonly openEditor: (path: string) => void;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Navigation bindings for the currently active tab (Decision 5A). */
|
|
106
|
+
function getTabNavBindings(ctx: BindingContext): Record<string, () => void> {
|
|
107
|
+
switch (ctx.activeTab) {
|
|
108
|
+
case "explorer":
|
|
109
|
+
return {
|
|
110
|
+
...listNavigationBindings({
|
|
111
|
+
getIndex: () => ctx.selectedIndex,
|
|
112
|
+
setIndex: ctx.setSelectedIndex,
|
|
113
|
+
getLength: () => ctx.visibleNodeCount,
|
|
114
|
+
onSelect: (index) => {
|
|
115
|
+
// Sentinel nodes are handled by FileTree's auto-load effect
|
|
116
|
+
if (ctx.isSentinel) return;
|
|
117
|
+
if (ctx.selectedNode && ctx.client) {
|
|
118
|
+
if (ctx.selectedNode.isDirectory) {
|
|
119
|
+
ctx.toggleNode(ctx.selectedNode.path, ctx.client);
|
|
120
|
+
} else {
|
|
121
|
+
ctx.fetchPreview(ctx.selectedNode.path, ctx.client);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
}),
|
|
126
|
+
// g = jump to start (not in listNavigationBindings)
|
|
127
|
+
g: () => ctx.setSelectedIndex(0),
|
|
128
|
+
};
|
|
129
|
+
case "shareLinks":
|
|
130
|
+
return {
|
|
131
|
+
...listNavigationBindings({
|
|
132
|
+
getIndex: () => ctx.selectedLinkIndex,
|
|
133
|
+
setIndex: ctx.setSelectedLinkIndex,
|
|
134
|
+
getLength: () => ctx.shareLinks.length,
|
|
135
|
+
}),
|
|
136
|
+
g: () => ctx.setSelectedLinkIndex(0),
|
|
137
|
+
};
|
|
138
|
+
case "uploads":
|
|
139
|
+
return {
|
|
140
|
+
...listNavigationBindings({
|
|
141
|
+
getIndex: () => ctx.selectedSessionIndex,
|
|
142
|
+
setIndex: ctx.setSelectedSessionIndex,
|
|
143
|
+
getLength: () => ctx.uploadSessions.length,
|
|
144
|
+
}),
|
|
145
|
+
g: () => ctx.setSelectedSessionIndex(0),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Tab cycling (shared across all modes). */
|
|
151
|
+
function getTabCycleBindings(ctx: BindingContext): Record<string, () => void> {
|
|
152
|
+
return {
|
|
153
|
+
...subTabCycleBindings(ctx.visibleTabs, ctx.activeTab, ctx.setActiveTab),
|
|
154
|
+
"shift+tab": () => ctx.toggleFocus("files"),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Explorer-specific action bindings (not navigation). */
|
|
159
|
+
function getExplorerActionBindings(ctx: BindingContext): Record<string, () => void> {
|
|
160
|
+
return {
|
|
161
|
+
// Tree navigation
|
|
162
|
+
l: () => {
|
|
163
|
+
if (ctx.isSentinel) return;
|
|
164
|
+
if (ctx.selectedNode?.isDirectory && ctx.client) ctx.toggleNode(ctx.selectedNode.path, ctx.client);
|
|
165
|
+
},
|
|
166
|
+
h: () => {
|
|
167
|
+
if (ctx.isSentinel) return;
|
|
168
|
+
if (ctx.selectedNode?.isDirectory) ctx.collapseNode(ctx.selectedNode.path);
|
|
169
|
+
},
|
|
170
|
+
// Metadata tabs
|
|
171
|
+
m: () => ctx.setMetadataTab("metadata"),
|
|
172
|
+
"shift+l": () => ctx.setMetadataTab("lineage"),
|
|
173
|
+
...(ctx.catalogAvailable ? {
|
|
174
|
+
a: () => ctx.setMetadataTab("aspects"),
|
|
175
|
+
s: () => ctx.setMetadataTab("schema"),
|
|
176
|
+
} : {}),
|
|
177
|
+
// File operations — bulk delete if selection active, otherwise single item
|
|
178
|
+
d: () => {
|
|
179
|
+
if (ctx.isSentinel) return;
|
|
180
|
+
const effective = getEffectiveSelection(
|
|
181
|
+
ctx.selectedPaths, ctx.visualModeAnchor, ctx.selectedIndex,
|
|
182
|
+
ctx.cachedFiles.map((f) => f.path),
|
|
183
|
+
);
|
|
184
|
+
if (effective.size > 0 || ctx.selectedItem) {
|
|
185
|
+
ctx.setConfirmDelete(true);
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
"shift+n": () => { ctx.setInputMode("mkdir"); ctx.setInputBuffer(""); },
|
|
189
|
+
"shift+r": () => {
|
|
190
|
+
if (ctx.isSentinel) return;
|
|
191
|
+
if (ctx.selectedItem) {
|
|
192
|
+
ctx.setInputMode("rename");
|
|
193
|
+
ctx.setInputBuffer(ctx.selectedItem.name);
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
// Edit existing file: open full-screen editor
|
|
197
|
+
e: () => {
|
|
198
|
+
if (ctx.selectedItem && !ctx.selectedItem.isDirectory) {
|
|
199
|
+
ctx.openEditor(ctx.selectedItem.path);
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
// Create new file: prompt for filename, then open editor
|
|
203
|
+
"shift+e": () => {
|
|
204
|
+
const dir = ctx.selectedItem?.isDirectory
|
|
205
|
+
? ctx.selectedItem.path
|
|
206
|
+
: ctx.currentPath;
|
|
207
|
+
const prefix = dir === "/" ? "/" : dir + "/";
|
|
208
|
+
ctx.setInputMode("create");
|
|
209
|
+
ctx.setInputBuffer(prefix);
|
|
210
|
+
},
|
|
211
|
+
// Copy path to system clipboard
|
|
212
|
+
y: () => {
|
|
213
|
+
if (ctx.isSentinel) return;
|
|
214
|
+
if (ctx.selectedItem) ctx.copy(ctx.selectedItem.path);
|
|
215
|
+
},
|
|
216
|
+
// Selection
|
|
217
|
+
space: () => {
|
|
218
|
+
if (ctx.isSentinel) return;
|
|
219
|
+
const item = ctx.cachedFiles[ctx.selectedIndex];
|
|
220
|
+
if (item) ctx.toggleSelect(item.path);
|
|
221
|
+
},
|
|
222
|
+
// Visual mode
|
|
223
|
+
v: () => {
|
|
224
|
+
if (ctx.visualModeAnchor !== null) {
|
|
225
|
+
ctx.exitVisualMode();
|
|
226
|
+
} else {
|
|
227
|
+
ctx.enterVisualMode(ctx.selectedIndex);
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
// Clipboard: copy/cut/paste
|
|
231
|
+
c: () => {
|
|
232
|
+
const effective = getEffectiveSelection(
|
|
233
|
+
ctx.selectedPaths, ctx.visualModeAnchor, ctx.selectedIndex,
|
|
234
|
+
ctx.cachedFiles.map((f) => f.path),
|
|
235
|
+
);
|
|
236
|
+
if (effective.size > 0) {
|
|
237
|
+
ctx.yankToClipboard([...effective]);
|
|
238
|
+
ctx.clearSelection();
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
x: () => {
|
|
242
|
+
const effective = getEffectiveSelection(
|
|
243
|
+
ctx.selectedPaths, ctx.visualModeAnchor, ctx.selectedIndex,
|
|
244
|
+
ctx.cachedFiles.map((f) => f.path),
|
|
245
|
+
);
|
|
246
|
+
if (effective.size > 0) {
|
|
247
|
+
ctx.cutToClipboard([...effective]);
|
|
248
|
+
ctx.clearSelection();
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
p: () => {
|
|
252
|
+
if (ctx.clipboard && ctx.client) {
|
|
253
|
+
ctx.pasteFiles(ctx.currentPath, ctx.client);
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
// Shift+P: paste to a specific path (prompts for destination)
|
|
257
|
+
"shift+p": () => {
|
|
258
|
+
if (ctx.clipboard && ctx.client) {
|
|
259
|
+
ctx.setInputMode("paste-dest");
|
|
260
|
+
ctx.setInputBuffer(ctx.currentPath);
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
// Escape: dismiss search results > exit visual mode > clear selection
|
|
264
|
+
escape: () => {
|
|
265
|
+
if (ctx.searchResults !== null) {
|
|
266
|
+
ctx.setSearchResults(null);
|
|
267
|
+
} else if (ctx.visualModeAnchor !== null) {
|
|
268
|
+
ctx.exitVisualMode();
|
|
269
|
+
} else if (ctx.selectedPaths.size > 0) {
|
|
270
|
+
ctx.clearSelection();
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
// Filter & search modes
|
|
274
|
+
"/": () => { ctx.setInputMode("filter"); ctx.setInputBuffer(""); },
|
|
275
|
+
"ctrl+f": () => { ctx.setInputMode("search"); ctx.setInputBuffer(""); },
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** ShareLinks-specific action bindings. */
|
|
280
|
+
function getShareLinksActionBindings(ctx: BindingContext): Record<string, () => void> {
|
|
281
|
+
return {
|
|
282
|
+
x: () => {
|
|
283
|
+
if (ctx.client) {
|
|
284
|
+
const link = ctx.shareLinks[ctx.selectedLinkIndex] as { link_id: string; status: string } | undefined;
|
|
285
|
+
if (link && link.status === "active") {
|
|
286
|
+
ctx.revokeLink(link.link_id, ctx.client);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
r: () => { if (ctx.client) ctx.fetchLinks(ctx.client); },
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** Input mode bindings (mkdir, rename, filter, search). */
|
|
295
|
+
function getInputModeBindings(
|
|
296
|
+
inputMode: InputMode,
|
|
297
|
+
ctx: BindingContext,
|
|
298
|
+
): Record<string, () => void> {
|
|
299
|
+
const resetInput = () => {
|
|
300
|
+
ctx.setInputMode("none");
|
|
301
|
+
ctx.setInputBuffer("");
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
const baseBindings: Record<string, () => void> = {
|
|
305
|
+
escape: resetInput,
|
|
306
|
+
backspace: () => ctx.setInputBuffer((b) => b.slice(0, -1)),
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
switch (inputMode) {
|
|
310
|
+
case "mkdir":
|
|
311
|
+
return {
|
|
312
|
+
...baseBindings,
|
|
313
|
+
return: () => {
|
|
314
|
+
const value = ctx.filterQuery.trim(); // inputBuffer is used but accessed via closure
|
|
315
|
+
if (!value || !ctx.client) { resetInput(); return; }
|
|
316
|
+
const dirPath = ctx.currentPath === "/" ? `/${value}` : `${ctx.currentPath}/${value}`;
|
|
317
|
+
useFilesStore.getState().mkdirFile(dirPath, ctx.client);
|
|
318
|
+
resetInput();
|
|
319
|
+
},
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
case "rename":
|
|
323
|
+
return {
|
|
324
|
+
...baseBindings,
|
|
325
|
+
return: () => {
|
|
326
|
+
const value = ctx.filterQuery.trim();
|
|
327
|
+
if (!value || !ctx.client || !ctx.selectedItem) { resetInput(); return; }
|
|
328
|
+
const parentPath = ctx.selectedItem.path.split("/").slice(0, -1).join("/") || "/";
|
|
329
|
+
const newPath = parentPath === "/" ? `/${value}` : `${parentPath}/${value}`;
|
|
330
|
+
useFilesStore.getState().renameFile(ctx.selectedItem.path, newPath, ctx.client);
|
|
331
|
+
resetInput();
|
|
332
|
+
},
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
case "filter":
|
|
336
|
+
return {
|
|
337
|
+
...baseBindings,
|
|
338
|
+
return: resetInput, // confirm filter and return to normal mode (filter stays applied)
|
|
339
|
+
escape: () => {
|
|
340
|
+
ctx.setFilterQuery("");
|
|
341
|
+
resetInput();
|
|
342
|
+
},
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
case "search":
|
|
346
|
+
return {
|
|
347
|
+
...baseBindings,
|
|
348
|
+
return: () => {
|
|
349
|
+
const query = ctx.searchQuery.trim();
|
|
350
|
+
if (query) ctx.executeSearch(query);
|
|
351
|
+
resetInput();
|
|
352
|
+
},
|
|
353
|
+
escape: () => {
|
|
354
|
+
ctx.setSearchQuery("");
|
|
355
|
+
resetInput();
|
|
356
|
+
},
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
case "paste-dest":
|
|
360
|
+
return {
|
|
361
|
+
...baseBindings,
|
|
362
|
+
return: () => {
|
|
363
|
+
const dest = ctx.filterQuery.trim();
|
|
364
|
+
if (dest && ctx.client) {
|
|
365
|
+
useFilesStore.getState().pasteFiles(dest, ctx.client);
|
|
366
|
+
}
|
|
367
|
+
resetInput();
|
|
368
|
+
},
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
case "create":
|
|
372
|
+
return {
|
|
373
|
+
...baseBindings,
|
|
374
|
+
return: () => {
|
|
375
|
+
const filePath = ctx.filterQuery.trim();
|
|
376
|
+
if (!filePath) { resetInput(); return; }
|
|
377
|
+
// Open editor for the new path (editor handles creation on save)
|
|
378
|
+
ctx.openEditor(filePath);
|
|
379
|
+
resetInput();
|
|
380
|
+
},
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
default:
|
|
384
|
+
return {};
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/** Top-level binding dispatch based on current mode. */
|
|
389
|
+
export function getKeyBindings(
|
|
390
|
+
inputMode: InputMode,
|
|
391
|
+
overlayActive: boolean,
|
|
392
|
+
confirmDelete: boolean,
|
|
393
|
+
editorOpen: boolean,
|
|
394
|
+
ctx: BindingContext,
|
|
395
|
+
): Record<string, () => void> {
|
|
396
|
+
if (overlayActive || confirmDelete || editorOpen) return {};
|
|
397
|
+
|
|
398
|
+
if (inputMode !== "none") {
|
|
399
|
+
return getInputModeBindings(inputMode, ctx);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Normal mode: navigation + tab cycling + tab-specific actions
|
|
403
|
+
const navBindings = getTabNavBindings(ctx);
|
|
404
|
+
const tabBindings = getTabCycleBindings(ctx);
|
|
405
|
+
|
|
406
|
+
const actionBindings = ctx.activeTab === "explorer"
|
|
407
|
+
? getExplorerActionBindings(ctx)
|
|
408
|
+
: ctx.activeTab === "shareLinks"
|
|
409
|
+
? getShareLinksActionBindings(ctx)
|
|
410
|
+
: {};
|
|
411
|
+
|
|
412
|
+
return { ...navBindings, ...tabBindings, ...actionBindings };
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// =============================================================================
|
|
416
|
+
// Input bar label per mode
|
|
417
|
+
// =============================================================================
|
|
418
|
+
|
|
419
|
+
export function getInputLabel(mode: InputMode, buffer: string): string {
|
|
420
|
+
switch (mode) {
|
|
421
|
+
case "mkdir": return `New directory: ${buffer}\u2588`;
|
|
422
|
+
case "rename": return `Rename to: ${buffer}\u2588`;
|
|
423
|
+
case "filter": return `/${buffer}\u2588`;
|
|
424
|
+
case "search": return `Search (g: glob, r: grep): ${buffer}\u2588`;
|
|
425
|
+
case "paste-dest": return `Paste to: ${buffer}\u2588`;
|
|
426
|
+
case "create": return `New file path: ${buffer}\u2588`;
|
|
427
|
+
default: return "";
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// =============================================================================
|
|
432
|
+
// Help bar text
|
|
433
|
+
// =============================================================================
|
|
434
|
+
|
|
435
|
+
export function getHelpText(
|
|
436
|
+
inputMode: InputMode,
|
|
437
|
+
activeTab: FilesTab,
|
|
438
|
+
catalogAvailable: boolean,
|
|
439
|
+
visualMode: boolean,
|
|
440
|
+
selectionCount: number,
|
|
441
|
+
clipboard: BindingContext["clipboard"],
|
|
442
|
+
): string {
|
|
443
|
+
if (inputMode === "filter") return "Type to filter, Enter:keep filter, Escape:clear";
|
|
444
|
+
if (inputMode === "search") return "g:pattern=glob r:pattern=grep plain=deep search Enter:search Esc:cancel";
|
|
445
|
+
if (inputMode === "paste-dest") return "Enter path, Enter:paste, Escape:cancel";
|
|
446
|
+
if (inputMode !== "none") return "Type name, Enter:confirm, Escape:cancel, Backspace:delete";
|
|
447
|
+
|
|
448
|
+
if (activeTab === "explorer") {
|
|
449
|
+
const parts = ["j/k:nav", "l/Enter:expand", "h:collapse"];
|
|
450
|
+
if (visualMode) {
|
|
451
|
+
parts.push("v:exit visual", "c:copy", "x:cut");
|
|
452
|
+
} else if (selectionCount > 0) {
|
|
453
|
+
parts.push(`${selectionCount} selected`, "c:copy", "x:cut", "Esc:clear");
|
|
454
|
+
} else {
|
|
455
|
+
parts.push("/:filter", "Ctrl+F:search", "v:visual", "Space:select");
|
|
456
|
+
}
|
|
457
|
+
if (clipboard) {
|
|
458
|
+
parts.push(`p:paste ${clipboard.paths.length} ${clipboard.operation === "cut" ? "cut" : "copied"}`, "P:paste to path");
|
|
459
|
+
}
|
|
460
|
+
parts.push("d:del", "N:mkdir", "R:rename", "e:edit", "E:new file");
|
|
461
|
+
if (catalogAvailable) parts.push("m/a/s:meta");
|
|
462
|
+
parts.push("?:help");
|
|
463
|
+
return parts.join(" ");
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
if (activeTab === "shareLinks") return "j/k:navigate x:revoke r:refresh Tab:switch tab q:quit";
|
|
467
|
+
return "j/k:navigate Tab:switch tab q:quit";
|
|
468
|
+
}
|