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/package.json CHANGED
@@ -1,17 +1,19 @@
1
1
  {
2
2
  "name": "dazscript-framework",
3
- "version": "0.3.2",
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/samples --menu-path /DazScriptFramework",
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/samples --menu-path /DazScriptFramework"
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": "samples/hello-world.dsa",
9
- "menuPath": "/DazScriptFramework/samples",
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": "Sample Dialog",
15
- "filePath": "samples/sample-dialog.dsa",
16
- "menuPath": "/DazScriptFramework/samples",
17
- "description": "sample-dialog"
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/samples/Installer","bundleName":"Samples"});
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,8 @@
1
+ import { debug } from '@dsf/common/log'
2
+ import { action } from '@dsf/core/action'
3
+ import { info } from '@dsf/helpers/message-box-helper'
4
+
5
+ action({ text: '01 Hello World' }, () => {
6
+ debug('Hello World!')
7
+ info('Hello World!')
8
+ })
@@ -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
+ })