dazscript-framework 1.0.1 → 1.0.2

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 CHANGED
@@ -241,6 +241,7 @@ This scans all `.dsa.ts` entry files, reads each top-level `action(...)` call, a
241
241
  The generated setup dialog:
242
242
 
243
243
  - Shows an install checkbox per action with columns for Action, Shortcut, Description, Menu, and Toolbar
244
+ - Adds a `Keyboard Shortcuts` tab when a project defines shortcut JSON
244
245
  - Includes a search box that filters across all columns
245
246
  - Supports Select All / Deselect All on the visible rows
246
247
  - Lets the user right-click to set or reset a shortcut (overrides shown with `[ovr]`)
@@ -251,8 +252,43 @@ Applying the dialog:
251
252
  - Checked rows are installed or updated
252
253
  - Unchecked rows are removed from their menu and toolbar targets
253
254
  - Affected toolbars are rebuilt; empty framework-created toolbars are removed
255
+ - Selected keyboard shortcut rows are applied after actions are installed
254
256
 
255
- This replaces the older `Install.dsa.ts` / `Uninstall.dsa.ts` pattern. The installer generator removes those legacy files if they exist.
257
+ This replaces the older `Install.dsa.ts` / `Uninstall.dsa.ts` pattern. The installer generator removes those legacy files if they exist, except when shortcut restoration is needed.
258
+
259
+ ### Setup Keyboard Shortcuts
260
+
261
+ Projects can define keyboard shortcuts for both framework custom actions and built-in Daz Studio actions. The installer generator looks for shortcut JSON in this order:
262
+
263
+ - `keyboardShortcutsPath`, `shortcutsPath`, or `actionAcceleratorsPath` in `dazscript.config.ts`
264
+ - `src/keyboard-shortcuts.json`
265
+ - `src/action-accelerators.json`
266
+ - `keyboard-shortcuts.json`
267
+ - `action-accelerators.json`
268
+
269
+ The JSON can be an array or an object containing `actions`, `shortcuts`, or `accelerators`. Each entry can use the Action Accelerator Finder style fields:
270
+
271
+ ```json
272
+ [
273
+ {
274
+ "name": "DzRenderAction",
275
+ "text": "Render",
276
+ "shortcut": "CTRL+R"
277
+ }
278
+ ]
279
+ ```
280
+
281
+ Accepted shortcut fields are `shortcut`, `accelerator`, or `key`. Accepted action-name fields are `name` or `action`.
282
+
283
+ At build time the JSON is embedded into generated `Setup.dsa.ts`; Daz Studio does not need to read the original JSON file at setup time. During setup, the `Keyboard Shortcuts` tab shows the action label, current shortcut, new shortcut, action type, and conflicts. The user chooses which shortcut rows to apply.
284
+
285
+ Before changing a non-custom Daz Studio action shortcut, setup writes the original value to:
286
+
287
+ ```text
288
+ App.getAppDataPath()/<appDataPath>/Installer/keyboard-shortcuts-backup.json
289
+ ```
290
+
291
+ When shortcut JSON exists, the generator also writes `src/Uninstall.dsa.ts`. Running that uninstall script restores backed-up non-custom shortcuts. Custom action shortcuts are not backed up because uninstalling the custom action removes the shortcut with the action.
256
292
 
257
293
  ---
258
294
 
@@ -21,14 +21,19 @@ function stringOrDefault(str, defaultValue) {
21
21
  return str !== undefined && str !== null && str !== '' ? str : defaultValue;
22
22
  }
23
23
 
