retold 4.0.1 → 4.0.3
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/.claude/settings.local.json +38 -1
- package/README.md +92 -2
- package/docs/README.md +7 -6
- package/docs/_sidebar.md +36 -21
- package/docs/_topbar.md +2 -2
- package/docs/architecture/comprehensions.md +282 -0
- package/docs/architecture/fluid-models.md +355 -0
- package/docs/architecture/module-architecture.md +234 -0
- package/docs/{modules.md → architecture/modules.md} +25 -22
- package/docs/cover.md +2 -2
- package/docs/css/docuserve.css +6 -6
- package/docs/examples/examples.md +71 -0
- package/docs/examples/todolist/todo-list-cli-client.md +178 -0
- package/docs/examples/todolist/todo-list-console-client.md +152 -0
- package/docs/examples/todolist/todo-list-model.md +114 -0
- package/docs/examples/todolist/todo-list-server.md +128 -0
- package/docs/examples/todolist/todo-list-web-client.md +177 -0
- package/docs/examples/todolist/todo-list.md +162 -0
- package/docs/getting-started.md +8 -7
- package/docs/index.html +4 -4
- package/docs/{meadow.md → modules/meadow.md} +4 -6
- package/docs/{orator.md → modules/orator.md} +1 -0
- package/docs/{pict.md → modules/pict.md} +30 -8
- package/docs/{utility.md → modules/utility.md} +0 -9
- package/docs/retold-catalog.json +1792 -231
- package/docs/retold-keyword-index.json +136439 -64616
- package/examples/todo-list/Dockerfile +45 -0
- package/examples/todo-list/README.md +394 -0
- package/examples/todo-list/cli-client/package-lock.json +418 -0
- package/examples/todo-list/cli-client/package.json +19 -0
- package/examples/todo-list/cli-client/source/TodoCLI-CLIProgram.js +30 -0
- package/examples/todo-list/cli-client/source/TodoCLI-Run.js +3 -0
- package/examples/todo-list/cli-client/source/commands/add/TodoCLI-Command-Add.js +74 -0
- package/examples/todo-list/cli-client/source/commands/complete/TodoCLI-Command-Complete.js +84 -0
- package/examples/todo-list/cli-client/source/commands/list/TodoCLI-Command-List.js +110 -0
- package/examples/todo-list/cli-client/source/commands/remove/TodoCLI-Command-Remove.js +49 -0
- package/examples/todo-list/cli-client/source/services/TodoCLI-Service-API.js +92 -0
- package/examples/todo-list/console-client/console-client.cjs +913 -0
- package/examples/todo-list/console-client/package-lock.json +426 -0
- package/examples/todo-list/console-client/package.json +19 -0
- package/examples/todo-list/console-client/views/PictView-TUI-Header.cjs +43 -0
- package/examples/todo-list/console-client/views/PictView-TUI-Layout.cjs +58 -0
- package/examples/todo-list/console-client/views/PictView-TUI-StatusBar.cjs +41 -0
- package/examples/todo-list/console-client/views/PictView-TUI-TaskList.cjs +104 -0
- package/examples/todo-list/docker-motd.sh +36 -0
- package/examples/todo-list/docker-run.sh +2 -0
- package/examples/todo-list/docker-shell.sh +2 -0
- package/examples/todo-list/model/MeadowSchema-Task.json +152 -0
- package/examples/todo-list/model/Task-Compiled.json +25 -0
- package/examples/todo-list/model/Task.mddl +15 -0
- package/examples/todo-list/model/data/seeded_todo_events.csv +1001 -0
- package/examples/todo-list/server/database-initialization-service.cjs +273 -0
- package/examples/todo-list/server/package-lock.json +6113 -0
- package/examples/todo-list/server/package.json +19 -0
- package/examples/todo-list/server/server.cjs +138 -0
- package/examples/todo-list/web-client/css/todolist-theme.css +235 -0
- package/examples/todo-list/web-client/generate-build-config.cjs +18 -0
- package/examples/todo-list/web-client/html/index.html +18 -0
- package/examples/todo-list/web-client/package-lock.json +12030 -0
- package/examples/todo-list/web-client/package.json +43 -0
- package/examples/todo-list/web-client/source/TodoList-Application-Config.json +12 -0
- package/examples/todo-list/web-client/source/TodoList-Application.cjs +383 -0
- package/examples/todo-list/web-client/source/providers/Provider-TaskData.cjs +243 -0
- package/examples/todo-list/web-client/source/providers/Router-Config.json +32 -0
- package/examples/todo-list/web-client/source/views/View-Layout.cjs +75 -0
- package/examples/todo-list/web-client/source/views/View-TaskForm.cjs +87 -0
- package/examples/todo-list/web-client/source/views/View-TaskList.cjs +127 -0
- package/examples/todo-list/web-client/source/views/calendar/View-MonthView.cjs +293 -0
- package/examples/todo-list/web-client/source/views/calendar/View-WeekView.cjs +149 -0
- package/examples/todo-list/web-client/source/views/calendar/View-YearView.cjs +226 -0
- package/modules/Include-Retold-Module-List.sh +2 -2
- package/package.json +5 -5
- package/docs/js/pict.min.js +0 -12
- package/docs/js/pict.min.js.map +0 -1
- package/docs/pict-docuserve.min.js +0 -58
- package/docs/pict-docuserve.min.js.map +0 -1
- /package/docs/{architecture.md → architecture/architecture.md} +0 -0
- /package/docs/{fable.md → modules/fable.md} +0 -0
|
@@ -0,0 +1,913 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Retold Todo List -- Console Client
|
|
4
|
+
*
|
|
5
|
+
* Demonstrates pict-terminalui + blessed for a TUI that connects
|
|
6
|
+
* to the same API server as the web client.
|
|
7
|
+
*
|
|
8
|
+
* Requires the server to be running: cd ../server && npm start
|
|
9
|
+
*
|
|
10
|
+
* Run: node console-client.cjs
|
|
11
|
+
* Quit: q or Ctrl-C
|
|
12
|
+
*
|
|
13
|
+
* Keys:
|
|
14
|
+
* Up/Down Navigate task selection
|
|
15
|
+
* Enter View selected task detail
|
|
16
|
+
* E Edit selected task
|
|
17
|
+
* A Add new task
|
|
18
|
+
* D Delete selected task
|
|
19
|
+
* S Sort order picker
|
|
20
|
+
* / Search / filter tasks
|
|
21
|
+
* R Refresh task list
|
|
22
|
+
* Q Quit
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
// Suppress blessed's Setulc stderr noise before anything loads
|
|
26
|
+
const _origStderrWrite = process.stderr.write;
|
|
27
|
+
process.stderr.write = function (pChunk)
|
|
28
|
+
{
|
|
29
|
+
if (typeof pChunk === 'string' && pChunk.indexOf('Setulc') !== -1)
|
|
30
|
+
{
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
return _origStderrWrite.apply(process.stderr, arguments);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const libHttp = require('http');
|
|
37
|
+
const blessed = require('blessed');
|
|
38
|
+
const libPict = require('pict');
|
|
39
|
+
const libPictApplication = require('pict-application');
|
|
40
|
+
|
|
41
|
+
const libPictTerminalUI = require('pict-terminalui');
|
|
42
|
+
|
|
43
|
+
// Views
|
|
44
|
+
const libViewLayout = require('./views/PictView-TUI-Layout.cjs');
|
|
45
|
+
const libViewHeader = require('./views/PictView-TUI-Header.cjs');
|
|
46
|
+
const libViewTaskList = require('./views/PictView-TUI-TaskList.cjs');
|
|
47
|
+
const libViewStatusBar = require('./views/PictView-TUI-StatusBar.cjs');
|
|
48
|
+
|
|
49
|
+
const API_BASE = 'http://localhost:8086';
|
|
50
|
+
|
|
51
|
+
// ─────────────────────────────────────────────
|
|
52
|
+
// Sort options available in the sort picker
|
|
53
|
+
// ─────────────────────────────────────────────
|
|
54
|
+
const SORT_OPTIONS =
|
|
55
|
+
[
|
|
56
|
+
{ Label: 'Due Date (newest first)', Column: 'DueDate', Direction: 'DESC' },
|
|
57
|
+
{ Label: 'Due Date (oldest first)', Column: 'DueDate', Direction: 'ASC' },
|
|
58
|
+
{ Label: 'Name (A-Z)', Column: 'Name', Direction: 'ASC' },
|
|
59
|
+
{ Label: 'Name (Z-A)', Column: 'Name', Direction: 'DESC' },
|
|
60
|
+
{ Label: 'Status (A-Z)', Column: 'Status', Direction: 'ASC' },
|
|
61
|
+
{ Label: 'Status (Z-A)', Column: 'Status', Direction: 'DESC' },
|
|
62
|
+
{ Label: 'Hours (most first)', Column: 'LengthInHours', Direction: 'DESC' },
|
|
63
|
+
{ Label: 'Hours (least first)', Column: 'LengthInHours', Direction: 'ASC' },
|
|
64
|
+
{ Label: 'Recently added', Column: 'IDTask', Direction: 'DESC' },
|
|
65
|
+
{ Label: 'Oldest added', Column: 'IDTask', Direction: 'ASC' }
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
// ─────────────────────────────────────────────
|
|
69
|
+
// HTTP helper (uses Node.js http module)
|
|
70
|
+
// ─────────────────────────────────────────────
|
|
71
|
+
function httpRequest(pMethod, pPath, pBody, fCallback)
|
|
72
|
+
{
|
|
73
|
+
let tmpURL = new URL(pPath, API_BASE);
|
|
74
|
+
let tmpOptions =
|
|
75
|
+
{
|
|
76
|
+
method: pMethod,
|
|
77
|
+
hostname: tmpURL.hostname,
|
|
78
|
+
port: tmpURL.port,
|
|
79
|
+
path: tmpURL.pathname + tmpURL.search,
|
|
80
|
+
headers: { 'Content-Type': 'application/json' }
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
let tmpReq = libHttp.request(tmpOptions,
|
|
84
|
+
(pResponse) =>
|
|
85
|
+
{
|
|
86
|
+
let tmpData = '';
|
|
87
|
+
pResponse.on('data', (pChunk) => { tmpData += pChunk; });
|
|
88
|
+
pResponse.on('end', () =>
|
|
89
|
+
{
|
|
90
|
+
try
|
|
91
|
+
{
|
|
92
|
+
let tmpParsed = JSON.parse(tmpData);
|
|
93
|
+
return fCallback(null, tmpParsed);
|
|
94
|
+
}
|
|
95
|
+
catch (pParseError)
|
|
96
|
+
{
|
|
97
|
+
return fCallback(pParseError);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
tmpReq.on('error', (pError) => { return fCallback(pError); });
|
|
103
|
+
|
|
104
|
+
if (pBody)
|
|
105
|
+
{
|
|
106
|
+
tmpReq.write(JSON.stringify(pBody));
|
|
107
|
+
}
|
|
108
|
+
tmpReq.end();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ─────────────────────────────────────────────
|
|
112
|
+
// Application class
|
|
113
|
+
// ─────────────────────────────────────────────
|
|
114
|
+
class TodoListConsoleApplication extends libPictApplication
|
|
115
|
+
{
|
|
116
|
+
constructor(pFable, pOptions, pServiceHash)
|
|
117
|
+
{
|
|
118
|
+
super(pFable, pOptions, pServiceHash);
|
|
119
|
+
|
|
120
|
+
this.terminalUI = null;
|
|
121
|
+
this._screen = null;
|
|
122
|
+
this._contentBox = null;
|
|
123
|
+
|
|
124
|
+
// Track whether a modal is currently open so we don't double-open
|
|
125
|
+
this._modalOpen = false;
|
|
126
|
+
|
|
127
|
+
// Add views
|
|
128
|
+
this.pict.addView('TUI-Layout', libViewLayout.default_configuration, libViewLayout);
|
|
129
|
+
this.pict.addView('TUI-Header', libViewHeader.default_configuration, libViewHeader);
|
|
130
|
+
this.pict.addView('TUI-TaskList', libViewTaskList.default_configuration, libViewTaskList);
|
|
131
|
+
this.pict.addView('TUI-StatusBar', libViewStatusBar.default_configuration, libViewStatusBar);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
onAfterInitializeAsync(fCallback)
|
|
135
|
+
{
|
|
136
|
+
// Initialize shared application state
|
|
137
|
+
this.pict.AppData.TodoList =
|
|
138
|
+
{
|
|
139
|
+
Tasks: [],
|
|
140
|
+
SelectedIndex: 0,
|
|
141
|
+
StatusMessage: 'Loading tasks...',
|
|
142
|
+
TaskListDisplay: '',
|
|
143
|
+
|
|
144
|
+
// List state drives server-side sort and search
|
|
145
|
+
ListState:
|
|
146
|
+
{
|
|
147
|
+
SortColumn: 'DueDate',
|
|
148
|
+
SortDirection: 'DESC',
|
|
149
|
+
SearchText: ''
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// Create the terminal UI environment
|
|
154
|
+
this.terminalUI = new libPictTerminalUI(this.pict,
|
|
155
|
+
{
|
|
156
|
+
Title: 'Todo List Console Client'
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Create the blessed screen
|
|
160
|
+
this._screen = this.terminalUI.createScreen();
|
|
161
|
+
|
|
162
|
+
// Build the blessed widget layout
|
|
163
|
+
this._createBlessedLayout(this._screen);
|
|
164
|
+
|
|
165
|
+
// Bind navigation keys
|
|
166
|
+
this._bindNavigation(this._screen);
|
|
167
|
+
|
|
168
|
+
// Load tasks from the API
|
|
169
|
+
this._loadTasks(
|
|
170
|
+
() =>
|
|
171
|
+
{
|
|
172
|
+
// Render the layout view (which triggers child view renders)
|
|
173
|
+
this.pict.views['TUI-Layout'].render();
|
|
174
|
+
|
|
175
|
+
// Do the initial blessed screen render
|
|
176
|
+
this._screen.render();
|
|
177
|
+
|
|
178
|
+
return super.onAfterInitializeAsync(fCallback);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Create the blessed widget layout and register widgets.
|
|
184
|
+
*/
|
|
185
|
+
_createBlessedLayout(pScreen)
|
|
186
|
+
{
|
|
187
|
+
// Application container
|
|
188
|
+
let tmpAppContainer = blessed.box(
|
|
189
|
+
{
|
|
190
|
+
parent: pScreen,
|
|
191
|
+
top: 0,
|
|
192
|
+
left: 0,
|
|
193
|
+
width: '100%',
|
|
194
|
+
height: '100%'
|
|
195
|
+
});
|
|
196
|
+
this.terminalUI.registerWidget('#TUI-Application-Container', tmpAppContainer);
|
|
197
|
+
|
|
198
|
+
// Header bar
|
|
199
|
+
let tmpHeader = blessed.box(
|
|
200
|
+
{
|
|
201
|
+
parent: pScreen,
|
|
202
|
+
top: 0,
|
|
203
|
+
left: 0,
|
|
204
|
+
width: '100%',
|
|
205
|
+
height: 3,
|
|
206
|
+
tags: true,
|
|
207
|
+
style:
|
|
208
|
+
{
|
|
209
|
+
fg: 'white',
|
|
210
|
+
bg: 'blue',
|
|
211
|
+
bold: true
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
this.terminalUI.registerWidget('#TUI-Header', tmpHeader);
|
|
215
|
+
|
|
216
|
+
// Main content area
|
|
217
|
+
this._contentBox = blessed.box(
|
|
218
|
+
{
|
|
219
|
+
parent: pScreen,
|
|
220
|
+
top: 3,
|
|
221
|
+
left: 0,
|
|
222
|
+
width: '100%',
|
|
223
|
+
bottom: 1,
|
|
224
|
+
tags: true,
|
|
225
|
+
scrollable: true,
|
|
226
|
+
mouse: true,
|
|
227
|
+
keys: true,
|
|
228
|
+
vi: true,
|
|
229
|
+
scrollbar:
|
|
230
|
+
{
|
|
231
|
+
style: { bg: 'green' }
|
|
232
|
+
},
|
|
233
|
+
border:
|
|
234
|
+
{
|
|
235
|
+
type: 'line'
|
|
236
|
+
},
|
|
237
|
+
style:
|
|
238
|
+
{
|
|
239
|
+
border: { fg: 'cyan' }
|
|
240
|
+
},
|
|
241
|
+
label: ' Tasks ',
|
|
242
|
+
padding:
|
|
243
|
+
{
|
|
244
|
+
left: 1,
|
|
245
|
+
right: 1
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
this.terminalUI.registerWidget('#TUI-Content', this._contentBox);
|
|
249
|
+
|
|
250
|
+
// Status bar
|
|
251
|
+
let tmpStatusBar = blessed.box(
|
|
252
|
+
{
|
|
253
|
+
parent: pScreen,
|
|
254
|
+
bottom: 0,
|
|
255
|
+
left: 0,
|
|
256
|
+
width: '100%',
|
|
257
|
+
height: 1,
|
|
258
|
+
tags: true,
|
|
259
|
+
style:
|
|
260
|
+
{
|
|
261
|
+
fg: 'white',
|
|
262
|
+
bg: 'gray'
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
this.terminalUI.registerWidget('#TUI-StatusBar', tmpStatusBar);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Bind keyboard shortcuts.
|
|
270
|
+
*/
|
|
271
|
+
_bindNavigation(pScreen)
|
|
272
|
+
{
|
|
273
|
+
let tmpSelf = this;
|
|
274
|
+
|
|
275
|
+
pScreen.key(['up'],
|
|
276
|
+
() =>
|
|
277
|
+
{
|
|
278
|
+
if (tmpSelf._modalOpen) return;
|
|
279
|
+
if (tmpSelf.pict.AppData.TodoList.SelectedIndex > 0)
|
|
280
|
+
{
|
|
281
|
+
tmpSelf.pict.AppData.TodoList.SelectedIndex--;
|
|
282
|
+
tmpSelf.pict.views['TUI-TaskList'].render();
|
|
283
|
+
tmpSelf._screen.render();
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
pScreen.key(['down'],
|
|
288
|
+
() =>
|
|
289
|
+
{
|
|
290
|
+
if (tmpSelf._modalOpen) return;
|
|
291
|
+
let tmpTasks = tmpSelf.pict.AppData.TodoList.Tasks;
|
|
292
|
+
if (tmpSelf.pict.AppData.TodoList.SelectedIndex < tmpTasks.length - 1)
|
|
293
|
+
{
|
|
294
|
+
tmpSelf.pict.AppData.TodoList.SelectedIndex++;
|
|
295
|
+
tmpSelf.pict.views['TUI-TaskList'].render();
|
|
296
|
+
tmpSelf._screen.render();
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
// Add task
|
|
301
|
+
pScreen.key(['a'],
|
|
302
|
+
() =>
|
|
303
|
+
{
|
|
304
|
+
if (tmpSelf._modalOpen) return;
|
|
305
|
+
tmpSelf._showEditModal(null);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
// View task detail (Enter)
|
|
309
|
+
pScreen.key(['enter'],
|
|
310
|
+
() =>
|
|
311
|
+
{
|
|
312
|
+
if (tmpSelf._modalOpen) return;
|
|
313
|
+
let tmpTasks = tmpSelf.pict.AppData.TodoList.Tasks;
|
|
314
|
+
if (tmpTasks.length > 0)
|
|
315
|
+
{
|
|
316
|
+
let tmpTask = tmpTasks[tmpSelf.pict.AppData.TodoList.SelectedIndex];
|
|
317
|
+
tmpSelf._showViewModal(tmpTask);
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
// Edit task
|
|
322
|
+
pScreen.key(['e'],
|
|
323
|
+
() =>
|
|
324
|
+
{
|
|
325
|
+
if (tmpSelf._modalOpen) return;
|
|
326
|
+
let tmpTasks = tmpSelf.pict.AppData.TodoList.Tasks;
|
|
327
|
+
if (tmpTasks.length > 0)
|
|
328
|
+
{
|
|
329
|
+
let tmpTask = tmpTasks[tmpSelf.pict.AppData.TodoList.SelectedIndex];
|
|
330
|
+
tmpSelf._showEditModal(tmpTask);
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// Delete task
|
|
335
|
+
pScreen.key(['d'],
|
|
336
|
+
() =>
|
|
337
|
+
{
|
|
338
|
+
if (tmpSelf._modalOpen) return;
|
|
339
|
+
let tmpTasks = tmpSelf.pict.AppData.TodoList.Tasks;
|
|
340
|
+
if (tmpTasks.length > 0)
|
|
341
|
+
{
|
|
342
|
+
let tmpTask = tmpTasks[tmpSelf.pict.AppData.TodoList.SelectedIndex];
|
|
343
|
+
tmpSelf._deleteTask(tmpTask.IDTask);
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
// Sort picker
|
|
348
|
+
pScreen.key(['s'],
|
|
349
|
+
() =>
|
|
350
|
+
{
|
|
351
|
+
if (tmpSelf._modalOpen) return;
|
|
352
|
+
tmpSelf._showSortModal();
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
// Search / filter
|
|
356
|
+
pScreen.key(['/'],
|
|
357
|
+
() =>
|
|
358
|
+
{
|
|
359
|
+
if (tmpSelf._modalOpen) return;
|
|
360
|
+
tmpSelf._showSearchModal();
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
// Refresh
|
|
364
|
+
pScreen.key(['r'],
|
|
365
|
+
() =>
|
|
366
|
+
{
|
|
367
|
+
if (tmpSelf._modalOpen) return;
|
|
368
|
+
tmpSelf._setStatus('Refreshing...');
|
|
369
|
+
tmpSelf._loadTasks(
|
|
370
|
+
() =>
|
|
371
|
+
{
|
|
372
|
+
tmpSelf._setStatus('Refreshed.');
|
|
373
|
+
tmpSelf.pict.views['TUI-TaskList'].render();
|
|
374
|
+
tmpSelf.pict.views['TUI-StatusBar'].render();
|
|
375
|
+
tmpSelf._screen.render();
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
// Quit
|
|
380
|
+
pScreen.key(['q', 'C-c'],
|
|
381
|
+
() =>
|
|
382
|
+
{
|
|
383
|
+
if (tmpSelf._modalOpen) return;
|
|
384
|
+
process.exit(0);
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// ─────────────────────────────────────────
|
|
389
|
+
// API methods
|
|
390
|
+
// ─────────────────────────────────────────
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Build the FilteredTo URL path based on the current ListState.
|
|
394
|
+
*/
|
|
395
|
+
_buildFilteredPath()
|
|
396
|
+
{
|
|
397
|
+
let tmpState = this.pict.AppData.TodoList.ListState;
|
|
398
|
+
let tmpFilter = 'FSF~' + tmpState.SortColumn + '~' + tmpState.SortDirection + '~0';
|
|
399
|
+
|
|
400
|
+
if (tmpState.SearchText)
|
|
401
|
+
{
|
|
402
|
+
let tmpSearchEncoded = encodeURIComponent('%' + tmpState.SearchText + '%');
|
|
403
|
+
tmpFilter = 'FBV~Name~LK~' + tmpSearchEncoded
|
|
404
|
+
+ '~FBVOR~Description~LK~' + tmpSearchEncoded
|
|
405
|
+
+ '~' + tmpFilter;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return '/1.0/Tasks/FilteredTo/' + tmpFilter + '/0/250';
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Load tasks from the API using the current sort and filter.
|
|
413
|
+
*/
|
|
414
|
+
_loadTasks(fCallback)
|
|
415
|
+
{
|
|
416
|
+
let tmpSelf = this;
|
|
417
|
+
let tmpPath = tmpSelf._buildFilteredPath();
|
|
418
|
+
|
|
419
|
+
httpRequest('GET', tmpPath, null,
|
|
420
|
+
(pError, pTasks) =>
|
|
421
|
+
{
|
|
422
|
+
if (pError)
|
|
423
|
+
{
|
|
424
|
+
tmpSelf._setStatus('Error: ' + pError.message);
|
|
425
|
+
if (fCallback) return fCallback();
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (Array.isArray(pTasks))
|
|
430
|
+
{
|
|
431
|
+
tmpSelf.pict.AppData.TodoList.Tasks = pTasks;
|
|
432
|
+
// Keep selected index in bounds
|
|
433
|
+
if (tmpSelf.pict.AppData.TodoList.SelectedIndex >= pTasks.length)
|
|
434
|
+
{
|
|
435
|
+
tmpSelf.pict.AppData.TodoList.SelectedIndex = Math.max(0, pTasks.length - 1);
|
|
436
|
+
}
|
|
437
|
+
tmpSelf._setStatus(pTasks.length + ' task(s) loaded.');
|
|
438
|
+
}
|
|
439
|
+
else
|
|
440
|
+
{
|
|
441
|
+
tmpSelf._setStatus('Unexpected response from server.');
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (fCallback) return fCallback();
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Delete a task.
|
|
450
|
+
*/
|
|
451
|
+
_deleteTask(pIDTask)
|
|
452
|
+
{
|
|
453
|
+
let tmpSelf = this;
|
|
454
|
+
tmpSelf._setStatus('Deleting task ' + pIDTask + '...');
|
|
455
|
+
|
|
456
|
+
httpRequest('DELETE', '/1.0/Task/' + pIDTask, null,
|
|
457
|
+
(pError) =>
|
|
458
|
+
{
|
|
459
|
+
if (pError)
|
|
460
|
+
{
|
|
461
|
+
tmpSelf._setStatus('Delete error: ' + pError.message);
|
|
462
|
+
tmpSelf.pict.views['TUI-StatusBar'].render();
|
|
463
|
+
tmpSelf._screen.render();
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
tmpSelf._loadTasks(
|
|
467
|
+
() =>
|
|
468
|
+
{
|
|
469
|
+
tmpSelf._setStatus('Task deleted.');
|
|
470
|
+
tmpSelf.pict.views['TUI-TaskList'].render();
|
|
471
|
+
tmpSelf.pict.views['TUI-StatusBar'].render();
|
|
472
|
+
tmpSelf._screen.render();
|
|
473
|
+
});
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Save a task (create or update).
|
|
479
|
+
*/
|
|
480
|
+
_saveTask(pExistingTask, pFieldData)
|
|
481
|
+
{
|
|
482
|
+
let tmpSelf = this;
|
|
483
|
+
let tmpIsEdit = !!pExistingTask;
|
|
484
|
+
|
|
485
|
+
let tmpTaskData =
|
|
486
|
+
{
|
|
487
|
+
Name: pFieldData.Name,
|
|
488
|
+
Description: pFieldData.Description,
|
|
489
|
+
DueDate: pFieldData.DueDate,
|
|
490
|
+
LengthInHours: parseFloat(pFieldData.LengthInHours) || 0,
|
|
491
|
+
Status: pFieldData.Status
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
if (tmpIsEdit)
|
|
495
|
+
{
|
|
496
|
+
tmpTaskData.IDTask = pExistingTask.IDTask;
|
|
497
|
+
tmpSelf._setStatus('Updating task ' + tmpTaskData.IDTask + '...');
|
|
498
|
+
httpRequest('PUT', '/1.0/Task', tmpTaskData,
|
|
499
|
+
(pError) =>
|
|
500
|
+
{
|
|
501
|
+
if (pError)
|
|
502
|
+
{
|
|
503
|
+
tmpSelf._setStatus('Update error: ' + pError.message);
|
|
504
|
+
}
|
|
505
|
+
else
|
|
506
|
+
{
|
|
507
|
+
tmpSelf._setStatus('Task updated.');
|
|
508
|
+
}
|
|
509
|
+
tmpSelf._loadTasks(
|
|
510
|
+
() =>
|
|
511
|
+
{
|
|
512
|
+
tmpSelf.pict.views['TUI-TaskList'].render();
|
|
513
|
+
tmpSelf.pict.views['TUI-StatusBar'].render();
|
|
514
|
+
tmpSelf._screen.render();
|
|
515
|
+
});
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
else
|
|
519
|
+
{
|
|
520
|
+
tmpSelf._setStatus('Creating task...');
|
|
521
|
+
httpRequest('POST', '/1.0/Task', tmpTaskData,
|
|
522
|
+
(pError) =>
|
|
523
|
+
{
|
|
524
|
+
if (pError)
|
|
525
|
+
{
|
|
526
|
+
tmpSelf._setStatus('Create error: ' + pError.message);
|
|
527
|
+
}
|
|
528
|
+
else
|
|
529
|
+
{
|
|
530
|
+
tmpSelf._setStatus('Task created.');
|
|
531
|
+
}
|
|
532
|
+
tmpSelf._loadTasks(
|
|
533
|
+
() =>
|
|
534
|
+
{
|
|
535
|
+
tmpSelf.pict.views['TUI-TaskList'].render();
|
|
536
|
+
tmpSelf.pict.views['TUI-StatusBar'].render();
|
|
537
|
+
tmpSelf._screen.render();
|
|
538
|
+
});
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// ─────────────────────────────────────────
|
|
544
|
+
// View Modal (read-only detail)
|
|
545
|
+
// ─────────────────────────────────────────
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Show a read-only detail box for a task.
|
|
549
|
+
* Press Escape or Enter to close, E to jump to edit.
|
|
550
|
+
*/
|
|
551
|
+
_showViewModal(pTask)
|
|
552
|
+
{
|
|
553
|
+
let tmpSelf = this;
|
|
554
|
+
tmpSelf._modalOpen = true;
|
|
555
|
+
|
|
556
|
+
let tmpDueDate = (pTask.DueDate || '-');
|
|
557
|
+
if (tmpDueDate.length > 10)
|
|
558
|
+
{
|
|
559
|
+
tmpDueDate = tmpDueDate.substring(0, 10);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
let tmpContent = [
|
|
563
|
+
'{bold}' + (pTask.Name || '(untitled)') + '{/bold}',
|
|
564
|
+
'',
|
|
565
|
+
'{bold}Status:{/bold} ' + (pTask.Status || '-'),
|
|
566
|
+
'{bold}Due Date:{/bold} ' + tmpDueDate,
|
|
567
|
+
'{bold}Hours:{/bold} ' + (pTask.LengthInHours || 0),
|
|
568
|
+
'',
|
|
569
|
+
'{bold}Description:{/bold}',
|
|
570
|
+
(pTask.Description || '(none)'),
|
|
571
|
+
'',
|
|
572
|
+
'{center}{gray-fg}[Esc] Close [E] Edit{/gray-fg}{/center}'
|
|
573
|
+
].join('\n');
|
|
574
|
+
|
|
575
|
+
let tmpBox = blessed.box(
|
|
576
|
+
{
|
|
577
|
+
parent: tmpSelf._screen,
|
|
578
|
+
top: 'center',
|
|
579
|
+
left: 'center',
|
|
580
|
+
width: '70%',
|
|
581
|
+
height: 'shrink',
|
|
582
|
+
padding: 1,
|
|
583
|
+
border: { type: 'line' },
|
|
584
|
+
style:
|
|
585
|
+
{
|
|
586
|
+
border: { fg: 'cyan' },
|
|
587
|
+
bg: 'black',
|
|
588
|
+
fg: 'white'
|
|
589
|
+
},
|
|
590
|
+
tags: true,
|
|
591
|
+
keys: true,
|
|
592
|
+
label: ' Task Detail ',
|
|
593
|
+
content: tmpContent
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
tmpBox.focus();
|
|
597
|
+
|
|
598
|
+
tmpBox.key(['escape', 'q'],
|
|
599
|
+
() =>
|
|
600
|
+
{
|
|
601
|
+
tmpBox.destroy();
|
|
602
|
+
tmpSelf._modalOpen = false;
|
|
603
|
+
tmpSelf._screen.render();
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
tmpBox.key(['e'],
|
|
607
|
+
() =>
|
|
608
|
+
{
|
|
609
|
+
tmpBox.destroy();
|
|
610
|
+
tmpSelf._modalOpen = false;
|
|
611
|
+
tmpSelf._showEditModal(pTask);
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
tmpSelf._screen.render();
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// ─────────────────────────────────────────
|
|
618
|
+
// Edit Modal (sequential field prompts)
|
|
619
|
+
// ─────────────────────────────────────────
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* Prompt the user for task fields (add or edit).
|
|
623
|
+
*
|
|
624
|
+
* @param {Object|null} pExistingTask - If non-null, edit this task. Otherwise create new.
|
|
625
|
+
*/
|
|
626
|
+
_showEditModal(pExistingTask)
|
|
627
|
+
{
|
|
628
|
+
let tmpSelf = this;
|
|
629
|
+
tmpSelf._modalOpen = true;
|
|
630
|
+
|
|
631
|
+
let tmpIsEdit = !!pExistingTask;
|
|
632
|
+
let tmpDefaults =
|
|
633
|
+
{
|
|
634
|
+
Name: tmpIsEdit ? (pExistingTask.Name || '') : '',
|
|
635
|
+
Description: tmpIsEdit ? (pExistingTask.Description || '') : '',
|
|
636
|
+
DueDate: tmpIsEdit ? (pExistingTask.DueDate || '') : '',
|
|
637
|
+
LengthInHours: tmpIsEdit ? (pExistingTask.LengthInHours || 0) : 0,
|
|
638
|
+
Status: tmpIsEdit ? (pExistingTask.Status || 'Pending') : 'Pending'
|
|
639
|
+
};
|
|
640
|
+
|
|
641
|
+
// Truncate DueDate to just the date portion for editing
|
|
642
|
+
if (tmpDefaults.DueDate.length > 10)
|
|
643
|
+
{
|
|
644
|
+
tmpDefaults.DueDate = tmpDefaults.DueDate.substring(0, 10);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
let tmpFields = ['Name', 'Description', 'DueDate', 'LengthInHours', 'Status'];
|
|
648
|
+
let tmpResults = {};
|
|
649
|
+
let tmpFieldIndex = 0;
|
|
650
|
+
|
|
651
|
+
let tmpPrompt = blessed.prompt(
|
|
652
|
+
{
|
|
653
|
+
parent: tmpSelf._screen,
|
|
654
|
+
top: 'center',
|
|
655
|
+
left: 'center',
|
|
656
|
+
width: '60%',
|
|
657
|
+
height: 'shrink',
|
|
658
|
+
border: { type: 'line' },
|
|
659
|
+
style:
|
|
660
|
+
{
|
|
661
|
+
border: { fg: 'yellow' },
|
|
662
|
+
bg: 'black',
|
|
663
|
+
fg: 'white'
|
|
664
|
+
},
|
|
665
|
+
tags: true,
|
|
666
|
+
keys: true,
|
|
667
|
+
vi: true,
|
|
668
|
+
label: tmpIsEdit ? ' Edit Task ' : ' New Task '
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
function promptNextField()
|
|
672
|
+
{
|
|
673
|
+
if (tmpFieldIndex >= tmpFields.length)
|
|
674
|
+
{
|
|
675
|
+
// All fields collected -- save the task
|
|
676
|
+
tmpPrompt.destroy();
|
|
677
|
+
tmpSelf._modalOpen = false;
|
|
678
|
+
tmpSelf._saveTask(pExistingTask, tmpResults);
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
let tmpFieldName = tmpFields[tmpFieldIndex];
|
|
683
|
+
let tmpDefault = String(tmpDefaults[tmpFieldName]);
|
|
684
|
+
let tmpLabel = tmpFieldName + ' [' + tmpDefault + ']:';
|
|
685
|
+
|
|
686
|
+
tmpPrompt.input(tmpLabel, tmpDefault,
|
|
687
|
+
(pError, pValue) =>
|
|
688
|
+
{
|
|
689
|
+
if (pError || pValue === null || pValue === undefined)
|
|
690
|
+
{
|
|
691
|
+
// User cancelled (Escape)
|
|
692
|
+
tmpPrompt.destroy();
|
|
693
|
+
tmpSelf._modalOpen = false;
|
|
694
|
+
tmpSelf._screen.render();
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
tmpResults[tmpFieldName] = pValue;
|
|
698
|
+
tmpFieldIndex++;
|
|
699
|
+
promptNextField();
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
promptNextField();
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// ─────────────────────────────────────────
|
|
707
|
+
// Sort Modal
|
|
708
|
+
// ─────────────────────────────────────────
|
|
709
|
+
|
|
710
|
+
/**
|
|
711
|
+
* Show a list picker for sort order.
|
|
712
|
+
*/
|
|
713
|
+
_showSortModal()
|
|
714
|
+
{
|
|
715
|
+
let tmpSelf = this;
|
|
716
|
+
tmpSelf._modalOpen = true;
|
|
717
|
+
|
|
718
|
+
let tmpLabels = [];
|
|
719
|
+
for (let i = 0; i < SORT_OPTIONS.length; i++)
|
|
720
|
+
{
|
|
721
|
+
tmpLabels.push(SORT_OPTIONS[i].Label);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
let tmpList = blessed.list(
|
|
725
|
+
{
|
|
726
|
+
parent: tmpSelf._screen,
|
|
727
|
+
top: 'center',
|
|
728
|
+
left: 'center',
|
|
729
|
+
width: '50%',
|
|
730
|
+
height: SORT_OPTIONS.length + 2,
|
|
731
|
+
border: { type: 'line' },
|
|
732
|
+
style:
|
|
733
|
+
{
|
|
734
|
+
border: { fg: 'green' },
|
|
735
|
+
bg: 'black',
|
|
736
|
+
fg: 'white',
|
|
737
|
+
selected:
|
|
738
|
+
{
|
|
739
|
+
bg: 'green',
|
|
740
|
+
fg: 'black',
|
|
741
|
+
bold: true
|
|
742
|
+
}
|
|
743
|
+
},
|
|
744
|
+
keys: true,
|
|
745
|
+
vi: true,
|
|
746
|
+
mouse: true,
|
|
747
|
+
tags: true,
|
|
748
|
+
label: ' Sort Order ',
|
|
749
|
+
items: tmpLabels
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
// Pre-select the current sort
|
|
753
|
+
let tmpState = tmpSelf.pict.AppData.TodoList.ListState;
|
|
754
|
+
for (let i = 0; i < SORT_OPTIONS.length; i++)
|
|
755
|
+
{
|
|
756
|
+
if (SORT_OPTIONS[i].Column === tmpState.SortColumn && SORT_OPTIONS[i].Direction === tmpState.SortDirection)
|
|
757
|
+
{
|
|
758
|
+
tmpList.select(i);
|
|
759
|
+
break;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
tmpList.focus();
|
|
764
|
+
|
|
765
|
+
tmpList.on('select',
|
|
766
|
+
(pItem, pIndex) =>
|
|
767
|
+
{
|
|
768
|
+
let tmpOption = SORT_OPTIONS[pIndex];
|
|
769
|
+
tmpSelf.pict.AppData.TodoList.ListState.SortColumn = tmpOption.Column;
|
|
770
|
+
tmpSelf.pict.AppData.TodoList.ListState.SortDirection = tmpOption.Direction;
|
|
771
|
+
|
|
772
|
+
tmpList.destroy();
|
|
773
|
+
tmpSelf._modalOpen = false;
|
|
774
|
+
tmpSelf._setStatus('Sorting by ' + tmpOption.Label + '...');
|
|
775
|
+
|
|
776
|
+
tmpSelf._loadTasks(
|
|
777
|
+
() =>
|
|
778
|
+
{
|
|
779
|
+
tmpSelf.pict.views['TUI-TaskList'].render();
|
|
780
|
+
tmpSelf.pict.views['TUI-StatusBar'].render();
|
|
781
|
+
tmpSelf._screen.render();
|
|
782
|
+
});
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
tmpList.key(['escape'],
|
|
786
|
+
() =>
|
|
787
|
+
{
|
|
788
|
+
tmpList.destroy();
|
|
789
|
+
tmpSelf._modalOpen = false;
|
|
790
|
+
tmpSelf._screen.render();
|
|
791
|
+
});
|
|
792
|
+
|
|
793
|
+
tmpSelf._screen.render();
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// ─────────────────────────────────────────
|
|
797
|
+
// Search Modal
|
|
798
|
+
// ─────────────────────────────────────────
|
|
799
|
+
|
|
800
|
+
/**
|
|
801
|
+
* Prompt for a search string, then reload the filtered list.
|
|
802
|
+
* An empty string clears the filter.
|
|
803
|
+
*/
|
|
804
|
+
_showSearchModal()
|
|
805
|
+
{
|
|
806
|
+
let tmpSelf = this;
|
|
807
|
+
tmpSelf._modalOpen = true;
|
|
808
|
+
|
|
809
|
+
let tmpCurrentSearch = tmpSelf.pict.AppData.TodoList.ListState.SearchText || '';
|
|
810
|
+
|
|
811
|
+
let tmpPrompt = blessed.prompt(
|
|
812
|
+
{
|
|
813
|
+
parent: tmpSelf._screen,
|
|
814
|
+
top: 'center',
|
|
815
|
+
left: 'center',
|
|
816
|
+
width: '60%',
|
|
817
|
+
height: 'shrink',
|
|
818
|
+
border: { type: 'line' },
|
|
819
|
+
style:
|
|
820
|
+
{
|
|
821
|
+
border: { fg: 'magenta' },
|
|
822
|
+
bg: 'black',
|
|
823
|
+
fg: 'white'
|
|
824
|
+
},
|
|
825
|
+
tags: true,
|
|
826
|
+
keys: true,
|
|
827
|
+
vi: true,
|
|
828
|
+
label: ' Search Tasks '
|
|
829
|
+
});
|
|
830
|
+
|
|
831
|
+
let tmpLabel = 'Search name/description (empty to clear):';
|
|
832
|
+
tmpPrompt.input(tmpLabel, tmpCurrentSearch,
|
|
833
|
+
(pError, pValue) =>
|
|
834
|
+
{
|
|
835
|
+
tmpPrompt.destroy();
|
|
836
|
+
tmpSelf._modalOpen = false;
|
|
837
|
+
|
|
838
|
+
if (pError || pValue === null || pValue === undefined)
|
|
839
|
+
{
|
|
840
|
+
// User cancelled
|
|
841
|
+
tmpSelf._screen.render();
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
tmpSelf.pict.AppData.TodoList.ListState.SearchText = pValue;
|
|
846
|
+
tmpSelf.pict.AppData.TodoList.SelectedIndex = 0;
|
|
847
|
+
|
|
848
|
+
if (pValue)
|
|
849
|
+
{
|
|
850
|
+
tmpSelf._setStatus('Searching for "' + pValue + '"...');
|
|
851
|
+
}
|
|
852
|
+
else
|
|
853
|
+
{
|
|
854
|
+
tmpSelf._setStatus('Filter cleared.');
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
tmpSelf._loadTasks(
|
|
858
|
+
() =>
|
|
859
|
+
{
|
|
860
|
+
tmpSelf.pict.views['TUI-TaskList'].render();
|
|
861
|
+
tmpSelf.pict.views['TUI-StatusBar'].render();
|
|
862
|
+
tmpSelf._screen.render();
|
|
863
|
+
});
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
// ─────────────────────────────────────────
|
|
868
|
+
// Helpers
|
|
869
|
+
// ─────────────────────────────────────────
|
|
870
|
+
|
|
871
|
+
/**
|
|
872
|
+
* Update the status message.
|
|
873
|
+
*/
|
|
874
|
+
_setStatus(pMessage)
|
|
875
|
+
{
|
|
876
|
+
this.pict.AppData.TodoList.StatusMessage = pMessage;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
/**
|
|
880
|
+
* No-op for layout template expression.
|
|
881
|
+
*/
|
|
882
|
+
renderLayoutWidgets()
|
|
883
|
+
{
|
|
884
|
+
return '';
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
// ─────────────────────────────────────────────
|
|
889
|
+
// Bootstrap
|
|
890
|
+
// ─────────────────────────────────────────────
|
|
891
|
+
let _Pict = new libPict(
|
|
892
|
+
{
|
|
893
|
+
Product: 'TodoListConsole',
|
|
894
|
+
LogNoisiness: 0
|
|
895
|
+
});
|
|
896
|
+
|
|
897
|
+
let _App = _Pict.addApplication('TodoList-Console',
|
|
898
|
+
{
|
|
899
|
+
Name: 'TodoList-Console',
|
|
900
|
+
MainViewportViewIdentifier: 'TUI-Layout',
|
|
901
|
+
AutoRenderMainViewportViewAfterInitialize: false,
|
|
902
|
+
AutoSolveAfterInitialize: false
|
|
903
|
+
}, TodoListConsoleApplication);
|
|
904
|
+
|
|
905
|
+
_App.initializeAsync(
|
|
906
|
+
(pError) =>
|
|
907
|
+
{
|
|
908
|
+
if (pError)
|
|
909
|
+
{
|
|
910
|
+
console.error('Application initialization failed:', pError);
|
|
911
|
+
process.exit(1);
|
|
912
|
+
}
|
|
913
|
+
});
|