Skip to content

Commit 4b92a32

Browse files
authored
Merge pull request #13741 from gitbutlerapp/push-wztynolskttm
Lite: misc
2 parents 5ed66f1 + 39e448f commit 4b92a32

15 files changed

Lines changed: 193 additions & 291 deletions

File tree

apps/lite/electron/src/ipc.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ export interface NativeMenuPosition {
201201

202202
type NativeMenuPopupItemData = {
203203
label: string;
204+
accelerator?: string;
204205
enabled?: boolean;
205206
itemId?: string;
206207
submenu?: Array<NativeMenuPopupItem>;

apps/lite/electron/src/main.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ const buildNativeMenuTemplate = (
195195

196196
return {
197197
label: item.label,
198+
accelerator: item.accelerator,
198199
enabled: item.enabled,
199200
click: itemId !== undefined ? () => onItem(itemId) : undefined,
200201
submenu: item.submenu ? buildNativeMenuTemplate(item.submenu, onItem) : undefined,

apps/lite/ui/src/commands/manager.ts

Lines changed: 42 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -15,41 +15,14 @@ import type { CommandGroup } from "./groups";
1515
import type { NativeMenuItem, NativeMenuItemData } from "#ui/native-menu.ts";
1616
import { useAppDispatch, useAppSelector } from "#ui/store.ts";
1717
import { commandsActions, CommandRegistrationId } from "./state";
18-
import { Order } from "effect";
19-
import { optionalOrder } from "#ui/lib/order.ts";
20-
21-
export type CommandLayer =
22-
| "global"
23-
| "selection-tree"
24-
| "selection"
25-
| "focused-selection-tree"
26-
| "focused-selection";
27-
28-
export const CommandLayerOrder: Order.Order<CommandLayer> = Order.mapInput(Order.number, (cl) => {
29-
switch (cl) {
30-
case "global":
31-
return 0;
32-
case "selection-tree":
33-
return 1;
34-
case "selection":
35-
return 2;
36-
case "focused-selection-tree":
37-
return 3;
38-
case "focused-selection":
39-
return 4;
40-
}
41-
});
4218

4319
// consider if many of these could typically share a label
4420
export type CommandOptions = {
21+
group: CommandGroup;
4522
/** @default true */
4623
enabled?: boolean;
47-
layer: CommandLayer;
4824
commandPalette?: {
49-
group: CommandGroup;
5025
label: string;
51-
/** @default true */
52-
hotkeys?: boolean;
5326
};
5427
shortcutsBar?: {
5528
label: string;
@@ -81,31 +54,49 @@ export type CommandFn = (scenario: CommandTrigger) => void;
8154
export const CommandFnContext: Context<Map<CommandRegistrationId, CommandFn> | null> =
8255
createContext<Map<CommandRegistrationId, CommandFn> | null>(null);
8356

84-
const sequenceKey = (s: HotkeySequence): string =>
85-
s
86-
.join("")
87-
// Disambiguate from non-sequenced hotkeys.
88-
.concat("_seq_");
89-
90-
const useMaxHotkeyLayers = (): Record<string, CommandLayer | undefined> => {
91-
const regs = useAppSelector((state) => state.commands.registrations);
57+
type HotkeySegment<T extends string> = T extends `${infer Head}+${infer Tail}`
58+
? Head | HotkeySegment<Tail>
59+
: T;
60+
61+
const electronAcceleratorKeys: Partial<Record<HotkeySegment<Hotkey>, string>> = {
62+
Alt: "Alt",
63+
ArrowDown: "Down",
64+
ArrowLeft: "Left",
65+
ArrowRight: "Right",
66+
ArrowUp: "Up",
67+
Backspace: "Backspace",
68+
Control: "Control",
69+
Delete: "Delete",
70+
End: "End",
71+
Escape: "Esc",
72+
Enter: "Enter",
73+
Home: "Home",
74+
Meta: "Command",
75+
Mod: "CommandOrControl",
76+
PageDown: "PageDown",
77+
PageUp: "PageUp",
78+
Shift: "Shift",
79+
Space: "Space",
80+
Tab: "Tab",
81+
};
9282

93-
return Object.values(regs).reduce(
94-
(acc, val) => {
95-
if (!val.hotkeys) return acc;
83+
const toElectronAccelerator = (hotkey: RegisterableHotkey): string | undefined => {
84+
const accelerator = normalizeRegisterableHotkey(hotkey)
85+
.split("+")
86+
.map((part) => electronAcceleratorKeys[part as HotkeySegment<Hotkey>] ?? part)
87+
.join("+");
9688

97-
for (const hotkey of val.hotkeys) {
98-
const k =
99-
"sequence" in hotkey
100-
? sequenceKey(hotkey.sequence)
101-
: normalizeRegisterableHotkey(hotkey.hotkey);
102-
acc[k] = Order.max(optionalOrder(CommandLayerOrder))(acc[k], val.layer);
103-
}
89+
return accelerator.length > 0 ? accelerator : undefined;
90+
};
10491

105-
return acc;
106-
},
107-
{} as Record<string, CommandLayer | undefined>,
92+
const firstNativeMenuAccelerator = (
93+
hotkeys: Array<CommandHotkey | CommandHotkeySequence> | undefined,
94+
): string | undefined => {
95+
const firstHotkey = hotkeys?.find(
96+
(hotkey): hotkey is CommandHotkey => !("sequence" in hotkey) && hotkey.enabled !== false,
10897
);
98+
99+
return firstHotkey ? toElectronAccelerator(firstHotkey.hotkey) : undefined;
109100
};
110101

111102
type ResolvedCommand<F extends CommandFn, O extends CommandOptions> = {
@@ -132,7 +123,6 @@ export const useCommand = <F extends CommandFn, O extends CommandOptions>(
132123
const dispatch = useAppDispatch();
133124
const regs = useAppSelector((state) => state.commands.registrations);
134125
const regOptions = regs[id];
135-
const maxKeybindLayers = useMaxHotkeyLayers();
136126

137127
useEffect(() => {
138128
dispatch(commandsActions.register({ id, options }));
@@ -148,16 +138,7 @@ export const useCommand = <F extends CommandFn, O extends CommandOptions>(
148138

149139
const { hotkeyDefs, sequenceDefs, resolvedHotkeys } = (regOptions?.hotkeys ?? []).reduce(
150140
(acc, hk) => {
151-
const maxKeybindLayer =
152-
maxKeybindLayers[
153-
"sequence" in hk ? sequenceKey(hk.sequence) : normalizeRegisterableHotkey(hk.hotkey)
154-
];
155-
156-
const defEnabled =
157-
regOptions &&
158-
regOptions.layer === maxKeybindLayer &&
159-
regOptions.enabled !== false &&
160-
hk.enabled !== false;
141+
const defEnabled = regOptions && regOptions.enabled !== false && hk.enabled !== false;
161142

162143
if (defEnabled)
163144
acc.resolvedHotkeys.push(
@@ -200,6 +181,7 @@ export const useCommand = <F extends CommandFn, O extends CommandOptions>(
200181
? ({
201182
enabled: regOptions.enabled !== false,
202183
onSelect: () => commandFn("contextMenu"),
184+
accelerator: firstNativeMenuAccelerator(regOptions.hotkeys),
203185
...regOptions.contextMenu,
204186
_tag: "Item",
205187
} satisfies NativeMenuItem)

apps/lite/ui/src/lib/order.ts

Lines changed: 0 additions & 9 deletions
This file was deleted.

apps/lite/ui/src/native-menu.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ type NativeMenuAction = () => void | Promise<void>;
55

66
export type NativeMenuItemData = {
77
label: string;
8+
accelerator?: string;
89
enabled?: boolean;
910
onSelect?: NativeMenuAction;
1011
submenu?: Array<NativeMenuItem>;
@@ -24,6 +25,7 @@ const serializeNativeMenuItems = (
2425
return {
2526
_tag: "Item",
2627
label: item.label,
28+
accelerator: item.accelerator,
2729
enabled: item.enabled,
2830
submenu: serializeNativeMenuItems(item.submenu, handlers, nextActionId),
2931
};
@@ -34,6 +36,7 @@ const serializeNativeMenuItems = (
3436
return {
3537
_tag: "Item",
3638
label: item.label,
39+
accelerator: item.accelerator,
3740
enabled: item.enabled,
3841
itemId,
3942
};

apps/lite/ui/src/operands.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ export type FileParent =
1111
| ({ _tag: "Branch" } & BranchFileParent)
1212
| ({ _tag: "Commit" } & CommitFileParent);
1313

14+
/** @public */
15+
export const changesFileParent: FileParent = {
16+
_tag: "Changes",
17+
};
18+
1419
/** @public */
1520
export const branchFileParent = ({ stackId, branchRef }: BranchFileParent): FileParent => ({
1621
_tag: "Branch",
@@ -25,11 +30,6 @@ export const commitFileParent = ({ stackId, commitId }: CommitFileParent): FileP
2530
commitId,
2631
});
2732

28-
/** @public */
29-
export const changesFileParent: FileParent = {
30-
_tag: "Changes",
31-
};
32-
3333
/** @public */
3434
export type StackOperand = {
3535
stackId: string;
@@ -150,3 +150,21 @@ export const operandFileParent = (operand: Operand): FileParent | null =>
150150
}),
151151
Match.orElse(() => null),
152152
);
153+
154+
const fileParentToOperand = (fileParent: FileParent): Operand =>
155+
Match.value(fileParent).pipe(
156+
Match.tagsExhaustive({
157+
Changes: () => changesSectionOperand,
158+
Branch: ({ stackId, branchRef }) => branchOperand({ stackId, branchRef }),
159+
Commit: ({ stackId, commitId }) => commitOperand({ stackId, commitId }),
160+
}),
161+
);
162+
163+
export const operandContains = (a: Operand, b: Operand) => {
164+
if (operandEquals(a, b)) return true;
165+
166+
const bFileParent = operandFileParent(b);
167+
if (bFileParent && operandEquals(a, fileParentToOperand(bFileParent))) return true;
168+
169+
return false;
170+
};

apps/lite/ui/src/outline/mode.ts

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
branchOperand,
55
CommitOperand,
66
commitOperand,
7+
operandContains,
78
operandEquals,
89
type Operand,
910
} from "#ui/operands.ts";
@@ -154,28 +155,20 @@ const operationModeHasOperation = ({
154155

155156
export const filterNavigationIndexForOutlineMode = ({
156157
navigationIndex: navigationIndexUnfiltered,
157-
selection,
158158
outlineMode,
159159
}: {
160160
navigationIndex: NavigationIndex;
161-
selection: Operand;
162161
outlineMode: OutlineMode;
163162
}) =>
164163
Match.value(outlineMode).pipe(
165164
Match.tagsExhaustive({
166165
Default: () => navigationIndexUnfiltered,
167-
Operation: (operationMode) =>
166+
Operation: ({ value: operationMode }) =>
168167
filterNavigationIndex(
169168
navigationIndexUnfiltered,
170169
(operand) =>
171-
// When entering operation mode, the selection must still be
172-
// selectable otherwise the details panel will suddenly appear to
173-
// change and the user may lose sight of their source operand (e.g.
174-
// hunk).
175-
operandEquals(selection, operand) ||
176-
// After selection moves, allow returning selection to the source operand.
177-
operandEquals(operationMode.value.source, operand) ||
178-
operationModeHasOperation({ mode: operationMode.value, operand }),
170+
operandContains(operand, operationMode.source) ||
171+
operationModeHasOperation({ mode: operationMode, operand }),
179172
),
180173
RenameBranch: (x) =>
181174
filterNavigationIndex(navigationIndexUnfiltered, (operand) =>

apps/lite/ui/src/panels.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -103,40 +103,40 @@ export const useNavigationIndexHotkeys = ({
103103
};
104104

105105
useCommand(selectPreviousItem, {
106+
group,
106107
enabled: focusedPanel === panel,
107-
layer: "focused-selection-tree",
108108
shortcutsBar: { label: "Up" },
109109
hotkeys: [{ hotkey: "ArrowUp" }, { hotkey: "K" }],
110110
});
111111

112112
useCommand(selectNextItem, {
113+
group,
113114
enabled: focusedPanel === panel,
114-
layer: "focused-selection-tree",
115115
shortcutsBar: { label: "Down" },
116116
hotkeys: [{ hotkey: "ArrowDown" }, { hotkey: "J" }],
117117
});
118118

119119
useCommand(selectPreviousSection, {
120+
group,
120121
enabled: focusedPanel === panel,
121-
layer: "focused-selection-tree",
122122
hotkeys: [{ hotkey: "Shift+ArrowUp" }, { hotkey: "Shift+K" }],
123123
});
124124

125125
useCommand(selectNextSection, {
126+
group,
126127
enabled: focusedPanel === panel,
127-
layer: "focused-selection-tree",
128128
hotkeys: [{ hotkey: "Shift+ArrowDown" }, { hotkey: "Shift+J" }],
129129
});
130130

131131
useCommand(selectFirstItem, {
132+
group,
132133
enabled: focusedPanel === panel,
133-
layer: "focused-selection-tree",
134134
hotkeys: [{ hotkey: "Home" }, { hotkey: "Meta+ArrowUp" }, { sequence: ["G", "G"] }],
135135
});
136136

137137
useCommand(selectLastItem, {
138+
group,
138139
enabled: focusedPanel === panel,
139-
layer: "focused-selection-tree",
140140
hotkeys: [{ hotkey: "End" }, { hotkey: "Meta+ArrowDown" }, { hotkey: "Shift+G" }],
141141
});
142142

@@ -163,33 +163,33 @@ export const useNavigationIndexHotkeys = ({
163163
};
164164

165165
useCommand(enterMoveMode, {
166+
group,
166167
enabled: focusedPanel === panel && outlineMode._tag === "Default",
167-
layer: "focused-selection-tree",
168-
commandPalette: { group, label: "Move" },
168+
commandPalette: { label: "Move" },
169169
shortcutsBar: { label: "Move" },
170170
hotkeys: [{ hotkey: "M" }],
171171
});
172172

173173
useCommand(enterCutMode, {
174+
group,
174175
enabled: focusedPanel === panel && outlineMode._tag === "Default",
175-
layer: "focused-selection-tree",
176-
commandPalette: { group, label: "Cut" },
176+
commandPalette: { label: "Cut" },
177177
shortcutsBar: { label: "Cut" },
178178
hotkeys: [{ hotkey: "Mod+X", ignoreInputs: true }],
179179
});
180180

181181
useCommand(enterRubMode, {
182+
group,
182183
enabled: focusedPanel === panel && outlineMode._tag === "Default",
183-
layer: "focused-selection-tree",
184-
commandPalette: { group, label: "Rub" },
184+
commandPalette: { label: "Rub" },
185185
shortcutsBar: { label: "Rub" },
186186
hotkeys: [{ hotkey: "R" }],
187187
});
188188

189189
useCommand(enterCommitMode, {
190+
group,
190191
enabled: focusedPanel === panel && outlineMode._tag === "Default",
191-
layer: "focused-selection-tree",
192-
commandPalette: { group, label: "Commit" },
192+
commandPalette: { label: "Commit" },
193193
shortcutsBar: { label: "Commit" },
194194
hotkeys: [{ hotkey: "C" }],
195195
});

apps/lite/ui/src/routes/RootLayout.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ const ProjectSelect: FC = () => {
2929
setPickerOpen(true);
3030
},
3131
{
32-
layer: "global",
3332
enabled: projects.length > 0,
34-
commandPalette: { group: "Global", label: "Select project" },
33+
group: "Global",
34+
commandPalette: { label: "Select project" },
3535
shortcutsBar: { label: "Project" },
3636
hotkeys: [{ hotkey: "Mod+Shift+P" }],
3737
},

0 commit comments

Comments
 (0)