@cueloop/client 0.1.0-alpha.56 → 0.1.0-alpha.59
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/package.json +5 -5
- package/src/App.tsx +174 -574
- package/src/app-key-state.ts +60 -0
- package/src/app-screens.tsx +266 -0
- package/src/app-view-model.ts +286 -0
- package/src/components/PlanSheet.tsx +2 -62
- package/src/components/PrototypeSheet.tsx +19 -2
- package/src/components/plan-sheet-run-style.ts +86 -0
- package/src/config.ts +61 -47
- package/src/intent-dispatch.ts +427 -307
- package/src/keymap.ts +81 -59
- package/src/prototype-browser.ts +6 -1
- package/src/use-settings-dialog.tsx +263 -0
package/src/keymap.ts
CHANGED
|
@@ -122,65 +122,8 @@ function status(message: string): Intent[] {
|
|
|
122
122
|
*/
|
|
123
123
|
export function reduceKey(state: KeyState, key: KeyInput, resolvedAction?: string): Intent[] {
|
|
124
124
|
const name = key.name;
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if (state.overlay === "compose" || state.overlay === "submit") {
|
|
128
|
-
if (name === "escape") return [{ type: "closeOverlay" }];
|
|
129
|
-
if (name === "return" || name === "enter") {
|
|
130
|
-
// In the composer, ⌥/Alt+⏎ (meta) and shift+⏎ insert a newline - the
|
|
131
|
-
// focused textarea owns that; only a bare ⏎ saves. The submit overlay
|
|
132
|
-
// keeps its plain ⏎ submit.
|
|
133
|
-
if (state.overlay === "compose") {
|
|
134
|
-
if (key.shift || key.meta) return [];
|
|
135
|
-
return [{ type: "saveCompose" }];
|
|
136
|
-
}
|
|
137
|
-
return [{ type: "submitVerdict" }];
|
|
138
|
-
}
|
|
139
|
-
if (state.overlay === "submit" && (name === "left" || name === "right")) {
|
|
140
|
-
return [{ type: "cycleVerdict", direction: name === "left" ? -1 : 1 }];
|
|
141
|
-
}
|
|
142
|
-
return [];
|
|
143
|
-
}
|
|
144
|
-
// a modal confirm owns the keys: ⏎ commits the action, escape backs out
|
|
145
|
-
if (state.overlay === "confirm") {
|
|
146
|
-
if (name === "return" || name === "enter") return [{ type: "confirmDialog" }];
|
|
147
|
-
if (name === "escape") return [{ type: "closeOverlay" }];
|
|
148
|
-
return [];
|
|
149
|
-
}
|
|
150
|
-
// a text prompt: the focused input owns typing; only ⏎ save and esc route here
|
|
151
|
-
if (state.overlay === "prompt") {
|
|
152
|
-
if (name === "return" || name === "enter") return [{ type: "confirmDialog" }];
|
|
153
|
-
if (name === "escape") return [{ type: "closeOverlay" }];
|
|
154
|
-
return [];
|
|
155
|
-
}
|
|
156
|
-
// the quick-actions list owns its keys: j/k move, ⏎ picks the highlighted
|
|
157
|
-
// action (inserting its preset comment), escape returns to the span toolbar
|
|
158
|
-
if (state.overlay === "spanActions") {
|
|
159
|
-
if (name === "j" || name === "down") return [{ type: "moveSpanAction", direction: 1 }];
|
|
160
|
-
if (name === "k" || name === "up") return [{ type: "moveSpanAction", direction: -1 }];
|
|
161
|
-
if (name === "return" || name === "enter") return [{ type: "pickSpanAction" }];
|
|
162
|
-
if (name === "escape") return [{ type: "closeSpanActions" }];
|
|
163
|
-
return [];
|
|
164
|
-
}
|
|
165
|
-
if (state.overlay === "completion-prompt" || state.overlay === "completion-counting") {
|
|
166
|
-
if (name === "return" || name === "enter" || name === "q") return [{ type: "finishReview" }];
|
|
167
|
-
if (name === "a") return [{ type: "optInAutoClose" }];
|
|
168
|
-
if (name === "escape") return [{ type: "dismissCompletion" }];
|
|
169
|
-
return [];
|
|
170
|
-
}
|
|
171
|
-
// the walk wizard owns its keys while active: ] advances (marking the
|
|
172
|
-
// current file viewed), [ steps back, escape leaves keeping progress, and
|
|
173
|
-
// return on the end card hands over to the submit confirm
|
|
174
|
-
if (state.overlay === "walk") {
|
|
175
|
-
if (name === "]") return [{ type: "walkForward" }];
|
|
176
|
-
if (name === "[") return [{ type: "walkBack" }];
|
|
177
|
-
if (name === "escape") return [{ type: "walkLeave" }];
|
|
178
|
-
if ((name === "return" || name === "enter") && state.walkAtEnd) {
|
|
179
|
-
return [{ type: "walkLeave" }, { type: "openSubmit" }];
|
|
180
|
-
}
|
|
181
|
-
if (name === "q") return [{ type: "exit" }];
|
|
182
|
-
return [];
|
|
183
|
-
}
|
|
125
|
+
const overlayGrammar = overlayGrammars[state.overlay];
|
|
126
|
+
if (overlayGrammar) return overlayGrammar(state, key);
|
|
184
127
|
const action = resolvedAction ?? actionFor(state.keys, name, key.shift);
|
|
185
128
|
if (action === "quit") return [{ type: "exit" }];
|
|
186
129
|
// the ONE read-only rule: any mutating attempt answers instead of acting
|
|
@@ -208,6 +151,85 @@ export function reduceKey(state: KeyState, key: KeyInput, resolvedAction?: strin
|
|
|
208
151
|
return planGrammar(state, action, name);
|
|
209
152
|
}
|
|
210
153
|
|
|
154
|
+
type OverlayGrammar = (state: KeyState, key: KeyInput) => Intent[];
|
|
155
|
+
|
|
156
|
+
const overlayGrammars: Partial<Record<KeyState["overlay"], OverlayGrammar>> = {
|
|
157
|
+
compose: composeOverlayGrammar,
|
|
158
|
+
submit: submitOverlayGrammar,
|
|
159
|
+
confirm: confirmOverlayGrammar,
|
|
160
|
+
prompt: promptOverlayGrammar,
|
|
161
|
+
spanActions: spanActionsOverlayGrammar,
|
|
162
|
+
"completion-prompt": completionOverlayGrammar,
|
|
163
|
+
"completion-counting": completionOverlayGrammar,
|
|
164
|
+
walk: walkOverlayGrammar,
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
function composeOverlayGrammar(state: KeyState, key: KeyInput): Intent[] {
|
|
168
|
+
const name = key.name;
|
|
169
|
+
if (name === "escape") return [{ type: "closeOverlay" }];
|
|
170
|
+
if (name === "return" || name === "enter") {
|
|
171
|
+
// In the composer, ⌥/Alt+⏎ (meta) and shift+⏎ insert a newline - the
|
|
172
|
+
// focused textarea owns that; only a bare ⏎ saves. The submit overlay
|
|
173
|
+
// keeps its plain ⏎ submit.
|
|
174
|
+
if (key.shift || key.meta) return [];
|
|
175
|
+
return [{ type: "saveCompose" }];
|
|
176
|
+
}
|
|
177
|
+
return [];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function submitOverlayGrammar(state: KeyState, key: KeyInput): Intent[] {
|
|
181
|
+
const name = key.name;
|
|
182
|
+
if (name === "escape") return [{ type: "closeOverlay" }];
|
|
183
|
+
if (name === "return" || name === "enter") return [{ type: "submitVerdict" }];
|
|
184
|
+
if (name === "left" || name === "right") {
|
|
185
|
+
return [{ type: "cycleVerdict", direction: name === "left" ? -1 : 1 }];
|
|
186
|
+
}
|
|
187
|
+
return [];
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function confirmOverlayGrammar(state: KeyState, key: KeyInput): Intent[] {
|
|
191
|
+
const name = key.name;
|
|
192
|
+
if (name === "return" || name === "enter") return [{ type: "confirmDialog" }];
|
|
193
|
+
if (name === "escape") return [{ type: "closeOverlay" }];
|
|
194
|
+
return [];
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function promptOverlayGrammar(state: KeyState, key: KeyInput): Intent[] {
|
|
198
|
+
const name = key.name;
|
|
199
|
+
if (name === "return" || name === "enter") return [{ type: "confirmDialog" }];
|
|
200
|
+
if (name === "escape") return [{ type: "closeOverlay" }];
|
|
201
|
+
return [];
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function spanActionsOverlayGrammar(state: KeyState, key: KeyInput): Intent[] {
|
|
205
|
+
const name = key.name;
|
|
206
|
+
if (name === "j" || name === "down") return [{ type: "moveSpanAction", direction: 1 }];
|
|
207
|
+
if (name === "k" || name === "up") return [{ type: "moveSpanAction", direction: -1 }];
|
|
208
|
+
if (name === "return" || name === "enter") return [{ type: "pickSpanAction" }];
|
|
209
|
+
if (name === "escape") return [{ type: "closeSpanActions" }];
|
|
210
|
+
return [];
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function completionOverlayGrammar(state: KeyState, key: KeyInput): Intent[] {
|
|
214
|
+
const name = key.name;
|
|
215
|
+
if (name === "return" || name === "enter" || name === "q") return [{ type: "finishReview" }];
|
|
216
|
+
if (name === "a") return [{ type: "optInAutoClose" }];
|
|
217
|
+
if (name === "escape") return [{ type: "dismissCompletion" }];
|
|
218
|
+
return [];
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function walkOverlayGrammar(state: KeyState, key: KeyInput): Intent[] {
|
|
222
|
+
const name = key.name;
|
|
223
|
+
if (name === "]") return [{ type: "walkForward" }];
|
|
224
|
+
if (name === "[") return [{ type: "walkBack" }];
|
|
225
|
+
if (name === "escape") return [{ type: "walkLeave" }];
|
|
226
|
+
if ((name === "return" || name === "enter") && state.walkAtEnd) {
|
|
227
|
+
return [{ type: "walkLeave" }, { type: "openSubmit" }];
|
|
228
|
+
}
|
|
229
|
+
if (name === "q") return [{ type: "exit" }];
|
|
230
|
+
return [];
|
|
231
|
+
}
|
|
232
|
+
|
|
211
233
|
/** The review-panel controls: cycle the mode, widen and narrow the rail. */
|
|
212
234
|
function reviewPanelGrammar(action: string | undefined): Intent[] | null {
|
|
213
235
|
if (action === "review_cycle") return [{ type: "cycleReviewPanel" }];
|
package/src/prototype-browser.ts
CHANGED
|
@@ -228,7 +228,12 @@ function elementAtScript(x: number, y: number): unknown {
|
|
|
228
228
|
};
|
|
229
229
|
const hit = document.elementFromPoint(x, y);
|
|
230
230
|
if (!(hit instanceof Element)) return null;
|
|
231
|
-
|
|
231
|
+
// A click on an interactive control anchors to that control - a button in a
|
|
232
|
+
// grid is the target, not the grid. Everything else climbs to its component.
|
|
233
|
+
const control = hit.closest(
|
|
234
|
+
"button, a, [role='button'], input, select, textarea, label, summary",
|
|
235
|
+
);
|
|
236
|
+
const node = control ?? componentRoot(hit);
|
|
232
237
|
const selector = selectorFor(node);
|
|
233
238
|
if (!selector) return null;
|
|
234
239
|
const text = (node.textContent || "").replace(/\s+/g, " ").trim();
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import React, { useState, type Dispatch, type SetStateAction } from "react";
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_QUICK_ACTIONS,
|
|
4
|
+
persistActions,
|
|
5
|
+
persistAutoClose,
|
|
6
|
+
persistTheme,
|
|
7
|
+
type AutoClose,
|
|
8
|
+
type QuickAction,
|
|
9
|
+
} from "./config";
|
|
10
|
+
import {
|
|
11
|
+
composeTheme,
|
|
12
|
+
THEME_LABELS,
|
|
13
|
+
THEME_NAMES,
|
|
14
|
+
type Appearance,
|
|
15
|
+
type ThemeName,
|
|
16
|
+
} from "./theme-presets";
|
|
17
|
+
import type { Theme } from "./theme";
|
|
18
|
+
import type { ReviewPanelMode } from "./review-panel";
|
|
19
|
+
import type { SettingsCategory } from "./components/SettingsDialog";
|
|
20
|
+
import { QuickActionsEditor } from "./components/quick-actions-editor";
|
|
21
|
+
import type { ReviewController } from "./session-controller";
|
|
22
|
+
|
|
23
|
+
export interface SettingsNav {
|
|
24
|
+
categoryId: string;
|
|
25
|
+
rowIndex: number;
|
|
26
|
+
zone: "nav" | "body";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface SettingsDialogModel {
|
|
30
|
+
settingsNav: SettingsNav;
|
|
31
|
+
actionsExpandedIndex: number | null;
|
|
32
|
+
setActionsExpandedIndex: Dispatch<SetStateAction<number | null>>;
|
|
33
|
+
settingsCategories: SettingsCategory[];
|
|
34
|
+
settingsValues: Record<string, string>;
|
|
35
|
+
cycleSetting: (rowKey: string) => void;
|
|
36
|
+
handleSettingsKey: (name: string) => void;
|
|
37
|
+
onCategorySelect: (categoryId: string) => void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const DOWN_KEYS = new Set(["j", "down"]);
|
|
41
|
+
const UP_KEYS = new Set(["k", "up"]);
|
|
42
|
+
const ENTER_BODY_KEYS = new Set(["l", "tab", "return"]);
|
|
43
|
+
const ACTIVATE_KEYS = new Set(["return", "space", "l"]);
|
|
44
|
+
|
|
45
|
+
function moveNavZone(
|
|
46
|
+
name: string,
|
|
47
|
+
categoryIndex: number,
|
|
48
|
+
categories: SettingsCategory[],
|
|
49
|
+
): SettingsNav | null {
|
|
50
|
+
if (DOWN_KEYS.has(name))
|
|
51
|
+
return {
|
|
52
|
+
categoryId: categories[Math.min(categories.length - 1, categoryIndex + 1)]!.id,
|
|
53
|
+
rowIndex: 0,
|
|
54
|
+
zone: "nav",
|
|
55
|
+
};
|
|
56
|
+
if (UP_KEYS.has(name))
|
|
57
|
+
return { categoryId: categories[Math.max(0, categoryIndex - 1)]!.id, rowIndex: 0, zone: "nav" };
|
|
58
|
+
if (ENTER_BODY_KEYS.has(name))
|
|
59
|
+
return { categoryId: categories[categoryIndex]!.id, rowIndex: 0, zone: "body" };
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function moveBodyRow(name: string, nav: SettingsNav, rowCount: number): SettingsNav | null {
|
|
64
|
+
if (DOWN_KEYS.has(name)) return { ...nav, rowIndex: Math.min(rowCount - 1, nav.rowIndex + 1) };
|
|
65
|
+
if (UP_KEYS.has(name)) return { ...nav, rowIndex: Math.max(0, nav.rowIndex - 1) };
|
|
66
|
+
if (name === "h" || name === "tab") return { ...nav, zone: "nav" };
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function useSettingsDialog(params: {
|
|
71
|
+
theme: Theme;
|
|
72
|
+
appearance: Appearance;
|
|
73
|
+
autoClose: AutoClose;
|
|
74
|
+
setAutoClose: Dispatch<SetStateAction<AutoClose>>;
|
|
75
|
+
reviewMode: ReviewPanelMode;
|
|
76
|
+
setReviewMode: Dispatch<SetStateAction<ReviewPanelMode>>;
|
|
77
|
+
themeName: ThemeName;
|
|
78
|
+
setThemeName: Dispatch<SetStateAction<ThemeName>>;
|
|
79
|
+
themeOverrides: Partial<Theme>;
|
|
80
|
+
setTheme: Dispatch<SetStateAction<Theme>>;
|
|
81
|
+
quickActions: QuickAction[];
|
|
82
|
+
setQuickActions: Dispatch<SetStateAction<QuickAction[]>>;
|
|
83
|
+
controller: ReviewController;
|
|
84
|
+
setMenuDialog: Dispatch<SetStateAction<"keybinds" | "settings" | null>>;
|
|
85
|
+
}): SettingsDialogModel {
|
|
86
|
+
const {
|
|
87
|
+
theme,
|
|
88
|
+
appearance,
|
|
89
|
+
autoClose,
|
|
90
|
+
setAutoClose,
|
|
91
|
+
reviewMode,
|
|
92
|
+
setReviewMode,
|
|
93
|
+
themeName,
|
|
94
|
+
setThemeName,
|
|
95
|
+
themeOverrides,
|
|
96
|
+
setTheme,
|
|
97
|
+
quickActions,
|
|
98
|
+
setQuickActions,
|
|
99
|
+
controller,
|
|
100
|
+
setMenuDialog,
|
|
101
|
+
} = params;
|
|
102
|
+
|
|
103
|
+
const [settingsNav, setSettingsNav] = useState<SettingsNav>({
|
|
104
|
+
categoryId: "general",
|
|
105
|
+
rowIndex: 0,
|
|
106
|
+
zone: "body",
|
|
107
|
+
});
|
|
108
|
+
const [actionsExpandedIndex, setActionsExpandedIndex] = useState<number | null>(null);
|
|
109
|
+
|
|
110
|
+
const commitActions = (next: QuickAction[]): void => {
|
|
111
|
+
setQuickActions(next);
|
|
112
|
+
persistActions(next);
|
|
113
|
+
};
|
|
114
|
+
const editActionMetadata = (index: number, metadata: string): void =>
|
|
115
|
+
commitActions(
|
|
116
|
+
quickActions.map((action, actionIndex) =>
|
|
117
|
+
actionIndex === index
|
|
118
|
+
? { ...action, metadata: metadata.trim() ? metadata : undefined }
|
|
119
|
+
: action,
|
|
120
|
+
),
|
|
121
|
+
);
|
|
122
|
+
const resetActions = (): void => {
|
|
123
|
+
setActionsExpandedIndex(null);
|
|
124
|
+
commitActions(DEFAULT_QUICK_ACTIONS.map((action) => ({ ...action })));
|
|
125
|
+
};
|
|
126
|
+
const addAction = (): void => {
|
|
127
|
+
setSettingsNav((state) => ({ ...state, zone: "body", rowIndex: quickActions.length }));
|
|
128
|
+
commitActions([...quickActions, { prompt: "New action" }]);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const settingsCategories: SettingsCategory[] = [
|
|
132
|
+
{
|
|
133
|
+
id: "general",
|
|
134
|
+
name: "General",
|
|
135
|
+
description: "submission behaviour",
|
|
136
|
+
rows: [
|
|
137
|
+
{
|
|
138
|
+
key: "autoClose",
|
|
139
|
+
label: "Auto-close on submit",
|
|
140
|
+
kind: "cycle",
|
|
141
|
+
options: ["off", "3s", "10s"],
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
id: "display",
|
|
147
|
+
name: "Display",
|
|
148
|
+
description: "the review panel",
|
|
149
|
+
rows: [
|
|
150
|
+
{
|
|
151
|
+
key: "reviewPanel",
|
|
152
|
+
label: "Review panel",
|
|
153
|
+
kind: "cycle",
|
|
154
|
+
options: ["expanded", "compact", "hidden"],
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
id: "appearance",
|
|
160
|
+
name: "Appearance",
|
|
161
|
+
description: "the color theme",
|
|
162
|
+
rows: [
|
|
163
|
+
{
|
|
164
|
+
key: "theme",
|
|
165
|
+
label: "Theme",
|
|
166
|
+
kind: "cycle",
|
|
167
|
+
options: THEME_NAMES.map((name) => THEME_LABELS[name]),
|
|
168
|
+
},
|
|
169
|
+
],
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
id: "actions",
|
|
173
|
+
name: "Actions",
|
|
174
|
+
description: "quick-action comments",
|
|
175
|
+
rows: [],
|
|
176
|
+
customBody: (
|
|
177
|
+
<QuickActionsEditor
|
|
178
|
+
actions={quickActions}
|
|
179
|
+
selectedIndex={settingsNav.categoryId === "actions" ? settingsNav.rowIndex : -1}
|
|
180
|
+
expandedIndex={actionsExpandedIndex}
|
|
181
|
+
onToggleExpand={(index) => {
|
|
182
|
+
setSettingsNav((state) => ({ ...state, zone: "body", rowIndex: index }));
|
|
183
|
+
setActionsExpandedIndex((current) => (current === index ? null : index));
|
|
184
|
+
}}
|
|
185
|
+
onEditMetadata={editActionMetadata}
|
|
186
|
+
onReset={resetActions}
|
|
187
|
+
onAdd={addAction}
|
|
188
|
+
theme={theme}
|
|
189
|
+
/>
|
|
190
|
+
),
|
|
191
|
+
},
|
|
192
|
+
];
|
|
193
|
+
const settingsValues = {
|
|
194
|
+
autoClose: autoClose === "off" ? "off" : `${autoClose}s`,
|
|
195
|
+
reviewPanel: reviewMode,
|
|
196
|
+
theme: THEME_LABELS[themeName],
|
|
197
|
+
};
|
|
198
|
+
const cycleSetting = (rowKey: string): void => {
|
|
199
|
+
if (rowKey === "autoClose") {
|
|
200
|
+
const next: AutoClose = autoClose === "off" ? 3 : autoClose === 3 ? 10 : "off";
|
|
201
|
+
setAutoClose(next);
|
|
202
|
+
persistAutoClose(next);
|
|
203
|
+
} else if (rowKey === "reviewPanel") {
|
|
204
|
+
const order: ReviewPanelMode[] = ["expanded", "compact", "hidden"];
|
|
205
|
+
const next = order[(order.indexOf(reviewMode) + 1) % order.length]!;
|
|
206
|
+
setReviewMode(next);
|
|
207
|
+
controller.saveReviewPanel({ mode: next });
|
|
208
|
+
} else if (rowKey === "theme") {
|
|
209
|
+
const next = THEME_NAMES[(THEME_NAMES.indexOf(themeName) + 1) % THEME_NAMES.length]!;
|
|
210
|
+
setThemeName(next);
|
|
211
|
+
setTheme(composeTheme(next, themeOverrides, appearance));
|
|
212
|
+
persistTheme(next);
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const onCategorySelect = (categoryId: string): void => {
|
|
217
|
+
setActionsExpandedIndex(null);
|
|
218
|
+
setSettingsNav({ categoryId, rowIndex: 0, zone: "body" });
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const handleSettingsKey = (name: string): void => {
|
|
222
|
+
// an open system-prompt input owns typing; only esc (close it) escapes here
|
|
223
|
+
if (actionsExpandedIndex !== null) {
|
|
224
|
+
if (name === "escape") setActionsExpandedIndex(null);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
if (name === "escape") return void setMenuDialog(null);
|
|
228
|
+
const categoryIndex = settingsCategories.findIndex(
|
|
229
|
+
(category) => category.id === settingsNav.categoryId,
|
|
230
|
+
);
|
|
231
|
+
const category = settingsCategories[categoryIndex]!;
|
|
232
|
+
if (settingsNav.zone === "nav") {
|
|
233
|
+
const next = moveNavZone(name, categoryIndex, settingsCategories);
|
|
234
|
+
if (next) setSettingsNav(next);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
// the Actions category is a list of quick actions plus a trailing "add" row
|
|
238
|
+
if (category.id === "actions") {
|
|
239
|
+
const moved = moveBodyRow(name, settingsNav, quickActions.length + 1);
|
|
240
|
+
if (moved) return void setSettingsNav(moved);
|
|
241
|
+
if (ACTIVATE_KEYS.has(name)) {
|
|
242
|
+
if (settingsNav.rowIndex === quickActions.length) addAction();
|
|
243
|
+
else setActionsExpandedIndex(settingsNav.rowIndex);
|
|
244
|
+
}
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
const moved = moveBodyRow(name, settingsNav, category.rows.length);
|
|
248
|
+
if (moved) return void setSettingsNav(moved);
|
|
249
|
+
if (name === "return" || name === "space")
|
|
250
|
+
cycleSetting(category.rows[settingsNav.rowIndex]!.key);
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
settingsNav,
|
|
255
|
+
actionsExpandedIndex,
|
|
256
|
+
setActionsExpandedIndex,
|
|
257
|
+
settingsCategories,
|
|
258
|
+
settingsValues,
|
|
259
|
+
cycleSetting,
|
|
260
|
+
handleSettingsKey,
|
|
261
|
+
onCategorySelect,
|
|
262
|
+
};
|
|
263
|
+
}
|