dazscript-framework 1.0.23 → 1.0.25
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/package.json +1 -1
- package/src/dialog/basic-dialog.ts +1 -4
- package/src/dialog/builders/dialog-builder.ts +0 -64
- package/src/helpers/custom-action-installer-helper.ts +16 -1
- package/src/helpers/custom-action-installer-shortcuts.test.ts +24 -0
- package/src/helpers/custom-action-installer-shortcuts.ts +4 -0
- package/src/dialog/dialog-geometry-trace.test.ts +0 -207
package/package.json
CHANGED
|
@@ -24,10 +24,7 @@ export abstract class BasicDialog {
|
|
|
24
24
|
run(): boolean {
|
|
25
25
|
this.init()
|
|
26
26
|
this.builder.restoreObjectName()
|
|
27
|
-
this.
|
|
28
|
-
let result = this.dialog.exec()
|
|
29
|
-
this.builder.traceGeometry('afterExec', { result })
|
|
30
|
-
return result
|
|
27
|
+
return this.dialog.exec()
|
|
31
28
|
}
|
|
32
29
|
|
|
33
30
|
ok(): boolean {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { DialogProperties } from '../shared';
|
|
2
2
|
import { WidgetBuilderContext, WidgetsBuilder } from './widgets-builder';
|
|
3
|
-
import { debug } from '@dsf/common/log';
|
|
4
3
|
|
|
5
4
|
export class DialogBuilder extends WidgetsBuilder {
|
|
6
5
|
public properties: DialogProperties
|
|
@@ -17,13 +16,10 @@ export class DialogBuilder extends WidgetsBuilder {
|
|
|
17
16
|
|
|
18
17
|
build(setup: () => void): DzBasicDialog {
|
|
19
18
|
this.init()
|
|
20
|
-
this.traceGeometry('init')
|
|
21
19
|
setup()
|
|
22
20
|
this.restoreObjectName()
|
|
23
|
-
this.traceGeometry('afterSetup')
|
|
24
21
|
this.resize()
|
|
25
22
|
this.restoreObjectName()
|
|
26
|
-
this.traceGeometry('afterResize')
|
|
27
23
|
return this.context.dialog
|
|
28
24
|
}
|
|
29
25
|
|
|
@@ -69,64 +65,4 @@ export class DialogBuilder extends WidgetsBuilder {
|
|
|
69
65
|
this.context.dialog.setFixedHeight(height);
|
|
70
66
|
}
|
|
71
67
|
}
|
|
72
|
-
|
|
73
|
-
traceGeometry(phase: string, extra: { [key: string]: string | number | boolean | undefined } = {}) {
|
|
74
|
-
let dialog = this.context.dialog
|
|
75
|
-
let dialogWidget = dialog.getWidget()
|
|
76
|
-
let objectName = this.read(dialogWidget, 'objectName')
|
|
77
|
-
let caption = this.read(dialog, 'caption')
|
|
78
|
-
let sizeHint = this.read(dialogWidget, 'minimumSizeHint')
|
|
79
|
-
let key = objectName ?? ''
|
|
80
|
-
let fields: { [key: string]: string | number | boolean | undefined } = {
|
|
81
|
-
event: 'dialog.geometry',
|
|
82
|
-
phase,
|
|
83
|
-
title: this.title,
|
|
84
|
-
id: this.id ?? '',
|
|
85
|
-
caption,
|
|
86
|
-
objectName,
|
|
87
|
-
windowGeometryKey: key,
|
|
88
|
-
dialogX: this.read(dialog, 'x'),
|
|
89
|
-
dialogY: this.read(dialog, 'y'),
|
|
90
|
-
dialogWidth: this.read(dialog, 'width'),
|
|
91
|
-
dialogHeight: this.read(dialog, 'height'),
|
|
92
|
-
widgetX: this.read(dialogWidget, 'x'),
|
|
93
|
-
widgetY: this.read(dialogWidget, 'y'),
|
|
94
|
-
widgetWidth: this.read(dialogWidget, 'width'),
|
|
95
|
-
widgetHeight: this.read(dialogWidget, 'height'),
|
|
96
|
-
sizeHintWidth: this.read(sizeHint, 'width'),
|
|
97
|
-
sizeHintHeight: this.read(sizeHint, 'height'),
|
|
98
|
-
propertyWidth: this.properties.width,
|
|
99
|
-
propertyHeight: this.properties.height,
|
|
100
|
-
propertyMaxWidth: this.properties.maxWidth,
|
|
101
|
-
propertyMaxHeight: this.properties.maxHeight,
|
|
102
|
-
resizable: this.properties.resizable === true,
|
|
103
|
-
registrySubKey: key ? `WindowGeometries\\${key}` : ''
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
Object.keys(extra).forEach((name) => fields[name] = extra[name])
|
|
107
|
-
debug(`[DialogGeometry] ${this.formatGeometryTrace(fields)}`)
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
private read(source: any, field: string): any {
|
|
111
|
-
if (!source) return undefined
|
|
112
|
-
try {
|
|
113
|
-
return source[field]
|
|
114
|
-
}
|
|
115
|
-
catch (err) {
|
|
116
|
-
return undefined
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
private formatGeometryTrace(fields: { [key: string]: string | number | boolean | undefined }): string {
|
|
121
|
-
return Object.keys(fields)
|
|
122
|
-
.filter((name) => fields[name] !== undefined)
|
|
123
|
-
.map((name) => `${name}=${this.formatGeometryValue(name, fields[name])}`)
|
|
124
|
-
.join(' ')
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
private formatGeometryValue(name: string, value: string | number | boolean | undefined): string {
|
|
128
|
-
if (name === 'event' || name === 'phase') return String(value)
|
|
129
|
-
if (typeof value === 'string') return JSON.stringify(value)
|
|
130
|
-
return String(value)
|
|
131
|
-
}
|
|
132
68
|
}
|
|
@@ -12,7 +12,7 @@ import { TreeNode } from '@dsf/lib/tree-node'
|
|
|
12
12
|
import { promptKeyboardShortcut } from '@dsf/shared/set-keyboard-shortcut'
|
|
13
13
|
import { readFromFile, saveToFile } from './file-helper'
|
|
14
14
|
import { getCanonicalInstallerEntry, toActionKey } from './custom-action-installer-entries'
|
|
15
|
-
import { canResetSetupShortcut, getDisplayedSetupShortcut, resetSetupShortcut, setSetupShortcut, updateShortcutOverrideState } from './custom-action-installer-shortcuts'
|
|
15
|
+
import { canResetSetupShortcut, getDisplayedSetupShortcut, resetSetupShortcut, resetSetupShortcuts, setSetupShortcut, updateShortcutOverrideState } from './custom-action-installer-shortcuts'
|
|
16
16
|
import { getScriptPath } from './script-helper'
|
|
17
17
|
|
|
18
18
|
type InstallerEntry = {
|
|
@@ -397,6 +397,7 @@ class InstallerSelectionDialog extends BasicDialog {
|
|
|
397
397
|
debug(`[Setup] context menu row resolved item="${this.safeListItemText(contextItem, 1)}" entry="${entry?.action?.text ?? ''}"`)
|
|
398
398
|
const canSetShortcut = Boolean(entry)
|
|
399
399
|
const canResetShortcut = Boolean(entry && canResetSetupShortcut(entry))
|
|
400
|
+
const canResetAllShortcuts = this.entries.some(canResetSetupShortcut)
|
|
400
401
|
|
|
401
402
|
const items = [
|
|
402
403
|
{
|
|
@@ -413,6 +414,10 @@ class InstallerSelectionDialog extends BasicDialog {
|
|
|
413
414
|
this.resetDefaultShortcut(entry)
|
|
414
415
|
}
|
|
415
416
|
},
|
|
417
|
+
{
|
|
418
|
+
text: 'Reset All Shortcuts to Defaults',
|
|
419
|
+
activated: () => this.resetAllDefaultShortcuts()
|
|
420
|
+
},
|
|
416
421
|
{},
|
|
417
422
|
{ text: 'Check All', activated: () => this.checkVisible(listView, true) },
|
|
418
423
|
{ text: 'Uncheck All', activated: () => this.checkVisible(listView, false) }
|
|
@@ -421,6 +426,7 @@ class InstallerSelectionDialog extends BasicDialog {
|
|
|
421
426
|
return new PopupMenuBuilder(this.builder.context).items(...items).build((menu) => {
|
|
422
427
|
menu.setItemEnabled(0, canSetShortcut)
|
|
423
428
|
menu.setItemEnabled(1, canResetShortcut)
|
|
429
|
+
menu.setItemEnabled(2, canResetAllShortcuts)
|
|
424
430
|
})
|
|
425
431
|
}
|
|
426
432
|
|
|
@@ -498,6 +504,15 @@ class InstallerSelectionDialog extends BasicDialog {
|
|
|
498
504
|
this.refreshListEvent$.trigger()
|
|
499
505
|
}
|
|
500
506
|
|
|
507
|
+
private resetAllDefaultShortcuts() {
|
|
508
|
+
const resettableEntries = this.entries.filter(canResetSetupShortcut)
|
|
509
|
+
if (resettableEntries.length === 0) return
|
|
510
|
+
|
|
511
|
+
resetSetupShortcuts(resettableEntries)
|
|
512
|
+
debug(`[Setup] reset ${resettableEntries.length} shortcuts to defaults`)
|
|
513
|
+
this.refreshListEvent$.trigger()
|
|
514
|
+
}
|
|
515
|
+
|
|
501
516
|
private checkVisible(listView: DzListView, onOff: boolean) {
|
|
502
517
|
if (!this.keywords$.value?.trim()) {
|
|
503
518
|
checkAll(listView, onOff)
|
|
@@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest'
|
|
|
2
2
|
import {
|
|
3
3
|
canResetSetupShortcut,
|
|
4
4
|
getDisplayedSetupShortcut,
|
|
5
|
+
resetSetupShortcuts,
|
|
5
6
|
resetSetupShortcut,
|
|
6
7
|
setSetupShortcut,
|
|
7
8
|
updateShortcutOverrideState
|
|
@@ -80,6 +81,29 @@ describe('setup shortcut state', () => {
|
|
|
80
81
|
expect(canResetSetupShortcut(entry)).toBe(false)
|
|
81
82
|
})
|
|
82
83
|
|
|
84
|
+
it('resets all shortcut entries to their defaults', () => {
|
|
85
|
+
const installed = shortcutState({
|
|
86
|
+
installedActionName: 'installed-guid',
|
|
87
|
+
defaultShortcut: 'Ctrl+P',
|
|
88
|
+
installedShortcut: 'F12',
|
|
89
|
+
effectiveShortcut: 'F12',
|
|
90
|
+
isShortcutOverridden: true
|
|
91
|
+
})
|
|
92
|
+
const pending = shortcutState({
|
|
93
|
+
defaultShortcut: '',
|
|
94
|
+
effectiveShortcut: 'Ctrl+Alt+K'
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
resetSetupShortcuts([installed, pending])
|
|
98
|
+
|
|
99
|
+
expect(installed.installedShortcut).toBe('Ctrl+P')
|
|
100
|
+
expect(installed.effectiveShortcut).toBe('Ctrl+P')
|
|
101
|
+
expect(installed.isShortcutOverridden).toBe(false)
|
|
102
|
+
expect(pending.installedShortcut).toBe('')
|
|
103
|
+
expect(pending.effectiveShortcut).toBe('')
|
|
104
|
+
expect(pending.isShortcutOverridden).toBe(false)
|
|
105
|
+
})
|
|
106
|
+
|
|
83
107
|
it('displays custom marker when installed shortcut was changed outside setup', () => {
|
|
84
108
|
const entry = shortcutState({
|
|
85
109
|
installedActionName: 'installed-guid',
|
|
@@ -47,3 +47,7 @@ export const resetSetupShortcut = (entry: InstallerShortcutState): void => {
|
|
|
47
47
|
entry.effectiveShortcut = defaultShortcut
|
|
48
48
|
updateShortcutOverrideState(entry)
|
|
49
49
|
}
|
|
50
|
+
|
|
51
|
+
export const resetSetupShortcuts = (entries: InstallerShortcutState[]): void => {
|
|
52
|
+
entries.forEach(resetSetupShortcut)
|
|
53
|
+
}
|
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
-
import { BasicDialog } from './basic-dialog'
|
|
3
|
-
import { setLogLevel } from '../common/log'
|
|
4
|
-
|
|
5
|
-
class TestDialog extends BasicDialog {
|
|
6
|
-
protected build(): void {
|
|
7
|
-
this.builder.options({ resizable: true })
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
class HeaderDialog extends BasicDialog {
|
|
12
|
-
protected build(): void {
|
|
13
|
-
this.builder.header({ text: 'Header' }).build()
|
|
14
|
-
this.builder.options({ resizable: true })
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
class NonResizableDefaultSizeDialog extends BasicDialog {
|
|
19
|
-
protected build(): void { }
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
class ExplicitFixedHeightDialog extends BasicDialog {
|
|
23
|
-
protected build(): void {
|
|
24
|
-
this.builder.options({ height: 35 })
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
describe('dialog geometry trace', () => {
|
|
29
|
-
beforeEach(() => {
|
|
30
|
-
; (globalThis as any).App = {
|
|
31
|
-
debug: vi.fn(),
|
|
32
|
-
log: vi.fn(),
|
|
33
|
-
warning: vi.fn(),
|
|
34
|
-
flushLogBuffer: vi.fn(),
|
|
35
|
-
statusLine: vi.fn()
|
|
36
|
-
}
|
|
37
|
-
; (globalThis as any).DzBasicDialog = class {
|
|
38
|
-
caption = ''
|
|
39
|
-
width = 300
|
|
40
|
-
height = 200
|
|
41
|
-
minHeight = 0
|
|
42
|
-
maxHeight = 0
|
|
43
|
-
maxWidth = 0
|
|
44
|
-
sizeGripEnabled = false
|
|
45
|
-
fixedHeight: number | undefined
|
|
46
|
-
fixedWidth: number | undefined
|
|
47
|
-
private widget = {
|
|
48
|
-
objectName: '',
|
|
49
|
-
x: 10,
|
|
50
|
-
y: 20,
|
|
51
|
-
width: 300,
|
|
52
|
-
height: 200,
|
|
53
|
-
minimumSizeHint: { width: 300, height: 200 }
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
getWidget() {
|
|
57
|
-
return this.widget
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
inherits(typeName: string) {
|
|
61
|
-
return typeName === 'DzWidget'
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
addLayout() { }
|
|
65
|
-
|
|
66
|
-
exec() {
|
|
67
|
-
if (this.sizeGripEnabled) {
|
|
68
|
-
this.width = 640
|
|
69
|
-
this.height = 480
|
|
70
|
-
this.widget.width = 640
|
|
71
|
-
this.widget.height = 480
|
|
72
|
-
return true
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
this.widget.minimumSizeHint = { width: 196, height: 76 }
|
|
76
|
-
this.width = this.fixedWidth ?? this.widget.minimumSizeHint.width
|
|
77
|
-
this.height = this.fixedHeight ?? this.widget.minimumSizeHint.height
|
|
78
|
-
this.widget.width = this.width
|
|
79
|
-
this.widget.height = this.height
|
|
80
|
-
return true
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
setFixedHeight(value: number) {
|
|
84
|
-
this.fixedHeight = value
|
|
85
|
-
this.height = value
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
setFixedWidth(value: number) {
|
|
89
|
-
this.fixedWidth = value
|
|
90
|
-
this.width = value
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
const Layout = class {
|
|
94
|
-
direction = 0
|
|
95
|
-
constructor(public parent: unknown) { }
|
|
96
|
-
inherits(typeName: string) {
|
|
97
|
-
return typeName === 'DzLayout'
|
|
98
|
-
}
|
|
99
|
-
addWidget() { }
|
|
100
|
-
}
|
|
101
|
-
class Widget {
|
|
102
|
-
private _name = ''
|
|
103
|
-
height = 30
|
|
104
|
-
width = 100
|
|
105
|
-
minHeight = 0
|
|
106
|
-
maxHeight = 0
|
|
107
|
-
toolTip = ''
|
|
108
|
-
whatsThis = ''
|
|
109
|
-
constructor(private parent: any) { }
|
|
110
|
-
set name(value: string) {
|
|
111
|
-
this._name = value
|
|
112
|
-
if (this.parent?.getWidget) this.parent.getWidget().objectName = value
|
|
113
|
-
}
|
|
114
|
-
get name() {
|
|
115
|
-
return this._name
|
|
116
|
-
}
|
|
117
|
-
getWidget() {
|
|
118
|
-
return this
|
|
119
|
-
}
|
|
120
|
-
inherits(typeName: string) {
|
|
121
|
-
return typeName === 'DzWidget'
|
|
122
|
-
}
|
|
123
|
-
setFixedHeight(value: number) {
|
|
124
|
-
this.height = value
|
|
125
|
-
}
|
|
126
|
-
setFixedWidth(value: number) {
|
|
127
|
-
this.width = value
|
|
128
|
-
}
|
|
129
|
-
show() { }
|
|
130
|
-
hide() { }
|
|
131
|
-
}
|
|
132
|
-
; (globalThis as any).DzVBoxLayout = Layout
|
|
133
|
-
; (globalThis as any).DzHBoxLayout = Layout
|
|
134
|
-
; (globalThis as any).DzWidget = Widget
|
|
135
|
-
; (globalThis as any).DzLabel = Widget
|
|
136
|
-
; (globalThis as any).DzTextBrowser = class extends Widget {
|
|
137
|
-
html = ''
|
|
138
|
-
readOnly = false
|
|
139
|
-
lineWrapMode = 0
|
|
140
|
-
wordWrapMode = 0
|
|
141
|
-
}
|
|
142
|
-
; (globalThis as any).DzTextEdit = { WidgetWidth: 1, WordWrap: 2 }
|
|
143
|
-
; (globalThis as any).DzSplitter = { Vertical: 1, Horizontal: 2 }
|
|
144
|
-
setLogLevel('trace')
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
it('logs dialog objectName and geometry before and after exec', () => {
|
|
148
|
-
new TestDialog('Power Loader', 'Vholf3D Power Loader').run()
|
|
149
|
-
|
|
150
|
-
const messages = (globalThis as any).App.debug.mock.calls.map((call: unknown[]) => String(call[0]))
|
|
151
|
-
|
|
152
|
-
expect(messages.some((message: string) =>
|
|
153
|
-
message.includes('event=dialog.geometry phase=init') &&
|
|
154
|
-
message.includes('objectName="Vholf3DPowerLoaderDlg"')
|
|
155
|
-
)).toBe(true)
|
|
156
|
-
expect(messages.some((message: string) =>
|
|
157
|
-
message.includes('event=dialog.geometry phase=beforeExec') &&
|
|
158
|
-
message.includes('dialogWidth=300')
|
|
159
|
-
)).toBe(true)
|
|
160
|
-
expect(messages.some((message: string) =>
|
|
161
|
-
message.includes('event=dialog.geometry phase=afterExec') &&
|
|
162
|
-
message.includes('dialogWidth=640') &&
|
|
163
|
-
message.includes('result=true')
|
|
164
|
-
)).toBe(true)
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
it('keeps the DAZ geometry objectName stable after building a header widget', () => {
|
|
168
|
-
new HeaderDialog('Power Menu - Active Tool', 'Vholf3D Power Menu - Active Tool').run()
|
|
169
|
-
|
|
170
|
-
const messages = (globalThis as any).App.debug.mock.calls.map((call: unknown[]) => String(call[0]))
|
|
171
|
-
|
|
172
|
-
expect(messages.some((message: string) =>
|
|
173
|
-
message.includes('event=dialog.geometry phase=afterSetup') &&
|
|
174
|
-
message.includes('objectName="Vholf3DPowerMenu-ActiveToolDlg"')
|
|
175
|
-
)).toBe(true)
|
|
176
|
-
expect(messages.some((message: string) =>
|
|
177
|
-
message.includes('event=dialog.geometry phase=beforeExec') &&
|
|
178
|
-
message.includes('objectName="Vholf3DPowerMenu-ActiveToolDlg"')
|
|
179
|
-
)).toBe(true)
|
|
180
|
-
})
|
|
181
|
-
|
|
182
|
-
it('does not freeze a non-resizable dialog to an early implicit size hint', () => {
|
|
183
|
-
new NonResizableDefaultSizeDialog('Store Active Camera').run()
|
|
184
|
-
|
|
185
|
-
const messages = (globalThis as any).App.debug.mock.calls.map((call: unknown[]) => String(call[0]))
|
|
186
|
-
|
|
187
|
-
expect(messages.some((message: string) =>
|
|
188
|
-
message.includes('event=dialog.geometry phase=afterExec') &&
|
|
189
|
-
message.includes('objectName="StoreActiveCameraDlg"') &&
|
|
190
|
-
message.includes('dialogHeight=76') &&
|
|
191
|
-
message.includes('sizeHintHeight=76')
|
|
192
|
-
)).toBe(true)
|
|
193
|
-
})
|
|
194
|
-
|
|
195
|
-
it('keeps explicit non-resizable dialog height fixed', () => {
|
|
196
|
-
new ExplicitFixedHeightDialog('Explicit Height').run()
|
|
197
|
-
|
|
198
|
-
const messages = (globalThis as any).App.debug.mock.calls.map((call: unknown[]) => String(call[0]))
|
|
199
|
-
|
|
200
|
-
expect(messages.some((message: string) =>
|
|
201
|
-
message.includes('event=dialog.geometry phase=afterExec') &&
|
|
202
|
-
message.includes('objectName="ExplicitHeightDlg"') &&
|
|
203
|
-
message.includes('dialogHeight=35') &&
|
|
204
|
-
message.includes('sizeHintHeight=76')
|
|
205
|
-
)).toBe(true)
|
|
206
|
-
})
|
|
207
|
-
})
|