24
- function generateInstallerTemplate(data, settingsPath, bundleName) {
24
+ function generateInstallerTemplate(data, options) {
25
25
  return `
26
26
  import { showSetupCustomActionsDialog as setup } from '@dsf/helpers/custom-action-installer-helper';
27
27
 
28
- setup(${data}, ${JSON.stringify({
29
- settingsPath,
30
- bundleName,
31
- })});
28
+ setup(${data}, ${JSON.stringify(options)});
29
+ `;
30
+ }
31
+
32
+ function generateUninstallTemplate(options) {
33
+ return `
34
+ import { restoreSetupKeyboardShortcuts as restoreShortcuts } from '@dsf/helpers/custom-action-installer-helper';
35
+
36
+ restoreShortcuts(${JSON.stringify(options)});
32
37
  `;
33
38
  }
34
39
 
@@ -151,7 +156,7 @@ function findActionEntryFiles(workdir, options) {
151
156
  });
152
157
  }
153
158
 
154
- function processScript(filePath, container, defaultMenuPath, settingsPath, bundleName) {
159
+ function processScript(filePath, container, defaultMenuPath, setupOptions) {
155
160
  const fileInfo = path.parse(filePath);
156
161
  const content = fs.readFileSync(filePath, 'utf-8').toString();
157
162
  const actionCall = findTopLevelActionCall(content, filePath);
@@ -213,8 +218,7 @@ function processScript(filePath, container, defaultMenuPath, settingsPath, bundl
213
218
 
214
219
  let bundleScriptContent = generateInstallerTemplate(
215
220
  JSON.stringify(container.scripts, null, 4),
216
- settingsPath,
217
- bundleName
221
+ setupOptions
218
222
  );
219
223
  let bundleScriptFilePath = path.join(
220
224
  path.parse(filePath).dir,
@@ -224,13 +228,66 @@ function processScript(filePath, container, defaultMenuPath, settingsPath, bundl
224
228
  }
225
229
  }
226
230
 
227
- function processScripts(paths, container, defaultMenuPath, settingsPath, bundleName) {
231
+ function processScripts(paths, container, defaultMenuPath, setupOptions) {
228
232
  paths.forEach((filePath) => {
229
233
  console.log(`Processing ${filePath}`);
230
- processScript(filePath, container, defaultMenuPath, settingsPath, bundleName);
234
+ processScript(filePath, container, defaultMenuPath, setupOptions);
231
235
  });
232
236
  }
233
237
 
238
+ function toPosix(filePath) {
239
+ return filePath.replace(/\\/g, '/');
240
+ }
241
+
242
+ function resolveShortcutFile(workdir, config) {
243
+ const configuredPath =
244
+ config.keyboardShortcutsPath ||
245
+ config.shortcutsPath ||
246
+ config.actionAcceleratorsPath;
247
+
248
+ const candidates = [];
249
+ if (configuredPath) {
250
+ candidates.push(path.resolve(workdir, configuredPath));
251
+ }
252
+
253
+ candidates.push(
254
+ path.join(workdir, 'src', 'keyboard-shortcuts.json'),
255
+ path.join(workdir, 'src', 'action-accelerators.json'),
256
+ path.join(workdir, 'keyboard-shortcuts.json'),
257
+ path.join(workdir, 'action-accelerators.json')
258
+ );
259
+
260
+ return candidates.find((candidate) => fs.existsSync(candidate)) || null;
261
+ }
262
+
263
+ function normalizeShortcutData(raw) {
264
+ if (!raw) return [];
265
+ if (Array.isArray(raw)) return raw;
266
+ if (Array.isArray(raw.actions)) return raw.actions;
267
+ if (Array.isArray(raw.shortcuts)) return raw.shortcuts;
268
+ if (Array.isArray(raw.accelerators)) return raw.accelerators;
269
+ return [];
270
+ }
271
+
272
+ function loadShortcutData(workdir, config) {
273
+ const shortcutFile = resolveShortcutFile(workdir, config);
274
+ if (!shortcutFile) {
275
+ return { shortcuts: undefined, sourcePath: undefined };
276
+ }
277
+
278
+ const raw = JSON.parse(fs.readFileSync(shortcutFile, 'utf8'));
279
+ const shortcuts = normalizeShortcutData(raw);
280
+
281
+ if (shortcuts.length === 0) {
282
+ console.warn(`[dazscript] Keyboard shortcut file has no shortcut entries: ${shortcutFile}`);
283
+ }
284
+
285
+ return {
286
+ shortcuts,
287
+ sourcePath: toPosix(path.relative(workdir, shortcutFile)),
288
+ };
289
+ }
290
+
234
291
  function generateInstallerFiles(workdir, options) {
235
292
  const defaultMenuPath = options.defaultMenuPath.endsWith('/')
236
293
  ? options.defaultMenuPath
@@ -241,10 +298,20 @@ function generateInstallerFiles(workdir, options) {
241
298
  const bundleName = typeof config.bundleName === 'string' && config.bundleName.trim()
242
299
  ? config.bundleName.trim()
243
300
  : undefined;
301
+ const shortcutData = loadShortcutData(workdir, config);
302
+ const setupOptions = {
303
+ settingsPath,
304
+ bundleName,
305
+ shortcutBackupPath: `${appDataPath}/Installer/keyboard-shortcuts-backup.json`,
306
+ };
307
+ if (shortcutData.shortcuts && shortcutData.shortcuts.length > 0) {
308
+ setupOptions.shortcuts = shortcutData.shortcuts;
309
+ setupOptions.shortcutsSourcePath = shortcutData.sourcePath;
310
+ }
244
311
  const container = { scripts: [] };
245
312
  const matches = findActionEntryFiles(workdir, options);
246
313
 
247
- processScripts(matches, container, defaultMenuPath, settingsPath, bundleName);
314
+ processScripts(matches, container, defaultMenuPath, setupOptions);
248
315
 
249
316
  container.scripts = container.scripts.sort((a, b) => {
250
317
  const aKey = a.menuPath + a.filePath;
@@ -254,15 +321,19 @@ function generateInstallerFiles(workdir, options) {
254
321
 
255
322
  const installerScriptContent = generateInstallerTemplate(
256
323
  JSON.stringify(container.scripts, null, 4),
257
- settingsPath,
258
- bundleName
324
+ setupOptions
259
325
  );
260
326
  fs.writeFileSync(path.join(workdir, 'src', 'Setup.dsa.ts'), installerScriptContent);
261
327
 
262
328
  const installPath = path.join(workdir, 'src', 'Install.dsa.ts');
263
329
  const uninstallPath = path.join(workdir, 'src', 'Uninstall.dsa.ts');
264
330
  if (fs.existsSync(installPath)) fs.unlinkSync(installPath);
265
- if (fs.existsSync(uninstallPath)) fs.unlinkSync(uninstallPath);
331
+
332
+ if (shortcutData.shortcuts && shortcutData.shortcuts.length > 0) {
333
+ fs.writeFileSync(path.join(workdir, 'src', 'Uninstall.dsa.ts'), generateUninstallTemplate(setupOptions));
334
+ } else if (fs.existsSync(uninstallPath)) {
335
+ fs.unlinkSync(uninstallPath);
336
+ }
266
337
  }
267
338
 
268
339
  if (require.main === module) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dazscript-framework",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "author": "Freddy Diaz",
5
5
  "license": "MPL-2.0",
6
6
  "description": "",
package/src/Setup.dsa.ts CHANGED
@@ -44,4 +44,4 @@ setup([
44
44
  "menuPath": "/DazScriptFramework/examples",
45
45
  "description": "06-showcase-dialog"
46
46
  }
47
- ], {"settingsPath":"DazScriptFramework/examples/Installer","bundleName":"Examples"});
47
+ ], {"settingsPath":"DazScriptFramework/examples/Installer","bundleName":"Examples","shortcutBackupPath":"DazScriptFramework/examples/Installer/keyboard-shortcuts-backup.json"});
@@ -2,15 +2,15 @@ import { debug } from '@dsf/common/log'
2
2
  import { CustomAction } from '@dsf/core/custom-action'
3
3
  import { BasicDialog } from '@dsf/dialog/basic-dialog'
4
4
  import { PopupMenuBuilder, PopupMenuItem } from '@dsf/dialog/builders/popup-menu-builder'
5
+ import { findAction, findActionsForShortcut, getActionShortcut, isCustomAction, normalizeShortcut, setActionShortcut } from '@dsf/helpers/action-helper'
5
6
  import { addToToolbar, applyCustomActionTargets, cleanupEmptyToolbar, clearToolbar, getInstalledCustomActionState, removeCustomActionTargets } from '@dsf/helpers/custom-action-helper'
6
7
  import { checkAll, getDataItem } from '@dsf/helpers/list-view-helper'
7
- import { normalizeShortcut, setActionShortcut } from '@dsf/helpers/action-helper'
8
8
  import { progress } from '@dsf/helpers/progress-helper'
9
9
  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
+ import { readFromFile, saveToFile } from './file-helper'
14
14
 
15
15
  type InstallerEntry = {
16
16
  action: CustomAction
@@ -29,14 +29,47 @@ type InstallerEntry = {
29
29
  type SetupDialogOptions = {
30
30
  settingsPath: string
31
31
  bundleName?: string
32
- shortcutsPath?: string
32
+ shortcuts?: ActionAccelerator[]
33
+ shortcutsSourcePath?: string
34
+ shortcutBackupPath?: string
33
35
  }
34
36
 
35
37
  type ActionAccelerator = {
38
+ name?: string
39
+ action?: string
40
+ text?: string
41
+ label?: string
42
+ shortcut?: string
43
+ accelerator?: string
44
+ key?: string
45
+ }
46
+
47
+ type ShortcutEntry = {
48
+ selected: boolean
49
+ name: string
50
+ label: string
51
+ currentShortcut: string
52
+ newShortcut: string
53
+ conflictText: string
54
+ exists: boolean
55
+ isCustom: boolean
56
+ }
57
+
58
+ type ShortcutBackupEntry = {
36
59
  name: string
37
60
  shortcut: string
38
61
  }
39
62
 
63
+ type ShortcutBackupFile = {
64
+ version: number
65
+ shortcuts: ShortcutBackupEntry[]
66
+ }
67
+
68
+ type SetupSelection = {
69
+ actions: InstallerEntry[]
70
+ shortcuts: ShortcutEntry[]
71
+ }
72
+
40
73
  const OVERRIDE_MARKER = '[ovr]'
41
74
 
42
75
  const toKey = (action: CustomAction): string => String(action.filePath ?? action.text ?? '')
@@ -44,9 +77,15 @@ const toKey = (action: CustomAction): string => String(action.filePath ?? action
44
77
  const toTreeNode = (entry: InstallerEntry): TreeNode<InstallerEntry> =>
45
78
  new TreeNode(String(entry.action.text), toKey(entry.action), entry)
46
79
 
80
+ const toShortcutTreeNode = (entry: ShortcutEntry): TreeNode<ShortcutEntry> =>
81
+ new TreeNode(entry.label, entry.name, entry)
82
+
47
83
  const getEntry = (item: TreeNode<InstallerEntry>): InstallerEntry =>
48
84
  item.value as InstallerEntry
49
85
 
86
+ const getShortcutEntry = (item: TreeNode<ShortcutEntry>): ShortcutEntry =>
87
+ item.value as ShortcutEntry
88
+
50
89
  const getDisplayedToolbar = (entry: InstallerEntry): string => String(entry.action.toolbar ?? '')
51
90
 
52
91
  const getSetupDialogOptions = (options: string | SetupDialogOptions): SetupDialogOptions =>
@@ -54,6 +93,9 @@ const getSetupDialogOptions = (options: string | SetupDialogOptions): SetupDialo
54
93
  ? { settingsPath: options }
55
94
  : options
56
95
 
96
+ const getShortcutBackupPath = (options: SetupDialogOptions): string =>
97
+ `${App.getAppDataPath()}/${options.shortcutBackupPath ?? `${options.settingsPath}/keyboard-shortcuts-backup.json`}`
98
+
57
99
  const getDisplayedShortcut = (entry: InstallerEntry): string => {
58
100
  if (!entry.isShortcutOverridden) return entry.effectiveShortcut
59
101
  return entry.effectiveShortcut
@@ -95,15 +137,66 @@ const buildEntries = (actions: CustomAction[]): InstallerEntry[] => {
95
137
  })
96
138
  }
97
139
 
140
+ const normalizeAccelerator = (accelerator: ActionAccelerator): { name: string, label: string, shortcut: string } | null => {
141
+ const name = String(accelerator.name ?? accelerator.action ?? '').trim()
142
+ const shortcut = normalizeShortcut(String(accelerator.shortcut ?? accelerator.accelerator ?? accelerator.key ?? '').trim())
143
+ if (!name || !shortcut) return null
144
+
145
+ return {
146
+ name,
147
+ label: String(accelerator.text ?? accelerator.label ?? name),
148
+ shortcut
149
+ }
150
+ }
151
+
152
+ const getShortcutConflictText = (name: string, shortcut: string): string => {
153
+ if (!shortcut) return ''
154
+
155
+ const conflicts = findActionsForShortcut(shortcut)
156
+ .filter(action => action && action.name !== name)
157
+
158
+ return conflicts.map(action => String(action.text || action.name)).join(', ')
159
+ }
160
+
161
+ const buildShortcutEntries = (accelerators: ActionAccelerator[] = []): ShortcutEntry[] => {
162
+ return accelerators
163
+ .map(normalizeAccelerator)
164
+ .filter((accelerator): accelerator is { name: string, label: string, shortcut: string } => accelerator !== null)
165
+ .map((accelerator) => {
166
+ const action = findAction(accelerator.name)
167
+ const currentShortcut = getActionShortcut(accelerator.name)
168
+ const newShortcut = normalizeShortcut(accelerator.shortcut)
169
+
170
+ return {
171
+ selected: true,
172
+ name: accelerator.name,
173
+ label: action?.text ? String(action.text) : accelerator.label,
174
+ currentShortcut,
175
+ newShortcut,
176
+ conflictText: getShortcutConflictText(accelerator.name, newShortcut),
177
+ exists: action !== null,
178
+ isCustom: action ? isCustomAction(action) : false
179
+ }
180
+ })
181
+ }
182
+
98
183
  class InstallerSelectionDialog extends BasicDialog {
99
184
  private readonly keywords$ = new Observable('')
100
185
  private readonly items$: Observable<TreeNode<InstallerEntry>[]>
186
+ private readonly shortcutKeywords$ = new Observable('')
187
+ private readonly shortcutItems$: Observable<TreeNode<ShortcutEntry>[]>
101
188
  private readonly refreshListEvent$ = new Observable<void>()
102
189
  private listView: DzListView | null = null
190
+ private shortcutListView: DzListView | null = null
103
191
 
104
- constructor(private readonly entries: InstallerEntry[], bundleName?: string) {
192
+ constructor(
193
+ private readonly entries: InstallerEntry[],
194
+ private readonly shortcutEntries: ShortcutEntry[],
195
+ bundleName?: string
196
+ ) {
105
197
  super(bundleName ? `${bundleName} Setup` : 'Setup Scripts', 'dsfSetupScripts')
106
198
  this.items$ = new Observable(entries.map(toTreeNode))
199
+ this.shortcutItems$ = new Observable(shortcutEntries.map(toShortcutTreeNode))
107
200
  }
108
201
 
109
202
  protected build(): void {
@@ -111,7 +204,28 @@ class InstallerSelectionDialog extends BasicDialog {
111
204
  this.dialog.setAcceptButtonText('Apply')
112
205
  this.dialog.setCancelButtonText('Cancel')
113
206
 
207
+ if (this.shortcutEntries.length > 0) {
208
+ const add = this.add
209
+ add.tab('Scripts').build(() => this.buildScriptsTab())
210
+ add.tab('Keyboard Shortcuts').build(() => this.buildShortcutsTab())
211
+ return
212
+ }
213
+
214
+ this.buildScriptsTab()
215
+ }
216
+
217
+ getSelections(): SetupSelection {
218
+ this.syncSelectionsFromListView()
219
+ this.syncShortcutSelectionsFromListView()
220
+ return {
221
+ actions: this.entries,
222
+ shortcuts: this.shortcutEntries
223
+ }
224
+ }
225
+
226
+ private buildScriptsTab(): void {
114
227
  const add = this.add
228
+
115
229
  add.label('Choose which scripts to install, then use the columns to review their shortcut, menu, and toolbar targets.')
116
230
  .wordWrap()
117
231
  .build()
@@ -179,9 +293,71 @@ class InstallerSelectionDialog extends BasicDialog {
179
293
  })
180
294
  }
181
295
 
182
- getSelections(): InstallerEntry[] {
183
- this.syncSelectionsFromListView()
184
- return this.entries
296
+ private buildShortcutsTab(): void {
297
+ const add = this.add
298
+
299
+ add.label('Choose which keyboard shortcuts to apply. Current and new shortcut values are shown side by side.')
300
+ .wordWrap()
301
+ .build()
302
+ add.group('Search').horizontal().build(() => {
303
+ add.edit()
304
+ .value(this.shortcutKeywords$)
305
+ .placeholder('Search shortcuts...')
306
+ .toolTip('Search by action name, current shortcut, new shortcut, status, or conflicts.')
307
+ add.button('Select All').clicked(() => this.setVisibleShortcutSelections(true))
308
+ add.button('Deselect All').clicked(() => this.setVisibleShortcutSelections(false))
309
+ })
310
+
311
+ add.group('Keyboard Shortcuts')
312
+ .style({ flat: true })
313
+ .build(() => {
314
+ add.list.view<ShortcutEntry, ShortcutEntry>()
315
+ .sorting(true)
316
+ .sortOnBuild(true)
317
+ .row((item, parent, id) => {
318
+ const entry = getShortcutEntry(item)
319
+ const listItem = new DzCheckListItem(parent, DzCheckListItem.CheckBox, id)
320
+ listItem.on = entry.selected
321
+ listItem.setText(0, '')
322
+ return listItem
323
+ })
324
+ .items(this.shortcutItems$)
325
+ .columns(['Apply', 'Action', 'Current', 'New', 'Status', 'Conflicts'], (index, width) => {
326
+ if (index === 0) return Math.max(width, 70)
327
+ if (index === 1) return Math.max(width * 2, 260)
328
+ if (index === 2) return Math.max(width, 130)
329
+ if (index === 3) return Math.max(width, 130)
330
+ if (index === 4) return Math.max(width, 120)
331
+ if (index === 5) return Math.max(width * 2, 260)
332
+ return width
333
+ })
334
+ .text((item) => {
335
+ const entry = getShortcutEntry(item)
336
+ return [
337
+ '',
338
+ entry.label,
339
+ entry.currentShortcut || '(none)',
340
+ entry.newShortcut || '(none)',
341
+ entry.exists ? (entry.isCustom ? 'Custom Action' : 'DAZ Action') : 'Not currently found',
342
+ entry.conflictText || ''
343
+ ]
344
+ })
345
+ .data((item) => getShortcutEntry(item))
346
+ .filter({
347
+ keywords: this.shortcutKeywords$,
348
+ field: (listItem) => [
349
+ listItem.text(1),
350
+ listItem.text(2),
351
+ listItem.text(3),
352
+ listItem.text(4),
353
+ listItem.text(5),
354
+ ].join(' ')
355
+ })
356
+ .build((listView) => {
357
+ this.shortcutListView = listView
358
+ listView.allColumnsShowFocus = true
359
+ })
360
+ })
185
361
  }
186
362
 
187
363
  private buildContextMenu(listView: DzListView, listItem: DzListViewItem | null): DzPopupMenu {
@@ -288,39 +464,106 @@ class InstallerSelectionDialog extends BasicDialog {
288
464
  }
289
465
  })
290
466
  }
467
+
468
+ private setVisibleShortcutSelections(onOff: boolean) {
469
+ if (!this.shortcutListView) return
470
+ this.checkShortcutVisible(this.shortcutListView, onOff)
471
+ }
472
+
473
+ private checkShortcutVisible(listView: DzListView, onOff: boolean) {
474
+ if (!this.shortcutKeywords$.value?.trim()) {
475
+ checkAll(listView, onOff)
476
+ }
477
+
478
+ listView.getItems(DzListView.All).forEach((item) => {
479
+ if (!item.visible || !(item as any).inherits('DzCheckListItem')) return
480
+
481
+ const checkbox = item as DzCheckListItem
482
+ checkbox.on = onOff
483
+
484
+ const data = getDataItem<ShortcutEntry>(item)
485
+ if (!data) return
486
+ data.selected = onOff
487
+ })
488
+ }
489
+
490
+ private syncShortcutSelectionsFromListView() {
491
+ if (!this.shortcutListView) return
492
+
493
+ const selectionByName: Record<string, boolean> = {}
494
+
495
+ this.shortcutListView.getItems(DzListView.All).forEach((item) => {
496
+ if (!(item as any).inherits('DzCheckListItem')) return
497
+
498
+ const data = getDataItem<ShortcutEntry>(item)
499
+ if (!data) return
500
+
501
+ const checkbox = item as DzCheckListItem
502
+ const selected = !(checkbox.state === 0 && !checkbox.on)
503
+ selectionByName[data.name] = selected
504
+ })
505
+
506
+ this.shortcutEntries.forEach((entry) => {
507
+ if (Object.prototype.hasOwnProperty.call(selectionByName, entry.name)) {
508
+ entry.selected = selectionByName[entry.name]
509
+ }
510
+ })
511
+ }
291
512
  }
292
513
 
293
- const runDialog = (actions: CustomAction[], options: SetupDialogOptions): InstallerEntry[] | null => {
514
+ const runDialog = (actions: CustomAction[], options: SetupDialogOptions): SetupSelection | null => {
294
515
  const entries = buildEntries(actions)
295
- const dialog = new InstallerSelectionDialog(entries, options.bundleName)
516
+ const shortcutEntries = buildShortcutEntries(options.shortcuts)
517
+ const dialog = new InstallerSelectionDialog(entries, shortcutEntries, options.bundleName)
296
518
  return dialog.ok() ? dialog.getSelections() : null
297
519
  }
298
520
 
521
+ const readShortcutBackup = (backupPath: string): ShortcutBackupFile => {
522
+ const backup = readFromFile<ShortcutBackupFile>(backupPath)
523
+ return backup ?? { version: 1, shortcuts: [] }
524
+ }
525
+
526
+ const saveShortcutBackup = (backupPath: string, backup: ShortcutBackupFile): void => {
527
+ saveToFile(backupPath, JSON.stringify(backup, null, 2))
528
+ }
529
+
530
+ const backupCurrentShortcut = (entry: ShortcutEntry, backupPath: string): void => {
531
+ const action = findAction(entry.name)
532
+ if (!action || isCustomAction(action)) return
533
+
534
+ const backup = readShortcutBackup(backupPath)
535
+ const alreadyBackedUp = backup.shortcuts.some(shortcut => shortcut.name === entry.name)
536
+ if (alreadyBackedUp) return
537
+
538
+ backup.shortcuts.push({
539
+ name: entry.name,
540
+ shortcut: getActionShortcut(entry.name)
541
+ })
542
+ saveShortcutBackup(backupPath, backup)
543
+ }
544
+
545
+ const applyKeyboardShortcuts = (shortcuts: ShortcutEntry[], options: SetupDialogOptions): void => {
546
+ const selected = shortcuts.filter(shortcut => shortcut.selected)
547
+ if (selected.length === 0) return
548
+
549
+ const backupPath = getShortcutBackupPath(options)
550
+
551
+ progress('Applying Keyboard Shortcuts', selected, (shortcut) => {
552
+ backupCurrentShortcut(shortcut, backupPath)
553
+ setActionShortcut(shortcut.name, shortcut.newShortcut)
554
+ })
555
+ }
556
+
299
557
  export const showSetupCustomActionsDialog = (actions: CustomAction[], options: string | SetupDialogOptions) => {
300
558
  const settings = getSetupDialogOptions(options)
301
559
  const selections = runDialog(actions, settings)
302
560
  if (!selections) return
303
561
 
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
-
319
- debug(`[Setup] applying ${selections.filter(selection => selection.selected).length}/${selections.length} selected actions`)
562
+ debug(`[Setup] applying ${selections.actions.filter(selection => selection.selected).length}/${selections.actions.length} selected actions`)
320
563
 
321
564
  const removedToolbarNames = new CustomSet<string>()
322
565
 
323
- progress('Setting Up Scripts', selections, (selection) => {
566
+ progress('Setting Up Scripts', selections.actions, (selection) => {
324
567
  debug(`[Setup] ${selection.selected ? 'install' : 'remove'} "${selection.action.text}"`)
325
568
 
326
569
  if (selection.selected) {
@@ -347,7 +590,7 @@ export const showSetupCustomActionsDialog = (actions: CustomAction[], options: s
347
590
  removedToolbarNames.forEach((toolbarName) => {
348
591
  clearToolbar(toolbarName)
349
592
 
350
- const stillSelected = selections.filter(s => s.selected && s.supportsToolbar && s.action.toolbar === toolbarName)
593
+ const stillSelected = selections.actions.filter(s => s.selected && s.supportsToolbar && s.action.toolbar === toolbarName)
351
594
  stillSelected.forEach((s) => addToToolbar({
352
595
  ...s.action,
353
596
  shortcut: s.effectiveShortcut
@@ -358,4 +601,18 @@ export const showSetupCustomActionsDialog = (actions: CustomAction[], options: s
358
601
  cleanupEmptyToolbar(toolbarName)
359
602
  }
360
603
  })
604
+
605
+ applyKeyboardShortcuts(selections.shortcuts, settings)
606
+ }
607
+
608
+ export const restoreSetupKeyboardShortcuts = (options: string | SetupDialogOptions) => {
609
+ const settings = getSetupDialogOptions(options)
610
+ const backupPath = getShortcutBackupPath(settings)
611
+ const backup = readFromFile<ShortcutBackupFile>(backupPath)
612
+ if (!backup || !backup.shortcuts || backup.shortcuts.length === 0) return
613
+
614
+ progress('Restoring Keyboard Shortcuts', backup.shortcuts, (shortcut) => {
615
+ if (!shortcut.name) return
616
+ setActionShortcut(shortcut.name, shortcut.shortcut ?? '')
617
+ })
361
618
  }
package/webpack.config.js CHANGED
@@ -75,6 +75,12 @@ module.exports = (env, argv) => {
75
75
  loader: 'ts-loader',
76
76
  options: {
77
77
  allowTsInNodeModules: true,
78
+ configFile: path.resolve(projectRoot, 'tsconfig.json'),
79
+ compilerOptions: {
80
+ strict: false,
81
+ strictNullChecks: false,
82
+ strictPropertyInitialization: false,
83
+ },
78
84
  ignoreDiagnostics: [5107],
79
85
  },
80
86
  },