jsgui3-server 0.0.144 → 0.0.146

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.
Files changed (71) hide show
  1. package/docs/jsgui3-html-improvement-ideas.md +162 -0
  2. package/docs/jsgui3-html-improvement-ideas.svg +151 -0
  3. package/docs/jsgui3-sass-patterns-book/01-vision-and-goals.md +31 -0
  4. package/docs/jsgui3-sass-patterns-book/02-stack-map.md +40 -0
  5. package/docs/jsgui3-sass-patterns-book/03-control-local-sass.md +60 -0
  6. package/docs/jsgui3-sass-patterns-book/04-extension-and-variants.md +76 -0
  7. package/docs/jsgui3-sass-patterns-book/05-theming-and-tokens.md +54 -0
  8. package/docs/jsgui3-sass-patterns-book/06-workspace-overrides.md +45 -0
  9. package/docs/jsgui3-sass-patterns-book/07-resource-and-bundling.md +46 -0
  10. package/docs/jsgui3-sass-patterns-book/08-examples.md +62 -0
  11. package/docs/jsgui3-sass-patterns-book/09-testing-and-adoption.md +27 -0
  12. package/docs/jsgui3-sass-patterns-book/README.md +23 -0
  13. package/docs/troubleshooting.md +44 -4
  14. package/examples/controls/14) window, canvas/client.js +1 -1
  15. package/examples/controls/14b) window, canvas (improved renderer)/client.js +1 -1
  16. package/examples/controls/14d) window, canvas globe/EarthGlobeRenderer.js +19 -14
  17. package/examples/controls/14d) window, canvas globe/client.js +1 -1
  18. package/examples/controls/14d) window, canvas globe/pipeline/TransformStage.js +5 -5
  19. package/examples/controls/14e) window, canvas multithreaded/client.js +1 -1
  20. package/examples/controls/14f) window, canvas polyglobe/client.js +1 -1
  21. package/examples/controls/16) window, form container/client.js +254 -0
  22. package/examples/controls/16) window, form container/server.js +20 -0
  23. package/examples/controls/17) window, mvvm binding/client.js +530 -0
  24. package/examples/controls/17) window, mvvm binding/server.js +20 -0
  25. package/examples/jsgui3-html/01) mvvm-counter/client.js +648 -0
  26. package/examples/jsgui3-html/01) mvvm-counter/server.js +21 -0
  27. package/examples/jsgui3-html/02) date-transform/client.js +764 -0
  28. package/examples/jsgui3-html/02) date-transform/server.js +21 -0
  29. package/examples/jsgui3-html/03) form-validation/client.js +1045 -0
  30. package/examples/jsgui3-html/03) form-validation/server.js +21 -0
  31. package/examples/jsgui3-html/04) data-grid/client.js +738 -0
  32. package/examples/jsgui3-html/04) data-grid/server.js +21 -0
  33. package/examples/jsgui3-html/05) master-detail/client.js +649 -0
  34. package/examples/jsgui3-html/05) master-detail/server.js +21 -0
  35. package/examples/jsgui3-html/06) theming/client.js +514 -0
  36. package/examples/jsgui3-html/06) theming/server.js +21 -0
  37. package/examples/jsgui3-html/07) mixins/client.js +465 -0
  38. package/examples/jsgui3-html/07) mixins/server.js +21 -0
  39. package/examples/jsgui3-html/08) router/client.js +372 -0
  40. package/examples/jsgui3-html/08) router/server.js +21 -0
  41. package/examples/jsgui3-html/09) resource-transform/client.js +692 -0
  42. package/examples/jsgui3-html/09) resource-transform/server.js +21 -0
  43. package/examples/jsgui3-html/10) binding-debugger/client.js +810 -0
  44. package/examples/jsgui3-html/10) binding-debugger/server.js +21 -0
  45. package/examples/jsgui3-html/README.md +48 -0
  46. package/http/responders/static/Static_Route_HTTP_Responder.js +25 -20
  47. package/package.json +3 -3
  48. package/publishers/http-webpageorsite-publisher.js +3 -1
  49. package/resources/processors/bundlers/js/esbuild/Core_JS_Non_Minifying_Bundler_Using_ESBuild.js +87 -100
  50. package/resources/processors/bundlers/js/esbuild/Core_JS_Single_File_Minifying_Bundler_Using_ESBuild.js +89 -60
  51. package/serve-factory.js +12 -5
  52. package/server.js +103 -85
  53. package/tests/README.md +52 -9
  54. package/tests/bundlers.test.js +53 -47
  55. package/tests/end-to-end.test.js +336 -365
  56. package/tests/examples-controls.e2e.test.js +15 -1
  57. package/tests/fixtures/end-to-end-client.js +54 -0
  58. package/tests/fixtures/jsgui3-html/binding_debugger_expectations.json +15 -0
  59. package/tests/fixtures/jsgui3-html/counter_expectations.json +31 -0
  60. package/tests/fixtures/jsgui3-html/data_grid_expectations.json +26 -0
  61. package/tests/fixtures/jsgui3-html/date_transform_expectations.json +26 -0
  62. package/tests/fixtures/jsgui3-html/form_validation_expectations.json +27 -0
  63. package/tests/fixtures/jsgui3-html/master_detail_expectations.json +15 -0
  64. package/tests/fixtures/jsgui3-html/mixins_expectations.json +10 -0
  65. package/tests/fixtures/jsgui3-html/resource_transform_expectations.json +11 -0
  66. package/tests/fixtures/jsgui3-html/router_expectations.json +10 -0
  67. package/tests/fixtures/jsgui3-html/theming_expectations.json +10 -0
  68. package/tests/jsgui3-html-examples.puppeteer.test.js +537 -0
  69. package/tests/sass-controls.e2e.test.js +123 -9
  70. package/tests/test-runner.js +1 -0
  71. package/tests/window-examples.puppeteer.test.js +217 -1
