@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/dist/gobj-ui.es.js
CHANGED
|
@@ -5827,6 +5827,414 @@ function register_c_yui_wizard() {
|
|
|
5827
5827
|
return create_gclass$17(GCLASS_NAME$17);
|
|
5828
5828
|
}
|
|
5829
5829
|
//#endregion
|
|
5830
|
+
//#region src/yui_file_field.js
|
|
5831
|
+
/***********************************************************************
|
|
5832
|
+
* yui_file_field.js
|
|
5833
|
+
*
|
|
5834
|
+
* The control that PICKS a file for a treedb `file` column, and the
|
|
5835
|
+
* manifest that carries its bytes beside the record.
|
|
5836
|
+
*
|
|
5837
|
+
* A column flagged ['fkey','file'] holds an fkey into the treedb's
|
|
5838
|
+
* system topic `__assets__`: the bytes live on disk under the treedb,
|
|
5839
|
+
* the index in memory, and the record names the asset by its sha256.
|
|
5840
|
+
* So this control does NOT put a file in the record. It keeps the
|
|
5841
|
+
* `File` the person picked and hands up an IDENTITY, and the bytes
|
|
5842
|
+
* travel beside the record in a `__files__` manifest keyed by column.
|
|
5843
|
+
*
|
|
5844
|
+
* Three things about that are not obvious and are the whole reason
|
|
5845
|
+
* this module is separate:
|
|
5846
|
+
*
|
|
5847
|
+
* - **The file is read at SAVE, not at pick.** An `<input type=file>`
|
|
5848
|
+
* hands over a `File`, which is a REFERENCE and not the bytes.
|
|
5849
|
+
* Picking shows a name and a size and reads nothing, so cancelling
|
|
5850
|
+
* a form does not mean a 40 MB video was read for nothing.
|
|
5851
|
+
* - **A `File` cannot travel in a kw.** A kw is plain json -- the
|
|
5852
|
+
* machine trace serialises it -- and a `File` is a host object. The
|
|
5853
|
+
* form KEEPS it and the host asks for it through a local method.
|
|
5854
|
+
* - **Reading is a promise, so saving stops being synchronous**, and
|
|
5855
|
+
* a resolved promise is an OS notification that must enter the
|
|
5856
|
+
* machine as an EVENT, never as a chain of callbacks. That is the
|
|
5857
|
+
* HOST's business; what lives here is the promise it awaits.
|
|
5858
|
+
*
|
|
5859
|
+
* The pure half (manifest, size, sha256, the name of a picked file)
|
|
5860
|
+
* is tested without a DOM; the DOM half builds the control.
|
|
5861
|
+
*
|
|
5862
|
+
* Using Bulma Framework (https://bulma.io)
|
|
5863
|
+
*
|
|
5864
|
+
* Copyright (c) 2026, ArtGins.
|
|
5865
|
+
* All Rights Reserved.
|
|
5866
|
+
***********************************************************************/
|
|
5867
|
+
var YUI_FILE_ACCEPT = [
|
|
5868
|
+
"image/jpeg",
|
|
5869
|
+
"image/png",
|
|
5870
|
+
"image/webp",
|
|
5871
|
+
"image/gif",
|
|
5872
|
+
"application/pdf",
|
|
5873
|
+
"video/mp4",
|
|
5874
|
+
"video/webm",
|
|
5875
|
+
"video/quicktime",
|
|
5876
|
+
"video/ogg",
|
|
5877
|
+
"video/x-matroska",
|
|
5878
|
+
"audio/mpeg",
|
|
5879
|
+
"audio/mp4",
|
|
5880
|
+
"audio/ogg",
|
|
5881
|
+
"audio/wav",
|
|
5882
|
+
"audio/webm",
|
|
5883
|
+
"audio/flac"
|
|
5884
|
+
].join(",");
|
|
5885
|
+
/***************************
|
|
5886
|
+
* The pure half
|
|
5887
|
+
***************************/
|
|
5888
|
+
/***************************************************************************
|
|
5889
|
+
* A size a person reads. Bytes up to 1 KB, then one decimal.
|
|
5890
|
+
***************************************************************************/
|
|
5891
|
+
function yui_file_size_label(bytes) {
|
|
5892
|
+
let n = Number(bytes);
|
|
5893
|
+
if (!isFinite(n) || n < 0) return "";
|
|
5894
|
+
if (n < 1024) return `${n} B`;
|
|
5895
|
+
const units = [
|
|
5896
|
+
"KB",
|
|
5897
|
+
"MB",
|
|
5898
|
+
"GB",
|
|
5899
|
+
"TB"
|
|
5900
|
+
];
|
|
5901
|
+
let i = -1;
|
|
5902
|
+
do {
|
|
5903
|
+
n = n / 1024;
|
|
5904
|
+
i++;
|
|
5905
|
+
} while (n >= 1024 && i < units.length - 1);
|
|
5906
|
+
return `${n.toFixed(1)} ${units[i]}`;
|
|
5907
|
+
}
|
|
5908
|
+
/***************************************************************************
|
|
5909
|
+
* The sha256 of an ArrayBuffer, lowercase hex, or null.
|
|
5910
|
+
*
|
|
5911
|
+
* NULL is a legal answer and not a failure: `crypto.subtle` exists only
|
|
5912
|
+
* in a SECURE CONTEXT, so a dev server on plain http has none. Then the
|
|
5913
|
+
* record carries no id and treedb fills it -- the write path hashes what
|
|
5914
|
+
* arrives anyway, because it never trusts the client's id. What the hash
|
|
5915
|
+
* buys here is an integrity check the backend can make (a wrong id with
|
|
5916
|
+
* good bytes is refused), not the identity itself.
|
|
5917
|
+
***************************************************************************/
|
|
5918
|
+
async function yui_file_sha256(buffer) {
|
|
5919
|
+
const subtle = typeof crypto !== "undefined" && crypto.subtle;
|
|
5920
|
+
if (!subtle) return null;
|
|
5921
|
+
try {
|
|
5922
|
+
const digest = await subtle.digest("SHA-256", buffer);
|
|
5923
|
+
return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
5924
|
+
} catch (e) {
|
|
5925
|
+
log_error(`cannot hash the picked file: ${e && e.message ? e.message : e}`);
|
|
5926
|
+
return null;
|
|
5927
|
+
}
|
|
5928
|
+
}
|
|
5929
|
+
/***************************************************************************
|
|
5930
|
+
* base64 of an ArrayBuffer.
|
|
5931
|
+
*
|
|
5932
|
+
* In chunks because `String.fromCharCode(...bytes)` spreads the whole
|
|
5933
|
+
* array onto the call stack, and a few hundred KB is already a
|
|
5934
|
+
* RangeError -- which for a file picker is every file that matters.
|
|
5935
|
+
***************************************************************************/
|
|
5936
|
+
function yui_array_buffer_to_base64(buffer) {
|
|
5937
|
+
const bytes = new Uint8Array(buffer);
|
|
5938
|
+
const CHUNK = 32768;
|
|
5939
|
+
let binary = "";
|
|
5940
|
+
for (let i = 0; i < bytes.length; i += CHUNK) binary += String.fromCharCode.apply(null, bytes.subarray(i, i + CHUNK));
|
|
5941
|
+
return btoa(binary);
|
|
5942
|
+
}
|
|
5943
|
+
/***************************************************************************
|
|
5944
|
+
* Read ONE picked file into what the manifest carries.
|
|
5945
|
+
*
|
|
5946
|
+
* -> {content64, content_type, original_name, size, id}
|
|
5947
|
+
*
|
|
5948
|
+
* `id` is the sha256 when it could be computed, and "" when it could
|
|
5949
|
+
* not. Nothing else in here is optional: treedb checks the size on the
|
|
5950
|
+
* base64 before decoding it, and the type against the BYTES.
|
|
5951
|
+
***************************************************************************/
|
|
5952
|
+
async function yui_file_read(file) {
|
|
5953
|
+
const buffer = await file.arrayBuffer();
|
|
5954
|
+
const id = await yui_file_sha256(buffer);
|
|
5955
|
+
return {
|
|
5956
|
+
content64: yui_array_buffer_to_base64(buffer),
|
|
5957
|
+
content_type: file.type || "",
|
|
5958
|
+
original_name: file.name || "",
|
|
5959
|
+
size: file.size,
|
|
5960
|
+
id: id || ""
|
|
5961
|
+
};
|
|
5962
|
+
}
|
|
5963
|
+
/***************************************************************************
|
|
5964
|
+
* The write a record with picked files becomes.
|
|
5965
|
+
*
|
|
5966
|
+
* picks {col: read} what yui_file_read() answered, per column
|
|
5967
|
+
* record the record the form collected (MODIFIED: each picked
|
|
5968
|
+
* column takes the id, or "" when there is none)
|
|
5969
|
+
*
|
|
5970
|
+
* -> {record, __files__}
|
|
5971
|
+
*
|
|
5972
|
+
* `__files__` is not a column: it is an instruction to the write path,
|
|
5973
|
+
* consumed and dropped at the door. The bytes never go INSIDE the
|
|
5974
|
+
* column -- a field that CARRIES the bytes and a field that KEEPS them
|
|
5975
|
+
* are one word apart and 460 MB of RAM apart.
|
|
5976
|
+
***************************************************************************/
|
|
5977
|
+
function yui_files_manifest(picks, record) {
|
|
5978
|
+
const out = record || {};
|
|
5979
|
+
const manifest = {};
|
|
5980
|
+
Object.keys(picks || {}).forEach((col) => {
|
|
5981
|
+
const read = picks[col];
|
|
5982
|
+
if (!read) return;
|
|
5983
|
+
manifest[col] = {
|
|
5984
|
+
content64: read.content64,
|
|
5985
|
+
content_type: read.content_type,
|
|
5986
|
+
original_name: read.original_name
|
|
5987
|
+
};
|
|
5988
|
+
out[col] = read.id || "";
|
|
5989
|
+
});
|
|
5990
|
+
return {
|
|
5991
|
+
record: out,
|
|
5992
|
+
__files__: manifest
|
|
5993
|
+
};
|
|
5994
|
+
}
|
|
5995
|
+
/***************************
|
|
5996
|
+
* The DOM half
|
|
5997
|
+
***************************/
|
|
5998
|
+
/***************************************************************************
|
|
5999
|
+
* What the control shows for what the column HOLDS today: the asset id,
|
|
6000
|
+
* shortened. A sha256 in full is 64 characters of noise in a form.
|
|
6001
|
+
***************************************************************************/
|
|
6002
|
+
function yui_file_id_label(id) {
|
|
6003
|
+
if (!id) return "";
|
|
6004
|
+
return id.length > 16 ? `${id.slice(0, 8)}…${id.slice(-4)}` : id;
|
|
6005
|
+
}
|
|
6006
|
+
/***************************************************************************
|
|
6007
|
+
* The control for a `file` column.
|
|
6008
|
+
*
|
|
6009
|
+
* name the column
|
|
6010
|
+
* value the asset id the record holds today ("" when none)
|
|
6011
|
+
* readonly draw it, do not let it change
|
|
6012
|
+
* on_pick (file|null) -> void. NULL is "the person cleared it".
|
|
6013
|
+
*
|
|
6014
|
+
* Returns the `<div class="control">` content, and hangs the state the
|
|
6015
|
+
* form reads off the returned element:
|
|
6016
|
+
*
|
|
6017
|
+
* $control.yui_file the picked File, or null
|
|
6018
|
+
* $control.yui_file_value the id the column keeps ("" = cleared)
|
|
6019
|
+
*
|
|
6020
|
+
* The `<input type="file">` is hidden and driven by a button, because a
|
|
6021
|
+
* bare file input cannot be styled and says "Sin archivos seleccionados"
|
|
6022
|
+
* in the browser's own language, not the app's.
|
|
6023
|
+
***************************************************************************/
|
|
6024
|
+
function yui_file_control(gobj, { name, value, readonly, accept, on_pick }) {
|
|
6025
|
+
const has_asset = !!value;
|
|
6026
|
+
const $input = createElement2(["input", {
|
|
6027
|
+
type: "file",
|
|
6028
|
+
class: "FILE_INPUT",
|
|
6029
|
+
name: `${name}__file`,
|
|
6030
|
+
accept: accept || YUI_FILE_ACCEPT,
|
|
6031
|
+
hidden: ""
|
|
6032
|
+
}]);
|
|
6033
|
+
const $pick = createElement2([
|
|
6034
|
+
"button",
|
|
6035
|
+
{
|
|
6036
|
+
type: "button",
|
|
6037
|
+
class: "FILE_PICK button is-small",
|
|
6038
|
+
"data-i18n-title": "choose a file",
|
|
6039
|
+
"aria-label": "choose a file"
|
|
6040
|
+
},
|
|
6041
|
+
[["span", { class: "FILE_PICK_ICON yi-upload" }], [
|
|
6042
|
+
"span",
|
|
6043
|
+
{
|
|
6044
|
+
class: "FILE_PICK_LABEL",
|
|
6045
|
+
i18n: "choose a file"
|
|
6046
|
+
},
|
|
6047
|
+
"choose a file"
|
|
6048
|
+
]]
|
|
6049
|
+
]);
|
|
6050
|
+
const $clear = createElement2([
|
|
6051
|
+
"button",
|
|
6052
|
+
{
|
|
6053
|
+
type: "button",
|
|
6054
|
+
class: "FILE_CLEAR button is-small",
|
|
6055
|
+
"data-i18n-title": "remove the file",
|
|
6056
|
+
"aria-label": "remove the file"
|
|
6057
|
+
},
|
|
6058
|
+
[["span", { class: "FILE_CLEAR_ICON yi-trash" }]]
|
|
6059
|
+
]);
|
|
6060
|
+
const $name = createElement2(["span", { class: "FILE_NAME" }]);
|
|
6061
|
+
const $size = createElement2(["span", { class: "FILE_SIZE" }]);
|
|
6062
|
+
const $state = createElement2(["span", { class: "FILE_STATE" }]);
|
|
6063
|
+
const $control = createElement2([
|
|
6064
|
+
"div",
|
|
6065
|
+
{ class: "FILE_FIELD" },
|
|
6066
|
+
[
|
|
6067
|
+
$input,
|
|
6068
|
+
$pick,
|
|
6069
|
+
$name,
|
|
6070
|
+
$size,
|
|
6071
|
+
$state,
|
|
6072
|
+
$clear
|
|
6073
|
+
]
|
|
6074
|
+
]);
|
|
6075
|
+
$control.yui_file = null;
|
|
6076
|
+
$control.yui_file_value = value || "";
|
|
6077
|
+
const render = () => {
|
|
6078
|
+
const picked = $control.yui_file;
|
|
6079
|
+
const kept = $control.yui_file_value;
|
|
6080
|
+
$name.textContent = picked ? picked.name || "" : yui_file_id_label(kept);
|
|
6081
|
+
$size.textContent = picked ? yui_file_size_label(picked.size) : "";
|
|
6082
|
+
$state.className = "FILE_STATE";
|
|
6083
|
+
$state.removeAttribute("data-i18n");
|
|
6084
|
+
$state.textContent = "";
|
|
6085
|
+
if (picked) {
|
|
6086
|
+
$state.classList.add("is-picked");
|
|
6087
|
+
$state.setAttribute("data-i18n", "not saved yet");
|
|
6088
|
+
$state.textContent = "not saved yet";
|
|
6089
|
+
} else if (!kept) {
|
|
6090
|
+
$state.classList.add("is-empty");
|
|
6091
|
+
$state.setAttribute("data-i18n", "no file");
|
|
6092
|
+
$state.textContent = "no file";
|
|
6093
|
+
}
|
|
6094
|
+
$clear.hidden = readonly || !picked && !kept;
|
|
6095
|
+
$pick.hidden = !!readonly;
|
|
6096
|
+
};
|
|
6097
|
+
if (!readonly) {
|
|
6098
|
+
$pick.addEventListener("click", () => {
|
|
6099
|
+
$input.click();
|
|
6100
|
+
});
|
|
6101
|
+
$input.addEventListener("change", () => {
|
|
6102
|
+
const file = $input.files && $input.files.length ? $input.files[0] : null;
|
|
6103
|
+
$control.yui_file = file;
|
|
6104
|
+
render();
|
|
6105
|
+
if (on_pick) on_pick(file);
|
|
6106
|
+
});
|
|
6107
|
+
$clear.addEventListener("click", () => {
|
|
6108
|
+
$control.yui_file = null;
|
|
6109
|
+
$control.yui_file_value = "";
|
|
6110
|
+
$input.value = "";
|
|
6111
|
+
render();
|
|
6112
|
+
if (on_pick) on_pick(null);
|
|
6113
|
+
});
|
|
6114
|
+
}
|
|
6115
|
+
$control.yui_file_render = render;
|
|
6116
|
+
render();
|
|
6117
|
+
return {
|
|
6118
|
+
$control,
|
|
6119
|
+
$input,
|
|
6120
|
+
has_asset
|
|
6121
|
+
};
|
|
6122
|
+
}
|
|
6123
|
+
//#endregion
|
|
6124
|
+
//#region src/yui_asset.js
|
|
6125
|
+
function ref_to_id(ref) {
|
|
6126
|
+
if (!ref) return null;
|
|
6127
|
+
if (typeof ref === "string") {
|
|
6128
|
+
if (ref.indexOf("^") < 0) return ref;
|
|
6129
|
+
const parts = ref.split("^");
|
|
6130
|
+
return parts.length >= 2 && parts[1] ? parts[1] : null;
|
|
6131
|
+
}
|
|
6132
|
+
if (typeof ref === "object" && typeof ref.id === "string") return ref.id;
|
|
6133
|
+
return null;
|
|
6134
|
+
}
|
|
6135
|
+
/***************************************************************************
|
|
6136
|
+
* Every asset id a column names, in order. Always an array, possibly empty.
|
|
6137
|
+
***************************************************************************/
|
|
6138
|
+
function yui_asset_ids(ref) {
|
|
6139
|
+
if (Array.isArray(ref)) return ref.map(ref_to_id).filter((id) => !!id);
|
|
6140
|
+
const id = ref_to_id(ref);
|
|
6141
|
+
return id ? [id] : [];
|
|
6142
|
+
}
|
|
6143
|
+
/***************************************************************************
|
|
6144
|
+
* The first asset id a column names, or null.
|
|
6145
|
+
***************************************************************************/
|
|
6146
|
+
function yui_asset_id(ref) {
|
|
6147
|
+
const ids = yui_asset_ids(ref);
|
|
6148
|
+
return ids.length ? ids[0] : null;
|
|
6149
|
+
}
|
|
6150
|
+
/***************************************************************************
|
|
6151
|
+
* Turn a `get-asset` answer into something an element can load.
|
|
6152
|
+
*
|
|
6153
|
+
* Returns null when the answer carries neither -- which the caller must
|
|
6154
|
+
* treat as "missing", never as an empty src: an <img src=""> reloads the
|
|
6155
|
+
* PAGE in some browsers.
|
|
6156
|
+
***************************************************************************/
|
|
6157
|
+
function yui_asset_src(answer) {
|
|
6158
|
+
if (!answer) return null;
|
|
6159
|
+
if (answer.mode === "url" && answer.url) return answer.url;
|
|
6160
|
+
if (answer.mode === "inline" && answer.content64) return `data:${answer.content_type || "application/octet-stream"};base64,${answer.content64}`;
|
|
6161
|
+
return null;
|
|
6162
|
+
}
|
|
6163
|
+
/***************************************************************************
|
|
6164
|
+
* The marker for an asset that is not there.
|
|
6165
|
+
*
|
|
6166
|
+
* A missing image used to leave a broken box and no word about it, which
|
|
6167
|
+
* is indistinguishable from a slow one and from a bug. This says so, and
|
|
6168
|
+
* it says WHICH: `detail` is data (a name, a path) and is never
|
|
6169
|
+
* translated, while the label carries its i18n key so a language change
|
|
6170
|
+
* re-translates it.
|
|
6171
|
+
***************************************************************************/
|
|
6172
|
+
function yui_asset_missing(detail, opts = {}) {
|
|
6173
|
+
const key = opts.key || "asset not available";
|
|
6174
|
+
const children = [["span", { class: "ASSET_MISSING_ICON yi-triangle-exclamation" }], [
|
|
6175
|
+
"span",
|
|
6176
|
+
{
|
|
6177
|
+
class: "ASSET_MISSING_LABEL",
|
|
6178
|
+
i18n: key
|
|
6179
|
+
},
|
|
6180
|
+
key
|
|
6181
|
+
]];
|
|
6182
|
+
if (detail) children.push([
|
|
6183
|
+
"span",
|
|
6184
|
+
{ class: "ASSET_MISSING_DETAIL" },
|
|
6185
|
+
String(detail)
|
|
6186
|
+
]);
|
|
6187
|
+
const attrs = { class: `ASSET_MISSING ${opts.class || ""}`.trim() };
|
|
6188
|
+
if (detail) attrs.title = String(detail);
|
|
6189
|
+
else attrs["data-i18n-title"] = key;
|
|
6190
|
+
return createElement2([
|
|
6191
|
+
"div",
|
|
6192
|
+
attrs,
|
|
6193
|
+
children
|
|
6194
|
+
]);
|
|
6195
|
+
}
|
|
6196
|
+
/***************************************************************************
|
|
6197
|
+
* The element for one asset.
|
|
6198
|
+
*
|
|
6199
|
+
* The KIND comes from the content type the backend stored, not from the
|
|
6200
|
+
* name a person typed: video and audio are assets too, and an <img> whose
|
|
6201
|
+
* src is a film shows the broken box this module exists to remove.
|
|
6202
|
+
*
|
|
6203
|
+
* `detail` is what the missing marker shows -- pass the original name or
|
|
6204
|
+
* the source path, the thing a person can act on.
|
|
6205
|
+
***************************************************************************/
|
|
6206
|
+
function yui_asset_element(answer, opts = {}) {
|
|
6207
|
+
const src = yui_asset_src(answer);
|
|
6208
|
+
if (!src) return yui_asset_missing(opts.detail, opts);
|
|
6209
|
+
const ct = (answer.content_type || "").toLowerCase();
|
|
6210
|
+
const cls = `ASSET_MEDIA ${opts.class || ""}`.trim();
|
|
6211
|
+
let el;
|
|
6212
|
+
if (ct.startsWith("video/")) el = createElement2(["video", {
|
|
6213
|
+
class: cls,
|
|
6214
|
+
controls: "",
|
|
6215
|
+
preload: "metadata"
|
|
6216
|
+
}]);
|
|
6217
|
+
else if (ct.startsWith("audio/")) el = createElement2(["audio", {
|
|
6218
|
+
class: cls,
|
|
6219
|
+
controls: "",
|
|
6220
|
+
preload: "metadata"
|
|
6221
|
+
}]);
|
|
6222
|
+
else {
|
|
6223
|
+
el = createElement2(["img", {
|
|
6224
|
+
class: cls,
|
|
6225
|
+
loading: "lazy"
|
|
6226
|
+
}]);
|
|
6227
|
+
el.alt = opts.alt || opts.detail || "";
|
|
6228
|
+
}
|
|
6229
|
+
el.src = src;
|
|
6230
|
+
el.onerror = () => {
|
|
6231
|
+
log_error(`asset could not be loaded: ${opts.detail || src}`);
|
|
6232
|
+
const $marker = yui_asset_missing(opts.detail, opts);
|
|
6233
|
+
if (el.parentNode) el.parentNode.replaceChild($marker, el);
|
|
6234
|
+
};
|
|
6235
|
+
return el;
|
|
6236
|
+
}
|
|
6237
|
+
//#endregion
|
|
5830
6238
|
//#region src/yui_inputs.js
|
|
5831
6239
|
/***************************************************************
|
|
5832
6240
|
* The ✕ shows only when there is something to clear AND the field
|
|
@@ -6356,13 +6764,7 @@ var attrs_table$16 = [
|
|
|
6356
6764
|
SDATA(data_type_t.DTP_BOOLEAN, "with_checkbox", 0, false, "Show a first column with checkbox to select rows"),
|
|
6357
6765
|
SDATA(data_type_t.DTP_BOOLEAN, "with_rownum", 0, false, "Print first column with row number"),
|
|
6358
6766
|
SDATA(data_type_t.DTP_BOOLEAN, "with_delete_row_btn", 0, false, "Print last column with delete row button"),
|
|
6359
|
-
SDATA(data_type_t.DTP_JSON, "toolbar", 0, [
|
|
6360
|
-
"save",
|
|
6361
|
-
"undo",
|
|
6362
|
-
"clear",
|
|
6363
|
-
"copy",
|
|
6364
|
-
"paste"
|
|
6365
|
-
], "Toolbar buttons to show, in order: save, undo, clear, copy, paste. [] hides the toolbar"),
|
|
6767
|
+
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"),
|
|
6366
6768
|
SDATA(data_type_t.DTP_JSON, "tabulator_settings", 0, {
|
|
6367
6769
|
layout: "fitDataFill",
|
|
6368
6770
|
validationMode: "highlight",
|
|
@@ -6541,7 +6943,7 @@ function toolbar_button$1(gobj, name) {
|
|
|
6541
6943
|
case "clear": return [
|
|
6542
6944
|
"button",
|
|
6543
6945
|
{
|
|
6544
|
-
class: "button p-1",
|
|
6946
|
+
class: "button p-1 button-clear",
|
|
6545
6947
|
title: "clear",
|
|
6546
6948
|
"aria-label": "clear"
|
|
6547
6949
|
},
|
|
@@ -6565,7 +6967,7 @@ function toolbar_button$1(gobj, name) {
|
|
|
6565
6967
|
case "copy": return [
|
|
6566
6968
|
"button",
|
|
6567
6969
|
{
|
|
6568
|
-
class: "button p-1",
|
|
6970
|
+
class: "button p-1 button-copy",
|
|
6569
6971
|
title: "copy",
|
|
6570
6972
|
"aria-label": "copy"
|
|
6571
6973
|
},
|
|
@@ -6589,7 +6991,7 @@ function toolbar_button$1(gobj, name) {
|
|
|
6589
6991
|
case "paste": return [
|
|
6590
6992
|
"button",
|
|
6591
6993
|
{
|
|
6592
|
-
class: "button p-1",
|
|
6994
|
+
class: "button p-1 button-paste",
|
|
6593
6995
|
title: "paste",
|
|
6594
6996
|
"aria-label": "paste"
|
|
6595
6997
|
},
|
|
@@ -6806,6 +7208,9 @@ function build_form_field_conf(gobj, field_desc) {
|
|
|
6806
7208
|
field_conf.tag = "select2";
|
|
6807
7209
|
field_conf.options = get_fkey_select_options(gobj, field_desc);
|
|
6808
7210
|
break;
|
|
7211
|
+
case "file":
|
|
7212
|
+
field_conf.tag = "file";
|
|
7213
|
+
break;
|
|
6809
7214
|
case "image":
|
|
6810
7215
|
case "icon":
|
|
6811
7216
|
case "string":
|
|
@@ -7491,6 +7896,17 @@ function create_form_field(gobj, $form, prefix, field_desc, { tag, inputType, na
|
|
|
7491
7896
|
$control.appendChild($extend);
|
|
7492
7897
|
break;
|
|
7493
7898
|
}
|
|
7899
|
+
case "file":
|
|
7900
|
+
$extend = yui_file_control(gobj, {
|
|
7901
|
+
name,
|
|
7902
|
+
value: "",
|
|
7903
|
+
readonly,
|
|
7904
|
+
on_pick: function() {
|
|
7905
|
+
gobj_send_event(gobj, "EV_RECORD_CHANGED", {}, gobj);
|
|
7906
|
+
}
|
|
7907
|
+
}).$control;
|
|
7908
|
+
$control.appendChild($extend);
|
|
7909
|
+
break;
|
|
7494
7910
|
default:
|
|
7495
7911
|
$extend = createElement2([
|
|
7496
7912
|
"strong",
|
|
@@ -7991,6 +8407,9 @@ function get_form_values(gobj, $form) {
|
|
|
7991
8407
|
case "radio":
|
|
7992
8408
|
value = getInputValue($input);
|
|
7993
8409
|
break;
|
|
8410
|
+
case "file":
|
|
8411
|
+
value = $input.yui_file_value || "";
|
|
8412
|
+
break;
|
|
7994
8413
|
case "jsoneditor":
|
|
7995
8414
|
let jsoneditor = $input.jsoneditor;
|
|
7996
8415
|
if (jsoneditor) {
|
|
@@ -8047,6 +8466,13 @@ function clear_data(gobj, $form) {
|
|
|
8047
8466
|
else $input.value = null;
|
|
8048
8467
|
}
|
|
8049
8468
|
});
|
|
8469
|
+
$form.querySelectorAll(".FILE_FIELD").forEach(($file) => {
|
|
8470
|
+
$file.yui_file = null;
|
|
8471
|
+
$file.yui_file_value = "";
|
|
8472
|
+
let $picker = $file.querySelector(".FILE_INPUT");
|
|
8473
|
+
if ($picker) $picker.value = "";
|
|
8474
|
+
if ($file.yui_file_render) $file.yui_file_render();
|
|
8475
|
+
});
|
|
8050
8476
|
$form.querySelectorAll(".yui-tabulator-table").forEach(($table) => {
|
|
8051
8477
|
if ($table.tabulator.initialized) $table.tabulator.clearData();
|
|
8052
8478
|
$table.querySelectorAll(".yui-tabulator-table-nested").forEach(($table_nested) => {
|
|
@@ -8116,6 +8542,11 @@ function set_form_values(gobj, template, $form, record) {
|
|
|
8116
8542
|
value = treedb_value_2_form_value(gobj, field_desc, value);
|
|
8117
8543
|
if (value === void 0) value = null;
|
|
8118
8544
|
switch (tag) {
|
|
8545
|
+
case "file":
|
|
8546
|
+
$input.yui_file = null;
|
|
8547
|
+
$input.yui_file_value = yui_asset_id(value) || "";
|
|
8548
|
+
if ($input.yui_file_render) $input.yui_file_render();
|
|
8549
|
+
break;
|
|
8119
8550
|
case "input":
|
|
8120
8551
|
switch (input_type) {
|
|
8121
8552
|
case "datetime-local":
|
|
@@ -8544,11 +8975,63 @@ function ac_timeout(gobj, event, kw, src) {
|
|
|
8544
8975
|
/***************************
|
|
8545
8976
|
* FSM
|
|
8546
8977
|
***************************/
|
|
8978
|
+
/************************************************************
|
|
8979
|
+
* Framework Method command
|
|
8980
|
+
************************************************************/
|
|
8981
|
+
function mt_command_parser$2(gobj, command, kw, src) {
|
|
8982
|
+
switch (command) {
|
|
8983
|
+
case "get_picked_files": return cmd_get_picked_files(gobj, command, kw, src);
|
|
8984
|
+
default:
|
|
8985
|
+
log_error(`${gobj_short_name(gobj)}: command not found: ${command}`);
|
|
8986
|
+
return {
|
|
8987
|
+
result: -1,
|
|
8988
|
+
comment: `Command not found: ${command}`,
|
|
8989
|
+
schema: null,
|
|
8990
|
+
data: null
|
|
8991
|
+
};
|
|
8992
|
+
}
|
|
8993
|
+
}
|
|
8994
|
+
/************************************************************
|
|
8995
|
+
* The files a person picked and the record cannot carry.
|
|
8996
|
+
*
|
|
8997
|
+
* A `File` is a host object and a kw is plain json -- the machine trace
|
|
8998
|
+
* serialises it -- so the picked files never travel in an event. The
|
|
8999
|
+
* form KEEPS them and the HOST asks for them here, at save, and turns
|
|
9000
|
+
* them into the `__files__` manifest that rides beside the record.
|
|
9001
|
+
*
|
|
9002
|
+
* Answers `data: {col: File}`, empty when nothing was picked, which is
|
|
9003
|
+
* the ordinary case and not an error.
|
|
9004
|
+
************************************************************/
|
|
9005
|
+
function cmd_get_picked_files(gobj, cmd, kw, src) {
|
|
9006
|
+
const files = {};
|
|
9007
|
+
const $container = gobj_read_attr(gobj, "$container");
|
|
9008
|
+
const $form = $container ? $container.querySelector("form") : null;
|
|
9009
|
+
if (!$form) {
|
|
9010
|
+
log_error(`${gobj_short_name(gobj)}: no form to read the picked files from`);
|
|
9011
|
+
return {
|
|
9012
|
+
result: -1,
|
|
9013
|
+
comment: "no form",
|
|
9014
|
+
schema: null,
|
|
9015
|
+
data: null
|
|
9016
|
+
};
|
|
9017
|
+
}
|
|
9018
|
+
$form.querySelectorAll(".yui-form-data-input").forEach(($input) => {
|
|
9019
|
+
if ($input.dataset.form_tag !== "file") return;
|
|
9020
|
+
if ($input.yui_file) files[$input.dataset.treedb_name] = $input.yui_file;
|
|
9021
|
+
});
|
|
9022
|
+
return {
|
|
9023
|
+
result: 0,
|
|
9024
|
+
comment: "",
|
|
9025
|
+
schema: null,
|
|
9026
|
+
data: files
|
|
9027
|
+
};
|
|
9028
|
+
}
|
|
8547
9029
|
var gmt$16 = {
|
|
8548
9030
|
mt_create: mt_create$16,
|
|
8549
9031
|
mt_start: mt_start$15,
|
|
8550
9032
|
mt_stop: mt_stop$15,
|
|
8551
|
-
mt_destroy: mt_destroy$16
|
|
9033
|
+
mt_destroy: mt_destroy$16,
|
|
9034
|
+
mt_command_parser: mt_command_parser$2
|
|
8552
9035
|
};
|
|
8553
9036
|
/***************************************************************
|
|
8554
9037
|
* Create the GClass
|
|
@@ -23991,6 +24474,9 @@ function create_tabulator(gobj) {
|
|
|
23991
24474
|
vertAlign = "middle";
|
|
23992
24475
|
colFormatter = icon_formatter;
|
|
23993
24476
|
break;
|
|
24477
|
+
case "file":
|
|
24478
|
+
colFormatter = file_cell_formatter;
|
|
24479
|
+
break;
|
|
23994
24480
|
case "object":
|
|
23995
24481
|
case "dict":
|
|
23996
24482
|
case "template":
|
|
@@ -24235,6 +24721,33 @@ function table__destroy(gobj) {
|
|
|
24235
24721
|
* render a solid square -- a glyph nobody recognises rather
|
|
24236
24722
|
* than a mistake somebody can fix.
|
|
24237
24723
|
************************************************************/
|
|
24724
|
+
/************************************************************
|
|
24725
|
+
* What a `file` column shows in a table cell: the asset it names.
|
|
24726
|
+
*
|
|
24727
|
+
* A sha256 in full is 64 characters of noise in a column, so it is
|
|
24728
|
+
* shortened and the whole of it is the tooltip. Empty is SAID, not left
|
|
24729
|
+
* blank -- a blank cell and a cell whose asset failed to load look the
|
|
24730
|
+
* same, and only one of them is normal.
|
|
24731
|
+
************************************************************/
|
|
24732
|
+
function file_cell_formatter(cell, formatterParams, onRendered) {
|
|
24733
|
+
const id = yui_asset_id(cell.getValue());
|
|
24734
|
+
if (!id) return createElement2([
|
|
24735
|
+
"span",
|
|
24736
|
+
{
|
|
24737
|
+
class: "FILE_CELL is-empty",
|
|
24738
|
+
"data-i18n": "no file"
|
|
24739
|
+
},
|
|
24740
|
+
"no file"
|
|
24741
|
+
]);
|
|
24742
|
+
return createElement2([
|
|
24743
|
+
"span",
|
|
24744
|
+
{
|
|
24745
|
+
class: "FILE_CELL",
|
|
24746
|
+
title: id
|
|
24747
|
+
},
|
|
24748
|
+
yui_file_id_label(id)
|
|
24749
|
+
]);
|
|
24750
|
+
}
|
|
24238
24751
|
function icon_formatter(cell) {
|
|
24239
24752
|
let name = cell.getValue();
|
|
24240
24753
|
if (!is_string(name) || empty_string(name)) return "";
|
|
@@ -24330,6 +24843,7 @@ function transform__treedb_value_2_table_value(gobj, col, value, row, field) {
|
|
|
24330
24843
|
].join("");
|
|
24331
24844
|
else value = "";
|
|
24332
24845
|
break;
|
|
24846
|
+
case "file":
|
|
24333
24847
|
case "fkey":
|
|
24334
24848
|
let new_value = [];
|
|
24335
24849
|
if (value) {
|
|
@@ -24854,6 +25368,7 @@ function transform__form_value_2_treedb_value(gobj, col, value, operation) {
|
|
|
24854
25368
|
case "hook":
|
|
24855
25369
|
value = null;
|
|
24856
25370
|
break;
|
|
25371
|
+
case "file": break;
|
|
24857
25372
|
case "fkey":
|
|
24858
25373
|
value = build_fkey_ref(gobj, col, value);
|
|
24859
25374
|
break;
|
|
@@ -25337,13 +25852,71 @@ function ac_paste_rows(gobj, event, kw, src) {
|
|
|
25337
25852
|
function ac_form_save_record(gobj, event, kw, src) {
|
|
25338
25853
|
if (refuse_if_readonly$2(gobj, event)) return -1;
|
|
25339
25854
|
let mode = gobj_read_str_attr(src, "form_mode");
|
|
25340
|
-
|
|
25855
|
+
let picked = {};
|
|
25856
|
+
const answer = gobj_command(src, "get_picked_files", {}, gobj);
|
|
25857
|
+
if (answer && answer.result >= 0 && answer.data) picked = answer.data;
|
|
25858
|
+
const cols = Object.keys(picked);
|
|
25859
|
+
if (cols.length === 0) {
|
|
25860
|
+
publish_treedb_write(gobj, mode, kw);
|
|
25861
|
+
setTimeout(function() {
|
|
25862
|
+
close_form_dialog(gobj);
|
|
25863
|
+
}, 0);
|
|
25864
|
+
return 0;
|
|
25865
|
+
}
|
|
25866
|
+
Promise.all(cols.map((col) => {
|
|
25867
|
+
return yui_file_read(picked[col]).then((read) => {
|
|
25868
|
+
return {
|
|
25869
|
+
col,
|
|
25870
|
+
read
|
|
25871
|
+
};
|
|
25872
|
+
});
|
|
25873
|
+
})).then((reads) => {
|
|
25874
|
+
const picks = {};
|
|
25875
|
+
reads.forEach((r) => {
|
|
25876
|
+
picks[r.col] = r.read;
|
|
25877
|
+
});
|
|
25878
|
+
gobj_send_event(gobj, "EV_FILES_READ", {
|
|
25879
|
+
mode,
|
|
25880
|
+
record: kw,
|
|
25881
|
+
picks
|
|
25882
|
+
}, gobj);
|
|
25883
|
+
}).catch((e) => {
|
|
25884
|
+
gobj_send_event(gobj, "EV_FILES_FAILED", { reason: e && e.message ? e.message : String(e) }, gobj);
|
|
25885
|
+
});
|
|
25886
|
+
return 0;
|
|
25887
|
+
}
|
|
25888
|
+
/************************************************************
|
|
25889
|
+
* The one place a write of this view leaves for the app.
|
|
25890
|
+
************************************************************/
|
|
25891
|
+
function publish_treedb_write(gobj, mode, record, jn_files) {
|
|
25892
|
+
const kw_write = {
|
|
25341
25893
|
topic_name: gobj_read_str_attr(gobj, "topic_name"),
|
|
25342
|
-
record
|
|
25894
|
+
record
|
|
25895
|
+
};
|
|
25896
|
+
if (jn_files && Object.keys(jn_files).length > 0) kw_write.record.__files__ = jn_files;
|
|
25897
|
+
gobj_publish_event(gobj, mode === "create" ? "EV_CREATE_RECORD" : "EV_UPDATE_RECORD", kw_write);
|
|
25898
|
+
}
|
|
25899
|
+
/************************************************************
|
|
25900
|
+
* {mode, record, picks} -- the bytes are here now
|
|
25901
|
+
************************************************************/
|
|
25902
|
+
function ac_files_read(gobj, event, kw, src) {
|
|
25903
|
+
const { record, __files__ } = yui_files_manifest(kw.picks, kw.record);
|
|
25904
|
+
publish_treedb_write(gobj, kw.mode, record, __files__);
|
|
25905
|
+
close_form_dialog(gobj);
|
|
25906
|
+
return 0;
|
|
25907
|
+
}
|
|
25908
|
+
/************************************************************
|
|
25909
|
+
* {reason} -- the browser could not read what was picked
|
|
25910
|
+
*
|
|
25911
|
+
* The form stays OPEN: the record is still there, and closing it would
|
|
25912
|
+
* throw away what the person typed for a failure that is not theirs.
|
|
25913
|
+
************************************************************/
|
|
25914
|
+
function ac_files_failed(gobj, event, kw, src) {
|
|
25915
|
+
log_error(`${gobj_short_name(gobj)}: cannot read the picked file: ${kw.reason}`);
|
|
25916
|
+
yui_shell_confirm_ok(yui_shell_of(gobj), "cannot read the picked file", {
|
|
25917
|
+
t,
|
|
25918
|
+
ok_label: "accept"
|
|
25343
25919
|
});
|
|
25344
|
-
setTimeout(function() {
|
|
25345
|
-
close_form_dialog(gobj);
|
|
25346
|
-
}, 0);
|
|
25347
25920
|
return 0;
|
|
25348
25921
|
}
|
|
25349
25922
|
/************************************************************
|
|
@@ -25862,6 +26435,16 @@ function create_gclass$5(gclass_name) {
|
|
|
25862
26435
|
ac_form_save_record,
|
|
25863
26436
|
null
|
|
25864
26437
|
],
|
|
26438
|
+
[
|
|
26439
|
+
"EV_FILES_READ",
|
|
26440
|
+
ac_files_read,
|
|
26441
|
+
null
|
|
26442
|
+
],
|
|
26443
|
+
[
|
|
26444
|
+
"EV_FILES_FAILED",
|
|
26445
|
+
ac_files_failed,
|
|
26446
|
+
null
|
|
26447
|
+
],
|
|
25865
26448
|
[
|
|
25866
26449
|
"EV_NEW_ROW",
|
|
25867
26450
|
ac_new_row,
|
|
@@ -25996,6 +26579,8 @@ function create_gclass$5(gclass_name) {
|
|
|
25996
26579
|
["EV_NODE_DELETED", 0],
|
|
25997
26580
|
["EV_EDITION_MODE", 0],
|
|
25998
26581
|
["EV_SAVE_RECORD", 0],
|
|
26582
|
+
["EV_FILES_READ", 0],
|
|
26583
|
+
["EV_FILES_FAILED", 0],
|
|
25999
26584
|
["EV_NEW_ROW", 0],
|
|
26000
26585
|
["EV_DELETE_ROWS", 0],
|
|
26001
26586
|
["EV_COPY_ROWS", 0],
|
|
@@ -67508,120 +68093,6 @@ function register_c_yui_period() {
|
|
|
67508
68093
|
return create_gclass(GCLASS_NAME);
|
|
67509
68094
|
}
|
|
67510
68095
|
//#endregion
|
|
67511
|
-
//#region src/yui_asset.js
|
|
67512
|
-
function ref_to_id(ref) {
|
|
67513
|
-
if (!ref) return null;
|
|
67514
|
-
if (typeof ref === "string") {
|
|
67515
|
-
if (ref.indexOf("^") < 0) return ref;
|
|
67516
|
-
const parts = ref.split("^");
|
|
67517
|
-
return parts.length >= 2 && parts[1] ? parts[1] : null;
|
|
67518
|
-
}
|
|
67519
|
-
if (typeof ref === "object" && typeof ref.id === "string") return ref.id;
|
|
67520
|
-
return null;
|
|
67521
|
-
}
|
|
67522
|
-
/***************************************************************************
|
|
67523
|
-
* Every asset id a column names, in order. Always an array, possibly empty.
|
|
67524
|
-
***************************************************************************/
|
|
67525
|
-
function yui_asset_ids(ref) {
|
|
67526
|
-
if (Array.isArray(ref)) return ref.map(ref_to_id).filter((id) => !!id);
|
|
67527
|
-
const id = ref_to_id(ref);
|
|
67528
|
-
return id ? [id] : [];
|
|
67529
|
-
}
|
|
67530
|
-
/***************************************************************************
|
|
67531
|
-
* The first asset id a column names, or null.
|
|
67532
|
-
***************************************************************************/
|
|
67533
|
-
function yui_asset_id(ref) {
|
|
67534
|
-
const ids = yui_asset_ids(ref);
|
|
67535
|
-
return ids.length ? ids[0] : null;
|
|
67536
|
-
}
|
|
67537
|
-
/***************************************************************************
|
|
67538
|
-
* Turn a `get-asset` answer into something an element can load.
|
|
67539
|
-
*
|
|
67540
|
-
* Returns null when the answer carries neither -- which the caller must
|
|
67541
|
-
* treat as "missing", never as an empty src: an <img src=""> reloads the
|
|
67542
|
-
* PAGE in some browsers.
|
|
67543
|
-
***************************************************************************/
|
|
67544
|
-
function yui_asset_src(answer) {
|
|
67545
|
-
if (!answer) return null;
|
|
67546
|
-
if (answer.mode === "url" && answer.url) return answer.url;
|
|
67547
|
-
if (answer.mode === "inline" && answer.content64) return `data:${answer.content_type || "application/octet-stream"};base64,${answer.content64}`;
|
|
67548
|
-
return null;
|
|
67549
|
-
}
|
|
67550
|
-
/***************************************************************************
|
|
67551
|
-
* The marker for an asset that is not there.
|
|
67552
|
-
*
|
|
67553
|
-
* A missing image used to leave a broken box and no word about it, which
|
|
67554
|
-
* is indistinguishable from a slow one and from a bug. This says so, and
|
|
67555
|
-
* it says WHICH: `detail` is data (a name, a path) and is never
|
|
67556
|
-
* translated, while the label carries its i18n key so a language change
|
|
67557
|
-
* re-translates it.
|
|
67558
|
-
***************************************************************************/
|
|
67559
|
-
function yui_asset_missing(detail, opts = {}) {
|
|
67560
|
-
const key = opts.key || "asset not available";
|
|
67561
|
-
const children = [["span", { class: "ASSET_MISSING_ICON yi-triangle-exclamation" }], [
|
|
67562
|
-
"span",
|
|
67563
|
-
{
|
|
67564
|
-
class: "ASSET_MISSING_LABEL",
|
|
67565
|
-
i18n: key
|
|
67566
|
-
},
|
|
67567
|
-
key
|
|
67568
|
-
]];
|
|
67569
|
-
if (detail) children.push([
|
|
67570
|
-
"span",
|
|
67571
|
-
{ class: "ASSET_MISSING_DETAIL" },
|
|
67572
|
-
String(detail)
|
|
67573
|
-
]);
|
|
67574
|
-
const attrs = { class: `ASSET_MISSING ${opts.class || ""}`.trim() };
|
|
67575
|
-
if (detail) attrs.title = String(detail);
|
|
67576
|
-
else attrs["data-i18n-title"] = key;
|
|
67577
|
-
return createElement2([
|
|
67578
|
-
"div",
|
|
67579
|
-
attrs,
|
|
67580
|
-
children
|
|
67581
|
-
]);
|
|
67582
|
-
}
|
|
67583
|
-
/***************************************************************************
|
|
67584
|
-
* The element for one asset.
|
|
67585
|
-
*
|
|
67586
|
-
* The KIND comes from the content type the backend stored, not from the
|
|
67587
|
-
* name a person typed: video and audio are assets too, and an <img> whose
|
|
67588
|
-
* src is a film shows the broken box this module exists to remove.
|
|
67589
|
-
*
|
|
67590
|
-
* `detail` is what the missing marker shows -- pass the original name or
|
|
67591
|
-
* the source path, the thing a person can act on.
|
|
67592
|
-
***************************************************************************/
|
|
67593
|
-
function yui_asset_element(answer, opts = {}) {
|
|
67594
|
-
const src = yui_asset_src(answer);
|
|
67595
|
-
if (!src) return yui_asset_missing(opts.detail, opts);
|
|
67596
|
-
const ct = (answer.content_type || "").toLowerCase();
|
|
67597
|
-
const cls = `ASSET_MEDIA ${opts.class || ""}`.trim();
|
|
67598
|
-
let el;
|
|
67599
|
-
if (ct.startsWith("video/")) el = createElement2(["video", {
|
|
67600
|
-
class: cls,
|
|
67601
|
-
controls: "",
|
|
67602
|
-
preload: "metadata"
|
|
67603
|
-
}]);
|
|
67604
|
-
else if (ct.startsWith("audio/")) el = createElement2(["audio", {
|
|
67605
|
-
class: cls,
|
|
67606
|
-
controls: "",
|
|
67607
|
-
preload: "metadata"
|
|
67608
|
-
}]);
|
|
67609
|
-
else {
|
|
67610
|
-
el = createElement2(["img", {
|
|
67611
|
-
class: cls,
|
|
67612
|
-
loading: "lazy"
|
|
67613
|
-
}]);
|
|
67614
|
-
el.alt = opts.alt || opts.detail || "";
|
|
67615
|
-
}
|
|
67616
|
-
el.src = src;
|
|
67617
|
-
el.onerror = () => {
|
|
67618
|
-
log_error(`asset could not be loaded: ${opts.detail || src}`);
|
|
67619
|
-
const $marker = yui_asset_missing(opts.detail, opts);
|
|
67620
|
-
if (el.parentNode) el.parentNode.replaceChild($marker, el);
|
|
67621
|
-
};
|
|
67622
|
-
return el;
|
|
67623
|
-
}
|
|
67624
|
-
//#endregion
|
|
67625
68096
|
//#region src/dev_machine_trace.js
|
|
67626
68097
|
/***********************************************************************
|
|
67627
68098
|
* dev_machine_trace.js
|
|
@@ -69305,6 +69776,6 @@ function setup_frontend_view(self) {
|
|
|
69305
69776
|
return win;
|
|
69306
69777
|
}
|
|
69307
69778
|
//#endregion
|
|
69308
|
-
export { EditControl, MarkerControl, YUI_PERIODS, YUI_PERIODS_DEFAULT, YUI_ROLLING, addClasses, apply_dev_traces, attach_clear, build_dev_panel, dev_window_was_open, disableElements, enableElements, epoch_to_local_input, epoch_to_ms, expose_view_container, fmt_epoch, getStrokeColor, infer_period, info_traffic, inject_svg_icons, is_current_period, iso_week, local_input_to_epoch, ms_to_epoch, period_bounds, period_bounds_epoch, period_label, period_name, period_shift, period_spec, period_start, refresh_clear, register_c_g6_nodes_tree, register_c_yui_form, register_c_yui_fsm_graph, register_c_yui_gclass, register_c_yui_gobj_tree_js, register_c_yui_json, register_c_yui_json_graph, register_c_yui_map, register_c_yui_nav, register_c_yui_pager, register_c_yui_period, register_c_yui_schema_editor, register_c_yui_service_view, register_c_yui_shell, register_c_yui_treedb_graph, register_c_yui_treedb_schema, register_c_yui_treedb_topic_with_form, register_c_yui_treedb_topics, register_c_yui_uplot, register_c_yui_window, register_c_yui_window_manager, register_c_yui_wizard, removeChildElements, removeClasses, rolling_bounds, safe_locale, set_active_state, set_cancel_state, set_pressed_state, set_submit_state, setup_dev, setup_frontend_view, toggleClasses, yui_asset_element, yui_asset_id, yui_asset_ids, yui_asset_missing, yui_asset_src, yui_is_dark, yui_mount_service_view, yui_shell_close_drawer, yui_shell_close_dropdown, yui_shell_confirm_danger, yui_shell_confirm_ok, yui_shell_confirm_yesno, yui_shell_confirm_yesnocancel, yui_shell_navigate, yui_shell_open_drawer, yui_shell_overlay_dismissed, yui_shell_pop_escape, yui_shell_push_escape, yui_shell_refresh_avatars, yui_shell_register_event_handler, yui_shell_register_overlay, yui_shell_set_avatar_provider, yui_shell_set_connection_state, yui_shell_set_sub_routes, yui_shell_set_toolbar_item_badge, yui_shell_set_toolbar_item_icon, yui_shell_set_translator, yui_shell_show_error, yui_shell_show_info, yui_shell_show_modal, yui_shell_show_route_map, yui_shell_show_warning, yui_shell_toggle_drawer, yui_shell_unpark_route, yui_theme_now, yui_toolbar, yui_watch_theme };
|
|
69779
|
+
export { EditControl, MarkerControl, YUI_FILE_ACCEPT, YUI_PERIODS, YUI_PERIODS_DEFAULT, YUI_ROLLING, addClasses, apply_dev_traces, attach_clear, build_dev_panel, dev_window_was_open, disableElements, enableElements, epoch_to_local_input, epoch_to_ms, expose_view_container, fmt_epoch, getStrokeColor, infer_period, info_traffic, inject_svg_icons, is_current_period, iso_week, local_input_to_epoch, ms_to_epoch, period_bounds, period_bounds_epoch, period_label, period_name, period_shift, period_spec, period_start, refresh_clear, register_c_g6_nodes_tree, register_c_yui_form, register_c_yui_fsm_graph, register_c_yui_gclass, register_c_yui_gobj_tree_js, register_c_yui_json, register_c_yui_json_graph, register_c_yui_map, register_c_yui_nav, register_c_yui_pager, register_c_yui_period, register_c_yui_schema_editor, register_c_yui_service_view, register_c_yui_shell, register_c_yui_treedb_graph, register_c_yui_treedb_schema, register_c_yui_treedb_topic_with_form, register_c_yui_treedb_topics, register_c_yui_uplot, register_c_yui_window, register_c_yui_window_manager, register_c_yui_wizard, removeChildElements, removeClasses, rolling_bounds, safe_locale, set_active_state, set_cancel_state, set_pressed_state, set_submit_state, setup_dev, setup_frontend_view, toggleClasses, yui_array_buffer_to_base64, yui_asset_element, yui_asset_id, yui_asset_ids, yui_asset_missing, yui_asset_src, yui_file_control, yui_file_id_label, yui_file_read, yui_file_sha256, yui_file_size_label, yui_files_manifest, yui_is_dark, yui_mount_service_view, yui_shell_close_drawer, yui_shell_close_dropdown, yui_shell_confirm_danger, yui_shell_confirm_ok, yui_shell_confirm_yesno, yui_shell_confirm_yesnocancel, yui_shell_navigate, yui_shell_open_drawer, yui_shell_overlay_dismissed, yui_shell_pop_escape, yui_shell_push_escape, yui_shell_refresh_avatars, yui_shell_register_event_handler, yui_shell_register_overlay, yui_shell_set_avatar_provider, yui_shell_set_connection_state, yui_shell_set_sub_routes, yui_shell_set_toolbar_item_badge, yui_shell_set_toolbar_item_icon, yui_shell_set_translator, yui_shell_show_error, yui_shell_show_info, yui_shell_show_modal, yui_shell_show_route_map, yui_shell_show_warning, yui_shell_toggle_drawer, yui_shell_unpark_route, yui_theme_now, yui_toolbar, yui_watch_theme };
|
|
69309
69780
|
|
|
69310
69781
|
//# sourceMappingURL=gobj-ui.es.js.map
|