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
package/package.json
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dazscript-framework",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"author": "Freddy Diaz",
|
|
5
5
|
"license": "MPL-2.0",
|
|
6
6
|
"description": "",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"prepare": "node ./scripts/install-git-hooks.js",
|
|
9
|
-
"prebuild": "node ./dist/scripts/cli.js installer --scripts-path ./src/
|
|
9
|
+
"prebuild": "node ./dist/scripts/cli.js installer --scripts-path ./src/examples --menu-path /DazScriptFramework",
|
|
10
10
|
"build": "node ./dist/scripts/cli.js build --out-dir ./out",
|
|
11
11
|
"postbuild": "node ./dist/scripts/cli.js icons --out-dir ./out",
|
|
12
12
|
"watch": "node ./dist/scripts/cli.js watch --out-dir ./out",
|
|
13
13
|
"icons": "node ./dist/scripts/cli.js icons --out-dir ./out",
|
|
14
|
-
"installer": "node ./dist/scripts/cli.js installer --scripts-path ./src/
|
|
14
|
+
"installer": "node ./dist/scripts/cli.js installer --scripts-path ./src/examples --menu-path /DazScriptFramework",
|
|
15
|
+
"test": "vitest run",
|
|
16
|
+
"test:watch": "vitest"
|
|
15
17
|
},
|
|
16
18
|
"bin": {
|
|
17
19
|
"dazscript": "./dist/scripts/cli.js"
|
|
@@ -69,6 +71,7 @@
|
|
|
69
71
|
"@types/node": "^22.10.5",
|
|
70
72
|
"dazscript-types": "^1.0.1",
|
|
71
73
|
"eslint": "^9.17.0",
|
|
72
|
-
"typescript": "^5.7.3"
|
|
74
|
+
"typescript": "^5.7.3",
|
|
75
|
+
"vitest": "^3.0.0"
|
|
73
76
|
}
|
|
74
77
|
}
|
package/src/Setup.dsa.ts
CHANGED
|
@@ -4,16 +4,44 @@ import { showSetupCustomActionsDialog as setup } from '@dsf/helpers/custom-actio
|
|
|
4
4
|
setup([
|
|
5
5
|
{
|
|
6
6
|
"name": null,
|
|
7
|
-
"text": "Hello World",
|
|
8
|
-
"filePath": "
|
|
9
|
-
"menuPath": "/DazScriptFramework/
|
|
10
|
-
"description": "hello-world"
|
|
7
|
+
"text": "01 Hello World",
|
|
8
|
+
"filePath": "examples/01-hello-world.dsa",
|
|
9
|
+
"menuPath": "/DazScriptFramework/examples",
|
|
10
|
+
"description": "01-hello-world"
|
|
11
11
|
},
|
|
12
12
|
{
|
|
13
13
|
"name": null,
|
|
14
|
-
"text": "
|
|
15
|
-
"filePath": "
|
|
16
|
-
"menuPath": "/DazScriptFramework/
|
|
17
|
-
"description": "
|
|
14
|
+
"text": "02 Persistence Dialog",
|
|
15
|
+
"filePath": "examples/02-persistence-dialog.dsa",
|
|
16
|
+
"menuPath": "/DazScriptFramework/examples",
|
|
17
|
+
"description": "02-persistence-dialog"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"name": null,
|
|
21
|
+
"text": "03 Simple Dialog",
|
|
22
|
+
"filePath": "examples/03-simple-dialog.dsa",
|
|
23
|
+
"menuPath": "/DazScriptFramework/examples",
|
|
24
|
+
"description": "03-simple-dialog"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"name": null,
|
|
28
|
+
"text": "04 Settings Dialog",
|
|
29
|
+
"filePath": "examples/04-settings-dialog.dsa",
|
|
30
|
+
"menuPath": "/DazScriptFramework/examples",
|
|
31
|
+
"description": "04-settings-dialog"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"name": null,
|
|
35
|
+
"text": "05 List Dialog",
|
|
36
|
+
"filePath": "examples/05-list-dialog.dsa",
|
|
37
|
+
"menuPath": "/DazScriptFramework/examples",
|
|
38
|
+
"description": "05-list-dialog"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"name": null,
|
|
42
|
+
"text": "06 Showcase Dialog",
|
|
43
|
+
"filePath": "examples/06-showcase-dialog.dsa",
|
|
44
|
+
"menuPath": "/DazScriptFramework/examples",
|
|
45
|
+
"description": "06-showcase-dialog"
|
|
18
46
|
}
|
|
19
|
-
], {"settingsPath":"DazScriptFramework/
|
|
47
|
+
], {"settingsPath":"DazScriptFramework/examples/Installer","bundleName":"Examples"});
|
|
@@ -193,6 +193,11 @@ class ListViewBindBuilder<TItem, TData> {
|
|
|
193
193
|
return this
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
+
contextMenu(fn: (listView: DzListView, item: DzListViewItem, pos: Point) => DzPopupMenu): this {
|
|
197
|
+
this.context.contextMenu = fn
|
|
198
|
+
return this
|
|
199
|
+
}
|
|
200
|
+
|
|
196
201
|
build(then?: (listView: DzListView) => void): DzListView {
|
|
197
202
|
let listView = build(this.context)
|
|
198
203
|
then?.(listView)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { debug } from '@dsf/common/log'
|
|
2
|
+
import { action } from '@dsf/core/action'
|
|
3
|
+
import { PersistenceDialog, PersistenceDialogModel } from './02-persistence-dialog'
|
|
4
|
+
|
|
5
|
+
action({ text: '02 Persistence Dialog' }, () => {
|
|
6
|
+
let model = new PersistenceDialogModel()
|
|
7
|
+
let dialog = new PersistenceDialog(model)
|
|
8
|
+
|
|
9
|
+
if (!dialog.run()) {
|
|
10
|
+
debug('Persistence dialog cancelled')
|
|
11
|
+
return
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const lines = [
|
|
15
|
+
`DzAppSettings checkbox : ${model.registryEnabled$.value}`,
|
|
16
|
+
`App data JSON checkbox : ${model.fileEnabled$.value}`,
|
|
17
|
+
`App data JSON file : ${model.filePath}`,
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
debug(lines.join('\n'))
|
|
21
|
+
})
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { BasicDialog } from '@dsf/dialog/basic-dialog'
|
|
2
|
+
import { readFromFile, saveToFile } from '@dsf/helpers/file-helper'
|
|
3
|
+
import { AppSettings } from '@dsf/lib/settings'
|
|
4
|
+
import { Observable } from '@dsf/lib/observable'
|
|
5
|
+
import { config } from './config'
|
|
6
|
+
|
|
7
|
+
type FilePersistenceState = {
|
|
8
|
+
enabled: boolean
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class PersistenceSettings extends AppSettings {
|
|
12
|
+
constructor() {
|
|
13
|
+
super(`${config.author}/02-PersistenceDialog`)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
registryEnabled$ = this.bindBoolean('registryEnabled$', false)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class PersistenceDialogModel {
|
|
20
|
+
readonly settings = new PersistenceSettings()
|
|
21
|
+
readonly registryEnabled$ = this.settings.registryEnabled$
|
|
22
|
+
readonly filePath = `${this.settings.appDataPath}/persistence-dialog.json`
|
|
23
|
+
readonly fileEnabled$ = new Observable(this.readFileState().enabled, () => this.writeFileState())
|
|
24
|
+
|
|
25
|
+
private readFileState(): FilePersistenceState {
|
|
26
|
+
return readFromFile<FilePersistenceState>(this.filePath) ?? { enabled: false }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
private writeFileState(): void {
|
|
30
|
+
saveToFile(this.filePath, JSON.stringify({ enabled: this.fileEnabled$.value }, null, 2))
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class PersistenceDialog extends BasicDialog {
|
|
35
|
+
constructor(private readonly model: PersistenceDialogModel) {
|
|
36
|
+
super('02 Persistence Dialog')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
protected build(): void {
|
|
40
|
+
let add = this.add
|
|
41
|
+
let model = this.model
|
|
42
|
+
|
|
43
|
+
this.builder.options({ resizable: true, width: 620, height: 360 })
|
|
44
|
+
this.dialog.setAcceptButtonText('Close')
|
|
45
|
+
|
|
46
|
+
add.label([
|
|
47
|
+
'Toggle either checkbox, close this dialog, then run the example again.',
|
|
48
|
+
'The checkbox values should reopen with the last saved state.',
|
|
49
|
+
].join('\n')).wordWrap().build()
|
|
50
|
+
|
|
51
|
+
add.group('DzAppSettings').build(() => {
|
|
52
|
+
add.label([
|
|
53
|
+
'Persists through DAZ Studio DzAppSettings.',
|
|
54
|
+
'On Windows this uses the registry-backed application settings store.',
|
|
55
|
+
'On macOS this uses the platform preferences store.',
|
|
56
|
+
].join('\n')).wordWrap().build()
|
|
57
|
+
add.checkbox('Stored in DzAppSettings').value(model.registryEnabled$)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
add.group('App Data JSON File').build(() => {
|
|
61
|
+
add.label([
|
|
62
|
+
'Persists to a JSON file under DAZ Studio app data.',
|
|
63
|
+
`File: ${model.filePath}`,
|
|
64
|
+
].join('\n')).wordWrap().build()
|
|
65
|
+
add.checkbox('Stored in app data JSON').value(model.fileEnabled$)
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { debug } from '@dsf/common/log'
|
|
2
|
+
import { action } from '@dsf/core/action'
|
|
3
|
+
import { info } from '@dsf/helpers/message-box-helper'
|
|
4
|
+
import { SimpleDialog, SimpleDialogModel } from './03-simple-dialog'
|
|
5
|
+
|
|
6
|
+
action({ text: '03 Simple Dialog' }, () => {
|
|
7
|
+
let model = new SimpleDialogModel()
|
|
8
|
+
let dialog = new SimpleDialog(model)
|
|
9
|
+
|
|
10
|
+
if (!dialog.run()) {
|
|
11
|
+
debug('Dialog cancelled')
|
|
12
|
+
return
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const lines = [
|
|
16
|
+
`Name : ${model.name$.value}`,
|
|
17
|
+
`Enabled : ${model.enabled$.value}`,
|
|
18
|
+
`Notes : ${model.notes$.value || '(none)'}`,
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
debug(lines.join('\n'))
|
|
22
|
+
info(lines.join('\n'))
|
|
23
|
+
})
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { BasicDialog } from '@dsf/dialog/basic-dialog'
|
|
2
|
+
import { Observable } from '@dsf/lib/observable'
|
|
3
|
+
|
|
4
|
+
// Model
|
|
5
|
+
|
|
6
|
+
export class SimpleDialogModel {
|
|
7
|
+
name$ = new Observable('My Object')
|
|
8
|
+
enabled$ = new Observable(true)
|
|
9
|
+
notes$ = new Observable('')
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Dialog
|
|
13
|
+
|
|
14
|
+
export class SimpleDialog extends BasicDialog {
|
|
15
|
+
constructor(private readonly model: SimpleDialogModel) {
|
|
16
|
+
super('03 Simple Dialog')
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
protected build(): void {
|
|
20
|
+
let add = this.add
|
|
21
|
+
let model = this.model
|
|
22
|
+
|
|
23
|
+
this.builder.options({ resizable: true, width: 420, height: 240 })
|
|
24
|
+
|
|
25
|
+
add.group('Object').build(() => {
|
|
26
|
+
add.horizontal((layout) => {
|
|
27
|
+
layout.spacing = 5
|
|
28
|
+
add.label('Name:').minWidth(45)
|
|
29
|
+
add.edit().value(model.name$).placeholder('Enter a name...')
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
add.horizontal((layout) => {
|
|
33
|
+
layout.spacing = 5
|
|
34
|
+
add.label('Notes:').minWidth(45)
|
|
35
|
+
add.edit().value(model.notes$).placeholder('Optional notes...')
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
add.checkbox('Enabled').value(model.enabled$)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
add.button('Reset').clicked(() => {
|
|
42
|
+
model.name$.value = 'My Object'
|
|
43
|
+
model.enabled$.value = true
|
|
44
|
+
model.notes$.value = ''
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { debug } from '@dsf/common/log'
|
|
2
|
+
import { action } from '@dsf/core/action'
|
|
3
|
+
import { info } from '@dsf/helpers/message-box-helper'
|
|
4
|
+
import { RenderSettings, SettingsDialog } from './04-settings-dialog'
|
|
5
|
+
|
|
6
|
+
action({ text: '04 Settings Dialog' }, () => {
|
|
7
|
+
// Settings are loaded from DzAppSettings on construction and
|
|
8
|
+
// auto-saved on every change - values persist between script runs.
|
|
9
|
+
let settings = new RenderSettings()
|
|
10
|
+
let dialog = new SettingsDialog(settings)
|
|
11
|
+
|
|
12
|
+
if (!dialog.run()) {
|
|
13
|
+
debug('Settings cancelled')
|
|
14
|
+
return
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const lines = [
|
|
18
|
+
`Quality : ${settings.quality$.value}`,
|
|
19
|
+
`Samples : ${settings.samples$.value}`,
|
|
20
|
+
`Scale : ${settings.scale$.value}`,
|
|
21
|
+
`Output : ${settings.outputPath$.value || '(default)'}`,
|
|
22
|
+
`Format : ${settings.format$.value}`,
|
|
23
|
+
`Use GPU : ${settings.useGpu$.value}`,
|
|
24
|
+
`Verbose : ${settings.verbose$.value}`,
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
debug(lines.join('\n'))
|
|
28
|
+
info(lines.join('\n'))
|
|
29
|
+
})
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { BasicDialog } from '@dsf/dialog/basic-dialog'
|
|
2
|
+
import { AppSettings } from '@dsf/lib/settings'
|
|
3
|
+
import { config } from './config'
|
|
4
|
+
|
|
5
|
+
// Settings (persisted across runs via DzAppSettings)
|
|
6
|
+
|
|
7
|
+
export class RenderSettings extends AppSettings {
|
|
8
|
+
constructor() {
|
|
9
|
+
super(`${config.author}/04-SettingsDialog`)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
quality$ = this.bindString('quality$', 'Medium')
|
|
13
|
+
samples$ = this.bindInt('samples$', 64)
|
|
14
|
+
scale$ = this.bindFloat('scale$', 1.0)
|
|
15
|
+
outputPath$ = this.bindString('outputPath$', '')
|
|
16
|
+
useGpu$ = this.bindBoolean('useGpu$', true)
|
|
17
|
+
verbose$ = this.bindBoolean('verbose$', false)
|
|
18
|
+
format$ = this.bindString('format$', 'PNG')
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Dialog
|
|
22
|
+
|
|
23
|
+
export class SettingsDialog extends BasicDialog {
|
|
24
|
+
constructor(private readonly settings: RenderSettings) {
|
|
25
|
+
super('04 Settings Dialog')
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
protected build(): void {
|
|
29
|
+
let add = this.add
|
|
30
|
+
let settings = this.settings
|
|
31
|
+
|
|
32
|
+
this.builder.options({ resizable: true, width: 520, height: 420 })
|
|
33
|
+
|
|
34
|
+
add.group('Render').build(() => {
|
|
35
|
+
add.horizontal((layout) => {
|
|
36
|
+
layout.spacing = 5
|
|
37
|
+
add.label('Quality:').minWidth(60)
|
|
38
|
+
add.combo()
|
|
39
|
+
.items(['Low', 'Medium', 'High', 'Ultra'])
|
|
40
|
+
.selected(settings.quality$)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
add.horizontal((layout) => {
|
|
44
|
+
layout.spacing = 5
|
|
45
|
+
add.label('Samples:').minWidth(60)
|
|
46
|
+
add.slider('integer').value(settings.samples$).min(1).max(512).build()
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
add.horizontal((layout) => {
|
|
50
|
+
layout.spacing = 5
|
|
51
|
+
add.label('Scale:').minWidth(60)
|
|
52
|
+
add.slider('float').value(settings.scale$).min(0.1).max(10.0).build()
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
add.group('Output').build(() => {
|
|
57
|
+
add.horizontal((layout) => {
|
|
58
|
+
layout.spacing = 5
|
|
59
|
+
add.label('Path:').minWidth(60)
|
|
60
|
+
add.edit()
|
|
61
|
+
.value(settings.outputPath$)
|
|
62
|
+
.placeholder('Leave empty for default output path...')
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
add.group('Format').horizontal().style({ flat: true }).build(() => {
|
|
66
|
+
add.radio('PNG')
|
|
67
|
+
.value(settings.format$.value === 'PNG')
|
|
68
|
+
.toggled(v => { if (v) settings.format$.value = 'PNG' })
|
|
69
|
+
add.radio('JPEG')
|
|
70
|
+
.value(settings.format$.value === 'JPEG')
|
|
71
|
+
.toggled(v => { if (v) settings.format$.value = 'JPEG' })
|
|
72
|
+
add.radio('EXR')
|
|
73
|
+
.value(settings.format$.value === 'EXR')
|
|
74
|
+
.toggled(v => { if (v) settings.format$.value = 'EXR' })
|
|
75
|
+
})
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
add.group('Options').horizontal().build(() => {
|
|
79
|
+
add.checkbox('Use GPU').value(settings.useGpu$)
|
|
80
|
+
add.checkbox('Verbose').value(settings.verbose$)
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { debug } from '@dsf/common/log'
|
|
2
|
+
import { action } from '@dsf/core/action'
|
|
3
|
+
import { info } from '@dsf/helpers/message-box-helper'
|
|
4
|
+
import { TreeNode } from '@dsf/lib/tree-node'
|
|
5
|
+
import { FileItem, ListDialog, ListDialogModel } from './05-list-dialog'
|
|
6
|
+
|
|
7
|
+
// Sample data
|
|
8
|
+
|
|
9
|
+
function buildFileTree(): TreeNode<FileItem>[] {
|
|
10
|
+
const folder = (name: string, children: TreeNode<FileItem>[] = []) =>
|
|
11
|
+
new TreeNode<FileItem>(name, name, { name, type: 'folder', size: 0 }, children)
|
|
12
|
+
|
|
13
|
+
const file = (name: string, size: number) =>
|
|
14
|
+
new TreeNode<FileItem>(name, name, { name, type: 'file', size })
|
|
15
|
+
|
|
16
|
+
return [
|
|
17
|
+
folder('Characters', [
|
|
18
|
+
file('Victoria.duf', 1_200_000),
|
|
19
|
+
file('Michael.duf', 980_000),
|
|
20
|
+
]),
|
|
21
|
+
folder('Props', [
|
|
22
|
+
file('Chair.obj', 45_000),
|
|
23
|
+
file('Table.obj', 62_000),
|
|
24
|
+
folder('Lights', [
|
|
25
|
+
file('Studio.duf', 12_000),
|
|
26
|
+
]),
|
|
27
|
+
]),
|
|
28
|
+
file('Scene.duf', 3_400_000),
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Action
|
|
33
|
+
|
|
34
|
+
action({ text: '05 List Dialog' }, () => {
|
|
35
|
+
let model = new ListDialogModel()
|
|
36
|
+
model.files$.value = buildFileTree()
|
|
37
|
+
model.recentFiles$.value = ['Victoria.duf', 'Chair.obj', 'Scene.duf']
|
|
38
|
+
|
|
39
|
+
let dialog = new ListDialog(model)
|
|
40
|
+
|
|
41
|
+
if (!dialog.run()) {
|
|
42
|
+
debug('List dialog cancelled')
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let sel = model.selected$.value
|
|
47
|
+
const lines = sel
|
|
48
|
+
? [`Selected : ${sel.name}`, `Type : ${sel.type}`, `Size : ${sel.size} bytes`]
|
|
49
|
+
: ['No item selected']
|
|
50
|
+
|
|
51
|
+
debug(lines.join('\n'))
|
|
52
|
+
info(lines.join('\n'))
|
|
53
|
+
})
|
|
@@ -0,0 +1,88 @@
|
|
|
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 { Observable } from '@dsf/lib/observable'
|
|
5
|
+
import { TreeNode } from '@dsf/lib/tree-node'
|
|
6
|
+
|
|
7
|
+
// Types
|
|
8
|
+
|
|
9
|
+
export interface FileItem {
|
|
10
|
+
name: string
|
|
11
|
+
type: 'folder' | 'file'
|
|
12
|
+
size: number
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Model
|
|
16
|
+
|
|
17
|
+
export class ListDialogModel {
|
|
18
|
+
keywords$ = new Observable('')
|
|
19
|
+
files$ = new Observable<TreeNode<FileItem>[]>([])
|
|
20
|
+
selected$ = new Observable<FileItem>()
|
|
21
|
+
recentFiles$ = new Observable<string[]>([])
|
|
22
|
+
selectedRecent$ = new Observable<string[]>([])
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Dialog
|
|
26
|
+
|
|
27
|
+
export class ListDialog extends BasicDialog {
|
|
28
|
+
constructor(private readonly model: ListDialogModel) {
|
|
29
|
+
super('05 List View')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
protected build(): void {
|
|
33
|
+
let add = this.add
|
|
34
|
+
let model = this.model
|
|
35
|
+
|
|
36
|
+
this.builder.options({ resizable: true, width: 640, height: 520 })
|
|
37
|
+
|
|
38
|
+
// Search bar
|
|
39
|
+
add.horizontal((layout) => {
|
|
40
|
+
layout.spacing = 5
|
|
41
|
+
add.label('Search:').minWidth(50)
|
|
42
|
+
add.edit()
|
|
43
|
+
.value(model.keywords$)
|
|
44
|
+
.placeholder('Filter files...')
|
|
45
|
+
.focus()
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
// Tree list with context menu and keyword filter
|
|
49
|
+
add.list.view<FileItem>()
|
|
50
|
+
.expanded(true)
|
|
51
|
+
.items(model.files$)
|
|
52
|
+
.columns(['Name', 'Type', 'Size (KB)'])
|
|
53
|
+
.text(item => [
|
|
54
|
+
item.name,
|
|
55
|
+
item.value?.type ?? '',
|
|
56
|
+
item.value ? String(Math.round(item.value.size / 1024)) : '',
|
|
57
|
+
])
|
|
58
|
+
.data(item => item.value)
|
|
59
|
+
.selected(model.selected$)
|
|
60
|
+
.filter({
|
|
61
|
+
keywords: model.keywords$,
|
|
62
|
+
field: li => li.text(0),
|
|
63
|
+
selectOnFilter: true,
|
|
64
|
+
})
|
|
65
|
+
.contextMenu((_, li) => {
|
|
66
|
+
let file = getDataItem<FileItem>(li)
|
|
67
|
+
let label = file?.name ?? 'item'
|
|
68
|
+
let items: PopupMenuItem[] = [
|
|
69
|
+
{ text: `Open "${label}"`, activated: () => { /* open */ } },
|
|
70
|
+
{ text: `Rename "${label}"`, activated: () => { /* rename */ } },
|
|
71
|
+
{ text: '' },
|
|
72
|
+
{ text: 'Copy Path', activated: () => { /* copy to clipboard */ } },
|
|
73
|
+
{ text: 'Delete', activated: () => { /* delete */ } },
|
|
74
|
+
]
|
|
75
|
+
return add.contextMenu().items(...items).build()
|
|
76
|
+
})
|
|
77
|
+
.build()
|
|
78
|
+
|
|
79
|
+
// Simple list box for recently opened files
|
|
80
|
+
add.group('Recent Files').build(() => {
|
|
81
|
+
add.list.box()
|
|
82
|
+
.items(model.recentFiles$)
|
|
83
|
+
.mode('extended')
|
|
84
|
+
.selected(model.selectedRecent$)
|
|
85
|
+
.build()
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { debug } from '@dsf/common/log'
|
|
2
|
+
import { action } from '@dsf/core/action'
|
|
3
|
+
import { info } from '@dsf/helpers/message-box-helper'
|
|
4
|
+
import { TreeNode } from '@dsf/lib/tree-node'
|
|
5
|
+
import { SceneObject, ShowcaseDialog, ShowcaseDialogModel } from './06-showcase-dialog'
|
|
6
|
+
|
|
7
|
+
// Sample data
|
|
8
|
+
|
|
9
|
+
function buildSceneTree(): TreeNode<SceneObject>[] {
|
|
10
|
+
let id = 0
|
|
11
|
+
const next = () => ++id
|
|
12
|
+
|
|
13
|
+
const group = (name: string, children: TreeNode<SceneObject>[] = []) =>
|
|
14
|
+
new TreeNode<SceneObject>(
|
|
15
|
+
name, name,
|
|
16
|
+
{ id: next(), name, type: 'Group', visible: true, locked: false },
|
|
17
|
+
children,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
const obj = (name: string, type: SceneObject['type'], visible = true, locked = false) =>
|
|
21
|
+
new TreeNode<SceneObject>(name, name, { id: next(), name, type, visible, locked })
|
|
22
|
+
|
|
23
|
+
return [
|
|
24
|
+
group('Characters', [
|
|
25
|
+
obj('Victoria 9', 'Figure'),
|
|
26
|
+
obj('Michael 9', 'Figure', true, true),
|
|
27
|
+
obj('Genesis 9', 'Figure', false),
|
|
28
|
+
]),
|
|
29
|
+
group('Environment', [
|
|
30
|
+
obj('Studio Floor', 'Prop'),
|
|
31
|
+
obj('Background', 'Prop', false),
|
|
32
|
+
group('Lights', [
|
|
33
|
+
obj('Key Light', 'Light'),
|
|
34
|
+
obj('Fill Light', 'Light', false),
|
|
35
|
+
obj('Rim Light', 'Light'),
|
|
36
|
+
]),
|
|
37
|
+
]),
|
|
38
|
+
obj('Camera 1', 'Camera'),
|
|
39
|
+
obj('Camera 2', 'Camera', false),
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Action
|
|
44
|
+
|
|
45
|
+
action({ text: '06 Showcase Dialog' }, () => {
|
|
46
|
+
let model = new ShowcaseDialogModel()
|
|
47
|
+
let dialog = new ShowcaseDialog(model)
|
|
48
|
+
|
|
49
|
+
model.objects$.value = buildSceneTree()
|
|
50
|
+
|
|
51
|
+
model.logEntries$.value = [
|
|
52
|
+
'[Info] Script started',
|
|
53
|
+
'[Info] Scene loaded - 4 root nodes',
|
|
54
|
+
'[Debug] Settings restored from DzAppSettings',
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
// Keep the detail panel in sync with the selected object.
|
|
58
|
+
model.selectedObject$.connect(obj => {
|
|
59
|
+
model.detailName$.value = obj?.name ?? ''
|
|
60
|
+
model.detailType$.value = obj?.type ?? ''
|
|
61
|
+
model.detailVisible$.value = obj?.visible ?? false
|
|
62
|
+
model.detailLocked$.value = obj?.locked ?? false
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
if (!dialog.run()) {
|
|
66
|
+
debug('Showcase dialog cancelled')
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let sel = model.selectedObject$.value
|
|
71
|
+
let settings = model.settings
|
|
72
|
+
const lines = sel
|
|
73
|
+
? [
|
|
74
|
+
`Selected : ${sel.name}`,
|
|
75
|
+
`Type : ${sel.type}`,
|
|
76
|
+
`Visible : ${sel.visible}`,
|
|
77
|
+
`Locked : ${sel.locked}`,
|
|
78
|
+
``,
|
|
79
|
+
`Quality : ${settings.quality$.value}`,
|
|
80
|
+
`Samples : ${settings.samples$.value}`,
|
|
81
|
+
`Format : ${settings.exportFormat$.value}`,
|
|
82
|
+
]
|
|
83
|
+
: ['No object selected']
|
|
84
|
+
|
|
85
|
+
debug(lines.join('\n'))
|
|
86
|
+
info(lines.join('\n'))
|
|
87
|
+
})
|