dazscript-framework 0.3.2 → 1.0.1

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.
@@ -0,0 +1,518 @@
1
+ import { BasicDialog } from '@dsf/dialog/basic-dialog'
2
+ import { PopupMenuItem } from '@dsf/dialog/builders/popup-menu-builder'
3
+ import { getDataItem } from '@dsf/helpers/list-view-helper'
4
+ import { AppSettings } from '@dsf/lib/settings'
5
+ import { Observable } from '@dsf/lib/observable'
6
+ import { TreeNode } from '@dsf/lib/tree-node'
7
+ import { config } from './config'
8
+
9
+ // Types
10
+
11
+ export interface SceneObject {
12
+ id: number
13
+ name: string
14
+ type: 'Figure' | 'Prop' | 'Light' | 'Camera' | 'Group'
15
+ visible: boolean
16
+ locked: boolean
17
+ }
18
+
19
+ // Settings (all persisted via DzAppSettings)
20
+
21
+ export class ShowcaseDialogSettings extends AppSettings {
22
+ constructor() {
23
+ super(`${config.author}/06-ShowcaseDialog`)
24
+ }
25
+
26
+ // Objects tab
27
+ activeTab$ = this.bindInt('activeTab$', 0)
28
+ flatList$ = this.bindBoolean('flatList$', false)
29
+ showIcons$ = this.bindBoolean('showIcons$', true)
30
+ showVisibleOnly$ = this.bindBoolean('showVisibleOnly$', false)
31
+ splitterState$ = this.bindString('splitterState$', '')
32
+ filterType$ = this.bindString('filterType$', 'All')
33
+
34
+ // Settings tab - Render
35
+ quality$ = this.bindString('quality$', 'Standard')
36
+ samples$ = this.bindInt('samples$', 64)
37
+ scale$ = this.bindFloat('scale$', 1.0)
38
+
39
+ // Settings tab - Export
40
+ exportFormat$ = this.bindString('exportFormat$', 'JSON')
41
+ outputPath$ = this.bindString('outputPath$', '')
42
+ autoSave$ = this.bindBoolean('autoSave$', false)
43
+
44
+ // Settings tab - Behaviour
45
+ notifications$ = this.bindBoolean('notifications$', true)
46
+ logLevel$ = this.bindString('logLevel$', 'Info')
47
+
48
+ // About tab
49
+ showFilters$ = this.bindBoolean('showFilters$', false)
50
+ }
51
+
52
+ // Model
53
+
54
+ export class ShowcaseDialogModel {
55
+ readonly settings = new ShowcaseDialogSettings()
56
+
57
+ objects$ = new Observable<TreeNode<SceneObject>[]>([])
58
+ selectedObject$ = new Observable<SceneObject>()
59
+ refreshObjects$ = new Observable<void>()
60
+ keywords$ = new Observable('')
61
+ logEntries$ = new Observable<string[]>([])
62
+ sceneStats$ = new Observable('')
63
+ selectedSummary$ = new Observable('No object selected')
64
+
65
+ // Detail panel fields - updated externally when selectedObject$ changes
66
+ detailName$ = new Observable('')
67
+ detailType$ = new Observable('')
68
+ detailVisible$ = new Observable(false)
69
+ detailLocked$ = new Observable(false)
70
+ }
71
+
72
+ // Dialog
73
+
74
+ export class ShowcaseDialog extends BasicDialog {
75
+ private sceneListView: DzListView | null = null
76
+
77
+ constructor(private readonly model: ShowcaseDialogModel) {
78
+ super('06 Showcase Dialog', `${config.author}/06-ShowcaseDialog`)
79
+ }
80
+
81
+ protected build(): void {
82
+ let add = this.add
83
+ let settings = this.model.settings
84
+
85
+ this.builder.options({ resizable: true, width: 780, height: 640 })
86
+ this.dialog.setAcceptButtonText('Apply')
87
+ this.dialog.setCancelButtonText('Close')
88
+
89
+ this.connectInteractions()
90
+
91
+ // Active tab index persists across runs via bindInt.
92
+ add.tab('Objects').bind(settings.activeTab$).build(() => {
93
+ this.buildObjectsTab()
94
+ })
95
+ add.tab('Settings').build(() => {
96
+ this.buildSettingsTab()
97
+ })
98
+ add.tab('About').build(() => {
99
+ this.buildAboutTab()
100
+ })
101
+
102
+ this.updateSceneStats()
103
+ }
104
+
105
+ // Tab 1: Objects
106
+
107
+ private buildObjectsTab(): void {
108
+ let add = this.add
109
+ let model = this.model
110
+ let settings = model.settings
111
+
112
+ // Toolbar row
113
+ add.group('Search').horizontal().build((layout) => {
114
+ layout.spacing = 4
115
+ add.edit()
116
+ .value(model.keywords$)
117
+ .placeholder('Filter objects...')
118
+ .focus()
119
+ add.label('Type:')
120
+ add.combo()
121
+ .items(['All', 'Figure', 'Prop', 'Light', 'Camera', 'Group'])
122
+ .selected(settings.filterType$)
123
+ add.button('Refresh')
124
+ .clicked(() => model.refreshObjects$.trigger())
125
+ .toolTip('Reload the object list')
126
+ add.button('Apply Checked Rows')
127
+ .clicked(() => this.applyCheckedRows())
128
+ .toolTip('Copies the list checkbox states back to the scene objects.')
129
+ })
130
+
131
+ add.horizontal((layout) => {
132
+ layout.spacing = 6
133
+ add.checkbox('Flat List').value(settings.flatList$)
134
+ add.checkbox('Show Icons').value(settings.showIcons$)
135
+ add.checkbox('Visible Only').value(settings.showVisibleOnly$)
136
+ const statsLabel = add.label(model.sceneStats$.value).build()
137
+ model.sceneStats$.connect(text => { statsLabel.text = text })
138
+ })
139
+
140
+ // Splitter - position persisted via bindString
141
+ add.splitter()
142
+ .state(settings.splitterState$)
143
+ .strech(2, 1)
144
+ .items(
145
+ // Left pane: tree list with filter + context menu
146
+ add.group().style({ flat: true }).build(() => {
147
+ add.list.view<SceneObject, SceneObject>()
148
+ .row((item, parent, id) => {
149
+ let obj = item.value as SceneObject
150
+ let listItem = new DzCheckListItem(parent, item.isLeaf ? DzCheckListItem.CheckBox : DzCheckListItem.CheckBoxController, id)
151
+ listItem.selectable = true
152
+ listItem.on = obj.visible
153
+ return listItem
154
+ })
155
+ .expanded(true)
156
+ .flat(settings.flatList$)
157
+ .refresh(model.refreshObjects$)
158
+ .items(model.objects$)
159
+ .columns(['Visible', 'Name', 'Type', 'Locked'])
160
+ .text(item => [
161
+ '',
162
+ this.getDisplayName(item.value),
163
+ item.value?.type ?? '',
164
+ item.value?.locked ? 'Yes' : 'No',
165
+ ])
166
+ .data(item => item.value)
167
+ .selected(model.selectedObject$)
168
+ .filter({
169
+ keywords: model.keywords$,
170
+ field: li => li.text(0),
171
+ selectOnFilter: true,
172
+ filters: li => {
173
+ let obj = getDataItem<SceneObject>(li)
174
+ if (!obj) return true
175
+ let type = settings.filterType$.value
176
+ let matchesType = type === 'All' || obj.type === type
177
+ let matchesVisibility = !settings.showVisibleOnly$.value || obj.visible
178
+ return matchesType && matchesVisibility
179
+ },
180
+ })
181
+ .contextMenu((_, li) => {
182
+ let obj = getDataItem<SceneObject>(li)
183
+ let label = obj?.name ?? 'item'
184
+ let items: PopupMenuItem[] = [
185
+ {
186
+ text: `Select "${label}"`,
187
+ activated: () => { /* select in viewport */ },
188
+ },
189
+ {
190
+ text: obj?.visible ? 'Hide' : 'Show',
191
+ activated: () => {
192
+ if (obj) obj.visible = !obj.visible
193
+ model.refreshObjects$.trigger()
194
+ },
195
+ },
196
+ { text: '' }, // separator
197
+ {
198
+ text: 'Duplicate',
199
+ activated: () => { /* duplicate */ },
200
+ },
201
+ {
202
+ text: 'Delete',
203
+ activated: () => { /* delete */ },
204
+ },
205
+ ]
206
+ return add.contextMenu().items(...items).build()
207
+ })
208
+ .build((listView) => {
209
+ this.sceneListView = listView
210
+ listView.allColumnsShowFocus = true
211
+ })
212
+ }),
213
+
214
+ // Right pane: detail panel for selected object
215
+ add.group('Details').build(() => {
216
+ add.horizontal((layout) => {
217
+ layout.spacing = 4
218
+ add.label('Name:').minWidth(45)
219
+ add.edit().readOnly().value(model.detailName$)
220
+ })
221
+ add.horizontal((layout) => {
222
+ layout.spacing = 4
223
+ add.label('Type:').minWidth(45)
224
+ add.edit().readOnly().value(model.detailType$)
225
+ })
226
+ add.group('Flags').horizontal().build(() => {
227
+ add.checkbox('Visible').value(model.detailVisible$)
228
+ add.checkbox('Locked').value(model.detailLocked$)
229
+ })
230
+ const summaryLabel = add.label(model.selectedSummary$.value).wordWrap().build()
231
+ model.selectedSummary$.connect(text => { summaryLabel.text = text })
232
+ add.group('Scene Node').build(() => {
233
+ // NodeSelection lets the user pick any node from the scene.
234
+ add.nodeSelection().build()
235
+ })
236
+ })
237
+ )
238
+ .build()
239
+ }
240
+
241
+ // Tab 2: Settings
242
+
243
+ private buildSettingsTab(): void {
244
+ let add = this.add
245
+ let settings = this.model.settings
246
+
247
+ add.horizontal((layout) => {
248
+ layout.spacing = 8
249
+
250
+ // Left column
251
+ add.vertical(() => {
252
+ add.group('Display').build(() => {
253
+ add.checkbox('Flat List').value(settings.flatList$)
254
+ add.checkbox('Show Icons').value(settings.showIcons$)
255
+ add.checkbox('Visible Only').value(settings.showVisibleOnly$)
256
+ add.horizontal((layout) => {
257
+ layout.spacing = 4
258
+ add.label('Object Type:').minWidth(80)
259
+ add.combo()
260
+ .items(['All', 'Figure', 'Prop', 'Light', 'Camera', 'Group'])
261
+ .selected(settings.filterType$)
262
+ })
263
+ })
264
+
265
+ // Radio buttons - initialize from persisted quality value;
266
+ // toggled() writes back when the user picks a different option.
267
+ add.group('Render Quality').build(() => {
268
+ add.radio('Draft')
269
+ .value(settings.quality$.value === 'Draft')
270
+ .toggled(v => {
271
+ if (!v) return
272
+ settings.quality$.value = 'Draft'
273
+ settings.samples$.value = 16
274
+ settings.scale$.value = 0.5
275
+ })
276
+ add.radio('Standard')
277
+ .value(settings.quality$.value === 'Standard')
278
+ .toggled(v => {
279
+ if (!v) return
280
+ settings.quality$.value = 'Standard'
281
+ settings.samples$.value = 64
282
+ settings.scale$.value = 1.0
283
+ })
284
+ add.radio('High')
285
+ .value(settings.quality$.value === 'High')
286
+ .toggled(v => {
287
+ if (!v) return
288
+ settings.quality$.value = 'High'
289
+ settings.samples$.value = 256
290
+ settings.scale$.value = 2.0
291
+ })
292
+ })
293
+ })
294
+
295
+ // Right column
296
+ add.vertical(() => {
297
+ add.group('Render').build(() => {
298
+ add.horizontal((layout) => {
299
+ layout.spacing = 4
300
+ add.label('Samples:').minWidth(55)
301
+ add.slider('integer')
302
+ .value(settings.samples$)
303
+ .min(1).max(512)
304
+ .build()
305
+ })
306
+ add.horizontal((layout) => {
307
+ layout.spacing = 4
308
+ add.label('Scale:').minWidth(55)
309
+ add.slider('float')
310
+ .value(settings.scale$)
311
+ .min(0.1).max(5.0)
312
+ .build()
313
+ })
314
+ })
315
+
316
+ add.group('Export').build(() => {
317
+ add.horizontal((layout) => {
318
+ layout.spacing = 4
319
+ add.label('Output:').minWidth(55)
320
+ add.edit()
321
+ .value(settings.outputPath$)
322
+ .placeholder('Default output path...')
323
+ })
324
+ add.horizontal((layout) => {
325
+ layout.spacing = 4
326
+ add.label('Format:').minWidth(55)
327
+ // ComboBox - two-way bind to persisted string
328
+ add.combo()
329
+ .items(['JSON', 'XML', 'CSV'])
330
+ .selected(settings.exportFormat$)
331
+ })
332
+ add.checkbox('Auto Save').value(settings.autoSave$)
333
+ add.label('Auto Save enables notifications and switches the log level to Debug.').wordWrap().build()
334
+ })
335
+ })
336
+ })
337
+
338
+ // Color pickers - read the final color from the widget on accept
339
+ add.group('Colors').horizontal().build(() => {
340
+ add.vertical(() => {
341
+ add.label('Background:').build()
342
+ add.color().build()
343
+ })
344
+ add.vertical(() => {
345
+ add.label('Foreground:').build()
346
+ add.color().build()
347
+ })
348
+ })
349
+
350
+ add.group('Behaviour').horizontal().build(() => {
351
+ add.checkbox('Notifications').value(settings.notifications$)
352
+ add.horizontal((layout) => {
353
+ layout.spacing = 4
354
+ add.label('Log Level:')
355
+ // ComboEdit: user can pick from list OR type a custom level
356
+ add.comboEdit()
357
+ .items(['Debug', 'Info', 'Warning', 'Error'])
358
+ .changed(settings.logLevel$)
359
+ })
360
+ })
361
+ }
362
+
363
+ // Tab 3: About
364
+
365
+ private buildAboutTab(): void {
366
+ let add = this.add
367
+ let model = this.model
368
+ let settings = model.settings
369
+
370
+ add.group('About This Example').build(() => {
371
+ add.label([
372
+ 'DAZScript Framework - Showcase Dialog (Example 06)',
373
+ '',
374
+ 'Widgets demonstrated in this dialog:',
375
+ ' - Tabs with persisted active-tab index (bindInt)',
376
+ ' - Splitter with persisted position (bindString)',
377
+ ' - list.view - columns, keyword filter, custom filters,',
378
+ ' checkbox rows, context menu, refresh trigger, flat/tree toggle,',
379
+ ' expanded, data binding, selection binding',
380
+ ' - list.box - scrollable list with multi-select',
381
+ ' - GroupBox - vertical / horizontal / flat style / visible toggle',
382
+ ' - Label - wordWrap, minWidth, alignment',
383
+ ' - LineEdit - value binding, placeholder, readOnly',
384
+ ' - CheckBox, RadioButton - Observable two-way binding',
385
+ ' - Button - clicked, toggle, toolTip',
386
+ ' - Slider - integer and float with min/max',
387
+ ' - ColorPicker (DzColorWgt)',
388
+ ' - ComboBox - items + selected binding',
389
+ ' - ComboEdit - items + changed/edited binding',
390
+ ' - NodeSelection combo (scene node picker)',
391
+ ' - All settings persisted via AppSettings (DzAppSettings)',
392
+ ].join('\n')).wordWrap(true).build()
393
+ })
394
+
395
+ // Log list - populated externally; demonstrates Observable<string[]> binding
396
+ add.group('Recent Log').build(() => {
397
+ add.list.box()
398
+ .items(model.logEntries$)
399
+ .mode('single')
400
+ .build()
401
+ })
402
+
403
+ // Conditional filter row - group visible when showFilters$ is true
404
+ add.group('Optional Filters').horizontal()
405
+ .visible(settings.showFilters$)
406
+ .build(() => {
407
+ add.label('Tag:').minWidth(30)
408
+ add.comboEdit()
409
+ .items(['render', 'test', 'draft', 'final'])
410
+ .build()
411
+ })
412
+
413
+ add.horizontal((layout) => {
414
+ layout.spacing = 4
415
+ add.checkbox('Show Filters').value(settings.showFilters$)
416
+ })
417
+ }
418
+
419
+ private connectInteractions(): void {
420
+ let model = this.model
421
+ let settings = model.settings
422
+
423
+ settings.filterType$.connect(() => model.refreshObjects$.trigger())
424
+ settings.showVisibleOnly$.connect(() => model.refreshObjects$.trigger())
425
+ settings.showIcons$.connect(() => model.refreshObjects$.trigger())
426
+ settings.flatList$.connect(() => model.refreshObjects$.trigger())
427
+
428
+ settings.autoSave$.connect((enabled) => {
429
+ if (!enabled) return
430
+ settings.notifications$.value = true
431
+ settings.logLevel$.value = 'Debug'
432
+ this.addLog('Auto Save enabled; notifications and Debug logging selected.')
433
+ })
434
+
435
+ model.objects$.connect(() => this.updateSceneStats())
436
+ model.refreshObjects$.connect(() => this.updateSceneStats())
437
+
438
+ model.selectedObject$.connect(obj => {
439
+ model.selectedSummary$.value = obj
440
+ ? `${obj.name}: ${obj.type}, ${obj.visible ? 'visible' : 'hidden'}, ${obj.locked ? 'locked' : 'unlocked'}`
441
+ : 'No object selected'
442
+ })
443
+
444
+ model.detailVisible$.connect((visible) => {
445
+ let obj = model.selectedObject$.value
446
+ if (!obj || obj.visible === visible) return
447
+ obj.visible = visible
448
+ this.addLog(`${obj.name} visibility changed from the detail checkbox.`)
449
+ model.refreshObjects$.trigger()
450
+ })
451
+
452
+ model.detailLocked$.connect((locked) => {
453
+ let obj = model.selectedObject$.value
454
+ if (!obj || obj.locked === locked) return
455
+ obj.locked = locked
456
+ this.addLog(`${obj.name} lock state changed from the detail checkbox.`)
457
+ model.refreshObjects$.trigger()
458
+ })
459
+ }
460
+
461
+ private getDisplayName(obj: SceneObject | null): string {
462
+ if (!obj) return ''
463
+ if (!this.model.settings.showIcons$.value) return obj.name
464
+
465
+ const prefixByType: Record<SceneObject['type'], string> = {
466
+ Figure: '[F]',
467
+ Prop: '[P]',
468
+ Light: '[L]',
469
+ Camera: '[C]',
470
+ Group: '[G]',
471
+ }
472
+
473
+ return `${prefixByType[obj.type]} ${obj.name}`
474
+ }
475
+
476
+ private addLog(message: string): void {
477
+ let entries = this.model.logEntries$.value.slice()
478
+ entries.push(`[Info] ${message}`)
479
+ if (entries.length > 8) entries.shift()
480
+ this.model.logEntries$.value = entries
481
+ }
482
+
483
+ private updateSceneStats(): void {
484
+ let objects = this.getAllObjects()
485
+ let visible = objects.filter(obj => obj.visible).length
486
+ let locked = objects.filter(obj => obj.locked).length
487
+ this.model.sceneStats$.value = `${visible}/${objects.length} visible, ${locked} locked`
488
+ }
489
+
490
+ private getAllObjects(): SceneObject[] {
491
+ let objects: SceneObject[] = []
492
+ this.model.objects$.value.forEach(root => {
493
+ root.forEach(node => {
494
+ if (node.value) objects.push(node.value)
495
+ })
496
+ })
497
+ return objects
498
+ }
499
+
500
+ private applyCheckedRows(): void {
501
+ if (!this.sceneListView) return
502
+
503
+ this.sceneListView.getItems(DzListView.All).forEach(item => {
504
+ if (!(item as any).inherits('DzCheckListItem')) return
505
+
506
+ let obj = getDataItem<SceneObject>(item)
507
+ if (!obj) return
508
+
509
+ let checkbox = item as DzCheckListItem
510
+ obj.visible = checkbox.state === 0 && !checkbox.on
511
+ ? false
512
+ : checkbox.on
513
+ })
514
+
515
+ this.addLog('List checkbox states applied to scene objects.')
516
+ this.model.refreshObjects$.trigger()
517
+ }
518
+ }
@@ -4,6 +4,7 @@ import { mainWindow } from '@dsf/core/global'
4
4
  import * as array from '@dsf/helpers/array-helper'
