dazscript-framework 1.0.1 → 1.0.3
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,9 +252,44 @@ 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
257
|
This replaces the older `Install.dsa.ts` / `Uninstall.dsa.ts` pattern. The installer generator removes those legacy files if they exist.
|
|
256
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
|
+
Shortcut setup is part of the generated setup dialog. The generator does not create a separate shortcut-only uninstall script.
|
|
292
|
+
|
|
257
293
|
---
|
|
258
294
|
|
|
259
295
|
### Action-Level Bundles
|
|
@@ -21,14 +21,11 @@ function stringOrDefault(str, defaultValue) {
|
|
|
21
21
|
return str !== undefined && str !== null && str !== '' ? str : defaultValue;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
function generateInstallerTemplate(data,
|
|
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)});
|
|
32
29
|
`;
|
|
33
30
|
}
|
|
34
31
|
|
|
@@ -151,7 +148,7 @@ function findActionEntryFiles(workdir, options) {
|
|
|
151
148
|
});
|
|
152
149
|
}
|
|
153
150
|
|
|
154
|
-
function processScript(filePath, container, defaultMenuPath,
|
|
151
|
+
function processScript(filePath, container, defaultMenuPath, setupOptions) {
|
|
155
152
|
const fileInfo = path.parse(filePath);
|
|
156
153
|
const content = fs.readFileSync(filePath, 'utf-8').toString();
|
|
157
154
|
const actionCall = findTopLevelActionCall(content, filePath);
|
|
@@ -213,8 +210,7 @@ function processScript(filePath, container, defaultMenuPath, settingsPath, bundl
|
|
|
213
210
|
|
|
214
211
|
let bundleScriptContent = generateInstallerTemplate(
|
|
215
212
|
JSON.stringify(container.scripts, null, 4),
|
|
216
|
-
|
|
217
|
-
bundleName
|
|
213
|
+
setupOptions
|
|
218
214
|
);
|
|
219
215
|
let bundleScriptFilePath = path.join(
|
|
220
216
|
path.parse(filePath).dir,
|
|
@@ -224,13 +220,66 @@ function processScript(filePath, container, defaultMenuPath, settingsPath, bundl
|
|
|
224
220
|
}
|
|
225
221
|
}
|
|
226
222
|
|
|
227
|
-
function processScripts(paths, container, defaultMenuPath,
|
|
223
|
+
function processScripts(paths, container, defaultMenuPath, setupOptions) {
|
|
228
224
|
paths.forEach((filePath) => {
|
|
229
225
|
console.log(`Processing ${filePath}`);
|
|
230
|
-
processScript(filePath, container, defaultMenuPath,
|
|
226
|
+
processScript(filePath, container, defaultMenuPath, setupOptions);
|
|
231
227
|
});
|
|
232
228
|
}
|
|
233
229
|
|
|
230
|
+
function toPosix(filePath) {
|
|
231
|
+
return filePath.replace(/\\/g, '/');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function resolveShortcutFile(workdir, config) {
|
|
235
|
+
const configuredPath =
|
|
236
|
+
config.keyboardShortcutsPath ||
|
|
237
|
+
config.shortcutsPath ||
|
|
238
|
+
config.actionAcceleratorsPath;
|
|
239
|
+
|
|
240
|
+
const candidates = [];
|
|
241
|
+
if (configuredPath) {
|
|
242
|
+
candidates.push(path.resolve(workdir, configuredPath));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
candidates.push(
|
|
246
|
+
path.join(workdir, 'src', 'keyboard-shortcuts.json'),
|
|
247
|
+
path.join(workdir, 'src', 'action-accelerators.json'),
|
|
248
|
+
path.join(workdir, 'keyboard-shortcuts.json'),
|
|
249
|
+
path.join(workdir, 'action-accelerators.json')
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
return candidates.find((candidate) => fs.existsSync(candidate)) || null;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function normalizeShortcutData(raw) {
|
|
256
|
+
if (!raw) return [];
|
|
257
|
+
if (Array.isArray(raw)) return raw;
|
|
258
|
+
if (Array.isArray(raw.actions)) return raw.actions;
|
|
259
|
+
if (Array.isArray(raw.shortcuts)) return raw.shortcuts;
|
|
260
|
+
if (Array.isArray(raw.accelerators)) return raw.accelerators;
|
|
261
|
+
return [];
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function loadShortcutData(workdir, config) {
|
|
265
|
+
const shortcutFile = resolveShortcutFile(workdir, config);
|
|
266
|
+
if (!shortcutFile) {
|
|
267
|
+
return { shortcuts: undefined, sourcePath: undefined };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const raw = JSON.parse(fs.readFileSync(shortcutFile, 'utf8'));
|
|
271
|
+
const shortcuts = normalizeShortcutData(raw);
|
|
272
|
+
|
|
273
|
+
if (shortcuts.length === 0) {
|
|
274
|
+
console.warn(`[dazscript] Keyboard shortcut file has no shortcut entries: ${shortcutFile}`);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
shortcuts,
|
|
279
|
+
sourcePath: toPosix(path.relative(workdir, shortcutFile)),
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
234
283
|
function generateInstallerFiles(workdir, options) {
|
|
235
284
|
const defaultMenuPath = options.defaultMenuPath.endsWith('/')
|
|
236
285
|
? options.defaultMenuPath
|
|
@@ -241,10 +290,20 @@ function generateInstallerFiles(workdir, options) {
|
|
|
241
290
|
const bundleName = typeof config.bundleName === 'string' && config.bundleName.trim()
|
|
242
291
|
? config.bundleName.trim()
|
|
243
292
|
: undefined;
|
|
293
|
+
const shortcutData = loadShortcutData(workdir, config);
|
|
294
|
+
const setupOptions = {
|
|
295
|
+
settingsPath,
|
|
296
|
+
bundleName,
|
|
297
|
+
shortcutBackupPath: `${appDataPath}/Installer/keyboard-shortcuts-backup.json`,
|
|
298
|
+
};
|
|
299
|
+
if (shortcutData.shortcuts && shortcutData.shortcuts.length > 0) {
|
|
300
|
+
setupOptions.shortcuts = shortcutData.shortcuts;
|
|
301
|
+
setupOptions.shortcutsSourcePath = shortcutData.sourcePath;
|
|
302
|
+
}
|
|
244
303
|
const container = { scripts: [] };
|
|
245
304
|
const matches = findActionEntryFiles(workdir, options);
|
|
246
305
|
|
|
247
|
-
processScripts(matches, container, defaultMenuPath,
|
|
306
|
+
processScripts(matches, container, defaultMenuPath, setupOptions);
|
|
248
307
|
|
|
249
308
|
container.scripts = container.scripts.sort((a, b) => {
|
|
250
309
|
const aKey = a.menuPath + a.filePath;
|
|
@@ -254,15 +313,17 @@ function generateInstallerFiles(workdir, options) {
|
|
|
254
313
|
|
|
255
314
|
const installerScriptContent = generateInstallerTemplate(
|
|
256
315
|
JSON.stringify(container.scripts, null, 4),
|
|
257
|
-
|
|
258
|
-
bundleName
|
|
316
|
+
setupOptions
|
|
259
317
|
);
|
|
260
318
|
fs.writeFileSync(path.join(workdir, 'src', 'Setup.dsa.ts'), installerScriptContent);
|
|
261
319
|
|
|
262
320
|
const installPath = path.join(workdir, 'src', 'Install.dsa.ts');
|
|
263
321
|
const uninstallPath = path.join(workdir, 'src', 'Uninstall.dsa.ts');
|
|
264
322
|
if (fs.existsSync(installPath)) fs.unlinkSync(installPath);
|
|
265
|
-
|
|
323
|
+
|
|
324
|
+
if (fs.existsSync(uninstallPath)) {
|
|
325
|
+
fs.unlinkSync(uninstallPath);
|
|
326
|
+
}
|
|
266
327
|
}
|
|
267
328
|
|
|
268
329
|
if (require.main === module) {
|
package/package.json
CHANGED
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"});
|
|
@@ -78,6 +78,10 @@ export type CustomActionTargets = {
|
|
|
78
78
|
toolbar: boolean
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
type ApplyCustomActionOptions = {
|
|
82
|
+
deferToolbar?: boolean
|
|
83
|
+
}
|
|
84
|
+
|
|
81
85
|
type CustomActionCandidate = {
|
|
82
86
|
name: string
|
|
83
87
|
text: string
|
|
@@ -377,13 +381,18 @@ export const getInstalledCustomActionState = (action: CustomAction, scriptsPath:
|
|
|
377
381
|
}
|
|
378
382
|
}
|
|
379
383
|
|
|
380
|
-
export const applyCustomActionTargets = (
|
|
384
|
+
export const applyCustomActionTargets = (
|
|
385
|
+
action: CustomAction,
|
|
386
|
+
targets: CustomActionTargets,
|
|
387
|
+
scriptsPath: string = getScriptPath(),
|
|
388
|
+
options: ApplyCustomActionOptions = {}
|
|
389
|
+
): CustomAction | null => {
|
|
381
390
|
const resolvedAction = resolveActionPaths(action, scriptsPath)
|
|
382
391
|
const shouldInstall = targets.menu || targets.toolbar
|
|
383
392
|
|
|
384
393
|
if (!shouldInstall) {
|
|
385
394
|
removeCustomActionTargets(action, { menu: true, toolbar: true }, scriptsPath)
|
|
386
|
-
return
|
|
395
|
+
return null
|
|
387
396
|
}
|
|
388
397
|
|
|
389
398
|
const customAction = createOrUpdateCustomAction(action, resolvedAction)
|
|
@@ -392,9 +401,11 @@ export const applyCustomActionTargets = (action: CustomAction, targets: CustomAc
|
|
|
392
401
|
addToMenu(customAction)
|
|
393
402
|
}
|
|
394
403
|
|
|
395
|
-
if (targets.toolbar && action.toolbar) {
|
|
404
|
+
if (targets.toolbar && action.toolbar && !options.deferToolbar) {
|
|
396
405
|
addToToolbar(customAction)
|
|
397
406
|
}
|
|
407
|
+
|
|
408
|
+
return customAction
|
|
398
409
|
}
|
|
399
410
|
|
|
400
411
|
export const removeCustomActionTargets = (action: CustomAction, targets: CustomActionTargets, scriptsPath: string = getScriptPath()) => {
|
|
@@ -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
|
-
|
|
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(
|
|
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
|
-
|
|
183
|
-
this.
|
|
184
|
-
|
|
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,54 +464,128 @@ 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):
|
|
514
|
+
const runDialog = (actions: CustomAction[], options: SetupDialogOptions): SetupSelection | null => {
|
|
294
515
|
const entries = buildEntries(actions)
|
|
295
|
-
const
|
|
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
|
-
|
|
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
|
-
const
|
|
564
|
+
const touchedToolbarNames = new CustomSet<string>()
|
|
565
|
+
const selectedToolbarActions: CustomAction[] = []
|
|
322
566
|
|
|
323
|
-
progress('Setting Up Scripts', selections, (selection) => {
|
|
567
|
+
progress('Setting Up Scripts', selections.actions, (selection) => {
|
|
324
568
|
debug(`[Setup] ${selection.selected ? 'install' : 'remove'} "${selection.action.text}"`)
|
|
325
569
|
|
|
570
|
+
if (selection.supportsToolbar && selection.action.toolbar) {
|
|
571
|
+
touchedToolbarNames.add(selection.action.toolbar)
|
|
572
|
+
}
|
|
573
|
+
|
|
326
574
|
if (selection.selected) {
|
|
327
|
-
applyCustomActionTargets({
|
|
575
|
+
const customAction = applyCustomActionTargets({
|
|
328
576
|
...selection.action,
|
|
329
577
|
shortcut: selection.effectiveShortcut
|
|
330
578
|
}, {
|
|
331
579
|
menu: selection.supportsMenu,
|
|
332
580
|
toolbar: selection.supportsToolbar
|
|
581
|
+
}, undefined, {
|
|
582
|
+
deferToolbar: true
|
|
333
583
|
})
|
|
334
|
-
return
|
|
335
|
-
}
|
|
336
584
|
|
|
337
|
-
|
|
338
|
-
|
|
585
|
+
if (customAction && selection.supportsToolbar && selection.action.toolbar) {
|
|
586
|
+
selectedToolbarActions.push(customAction)
|
|
587
|
+
}
|
|
588
|
+
return
|
|
339
589
|
}
|
|
340
590
|
|
|
341
591
|
removeCustomActionTargets(selection.action, {
|
|
@@ -344,18 +594,29 @@ export const showSetupCustomActionsDialog = (actions: CustomAction[], options: s
|
|
|
344
594
|
})
|
|
345
595
|
})
|
|
346
596
|
|
|
347
|
-
|
|
597
|
+
touchedToolbarNames.forEach((toolbarName) => {
|
|
348
598
|
clearToolbar(toolbarName)
|
|
349
599
|
|
|
350
|
-
const stillSelected =
|
|
351
|
-
stillSelected.forEach((
|
|
352
|
-
...s.action,
|
|
353
|
-
shortcut: s.effectiveShortcut
|
|
354
|
-
}))
|
|
600
|
+
const stillSelected = selectedToolbarActions.filter(action => action.toolbar === toolbarName)
|
|
601
|
+
stillSelected.forEach((action) => addToToolbar(action))
|
|
355
602
|
debug(`[Setup] Toolbar ${toolbarName} rebuilt with ${stillSelected.length} remaining actions`)
|
|
356
603
|
|
|
357
604
|
if (stillSelected.length === 0) {
|
|
358
605
|
cleanupEmptyToolbar(toolbarName)
|
|
359
606
|
}
|
|
360
607
|
})
|
|
608
|
+
|
|
609
|
+
applyKeyboardShortcuts(selections.shortcuts, settings)
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
export const restoreSetupKeyboardShortcuts = (options: string | SetupDialogOptions) => {
|
|
613
|
+
const settings = getSetupDialogOptions(options)
|
|
614
|
+
const backupPath = getShortcutBackupPath(settings)
|
|
615
|
+
const backup = readFromFile<ShortcutBackupFile>(backupPath)
|
|
616
|
+
if (!backup || !backup.shortcuts || backup.shortcuts.length === 0) return
|
|
617
|
+
|
|
618
|
+
progress('Restoring Keyboard Shortcuts', backup.shortcuts, (shortcut) => {
|
|
619
|
+
if (!shortcut.name) return
|
|
620
|
+
setActionShortcut(shortcut.name, shortcut.shortcut ?? '')
|
|
621
|
+
})
|
|
361
622
|
}
|
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
|
},
|