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.
@@ -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, settingsPath, bundleName) {
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
- settingsPath,
30
- bundleName,
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, settingsPath, bundleName) {
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
- settingsPath,
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, settingsPath, bundleName) {
231
+ function processScripts(paths, container, defaultMenuPath, setupOptions) {
228
232
  paths.forEach((filePath) => {
229
233
  console.log(`Processing ${filePath}`);
230
- processScript(filePath, container, defaultMenuPath, settingsPath, bundleName);
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, settingsPath, bundleName);
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
- settingsPath,
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
- if (fs.existsSync(uninstallPath)) fs.unlinkSync(uninstallPath);
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.2",
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/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","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,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
+ })