dazscript-framework 0.3.2 → 1.0.1
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 +327 -267
- package/package.json +7 -4
- package/src/Setup.dsa.ts +37 -9
- package/src/dialog/builders/list-view-builder.ts +5 -0
- package/src/examples/01-hello-world.dsa.ts +8 -0
- package/src/examples/02-persistence-dialog.dsa.ts +21 -0
- package/src/examples/02-persistence-dialog.ts +68 -0
- package/src/examples/03-simple-dialog.dsa.ts +23 -0
- package/src/examples/03-simple-dialog.ts +47 -0
- package/src/examples/04-settings-dialog.dsa.ts +29 -0
- package/src/examples/04-settings-dialog.ts +83 -0
- package/src/examples/05-list-dialog.dsa.ts +53 -0
- package/src/examples/05-list-dialog.ts +88 -0
- package/src/examples/06-showcase-dialog.dsa.ts +87 -0
- package/src/examples/06-showcase-dialog.ts +518 -0
- package/src/helpers/custom-action-helper.ts +2 -1
- package/src/helpers/custom-action-installer-helper.ts +39 -10
- package/src/lib/observable.test.ts +416 -0
- package/src/lib/observable.ts +24 -18
- package/src/lib/tree-node.test.ts +21 -0
- package/tsconfig.json +28 -112
- package/webpack.config.js +1 -0
- package/src/samples/hello-world.dsa.ts +0 -8
- package/src/samples/sample-dialog.dsa.ts +0 -47
- package/src/samples/sample-dialog.ts +0 -49
- /package/src/{samples → examples}/config.ts +0 -0
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
import { BasicDialog } from '@dsf/dialog/basic-dialog'
|
|
2
|
+
import { PopupMenuItem } from '@dsf/dialog/builders/popup-menu-builder'
|
|
3
|
+
import { getDataItem } from '@dsf/helpers/list-view-helper'
|
|
4
|
+
import { AppSettings } from '@dsf/lib/settings'
|
|
5
|
+
import { Observable } from '@dsf/lib/observable'
|
|
6
|
+
import { TreeNode } from '@dsf/lib/tree-node'
|
|
7
|
+
import { config } from './config'
|
|
8
|
+
|
|
9
|
+
// Types
|
|
10
|
+
|
|
11
|
+
export interface SceneObject {
|
|
12
|
+
id: number
|
|
13
|
+
name: string
|
|
14
|
+
type: 'Figure' | 'Prop' | 'Light' | 'Camera' | 'Group'
|
|
15
|
+
visible: boolean
|
|
16
|
+
locked: boolean
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Settings (all persisted via DzAppSettings)
|
|
20
|
+
|
|
21
|
+
export class ShowcaseDialogSettings extends AppSettings {
|
|
22
|
+
constructor() {
|
|
23
|
+
super(`${config.author}/06-ShowcaseDialog`)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Objects tab
|
|
27
|
+
activeTab$ = this.bindInt('activeTab$', 0)
|
|
28
|
+
flatList$ = this.bindBoolean('flatList$', false)
|
|
29
|
+
showIcons$ = this.bindBoolean('showIcons$', true)
|
|
30
|
+
showVisibleOnly$ = this.bindBoolean('showVisibleOnly$', false)
|
|
31
|
+
splitterState$ = this.bindString('splitterState$', '')
|
|
32
|
+
filterType$ = this.bindString('filterType$', 'All')
|
|
33
|
+
|
|
34
|
+
// Settings tab - Render
|
|
35
|
+
quality$ = this.bindString('quality$', 'Standard')
|
|
36
|
+
samples$ = this.bindInt('samples$', 64)
|
|
37
|
+
scale$ = this.bindFloat('scale$', 1.0)
|
|
38
|
+
|
|
39
|
+
// Settings tab - Export
|
|
40
|
+
exportFormat$ = this.bindString('exportFormat$', 'JSON')
|
|
41
|
+
outputPath$ = this.bindString('outputPath$', '')
|
|
42
|
+
autoSave$ = this.bindBoolean('autoSave$', false)
|
|
43
|
+
|
|
44
|
+
// Settings tab - Behaviour
|
|
45
|
+
notifications$ = this.bindBoolean('notifications$', true)
|
|
46
|
+
logLevel$ = this.bindString('logLevel$', 'Info')
|
|
47
|
+
|
|
48
|
+
// About tab
|
|
49
|
+
showFilters$ = this.bindBoolean('showFilters$', false)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Model
|
|
53
|
+
|
|
54
|
+
export class ShowcaseDialogModel {
|
|
55
|
+
readonly settings = new ShowcaseDialogSettings()
|
|
56
|
+
|
|
57
|
+
objects$ = new Observable<TreeNode<SceneObject>[]>([])
|
|
58
|
+
selectedObject$ = new Observable<SceneObject>()
|
|
59
|
+
refreshObjects$ = new Observable<void>()
|
|
60
|
+
keywords$ = new Observable('')
|
|
61
|
+
logEntries$ = new Observable<string[]>([])
|
|
62
|
+
sceneStats$ = new Observable('')
|
|
63
|
+
selectedSummary$ = new Observable('No object selected')
|
|
64
|
+
|
|
65
|
+
// Detail panel fields - updated externally when selectedObject$ changes
|
|
66
|
+
detailName$ = new Observable('')
|
|
67
|
+
detailType$ = new Observable('')
|
|
68
|
+
detailVisible$ = new Observable(false)
|
|
69
|
+
detailLocked$ = new Observable(false)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Dialog
|
|
73
|
+
|
|
74
|
+
export class ShowcaseDialog extends BasicDialog {
|
|
75
|
+
private sceneListView: DzListView | null = null
|
|
76
|
+
|
|
77
|
+
constructor(private readonly model: ShowcaseDialogModel) {
|
|
78
|
+
super('06 Showcase Dialog', `${config.author}/06-ShowcaseDialog`)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
protected build(): void {
|
|
82
|
+
let add = this.add
|
|
83
|
+
let settings = this.model.settings
|
|
84
|
+
|
|
85
|
+
this.builder.options({ resizable: true, width: 780, height: 640 })
|
|
86
|
+
this.dialog.setAcceptButtonText('Apply')
|
|
87
|
+
this.dialog.setCancelButtonText('Close')
|
|
88
|
+
|
|
89
|
+
this.connectInteractions()
|
|
90
|
+
|
|
91
|
+
// Active tab index persists across runs via bindInt.
|
|
92
|
+
add.tab('Objects').bind(settings.activeTab$).build(() => {
|
|
93
|
+
this.buildObjectsTab()
|
|
94
|
+
})
|
|
95
|
+
add.tab('Settings').build(() => {
|
|
96
|
+
this.buildSettingsTab()
|
|
97
|
+
})
|
|
98
|
+
add.tab('About').build(() => {
|
|
99
|
+
this.buildAboutTab()
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
this.updateSceneStats()
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Tab 1: Objects
|
|
106
|
+
|
|
107
|
+
private buildObjectsTab(): void {
|
|
108
|
+
let add = this.add
|
|
109
|
+
let model = this.model
|
|
110
|
+
let settings = model.settings
|
|
111
|
+
|
|
112
|
+
// Toolbar row
|
|
113
|
+
add.group('Search').horizontal().build((layout) => {
|
|
114
|
+
layout.spacing = 4
|
|
115
|
+
add.edit()
|
|
116
|
+
.value(model.keywords$)
|
|
117
|
+
.placeholder('Filter objects...')
|
|
118
|
+
.focus()
|
|
119
|
+
add.label('Type:')
|
|
120
|
+
add.combo()
|
|
121
|
+
.items(['All', 'Figure', 'Prop', 'Light', 'Camera', 'Group'])
|
|
122
|
+
.selected(settings.filterType$)
|
|
123
|
+
add.button('Refresh')
|
|
124
|
+
.clicked(() => model.refreshObjects$.trigger())
|
|
125
|
+
.toolTip('Reload the object list')
|
|
126
|
+
add.button('Apply Checked Rows')
|
|
127
|
+
.clicked(() => this.applyCheckedRows())
|
|
128
|
+
.toolTip('Copies the list checkbox states back to the scene objects.')
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
add.horizontal((layout) => {
|
|
132
|
+
layout.spacing = 6
|
|
133
|
+
add.checkbox('Flat List').value(settings.flatList$)
|
|
134
|
+
add.checkbox('Show Icons').value(settings.showIcons$)
|
|
135
|
+
add.checkbox('Visible Only').value(settings.showVisibleOnly$)
|
|
136
|
+
const statsLabel = add.label(model.sceneStats$.value).build()
|
|
137
|
+
model.sceneStats$.connect(text => { statsLabel.text = text })
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
// Splitter - position persisted via bindString
|
|
141
|
+
add.splitter()
|
|
142
|
+
.state(settings.splitterState$)
|
|
143
|
+
.strech(2, 1)
|
|
144
|
+
.items(
|
|
145
|
+
// Left pane: tree list with filter + context menu
|
|
146
|
+
add.group().style({ flat: true }).build(() => {
|
|
147
|
+
add.list.view<SceneObject, SceneObject>()
|
|
148
|
+
.row((item, parent, id) => {
|
|
149
|
+
let obj = item.value as SceneObject
|
|
150
|
+
let listItem = new DzCheckListItem(parent, item.isLeaf ? DzCheckListItem.CheckBox : DzCheckListItem.CheckBoxController, id)
|
|
151
|
+
listItem.selectable = true
|
|
152
|
+
listItem.on = obj.visible
|
|
153
|
+
return listItem
|
|
154
|
+
})
|
|
155
|
+
.expanded(true)
|
|
156
|
+
.flat(settings.flatList$)
|
|
157
|
+
.refresh(model.refreshObjects$)
|
|
158
|
+
.items(model.objects$)
|
|
159
|
+
.columns(['Visible', 'Name', 'Type', 'Locked'])
|
|
160
|
+
.text(item => [
|
|
161
|
+
'',
|
|
162
|
+
this.getDisplayName(item.value),
|
|
163
|
+
item.value?.type ?? '',
|
|
164
|
+
item.value?.locked ? 'Yes' : 'No',
|
|
165
|
+
])
|
|
166
|
+
.data(item => item.value)
|
|
167
|
+
.selected(model.selectedObject$)
|
|
168
|
+
.filter({
|
|
169
|
+
keywords: model.keywords$,
|
|
170
|
+
field: li => li.text(0),
|
|
171
|
+
selectOnFilter: true,
|
|
172
|
+
filters: li => {
|
|
173
|
+
let obj = getDataItem<SceneObject>(li)
|
|
174
|
+
if (!obj) return true
|
|
175
|
+
let type = settings.filterType$.value
|
|
176
|
+
let matchesType = type === 'All' || obj.type === type
|
|
177
|
+
let matchesVisibility = !settings.showVisibleOnly$.value || obj.visible
|
|
178
|
+
return matchesType && matchesVisibility
|
|
179
|
+
},
|
|
180
|
+
})
|
|
181
|
+
.contextMenu((_, li) => {
|
|
182
|
+
let obj = getDataItem<SceneObject>(li)
|
|
183
|
+
let label = obj?.name ?? 'item'
|
|
184
|
+
let items: PopupMenuItem[] = [
|
|
185
|
+
{
|
|
186
|
+
text: `Select "${label}"`,
|
|
187
|
+
activated: () => { /* select in viewport */ },
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
text: obj?.visible ? 'Hide' : 'Show',
|
|
191
|
+
activated: () => {
|
|
192
|
+
if (obj) obj.visible = !obj.visible
|
|
193
|
+
model.refreshObjects$.trigger()
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
{ text: '' }, // separator
|
|
197
|
+
{
|
|
198
|
+
text: 'Duplicate',
|
|
199
|
+
activated: () => { /* duplicate */ },
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
text: 'Delete',
|
|
203
|
+
activated: () => { /* delete */ },
|
|
204
|
+
},
|
|
205
|
+
]
|
|
206
|
+
return add.contextMenu().items(...items).build()
|
|
207
|
+
})
|
|
208
|
+
.build((listView) => {
|
|
209
|
+
this.sceneListView = listView
|
|
210
|
+
listView.allColumnsShowFocus = true
|
|
211
|
+
})
|
|
212
|
+
}),
|
|
213
|
+
|
|
214
|
+
// Right pane: detail panel for selected object
|
|
215
|
+
add.group('Details').build(() => {
|
|
216
|
+
add.horizontal((layout) => {
|
|
217
|
+
layout.spacing = 4
|
|
218
|
+
add.label('Name:').minWidth(45)
|
|
219
|
+
add.edit().readOnly().value(model.detailName$)
|
|
220
|
+
})
|
|
221
|
+
add.horizontal((layout) => {
|
|
222
|
+
layout.spacing = 4
|
|
223
|
+
add.label('Type:').minWidth(45)
|
|
224
|
+
add.edit().readOnly().value(model.detailType$)
|
|
225
|
+
})
|
|
226
|
+
add.group('Flags').horizontal().build(() => {
|
|
227
|
+
add.checkbox('Visible').value(model.detailVisible$)
|
|
228
|
+
add.checkbox('Locked').value(model.detailLocked$)
|
|
229
|
+
})
|
|
230
|
+
const summaryLabel = add.label(model.selectedSummary$.value).wordWrap().build()
|
|
231
|
+
model.selectedSummary$.connect(text => { summaryLabel.text = text })
|
|
232
|
+
add.group('Scene Node').build(() => {
|
|
233
|
+
// NodeSelection lets the user pick any node from the scene.
|
|
234
|
+
add.nodeSelection().build()
|
|
235
|
+
})
|
|
236
|
+
})
|
|
237
|
+
)
|
|
238
|
+
.build()
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Tab 2: Settings
|
|
242
|
+
|
|
243
|
+
private buildSettingsTab(): void {
|
|
244
|
+
let add = this.add
|
|
245
|
+
let settings = this.model.settings
|
|
246
|
+
|
|
247
|
+
add.horizontal((layout) => {
|
|
248
|
+
layout.spacing = 8
|
|
249
|
+
|
|
250
|
+
// Left column
|
|
251
|
+
add.vertical(() => {
|
|
252
|
+
add.group('Display').build(() => {
|
|
253
|
+
add.checkbox('Flat List').value(settings.flatList$)
|
|
254
|
+
add.checkbox('Show Icons').value(settings.showIcons$)
|
|
255
|
+
add.checkbox('Visible Only').value(settings.showVisibleOnly$)
|
|
256
|
+
add.horizontal((layout) => {
|
|
257
|
+
layout.spacing = 4
|
|
258
|
+
add.label('Object Type:').minWidth(80)
|
|
259
|
+
add.combo()
|
|
260
|
+
.items(['All', 'Figure', 'Prop', 'Light', 'Camera', 'Group'])
|
|
261
|
+
.selected(settings.filterType$)
|
|
262
|
+
})
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
// Radio buttons - initialize from persisted quality value;
|
|
266
|
+
// toggled() writes back when the user picks a different option.
|
|
267
|
+
add.group('Render Quality').build(() => {
|
|
268
|
+
add.radio('Draft')
|
|
269
|
+
.value(settings.quality$.value === 'Draft')
|
|
270
|
+
.toggled(v => {
|
|
271
|
+
if (!v) return
|
|
272
|
+
settings.quality$.value = 'Draft'
|
|
273
|
+
settings.samples$.value = 16
|
|
274
|
+
settings.scale$.value = 0.5
|
|
275
|
+
})
|
|
276
|
+
add.radio('Standard')
|
|
277
|
+
.value(settings.quality$.value === 'Standard')
|
|
278
|
+
.toggled(v => {
|
|
279
|
+
if (!v) return
|
|
280
|
+
settings.quality$.value = 'Standard'
|
|
281
|
+
settings.samples$.value = 64
|
|
282
|
+
settings.scale$.value = 1.0
|
|
283
|
+
})
|
|
284
|
+
add.radio('High')
|
|
285
|
+
.value(settings.quality$.value === 'High')
|
|
286
|
+
.toggled(v => {
|
|
287
|
+
if (!v) return
|
|
288
|
+
settings.quality$.value = 'High'
|
|
289
|
+
settings.samples$.value = 256
|
|
290
|
+
settings.scale$.value = 2.0
|
|
291
|
+
})
|
|
292
|
+
})
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
// Right column
|
|
296
|
+
add.vertical(() => {
|
|
297
|
+
add.group('Render').build(() => {
|
|
298
|
+
add.horizontal((layout) => {
|
|
299
|
+
layout.spacing = 4
|
|
300
|
+
add.label('Samples:').minWidth(55)
|
|
301
|
+
add.slider('integer')
|
|
302
|
+
.value(settings.samples$)
|
|
303
|
+
.min(1).max(512)
|
|
304
|
+
.build()
|
|
305
|
+
})
|
|
306
|
+
add.horizontal((layout) => {
|
|
307
|
+
layout.spacing = 4
|
|
308
|
+
add.label('Scale:').minWidth(55)
|
|
309
|
+
add.slider('float')
|
|
310
|
+
.value(settings.scale$)
|
|
311
|
+
.min(0.1).max(5.0)
|
|
312
|
+
.build()
|
|
313
|
+
})
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
add.group('Export').build(() => {
|
|
317
|
+
add.horizontal((layout) => {
|
|
318
|
+
layout.spacing = 4
|
|
319
|
+
add.label('Output:').minWidth(55)
|
|
320
|
+
add.edit()
|
|
321
|
+
.value(settings.outputPath$)
|
|
322
|
+
.placeholder('Default output path...')
|
|
323
|
+
})
|
|
324
|
+
add.horizontal((layout) => {
|
|
325
|
+
layout.spacing = 4
|
|
326
|
+
add.label('Format:').minWidth(55)
|
|
327
|
+
// ComboBox - two-way bind to persisted string
|
|
328
|
+
add.combo()
|
|
329
|
+
.items(['JSON', 'XML', 'CSV'])
|
|
330
|
+
.selected(settings.exportFormat$)
|
|
331
|
+
})
|
|
332
|
+
add.checkbox('Auto Save').value(settings.autoSave$)
|
|
333
|
+
add.label('Auto Save enables notifications and switches the log level to Debug.').wordWrap().build()
|
|
334
|
+
})
|
|
335
|
+
})
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
// Color pickers - read the final color from the widget on accept
|
|
339
|
+
add.group('Colors').horizontal().build(() => {
|
|
340
|
+
add.vertical(() => {
|
|
341
|
+
add.label('Background:').build()
|
|
342
|
+
add.color().build()
|
|
343
|
+
})
|
|
344
|
+
add.vertical(() => {
|
|
345
|
+
add.label('Foreground:').build()
|
|
346
|
+
add.color().build()
|
|
347
|
+
})
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
add.group('Behaviour').horizontal().build(() => {
|
|
351
|
+
add.checkbox('Notifications').value(settings.notifications$)
|
|
352
|
+
add.horizontal((layout) => {
|
|
353
|
+
layout.spacing = 4
|
|
354
|
+
add.label('Log Level:')
|
|
355
|
+
// ComboEdit: user can pick from list OR type a custom level
|
|
356
|
+
add.comboEdit()
|
|
357
|
+
.items(['Debug', 'Info', 'Warning', 'Error'])
|
|
358
|
+
.changed(settings.logLevel$)
|
|
359
|
+
})
|
|
360
|
+
})
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Tab 3: About
|
|
364
|
+
|
|
365
|
+
private buildAboutTab(): void {
|
|
366
|
+
let add = this.add
|
|
367
|
+
let model = this.model
|
|
368
|
+
let settings = model.settings
|
|
369
|
+
|
|
370
|
+
add.group('About This Example').build(() => {
|
|
371
|
+
add.label([
|
|
372
|
+
'DAZScript Framework - Showcase Dialog (Example 06)',
|
|
373
|
+
'',
|
|
374
|
+
'Widgets demonstrated in this dialog:',
|
|
375
|
+
' - Tabs with persisted active-tab index (bindInt)',
|
|
376
|
+
' - Splitter with persisted position (bindString)',
|
|
377
|
+
' - list.view - columns, keyword filter, custom filters,',
|
|
378
|
+
' checkbox rows, context menu, refresh trigger, flat/tree toggle,',
|
|
379
|
+
' expanded, data binding, selection binding',
|
|
380
|
+
' - list.box - scrollable list with multi-select',
|
|
381
|
+
' - GroupBox - vertical / horizontal / flat style / visible toggle',
|
|
382
|
+
' - Label - wordWrap, minWidth, alignment',
|
|
383
|
+
' - LineEdit - value binding, placeholder, readOnly',
|
|
384
|
+
' - CheckBox, RadioButton - Observable two-way binding',
|
|
385
|
+
' - Button - clicked, toggle, toolTip',
|
|
386
|
+
' - Slider - integer and float with min/max',
|
|
387
|
+
' - ColorPicker (DzColorWgt)',
|
|
388
|
+
' - ComboBox - items + selected binding',
|
|
389
|
+
' - ComboEdit - items + changed/edited binding',
|
|
390
|
+
' - NodeSelection combo (scene node picker)',
|
|
391
|
+
' - All settings persisted via AppSettings (DzAppSettings)',
|
|
392
|
+
].join('\n')).wordWrap(true).build()
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
// Log list - populated externally; demonstrates Observable<string[]> binding
|
|
396
|
+
add.group('Recent Log').build(() => {
|
|
397
|
+
add.list.box()
|
|
398
|
+
.items(model.logEntries$)
|
|
399
|
+
.mode('single')
|
|
400
|
+
.build()
|
|
401
|
+
})
|
|
402
|
+
|
|
403
|
+
// Conditional filter row - group visible when showFilters$ is true
|
|
404
|
+
add.group('Optional Filters').horizontal()
|
|
405
|
+
.visible(settings.showFilters$)
|
|
406
|
+
.build(() => {
|
|
407
|
+
add.label('Tag:').minWidth(30)
|
|
408
|
+
add.comboEdit()
|
|
409
|
+
.items(['render', 'test', 'draft', 'final'])
|
|
410
|
+
.build()
|
|
411
|
+
})
|
|
412
|
+
|
|
413
|
+
add.horizontal((layout) => {
|
|
414
|
+
layout.spacing = 4
|
|
415
|
+
add.checkbox('Show Filters').value(settings.showFilters$)
|
|
416
|
+
})
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
private connectInteractions(): void {
|
|
420
|
+
let model = this.model
|
|
421
|
+
let settings = model.settings
|
|
422
|
+
|
|
423
|
+
settings.filterType$.connect(() => model.refreshObjects$.trigger())
|
|
424
|
+
settings.showVisibleOnly$.connect(() => model.refreshObjects$.trigger())
|
|
425
|
+
settings.showIcons$.connect(() => model.refreshObjects$.trigger())
|
|
426
|
+
settings.flatList$.connect(() => model.refreshObjects$.trigger())
|
|
427
|
+
|
|
428
|
+
settings.autoSave$.connect((enabled) => {
|
|
429
|
+
if (!enabled) return
|
|
430
|
+
settings.notifications$.value = true
|
|
431
|
+
settings.logLevel$.value = 'Debug'
|
|
432
|
+
this.addLog('Auto Save enabled; notifications and Debug logging selected.')
|
|
433
|
+
})
|
|
434
|
+
|
|
435
|
+
model.objects$.connect(() => this.updateSceneStats())
|
|
436
|
+
model.refreshObjects$.connect(() => this.updateSceneStats())
|
|
437
|
+
|
|
438
|
+
model.selectedObject$.connect(obj => {
|
|
439
|
+
model.selectedSummary$.value = obj
|
|
440
|
+
? `${obj.name}: ${obj.type}, ${obj.visible ? 'visible' : 'hidden'}, ${obj.locked ? 'locked' : 'unlocked'}`
|
|
441
|
+
: 'No object selected'
|
|
442
|
+
})
|
|
443
|
+
|
|
444
|
+
model.detailVisible$.connect((visible) => {
|
|
445
|
+
let obj = model.selectedObject$.value
|
|
446
|
+
if (!obj || obj.visible === visible) return
|
|
447
|
+
obj.visible = visible
|
|
448
|
+
this.addLog(`${obj.name} visibility changed from the detail checkbox.`)
|
|
449
|
+
model.refreshObjects$.trigger()
|
|
450
|
+
})
|
|
451
|
+
|
|
452
|
+
model.detailLocked$.connect((locked) => {
|
|
453
|
+
let obj = model.selectedObject$.value
|
|
454
|
+
if (!obj || obj.locked === locked) return
|
|
455
|
+
obj.locked = locked
|
|
456
|
+
this.addLog(`${obj.name} lock state changed from the detail checkbox.`)
|
|
457
|
+
model.refreshObjects$.trigger()
|
|
458
|
+
})
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
private getDisplayName(obj: SceneObject | null): string {
|
|
462
|
+
if (!obj) return ''
|
|
463
|
+
if (!this.model.settings.showIcons$.value) return obj.name
|
|
464
|
+
|
|
465
|
+
const prefixByType: Record<SceneObject['type'], string> = {
|
|
466
|
+
Figure: '[F]',
|
|
467
|
+
Prop: '[P]',
|
|
468
|
+
Light: '[L]',
|
|
469
|
+
Camera: '[C]',
|
|
470
|
+
Group: '[G]',
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return `${prefixByType[obj.type]} ${obj.name}`
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
private addLog(message: string): void {
|
|
477
|
+
let entries = this.model.logEntries$.value.slice()
|
|
478
|
+
entries.push(`[Info] ${message}`)
|
|
479
|
+
if (entries.length > 8) entries.shift()
|
|
480
|
+
this.model.logEntries$.value = entries
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
private updateSceneStats(): void {
|
|
484
|
+
let objects = this.getAllObjects()
|
|
485
|
+
let visible = objects.filter(obj => obj.visible).length
|
|
486
|
+
let locked = objects.filter(obj => obj.locked).length
|
|
487
|
+
this.model.sceneStats$.value = `${visible}/${objects.length} visible, ${locked} locked`
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
private getAllObjects(): SceneObject[] {
|
|
491
|
+
let objects: SceneObject[] = []
|
|
492
|
+
this.model.objects$.value.forEach(root => {
|
|
493
|
+
root.forEach(node => {
|
|
494
|
+
if (node.value) objects.push(node.value)
|
|
495
|
+
})
|
|
496
|
+
})
|
|
497
|
+
return objects
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
private applyCheckedRows(): void {
|
|
501
|
+
if (!this.sceneListView) return
|
|
502
|
+
|
|
503
|
+
this.sceneListView.getItems(DzListView.All).forEach(item => {
|
|
504
|
+
if (!(item as any).inherits('DzCheckListItem')) return
|
|
505
|
+
|
|
506
|
+
let obj = getDataItem<SceneObject>(item)
|
|
507
|
+
if (!obj) return
|
|
508
|
+
|
|
509
|
+
let checkbox = item as DzCheckListItem
|
|
510
|
+
obj.visible = checkbox.state === 0 && !checkbox.on
|
|
511
|
+
? false
|
|
512
|
+
: checkbox.on
|
|
513
|
+
})
|
|
514
|
+
|
|
515
|
+
this.addLog('List checkbox states applied to scene objects.')
|
|
516
|
+
this.model.refreshObjects$.trigger()
|
|
517
|
+
}
|
|
518
|
+
}
|
|
@@ -4,6 +4,7 @@ import { mainWindow } from '@dsf/core/global'
|
|
|
4
4
|
import * as array from '@dsf/helpers/array-helper'
|
|
5
5
|
import { keys } from '@dsf/helpers/object-helper'
|
|
6
6
|
import { progress } from '@dsf/helpers/progress-helper'
|
|
7
|
+
import CustomSet from '@dsf/lib/set'
|
|
7
8
|
import { getMenu } from './menu-helper'
|
|
8
9
|
import { getScriptPath } from './script-helper'
|
|
9
10
|
|
|
@@ -445,7 +446,7 @@ export const installCustomActions = (actions: CustomAction[]) => {
|
|
|
445
446
|
|
|
446
447
|
export const uninstallCustomActions = (actions: CustomAction[]) => {
|
|
447
448
|
debug(`Uninstalling Actions`)
|
|
448
|
-
const toolbarNames = new
|
|
449
|
+
const toolbarNames = new CustomSet<string>()
|
|
449
450
|
|
|
450
451
|
actions.forEach(action => {
|
|
451
452
|
if (!action) return
|
|
@@ -10,6 +10,7 @@ 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
14
|
|
|
14
15
|
type InstallerEntry = {
|
|
15
16
|
action: CustomAction
|
|
@@ -28,6 +29,12 @@ type InstallerEntry = {
|
|
|
28
29
|
type SetupDialogOptions = {
|
|
29
30
|
settingsPath: string
|
|
30
31
|
bundleName?: string
|
|
32
|
+
shortcutsPath?: string
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
type ActionAccelerator = {
|
|
36
|
+
name: string
|
|
37
|
+
shortcut: string
|
|
31
38
|
}
|
|
32
39
|
|
|
33
40
|
const OVERRIDE_MARKER = '[ovr]'
|
|
@@ -37,6 +44,9 @@ const toKey = (action: CustomAction): string => String(action.filePath ?? action
|
|
|
37
44
|
const toTreeNode = (entry: InstallerEntry): TreeNode<InstallerEntry> =>
|
|
38
45
|
new TreeNode(String(entry.action.text), toKey(entry.action), entry)
|
|
39
46
|
|
|
47
|
+
const getEntry = (item: TreeNode<InstallerEntry>): InstallerEntry =>
|
|
48
|
+
item.value as InstallerEntry
|
|
49
|
+
|
|
40
50
|
const getDisplayedToolbar = (entry: InstallerEntry): string => String(entry.action.toolbar ?? '')
|
|
41
51
|
|
|
42
52
|
const getSetupDialogOptions = (options: string | SetupDialogOptions): SetupDialogOptions =>
|
|
@@ -123,8 +133,9 @@ class InstallerSelectionDialog extends BasicDialog {
|
|
|
123
133
|
.sortOnBuild(true)
|
|
124
134
|
.refresh(this.refreshListEvent$)
|
|
125
135
|
.row((item, parent, id) => {
|
|
136
|
+
const entry = getEntry(item)
|
|
126
137
|
const listItem = new DzCheckListItem(parent, DzCheckListItem.CheckBox, id)
|
|
127
|
-
listItem.on =
|
|
138
|
+
listItem.on = entry.selected
|
|
128
139
|
listItem.setText(0, '')
|
|
129
140
|
return listItem
|
|
130
141
|
})
|
|
@@ -139,15 +150,18 @@ class InstallerSelectionDialog extends BasicDialog {
|
|
|
139
150
|
if (index === 5) return Math.max(width * 1.2, 140)
|
|
140
151
|
return width
|
|
141
152
|
})
|
|
142
|
-
.text((item) =>
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
153
|
+
.text((item) => {
|
|
154
|
+
const entry = getEntry(item)
|
|
155
|
+
return [
|
|
156
|
+
'',
|
|
157
|
+
String(entry.action.text ?? ''),
|
|
158
|
+
getDisplayedShortcut(entry),
|
|
159
|
+
String(entry.action.description ?? ''),
|
|
160
|
+
String(entry.action.menuPath ?? ''),
|
|
161
|
+
getDisplayedToolbar(entry),
|
|
162
|
+
]
|
|
163
|
+
})
|
|
164
|
+
.data((item) => getEntry(item))
|
|
151
165
|
.filter({
|
|
152
166
|
keywords: this.keywords$,
|
|
153
167
|
field: (listItem) => [
|
|
@@ -287,6 +301,21 @@ export const showSetupCustomActionsDialog = (actions: CustomAction[], options: s
|
|
|
287
301
|
const selections = runDialog(actions, settings)
|
|
288
302
|
if (!selections) return
|
|
289
303
|
|
|
304
|
+
if (settings.shortcutsPath) {
|
|
305
|
+
try {
|
|
306
|
+
const shortcuts = readFromFile<ActionAccelerator[]>(settings.shortcutsPath)
|
|
307
|
+
if (shortcuts) {
|
|
308
|
+
progress('Applying Keyboard Shortcuts', shortcuts, (shortcut) => {
|
|
309
|
+
if (shortcut.name && shortcut.shortcut) {
|
|
310
|
+
setActionShortcut(shortcut.name, shortcut.shortcut)
|
|
311
|
+
}
|
|
312
|
+
})
|
|
313
|
+
}
|
|
314
|
+
} catch (e) {
|
|
315
|
+
debug(`[Setup] Failed to apply shortcuts from ${settings.shortcutsPath}: ${e}`)
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
290
319
|
debug(`[Setup] applying ${selections.filter(selection => selection.selected).length}/${selections.length} selected actions`)
|
|
291
320
|
|
|
292
321
|
const removedToolbarNames = new CustomSet<string>()
|