@nebularstreams/libmui 2.0.2 → 2.0.3

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.
@@ -0,0 +1,449 @@
1
+ import m from "mithril";
2
+ import defineWidget from "../util/widgets.utils";
3
+ import * as widget from "../util/widgets";
4
+ import {Icon, Notification, ButtonNew} from "./Basic"
5
+ import {OverflowMenu} from "../util/widgets.core";
6
+ import SwitchWidget from "../widgets/SwitchWidget";
7
+ import InputWidget from "../widgets/InputWidget";
8
+ import Notice from "../widgets/NoticeWidget";
9
+ import clone from "../util/util.clone"
10
+
11
+ function choose(items, config = {
12
+ icon: "circle", onSelect: () => {
13
+ }
14
+ }) {
15
+
16
+ const
17
+ {icon, title, field, onSelect, parser = (v) => v} = config,
18
+ options = items.map((item, index) => ({
19
+ icon: field && item.icon || icon,
20
+ // WARNING !!!!!
21
+ caption: parser(field ? item[field] : item, item),
22
+ // caption: idField ? item[idField] : item,
23
+ onClick: (e) => onSelect(item, e, index)
24
+ }));
25
+
26
+ if (Array.isArray(items) && !items.length) {
27
+ Notification.add("No items available");
28
+ } else {
29
+ OverflowMenu(
30
+ document.body,
31
+ {title},
32
+ options
33
+ )
34
+ .then(({action}) => action.onClick());
35
+ }
36
+ }
37
+
38
+ function promptOptions(items, onchange, options = {}) {
39
+
40
+ const
41
+ {title, field, icon, textNew, textHint, resolve} = options,
42
+ dialogOptions = Array.isArray(items)
43
+ ? items.map((item) => ({
44
+ icon,
45
+ // WARNING !!!!!
46
+ caption: field ? item[field] : item,
47
+ // caption: idField ? item[idField] : item,
48
+ onClick: (e) => onchange(item, e)
49
+ }))
50
+ : Object.keys(items)
51
+ .map((key) => ({
52
+ icon: items[key].icon || icon,
53
+ caption: items[key].caption || key,
54
+ onClick: (e) => onchange(
55
+ resolve && items[key].value !== undefined
56
+ ? items[key].value
57
+ : items[key],
58
+ e)
59
+ }));
60
+
61
+ if (Array.isArray(items) && !items.length) {
62
+ Notification.add("No items available");
63
+ } else {
64
+ OverflowMenu(
65
+ document.body,
66
+ {title},
67
+ [
68
+ ...textNew && !field
69
+ ? [
70
+ {
71
+ icon: "plus",
72
+ caption: textNew,
73
+ onClick: () => promptName(
74
+ textNew,
75
+ "",
76
+ textHint,
77
+ (value) => {
78
+ if (value) {
79
+ if (value && value.trim()) {
80
+ onchange(value);
81
+ m.redraw();
82
+ }
83
+ }
84
+ }
85
+ )
86
+ }
87
+ ]
88
+ : [],
89
+ ...dialogOptions
90
+ ]
91
+ )
92
+ .then(({action}) => action.onClick())
93
+ .finally(() => {
94
+ });
95
+ }
96
+ }
97
+
98
+ function promptName(title, value, hint, action, state = {}) {
99
+ setTimeout(() => OverflowMenu(
100
+ document.body,
101
+ {title},
102
+ [
103
+ {
104
+ type: "text",
105
+ hint,
106
+ value
107
+ }
108
+ ]
109
+ )
110
+ .then(({value: val}) => {
111
+ state.refreshKey = String(Date.now());
112
+ return action(val);
113
+ }), 100);
114
+ }
115
+
116
+ const
117
+ WHAT_DO_UPDATE_LIGHTS = 0, // 0
118
+ WHAT_DO_UPDATE_FACE = 1, // 1
119
+ WHAT_DO_UPDATE_PARTICLES = 2, // 4
120
+ WHAT_DO_UPDATE_SEGMENTATION = 3,
121
+ WHAT_DO_UPDATE_VAL_VPLANE = 4,
122
+ WHAT_DO_UPDATE_VAL_PANO = 5,
123
+ WHAT_DO_UPDATE_VAL_FREE = 6,
124
+ WHAT_DO_UPDATE_VAL_DRONE = 7,
125
+ WHAT_DO_UPDATE_VAL_TRANSITION = 8,
126
+ WHAT_DO_UPDATE_TVFX = 9,
127
+ WHAT_DO_UPDATE_VAL_FOLLOW = 10,
128
+
129
+ PROPERTIES = [
130
+ ["Lights and Fog", null, ["amblight", "l0", "l1", "l2", "l3", "fog"], WHAT_DO_UPDATE_LIGHTS],
131
+ ["Face and Accessories", null, ["facekit"], WHAT_DO_UPDATE_FACE],
132
+ ["Particle settings", null, ["particles"], WHAT_DO_UPDATE_PARTICLES],
133
+ ["Video Segmentation", "common", ["segcam", "mask", "green"], WHAT_DO_UPDATE_SEGMENTATION, true],
134
+ ["Video Effects", null, ["fxconfig"], WHAT_DO_UPDATE_TVFX],
135
+ ["Video Plane", "val", ["xvideo", "yvideo", "zvideo", "rvideo", "svideo", "video"], WHAT_DO_UPDATE_VAL_VPLANE],
136
+ ["Main Camera", "val", ["ypos", "xpos", "zoom", "yaw", "pitch", "roll", "ai"], WHAT_DO_UPDATE_VAL_PANO],
137
+ ["Drone Camera", "val", ["free"], WHAT_DO_UPDATE_VAL_FREE],
138
+ ["Followed Agent", "val", ["track"], WHAT_DO_UPDATE_VAL_FOLLOW],
139
+ ["Game Camera", "val", ["orbit", "track", "ocam"], WHAT_DO_UPDATE_VAL_DRONE, undefined, true],
140
+ ["Transition Speed", "val", ["spd"], WHAT_DO_UPDATE_VAL_TRANSITION]
141
+ ],
142
+
143
+ TitleBarWidget = defineWidget(
144
+ ({attrs}) => m("h2.marg-2.no-marg-bottom", attrs.title)
145
+ ),
146
+ EntityTemplates = defineWidget(
147
+ ({attrs}) => {
148
+
149
+ const
150
+ {type = "changes", meta, onInput, title, options} = attrs,
151
+ valid = meta.title?.trim().length && meta.description?.trim().length,
152
+ embedded = options && options.embedded;
153
+
154
+ return m(".flexcol.grows.ohidden.fadein", [
155
+ ...attrs.title ? [
156
+ m(TitleBarWidget, {
157
+ title
158
+ })] : [],
159
+ m(".flexrow.wrap.grows.pad-3.propsgroup.scrolly", [
160
+ ...attrs.notice ? [
161
+ m(Notice, {
162
+ class: "marg-1 marg-v withbottommargin font-75 col-12",
163
+ size: 1,
164
+ icon: "info-circle",
165
+ text: attrs.notice
166
+ })
167
+ ] : [],
168
+ m(
169
+ ".witharia.col-12.pad-1.pad-v",
170
+ {"aria-label": type + " name"},
171
+ m(InputWidget, {
172
+ class: "pad-2 pad-xh " + (valid ? "valid" : "invalid"),
173
+ value: meta.title,
174
+ maxlength: 64,
175
+ onchange: (newvalue) => {
176
+ meta.title = newvalue;
177
+ m.redraw();
178
+ }
179
+ })
180
+ ),
181
+ ...embedded ? []
182
+ : [
183
+ m(
184
+ ".col-12.witharia.pad-1.pad-v",
185
+ {"aria-label": "description"},
186
+ m(InputWidget, {
187
+ class: "pad-2 pad-xh " + (meta.description ? "valid" : "invalid"),
188
+ value: meta.description,
189
+ maxlength: 240,
190
+ multi: true,
191
+ alwaysmulti: true,
192
+ onchange: (newvalue) => {
193
+ meta.description = newvalue;
194
+ m.redraw();
195
+ }
196
+ })
197
+ ),
198
+ m(
199
+ ".col-8.witharia.pad-1.pad-v",
200
+ {"aria-label": "tags"},
201
+ m(InputWidget, {
202
+ class: "pad-2 pad-xh",
203
+ value: meta.tags,
204
+ maxlength: 128,
205
+ onchange: (newvalue) => {
206
+ meta.tags = newvalue.toLowerCase()
207
+ .split(" ")
208
+ .join(",")
209
+ .split(",")
210
+ .filter((tag) => tag.length)
211
+ .join(",");
212
+ }
213
+ })
214
+ ),
215
+ m(
216
+ ".col-4.witharia.pad-1.pad-v",
217
+ {"aria-label": "youtube video"},
218
+ m(InputWidget, {
219
+ class: "pad-2 pad-xh",
220
+ value: meta.ytref || "",
221
+ hint: "YouTube ID",
222
+ maxlength: 16,
223
+ onchange: (newvalue) => {
224
+ newvalue = newvalue.trim();
225
+ if (newvalue) {
226
+ if (newvalue.indexOf(" ") === -1) {
227
+ meta.ytref = newvalue.trim();
228
+ }
229
+ } else {
230
+ delete meta.ytref;
231
+ }
232
+ }
233
+ })
234
+ )
235
+ ],
236
+ m(
237
+ ".col-4.witharia.pad-1.pad-v",
238
+ {"aria-label": "content attribution"},
239
+ m(InputWidget, {
240
+ class: "pad-2 pad-xh",
241
+ value: meta.author,
242
+ hint: "author name",
243
+ maxlength: 128,
244
+ onchange: (newvalue) => {
245
+ meta.author = newvalue;
246
+ }
247
+ })
248
+ ),
249
+
250
+ m(
251
+ ".col-4.witharia.pad-1.pad-v",
252
+ {"aria-label": "license or author link"},
253
+ m(InputWidget, {
254
+ class: "pad-2 pad-xh",
255
+ hint: "https://...",
256
+ value: meta.link,
257
+ maxlength: 128,
258
+ onchange: (newvalue) => {
259
+ meta.link = newvalue;
260
+ }
261
+ })
262
+ ),
263
+ m(
264
+ ".col-4.witharia.pad-1.pad-v",
265
+ {"aria-label": "source domain"},
266
+ m(InputWidget, {
267
+ class: "pad-2 pad-xh",
268
+ value: meta.source,
269
+ hint: "example.com",
270
+ maxlength: 128,
271
+ onchange: (newvalue) => {
272
+ meta.source = newvalue;
273
+ }
274
+ })
275
+ ),
276
+
277
+ m(
278
+ ".col-12.al-right.marg-2.marg-v",
279
+ m(ButtonNew, {
280
+ class: "noshrink accentbutton unitheightsmall col-4",
281
+ text: "save " + type,
282
+ onClick: () => {
283
+
284
+ const isValid = meta.title?.trim().length && (embedded || meta.description?.trim().length);
285
+
286
+ if (isValid) {
287
+ widget.popupMessage(null);
288
+ onInput(meta);
289
+ } else {
290
+ Notification.add("Information is incomplete or invalid");
291
+ }
292
+ }
293
+ })
294
+ )
295
+ ])
296
+ ]);
297
+ }
298
+ ),
299
+ promptEntity = (meta, title, type, options = {}) => new Promise((resolve, reject) => {
300
+ widget.popupMessage(
301
+ m(EntityTemplates, {
302
+ meta,
303
+ type,
304
+ title,
305
+ options,
306
+ onInput: (newMeta) => resolve(newMeta)
307
+ }),
308
+ () => {
309
+ reject(new Error("cancelled"));
310
+ }
311
+ );
312
+ }),
313
+ PresetTemplate = defineWidget(
314
+ ({attrs}) => {
315
+
316
+ const
317
+
318
+ {parameters, preset, presetComponents, types, globalData, fetcher} = attrs,
319
+ available = [],
320
+ sourceAvailable = [],
321
+ items = types
322
+ ? PROPERTIES
323
+ : PROPERTIES.filter((cosa) => {
324
+
325
+ const
326
+ [, primaryTarget, elements, index, globalSource] = cosa,
327
+ target = primaryTarget ? preset[primaryTarget] : preset,
328
+ rootSource = globalSource ? globalData : parameters,
329
+ source = primaryTarget ? rootSource[primaryTarget] : rootSource;
330
+
331
+ let found = false;
332
+
333
+ if (elements.length) {
334
+
335
+ for (const element of elements) {
336
+ if (target && target[element] !== undefined) {
337
+ found = available[index] = true;
338
+ }
339
+ if (source[element] !== undefined) {
340
+ found = sourceAvailable[index] = true;
341
+ }
342
+ }
343
+ return found;
344
+ } else {
345
+ found = true;
346
+ sourceAvailable[index] = !!source;
347
+ available[index] = !!target;
348
+ }
349
+ return found;
350
+
351
+ });
352
+
353
+ return m(
354
+ "ul.flexrow.wrap.userdialog",
355
+ items.length
356
+ ? items.map((cosa) => {
357
+
358
+ const
359
+ [title, primaryTarget, elements, cosaIndex, globalSource, fetchLive] = cosa,
360
+ target = types ? undefined : primaryTarget ? preset[primaryTarget] : preset,
361
+ rootSource = globalSource ? globalData : parameters,
362
+ source = types ? undefined : primaryTarget ? rootSource[primaryTarget] : rootSource,
363
+ deleteCurrent = () => {
364
+ if (elements.length) {
365
+ for (const field of elements) {
366
+ delete target[field];
367
+ }
368
+ } else if (elements.length === 0 && primaryTarget) {
369
+ delete preset[primaryTarget];
370
+ }
371
+ attrs.redraw && attrs.redraw();
372
+ },
373
+ updateCurrent = () => {
374
+ if (elements.length) {
375
+ if (primaryTarget && !preset[primaryTarget]) preset[primaryTarget] = {};
376
+ const updatedTarget = primaryTarget ? preset[primaryTarget] : preset;
377
+ for (const field of elements) {
378
+ if (source[field] !== undefined) {
379
+ const finalSource = fetchLive && fetcher ? fetcher(updatedTarget[field], cosaIndex) : source[field];
380
+ updatedTarget[field] = clone(finalSource);
381
+ }
382
+ }
383
+ } else if (elements.length === 0 && primaryTarget) {
384
+ if (source !== undefined) {
385
+ const finalSource = fetchLive && fetcher ? fetcher(source, cosaIndex) : source;
386
+ preset[primaryTarget] = clone(finalSource);
387
+ }
388
+ }
389
+ attrs.redraw && attrs.redraw();
390
+ Notification.add(`updated ${title} from current scene`);
391
+ },
392
+ isAvailable = types || available[cosaIndex],
393
+ isSourceAvailable = sourceAvailable[cosaIndex];
394
+
395
+ return [
396
+ ...presetComponents ? [
397
+ m(SwitchWidget, {
398
+ title: `Include ${cosa[0]} in snapshots`,
399
+ class: "grows unitheightsmall" + (presetComponents[cosaIndex] ? " accent" : ""),
400
+ checked: !!presetComponents[cosaIndex],
401
+ onchange: (status) => {
402
+ presetComponents[cosaIndex] = !presetComponents[cosaIndex];
403
+ m.redraw();
404
+ attrs.redraw && attrs.redraw();
405
+ }
406
+ }),
407
+ m(".grows", {class: isAvailable ? "avail" : "unavail"}, title)
408
+ ] : [],
409
+ ...!attrs.types ? [
410
+ m(ButtonNew, {
411
+ class: "col-3" + (isSourceAvailable ? "avail" : "unavail"),
412
+ icon: "sync", title: `Copy ${title} from current state`,
413
+ onClick: updateCurrent
414
+ }),
415
+ m(ButtonNew, {
416
+ class: "col-3 " + (isAvailable ? "" : "disabled text-lo"),
417
+ icon: "trash", title: `Delete ${title} in preset`,
418
+ onClick: () => isAvailable && deleteCurrent()
419
+ })
420
+ ] : [],
421
+ m(".flexbreak")
422
+ ];
423
+
424
+ })
425
+ : m("h3", "Preset is Empty")
426
+ );
427
+ }
428
+ ),
429
+ promptPreset = (preset, parameters, options = {}) => new Promise((resolve, reject) => {
430
+ widget.popupMessage(
431
+ () => m(PresetTemplate, {
432
+ ...options,
433
+ preset,
434
+ parameters,
435
+ redraw: () => widget.popupMessage.redraw()
436
+ }),
437
+ () => {
438
+ reject(new Error("cancelled"));
439
+ }
440
+ );
441
+ });
442
+
443
+ export {
444
+ promptName,
445
+ promptEntity,
446
+ promptPreset,
447
+ promptOptions,
448
+ choose
449
+ };
@@ -0,0 +1,200 @@
1
+ import m from "mithril";
2
+ import MithrilRange from "../util/mithril-range";
3
+
4
+ let
5
+ __timer;
6
+
7
+ const
8
+ resetMultiplier = (state) => {
9
+ /*
10
+ if (state.curMultiplier > 1 && state.value < state.curMax * (state.curMultiplier - 1)) {
11
+ state.curMultiplier = Math.ceil(state.value / state.curMax);
12
+ m.redraw();
13
+ }
14
+ */
15
+
16
+ const
17
+ // req multiplier to show value within negative bounds
18
+ maxMultiplier = state.value > state.curMax ? Math.ceil(state.value / state.curMax) : 1,
19
+ // req multiplier to show value within positive bounds
20
+ minMultiplier = (state.curMin < 0 && state.value < state.curMin) ? Math.abs(Math.ceil(state.value / state.curMin)) : 1,
21
+ // chosen
22
+ multiplier = Math.min(Math.max(minMultiplier, maxMultiplier), state.maxMultiplier);
23
+
24
+ if (state.curMultiplier !== multiplier) {
25
+ state.curMultiplier = multiplier;
26
+ m.redraw();
27
+ }
28
+ },
29
+ increaseMultiplier = (state) => {
30
+ if (
31
+ state.value >= (state.curMax * state.curMultiplier) * 0.9
32
+ && state.maxMultiplier > 1
33
+ && state.curMultiplier <= state.maxMultiplier - 1
34
+ ) {
35
+ state.curMultiplier++;
36
+ return true;
37
+ }
38
+ if (state.curMin < 0) return increaseMinMultiplier(state);
39
+ },
40
+ increaseMinMultiplier = (state) => {
41
+ if (state.value <= (state.curMin * state.curMultiplier) * 0.9
42
+ && state.maxMultiplier > 1
43
+ && state.curMultiplier <= state.maxMultiplier - 1
44
+ ) {
45
+ state.curMultiplier++;
46
+ return true;
47
+ }
48
+ },
49
+ RangeSlider = {
50
+
51
+ oninit: ({attrs, state}) => {
52
+ Object.assign(state, {
53
+ value: attrs.value || 0,
54
+ initial: attrs.value || 0,
55
+ curMax: attrs.max,
56
+ curMin: attrs.min,
57
+ curMultiplier: 1,
58
+ maxMultiplier: attrs.multiplier || 1,
59
+ id: "I" + Date.now()
60
+ });
61
+ // adjust initial multiplier
62
+ if (state.maxMultiplier > 1) {
63
+ if (state.value > attrs.max) {
64
+ state.curMultiplier = Math.ceil(state.value / attrs.max);
65
+ } else if (attrs.min < 0 && state.value < attrs.min) {
66
+ state.curMultiplier = Math.abs(Math.ceil(state.value / attrs.min));
67
+ }
68
+ }
69
+ },
70
+ onupdate: ({attrs, state}) => {
71
+ const
72
+ attrsMultiplier = attrs.multiplier || 1,
73
+ attrsValue = attrs.value || 0;
74
+
75
+ if (state.curMax !== attrs.max || state.maxMultiplier !== attrsMultiplier) {
76
+
77
+ Object.assign(state, {
78
+ curMax: attrs.max,
79
+ curMin: attrs.min,
80
+ maxMultiplier: attrsMultiplier,
81
+ value: attrs.value,
82
+ initial: attrsValue
83
+ });
84
+ if (!increaseMultiplier(state)) {
85
+ clearTimeout(state.timer);
86
+ state.timer = setTimeout(resetMultiplier, 1000, state);
87
+ }
88
+
89
+ m.redraw();
90
+
91
+ } else if (state.initial !== attrsValue) {
92
+
93
+ Object.assign(state, {
94
+ value: attrsValue,
95
+ initial: attrsValue
96
+ });
97
+
98
+ clearTimeout(__timer);
99
+ __timer = setTimeout(() => m.redraw(), 50);
100
+
101
+ /*
102
+ if (!increaseMultiplier(state)) {
103
+ state.timer = setTimeout(resetMultiplier, 1000, state);
104
+ clearTimeout(state.timer);
105
+ }
106
+ */
107
+ }
108
+ },
109
+ view: ({attrs, state}) => m(
110
+ ".range.flexcol.control.relative" + (attrs.multiplier > 1 ? ".withmultiplier" : ""),
111
+ {
112
+ ...attrs.class && {class: attrs.class},
113
+ title: attrs.hint || "Click, then use arrows to finetune value if needed"
114
+ },
115
+ [
116
+ ...attrs.title ? [m("label", {class: "rangelabel", htmlFor: state.id}, attrs.title)] : [],
117
+ m(MithrilRange, {
118
+ onchange: (value) => {
119
+
120
+ state.value = value;
121
+
122
+ const increased = increaseMultiplier(state);
123
+
124
+ if (!increased && state.curMultiplier > 1) {
125
+ clearTimeout(state.timer);
126
+ state.timer = setTimeout(resetMultiplier, 1000, state);
127
+ }
128
+
129
+ if (attrs.onchange) {
130
+ attrs.onchange(value);
131
+ } else {
132
+ m.redraw();
133
+ }
134
+ },
135
+ ondrag: (value) => {
136
+ if (value !== state.value) {
137
+ state.value = value;
138
+ if (attrs.ondrag) attrs.ondrag(value);
139
+ return true;
140
+ }
141
+ return false;
142
+ },
143
+ value: state.value || 0,
144
+ min: (attrs.min || 0) * state.curMultiplier,
145
+ max: (state.curMax * state.curMultiplier) || 100,
146
+ step: attrs.step || 1,
147
+ relative: attrs.relative,
148
+ factor: attrs.factor
149
+ }, [
150
+ m(".currentval", attrs.parser
151
+ ? attrs.parser(state.value, attrs.step < 1)
152
+ : state.value
153
+ ? attrs.step < 1
154
+ ? state.value.toFixed(2)
155
+ : ("" + Math.floor(state.value)) : "")
156
+ ])
157
+ /*
158
+ m(".datalist.flexrow", [
159
+ m("div", attrs.getLabel(0)),
160
+ m(".ismax", attrs.getLabel(attrs.max || 100))
161
+ ])
162
+ */
163
+ ]
164
+ )
165
+ },
166
+ RangeWidget = {
167
+ view: ({attrs, state}) => m(RangeSlider, {
168
+ value: attrs.value || 0,
169
+ min: attrs.min || 0,
170
+ max: attrs.max || 100,
171
+ step: attrs.step || 0.1,
172
+ class: (attrs.disabled ? "disabled " : "") + (attrs.class || ""),
173
+ title: attrs.title,
174
+ multiplier: attrs.multiplier,
175
+ hint: attrs.hint,
176
+ parser: attrs.parser,
177
+ relative: attrs.relative,
178
+ factor: attrs.factor,
179
+
180
+ // values: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
181
+ getLabel: (value) => (value ? "" + value : "default"),
182
+ ondrag: (value) => {
183
+ state.changed = true;
184
+ state.value = value;
185
+ if (attrs.onchange) attrs.onchange(value, true);
186
+ return true;
187
+ },
188
+ onchange: (value) => {
189
+ if (attrs.auto0) {
190
+ value = 0;
191
+ } else if (attrs.autoValue) value = attrs.autoValue;
192
+ state.changed = true;
193
+ state.value = value;
194
+ if (attrs.onchange) attrs.onchange(value, false);
195
+ return true;
196
+ }
197
+ })
198
+ };
199
+
200
+ export default RangeWidget;
@@ -0,0 +1,21 @@
1
+ import m from "mithril";
2
+ import defineWidget from "../util/widgets.utils";
3
+
4
+
5
+ const
6
+
7
+ Separator = defineWidget(
8
+ ({ attrs, children }) => m(
9
+ ".separator",
10
+ {
11
+ class: attrs.class || "mini",
12
+ ...attrs.onClick && { onclick: attrs.onClick }
13
+ },
14
+ [
15
+ m("span.grows", attrs.text),
16
+ ...(children || [])
17
+ ]
18
+ )
19
+ );
20
+
21
+ export default Separator;