@yuneta/gobj-ui 7.23.43 → 7.23.45
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 +292 -265
- package/dist/gobj-ui.es.js +292 -265
- package/package.json +2 -2
- package/src/c_g6_nodes_tree.js +42 -16
- package/src/c_yui_form.js +6 -5
- package/src/json_view_helpers.js +23 -7
- package/src/json_view_helpers.test.js +33 -0
- package/src/lib_graph.css +110 -0
package/src/c_g6_nodes_tree.js
CHANGED
|
@@ -3348,6 +3348,25 @@ function create_popover_base(left, top, className, borderColor, minWidth)
|
|
|
3348
3348
|
'min-width:' + minWidth + 'px;font-size:13px;';
|
|
3349
3349
|
popover.addEventListener('click', (e) => e.stopPropagation());
|
|
3350
3350
|
popover.addEventListener('pointerdown', (e) => e.stopPropagation());
|
|
3351
|
+
/*
|
|
3352
|
+
* AND THE WHEEL, or a popover that overflows cannot be scrolled with
|
|
3353
|
+
* the wheel at all -- the scrollbar has to be dragged.
|
|
3354
|
+
*
|
|
3355
|
+
* G6's zoom behavior binds a wheel listener on the CONTAINER and
|
|
3356
|
+
* calls preventDefault() on every notch, whatever the target is;
|
|
3357
|
+
* then it declines to zoom because the target is not the canvas. So
|
|
3358
|
+
* the gesture is cancelled and nobody uses it: the graph does not
|
|
3359
|
+
* zoom AND the box does not scroll. Measured on a live node: with
|
|
3360
|
+
* the popover overflowing (scrollHeight 674 over 357 visible),
|
|
3361
|
+
* scrollTop stayed at 0 and the zoom readout stayed at 50%.
|
|
3362
|
+
*
|
|
3363
|
+
* Stopping it here -- in the bubble phase, where the click and the
|
|
3364
|
+
* pointerdown above are already stopped -- is enough: G6 sees
|
|
3365
|
+
* nothing and the browser scrolls the box normally. Not
|
|
3366
|
+
* preventDefault: cancelling it ourselves would be the same bug
|
|
3367
|
+
* from our side.
|
|
3368
|
+
*/
|
|
3369
|
+
popover.addEventListener('wheel', (e) => e.stopPropagation());
|
|
3351
3370
|
return popover;
|
|
3352
3371
|
}
|
|
3353
3372
|
|
|
@@ -5492,6 +5511,9 @@ function show_node_detail_popover(gobj, node_id)
|
|
|
5492
5511
|
popover.style.maxWidth = '320px';
|
|
5493
5512
|
popover.style.maxHeight = '60%';
|
|
5494
5513
|
popover.style.overflow = 'auto';
|
|
5514
|
+
/* The sticky head spans the full width, so the side padding moves
|
|
5515
|
+
* to the rows; `create_popover_base` put 12px on the box itself. */
|
|
5516
|
+
popover.style.padding = '0 12px 12px';
|
|
5495
5517
|
popover.style.fontFamily =
|
|
5496
5518
|
"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, " +
|
|
5497
5519
|
"Helvetica, Arial, sans-serif";
|
|
@@ -5514,29 +5536,33 @@ function show_node_detail_popover(gobj, node_id)
|
|
|
5514
5536
|
vs = vs.slice(0, 240) + '…';
|
|
5515
5537
|
}
|
|
5516
5538
|
rows +=
|
|
5517
|
-
`<div style="
|
|
5518
|
-
|
|
5519
|
-
|
|
5520
|
-
|
|
5521
|
-
`<div style="flex:1 1 auto;font-size:12px;` +
|
|
5522
|
-
`word-break:break-word;">${escapeHtml(vs)}</div>` +
|
|
5539
|
+
`<div class="g6-detail-row" style="border-top:1px solid ${rowbd};">` +
|
|
5540
|
+
`<div class="g6-detail-key" style="color:${sub};">` +
|
|
5541
|
+
`${escapeHtml(key)}</div>` +
|
|
5542
|
+
`<div class="g6-detail-val">${escapeHtml(vs)}</div>` +
|
|
5523
5543
|
`</div>`;
|
|
5524
5544
|
}
|
|
5525
5545
|
|
|
5546
|
+
/*
|
|
5547
|
+
* The head is STICKY: the box scrolls, and with the head scrolling
|
|
5548
|
+
* away the ✕ left the screen -- on a phone, where the box is most
|
|
5549
|
+
* of the screen, that is a popover you cannot close without
|
|
5550
|
+
* scrolling back up for the button. Its background is the
|
|
5551
|
+
* popover's own, or the rows would show through it.
|
|
5552
|
+
*/
|
|
5526
5553
|
popover.innerHTML =
|
|
5527
|
-
`<div
|
|
5528
|
-
`margin-bottom:6px;">` +
|
|
5554
|
+
`<div class="g6-detail-head" style="background:${bg};">` +
|
|
5529
5555
|
`<div style="flex:1 1 auto;min-width:0;">` +
|
|
5530
|
-
`<div
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
`margin-top:2px;">${escapeHtml(topic)}</div>` +
|
|
5556
|
+
`<div class="g6-detail-id">${escapeHtml(id)}</div>` +
|
|
5557
|
+
`<div class="g6-detail-topic" style="color:${sub};">` +
|
|
5558
|
+
`${escapeHtml(topic)}</div>` +
|
|
5534
5559
|
`</div>` +
|
|
5535
|
-
`<div class="g6-detail-close"
|
|
5536
|
-
`
|
|
5537
|
-
`
|
|
5560
|
+
`<div class="g6-detail-close" role="button" tabindex="0"` +
|
|
5561
|
+
` aria-label="${escapeHtml(t('close'))}"` +
|
|
5562
|
+
` title="${escapeHtml(t('close'))}"` +
|
|
5563
|
+
` style="color:${sub};">×</div>` +
|
|
5538
5564
|
`</div>` +
|
|
5539
|
-
(rows || `<div style="
|
|
5565
|
+
(rows || `<div class="g6-detail-empty" style="color:${sub};">` +
|
|
5540
5566
|
`${escapeHtml(t('no fields'))}</div>`);
|
|
5541
5567
|
|
|
5542
5568
|
let close_el = popover.querySelector('.g6-detail-close');
|
package/src/c_yui_form.js
CHANGED
|
@@ -71,6 +71,7 @@ import "tabulator-tables/dist/css/tabulator.min.css"; // Import Tabulator CSS
|
|
|
71
71
|
import "tabulator-tables/dist/css/tabulator_bulma.css";
|
|
72
72
|
|
|
73
73
|
import "./tabulator.css";
|
|
74
|
+
import {is_time_field} from "./json_view_helpers.js";
|
|
74
75
|
import { TabulatorFull as Tabulator } from "tabulator-tables"; // Import Full Tabulator JS
|
|
75
76
|
// import { Tabulator } from "tabulator-tables"; // Import light Tabulator JS
|
|
76
77
|
|
|
@@ -1365,12 +1366,12 @@ function create_form_field(
|
|
|
1365
1366
|
onChange: function() {
|
|
1366
1367
|
gobj_send_event(gobj, "EV_RECORD_CHANGED", {}, gobj);
|
|
1367
1368
|
},
|
|
1369
|
+
/* La lista vive en json_view_helpers.js: el visor de
|
|
1370
|
+
* JSON y este editor tienen que reconocer los MISMOS
|
|
1371
|
+
* campos, y tener cada uno su copia es como dejaron de
|
|
1372
|
+
* hacerlo. */
|
|
1368
1373
|
timestampTag: function({field, value, path}) {
|
|
1369
|
-
return field
|
|
1370
|
-
field === 'tm' || field === 't' || field === 'time' ||
|
|
1371
|
-
field === 'from_t' || field === 'to_t' ||
|
|
1372
|
-
field === 't_input' || field === 't_output' ||
|
|
1373
|
-
field === 'from_tm' || field === 'to_tm';
|
|
1374
|
+
return is_time_field(field);
|
|
1374
1375
|
},
|
|
1375
1376
|
}
|
|
1376
1377
|
});
|
package/src/json_view_helpers.js
CHANGED
|
@@ -183,18 +183,34 @@ export function subtree_matches(value, term)
|
|
|
183
183
|
|
|
184
184
|
/************************************************************
|
|
185
185
|
* Fields whose numeric value is an epoch timestamp.
|
|
186
|
-
*
|
|
187
|
-
*
|
|
186
|
+
*
|
|
187
|
+
* THE ONE LIST. C_YUI_FORM's jsoneditor takes its
|
|
188
|
+
* `timestampTag` from here: it used to carry its own copy,
|
|
189
|
+
* and a copy is how the two stopped agreeing.
|
|
190
|
+
*
|
|
191
|
+
* TWO SPELLINGS, AND LAS DOS SON DEL BACKEND. timeranger2
|
|
192
|
+
* writes `from_t`/`from_tm` in the match_cond of a query and
|
|
193
|
+
* `fr_t`/`fr_tm` in the metadata of a topic and of a file --
|
|
194
|
+
* which is what a Raw JSON of a treedb actually shows. With
|
|
195
|
+
* only the long pair listed, `to_t` got its date and the
|
|
196
|
+
* `fr_t` right above it did not, which reads as a broken
|
|
197
|
+
* annotator rather than a missing name.
|
|
188
198
|
************************************************************/
|
|
199
|
+
const TIME_FIELDS = [
|
|
200
|
+
"__t__", "__tm__",
|
|
201
|
+
"t", "tm", "time",
|
|
202
|
+
"from_t", "to_t", "from_tm", "to_tm",
|
|
203
|
+
"fr_t", "fr_tm",
|
|
204
|
+
"t_input", "t_output"
|
|
205
|
+
];
|
|
206
|
+
|
|
189
207
|
export function is_time_field(field)
|
|
190
208
|
{
|
|
191
|
-
return field
|
|
192
|
-
field === "tm" || field === "t" || field === "time" ||
|
|
193
|
-
field === "from_t" || field === "to_t" ||
|
|
194
|
-
field === "t_input" || field === "t_output" ||
|
|
195
|
-
field === "from_tm" || field === "to_tm";
|
|
209
|
+
return TIME_FIELDS.indexOf(field) >= 0;
|
|
196
210
|
}
|
|
197
211
|
|
|
212
|
+
export {TIME_FIELDS};
|
|
213
|
+
|
|
198
214
|
/************************************************************
|
|
199
215
|
* Format an epoch value as a local wall-clock string.
|
|
200
216
|
* Accepts seconds or milliseconds; returns null when the
|
|
@@ -310,3 +310,36 @@ test("is_pure_collection: the schema this came from", () => {
|
|
|
310
310
|
/* A column has content, so it keeps its card. */
|
|
311
311
|
expect(is_pure_collection(col)).toBe(false);
|
|
312
312
|
});
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
/*
|
|
316
|
+
* DOS ORTOGRAFIAS, Y LAS DOS SON DEL BACKEND.
|
|
317
|
+
*
|
|
318
|
+
* timeranger2 escribe `from_t`/`from_tm` en el match_cond de una consulta y
|
|
319
|
+
* `fr_t`/`fr_tm` en los metadatos de un topic y de un fichero -- que es lo
|
|
320
|
+
* que enseña el Raw JSON de un treedb. Con solo la larga en la lista,
|
|
321
|
+
* `to_t` salia fechado y el `fr_t` de encima no: se lee como un anotador
|
|
322
|
+
* roto y es un nombre que falta.
|
|
323
|
+
*/
|
|
324
|
+
test("is_time_field reconoce la abreviada, la que sale en un Raw JSON", () => {
|
|
325
|
+
expect(is_time_field("fr_t")).toBe(true);
|
|
326
|
+
expect(is_time_field("fr_tm")).toBe(true);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
test("is_time_field sigue reconociendo la larga, la de las consultas", () => {
|
|
330
|
+
expect(is_time_field("from_t")).toBe(true);
|
|
331
|
+
expect(is_time_field("from_tm")).toBe(true);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
test("is_time_field: las dos mitades de cada par, o la fila queda a medias", () => {
|
|
335
|
+
[["fr_t", "to_t"], ["fr_tm", "to_tm"],
|
|
336
|
+
["from_t", "to_t"], ["from_tm", "to_tm"]].forEach((pair) => {
|
|
337
|
+
expect(is_time_field(pair[0])).toBe(is_time_field(pair[1]));
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
test("is_time_field no fecha lo que no es una fecha", () => {
|
|
342
|
+
["rows", "id", "", undefined, null].forEach((f) => {
|
|
343
|
+
expect(is_time_field(f)).toBe(false);
|
|
344
|
+
});
|
|
345
|
+
});
|
package/src/lib_graph.css
CHANGED
|
@@ -111,6 +111,116 @@
|
|
|
111
111
|
background-color: var(--bulma-background, #fff);
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
/*
|
|
115
|
+
* THE NODE DETAIL POPOVER.
|
|
116
|
+
*
|
|
117
|
+
* Sizes live here and not in the inline styles that build it, for one
|
|
118
|
+
* reason: a media query cannot reach an inline style, and this box is
|
|
119
|
+
* read on a phone as much as on a desk.
|
|
120
|
+
*
|
|
121
|
+
* 12px was the size of the rows and 11px of the topic. That is caption
|
|
122
|
+
* size, and this popover exists ONLY to be read -- it is the record of
|
|
123
|
+
* a node, field by field. Same reasoning as the context menu below:
|
|
124
|
+
* 12px is a caption, not content.
|
|
125
|
+
*/
|
|
126
|
+
.graph-container .g6-detail-head {
|
|
127
|
+
display: flex;
|
|
128
|
+
align-items: flex-start;
|
|
129
|
+
gap: 8px;
|
|
130
|
+
/* Sticky, so the ✕ never scrolls off a box that is most of the
|
|
131
|
+
* screen on a phone. The 12px of side padding the popover used to
|
|
132
|
+
* carry moved to the rows, so the head reaches its edges. */
|
|
133
|
+
position: sticky;
|
|
134
|
+
top: 0;
|
|
135
|
+
z-index: 1;
|
|
136
|
+
padding: 12px 0 6px;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.graph-container .g6-detail-id {
|
|
140
|
+
font-size: 15px;
|
|
141
|
+
font-weight: 600;
|
|
142
|
+
word-break: break-word;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.graph-container .g6-detail-topic {
|
|
146
|
+
font-size: 12px;
|
|
147
|
+
margin-top: 2px;
|
|
148
|
+
word-break: break-word;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.graph-container .g6-detail-row {
|
|
152
|
+
display: flex;
|
|
153
|
+
gap: 10px;
|
|
154
|
+
padding: 6px 0;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.graph-container .g6-detail-key,
|
|
158
|
+
.graph-container .g6-detail-val,
|
|
159
|
+
.graph-container .g6-detail-empty {
|
|
160
|
+
font-size: 13px;
|
|
161
|
+
word-break: break-word;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.graph-container .g6-detail-key {
|
|
165
|
+
flex: 0 0 38%;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.graph-container .g6-detail-val {
|
|
169
|
+
flex: 1 1 auto;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/*
|
|
173
|
+
* The close button was a 13x16 box: a glyph with 2px of side padding
|
|
174
|
+
* and nothing else. That is a mouse target on a good day and not a
|
|
175
|
+
* finger target on any. It is a real box now, and a square one, so the
|
|
176
|
+
* ✕ has somewhere to be.
|
|
177
|
+
*/
|
|
178
|
+
.graph-container .g6-detail-close {
|
|
179
|
+
flex: 0 0 auto;
|
|
180
|
+
cursor: pointer;
|
|
181
|
+
font-size: 18px;
|
|
182
|
+
line-height: 1;
|
|
183
|
+
display: flex;
|
|
184
|
+
align-items: center;
|
|
185
|
+
justify-content: center;
|
|
186
|
+
width: 28px;
|
|
187
|
+
height: 28px;
|
|
188
|
+
margin: -4px -4px 0 0;
|
|
189
|
+
border-radius: 4px;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.graph-container .g6-detail-close:hover {
|
|
193
|
+
background: var(--bulma-background, rgba(128, 128, 128, 0.15));
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/*
|
|
197
|
+
* A finger, and the rule the context menu below already follows: the
|
|
198
|
+
* target grows to something a thumb hits, and the text to something
|
|
199
|
+
* read at arm's length.
|
|
200
|
+
*/
|
|
201
|
+
@media (pointer: coarse) {
|
|
202
|
+
.graph-container .g6-detail-close {
|
|
203
|
+
width: 44px;
|
|
204
|
+
height: 44px;
|
|
205
|
+
font-size: 22px;
|
|
206
|
+
margin: -8px -8px 0 0;
|
|
207
|
+
}
|
|
208
|
+
.graph-container .g6-detail-id {
|
|
209
|
+
font-size: 16px;
|
|
210
|
+
}
|
|
211
|
+
.graph-container .g6-detail-topic {
|
|
212
|
+
font-size: 13px;
|
|
213
|
+
}
|
|
214
|
+
.graph-container .g6-detail-key,
|
|
215
|
+
.graph-container .g6-detail-val,
|
|
216
|
+
.graph-container .g6-detail-empty {
|
|
217
|
+
font-size: 14px;
|
|
218
|
+
}
|
|
219
|
+
.graph-container .g6-detail-row {
|
|
220
|
+
padding: 8px 0;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
114
224
|
/*
|
|
115
225
|
* G6 toolbar: larger icons (via className option)
|
|
116
226
|
*/
|