jsgui3-server 0.0.147 → 0.0.149
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/.github/workflows/control-scan-manifest-check.yml +31 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-071799b982906680f5fd699d.js +40 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-07352945ad5c92654fcb8b65.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-138a601fadb6191ea314c6fd.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-171f6c381c2cadf2e9fa7087.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-1d973388156b84a04373fac9.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-20e117bc8a10d2cd16234bbe.js +40 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-2b028a82b0e5efddba42425f.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-4518556cd5c7e059e82b22b8.js +40 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-5bac1aa0f213902f718ed74f.js +40 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-5f9996ac7822caf777d92f56.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-60a92c702e65fd9cf748e3ec.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-6164c1f8f738995c541895d2.js +44 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-6718a85eb9e5aa782dd47a05.js +45 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-69e280f14e37aee76a1d4675.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-7570d1b030d44b111ed59c4c.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-7798c9bbd55e510d5039f936.js +42 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-78cd511ea1ef18ecb03d1be5.js +40 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-7d482e0b95bcb5e3c543118b.js +43 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-80e9476d1127c55b40fdb36f.js +40 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-810ced55d5320a3088a05b13.js +40 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-8423565f1a40e329afc8c6cf.js +40 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-900bef783b8cee36506ec282.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-a1a37aff6416fdad74040ddf.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-ad48d5e8eda40f175b4df090.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-aec5a2d963015528c9099462.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-af9d34e0f1722fab9e28c269.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-b818e4015e2f1fe86280b5ab.js +41 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-bcb2541adc70b7aba61768c5.js +44 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-bfe89d2c78ed44f95ed7dd73.js +40 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-c06f04806a1e688e1187110c.js +40 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-c3f3adf904f585afc544b96a.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-d45acb873e1d8e32d5e60f2e.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-db06f132533706f4a0163b8c.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-f660f40d78b135fc8560a862.js +39 -0
- package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-f9dee4ec18a96e09bee06bae.js +39 -0
- package/README.md +85 -3
- package/admin-ui/client.js +213 -0
- package/admin-ui/server.js +104 -0
- package/client/controls/auto-observable.js +207 -0
- package/dev-status.svg +139 -0
- package/docs/api-reference.md +301 -43
- package/docs/books/admin-ui/01-introduction.md +32 -0
- package/docs/books/admin-ui/02-architecture.md +92 -0
- package/docs/books/admin-ui/03-controls.md +194 -0
- package/docs/books/admin-ui/04-implementation-plan.md +62 -0
- package/docs/books/admin-ui/README.md +26 -0
- package/docs/books/jsgui3-bundling-research-book/00-table-of-contents.md +35 -0
- package/docs/books/jsgui3-bundling-research-book/01-pipeline-and-runtime-semantics.md +34 -0
- package/docs/books/jsgui3-bundling-research-book/02-javascript-bundling-core.md +36 -0
- package/docs/books/jsgui3-bundling-research-book/03-style-extraction-and-css-compilation.md +35 -0
- package/docs/books/jsgui3-bundling-research-book/04-static-publishing-and-delivery.md +39 -0
- package/docs/books/jsgui3-bundling-research-book/05-current-limits-and-size-bloat-vectors.md +25 -0
- package/docs/books/jsgui3-bundling-research-book/06-unused-module-elimination-strategy.md +77 -0
- package/docs/books/jsgui3-bundling-research-book/07-jsgui3-html-control-and-mixin-pruning.md +63 -0
- package/docs/books/jsgui3-bundling-research-book/08-test-and-verification-methodology.md +43 -0
- package/docs/books/jsgui3-bundling-research-book/09-roadmap-and-rollout.md +42 -0
- package/docs/books/jsgui3-bundling-research-book/10-further-research-strategies-and-upgrades.md +211 -0
- package/docs/books/jsgui3-bundling-research-book/README.md +35 -0
- package/docs/bundling-system-deep-dive.md +9 -4
- package/docs/comprehensive-documentation.md +49 -18
- package/docs/configuration-reference.md +152 -27
- package/docs/core/README.md +19 -0
- package/docs/core/jsgui3-server-core-book/00-table-of-contents.md +21 -0
- package/docs/core/jsgui3-server-core-book/01-startup-readiness-state-machine.md +41 -0
- package/docs/core/jsgui3-server-core-book/02-resource-abstraction-and-lifecycle.md +92 -0
- package/docs/core/jsgui3-server-core-book/03-resource-pool-and-event-topology.md +47 -0
- package/docs/core/jsgui3-server-core-book/04-sse-publisher-semantics.md +41 -0
- package/docs/core/jsgui3-server-core-book/05-serve-factory-resource-wiring.md +46 -0
- package/docs/core/jsgui3-server-core-book/06-e2e-testing-methodology.md +48 -0
- package/docs/core/jsgui3-server-core-book/07-defect-detection-and-hardening-loop.md +47 -0
- package/docs/publishers-guide.md +59 -4
- package/docs/resources-guide.md +184 -35
- package/docs/simple-server-api-design.md +72 -17
- package/docs/system-architecture.md +18 -14
- package/examples/controls/15) window, observable SSE/server.js +6 -1
- package/examples/controls/19) window, auto observable ui/client.js +125 -0
- package/examples/controls/19) window, auto observable ui/server.js +73 -0
- package/examples/controls/20) window, task manager app/README.md +133 -0
- package/examples/controls/20) window, task manager app/client.js +797 -0
- package/examples/controls/20) window, task manager app/server.js +178 -0
- package/examples/controls/6) window, color_palette/client.js +165 -68
- package/examples/controls/9) window, date picker/client.js +362 -76
- package/examples/controls/9b) window, shared data.model mirrored date pickers/client.js +104 -83
- package/examples/jsgui3-html/06) theming/client.js +22 -1
- package/examples/jsgui3-html/10) binding-debugger/client.js +137 -1
- package/http/responders/static/Static_Route_HTTP_Responder.js +52 -34
- package/lab/experiments/capture-color-controls.js +196 -0
- package/lab/results/screenshots/color-controls/full_page.png +0 -0
- package/lab/results/screenshots/color-controls/section_1_color_grid_12x12.png +0 -0
- package/lab/results/screenshots/color-controls/section_2_color_grid_4x2.png +0 -0
- package/lab/results/screenshots/color-controls/section_3_color_palette.png +0 -0
- package/lab/results/screenshots/color-controls/section_4_palette_comparison.png +0 -0
- package/lab/results/screenshots/color-controls/section_5_raw_swatches.png +0 -0
- package/lab/results/screenshots/color-controls/section_6_optimized_crayola.png +0 -0
- package/lab/results/screenshots/color-controls/section_7_pastel_palette.png +0 -0
- package/lab/results/screenshots/color-controls/section_8_extended_144.png +0 -0
- package/lab/screenshot-utils.js +248 -0
- package/module.js +11 -4
- package/package.json +14 -4
- package/publishers/Publishers.js +4 -3
- package/publishers/helpers/assigners/static-compressed-response-buffers/Single_Control_Webpage_Server_Static_Compressed_Response_Buffers_Assigner.js +5 -5
- package/publishers/http-observable-publisher.js +8 -0
- package/publishers/http-sse-publisher.js +341 -0
- package/publishers/http-webpage-publisher.js +13 -3
- package/publishers/http-webpageorsite-publisher.js +18 -0
- package/resources/process-resource.js +950 -0
- package/resources/processors/bundlers/js/esbuild/Advanced_JS_Bundler_Using_ESBuild.js +164 -46
- package/resources/processors/bundlers/js/esbuild/Core_JS_Non_Minifying_Bundler_Using_ESBuild.js +18 -7
- package/resources/processors/bundlers/js/esbuild/JSGUI3_HTML_Control_Optimizer.js +829 -0
- package/resources/remote-process-resource.js +355 -0
- package/resources/server-resource-pool.js +354 -41
- package/serve-factory.js +441 -259
- package/server.js +161 -16
- package/tests/README.md +66 -4
- package/tests/admin-ui-render.test.js +24 -0
- package/tests/assigners.test.js +56 -40
- package/tests/bundling-default-control-elimination.puppeteer.test.js +260 -0
- package/tests/configuration-validation.test.js +21 -18
- package/tests/content-analysis.test.js +7 -6
- package/tests/control-optimizer-cache-behavior.test.js +52 -0
- package/tests/control-scan-manifest-regression.test.js +144 -0
- package/tests/end-to-end.test.js +15 -14
- package/tests/error-handling.test.js +222 -179
- package/tests/fixtures/bundling-default-button-client.js +37 -0
- package/tests/fixtures/bundling-default-window-client.js +34 -0
- package/tests/fixtures/control_scan_manifest_expectations.json +48 -0
- package/tests/fixtures/resource-monitor-client.js +319 -0
- package/tests/helpers/puppeteer-e2e-harness.js +317 -0
- package/tests/http-sse-publisher.test.js +136 -0
- package/tests/performance.test.js +69 -65
- package/tests/process-resource.test.js +138 -0
- package/tests/publishers.test.js +7 -7
- package/tests/remote-process-resource.test.js +160 -0
- package/tests/sass-controls.e2e.test.js +7 -1
- package/tests/serve-resources.test.js +270 -0
- package/tests/serve.test.js +120 -50
- package/tests/server-resource-pool.test.js +106 -0
- package/tests/small-controls-bundle-size.test.js +252 -0
- package/tests/test-runner.js +13 -1
- package/tests/window-examples.puppeteer.test.js +204 -1
- package/tests/window-resource-integration.puppeteer.test.js +585 -0
- package/tests/temp_invalid.js +0 -7
- package/tests/temp_invalid_utf8.js +0 -1
- package/tests/temp_malformed.js +0 -10
|
@@ -0,0 +1,797 @@
|
|
|
1
|
+
const jsgui = require('jsgui3-client');
|
|
2
|
+
const { controls, Control, mixins } = jsgui;
|
|
3
|
+
const { Select_Options, Button, Text_Field } = controls;
|
|
4
|
+
const Active_HTML_Document = require('../../../controls/Active_HTML_Document');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Task Manager Application
|
|
8
|
+
*
|
|
9
|
+
* A multi-window application demonstrating:
|
|
10
|
+
* - Multiple windows with different purposes
|
|
11
|
+
* - Tabbed panels for organization
|
|
12
|
+
* - Interactive controls (text inputs, buttons, selects)
|
|
13
|
+
* - API integration for data persistence
|
|
14
|
+
* - Real-time UI updates
|
|
15
|
+
*/
|
|
16
|
+
class Task_Manager_App extends Active_HTML_Document {
|
|
17
|
+
constructor(spec = {}) {
|
|
18
|
+
spec.__type_name = spec.__type_name || 'task_manager_app';
|
|
19
|
+
super(spec);
|
|
20
|
+
const { context } = this;
|
|
21
|
+
|
|
22
|
+
if (typeof this.body.add_class === 'function') {
|
|
23
|
+
this.body.add_class('task-manager-app');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Task data stored directly on the instance
|
|
27
|
+
this._tasks = [];
|
|
28
|
+
this._projects = ['Personal', 'Work', 'Shopping', 'Fitness'];
|
|
29
|
+
|
|
30
|
+
const compose = () => {
|
|
31
|
+
// Main Task Manager Window
|
|
32
|
+
const main_window = new controls.Window({
|
|
33
|
+
context,
|
|
34
|
+
title: 'Task Manager',
|
|
35
|
+
pos: [20, 20]
|
|
36
|
+
});
|
|
37
|
+
main_window.size = [500, 450];
|
|
38
|
+
|
|
39
|
+
// Create tabbed panel for main window - using simple Control for each tab
|
|
40
|
+
const tasks_panel = new Control({ context, size: [480, 350], background: { color: '#f8f9fa' } });
|
|
41
|
+
const projects_panel = new Control({ context, size: [480, 350], background: { color: '#e8f5e9' } });
|
|
42
|
+
const stats_panel = new Control({ context, size: [480, 350], background: { color: '#fff3e0' } });
|
|
43
|
+
|
|
44
|
+
// Build tasks panel content
|
|
45
|
+
this._build_tasks_panel(context, tasks_panel);
|
|
46
|
+
|
|
47
|
+
// Build projects panel content
|
|
48
|
+
this._build_projects_panel(context, projects_panel);
|
|
49
|
+
|
|
50
|
+
// Build stats panel content
|
|
51
|
+
this._build_stats_panel(context, stats_panel);
|
|
52
|
+
|
|
53
|
+
const main_tabs = new controls.Tabbed_Panel({
|
|
54
|
+
context,
|
|
55
|
+
tabs: [
|
|
56
|
+
['Tasks', tasks_panel],
|
|
57
|
+
['Projects', projects_panel],
|
|
58
|
+
['Stats', stats_panel]
|
|
59
|
+
]
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
main_window.inner.add(main_tabs);
|
|
63
|
+
this.body.add(main_window);
|
|
64
|
+
|
|
65
|
+
// Quick Add Window (smaller floating window)
|
|
66
|
+
const quick_add_window = new controls.Window({
|
|
67
|
+
context,
|
|
68
|
+
title: 'Quick Add Task',
|
|
69
|
+
pos: [540, 20]
|
|
70
|
+
});
|
|
71
|
+
quick_add_window.size = [280, 180];
|
|
72
|
+
|
|
73
|
+
this._build_quick_add_panel(context, quick_add_window.inner);
|
|
74
|
+
this.body.add(quick_add_window);
|
|
75
|
+
|
|
76
|
+
// Store references
|
|
77
|
+
this._ctrl_fields = {
|
|
78
|
+
main_window,
|
|
79
|
+
main_tabs,
|
|
80
|
+
quick_add_window
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
if (!spec.el) {
|
|
85
|
+
compose();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
_build_tasks_panel(context, panel) {
|
|
90
|
+
// Filter bar
|
|
91
|
+
const filter_bar = new controls.div({ context });
|
|
92
|
+
filter_bar.dom.attributes['class'] = 'filter-bar';
|
|
93
|
+
|
|
94
|
+
const filter_label = new controls.span({ context });
|
|
95
|
+
filter_label.add('Filter by project:');
|
|
96
|
+
filter_bar.add(filter_label);
|
|
97
|
+
|
|
98
|
+
const project_filter = new Select_Options({
|
|
99
|
+
context,
|
|
100
|
+
options: ['All', ...this._projects]
|
|
101
|
+
});
|
|
102
|
+
filter_bar.add(project_filter);
|
|
103
|
+
this._project_filter = project_filter;
|
|
104
|
+
|
|
105
|
+
panel.add(filter_bar);
|
|
106
|
+
|
|
107
|
+
// Action buttons
|
|
108
|
+
const action_bar = new controls.div({ context });
|
|
109
|
+
action_bar.dom.attributes['class'] = 'action-bar';
|
|
110
|
+
|
|
111
|
+
const refresh_btn = new Button({ context, text: 'Refresh' });
|
|
112
|
+
refresh_btn.dom.attributes['id'] = 'refresh-btn';
|
|
113
|
+
action_bar.add(refresh_btn);
|
|
114
|
+
this._refresh_btn = refresh_btn;
|
|
115
|
+
|
|
116
|
+
const clear_completed_btn = new Button({ context, text: 'Clear Completed' });
|
|
117
|
+
clear_completed_btn.dom.attributes['id'] = 'clear-completed-btn';
|
|
118
|
+
action_bar.add(clear_completed_btn);
|
|
119
|
+
this._clear_completed_btn = clear_completed_btn;
|
|
120
|
+
|
|
121
|
+
panel.add(action_bar);
|
|
122
|
+
|
|
123
|
+
// Task list container - will be populated dynamically
|
|
124
|
+
const task_list = new controls.div({ context });
|
|
125
|
+
task_list.dom.attributes['class'] = 'task-list';
|
|
126
|
+
task_list.dom.attributes['id'] = 'task-list';
|
|
127
|
+
this._task_list = task_list;
|
|
128
|
+
panel.add(task_list);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
_build_projects_panel(context, panel) {
|
|
132
|
+
const title = new controls.h3({ context });
|
|
133
|
+
title.add('Manage Projects');
|
|
134
|
+
panel.add(title);
|
|
135
|
+
|
|
136
|
+
// Project list
|
|
137
|
+
const project_list = new controls.div({ context });
|
|
138
|
+
project_list.dom.attributes['class'] = 'project-list';
|
|
139
|
+
|
|
140
|
+
for (const project of this._projects) {
|
|
141
|
+
const project_item = new controls.div({ context });
|
|
142
|
+
project_item.dom.attributes['class'] = 'project-item';
|
|
143
|
+
|
|
144
|
+
const project_name = new controls.span({ context });
|
|
145
|
+
project_name.add(project);
|
|
146
|
+
project_item.add(project_name);
|
|
147
|
+
|
|
148
|
+
const task_count = new controls.span({ context });
|
|
149
|
+
task_count.dom.attributes['class'] = 'task-count';
|
|
150
|
+
task_count.add('0 tasks');
|
|
151
|
+
project_item.add(task_count);
|
|
152
|
+
|
|
153
|
+
project_list.add(project_item);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
panel.add(project_list);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
_build_stats_panel(context, panel) {
|
|
160
|
+
const title = new controls.h3({ context });
|
|
161
|
+
title.add('Statistics');
|
|
162
|
+
panel.add(title);
|
|
163
|
+
|
|
164
|
+
// Stats grid
|
|
165
|
+
const stats_grid = new controls.div({ context });
|
|
166
|
+
stats_grid.dom.attributes['class'] = 'stats-grid';
|
|
167
|
+
|
|
168
|
+
// Total tasks card
|
|
169
|
+
const total_card = new controls.div({ context });
|
|
170
|
+
total_card.dom.attributes['class'] = 'stat-card';
|
|
171
|
+
const total_value = new controls.div({ context });
|
|
172
|
+
total_value.dom.attributes['id'] = 'total-value';
|
|
173
|
+
total_value.add('0');
|
|
174
|
+
total_card.add(total_value);
|
|
175
|
+
const total_label = new controls.div({ context });
|
|
176
|
+
total_label.add('Total Tasks');
|
|
177
|
+
total_card.add(total_label);
|
|
178
|
+
stats_grid.add(total_card);
|
|
179
|
+
|
|
180
|
+
// Completed tasks card
|
|
181
|
+
const completed_card = new controls.div({ context });
|
|
182
|
+
completed_card.dom.attributes['class'] = 'stat-card completed';
|
|
183
|
+
const completed_value = new controls.div({ context });
|
|
184
|
+
completed_value.dom.attributes['id'] = 'completed-value';
|
|
185
|
+
completed_value.add('0');
|
|
186
|
+
completed_card.add(completed_value);
|
|
187
|
+
const completed_label = new controls.div({ context });
|
|
188
|
+
completed_label.add('Completed');
|
|
189
|
+
completed_card.add(completed_label);
|
|
190
|
+
stats_grid.add(completed_card);
|
|
191
|
+
|
|
192
|
+
// Pending tasks card
|
|
193
|
+
const pending_card = new controls.div({ context });
|
|
194
|
+
pending_card.dom.attributes['class'] = 'stat-card pending';
|
|
195
|
+
const pending_value = new controls.div({ context });
|
|
196
|
+
pending_value.dom.attributes['id'] = 'pending-value';
|
|
197
|
+
pending_value.add('0');
|
|
198
|
+
pending_card.add(pending_value);
|
|
199
|
+
const pending_label = new controls.div({ context });
|
|
200
|
+
pending_label.add('Pending');
|
|
201
|
+
pending_card.add(pending_label);
|
|
202
|
+
stats_grid.add(pending_card);
|
|
203
|
+
|
|
204
|
+
// Rate card
|
|
205
|
+
const rate_card = new controls.div({ context });
|
|
206
|
+
rate_card.dom.attributes['class'] = 'stat-card rate';
|
|
207
|
+
const rate_value = new controls.div({ context });
|
|
208
|
+
rate_value.dom.attributes['id'] = 'rate-value';
|
|
209
|
+
rate_value.add('0%');
|
|
210
|
+
rate_card.add(rate_value);
|
|
211
|
+
const rate_label = new controls.div({ context });
|
|
212
|
+
rate_label.add('Completion Rate');
|
|
213
|
+
rate_card.add(rate_label);
|
|
214
|
+
stats_grid.add(rate_card);
|
|
215
|
+
|
|
216
|
+
panel.add(stats_grid);
|
|
217
|
+
|
|
218
|
+
// Progress bar
|
|
219
|
+
const progress_section = new controls.div({ context });
|
|
220
|
+
progress_section.dom.attributes['class'] = 'progress-section';
|
|
221
|
+
const progress_bar = new controls.div({ context });
|
|
222
|
+
progress_bar.dom.attributes['class'] = 'progress-bar';
|
|
223
|
+
const progress_fill = new controls.div({ context });
|
|
224
|
+
progress_fill.dom.attributes['class'] = 'progress-fill';
|
|
225
|
+
progress_fill.dom.attributes['id'] = 'progress-fill';
|
|
226
|
+
progress_bar.add(progress_fill);
|
|
227
|
+
progress_section.add(progress_bar);
|
|
228
|
+
panel.add(progress_section);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
_build_quick_add_panel(context, panel) {
|
|
232
|
+
// Task text input
|
|
233
|
+
const task_input = new Text_Field({
|
|
234
|
+
context,
|
|
235
|
+
placeholder: 'Enter task description...'
|
|
236
|
+
});
|
|
237
|
+
panel.add(task_input);
|
|
238
|
+
this._task_input = task_input;
|
|
239
|
+
|
|
240
|
+
// Project selector label
|
|
241
|
+
const project_label = new controls.div({ context });
|
|
242
|
+
project_label.add('Project:');
|
|
243
|
+
panel.add(project_label);
|
|
244
|
+
|
|
245
|
+
const project_select = new Select_Options({
|
|
246
|
+
context,
|
|
247
|
+
options: this._projects
|
|
248
|
+
});
|
|
249
|
+
panel.add(project_select);
|
|
250
|
+
this._project_select = project_select;
|
|
251
|
+
|
|
252
|
+
// Add button
|
|
253
|
+
const add_btn = new Button({ context, text: 'Add Task' });
|
|
254
|
+
add_btn.dom.attributes['id'] = 'add-task-btn';
|
|
255
|
+
panel.add(add_btn);
|
|
256
|
+
this._add_task_btn = add_btn;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
_render_task_list() {
|
|
260
|
+
// Use getElementById since we set the id in compose
|
|
261
|
+
const task_list_el = document.getElementById('task-list');
|
|
262
|
+
if (!task_list_el) return;
|
|
263
|
+
|
|
264
|
+
// Clear existing tasks
|
|
265
|
+
task_list_el.innerHTML = '';
|
|
266
|
+
|
|
267
|
+
const tasks = this._tasks || [];
|
|
268
|
+
const filter = this._current_filter || 'All';
|
|
269
|
+
|
|
270
|
+
const filtered_tasks = filter === 'All'
|
|
271
|
+
? tasks
|
|
272
|
+
: tasks.filter(t => t.project === filter);
|
|
273
|
+
|
|
274
|
+
if (filtered_tasks.length === 0) {
|
|
275
|
+
const empty_msg = document.createElement('div');
|
|
276
|
+
empty_msg.className = 'empty-message';
|
|
277
|
+
empty_msg.textContent = 'No tasks found. Add one!';
|
|
278
|
+
task_list_el.appendChild(empty_msg);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
for (const task of filtered_tasks) {
|
|
283
|
+
const task_el = document.createElement('div');
|
|
284
|
+
task_el.className = 'task-item' + (task.completed ? ' completed' : '');
|
|
285
|
+
task_el.dataset.taskId = task.id;
|
|
286
|
+
|
|
287
|
+
const checkbox = document.createElement('input');
|
|
288
|
+
checkbox.type = 'checkbox';
|
|
289
|
+
checkbox.checked = task.completed;
|
|
290
|
+
checkbox.className = 'task-checkbox';
|
|
291
|
+
checkbox.addEventListener('change', () => this._toggle_task(task.id));
|
|
292
|
+
task_el.appendChild(checkbox);
|
|
293
|
+
|
|
294
|
+
const text = document.createElement('span');
|
|
295
|
+
text.className = 'task-text';
|
|
296
|
+
text.textContent = task.text;
|
|
297
|
+
task_el.appendChild(text);
|
|
298
|
+
|
|
299
|
+
const project_badge = document.createElement('span');
|
|
300
|
+
project_badge.className = 'project-badge';
|
|
301
|
+
project_badge.textContent = task.project;
|
|
302
|
+
task_el.appendChild(project_badge);
|
|
303
|
+
|
|
304
|
+
const delete_btn = document.createElement('button');
|
|
305
|
+
delete_btn.className = 'delete-btn';
|
|
306
|
+
delete_btn.textContent = '×';
|
|
307
|
+
delete_btn.addEventListener('click', () => this._delete_task(task.id));
|
|
308
|
+
task_el.appendChild(delete_btn);
|
|
309
|
+
|
|
310
|
+
task_list_el.appendChild(task_el);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
_update_stats() {
|
|
315
|
+
const tasks = this._tasks || [];
|
|
316
|
+
const total = tasks.length;
|
|
317
|
+
const completed = tasks.filter(t => t.completed).length;
|
|
318
|
+
const pending = total - completed;
|
|
319
|
+
const rate = total > 0 ? Math.round((completed / total) * 100) : 0;
|
|
320
|
+
|
|
321
|
+
// Update UI using getElementById
|
|
322
|
+
const total_el = document.getElementById('total-value');
|
|
323
|
+
const completed_el = document.getElementById('completed-value');
|
|
324
|
+
const pending_el = document.getElementById('pending-value');
|
|
325
|
+
const rate_el = document.getElementById('rate-value');
|
|
326
|
+
const progress_el = document.getElementById('progress-fill');
|
|
327
|
+
|
|
328
|
+
if (total_el) total_el.textContent = total;
|
|
329
|
+
if (completed_el) completed_el.textContent = completed;
|
|
330
|
+
if (pending_el) pending_el.textContent = pending;
|
|
331
|
+
if (rate_el) rate_el.textContent = rate + '%';
|
|
332
|
+
if (progress_el) progress_el.style.width = rate + '%';
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async _add_task() {
|
|
336
|
+
// Get text from DOM since control references don't persist through hydration
|
|
337
|
+
// The text input is in the "Quick Add Task" window (second window with class 'inner')
|
|
338
|
+
const input_el = document.querySelector('input[type="text"]');
|
|
339
|
+
// The project select is the second select on the page (first is the filter)
|
|
340
|
+
const selects = document.querySelectorAll('select');
|
|
341
|
+
const select_el = selects.length > 1 ? selects[1] : null;
|
|
342
|
+
|
|
343
|
+
const text = input_el ? input_el.value : '';
|
|
344
|
+
if (!text.trim()) {
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const project = select_el ? select_el.value : this._projects[0];
|
|
349
|
+
|
|
350
|
+
try {
|
|
351
|
+
const response = await fetch('/api/tasks/add', {
|
|
352
|
+
method: 'POST',
|
|
353
|
+
headers: { 'Content-Type': 'application/json' },
|
|
354
|
+
body: JSON.stringify({ text: text.trim(), project })
|
|
355
|
+
});
|
|
356
|
+
const result = await response.json();
|
|
357
|
+
|
|
358
|
+
if (result.success) {
|
|
359
|
+
this._tasks = result.tasks;
|
|
360
|
+
this._render_task_list();
|
|
361
|
+
this._update_stats();
|
|
362
|
+
|
|
363
|
+
// Clear input using DOM
|
|
364
|
+
if (input_el) {
|
|
365
|
+
input_el.value = '';
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
} catch (err) {
|
|
369
|
+
console.error('Failed to add task:', err);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
async _toggle_task(id) {
|
|
374
|
+
try {
|
|
375
|
+
const response = await fetch('/api/tasks/toggle', {
|
|
376
|
+
method: 'POST',
|
|
377
|
+
headers: { 'Content-Type': 'application/json' },
|
|
378
|
+
body: JSON.stringify({ id })
|
|
379
|
+
});
|
|
380
|
+
const result = await response.json();
|
|
381
|
+
|
|
382
|
+
if (result.success) {
|
|
383
|
+
this._tasks = result.tasks;
|
|
384
|
+
this._render_task_list();
|
|
385
|
+
this._update_stats();
|
|
386
|
+
}
|
|
387
|
+
} catch (err) {
|
|
388
|
+
console.error('Failed to toggle task:', err);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
async _delete_task(id) {
|
|
393
|
+
try {
|
|
394
|
+
const response = await fetch('/api/tasks/delete', {
|
|
395
|
+
method: 'POST',
|
|
396
|
+
headers: { 'Content-Type': 'application/json' },
|
|
397
|
+
body: JSON.stringify({ id })
|
|
398
|
+
});
|
|
399
|
+
const result = await response.json();
|
|
400
|
+
|
|
401
|
+
if (result.success) {
|
|
402
|
+
this._tasks = result.tasks;
|
|
403
|
+
this._render_task_list();
|
|
404
|
+
this._update_stats();
|
|
405
|
+
}
|
|
406
|
+
} catch (err) {
|
|
407
|
+
console.error('Failed to delete task:', err);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
async _load_tasks() {
|
|
412
|
+
try {
|
|
413
|
+
const response = await fetch('/api/tasks/list');
|
|
414
|
+
const result = await response.json();
|
|
415
|
+
this._tasks = result.tasks || [];
|
|
416
|
+
this._render_task_list();
|
|
417
|
+
this._update_stats();
|
|
418
|
+
} catch (err) {
|
|
419
|
+
console.error('Failed to load tasks:', err);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
async _clear_completed() {
|
|
424
|
+
try {
|
|
425
|
+
const response = await fetch('/api/tasks/clear-completed', { method: 'POST' });
|
|
426
|
+
const result = await response.json();
|
|
427
|
+
|
|
428
|
+
if (result.success) {
|
|
429
|
+
this._tasks = result.tasks;
|
|
430
|
+
this._render_task_list();
|
|
431
|
+
this._update_stats();
|
|
432
|
+
}
|
|
433
|
+
} catch (err) {
|
|
434
|
+
console.error('Failed to clear completed:', err);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
activate() {
|
|
439
|
+
if (!this.__active) {
|
|
440
|
+
super.activate();
|
|
441
|
+
const { context } = this;
|
|
442
|
+
|
|
443
|
+
// Load initial tasks
|
|
444
|
+
this._load_tasks();
|
|
445
|
+
|
|
446
|
+
// Add task button handler - use getElementById for hydration support
|
|
447
|
+
const add_task_btn = document.getElementById('add-task-btn');
|
|
448
|
+
if (add_task_btn) {
|
|
449
|
+
add_task_btn.addEventListener('click', () => this._add_task());
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Refresh button handler
|
|
453
|
+
const refresh_btn = document.getElementById('refresh-btn');
|
|
454
|
+
if (refresh_btn) {
|
|
455
|
+
refresh_btn.addEventListener('click', () => this._load_tasks());
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Clear completed button handler
|
|
459
|
+
const clear_completed_btn = document.getElementById('clear-completed-btn');
|
|
460
|
+
if (clear_completed_btn) {
|
|
461
|
+
clear_completed_btn.addEventListener('click', () => this._clear_completed());
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Filter change handler
|
|
465
|
+
if (this._project_filter?.data?.model) {
|
|
466
|
+
this._project_filter.data.model.on('change', e => {
|
|
467
|
+
this._current_filter = this._project_filter.data.model.value || 'All';
|
|
468
|
+
this._render_task_list();
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Enter key to add task
|
|
473
|
+
if (this._task_input?.dom?.el) {
|
|
474
|
+
const input = this._task_input.dom.el.querySelector('input') || this._task_input.dom.el;
|
|
475
|
+
input.addEventListener('keypress', (e) => {
|
|
476
|
+
if (e.key === 'Enter') this._add_task();
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Handle window resize
|
|
481
|
+
context.on('window-resize', e_resize => {});
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
Task_Manager_App.css = `
|
|
487
|
+
* {
|
|
488
|
+
margin: 0;
|
|
489
|
+
padding: 0;
|
|
490
|
+
box-sizing: border-box;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
body {
|
|
494
|
+
overflow-x: hidden;
|
|
495
|
+
overflow-y: hidden;
|
|
496
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
497
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
.task-manager-app {
|
|
501
|
+
min-height: 100vh;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/* Panel styles */
|
|
505
|
+
.tasks-panel,
|
|
506
|
+
.projects-panel,
|
|
507
|
+
.stats-panel,
|
|
508
|
+
.quick-add-panel {
|
|
509
|
+
padding: 15px;
|
|
510
|
+
height: 100%;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/* Filter bar */
|
|
514
|
+
.filter-bar {
|
|
515
|
+
display: flex;
|
|
516
|
+
align-items: center;
|
|
517
|
+
gap: 10px;
|
|
518
|
+
margin-bottom: 15px;
|
|
519
|
+
padding-bottom: 10px;
|
|
520
|
+
border-bottom: 1px solid #ddd;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
.filter-label {
|
|
524
|
+
font-weight: 500;
|
|
525
|
+
color: #555;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/* Task list */
|
|
529
|
+
.task-list {
|
|
530
|
+
max-height: 250px;
|
|
531
|
+
overflow-y: auto;
|
|
532
|
+
margin-bottom: 15px;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
.task-item {
|
|
536
|
+
display: flex;
|
|
537
|
+
align-items: center;
|
|
538
|
+
gap: 10px;
|
|
539
|
+
padding: 10px;
|
|
540
|
+
background: #f8f9fa;
|
|
541
|
+
border-radius: 6px;
|
|
542
|
+
margin-bottom: 8px;
|
|
543
|
+
transition: all 0.2s ease;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
.task-item:hover {
|
|
547
|
+
background: #e9ecef;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
.task-item.completed {
|
|
551
|
+
opacity: 0.6;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
.task-item.completed .task-text {
|
|
555
|
+
text-decoration: line-through;
|
|
556
|
+
color: #888;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
.task-checkbox {
|
|
560
|
+
width: 18px;
|
|
561
|
+
height: 18px;
|
|
562
|
+
cursor: pointer;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
.task-text {
|
|
566
|
+
flex: 1;
|
|
567
|
+
font-size: 14px;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
.project-badge {
|
|
571
|
+
font-size: 11px;
|
|
572
|
+
padding: 2px 8px;
|
|
573
|
+
background: #667eea;
|
|
574
|
+
color: white;
|
|
575
|
+
border-radius: 12px;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
.delete-btn {
|
|
579
|
+
width: 24px;
|
|
580
|
+
height: 24px;
|
|
581
|
+
border: none;
|
|
582
|
+
background: #dc3545;
|
|
583
|
+
color: white;
|
|
584
|
+
border-radius: 50%;
|
|
585
|
+
cursor: pointer;
|
|
586
|
+
font-size: 16px;
|
|
587
|
+
line-height: 1;
|
|
588
|
+
opacity: 0;
|
|
589
|
+
transition: opacity 0.2s;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
.task-item:hover .delete-btn {
|
|
593
|
+
opacity: 1;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
.empty-message {
|
|
597
|
+
text-align: center;
|
|
598
|
+
color: #888;
|
|
599
|
+
padding: 30px;
|
|
600
|
+
font-style: italic;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/* Action bar */
|
|
604
|
+
.action-bar {
|
|
605
|
+
display: flex;
|
|
606
|
+
gap: 10px;
|
|
607
|
+
padding-top: 10px;
|
|
608
|
+
border-top: 1px solid #ddd;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/* Buttons */
|
|
612
|
+
.btn {
|
|
613
|
+
padding: 8px 16px;
|
|
614
|
+
border: none;
|
|
615
|
+
border-radius: 6px;
|
|
616
|
+
cursor: pointer;
|
|
617
|
+
font-size: 13px;
|
|
618
|
+
font-weight: 500;
|
|
619
|
+
transition: all 0.2s ease;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
.btn-primary {
|
|
623
|
+
background: #667eea;
|
|
624
|
+
color: white;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
.btn-primary:hover {
|
|
628
|
+
background: #5a6fd6;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
.btn-secondary {
|
|
632
|
+
background: #6c757d;
|
|
633
|
+
color: white;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
.btn-secondary:hover {
|
|
637
|
+
background: #5a6268;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
.btn-warning {
|
|
641
|
+
background: #ffc107;
|
|
642
|
+
color: #212529;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
.btn-warning:hover {
|
|
646
|
+
background: #e0a800;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/* Projects panel */
|
|
650
|
+
.projects-panel h3 {
|
|
651
|
+
margin-bottom: 15px;
|
|
652
|
+
color: #333;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
.project-list {
|
|
656
|
+
margin-bottom: 20px;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
.project-item {
|
|
660
|
+
display: flex;
|
|
661
|
+
justify-content: space-between;
|
|
662
|
+
align-items: center;
|
|
663
|
+
padding: 12px;
|
|
664
|
+
background: #f8f9fa;
|
|
665
|
+
border-radius: 6px;
|
|
666
|
+
margin-bottom: 8px;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
.project-name {
|
|
670
|
+
font-weight: 500;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
.task-count {
|
|
674
|
+
font-size: 12px;
|
|
675
|
+
color: #888;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
.add-project-section {
|
|
679
|
+
display: flex;
|
|
680
|
+
gap: 10px;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
.new-project-input {
|
|
684
|
+
flex: 1;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/* Stats panel */
|
|
688
|
+
.stats-panel h3 {
|
|
689
|
+
margin-bottom: 20px;
|
|
690
|
+
color: #333;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
.stats-grid {
|
|
694
|
+
display: grid;
|
|
695
|
+
grid-template-columns: repeat(2, 1fr);
|
|
696
|
+
gap: 15px;
|
|
697
|
+
margin-bottom: 20px;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
.stat-card {
|
|
701
|
+
background: #f8f9fa;
|
|
702
|
+
padding: 20px;
|
|
703
|
+
border-radius: 10px;
|
|
704
|
+
text-align: center;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
.stat-card.completed {
|
|
708
|
+
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
|
|
709
|
+
color: white;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
.stat-card.pending {
|
|
713
|
+
background: linear-gradient(135deg, #ffc107 0%, #fd7e14 100%);
|
|
714
|
+
color: white;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
.stat-card.rate {
|
|
718
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
719
|
+
color: white;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
.stat-value {
|
|
723
|
+
font-size: 32px;
|
|
724
|
+
font-weight: bold;
|
|
725
|
+
margin-bottom: 5px;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
.stat-label {
|
|
729
|
+
font-size: 12px;
|
|
730
|
+
text-transform: uppercase;
|
|
731
|
+
opacity: 0.8;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
.progress-section {
|
|
735
|
+
margin-top: 10px;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
.progress-bar {
|
|
739
|
+
height: 20px;
|
|
740
|
+
background: #e9ecef;
|
|
741
|
+
border-radius: 10px;
|
|
742
|
+
overflow: hidden;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
.progress-fill {
|
|
746
|
+
height: 100%;
|
|
747
|
+
background: linear-gradient(90deg, #28a745 0%, #20c997 100%);
|
|
748
|
+
width: 0%;
|
|
749
|
+
transition: width 0.5s ease;
|
|
750
|
+
border-radius: 10px;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
/* Quick add panel */
|
|
754
|
+
.quick-add-panel {
|
|
755
|
+
display: flex;
|
|
756
|
+
flex-direction: column;
|
|
757
|
+
gap: 12px;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
.task-input {
|
|
761
|
+
width: 100%;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
.input-label {
|
|
765
|
+
font-size: 12px;
|
|
766
|
+
color: #666;
|
|
767
|
+
margin-bottom: -8px;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
.add-task-btn {
|
|
771
|
+
width: 100%;
|
|
772
|
+
padding: 12px;
|
|
773
|
+
font-size: 14px;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
/* Custom scrollbar */
|
|
777
|
+
.task-list::-webkit-scrollbar {
|
|
778
|
+
width: 6px;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
.task-list::-webkit-scrollbar-track {
|
|
782
|
+
background: #f1f1f1;
|
|
783
|
+
border-radius: 3px;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
.task-list::-webkit-scrollbar-thumb {
|
|
787
|
+
background: #c1c1c1;
|
|
788
|
+
border-radius: 3px;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
.task-list::-webkit-scrollbar-thumb:hover {
|
|
792
|
+
background: #a8a8a8;
|
|
793
|
+
}
|
|
794
|
+
`;
|
|
795
|
+
|
|
796
|
+
controls.Task_Manager_App = Task_Manager_App;
|
|
797
|
+
module.exports = jsgui;
|