@@ -0,0 +1,692 @@
1
+ const jsgui = require('jsgui3-client');
2
+ const Active_HTML_Document = require('../../../controls/Active_HTML_Document');
3
+ const { Data_Object } = jsgui;
4
+
5
+ const Data_Transform_Resource = jsgui.Resource.Data_Transform;
6
+
7
+ const DEFAULT_RECORDS = Object.freeze([
8
+ { id: 'A1', project: 'Atlas', owner: 'Ada Lovelace', hours: 18.5, status: 'Complete' },
9
+ { id: 'B2', project: 'Beacon', owner: 'Grace Hopper', hours: 12, status: 'In Progress' },
10
+ { id: 'C3', project: 'Compass', owner: 'Katherine Johnson', hours: 22.75, status: 'Complete' },
11
+ { id: 'D4', project: 'Dawn', owner: 'Mary Jackson', hours: 9, status: 'Review' }
12
+ ]);
13
+
14
+ const PIPELINE_STEPS = Object.freeze([
15
+ 'Normalize incoming records',
16
+ 'Filter by minimum hours',
17
+ 'Aggregate summary metrics',
18
+ 'Prepare focus output'
19
+ ]);
20
+
21
+ const title_case = (value) => {
22
+ const raw_value = value ? String(value) : '';
23
+ return raw_value.replace(/\w\S*/g, (word) => {
24
+ return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
25
+ });
26
+ };
27
+
28
+ class Pipeline_Transform_Resource extends Data_Transform_Resource {
29
+ constructor(spec = {}) {
30
+ super(spec);
31
+ this.name = spec.name || 'pipeline_transform_resource';
32
+ }
33
+
34
+ async transform(records = [], options = {}) {
35
+ return this.run_pipeline(records, options);
36
+ }
37
+
38
+ async run_pipeline(records, options) {
39
+ const normalized_records = this.normalize_records(records);
40
+ const min_hours = Number.isFinite(options.min_hours) ? options.min_hours : 0;
41
+ const filtered_records = normalized_records.filter((record) => record.hours >= min_hours);
42
+ const summary = this.build_summary(filtered_records);
43
+ const output_lines = this.build_output_lines(filtered_records, options.mode || 'focus');
44
+
45
+ await this.sleep(280);
46
+
47
+ return {
48
+ summary,
49
+ output_lines,
50
+ filtered_records
51
+ };
52
+ }
53
+
54
+ normalize_records(records) {
55
+ const raw_records = Array.isArray(records) ? records : [];
56
+ return raw_records.map((record) => {
57
+ return {
58
+ id: record.id ? String(record.id).toUpperCase() : 'N/A',
59
+ project: title_case(record.project),
60
+ owner: title_case(record.owner),
61
+ hours: Number(record.hours) || 0,
62
+ status: title_case(record.status)
63
+ };
64
+ });
65
+ }
66
+
67
+ build_summary(records) {
68
+ const record_count = records.length;
69
+ const total_hours = records.reduce((acc, record) => acc + record.hours, 0);
70
+ const avg_hours = record_count ? total_hours / record_count : 0;
71
+ const complete_count = records.filter((record) => record.status === 'Complete').length;
72
+
73
+ return {
74
+ record_count,
75
+ total_hours,
76
+ avg_hours,
77
+ complete_count
78
+ };
79
+ }
80
+
81
+ build_output_lines(records, mode) {
82
+ if (mode === 'status') {
83
+ const counts = records.reduce((acc, record) => {
84
+ acc[record.status] = (acc[record.status] || 0) + 1;
85
+ return acc;
86
+ }, {});
87
+ return Object.keys(counts).sort().map((status) => {
88
+ return `${status}: ${counts[status]} record(s)`;
89
+ });
90
+ }
91
+
92
+ const sorted_records = records.slice().sort((a, b) => b.hours - a.hours);
93
+ return sorted_records.slice(0, 3).map((record) => {
94
+ return `${record.project} - ${record.owner} | ${record.hours}h`;
95
+ });
96
+ }
97
+
98
+ sleep(duration_ms) {
99
+ return new Promise((resolve) => setTimeout(resolve, duration_ms));
100
+ }
101
+ }
102
+
103
+ class Resource_Transform_Control extends jsgui.Control {
104
+ constructor(spec = {}) {
105
+ spec.__type_name = spec.__type_name || 'resource_transform_control';
106
+ super(spec);
107
+
108
+ this.transform_resource = new Pipeline_Transform_Resource();
109
+ this.log_counter = 0;
110
+
111
+ this.data.model = new Data_Object({
112
+ transform_mode: 'focus',
113
+ min_hours: 0
114
+ });
115
+ this.data.model.set('records', spec.records || DEFAULT_RECORDS, true);
116
+
117
+ this.view.data.model = new Data_Object({
118
+ status_text: 'Idle: ready to transform.',
119
+ summary_text: 'No transform yet.',
120
+ output_lines: [],
121
+ last_run_text: 'Not run',
122
+ error_text: '',
123
+ is_busy: false
124
+ });
125
+
126
+ if (!spec.el) {
127
+ this.compose();
128
+ }
129
+ }
130
+
131
+ get_records() {
132
+ return Array.isArray(this.data.model.records) ? this.data.model.records.slice() : [];
133
+ }
134
+
135
+ format_summary(summary) {
136
+ const record_count = summary.record_count;
137
+ const total_hours = this.transforms.number.toFixed(summary.total_hours, 2);
138
+ const avg_hours = this.transforms.number.toFixed(summary.avg_hours, 1);
139
+ return `${record_count} records | ${total_hours} total hours | Avg ${avg_hours} hrs`;
140
+ }
141
+
142
+ async run_transform() {
143
+ if (this.view.data.model.is_busy) return;
144
+
145
+ this.view.data.model.is_busy = true;
146
+ this.view.data.model.status_text = 'Transforming records...';
147
+ this.view.data.model.error_text = '';
148
+
149
+ const transform_mode = this.data.model.transform_mode;
150
+ const min_hours = Number(this.data.model.min_hours) || 0;
151
+ const records = this.get_records();
152
+
153
+ try {
154
+ const result = await this.transform_resource.transform(records, {
155
+ mode: transform_mode,
156
+ min_hours
157
+ });
158
+ this.view.data.model.summary_text = this.format_summary(result.summary);
159
+ this.view.data.model.output_lines = result.output_lines;
160
+ this.view.data.model.status_text = 'Transform complete.';
161
+ this.view.data.model.last_run_text = `Run #${this.next_log_index()}`;
162
+ } catch (error) {
163
+ this.view.data.model.status_text = 'Transform failed.';
164
+ this.view.data.model.error_text = error && error.message ? error.message : 'Unknown error.';
165
+ } finally {
166
+ this.view.data.model.is_busy = false;
167
+ }
168
+ }
169
+
170
+ next_log_index() {
171
+ this.log_counter += 1;
172
+ return this.log_counter;
173
+ }
174
+
175
+ render_output_list(list_container, output_lines) {
176
+ list_container.clear();
177
+
178
+ output_lines.forEach((line, index) => {
179
+ const item = new jsgui.Control({
180
+ context: this.context,
181
+ tagName: 'li',
182
+ class: 'output-item',
183
+ content: line
184
+ });
185
+ item.dom.attributes['data-test'] = `output-line-${index}`;
186
+ list_container.add(item);
187
+ });
188
+ }
189
+
190
+ render_output_dom(list_el, output_lines) {
191
+ if (!list_el) return;
192
+ list_el.innerHTML = '';
193
+
194
+ output_lines.forEach((line, index) => {
195
+ const item_el = document.createElement('li');
196
+ item_el.className = 'output-item';
197
+ item_el.textContent = line;
198
+ item_el.setAttribute('data-test', `output-line-${index}`);
199
+ list_el.appendChild(item_el);
200
+ });
201
+ }
202
+
203
+ activate() {
204
+ if (this.__active) return;
205
+ super.activate();
206
+
207
+ if (this._dom_bound) return;
208
+ const root_el = this.dom.el;
209
+ if (!root_el) return;
210
+ this._dom_bound = true;
211
+
212
+ const status_el = root_el.querySelector('[data-test="transform-status"]');
213
+ const summary_el = root_el.querySelector('[data-test="summary-text"]');
214
+ const run_button_el = root_el.querySelector('[data-test="run-transform"]');
215
+ const output_list_el = root_el.querySelector('[data-test="output-list"]');
216
+ const mode_select_el = root_el.querySelector('[data-test="mode-select"]');
217
+ const min_hours_el = root_el.querySelector('[data-test="min-hours-input"]');
218
+ const last_run_el = root_el.querySelector('[data-test="last-run"]');
219
+ const error_el = root_el.querySelector('[data-test="error-text"]');
220
+
221
+ if (run_button_el) {
222
+ run_button_el.addEventListener('click', () => this.run_transform());
223
+ }
224
+
225
+ if (mode_select_el) {
226
+ mode_select_el.addEventListener('change', (event) => {
227
+ const value = event && event.target ? event.target.value : 'focus';
228
+ this.data.model.transform_mode = value;
229
+ });
230
+ }
231
+
232
+ if (min_hours_el) {
233
+ min_hours_el.addEventListener('input', (event) => {
234
+ const raw_value = event && event.target ? event.target.value : '0';
235
+ this.data.model.min_hours = Number(raw_value) || 0;
236
+ });
237
+ }
238
+
239
+ this.watch(
240
+ this.view.data.model,
241
+ 'status_text',
242
+ (status_text) => {
243
+ if (status_el) status_el.textContent = status_text || '';
244
+ },
245
+ { immediate: true }
246
+ );
247
+
248
+ this.watch(
249
+ this.view.data.model,
250
+ 'summary_text',
251
+ (summary_text) => {
252
+ if (summary_el) summary_el.textContent = summary_text || '';
253
+ },
254
+ { immediate: true }
255
+ );
256
+
257
+ this.watch(
258
+ this.view.data.model,
259
+ 'output_lines',
260
+ (output_lines) => {
261
+ this.render_output_dom(output_list_el, Array.isArray(output_lines) ? output_lines : []);
262
+ },
263
+ { immediate: true }
264
+ );
265
+
266
+ this.watch(
267
+ this.view.data.model,
268
+ 'last_run_text',
269
+ (last_run_text) => {
270
+ if (last_run_el) last_run_el.textContent = last_run_text || '';
271
+ },
272
+ { immediate: true }
273
+ );
274
+
275
+ this.watch(
276
+ this.view.data.model,
277
+ 'error_text',
278
+ (error_text) => {
279
+ if (error_el) {
280
+ error_el.textContent = error_text || '';
281
+ error_el.classList.toggle('has-error', !!error_text);
282
+ }
283
+ },
284
+ { immediate: true }
285
+ );
286
+
287
+ this.watch(
288
+ this.data.model,
289
+ 'transform_mode',
290
+ (transform_mode) => {
291
+ if (mode_select_el) {
292
+ mode_select_el.value = transform_mode || 'focus';
293
+ }
294
+ },
295
+ { immediate: true }
296
+ );
297
+
298
+ this.watch(
299
+ this.data.model,
300
+ 'min_hours',
301
+ (min_hours) => {
302
+ if (min_hours_el) {
303
+ min_hours_el.value = String(Number(min_hours) || 0);
304
+ }
305
+ },
306
+ { immediate: true }
307
+ );
308
+ }
309
+
310
+ compose() {
311
+ // Framework expects the method name `compose`.
312
+ const page_context = this.context;
313
+
314
+ this.add_class('resource-transform-control');
315
+ this.dom.attributes['data-test'] = 'resource-transform-control';
316
+
317
+ const header = new jsgui.Control({
318
+ context: page_context,
319
+ tagName: 'header',
320
+ class: 'rt-header'
321
+ });
322
+
323
+ const title = new jsgui.Control({
324
+ context: page_context,
325
+ tagName: 'h1',
326
+ class: 'rt-title',
327
+ content: 'Resource Transform Pipeline'
328
+ });
329
+
330
+ const subtitle = new jsgui.Control({
331
+ context: page_context,
332
+ tagName: 'p',
333
+ class: 'rt-subtitle',
334
+ content: 'Run a data transform resource and inspect the summary output.'
335
+ });
336
+
337
+ header.add(title);
338
+ header.add(subtitle);
339
+
340
+ const controls_panel = new jsgui.Control({
341
+ context: page_context,
342
+ tagName: 'div',
343
+ class: 'rt-controls'
344
+ });
345
+
346
+ const mode_field = new jsgui.Control({
347
+ context: page_context,
348
+ tagName: 'label',
349
+ class: 'rt-field'
350
+ });
351
+ mode_field.add('Mode');
352
+ const mode_select = new jsgui.Control({
353
+ context: page_context,
354
+ tagName: 'select',
355
+ class: 'rt-select'
356
+ });
357
+ mode_select.dom.attributes['data-test'] = 'mode-select';
358
+ ['focus', 'status'].forEach((mode) => {
359
+ const option = new jsgui.Control({
360
+ context: page_context,
361
+ tagName: 'option',
362
+ content: mode === 'focus' ? 'Focus list' : 'Status counts'
363
+ });
364
+ option.dom.attributes.value = mode;
365
+ mode_select.add(option);
366
+ });
367
+ mode_field.add(mode_select);
368
+
369
+ const min_hours_field = new jsgui.Control({
370
+ context: page_context,
371
+ tagName: 'label',
372
+ class: 'rt-field'
373
+ });
374
+ min_hours_field.add('Min hours');
375
+ const min_hours_input = new jsgui.Control({
376
+ context: page_context,
377
+ tagName: 'input',
378
+ class: 'rt-input'
379
+ });
380
+ min_hours_input.dom.attributes.type = 'number';
381
+ min_hours_input.dom.attributes.min = '0';
382
+ min_hours_input.dom.attributes.step = '1';
383
+ min_hours_input.dom.attributes['data-test'] = 'min-hours-input';
384
+ min_hours_field.add(min_hours_input);
385
+
386
+ const run_button = new jsgui.Control({
387
+ context: page_context,
388
+ tagName: 'button',
389
+ class: 'rt-button',
390
+ content: 'Run transform'
391
+ });
392
+ run_button.dom.attributes['data-test'] = 'run-transform';
393
+
394
+ controls_panel.add(mode_field);
395
+ controls_panel.add(min_hours_field);
396
+ controls_panel.add(run_button);
397
+
398
+ const pipeline_panel = new jsgui.Control({
399
+ context: page_context,
400
+ tagName: 'div',
401
+ class: 'rt-pipeline'
402
+ });
403
+
404
+ const pipeline_title = new jsgui.Control({
405
+ context: page_context,
406
+ tagName: 'h2',
407
+ class: 'rt-section-title',
408
+ content: 'Pipeline Steps'
409
+ });
410
+
411
+ const pipeline_list = new jsgui.Control({
412
+ context: page_context,
413
+ tagName: 'ol',
414
+ class: 'rt-pipeline-list'
415
+ });
416
+
417
+ PIPELINE_STEPS.forEach((step) => {
418
+ pipeline_list.add(new jsgui.Control({
419
+ context: page_context,
420
+ tagName: 'li',
421
+ content: step
422
+ }));
423
+ });
424
+
425
+ pipeline_panel.add(pipeline_title);
426
+ pipeline_panel.add(pipeline_list);
427
+
428
+ const output_panel = new jsgui.Control({
429
+ context: page_context,
430
+ tagName: 'div',
431
+ class: 'rt-output'
432
+ });
433
+
434
+ const status_text = new jsgui.Control({
435
+ context: page_context,
436
+ tagName: 'div',
437
+ class: 'rt-status',
438
+ content: this.view.data.model.status_text
439
+ });
440
+ status_text.dom.attributes['data-test'] = 'transform-status';
441
+
442
+ const last_run = new jsgui.Control({
443
+ context: page_context,
444
+ tagName: 'div',
445
+ class: 'rt-last-run',
446
+ content: this.view.data.model.last_run_text
447
+ });
448
+ last_run.dom.attributes['data-test'] = 'last-run';
449
+
450
+ const summary_text = new jsgui.Control({
451
+ context: page_context,
452
+ tagName: 'div',
453
+ class: 'rt-summary',
454
+ content: this.view.data.model.summary_text
455
+ });
456
+ summary_text.dom.attributes['data-test'] = 'summary-text';
457
+
458
+ const error_text = new jsgui.Control({
459
+ context: page_context,
460
+ tagName: 'div',
461
+ class: 'rt-error',
462
+ content: this.view.data.model.error_text
463
+ });
464
+ error_text.dom.attributes['data-test'] = 'error-text';
465
+
466
+ const output_list = new jsgui.Control({
467
+ context: page_context,
468
+ tagName: 'ul',
469
+ class: 'rt-output-list'
470
+ });
471
+ output_list.dom.attributes['data-test'] = 'output-list';
472
+
473
+ this.render_output_list(output_list, this.view.data.model.output_lines);
474
+
475
+ output_panel.add(status_text);
476
+ output_panel.add(last_run);
477
+ output_panel.add(summary_text);
478
+ output_panel.add(error_text);
479
+ output_panel.add(output_list);
480
+
481
+ const layout = new jsgui.Control({
482
+ context: page_context,
483
+ tagName: 'div',
484
+ class: 'rt-layout'
485
+ });
486
+ layout.add(controls_panel);
487
+ layout.add(pipeline_panel);
488
+ layout.add(output_panel);
489
+
490
+ this.add(header);
491
+ this.add(layout);
492
+ }
493
+ }
494
+
495
+ class Demo_UI extends Active_HTML_Document {
496
+ constructor(spec = {}) {
497
+ spec.__type_name = spec.__type_name || 'resource_transform_demo_ui';
498
+ super(spec);
499
+
500
+ if (!spec.el) {
501
+ this.compose();
502
+ }
503
+ }
504
+
505
+ compose() {
506
+ // Framework expects the method name `compose`.
507
+ const page_context = this.context;
508
+ this.body.add_class('resource-transform-demo');
509
+
510
+ const main_control = new Resource_Transform_Control({
511
+ context: page_context,
512
+ records: DEFAULT_RECORDS
513
+ });
514
+
515
+ this.body.add(main_control);
516
+ }
517
+ }
518
+
519
+ Demo_UI.css = `
520
+ :root {
521
+ --rt-ink: #1d1d22;
522
+ --rt-muted: #6a6a76;
523
+ --rt-accent: #db6b3d;
524
+ --rt-panel: #ffffff;
525
+ --rt-border: #d9d2c8;
526
+ --rt-bg: #f8f3ec;
527
+ }
528
+
529
+ * {
530
+ box-sizing: border-box;
531
+ }
532
+
533
+ body {
534
+ margin: 0;
535
+ padding: 0;
536
+ background: radial-gradient(circle at top, #fdf7f0 0%, #f3e9dc 55%, #e6ded3 100%);
537
+ color: var(--rt-ink);
538
+ font-family: "Libre Baskerville", "Georgia", serif;
539
+ }
540
+
541
+ .resource-transform-demo {
542
+ min-height: 100vh;
543
+ display: flex;
544
+ align-items: center;
545
+ justify-content: center;
546
+ padding: 40px 24px;
547
+ }
548
+
549
+ .resource-transform-control {
550
+ width: min(980px, 100%);
551
+ background: var(--rt-panel);
552
+ border-radius: 24px;
553
+ padding: 32px 36px 40px;
554
+ border: 1px solid var(--rt-border);
555
+ box-shadow: 0 26px 60px rgba(50, 41, 32, 0.18);
556
+ }
557
+
558
+ .rt-header {
559
+ margin-bottom: 24px;
560
+ }
561
+
562
+ .rt-title {
563
+ margin: 0 0 8px;
564
+ font-size: 30px;
565
+ }
566
+
567
+ .rt-subtitle {
568
+ margin: 0;
569
+ color: var(--rt-muted);
570
+ font-size: 15px;
571
+ }
572
+
573
+ .rt-layout {
574
+ display: grid;
575
+ grid-template-columns: 1.1fr 1fr 1.2fr;
576
+ gap: 20px;
577
+ }
578
+
579
+ .rt-controls,
580
+ .rt-pipeline,
581
+ .rt-output {
582
+ background: #fdfbf7;
583
+ border-radius: 16px;
584
+ padding: 18px 20px;
585
+ border: 1px solid #efe6d8;
586
+ }
587
+
588
+ .rt-controls {
589
+ display: grid;
590
+ gap: 14px;
591
+ }
592
+
593
+ .rt-field {
594
+ display: grid;
595
+ gap: 6px;
596
+ font-size: 13px;
597
+ text-transform: uppercase;
598
+ letter-spacing: 0.08em;
599
+ }
600
+
601
+ .rt-select,
602
+ .rt-input {
603
+ padding: 10px 12px;
604
+ border-radius: 10px;
605
+ border: 1px solid #cfc8bc;
606
+ font-size: 14px;
607
+ font-family: "Fira Sans", "Arial", sans-serif;
608
+ }
609
+
610
+ .rt-button {
611
+ background: var(--rt-accent);
612
+ border: none;
613
+ color: #ffffff;
614
+ padding: 12px 16px;
615
+ border-radius: 999px;
616
+ font-size: 14px;
617
+ cursor: pointer;
618
+ font-family: "Fira Sans", "Arial", sans-serif;
619
+ }
620
+
621
+ .rt-button:hover {
622
+ filter: brightness(0.95);
623
+ }
624
+
625
+ .rt-section-title {
626
+ margin: 0 0 12px;
627
+ font-size: 16px;
628
+ }
629
+
630
+ .rt-pipeline-list {
631
+ margin: 0;
632
+ padding-left: 20px;
633
+ color: var(--rt-muted);
634
+ font-size: 14px;
635
+ display: grid;
636
+ gap: 8px;
637
+ }
638
+
639
+ .rt-output {
640
+ display: grid;
641
+ gap: 10px;
642
+ }
643
+
644
+ .rt-status {
645
+ font-weight: 600;
646
+ }
647
+
648
+ .rt-last-run {
649
+ font-size: 12px;
650
+ color: var(--rt-muted);
651
+ }
652
+
653
+ .rt-summary {
654
+ font-size: 14px;
655
+ }
656
+
657
+ .rt-error {
658
+ font-size: 12px;
659
+ color: #b0482e;
660
+ }
661
+
662
+ .rt-error.has-error {
663
+ font-weight: 600;
664
+ }
665
+
666
+ .rt-output-list {
667
+ list-style: none;
668
+ padding: 0;
669
+ margin: 0;
670
+ display: grid;
671
+ gap: 8px;
672
+ }
673
+
674
+ .output-item {
675
+ background: #fff5ea;
676
+ border-radius: 10px;
677
+ padding: 10px 12px;
678
+ font-size: 13px;
679
+ }
680
+
681
+ @media (max-width: 900px) {
682
+ .rt-layout {
683
+ grid-template-columns: 1fr;
684
+ }
685
+ }
686
+ `;
687
+
688
+ jsgui.controls.Resource_Transform_Control = Resource_Transform_Control;
689
+ jsgui.controls.Demo_UI = Demo_UI;
690
+ jsgui.controls.resource_transform_demo_ui = Demo_UI;
691
+
692
+ module.exports = jsgui;
@@ -0,0 +1,21 @@
1
+ const jsgui = require('./client');
2
+ const Server = require('../../../server');
3
+ const { Demo_UI } = jsgui.controls;
4
+
5
+ if (require.main === module) {
6
+ const server_instance = new Server({
7
+ Ctrl: Demo_UI,
8
+ src_path_client_js: require.resolve('./client.js')
9
+ });
10
+
11
+ server_instance.allowed_addresses = ['127.0.0.1'];
12
+
13
+ server_instance.on('ready', () => {
14
+ server_instance.start(52000, (err) => {
15
+ if (err) {
16
+ throw err;
17
+ }
18
+ console.log('server started on port 52000');
19
+ });
20
+ });
21
+ }