@yuneta/gobj-ui 7.23.58 → 7.23.60
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/dist/gobj-ui.cjs.js +609 -130
- package/dist/gobj-ui.es.js +602 -131
- package/index.js +4 -0
- package/package.json +1 -1
- package/src/c_yui_form.js +144 -9
- package/src/c_yui_treedb_topic_with_form.css +15 -0
- package/src/c_yui_treedb_topic_with_form.js +141 -10
- package/src/yui_file_field.css +67 -0
- package/src/yui_file_field.js +361 -0
- package/src/yui_file_field.test.js +119 -0
package/index.js
CHANGED
|
@@ -120,6 +120,10 @@ export { addClasses, removeClasses, toggleClasses, removeChildElements, disableE
|
|
|
120
120
|
export { inject_svg_icons } from "./src/lib_icons.js";
|
|
121
121
|
export { EditControl, MarkerControl } from "./src/lib_maplibre.js";
|
|
122
122
|
export { yui_asset_id, yui_asset_ids, yui_asset_src, yui_asset_element, yui_asset_missing } from "./src/yui_asset.js";
|
|
123
|
+
export {
|
|
124
|
+
YUI_FILE_ACCEPT, yui_file_size_label, yui_file_sha256, yui_array_buffer_to_base64,
|
|
125
|
+
yui_file_read, yui_files_manifest, yui_file_id_label, yui_file_control
|
|
126
|
+
} from "./src/yui_file_field.js";
|
|
123
127
|
export { yui_toolbar } from "./src/yui_toolbar.js";
|
|
124
128
|
export { yui_theme_now, yui_is_dark, yui_watch_theme } from "./src/yui_theme.js";
|
|
125
129
|
export { attach_clear, refresh_clear } from "./src/yui_inputs.js";
|
package/package.json
CHANGED
package/src/c_yui_form.js
CHANGED
|
@@ -55,9 +55,16 @@ import TomSelect from "tom-select"; // Import Tom-Select JS
|
|
|
55
55
|
|
|
56
56
|
import { createJSONEditor } from 'vanilla-jsoneditor';
|
|
57
57
|
import "vanilla-jsoneditor/themes/jse-theme-dark.css";
|
|
58
|
+
import {
|
|
59
|
+
yui_file_control
|
|
60
|
+
} from "./yui_file_field.js";
|
|
61
|
+
import {
|
|
62
|
+
yui_asset_id
|
|
63
|
+
} from "./yui_asset.js";
|
|
64
|
+
|
|
58
65
|
import "./c_yui_form.css";
|
|
59
66
|
import {attach_clear, refresh_clear} from "./yui_inputs.js";
|
|
60
|
-
import {plan_toolbar} from "./form_toolbar_plan.js";
|
|
67
|
+
import {plan_toolbar, DEFAULT_TOOLBAR} from "./form_toolbar_plan.js";
|
|
61
68
|
/* Read at field-build time only — deliberately NOT watched: the hosting
|
|
62
69
|
* dialog rebuilds the form on every open, and re-rendering a form under
|
|
63
70
|
* the user mid-edit would throw away what they typed. */
|
|
@@ -117,7 +124,12 @@ SDATA(data_type_t.DTP_BOOLEAN, "with_delete_row_btn", 0, false, "Print last co
|
|
|
117
124
|
* Unknown names are ignored, with a warning: a typo must not silently
|
|
118
125
|
* drop the save button.
|
|
119
126
|
*/
|
|
120
|
-
|
|
127
|
+
/* The default is the LIST, not a copy of it: written out here it was a
|
|
128
|
+
* second copy of `DEFAULT_TOOLBAR` that `plan_toolbar` never got to
|
|
129
|
+
* see -- the attr always has a value, so the module that decides the
|
|
130
|
+
* plan could change its default and nothing moved. Sliced because an
|
|
131
|
+
* attr default is handed out and must not be the shared array. */
|
|
132
|
+
SDATA(data_type_t.DTP_JSON, "toolbar", 0, DEFAULT_TOOLBAR.slice(), "Toolbar buttons to show. The order inside each group is the order given here; which side each group takes is form_toolbar_plan.js. [] hides the toolbar"),
|
|
121
133
|
/*
|
|
122
134
|
* Defaults for tabulator
|
|
123
135
|
*/
|
|
@@ -354,7 +366,7 @@ function toolbar_button(gobj, name)
|
|
|
354
366
|
}
|
|
355
367
|
}];
|
|
356
368
|
case "clear":
|
|
357
|
-
return ['button', {class: 'button p-1', title: 'clear', 'aria-label': 'clear'}, [
|
|
369
|
+
return ['button', {class: 'button p-1 button-clear', title: 'clear', 'aria-label': 'clear'}, [
|
|
358
370
|
['span', {class: 'icon m-0'}, '<i class="yi-broom-wide"></i>'],
|
|
359
371
|
['span', {class: 'is-hidden-mobile pl-1 pr-1', i18n: 'clear'}, 'clear']
|
|
360
372
|
], {
|
|
@@ -364,7 +376,7 @@ function toolbar_button(gobj, name)
|
|
|
364
376
|
}
|
|
365
377
|
}];
|
|
366
378
|
case "copy":
|
|
367
|
-
return ['button', {class: 'button p-1', title: 'copy', 'aria-label': 'copy'}, [
|
|
379
|
+
return ['button', {class: 'button p-1 button-copy', title: 'copy', 'aria-label': 'copy'}, [
|
|
368
380
|
['span', {class: 'icon m-0'}, '<i class="yi-copy"></i>'],
|
|
369
381
|
['span', {class: 'is-hidden-mobile pl-1 pr-1', i18n: 'copy'}, 'copy']
|
|
370
382
|
], {click: function(evt) {
|
|
@@ -373,7 +385,7 @@ function toolbar_button(gobj, name)
|
|
|
373
385
|
}
|
|
374
386
|
}];
|
|
375
387
|
case "paste":
|
|
376
|
-
return ['button', {class: 'button p-1', title: 'paste', 'aria-label': 'paste'}, [
|
|
388
|
+
return ['button', {class: 'button p-1 button-paste', title: 'paste', 'aria-label': 'paste'}, [
|
|
377
389
|
['span', {class: 'icon m-0'}, '<i class="yi-paste"></i>'],
|
|
378
390
|
['span', {class: 'is-hidden-mobile pl-1 pr-1', i18n: 'paste'}, 'paste']
|
|
379
391
|
], {click: async function(evt) {
|
|
@@ -664,6 +676,13 @@ function build_form_field_conf(gobj, field_desc)
|
|
|
664
676
|
field_conf.options = get_fkey_select_options(gobj, field_desc);
|
|
665
677
|
break;
|
|
666
678
|
|
|
679
|
+
/* An fkey into __assets__, and NOT a select: what a person picks
|
|
680
|
+
* is a file on their machine, not a row of an index they have
|
|
681
|
+
* never seen. The bytes travel BESIDE the record (yui_file_field). */
|
|
682
|
+
case "file":
|
|
683
|
+
field_conf.tag = "file";
|
|
684
|
+
break;
|
|
685
|
+
|
|
667
686
|
case "image":
|
|
668
687
|
case "icon":
|
|
669
688
|
case "string":
|
|
@@ -1441,6 +1460,24 @@ function create_form_field(
|
|
|
1441
1460
|
break;
|
|
1442
1461
|
}
|
|
1443
1462
|
|
|
1463
|
+
case "file":
|
|
1464
|
+
{
|
|
1465
|
+
/* The picked File is KEPT on the element and never enters the
|
|
1466
|
+
* record: a kw is plain json and a File is a host object, so
|
|
1467
|
+
* what the record carries is the id (yui_file_field). */
|
|
1468
|
+
let file_control = yui_file_control(gobj, {
|
|
1469
|
+
name: name,
|
|
1470
|
+
value: "", // set_form_values() fills it from the record
|
|
1471
|
+
readonly: readonly,
|
|
1472
|
+
on_pick: function() {
|
|
1473
|
+
gobj_send_event(gobj, "EV_RECORD_CHANGED", {}, gobj);
|
|
1474
|
+
}
|
|
1475
|
+
});
|
|
1476
|
+
$extend = file_control.$control;
|
|
1477
|
+
$control.appendChild($extend);
|
|
1478
|
+
break;
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1444
1481
|
default:
|
|
1445
1482
|
$extend = createElement2(['strong', {class: 'has-text-danger'}, `Unsupported element type: "${tag}"`]);
|
|
1446
1483
|
$control.appendChild($extend);
|
|
@@ -2217,6 +2254,13 @@ function get_form_values(gobj, $form)
|
|
|
2217
2254
|
value = getInputValue($input);
|
|
2218
2255
|
break;
|
|
2219
2256
|
|
|
2257
|
+
/* The id the column keeps, never the bytes. A file picked
|
|
2258
|
+
* and not yet saved has no id -- the host reads the File
|
|
2259
|
+
* itself, through `get_picked_files`, and builds __files__. */
|
|
2260
|
+
case "file":
|
|
2261
|
+
value = $input.yui_file_value || "";
|
|
2262
|
+
break;
|
|
2263
|
+
|
|
2220
2264
|
case "jsoneditor":
|
|
2221
2265
|
let jsoneditor = $input.jsoneditor;
|
|
2222
2266
|
if(jsoneditor) {
|
|
@@ -2318,6 +2362,20 @@ function clear_data(gobj, $form)
|
|
|
2318
2362
|
}
|
|
2319
2363
|
});
|
|
2320
2364
|
|
|
2365
|
+
/* A file control is a `div`, so the input/select/textarea sweep above
|
|
2366
|
+
* does not reach it, and Clear used to leave the old asset showing. */
|
|
2367
|
+
$form.querySelectorAll('.FILE_FIELD').forEach($file => {
|
|
2368
|
+
$file.yui_file = null;
|
|
2369
|
+
$file.yui_file_value = "";
|
|
2370
|
+
let $picker = $file.querySelector('.FILE_INPUT');
|
|
2371
|
+
if($picker) {
|
|
2372
|
+
$picker.value = "";
|
|
2373
|
+
}
|
|
2374
|
+
if($file.yui_file_render) {
|
|
2375
|
+
$file.yui_file_render();
|
|
2376
|
+
}
|
|
2377
|
+
});
|
|
2378
|
+
|
|
2321
2379
|
$form.querySelectorAll('.yui-tabulator-table').forEach($table => {
|
|
2322
2380
|
if($table.tabulator.initialized) {
|
|
2323
2381
|
$table.tabulator.clearData();
|
|
@@ -2422,6 +2480,18 @@ function set_form_values(gobj, template, $form, record)
|
|
|
2422
2480
|
}
|
|
2423
2481
|
|
|
2424
2482
|
switch(tag) {
|
|
2483
|
+
/* A `file` column comes back as an fkey, in any of the three
|
|
2484
|
+
* shapes a link takes -- the stored reference, the bare id
|
|
2485
|
+
* `fkey_only_id` collapses it to, or an expanded ref -- so it
|
|
2486
|
+
* is read with the reader that knows all three. */
|
|
2487
|
+
case "file":
|
|
2488
|
+
$input.yui_file = null;
|
|
2489
|
+
$input.yui_file_value = yui_asset_id(value) || "";
|
|
2490
|
+
if($input.yui_file_render) {
|
|
2491
|
+
$input.yui_file_render();
|
|
2492
|
+
}
|
|
2493
|
+
break;
|
|
2494
|
+
|
|
2425
2495
|
case "input":
|
|
2426
2496
|
switch(input_type) {
|
|
2427
2497
|
case "datetime-local":
|
|
@@ -3118,14 +3188,79 @@ function ac_timeout(gobj, event, kw, src)
|
|
|
3118
3188
|
|
|
3119
3189
|
|
|
3120
3190
|
|
|
3191
|
+
/************************************************************
|
|
3192
|
+
* Framework Method command
|
|
3193
|
+
************************************************************/
|
|
3194
|
+
function mt_command_parser(gobj, command, kw, src)
|
|
3195
|
+
{
|
|
3196
|
+
switch(command) {
|
|
3197
|
+
case "get_picked_files":
|
|
3198
|
+
return cmd_get_picked_files(gobj, command, kw, src);
|
|
3199
|
+
default:
|
|
3200
|
+
log_error(`${gobj_short_name(gobj)}: command not found: ${command}`);
|
|
3201
|
+
return {
|
|
3202
|
+
result: -1,
|
|
3203
|
+
comment: `Command not found: ${command}`,
|
|
3204
|
+
schema: null,
|
|
3205
|
+
data: null
|
|
3206
|
+
};
|
|
3207
|
+
}
|
|
3208
|
+
}
|
|
3209
|
+
|
|
3210
|
+
/************************************************************
|
|
3211
|
+
* The files a person picked and the record cannot carry.
|
|
3212
|
+
*
|
|
3213
|
+
* A `File` is a host object and a kw is plain json -- the machine trace
|
|
3214
|
+
* serialises it -- so the picked files never travel in an event. The
|
|
3215
|
+
* form KEEPS them and the HOST asks for them here, at save, and turns
|
|
3216
|
+
* them into the `__files__` manifest that rides beside the record.
|
|
3217
|
+
*
|
|
3218
|
+
* Answers `data: {col: File}`, empty when nothing was picked, which is
|
|
3219
|
+
* the ordinary case and not an error.
|
|
3220
|
+
************************************************************/
|
|
3221
|
+
function cmd_get_picked_files(gobj, cmd, kw, src)
|
|
3222
|
+
{
|
|
3223
|
+
const files = {};
|
|
3224
|
+
const $container = gobj_read_attr(gobj, "$container");
|
|
3225
|
+
const $form = $container? $container.querySelector('form'): null;
|
|
3226
|
+
|
|
3227
|
+
if(!$form) {
|
|
3228
|
+
log_error(`${gobj_short_name(gobj)}: no form to read the picked files from`);
|
|
3229
|
+
return {
|
|
3230
|
+
result: -1,
|
|
3231
|
+
comment: "no form",
|
|
3232
|
+
schema: null,
|
|
3233
|
+
data: null
|
|
3234
|
+
};
|
|
3235
|
+
}
|
|
3236
|
+
|
|
3237
|
+
$form.querySelectorAll('.yui-form-data-input').forEach(($input) => {
|
|
3238
|
+
if($input.dataset.form_tag !== "file") {
|
|
3239
|
+
return;
|
|
3240
|
+
}
|
|
3241
|
+
if($input.yui_file) {
|
|
3242
|
+
files[$input.dataset.treedb_name] = $input.yui_file;
|
|
3243
|
+
}
|
|
3244
|
+
});
|
|
3245
|
+
|
|
3246
|
+
return {
|
|
3247
|
+
result: 0,
|
|
3248
|
+
comment: "",
|
|
3249
|
+
schema: null,
|
|
3250
|
+
data: files
|
|
3251
|
+
};
|
|
3252
|
+
}
|
|
3253
|
+
|
|
3254
|
+
|
|
3121
3255
|
/*---------------------------------------------*
|
|
3122
3256
|
* Global methods table
|
|
3123
3257
|
*---------------------------------------------*/
|
|
3124
3258
|
const gmt = {
|
|
3125
|
-
mt_create:
|
|
3126
|
-
mt_start:
|
|
3127
|
-
mt_stop:
|
|
3128
|
-
mt_destroy:
|
|
3259
|
+
mt_create: mt_create,
|
|
3260
|
+
mt_start: mt_start,
|
|
3261
|
+
mt_stop: mt_stop,
|
|
3262
|
+
mt_destroy: mt_destroy,
|
|
3263
|
+
mt_command_parser: mt_command_parser
|
|
3129
3264
|
};
|
|
3130
3265
|
|
|
3131
3266
|
/***************************************************************
|
|
@@ -126,3 +126,18 @@
|
|
|
126
126
|
line-height: 1;
|
|
127
127
|
vertical-align: middle;
|
|
128
128
|
}
|
|
129
|
+
|
|
130
|
+
/*
|
|
131
|
+
* What a `file` column shows in a cell: the asset id, shortened, or the
|
|
132
|
+
* word for having none. Monospace because it is a hash and a hash read
|
|
133
|
+
* in a proportional font is unreadable.
|
|
134
|
+
*/
|
|
135
|
+
.FILE_CELL {
|
|
136
|
+
font-family: monospace;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.FILE_CELL.is-empty {
|
|
140
|
+
font-family: inherit;
|
|
141
|
+
font-style: italic;
|
|
142
|
+
opacity: 0.6;
|
|
143
|
+
}
|
|
@@ -92,6 +92,8 @@ import {row_matches} from "./yui_row_search.js";
|
|
|
92
92
|
import {t} from "i18next";
|
|
93
93
|
|
|
94
94
|
import {plan_treedb_writes, READONLY_FORM_TOOLBAR} from "./treedb_write_plan.js";
|
|
95
|
+
import {yui_file_read, yui_files_manifest, yui_file_id_label} from "./yui_file_field.js";
|
|
96
|
+
import {yui_asset_id} from "./yui_asset.js";
|
|
95
97
|
import {delete_impact} from "./delete_impact.js";
|
|
96
98
|
|
|
97
99
|
import "./c_yui_treedb_topic_with_form.css";
|
|
@@ -1293,6 +1295,14 @@ function create_tabulator(gobj)
|
|
|
1293
1295
|
vertAlign = "middle";
|
|
1294
1296
|
colFormatter = icon_formatter;
|
|
1295
1297
|
break;
|
|
1298
|
+
/* An fkey into __assets__. It draws the id and NOT the
|
|
1299
|
+
* picture, because a picture is a `get-asset` per cell -- an
|
|
1300
|
+
* action, with its own event and its own answer -- and a
|
|
1301
|
+
* table of 50 rows would fire 50 of them at render time. The
|
|
1302
|
+
* id is what the cell can say honestly on its own. */
|
|
1303
|
+
case "file":
|
|
1304
|
+
colFormatter = file_cell_formatter;
|
|
1305
|
+
break;
|
|
1296
1306
|
case "object":
|
|
1297
1307
|
case "dict":
|
|
1298
1308
|
case "template":
|
|
@@ -1743,6 +1753,28 @@ function table__destroy(gobj)
|
|
|
1743
1753
|
* render a solid square -- a glyph nobody recognises rather
|
|
1744
1754
|
* than a mistake somebody can fix.
|
|
1745
1755
|
************************************************************/
|
|
1756
|
+
/************************************************************
|
|
1757
|
+
* What a `file` column shows in a table cell: the asset it names.
|
|
1758
|
+
*
|
|
1759
|
+
* A sha256 in full is 64 characters of noise in a column, so it is
|
|
1760
|
+
* shortened and the whole of it is the tooltip. Empty is SAID, not left
|
|
1761
|
+
* blank -- a blank cell and a cell whose asset failed to load look the
|
|
1762
|
+
* same, and only one of them is normal.
|
|
1763
|
+
************************************************************/
|
|
1764
|
+
function file_cell_formatter(cell, formatterParams, onRendered)
|
|
1765
|
+
{
|
|
1766
|
+
const id = yui_asset_id(cell.getValue());
|
|
1767
|
+
if(!id) {
|
|
1768
|
+
const $empty = createElement2(
|
|
1769
|
+
["span", {class: "FILE_CELL is-empty", "data-i18n": "no file"}, "no file"]
|
|
1770
|
+
);
|
|
1771
|
+
return $empty;
|
|
1772
|
+
}
|
|
1773
|
+
return createElement2(
|
|
1774
|
+
["span", {class: "FILE_CELL", title: id}, yui_file_id_label(id)]
|
|
1775
|
+
);
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1746
1778
|
function icon_formatter(cell)
|
|
1747
1779
|
{
|
|
1748
1780
|
let name = cell.getValue();
|
|
@@ -1869,6 +1901,7 @@ function transform__treedb_value_2_table_value(gobj, col, value, row, field)
|
|
|
1869
1901
|
}
|
|
1870
1902
|
break;
|
|
1871
1903
|
|
|
1904
|
+
case "file": // an fkey into __assets__, read like any other
|
|
1872
1905
|
case "fkey": // Convert data from backend to frontend TABLE CELL
|
|
1873
1906
|
let new_value = [];
|
|
1874
1907
|
if(value) {
|
|
@@ -2639,6 +2672,12 @@ function transform__form_value_2_treedb_value(gobj, col, value, operation)
|
|
|
2639
2672
|
value = null;
|
|
2640
2673
|
break;
|
|
2641
2674
|
|
|
2675
|
+
/* A `file` column is written by the FORM, which sends the bare
|
|
2676
|
+
* id and lets treedb expand it (the client never learns the
|
|
2677
|
+
* derived hook name). In the table it is not editable in place. */
|
|
2678
|
+
case "file":
|
|
2679
|
+
break;
|
|
2680
|
+
|
|
2642
2681
|
case "fkey": // Convert data from frontend to backend
|
|
2643
2682
|
value = build_fkey_ref(gobj, col, value);
|
|
2644
2683
|
break;
|
|
@@ -3430,22 +3469,110 @@ function ac_form_save_record(gobj, event, kw, src)
|
|
|
3430
3469
|
return -1; /* Error already logged */
|
|
3431
3470
|
}
|
|
3432
3471
|
let mode = gobj_read_str_attr(src, "form_mode");
|
|
3472
|
+
|
|
3473
|
+
/*------------------------------------------------------*
|
|
3474
|
+
* Did anybody pick a file?
|
|
3475
|
+
*
|
|
3476
|
+
* A `File` is a host object and a kw is plain json, so the picked
|
|
3477
|
+
* files never came with the record: the form KEPT them and this is
|
|
3478
|
+
* where they are asked for. Nothing picked is the ordinary case.
|
|
3479
|
+
*------------------------------------------------------*/
|
|
3480
|
+
let picked = {};
|
|
3481
|
+
const answer = gobj_command(src, "get_picked_files", {}, gobj);
|
|
3482
|
+
if(answer && answer.result >= 0 && answer.data) {
|
|
3483
|
+
picked = answer.data;
|
|
3484
|
+
}
|
|
3485
|
+
const cols = Object.keys(picked);
|
|
3486
|
+
|
|
3487
|
+
if(cols.length === 0) {
|
|
3488
|
+
publish_treedb_write(gobj, mode, kw);
|
|
3489
|
+
|
|
3490
|
+
/* we are INSIDE the form's gobj_publish_event stack — never
|
|
3491
|
+
* destroy the publisher synchronously from a subscriber
|
|
3492
|
+
* callback: defer the close */
|
|
3493
|
+
setTimeout(function() {
|
|
3494
|
+
close_form_dialog(gobj);
|
|
3495
|
+
}, 0);
|
|
3496
|
+
return 0;
|
|
3497
|
+
}
|
|
3498
|
+
|
|
3499
|
+
/*------------------------------------------------------*
|
|
3500
|
+
* Reading a file is a PROMISE, and a resolved promise is an OS
|
|
3501
|
+
* notification: it enters the machine as an EVENT, never as a
|
|
3502
|
+
* chain of callbacks that the trace cannot see. The dialog stays
|
|
3503
|
+
* open until it lands, which is what tells the person that 40 MB
|
|
3504
|
+
* are still going up.
|
|
3505
|
+
*------------------------------------------------------*/
|
|
3506
|
+
Promise.all(cols.map((col) => {
|
|
3507
|
+
return yui_file_read(picked[col]).then((read) => {
|
|
3508
|
+
return {col: col, read: read};
|
|
3509
|
+
});
|
|
3510
|
+
})).then((reads) => {
|
|
3511
|
+
const picks = {};
|
|
3512
|
+
reads.forEach((r) => {
|
|
3513
|
+
picks[r.col] = r.read;
|
|
3514
|
+
});
|
|
3515
|
+
gobj_send_event(gobj, "EV_FILES_READ",
|
|
3516
|
+
{mode: mode, record: kw, picks: picks}, gobj
|
|
3517
|
+
);
|
|
3518
|
+
}).catch((e) => {
|
|
3519
|
+
gobj_send_event(gobj, "EV_FILES_FAILED",
|
|
3520
|
+
{reason: (e && e.message)? e.message: String(e)}, gobj
|
|
3521
|
+
);
|
|
3522
|
+
});
|
|
3523
|
+
|
|
3524
|
+
return 0;
|
|
3525
|
+
}
|
|
3526
|
+
|
|
3527
|
+
/************************************************************
|
|
3528
|
+
* The one place a write of this view leaves for the app.
|
|
3529
|
+
************************************************************/
|
|
3530
|
+
function publish_treedb_write(gobj, mode, record, jn_files)
|
|
3531
|
+
{
|
|
3532
|
+
const kw_write = {
|
|
3533
|
+
topic_name: gobj_read_str_attr(gobj, "topic_name"),
|
|
3534
|
+
record: record
|
|
3535
|
+
};
|
|
3536
|
+
if(jn_files && Object.keys(jn_files).length > 0) {
|
|
3537
|
+
/* Not a column: an instruction to the treedb write path,
|
|
3538
|
+
* consumed and dropped at the door. */
|
|
3539
|
+
kw_write.record.__files__ = jn_files;
|
|
3540
|
+
}
|
|
3433
3541
|
gobj_publish_event(
|
|
3434
3542
|
gobj,
|
|
3435
3543
|
(mode === "create")? "EV_CREATE_RECORD" : "EV_UPDATE_RECORD",
|
|
3436
|
-
|
|
3437
|
-
topic_name: gobj_read_str_attr(gobj, "topic_name"),
|
|
3438
|
-
record: kw
|
|
3439
|
-
}
|
|
3544
|
+
kw_write
|
|
3440
3545
|
);
|
|
3546
|
+
}
|
|
3441
3547
|
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
},
|
|
3548
|
+
/************************************************************
|
|
3549
|
+
* {mode, record, picks} -- the bytes are here now
|
|
3550
|
+
************************************************************/
|
|
3551
|
+
function ac_files_read(gobj, event, kw, src)
|
|
3552
|
+
{
|
|
3553
|
+
const {record, __files__} = yui_files_manifest(kw.picks, kw.record);
|
|
3554
|
+
publish_treedb_write(gobj, kw.mode, record, __files__);
|
|
3555
|
+
close_form_dialog(gobj);
|
|
3556
|
+
return 0;
|
|
3557
|
+
}
|
|
3448
3558
|
|
|
3559
|
+
/************************************************************
|
|
3560
|
+
* {reason} -- the browser could not read what was picked
|
|
3561
|
+
*
|
|
3562
|
+
* The form stays OPEN: the record is still there, and closing it would
|
|
3563
|
+
* throw away what the person typed for a failure that is not theirs.
|
|
3564
|
+
************************************************************/
|
|
3565
|
+
function ac_files_failed(gobj, event, kw, src)
|
|
3566
|
+
{
|
|
3567
|
+
log_error(`${gobj_short_name(gobj)}: cannot read the picked file: ${kw.reason}`);
|
|
3568
|
+
/* An i18n KEY, not a sentence: the dialog renders it with `i18n`, so
|
|
3569
|
+
* a sentence here would be a key nobody defined and would render as
|
|
3570
|
+
* itself in every language. The reason is DATA and stays in the log,
|
|
3571
|
+
* where it can be read without being translated. */
|
|
3572
|
+
yui_shell_confirm_ok(
|
|
3573
|
+
yui_shell_of(gobj), 'cannot read the picked file',
|
|
3574
|
+
{t: t, ok_label: "accept"}
|
|
3575
|
+
);
|
|
3449
3576
|
return 0;
|
|
3450
3577
|
}
|
|
3451
3578
|
|
|
@@ -4041,6 +4168,8 @@ function create_gclass(gclass_name)
|
|
|
4041
4168
|
|
|
4042
4169
|
["EV_EDITION_MODE", ac_edition_mode, null],
|
|
4043
4170
|
["EV_SAVE_RECORD", ac_form_save_record, null],
|
|
4171
|
+
["EV_FILES_READ", ac_files_read, null],
|
|
4172
|
+
["EV_FILES_FAILED", ac_files_failed, null],
|
|
4044
4173
|
|
|
4045
4174
|
["EV_NEW_ROW", ac_new_row, null],
|
|
4046
4175
|
["EV_DELETE_ROWS", ac_delete_rows, null],
|
|
@@ -4082,6 +4211,8 @@ function create_gclass(gclass_name)
|
|
|
4082
4211
|
|
|
4083
4212
|
["EV_EDITION_MODE", 0],
|
|
4084
4213
|
["EV_SAVE_RECORD", 0],
|
|
4214
|
+
["EV_FILES_READ", 0],
|
|
4215
|
+
["EV_FILES_FAILED", 0],
|
|
4085
4216
|
["EV_NEW_ROW", 0],
|
|
4086
4217
|
["EV_DELETE_ROWS", 0],
|
|
4087
4218
|
["EV_COPY_ROWS", 0],
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* yui_file_field.css
|
|
3
|
+
*
|
|
4
|
+
* The control for a treedb `file` column. One row: the button, what it
|
|
5
|
+
* is holding, and the way to clear it.
|
|
6
|
+
*
|
|
7
|
+
* No transitions -- the state changes the moment the person acts on it.
|
|
8
|
+
*
|
|
9
|
+
* Copyright (c) 2026, ArtGins.
|
|
10
|
+
* All Rights Reserved.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
.FILE_FIELD {
|
|
14
|
+
display: flex;
|
|
15
|
+
align-items: center;
|
|
16
|
+
gap: 0.5rem;
|
|
17
|
+
flex-wrap: wrap;
|
|
18
|
+
min-width: 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.FILE_FIELD .FILE_PICK,
|
|
22
|
+
.FILE_FIELD .FILE_CLEAR {
|
|
23
|
+
flex-shrink: 0;
|
|
24
|
+
transition: none;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.FILE_FIELD .FILE_PICK_ICON,
|
|
28
|
+
.FILE_FIELD .FILE_CLEAR_ICON {
|
|
29
|
+
margin-right: 0.25rem;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.FILE_FIELD .FILE_CLEAR_ICON {
|
|
33
|
+
margin-right: 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/*
|
|
37
|
+
* The name is the part that must give way: an id or a long filename
|
|
38
|
+
* cannot push the buttons out of the row.
|
|
39
|
+
*/
|
|
40
|
+
.FILE_FIELD .FILE_NAME {
|
|
41
|
+
flex: 1 1 8rem;
|
|
42
|
+
min-width: 0;
|
|
43
|
+
overflow: hidden;
|
|
44
|
+
text-overflow: ellipsis;
|
|
45
|
+
white-space: nowrap;
|
|
46
|
+
font-family: monospace;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.FILE_FIELD .FILE_SIZE {
|
|
50
|
+
flex-shrink: 0;
|
|
51
|
+
opacity: 0.7;
|
|
52
|
+
font-size: 0.85em;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.FILE_FIELD .FILE_STATE {
|
|
56
|
+
flex-shrink: 0;
|
|
57
|
+
font-size: 0.85em;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.FILE_FIELD .FILE_STATE.is-picked {
|
|
61
|
+
font-weight: bold;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.FILE_FIELD .FILE_STATE.is-empty {
|
|
65
|
+
opacity: 0.6;
|
|
66
|
+
font-style: italic;
|
|
67
|
+
}
|