dazscript-framework 0.3.2 → 1.0.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.
- package/README.md +361 -265
- package/dist/scripts/install-generator.js +85 -14
- 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 +306 -20
- 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 +7 -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
|
@@ -21,14 +21,19 @@ function stringOrDefault(str, defaultValue) {
|
|
|
21
21
|
return str !== undefined && str !== null && str !== '' ? str : defaultValue;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
function generateInstallerTemplate(data,
|
|
24
|
+
function generateInstallerTemplate(data, options) {
|
|
25
25
|
return `
|
|
26
26
|
import { showSetupCustomActionsDialog as setup } from '@dsf/helpers/custom-action-installer-helper';
|
|
27
27
|
|
|
28
|
-
setup(${data}, ${JSON.stringify(
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
setup(${data}, ${JSON.stringify(options)});
|
|
29
|
+
`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function generateUninstallTemplate(options) {
|
|
33
|
+
return `
|
|
34
|
+
import { restoreSetupKeyboardShortcuts as restoreShortcuts } from '@dsf/helpers/custom-action-installer-helper';
|
|
35
|
+
|
|
36
|
+
restoreShortcuts(${JSON.stringify(options)});
|
|
32
37
|
`;
|
|
33
38
|
}
|
|
34
39
|
|
|
@@ -151,7 +156,7 @@ function findActionEntryFiles(workdir, options) {
|
|
|
151
156
|
});
|
|
152
157
|
}
|
|
153
158
|
|
|
154
|
-
function processScript(filePath, container, defaultMenuPath,
|
|
159
|
+
function processScript(filePath, container, defaultMenuPath, setupOptions) {
|
|
155
160
|
const fileInfo = path.parse(filePath);
|
|
156
161
|
const content = fs.readFileSync(filePath, 'utf-8').toString();
|
|
157
162
|
const actionCall = findTopLevelActionCall(content, filePath);
|
|
@@ -213,8 +218,7 @@ function processScript(filePath, container, defaultMenuPath, settingsPath, bundl
|
|
|
213
218
|
|
|
214
219
|
let bundleScriptContent = generateInstallerTemplate(
|
|
215
220
|
JSON.stringify(container.scripts, null, 4),
|
|
216
|
-
|
|
217
|
-
bundleName
|
|
221
|
+
setupOptions
|
|
218
222
|
);
|
|
219
223
|
let bundleScriptFilePath = path.join(
|
|
220
224
|
path.parse(filePath).dir,
|
|
@@ -224,13 +228,66 @@ function processScript(filePath, container, defaultMenuPath, settingsPath, bundl
|
|
|
224
228
|
}
|
|
225
229
|
}
|
|
226
230
|
|
|
227
|
-
function processScripts(paths, container, defaultMenuPath,
|
|
231
|
+
function processScripts(paths, container, defaultMenuPath, setupOptions) {
|
|
228
232
|
paths.forEach((filePath) => {
|
|
229
233
|
console.log(`Processing ${filePath}`);
|
|
230
|
-
processScript(filePath, container, defaultMenuPath,
|
|
234
|
+
processScript(filePath, container, defaultMenuPath, setupOptions);
|
|
231
235
|
});
|
|
232
236
|
}
|
|
233
237
|
|
|
238
|
+
function toPosix(filePath) {
|
|
239
|
+
return filePath.replace(/\\/g, '/');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function resolveShortcutFile(workdir, config) {
|
|
243
|
+
const configuredPath =
|
|
244
|
+
config.keyboardShortcutsPath ||
|
|
245
|
+
config.shortcutsPath ||
|
|
246
|
+
config.actionAcceleratorsPath;
|
|
247
|
+
|
|
248
|
+
const candidates = [];
|
|
249
|
+
if (configuredPath) {
|
|
250
|
+
candidates.push(path.resolve(workdir, configuredPath));
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
candidates.push(
|
|
254
|
+
path.join(workdir, 'src', 'keyboard-shortcuts.json'),
|
|
255
|
+
path.join(workdir, 'src', 'action-accelerators.json'),
|
|
256
|
+
path.join(workdir, 'keyboard-shortcuts.json'),
|
|
257
|
+
path.join(workdir, 'action-accelerators.json')
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
return candidates.find((candidate) => fs.existsSync(candidate)) || null;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function normalizeShortcutData(raw) {
|
|
264
|
+
if (!raw) return [];
|
|
265
|
+
if (Array.isArray(raw)) return raw;
|
|
266
|
+
if (Array.isArray(raw.actions)) return raw.actions;
|
|
267
|
+
if (Array.isArray(raw.shortcuts)) return raw.shortcuts;
|
|
268
|
+
if (Array.isArray(raw.accelerators)) return raw.accelerators;
|
|
269
|
+
return [];
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function loadShortcutData(workdir, config) {
|
|
273
|
+
const shortcutFile = resolveShortcutFile(workdir, config);
|
|
274
|
+
if (!shortcutFile) {
|
|
275
|
+
return { shortcuts: undefined, sourcePath: undefined };
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const raw = JSON.parse(fs.readFileSync(shortcutFile, 'utf8'));
|
|
279
|
+
const shortcuts = normalizeShortcutData(raw);
|
|
280
|
+
|
|
281
|
+
if (shortcuts.length === 0) {
|
|
282
|
+
console.warn(`[dazscript] Keyboard shortcut file has no shortcut entries: ${shortcutFile}`);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return {
|
|
286
|
+
shortcuts,
|
|
287
|
+
sourcePath: toPosix(path.relative(workdir, shortcutFile)),
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
234
291
|
function generateInstallerFiles(workdir, options) {
|
|
235
292
|
const defaultMenuPath = options.defaultMenuPath.endsWith('/')
|
|
236
293
|
? options.defaultMenuPath
|
|
@@ -241,10 +298,20 @@ function generateInstallerFiles(workdir, options) {
|
|
|
241
298
|
const bundleName = typeof config.bundleName === 'string' && config.bundleName.trim()
|
|
242
299
|
? config.bundleName.trim()
|
|
243
300
|
: undefined;
|
|
301
|
+
const shortcutData = loadShortcutData(workdir, config);
|
|
302
|
+
const setupOptions = {
|
|
303
|
+
settingsPath,
|
|
304
|
+
bundleName,
|
|
305
|
+
shortcutBackupPath: `${appDataPath}/Installer/keyboard-shortcuts-backup.json`,
|
|
306
|
+
};
|
|
307
|
+
if (shortcutData.shortcuts && shortcutData.shortcuts.length > 0) {
|
|
308
|
+
setupOptions.shortcuts = shortcutData.shortcuts;
|
|
309
|
+
setupOptions.shortcutsSourcePath = shortcutData.sourcePath;
|
|
310
|
+
}
|
|
244
311
|
const container = { scripts: [] };
|
|
245
312
|
const matches = findActionEntryFiles(workdir, options);
|
|
246
313
|
|
|
247
|
-
processScripts(matches, container, defaultMenuPath,
|
|
314
|
+
processScripts(matches, container, defaultMenuPath, setupOptions);
|
|
248
315
|
|
|
249
316
|
container.scripts = container.scripts.sort((a, b) => {
|
|
250
317
|
const aKey = a.menuPath + a.filePath;
|
|
@@ -254,15 +321,19 @@ function generateInstallerFiles(workdir, options) {
|
|
|
254
321
|
|
|
255
322
|
const installerScriptContent = generateInstallerTemplate(
|
|
256
323
|
JSON.stringify(container.scripts, null, 4),
|
|
257
|
-
|
|
258
|
-
bundleName
|
|
324
|
+
setupOptions
|
|
259
325
|
);
|
|
260
326
|
fs.writeFileSync(path.join(workdir, 'src', 'Setup.dsa.ts'), installerScriptContent);
|
|
261
327
|
|
|
262
328
|
const installPath = path.join(workdir, 'src', 'Install.dsa.ts');
|
|
263
329
|
const uninstallPath = path.join(workdir, 'src', 'Uninstall.dsa.ts');
|
|
264
330
|
if (fs.existsSync(installPath)) fs.unlinkSync(installPath);
|
|
265
|
-
|
|
331
|
+
|
|
332
|
+
if (shortcutData.shortcuts && shortcutData.shortcuts.length > 0) {
|
|
333
|
+
fs.writeFileSync(path.join(workdir, 'src', 'Uninstall.dsa.ts'), generateUninstallTemplate(setupOptions));
|
|
334
|
+
} else if (fs.existsSync(uninstallPath)) {
|
|
335
|
+
fs.unlinkSync(uninstallPath);
|
|
336
|
+
}
|
|
266
337
|
}
|
|
267
338
|
|
|
268
339
|
if (require.main === module) {
|
package/package.json
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dazscript-framework",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.2",
|
|
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","shortcutBackupPath":"DazScriptFramework/examples/Installer/keyboard-shortcuts-backup.json"});
|
|
@@ -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
|
+
})
|