Skip to content

Commit 2a5f541

Browse files
mondalacicursoragentert78gbkareltucek
authored
feat: auto-group macros in the sidebar (#2993)
Add optional sidebar grouping derived from macro names, with settings for camelCase splitting, nesting depth, and minimum group size. Grouping is off by default and only affects sidebar display, not stored macro names. Closes #1845 Use \p{L} and \p{N} so non-English macro names and unicode digits group correctly in the sidebar instead of breaking on accented characters. Show full macro names and nest same-named macros in groups Grouped sidebar items now always display the full macro name, and macros whose name matches a group label are listed inside that group. When a macro is selected via Jump to macro or navigation, open its ancestor sidebar groups so the target macro stays visible. --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Kiss Róbert <ert78gb@gmail.com> Co-authored-by: Karel Tucek <kareltucek@centrum.cz>
1 parent 8a0fd9e commit 2a5f541

21 files changed

Lines changed: 1054 additions & 24 deletions

packages/uhk-common/src/models/application-settings.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { MacroGroupingSettings } from './macro-grouping-settings.js';
12
import { RgbColorInterface } from './rgb-color-interface.js';
23

34
export enum AppTheme {
@@ -34,4 +35,8 @@ export interface ApplicationSettings {
3435
* If true, the Advanced settings menu is shown on Agent startup.
3536
*/
3637
alwaysEnableAdvancedMode?: boolean;
38+
/**
39+
* Sidebar macro grouping preferences.
40+
*/
41+
macroGrouping?: Partial<MacroGroupingSettings>;
3742
}

packages/uhk-common/src/models/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export * from './application-settings.js';
2+
export * from './macro-grouping-settings.js';
23
export * from './backup-user-configuration.js';
34
export * from './backup-user-configuration-info.js';
45
export * from './ble-address-pair.js';
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export interface MacroGroupingSettings {
2+
camelCaseSeparation: boolean;
3+
enabled: boolean;
4+
maxDepth: number;
5+
minChildren: number;
6+
}
7+
8+
export const MACRO_GROUPING_MIN_DEPTH = 1;
9+
10+
export const DEFAULT_MACRO_GROUPING_SETTINGS: MacroGroupingSettings = {
11+
camelCaseSeparation: false,
12+
enabled: false,
13+
maxDepth: 1,
14+
minChildren: 2,
15+
};

packages/uhk-web/src/app/components/agent/settings/settings.component.html

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,8 @@ <h1>
4444
</div>
4545
</div>
4646

47-
48-
<div class="col-12 col-md-6 mb-0 mt-0">
49-
<div class="d-flex align-items-center mt-2">
47+
<div class="col-12 col-md-6 mb-0 mt-2">
48+
<div class="d-flex align-items-center">
5049
<label class="mb-0 me-2">Theme</label>
5150
<ng-select
5251
class="theme-selector"
@@ -69,6 +68,64 @@ <h1>
6968
<button type="button" class="btn btn-link px-0" (click)="openConfigFolder()">Open configuration folder</button>
7069
</div>
7170

71+
<div class="col-12 mt-3" *ngIf="macroGroupingSettings$ | async as macroGroupingSettings">
72+
<h2 class="h5">Macro sidebar grouping</h2>
73+
<div class="checkbox d-inline-block mb-0 mt-0">
74+
<div class="checkbox mb-0">
75+
<label>
76+
<input type="checkbox"
77+
[checked]="macroGroupingSettings.enabled"
78+
(change)="updateMacroGroupingSettings({ enabled: $event.target.checked })"
79+
> Group macros in the sidebar
80+
<circle-tooltip
81+
tooltip="Turns sidebar grouping on or off. When enabled, macros are sorted into collapsible folders by splitting names at non-alphanumeric characters, such as colons, underscores, hyphens, and spaces. For example, Doom: Chainsaw and Doom_Plasma gun appear under a Doom folder. When disabled, macros are shown as a flat alphabetical list. Only the sidebar layout changes; macro names in your configuration stay the same."
82+
[width]="420"
83+
/>
84+
</label>
85+
</div>
86+
</div>
87+
<div class="macro-grouping-dependent-options"
88+
[class.macro-grouping-dependent-options--disabled]="!macroGroupingSettings.enabled">
89+
<div class="checkbox d-inline-block mt-1">
90+
<div class="checkbox mb-0">
91+
<label>
92+
<input type="checkbox"
93+
[checked]="macroGroupingSettings.camelCaseSeparation"
94+
[disabled]="!macroGroupingSettings.enabled"
95+
(change)="updateMacroGroupingSettings({ camelCaseSeparation: $event.target.checked })"
96+
> Split camel case macro names
97+
<circle-tooltip
98+
tooltip="Requires Group macros in the sidebar. Adds a second splitting rule on top of non-alphanumeric separators: camelCase boundaries within each name segment. For example, bindMouseLeft and bindMouseRight are grouped under bind. Use this if you name macros in camelCase without non-alphanumeric separators. Leave off if camelCase names should stay whole, such as when the full name is a single identifier used by macro commands."
99+
[width]="420"
100+
/>
101+
</label>
102+
</div>
103+
</div>
104+
<div class="macro-grouping-inline-option mt-1">
105+
<label class="mb-0" for="macro-grouping-max-depth">Maximum nesting depth:</label>
106+
<input id="macro-grouping-max-depth"
107+
type="number"
108+
class="form-control macro-grouping-number-input"
109+
min="1"
110+
[max]="macroGroupingMaxDepth"
111+
[disabled]="!macroGroupingSettings.enabled"
112+
[ngModel]="macroGroupingSettings.maxDepth"
113+
(ngModelChange)="updateMacroGroupingSettings({ maxDepth: +$event })">
114+
</div>
115+
<div class="macro-grouping-inline-option mt-1">
116+
<label class="mb-0" for="macro-grouping-min-children">Minimum macros per group:</label>
117+
<input id="macro-grouping-min-children"
118+
type="number"
119+
class="form-control macro-grouping-number-input"
120+
min="2"
121+
max="10"
122+
[disabled]="!macroGroupingSettings.enabled"
123+
[ngModel]="macroGroupingSettings.minChildren"
124+
(ngModelChange)="updateMacroGroupingSettings({ minChildren: +$event })">
125+
</div>
126+
</div>
127+
</div>
128+
72129
<ng-template #systemThemeTooltip>
73130
<p class="text-start mb-0">The <strong>Follow operating system theme</strong> option may not be supported on all Linux distributions.</p>
74131
</ng-template>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,21 @@
1+
h1 {
2+
margin-bottom: 1rem;
3+
}
4+
15
.theme-selector {
26
min-width: 230px;
37
}
8+
9+
.macro-grouping-number-input {
10+
width: 4rem;
11+
}
12+
13+
.macro-grouping-inline-option {
14+
align-items: center;
15+
display: flex;
16+
gap: 0.5rem;
17+
}
18+
19+
.macro-grouping-dependent-options--disabled {
20+
opacity: 0.55;
21+
}

packages/uhk-web/src/app/components/agent/settings/settings.component.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,26 @@ import { Observable } from 'rxjs';
44
import { map } from 'rxjs/operators';
55
import { faCog } from '@fortawesome/free-solid-svg-icons';
66

7-
import { AppTheme, AppThemeSelect } from 'uhk-common';
7+
import { AppTheme, AppThemeSelect, MacroGroupingSettings } from 'uhk-common';
88
import {
99
AppState,
1010
appUpdateSettingsState,
1111
getAlwaysEnableAdvancedMode,
1212
getAnimationEnabled,
1313
getAppTheme,
1414
getIsAdvancedSettingsMenuVisible,
15+
getMacroGroupingSettings,
1516
getOperatingSystem,
1617
getSupportedThemes,
1718
keyboardHalvesAlwaysJoined
1819
} from '../../../store';
20+
import { MACRO_GROUPING_MAX_DEPTH } from '../../../util/group-macros-by-name';
1921
import { State as UpdateSettingsState } from '../../../store/reducers/auto-update-settings';
2022
import {
2123
CheckForUpdateNowAction,
2224
ToggleCheckForUpdateOnStartupAction
2325
} from '../../../store/actions/auto-update-settings';
24-
import { OpenConfigFolderAction, SetAppThemeAction, ToggleAnimationEnabledAction, ToggleKeyboardHalvesAlwaysJoinedAction } from '../../../store/actions/app';
26+
import { OpenConfigFolderAction, SetAppThemeAction, SetMacroGroupingSettingsAction, ToggleAnimationEnabledAction, ToggleKeyboardHalvesAlwaysJoinedAction } from '../../../store/actions/app';
2527
import { ToggleAlwaysEnableAdvancedModeAction } from '../../../store/actions/advance-settings.action';
2628
import { OperatingSystem } from '../../../models/operating-system';
2729

@@ -44,6 +46,8 @@ export class SettingsComponent {
4446
keyboardHalvesAlwaysJoined$: Observable<boolean>;
4547
alwaysEnableAdvancedMode$: Observable<boolean>;
4648
alwaysEnableAdvancedModeSettingVisible$: Observable<boolean>;
49+
macroGroupingSettings$: Observable<MacroGroupingSettings>;
50+
macroGroupingMaxDepth = MACRO_GROUPING_MAX_DEPTH;
4751

4852
constructor(private store: Store<AppState>) {
4953
this.updateSettingsState$ = store.select(appUpdateSettingsState);
@@ -54,6 +58,7 @@ export class SettingsComponent {
5458
this.keyboardHalvesAlwaysJoined$ = store.select(keyboardHalvesAlwaysJoined);
5559
this.alwaysEnableAdvancedMode$ = store.select(getAlwaysEnableAdvancedMode);
5660
this.alwaysEnableAdvancedModeSettingVisible$ = store.select(getIsAdvancedSettingsMenuVisible);
61+
this.macroGroupingSettings$ = store.select(getMacroGroupingSettings);
5762
}
5863

5964
openConfigFolder(): void {
@@ -84,4 +89,8 @@ export class SettingsComponent {
8489
this.store.dispatch(new ToggleAlwaysEnableAdvancedModeAction(enabled));
8590
}
8691

92+
updateMacroGroupingSettings(settings: Partial<MacroGroupingSettings>): void {
93+
this.store.dispatch(new SetMacroGroupingSettingsAction(settings));
94+
}
95+
8796
}

packages/uhk-web/src/app/components/popover/tab/macro/macro-tab.component.scss

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
.col-controls {
2929
min-width: 145px;
3030
text-align: right;
31-
white-space: nowrap;
3231

3332
&.arguments {
3433
fa-icon {

packages/uhk-web/src/app/components/side-menu/side-menu.component.html

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -221,23 +221,40 @@
221221
class="chevron float-end me-1"
222222
(click)="toggleMenuItem('macro')"></fa-icon>
223223
</div>
224-
<ul [@toggler]="this.sideMenuState.macro.animation">
225-
<li *ngFor="let macro of state.macros" class="sidebar__level-2--item">
226-
<div class="sidebar__level-2" [routerLinkActive]="['active']">
227-
<a [routerLink]="['/macro', macro.id]"
228-
[class.disabled]="state.updatingFirmware">{{macro.name}}</a>
229-
<span class="sidebar__macro_count badge rounded-pill"
230-
ngbTooltip="This is the number of times the macro is used across all keymaps."
231-
placement="bottom"
232-
container="body">{{ macro.usageCount }}</span>
233-
</div>
234-
</li>
224+
<ul class="sidebar__macro-tree" [@toggler]="this.sideMenuState.macro.animation">
225+
<ng-container *ngTemplateOutlet="macroTree; context: { $implicit: state.macroTree }"></ng-container>
235226
</ul>
236227
</li>
237228
</ul>
238229
</li>
239230
</ul>
240231

232+
<ng-template #macroTree let-nodes>
233+
<ng-container *ngFor="let node of nodes">
234+
<li *ngIf="node.type === 'macro'" class="sidebar__macro-tree-item">
235+
<div class="sidebar__macro-tree-entry" [routerLinkActive]="['active']">
236+
<a [routerLink]="['/macro', node.macro.id]"
237+
[class.disabled]="state.updatingFirmware">{{ node.macro.name }}</a>
238+
<span class="sidebar__macro_count badge rounded-pill"
239+
ngbTooltip="This is the number of times the macro is used across all keymaps."
240+
placement="bottom"
241+
container="body">{{ node.macro.usageCount }}</span>
242+
</div>
243+
</li>
244+
<li *ngIf="node.type === 'group'" class="sidebar__macro-tree-item">
245+
<div class="sidebar__macro-tree-entry sidebar__macro-group"
246+
(click)="toggleMacroGroup(node.path)">
247+
{{ node.label }}…
248+
<fa-icon [icon]="getMacroGroupArrowIcon(node.path)"
249+
class="sidebar__macro-group-arrow"></fa-icon>
250+
</div>
251+
<ul class="sidebar__macro-group-children" [@toggler]="getMacroGroupState(node.path).animation">
252+
<ng-container *ngTemplateOutlet="macroTree; context: { $implicit: node.children }"></ng-container>
253+
</ul>
254+
</li>
255+
</ng-container>
256+
</ng-template>
257+
241258
<ul class="menu--bottom">
242259
<li class="menu--bottom__item">
243260
<a [routerLink]="['/settings']"

packages/uhk-web/src/app/components/side-menu/side-menu.component.scss

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
$level-0-item-spacing: 0.5rem 0.5rem;
55
$level-1-item-spacing: 0.25rem 1rem 0.25rem 1.5rem;
66
$level-2-item-spacing: 0.125rem 0 0.125rem 2rem;
7+
$macro-tree-base-indent: 2rem;
8+
$macro-tree-indent-step: 0.75rem;
9+
// Keep MACRO_GROUPING_MAX_DEPTH in util/group-macros-by-name.ts in sync with these layout values.
710

811
:host {
912
background-color: var(--color-sidemenu-bg);
@@ -139,10 +142,73 @@ ul {
139142
}
140143
}
141144

145+
// Indentation is purely structural: it comes from the nested <ul> lists, not
146+
// from any per-leaf padding. The root list supplies the base indent and each
147+
// nested group list adds one step, so depth accumulates through the DOM.
148+
&__macro-tree-entry {
149+
padding: 0.125rem 0;
150+
}
151+
152+
&__macro-tree {
153+
width: auto;
154+
margin-left: $macro-tree-base-indent;
155+
}
156+
157+
&__macro-group-children {
158+
width: auto;
159+
padding-left: $macro-tree-indent-step;
160+
border-left: 1px solid silver;
161+
}
162+
163+
&__macro-tree-item {
164+
position: relative;
165+
166+
&.active,
167+
.active {
168+
background-color: var(--color-sidemenu-active-bg);
169+
color: var(--color-sidemenu-active-text);
170+
171+
a {
172+
color: var(--color-sidemenu-active-text);
173+
}
174+
175+
&:hover {
176+
background-color: var(--color-sidemenu-active-bg);
177+
}
178+
}
179+
180+
&:hover {
181+
cursor: pointer;
182+
}
183+
184+
a {
185+
display: block;
186+
width: 100%;
187+
188+
&.disabled {
189+
opacity: 0.65;
190+
}
191+
}
192+
}
193+
194+
&__macro-group {
195+
cursor: pointer;
196+
font-weight: 500;
197+
198+
&-arrow {
199+
float: right;
200+
font-size: 0.65rem;
201+
line-height: inherit;
202+
margin-right: 0.25rem;
203+
opacity: 0.45;
204+
}
205+
}
206+
142207
// General hover over menu items.
143208
&__level-0,
144209
&__level-1,
145-
&__level-2 {
210+
&__level-2,
211+
&__macro-tree-entry {
146212
&:hover {
147213
background-color: var(--color-hover-link-color);
148214

0 commit comments

Comments
 (0)