dazscript-framework 0.2.5 → 0.3.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.
@@ -0,0 +1,332 @@
1
+ import { debug } from '@dsf/common/log'
2
+ import { CustomAction } from '@dsf/core/custom-action'
3
+ import { BasicDialog } from '@dsf/dialog/basic-dialog'
4
+ import { PopupMenuBuilder, PopupMenuItem } from '@dsf/dialog/builders/popup-menu-builder'
5
+ import { addToToolbar, applyCustomActionTargets, cleanupEmptyToolbar, clearToolbar, getInstalledCustomActionState, removeCustomActionTargets } from '@dsf/helpers/custom-action-helper'
6
+ import { checkAll, getDataItem } from '@dsf/helpers/list-view-helper'
7
+ import { normalizeShortcut, setActionShortcut } from '@dsf/helpers/action-helper'
8
+ import { progress } from '@dsf/helpers/progress-helper'
9
+ import { Observable } from '@dsf/lib/observable'
10
+ import CustomSet from '@dsf/lib/set'
11
+ import { TreeNode } from '@dsf/lib/tree-node'
12
+ import { promptKeyboardShortcut } from '@dsf/shared/set-keyboard-shortcut'
13
+
14
+ type InstallerEntry = {
15
+ action: CustomAction
16
+ supportsMenu: boolean
17
+ supportsToolbar: boolean
18
+ selected: boolean
19
+ installedMenu: boolean
20
+ installedToolbar: boolean
21
+ installedShortcut: string
22
+ defaultShortcut: string
23
+ effectiveShortcut: string
24
+ isShortcutOverridden: boolean
25
+ installedActionName: string
26
+ }
27
+
28
+ type SetupDialogOptions = {
29
+ settingsPath: string
30
+ bundleName?: string
31
+ }
32
+
33
+ const OVERRIDE_MARKER = '[ovr]'
34
+
35
+ const toKey = (action: CustomAction): string => String(action.filePath ?? action.text ?? '')
36
+
37
+ const toTreeNode = (entry: InstallerEntry): TreeNode<InstallerEntry> =>
38
+ new TreeNode(String(entry.action.text), toKey(entry.action), entry)
39
+
40
+ const getDisplayedToolbar = (entry: InstallerEntry): string => String(entry.action.toolbar ?? '')
41
+
42
+ const getSetupDialogOptions = (options: string | SetupDialogOptions): SetupDialogOptions =>
43
+ typeof options === 'string'
44
+ ? { settingsPath: options }
45
+ : options
46
+
47
+ const getDisplayedShortcut = (entry: InstallerEntry): string => {
48
+ if (!entry.isShortcutOverridden) return entry.effectiveShortcut
49
+ return entry.effectiveShortcut
50
+ ? `${entry.effectiveShortcut} ${OVERRIDE_MARKER}`
51
+ : OVERRIDE_MARKER
52
+ }
53
+
54
+ const updateOverrideState = (entry: InstallerEntry) => {
55
+ entry.isShortcutOverridden = Boolean(entry.installedActionName) &&
56
+ normalizeShortcut(entry.installedShortcut) !== normalizeShortcut(entry.defaultShortcut)
57
+ }
58
+
59
+ const buildEntries = (actions: CustomAction[]): InstallerEntry[] => {
60
+ return actions
61
+ .filter((action) => Boolean(action.menuPath) || Boolean(action.toolbar))
62
+ .map((action) => {
63
+ const installed = getInstalledCustomActionState(action)
64
+ const supportsMenu = Boolean(action.menuPath)
65
+ const supportsToolbar = Boolean(action.toolbar)
66
+ const hasInstalledTargets = installed.installedMenu || installed.installedToolbar
67
+ const defaultShortcut = normalizeShortcut(String(action.shortcut ?? ''))
68
+ const installedShortcut = normalizeShortcut(String(installed.customAction?.shortcut ?? ''))
69
+ const entry: InstallerEntry = {
70
+ action: { ...action },
71
+ supportsMenu,
72
+ supportsToolbar,
73
+ selected: hasInstalledTargets,
74
+ installedMenu: installed.installedMenu,
75
+ installedToolbar: installed.installedToolbar,
76
+ installedShortcut,
77
+ defaultShortcut,
78
+ effectiveShortcut: installed.customAction?.name ? installedShortcut : defaultShortcut,
79
+ isShortcutOverridden: false,
80
+ installedActionName: String(installed.customAction?.name ?? '')
81
+ }
82
+
83
+ updateOverrideState(entry)
84
+ return entry
85
+ })
86
+ }
87
+
88
+ class InstallerSelectionDialog extends BasicDialog {
89
+ private readonly keywords$ = new Observable('')
90
+ private readonly items$: Observable<TreeNode<InstallerEntry>[]>
91
+ private readonly refreshListEvent$ = new Observable<void>()
92
+ private listView: DzListView | null = null
93
+
94
+ constructor(private readonly entries: InstallerEntry[], bundleName?: string) {
95
+ super(bundleName ? `${bundleName} Setup` : 'Setup Scripts', 'dsfSetupScripts')
96
+ this.items$ = new Observable(entries.map(toTreeNode))
97
+ }
98
+
99
+ protected build(): void {
100
+ this.builder.options({ resizable: true, width: 1100, height: 700 })
101
+ this.dialog.setAcceptButtonText('Apply')
102
+ this.dialog.setCancelButtonText('Cancel')
103
+
104
+ const add = this.add
105
+ add.label('Choose which scripts to install, then use the columns to review their shortcut, menu, and toolbar targets.')
106
+ .wordWrap()
107
+ .build()
108
+ add.group('Search').horizontal().build(() => {
109
+ add.edit()
110
+ .value(this.keywords$)
111
+ .focus()
112
+ .placeholder('Search actions...')
113
+ .toolTip('Search by action name, description, path, shortcut, or available targets.')
114
+ add.button('Select All').clicked(() => this.setVisibleSelections(true))
115
+ add.button('Deselect All').clicked(() => this.setVisibleSelections(false))
116
+ })
117
+
118
+ add.group('Scripts')
119
+ .style({ flat: true })
120
+ .build(() => {
121
+ add.list.view<InstallerEntry, InstallerEntry>()
122
+ .sorting(true)
123
+ .sortOnBuild(true)
124
+ .refresh(this.refreshListEvent$)
125
+ .row((item, parent, id) => {
126
+ const listItem = new DzCheckListItem(parent, DzCheckListItem.CheckBox, id)
127
+ listItem.on = item.value.selected
128
+ listItem.setText(0, '')
129
+ return listItem
130
+ })
131
+ .contextMenu((listView, item) => this.buildContextMenu(listView, item))
132
+ .items(this.items$)
133
+ .columns(['Install', 'Action', 'Shortcut', 'Description', 'Menu', 'Toolbar'], (index, width) => {
134
+ if (index === 0) return Math.max(width, 70)
135
+ if (index === 1) return Math.max(width * 1.6, 220)
136
+ if (index === 2) return Math.max(width, 140)
137
+ if (index === 3) return Math.max(width * 1.8, 240)
138
+ if (index === 4) return Math.max(width * 2.5, 320)
139
+ if (index === 5) return Math.max(width * 1.2, 140)
140
+ return width
141
+ })
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)
151
+ .filter({
152
+ keywords: this.keywords$,
153
+ field: (listItem) => [
154
+ listItem.text(1),
155
+ listItem.text(2),
156
+ listItem.text(3),
157
+ listItem.text(4),
158
+ listItem.text(5),
159
+ ].join(' ')
160
+ })
161
+ .build((listView) => {
162
+ this.listView = listView
163
+ listView.allColumnsShowFocus = true
164
+ })
165
+ })
166
+ }
167
+
168
+ getSelections(): InstallerEntry[] {
169
+ this.syncSelectionsFromListView()
170
+ return this.entries
171
+ }
172
+
173
+ private buildContextMenu(listView: DzListView, listItem: DzListViewItem | null): DzPopupMenu {
174
+ const entry = listItem ? getDataItem<InstallerEntry>(listItem) : null
175
+ const canSetShortcut = Boolean(entry)
176
+ const canResetShortcut = Boolean(entry?.installedActionName && entry?.isShortcutOverridden)
177
+
178
+ const items = [
179
+ {
180
+ text: 'Set Shortcut',
181
+ activated: () => {
182
+ if (!entry) return
183
+ this.setShortcut(entry)
184
+ }
185
+ },
186
+ {
187
+ text: 'Reset Default Shortcut',
188
+ activated: () => {
189
+ if (!entry) return
190
+ this.resetDefaultShortcut(entry)
191
+ }
192
+ },
193
+ {},
194
+ { text: 'Check All', activated: () => this.checkVisible(listView, true) },
195
+ { text: 'Uncheck All', activated: () => this.checkVisible(listView, false) }
196
+ ] as PopupMenuItem[]
197
+
198
+ return new PopupMenuBuilder(this.builder.context).items(...items).build((menu) => {
199
+ menu.setItemEnabled(0, canSetShortcut)
200
+ menu.setItemEnabled(1, canResetShortcut)
201
+ })
202
+ }
203
+
204
+ private setShortcut(entry: InstallerEntry) {
205
+ const actionName = entry.installedActionName || toKey(entry.action)
206
+ const actionLabel = String(entry.action.text ?? entry.action.filePath ?? actionName)
207
+ const shortcut = promptKeyboardShortcut(actionLabel, actionName, entry.effectiveShortcut)
208
+ if (shortcut == null) return
209
+
210
+ if (entry.installedActionName) {
211
+ setActionShortcut(entry.installedActionName, shortcut)
212
+ entry.installedShortcut = normalizeShortcut(shortcut)
213
+ entry.effectiveShortcut = entry.installedShortcut
214
+ updateOverrideState(entry)
215
+ } else {
216
+ entry.effectiveShortcut = normalizeShortcut(shortcut)
217
+ }
218
+
219
+ this.refreshListEvent$.trigger()
220
+ }
221
+
222
+ private resetDefaultShortcut(entry: InstallerEntry) {
223
+ if (!entry.installedActionName || !entry.isShortcutOverridden) return
224
+
225
+ setActionShortcut(entry.installedActionName, entry.defaultShortcut)
226
+ entry.installedShortcut = normalizeShortcut(entry.defaultShortcut)
227
+ entry.effectiveShortcut = entry.installedShortcut
228
+ updateOverrideState(entry)
229
+ this.refreshListEvent$.trigger()
230
+ }
231
+
232
+ private checkVisible(listView: DzListView, onOff: boolean) {
233
+ if (!this.keywords$.value?.trim()) {
234
+ checkAll(listView, onOff)
235
+ }
236
+
237
+ listView.getItems(DzListView.All).forEach((item) => {
238
+ if (!item.visible || !(item as any).inherits('DzCheckListItem')) return
239
+
240
+ const checkbox = item as DzCheckListItem
241
+ checkbox.on = onOff
242
+
243
+ const data = getDataItem<InstallerEntry>(item)
244
+ if (!data) return
245
+ data.selected = onOff
246
+ })
247
+ }
248
+
249
+ private setVisibleSelections(onOff: boolean) {
250
+ if (!this.listView) return
251
+ this.checkVisible(this.listView, onOff)
252
+ }
253
+
254
+ private syncSelectionsFromListView() {
255
+ if (!this.listView) return
256
+
257
+ const selectionByKey: Record<string, boolean> = {}
258
+
259
+ this.listView.getItems(DzListView.All).forEach((item) => {
260
+ if (!(item as any).inherits('DzCheckListItem')) return
261
+
262
+ const data = getDataItem<InstallerEntry>(item)
263
+ if (!data) return
264
+
265
+ const checkbox = item as DzCheckListItem
266
+ const selected = !(checkbox.state === 0 && !checkbox.on)
267
+ selectionByKey[toKey(data.action)] = selected
268
+ })
269
+
270
+ this.entries.forEach((entry) => {
271
+ const key = toKey(entry.action)
272
+ if (Object.prototype.hasOwnProperty.call(selectionByKey, key)) {
273
+ entry.selected = selectionByKey[key]
274
+ }
275
+ })
276
+ }
277
+ }
278
+
279
+ const runDialog = (actions: CustomAction[], options: SetupDialogOptions): InstallerEntry[] | null => {
280
+ const entries = buildEntries(actions)
281
+ const dialog = new InstallerSelectionDialog(entries, options.bundleName)
282
+ return dialog.ok() ? dialog.getSelections() : null
283
+ }
284
+
285
+ export const showSetupCustomActionsDialog = (actions: CustomAction[], options: string | SetupDialogOptions) => {
286
+ const settings = getSetupDialogOptions(options)
287
+ const selections = runDialog(actions, settings)
288
+ if (!selections) return
289
+
290
+ debug(`[Setup] applying ${selections.filter(selection => selection.selected).length}/${selections.length} selected actions`)
291
+
292
+ const removedToolbarNames = new CustomSet<string>()
293
+
294
+ progress('Setting Up Scripts', selections, (selection) => {
295
+ debug(`[Setup] ${selection.selected ? 'install' : 'remove'} "${selection.action.text}"`)
296
+
297
+ if (selection.selected) {
298
+ applyCustomActionTargets({
299
+ ...selection.action,
300
+ shortcut: selection.effectiveShortcut
301
+ }, {
302
+ menu: selection.supportsMenu,
303
+ toolbar: selection.supportsToolbar
304
+ })
305
+ return
306
+ }
307
+
308
+ if (selection.supportsToolbar && selection.action.toolbar) {
309
+ removedToolbarNames.add(selection.action.toolbar)
310
+ }
311
+
312
+ removeCustomActionTargets(selection.action, {
313
+ menu: selection.supportsMenu,
314
+ toolbar: selection.supportsToolbar
315
+ })
316
+ })
317
+
318
+ removedToolbarNames.forEach((toolbarName) => {
319
+ clearToolbar(toolbarName)
320
+
321
+ const stillSelected = selections.filter(s => s.selected && s.supportsToolbar && s.action.toolbar === toolbarName)
322
+ stillSelected.forEach((s) => addToToolbar({
323
+ ...s.action,
324
+ shortcut: s.effectiveShortcut
325
+ }))
326
+ debug(`[Setup] Toolbar ${toolbarName} rebuilt with ${stillSelected.length} remaining actions`)
327
+
328
+ if (stillSelected.length === 0) {
329
+ cleanupEmptyToolbar(toolbarName)
330
+ }
331
+ })
332
+ }
@@ -80,7 +80,7 @@ export const deleteFile = (filePath: string): boolean => {
80
80
  try {
81
81
  var file = new DzFile(filePath)
82
82
  if (!file.exists()) return false
83
- file.remove(filePath)
83
+ file.remove(/*filePath*/)
84
84
  file.deleteLater()
85
85
  return true
86
86
  } catch (error) {
@@ -13,7 +13,7 @@ export const setDataItem = (listItem: DzListViewItem, data: any) => {
13
13
  }
14
14
 
15
15
  export const getDataItem = <T>(listItem: DzListViewItem): T | null => {
16
- return listItem?.getDataItem('data') ?? null
16
+ return (listItem?.getDataItem('data') ?? null) as T | null
17
17
  }
18
18
 
19
19
  export const filter = (listView: DzListView, filterOn: (viewItem: DzListViewItem) => string, keywords: string, options?: { selectOnFilter?: boolean, filters?: (viewItem: DzListViewItem) => boolean }) => {
@@ -10,7 +10,7 @@ export const error = (message: string, writeLog: boolean = true) => {
10
10
  }
11
11
 
12
12
  export const warning = (message: string) => {
13
- MessageBox.warning(message, "Warning", "Ok")
13
+ MessageBox.warning(message, "Warning", "Ok", "")
14
14
  }
15
15
 
16
16
  export const confirm = (message?: string): { ok: boolean, cancel: boolean } => {
@@ -20,7 +20,7 @@ export const confirm = (message?: string): { ok: boolean, cancel: boolean } => {
20
20
  }
21
21
 
22
22
  export const prompt = (text: string, title: string, button0: string, button1?: string): { cancel: boolean, selection: number } => {
23
- const response = MessageBox.question(text, title, button0, button1, "Cancel")
23
+ const response = MessageBox.question(text, title, button0, button1 ?? "", "Cancel")
24
24
 
25
25
  return { cancel: response === 0, selection: response };
26
26
  }
@@ -26,7 +26,7 @@ export const isFigure = (node: DzNode): boolean => {
26
26
  * @param node
27
27
  * @returns The root of the node, if the node is part of a figure, return the figure (skeleton) otherwise return the node itself
28
28
  */
29
- export const getRoot = (node: DzNode): DzNode => {
29
+ export const getRoot = (node: DzNode | null): DzNode | null => {
30
30
  if (node && node.className() === "DzBone" && node.getSkeleton)
31
31
  return node.getSkeleton();
32
32
  else
@@ -44,8 +44,8 @@ export const getFigure = (node: DzNode): DzSkeleton | null => {
44
44
  * @returns The first property with the given name or internal name, or NULL.
45
45
  */
46
46
  export const findProperty = <T extends DzProperty = DzProperty>(node: DzNode, name: string): T | null => {
47
- return sceneHelper.findPropertyOnNode(name, node) as T
48
- ?? sceneHelper.findPropertyOnNodeByInternalName(name, node) as T
47
+ return sceneHelper.findPropertyOnNode(name, node) as unknown as T
48
+ ?? sceneHelper.findPropertyOnNodeByInternalName(name, node) as unknown as T
49
49
  }
50
50
 
51
51
  /**
@@ -90,7 +90,7 @@ export const isChildOf = (node: DzNode, figure: DzNode): boolean => {
90
90
  * @returns the fitting target (skeleton) of the node or null
91
91
  */
92
92
  export const getFittingTarget = (node: DzNode): DzSkeleton | null => {
93
- return getRoot(node).getSkeleton()?.getFollowTarget()
93
+ return getRoot(node)?.getSkeleton()?.getFollowTarget() ?? null
94
94
  }
95
95
 
96
96
  /**
@@ -99,7 +99,7 @@ export const getFittingTarget = (node: DzNode): DzSkeleton | null => {
99
99
  * @param target if specified, check if the node is fitted to the target node, otherwise check if the node is fitted to any other node
100
100
  * @returns true if the node is fitted to another node
101
101
  */
102
- export const isFitting = (figure: DzSkeleton, target?: DzNode): boolean => {
102
+ export const isFitting = (figure: DzSkeleton | null, target?: DzNode): boolean => {
103
103
  return target
104
104
  ? figure?.getFollowTarget() == target
105
105
  : figure?.getFollowTarget() != null
@@ -140,12 +140,12 @@ export const getTransforms = (node: DzNode, include: { rotations?: boolean, tran
140
140
  export const getRotations = (node: DzNode, includeExtraRotations: boolean = true): DzFloatProperty[] => {
141
141
  if (!node) return [];
142
142
 
143
- const xRotate = sceneHelper.findPropertyOnNode('XRotate', node) as DzFloatProperty;
144
- const xRotate2 = sceneHelper.findPropertyOnNode('XRotate2', node) as DzFloatProperty;
145
- const yRotate = sceneHelper.findPropertyOnNode('YRotate', node) as DzFloatProperty;
146
- const yRotate2 = sceneHelper.findPropertyOnNode('YRotate2', node) as DzFloatProperty;
147
- const zRotate = sceneHelper.findPropertyOnNode('ZRotate', node) as DzFloatProperty;
148
- const zRotate2 = sceneHelper.findPropertyOnNode('ZRotate2', node) as DzFloatProperty;
143
+ const xRotate = sceneHelper.findPropertyOnNode('XRotate', node) as unknown as DzFloatProperty;
144
+ const xRotate2 = sceneHelper.findPropertyOnNode('XRotate2', node) as unknown as DzFloatProperty;
145
+ const yRotate = sceneHelper.findPropertyOnNode('YRotate', node) as unknown as DzFloatProperty;
146
+ const yRotate2 = sceneHelper.findPropertyOnNode('YRotate2', node) as unknown as DzFloatProperty;
147
+ const zRotate = sceneHelper.findPropertyOnNode('ZRotate', node) as unknown as DzFloatProperty;
148
+ const zRotate2 = sceneHelper.findPropertyOnNode('ZRotate2', node) as unknown as DzFloatProperty;
149
149
 
150
150
  if (includeExtraRotations) {
151
151
  return [xRotate, xRotate2, yRotate, yRotate2, zRotate, zRotate2].filter(Boolean);
@@ -162,7 +162,7 @@ export const getRotations = (node: DzNode, includeExtraRotations: boolean = true
162
162
  export const getRotationsForAxis = (node: DzNode, axis: 'x' | 'y' | 'z'): DzFloatProperty => {
163
163
  debug(`Getting rotation property for axis: ${axis} on node: ${node.getName()} - isBone: ${isBone(node)}`);
164
164
 
165
- let prop: DzFloatProperty | undefined;
165
+ let prop: DzFloatProperty | null = null;
166
166
 
167
167
  if (isBone(node)) {
168
168
  prop = getNamedRotationForAxis(node, axis);
@@ -175,7 +175,7 @@ export const getRotationsForAxis = (node: DzNode, axis: 'x' | 'y' | 'z'): DzFloa
175
175
  prop = find(
176
176
  getRotations(node, false),
177
177
  (rotation) => rotation.getName().valueOf()[0].toLowerCase() === axis
178
- ) as DzFloatProperty;
178
+ ) as unknown as DzFloatProperty;
179
179
  }
180
180
 
181
181
  return prop;
@@ -185,7 +185,7 @@ export const getNamedRotations = (node: DzNode): DzFloatProperty[] => {
185
185
  const rotations: DzFloatProperty[] = []
186
186
 
187
187
  for (let namedAxis in NamedAxis) {
188
- let property = sceneHelper.findPropertyOnNodeByLabel(NamedAxis[namedAxis], node) as DzFloatProperty
188
+ let property = sceneHelper.findPropertyOnNodeByLabel(NamedAxis[namedAxis], node) as unknown as DzFloatProperty
189
189
  if (property) rotations.push(property)
190
190
  }
191
191
 
@@ -1,11 +1,11 @@
1
1
  import { mainWindow } from '@dsf/core/global'
2
2
 
3
3
  const findPane = <T extends DzPane>(className: string): T => {
4
- return mainWindow.getPaneMgr().findPane(className) as T
4
+ return mainWindow.getPaneMgr().findPane(className) as unknown as T
5
5
  }
6
6
 
7
7
  export const getSmartContentPane = (): DzSmartContentPane => {
8
- return <DzSmartContentPane>findPane("DzSmartContentPane")
8
+ return findPane("DzSmartContentPane") as unknown as DzSmartContentPane
9
9
  }
10
10
 
11
11
  export const getSurfacesPane = (): DzSurfacesPane => {
@@ -20,10 +20,10 @@ export const getParametersPane = (): DzParametersPane => {
20
20
  return findPane<DzParametersPane>("DzParametersPane")
21
21
  }
22
22
 
23
- export const getParametersPaneNodeEditor = (): DzPropertySideNavHierarchy => {
23
+ export const getParametersPaneNodeEditor = (): DzPropertySideNavHierarchy | null => {
24
24
  return findPane<DzParametersPane>("DzParametersPane")?.getNodeEditor() ?? null
25
25
  }
26
26
 
27
- export const getPaneNodeEditor = (pane: DzAbstractNodeEditorPane): DzPropertySideNavHierarchy => {
27
+ export const getPaneNodeEditor = (pane: DzAbstractNodeEditorPane): DzPropertySideNavHierarchy | null => {
28
28
  return pane.getNodeEditor ? pane.getNodeEditor() : null
29
- }
29
+ }
@@ -4,9 +4,9 @@ import { getRoot, isFigure } from './node-helper'
4
4
  import { getPaneNodeEditor, getParametersPane, getParametersPaneNodeEditor } from './pane-helper'
5
5
 
6
6
  export const getSelectedOf = <T>(typeName: string): DzNode[] => {
7
- let nodes = []
7
+ let nodes: DzNode[] = []
8
8
  for (let node of scene.getSelectedNodeList()) {
9
- if (!node.inherits(typeName)) continue
9
+ if (!node || !node.inherits(typeName)) continue
10
10
  nodes.push(node)
11
11
  }
12
12
  return nodes
@@ -45,7 +45,7 @@ export const getFigures = (): DzSkeleton[] => {
45
45
  * @returns the figure skeleton, or null if there is no current selection or the selected node is not part of a figure
46
46
  */
47
47
  export const getSelectedFigure = (): DzSkeleton | null => {
48
- return getSelectedNode()?.getSkeleton?.()
48
+ return getSelectedNode()?.getSkeleton?.() ?? null
49
49
  }
50
50
 
51
51
  /**
@@ -66,12 +66,12 @@ export const getSelectedNodes = (): DzNode[] => {
66
66
  return scene.getSelectedNodeList()
67
67
  }
68
68
 
69
- export const getSelectedRoot = (): DzNode => {
69
+ export const getSelectedRoot = (): DzNode | null => {
70
70
  return getRoot(getSelectedNode())
71
71
  }
72
72
 
73
73
  export const getSelectedRoots = (): DzNode[] => {
74
- return distinct(getSelectedNodes().map(n => getRoot(n)))
74
+ return distinct(getSelectedNodes().map(n => getRoot(n))).filter(n => n !== null) as DzNode[]
75
75
  }
76
76
 
77
77
  export const getSelectedProperties = (): DzProperty[] => {
@@ -79,7 +79,7 @@ export const getSelectedProperties = (): DzProperty[] => {
79
79
  }
80
80
 
81
81
  export const getSelectedNumericProperties = (): (DzFloatProperty | DzIntProperty | DzBoolProperty)[] => {
82
- return getParametersPane()?.getNodeEditor()?.getPropertySelections(true).map(p => p as DzFloatProperty | DzIntProperty | DzBoolProperty) ?? []
82
+ return getParametersPane()?.getNodeEditor()?.getPropertySelections(true).map(p => p as unknown as DzFloatProperty | DzIntProperty | DzBoolProperty) ?? []
83
83
  }
84
84
 
85
85
  export const getSelectedPropertiesOfType = <TProperty extends DzProperty>(type: string): TProperty[] => {
@@ -125,7 +125,7 @@ export const getCurrentFrame = (): number => {
125
125
  }
126
126
 
127
127
  export const getEndFrame = (): number => {
128
- return scene.getAnimRange().end / scene.getTimeStep().valueOf()
128
+ return scene.getAnimRange().end.valueOf() / scene.getTimeStep().valueOf()
129
129
  }
130
130
 
131
131
  /**
@@ -133,7 +133,7 @@ export const getEndFrame = (): number => {
133
133
  * @returns the last frame number (0-based)
134
134
  */
135
135
  export const getLastFrame = (): number => {
136
- return scene.getAnimRange().end / scene.getTimeStep().valueOf()
136
+ return scene.getAnimRange().end.valueOf() / scene.getTimeStep().valueOf()
137
137
  }
138
138
 
139
139
  export const timeToFrame = (time: DzTime): number => {
@@ -142,4 +142,4 @@ export const timeToFrame = (time: DzTime): number => {
142
142
 
143
143
  export const frameToTime = (frame: number): DzTime => {
144
144
  return new DzTime(scene.getTimeStep().valueOf() * frame)
145
- }
145
+ }
@@ -2,7 +2,7 @@ import { mainWindow } from '@dsf/core/global'
2
2
 
3
3
  export const selectUniversalRotateTool = (coordinateSpace?: number): DzUniversalRotateTool => {
4
4
  const viewportMgr = mainWindow.getViewportMgr()
5
- const tool = viewportMgr.findTool('DzUniversalRotateTool') as DzUniversalRotateTool
5
+ const tool = viewportMgr.findTool('DzUniversalRotateTool') as unknown as DzUniversalRotateTool
6
6
  viewportMgr.setActiveTool(tool)
7
7
  if (coordinateSpace) tool.setCoordinateSpace(coordinateSpace)
8
8
  return tool
@@ -19,4 +19,4 @@ export const getAuxViewport = (): DzViewport | null => {
19
19
  if (viewport.name === 'AuxViewportView') return viewport
20
20
  }
21
21
  return null
22
- }
22
+ }
@@ -1,6 +1,6 @@
1
1
  import { BasicDialog } from '@dsf/dialog/basic-dialog';
2
2
  import { debug } from '@dsf/common/log';
3
- import { findAction, findActionsForShortcut, setActionShortcut } from '@dsf/helpers/action-helper';
3
+ import { findAction, findActionsForShortcut, getActionShortcut, normalizeShortcut, setActionShortcut } from '@dsf/helpers/action-helper';
4
4
  import { contains } from '@dsf/helpers/array-helper';
5
5
  import { confirm } from '@dsf/helpers/message-box-helper';
6
6
  import { Observable } from '@dsf/lib/observable';
@@ -69,6 +69,8 @@ class KeyboardShortcutDialog extends BasicDialog {
69
69
  this.dialog.setAcceptButtonEnabled(Boolean(text))
70
70
  })
71
71
 
72
+ this.loadInitialShortcut(model.shortcut.value)
73
+
72
74
  add.group('Assign Keyboard Shortcut').build((layout) => {
73
75
  layout.spacing = 5
74
76
  add.edit().value(model.actionLabel).readOnly()
@@ -117,17 +119,32 @@ class KeyboardShortcutDialog extends BasicDialog {
117
119
 
118
120
  return `Already assigned to: ${conflicts.map(action => action.text || action.name).join(', ')}`
119
121
  }
122
+
123
+ private loadInitialShortcut(shortcut: string): void {
124
+ const normalized = normalizeShortcut(shortcut)
125
+ if (!normalized) return
126
+
127
+ const parts = normalized.split('+').map(part => part.trim()).filter(Boolean)
128
+ const key = parts.pop() ?? ''
129
+
130
+ this.model.control.value = parts.indexOf('Ctrl') >= 0
131
+ this.model.alt.value = parts.indexOf('Alt') >= 0
132
+ this.model.shift.value = parts.indexOf('Shift') >= 0
133
+ this.model.windows.value = parts.indexOf('Win') >= 0
134
+ this.key.value = key
135
+ }
120
136
  }
121
137
 
122
- export const setKeyboardShortcut = (actionLabel: string, actionName: string) => {
138
+ export const promptKeyboardShortcut = (actionLabel: string, actionName: string, initialShortcut?: string): string | null => {
123
139
  let model = new KeyboardShortcutModel()
124
140
  model.actionLabel = actionLabel
125
141
  model.actionName = actionName
142
+ model.shortcut.value = normalizeShortcut(initialShortcut ?? getActionShortcut(actionName) ?? '')
126
143
 
127
144
  let dialog = new KeyboardShortcutDialog(model)
128
145
  let result = dialog.run()
129
146
 
130
- if (!result || !model.shortcut) return
147
+ if (!result || !model.shortcut) return null
131
148
 
132
149
  const conflicts = findActionsForShortcut(model.shortcut.value)
133
150
  .filter(action => action && action.name !== actionName)
@@ -139,8 +156,15 @@ export const setKeyboardShortcut = (actionLabel: string, actionName: string) =>
139
156
  if (conflicts.length > 0) {
140
157
  const conflictText = conflicts.map(action => action.text || action.name).join(', ')
141
158
  const response = confirm(`"${model.shortcut.value}" is already assigned to: ${conflictText}\n\nReplace it?`)
142
- if (!response.ok) return
159
+ if (!response.ok) return null
143
160
  }
144
161
 
145
- setActionShortcut(actionName, model.shortcut.value)
162
+ return model.shortcut.value
163
+ }
164
+
165
+ export const setKeyboardShortcut = (actionLabel: string, actionName: string, initialShortcut?: string) => {
166
+ const shortcut = promptKeyboardShortcut(actionLabel, actionName, initialShortcut)
167
+ if (!shortcut) return
168
+
169
+ setActionShortcut(actionName, shortcut)
146
170
  }
package/tsconfig.json CHANGED
@@ -32,7 +32,11 @@
32
32
  "baseUrl": "./" /* Specify the base directory to resolve non-relative module names. */,
33
33
  "paths": {
34
34
  "shared/*": ["src/shared/*"],
35
- "@dst/*": ["node_modules/dazscript-types/src/types/*"],
35
+ "@dst/*": [
36
+ "node_modules/dazscript-types/src/types/*",
37
+ "../node_modules/dazscript-types/src/types/*",
38
+ "../script-types/src/types/*"
39
+ ],
36
40
  "@dsf/*": ["src/*"]
37
41
  },
38
42
  // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
@@ -112,5 +116,10 @@
112
116
  // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
113
117
  "skipLibCheck": true /* Skip type checking all .d.ts files. */
114
118
  },
115
- "include": ["node_modules/dazscript-types/src/types/**/*", "src/**/*"]
119
+ "include": [
120
+ "node_modules/dazscript-types/src/types/**/*",
121
+ "../node_modules/dazscript-types/src/types/**/*",
122
+ "../script-types/src/types/**/*",
123
+ "src/**/*"
124
+ ]
116
125
  }
package/webpack.config.js CHANGED
@@ -1,7 +1,8 @@
1
1
  const path = require('path');
2
2
  const glob = require('glob');
3
3
  const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
4
- const { createActionLaunchers, validateAppDataPath } = require('./dist/scripts/launchers');
4
+ const { createActionLaunchers } = require('./dist/scripts/launchers');
5
+ const { validateAppDataPath } = require('./dist/scripts/app-data-path');
5
6
  const { loadConfig } = require('./dist/scripts/config-loader');
6
7
 
7
8
  class ActionLauncherPlugin {