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 +37 -1
- package/dist/scripts/install-generator.js +85 -14
- package/package.json +1 -1
- package/src/Setup.dsa.ts +1 -1
- package/src/helpers/custom-action-installer-helper.ts +284 -27
- package/webpack.config.js +6 -0
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,
|
|
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
|
-
|
|
30
|
-
|
|
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,
|
|
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
|
-
|
|
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,
|
|
231
|
+
function processScripts(paths, container, defaultMenuPath, setupOptions) {
|
|
228
232
|
paths.forEach((filePath) => {
|
|
229
233
|
console.log(`Processing ${filePath}`);
|
|
230
|
-
processScript(filePath, container, defaultMenuPath,
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
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
|
-
|
|
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,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):
|
|
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
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
|
},
|