5
5
  import { keys } from '@dsf/helpers/object-helper'
6
6
  import { progress } from '@dsf/helpers/progress-helper'
7
+ import CustomSet from '@dsf/lib/set'
7
8
  import { getMenu } from './menu-helper'
8
9
  import { getScriptPath } from './script-helper'
9
10
 
@@ -445,7 +446,7 @@ export const installCustomActions = (actions: CustomAction[]) => {
445
446
 
446
447
  export const uninstallCustomActions = (actions: CustomAction[]) => {
447
448
  debug(`Uninstalling Actions`)
448
- const toolbarNames = new Set<string>()
449
+ const toolbarNames = new CustomSet<string>()
449
450
 
450
451
  actions.forEach(action => {
451
452
  if (!action) return
@@ -10,6 +10,7 @@ import { Observable } from '@dsf/lib/observable'
10
10
  import CustomSet from '@dsf/lib/set'
11
11
  import { TreeNode } from '@dsf/lib/tree-node'
12
12
  import { promptKeyboardShortcut } from '@dsf/shared/set-keyboard-shortcut'
13
+ import { readFromFile } from './file-helper'
13
14
 
14
15
  type InstallerEntry = {
15
16
  action: CustomAction
@@ -28,6 +29,12 @@ type InstallerEntry = {
28
29
  type SetupDialogOptions = {
29
30
  settingsPath: string
30
31
  bundleName?: string
32
+ shortcutsPath?: string
33
+ }
34
+
35
+ type ActionAccelerator = {
36
+ name: string
37
+ shortcut: string
31
38
  }
32
39
 
33
40
  const OVERRIDE_MARKER = '[ovr]'
@@ -37,6 +44,9 @@ const toKey = (action: CustomAction): string => String(action.filePath ?? action
37
44
  const toTreeNode = (entry: InstallerEntry): TreeNode<InstallerEntry> =>
38
45
  new TreeNode(String(entry.action.text), toKey(entry.action), entry)
39
46
 
47
+ const getEntry = (item: TreeNode<InstallerEntry>): InstallerEntry =>
48
+ item.value as InstallerEntry
49
+
40
50
  const getDisplayedToolbar = (entry: InstallerEntry): string => String(entry.action.toolbar ?? '')
41
51
 
42
52
  const getSetupDialogOptions = (options: string | SetupDialogOptions): SetupDialogOptions =>
@@ -123,8 +133,9 @@ class InstallerSelectionDialog extends BasicDialog {
123
133
  .sortOnBuild(true)
124
134
  .refresh(this.refreshListEvent$)
125
135
  .row((item, parent, id) => {
136
+ const entry = getEntry(item)
126
137
  const listItem = new DzCheckListItem(parent, DzCheckListItem.CheckBox, id)
127
- listItem.on = item.value.selected
138
+ listItem.on = entry.selected
128
139
  listItem.setText(0, '')
129
140
  return listItem
130
141
  })
@@ -139,15 +150,18 @@ class InstallerSelectionDialog extends BasicDialog {
139
150
  if (index === 5) return Math.max(width * 1.2, 140)
140
151
  return width
141
152
  })
142
- .text((item) => [
143
- '',
144
- String(item.value.action.text ?? ''),
145
- getDisplayedShortcut(item.value),
146
- String(item.value.action.description ?? ''),
147
- String(item.value.action.menuPath ?? ''),
148
- getDisplayedToolbar(item.value),
149
- ])
150
- .data((item) => item.value)
153
+ .text((item) => {
154
+ const entry = getEntry(item)
155
+ return [
156
+ '',
157
+ String(entry.action.text ?? ''),
158
+ getDisplayedShortcut(entry),
159
+ String(entry.action.description ?? ''),
160
+ String(entry.action.menuPath ?? ''),
161
+ getDisplayedToolbar(entry),
162
+ ]
163
+ })
164
+ .data((item) => getEntry(item))
151
165
  .filter({
152
166
  keywords: this.keywords$,
153
167
  field: (listItem) => [
@@ -287,6 +301,21 @@ export const showSetupCustomActionsDialog = (actions: CustomAction[], options: s
287
301
  const selections = runDialog(actions, settings)
288
302
  if (!selections) return
289
303
 
304
+ if (settings.shortcutsPath) {
305
+ try {
306
+ const shortcuts = readFromFile<ActionAccelerator[]>(settings.shortcutsPath)
307
+ if (shortcuts) {
308
+ progress('Applying Keyboard Shortcuts', shortcuts, (shortcut) => {
309
+ if (shortcut.name && shortcut.shortcut) {
310
+ setActionShortcut(shortcut.name, shortcut.shortcut)
311
+ }
312
+ })
313
+ }
314
+ } catch (e) {
315
+ debug(`[Setup] Failed to apply shortcuts from ${settings.shortcutsPath}: ${e}`)
316
+ }
317
+ }
318
+
290
319
  debug(`[Setup] applying ${selections.filter(selection => selection.selected).length}/${selections.length} selected actions`)
291
320
 
292
321
  const removedToolbarNames = new CustomSet<string>()