@nebularstreams/libmui 0.1.0
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/libui.cjs.js +2 -0
- package/dist/libui.cjs.js.map +1 -0
- package/dist/libui.esm.js +2 -0
- package/dist/libui.esm.js.map +1 -0
- package/dist/libui.umd.js +2 -0
- package/dist/libui.umd.js.map +1 -0
- package/package.json +24 -0
- package/rollup.config.js +27 -0
- package/src/index.js +29 -0
- package/src/util/fuckingconfirm.js +28 -0
- package/src/util/mithril-range.js +444 -0
- package/src/util/util.clone.js +10 -0
- package/src/util/util.tokenizer.js +227 -0
- package/src/util/widgets.core.js +314 -0
- package/src/util/widgets.js +72 -0
- package/src/util/widgets.utils.js +10 -0
- package/src/widgets/Basic.js +335 -0
- package/src/widgets/CardieWidget.js +168 -0
- package/src/widgets/ComboPrompt.js +112 -0
- package/src/widgets/InputWidget.js +238 -0
- package/src/widgets/NoticeWidget.js +32 -0
- package/src/widgets/OverflowUtils.js +449 -0
- package/src/widgets/RangeWidget.js +200 -0
- package/src/widgets/Separator.js +21 -0
- package/src/widgets/SwitchWidget.js +42 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/* eslint-disable no-nested-ternary */
|
|
2
|
+
/**
|
|
3
|
+
* Basic Mithril Widget functions
|
|
4
|
+
* Provides some utilities and basic UI functions like an Alert or a Confirmation dialog
|
|
5
|
+
*
|
|
6
|
+
* @author Rodolfo Lopez Pintor, 2020 <rlp@nebular.tv>
|
|
7
|
+
* @module apps.widgets.basic
|
|
8
|
+
*
|
|
9
|
+
*/
|
|
10
|
+
import m from "mithril";
|
|
11
|
+
import * as widget from "./widgets";
|
|
12
|
+
import * as Tokenizer from "./util.tokenizer";
|
|
13
|
+
import {Button, Empty} from "../widgets/Basic";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
*
|
|
17
|
+
* Computes a Class String
|
|
18
|
+
*
|
|
19
|
+
* @param {string} initials INitial CSS Classes
|
|
20
|
+
* @param {Object<string, boolean>} clases New classes dictionary
|
|
21
|
+
* @return {string} The CSS Class expression like .class1.class2.class3
|
|
22
|
+
* @public
|
|
23
|
+
* @static
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const packClassString = (initials, clases) => {
|
|
27
|
+
const finals = [];
|
|
28
|
+
if (clases) for (const cla of Object.keys(clases)) if (clases[cla]) finals.push("." + cla);
|
|
29
|
+
return (initials || "") + finals.join("");
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
const packClass = (initials, clases) => {
|
|
34
|
+
const finals = initials ? [initials] : [];
|
|
35
|
+
if (clases) for (const cla of Object.keys(clases)) if (clases[cla]) finals.push(cla);
|
|
36
|
+
return finals.join(" ");
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Shows a modal confirm dialog
|
|
41
|
+
* @param {string} title The dialog title
|
|
42
|
+
* @param {string} message The dialog message
|
|
43
|
+
* @param {Array} buttons Buttons to display
|
|
44
|
+
* @param {Object=} options noCancel: dialog is uncanellable
|
|
45
|
+
* @public
|
|
46
|
+
* @static
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
function Confirm(title, message,
|
|
50
|
+
buttons = ["CANCEL", "OK"], options = {}
|
|
51
|
+
) {
|
|
52
|
+
widget.popupMessage(
|
|
53
|
+
m(
|
|
54
|
+
{
|
|
55
|
+
view: () => [
|
|
56
|
+
...title ? [m("h3", title)] : [],
|
|
57
|
+
...message ? [m("h4", message)] : [],
|
|
58
|
+
m(".container",
|
|
59
|
+
buttons.map(
|
|
60
|
+
option => m(
|
|
61
|
+
"button",
|
|
62
|
+
{
|
|
63
|
+
onclick: () => {
|
|
64
|
+
document.getElementById("modal-container").style.display = "none";
|
|
65
|
+
if (option.onclick) option.onclick(option.caption);
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
option.caption || "OK"
|
|
69
|
+
)
|
|
70
|
+
))
|
|
71
|
+
]
|
|
72
|
+
},
|
|
73
|
+
{}
|
|
74
|
+
),
|
|
75
|
+
options.onCancel || !options.noCancel
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function ConfirmPromise(title, message, actions, isModal = false) {
|
|
80
|
+
|
|
81
|
+
let onCancel = undefined;
|
|
82
|
+
|
|
83
|
+
if (typeof isModal === "function") {
|
|
84
|
+
onCancel = isModal;
|
|
85
|
+
isModal = false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return new Promise((resolve) => {
|
|
89
|
+
|
|
90
|
+
const opts = actions.map((name, idx) => ({
|
|
91
|
+
caption: name,
|
|
92
|
+
onclick: () => resolve({
|
|
93
|
+
idx,
|
|
94
|
+
name
|
|
95
|
+
})
|
|
96
|
+
}));
|
|
97
|
+
|
|
98
|
+
Confirm(title, message, opts, {
|
|
99
|
+
onCancel,
|
|
100
|
+
noCancel: isModal
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Shows An Alert Modal Dialog
|
|
107
|
+
* @param {string} title Dialog Title
|
|
108
|
+
* @param {string} message Dialog Message
|
|
109
|
+
* @param {string?} caption Button caption
|
|
110
|
+
* @param {function?} onclick On press handler
|
|
111
|
+
* @public
|
|
112
|
+
* @static
|
|
113
|
+
*/
|
|
114
|
+
|
|
115
|
+
function Alert(title, message, caption, onclick) {
|
|
116
|
+
return Confirm(title, message, [{caption, onclick}]);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
function OverflowMenu(domItem, item, actions, options) {
|
|
121
|
+
|
|
122
|
+
return new Promise((resolve) => {
|
|
123
|
+
|
|
124
|
+
options = options || {};
|
|
125
|
+
|
|
126
|
+
function closeResolve(value) {
|
|
127
|
+
widget.popupMessage(null);
|
|
128
|
+
try {
|
|
129
|
+
resolve(value);
|
|
130
|
+
} catch (e) {
|
|
131
|
+
//
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const
|
|
136
|
+
classes = packClassString(".popup.flexcol", {
|
|
137
|
+
".favlist": options.favlist,
|
|
138
|
+
".isfav": domItem && domItem.classList.contains("isfav"),
|
|
139
|
+
".isplay": domItem && domItem.classList.contains("selected")
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
widget.popupMessage(
|
|
143
|
+
m({
|
|
144
|
+
view: () => m(
|
|
145
|
+
classes,
|
|
146
|
+
{
|
|
147
|
+
onclick: (event) => {
|
|
148
|
+
|
|
149
|
+
if (event.target.nodeName === "INPUT") {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const
|
|
154
|
+
action = event.target.getAttribute("data-action");
|
|
155
|
+
|
|
156
|
+
if (action) {
|
|
157
|
+
closeResolve({
|
|
158
|
+
domItem,
|
|
159
|
+
item,
|
|
160
|
+
action
|
|
161
|
+
});
|
|
162
|
+
} else {
|
|
163
|
+
widget.popupMessage(null);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
[
|
|
168
|
+
...item.title ? [m("h3", {id: "modaltitle"}, item.title)] : [],
|
|
169
|
+
...actions.map(action => (action.type === "text"
|
|
170
|
+
? m(
|
|
171
|
+
"input.action",
|
|
172
|
+
{
|
|
173
|
+
value: action.value || "",
|
|
174
|
+
type: "text",
|
|
175
|
+
placeholder: action.hint || "",
|
|
176
|
+
oncreate: ({dom}) => dom.focus(),
|
|
177
|
+
onkeyup: (ev) => {
|
|
178
|
+
if (ev.keyCode === 27) {
|
|
179
|
+
closeResolve({domItem, item, action});
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
onchange: (ev) => {
|
|
183
|
+
ev.target.blur();
|
|
184
|
+
closeResolve({
|
|
185
|
+
domItem,
|
|
186
|
+
item,
|
|
187
|
+
action,
|
|
188
|
+
value: ev.target.value
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
)
|
|
193
|
+
: m(
|
|
194
|
+
Button,
|
|
195
|
+
{
|
|
196
|
+
...action,
|
|
197
|
+
onclick: () => closeResolve({
|
|
198
|
+
domItem,
|
|
199
|
+
item,
|
|
200
|
+
action
|
|
201
|
+
})
|
|
202
|
+
},
|
|
203
|
+
action.caption
|
|
204
|
+
)))
|
|
205
|
+
]
|
|
206
|
+
)
|
|
207
|
+
}),
|
|
208
|
+
true
|
|
209
|
+
);
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Creates an HTML UL/LI structure with the object properties
|
|
215
|
+
* @param {Object} object An object to render
|
|
216
|
+
* @param {RenderOptions?} options
|
|
217
|
+
* @param {Object[]} children additional vnodes
|
|
218
|
+
* @param {string} recurse parent key
|
|
219
|
+
* @param isArrayChild
|
|
220
|
+
* @return {m} The rendered view
|
|
221
|
+
* @public
|
|
222
|
+
* @static
|
|
223
|
+
*/
|
|
224
|
+
|
|
225
|
+
function RenderObject(object, options, children, recurse, isArrayChild = false) {
|
|
226
|
+
|
|
227
|
+
// noinspection JSValidateTypes
|
|
228
|
+
options = options || {};
|
|
229
|
+
const {arrayClass = "isarray", arrayChildClass = "ischildren", objectClass = "isobject"} = options;
|
|
230
|
+
|
|
231
|
+
return m(
|
|
232
|
+
"ul",
|
|
233
|
+
{
|
|
234
|
+
...(options.attrs || {}),
|
|
235
|
+
class: options.class
|
|
236
|
+
},
|
|
237
|
+
object
|
|
238
|
+
? [
|
|
239
|
+
...(options.keys || Object.keys(object))
|
|
240
|
+
.map((key) => {
|
|
241
|
+
|
|
242
|
+
const
|
|
243
|
+
value = object[key] || null,
|
|
244
|
+
isObject = value !== null && (typeof value === "object"),
|
|
245
|
+
isArray = isObject && value.length,
|
|
246
|
+
fullkey = (recurse ? recurse + "_" : "") + key;
|
|
247
|
+
|
|
248
|
+
return m(
|
|
249
|
+
"li.field_" + (options.start || "") + fullkey,
|
|
250
|
+
{
|
|
251
|
+
class: (isArray ? arrayClass : isObject ? (objectClass + " ") : "") + (isArrayChild ? arrayChildClass : ""),
|
|
252
|
+
"data-title": options.titles ? options.titles[key] || key : key,
|
|
253
|
+
...(options.itemAttrs || {})
|
|
254
|
+
},
|
|
255
|
+
// eslint-disable-next-line
|
|
256
|
+
isObject
|
|
257
|
+
? [
|
|
258
|
+
m(".subvalues",
|
|
259
|
+
RenderObject(
|
|
260
|
+
value,
|
|
261
|
+
{
|
|
262
|
+
...options,
|
|
263
|
+
keys: null
|
|
264
|
+
},
|
|
265
|
+
null,
|
|
266
|
+
(recurse ? recurse + "_" : "") + key,
|
|
267
|
+
isArray
|
|
268
|
+
))]
|
|
269
|
+
: (
|
|
270
|
+
(options.renderer && options.renderer[fullkey])
|
|
271
|
+
? options.renderer[fullkey](value)
|
|
272
|
+
: m("span.value", object[key])
|
|
273
|
+
)
|
|
274
|
+
);
|
|
275
|
+
}),
|
|
276
|
+
...children || []
|
|
277
|
+
]
|
|
278
|
+
: [
|
|
279
|
+
m(".empty", {})
|
|
280
|
+
]
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
const
|
|
287
|
+
JSONWidget = {
|
|
288
|
+
view:
|
|
289
|
+
({attrs, children}) => {
|
|
290
|
+
let object;
|
|
291
|
+
try {
|
|
292
|
+
object = attrs.pointer
|
|
293
|
+
? Tokenizer.getPointedData(attrs.object, attrs.pointer)
|
|
294
|
+
: attrs.object;
|
|
295
|
+
} catch (e) {
|
|
296
|
+
return m(Empty, {message: e.message, ...attrs});
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return object
|
|
300
|
+
? RenderObject(object, attrs, children)
|
|
301
|
+
: m(Empty, attrs);
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
// noinspection JSUnusedGlobalSymbols
|
|
306
|
+
export {
|
|
307
|
+
Alert,
|
|
308
|
+
Confirm,
|
|
309
|
+
ConfirmPromise,
|
|
310
|
+
RenderObject,
|
|
311
|
+
OverflowMenu,
|
|
312
|
+
JSONWidget,
|
|
313
|
+
packClass
|
|
314
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import m from "mithril";
|
|
2
|
+
|
|
3
|
+
let
|
|
4
|
+
mCurrentCancelable;
|
|
5
|
+
|
|
6
|
+
function runCancelable(container) {
|
|
7
|
+
if (typeof mCurrentCancelable === "function") {
|
|
8
|
+
mCurrentCancelable();
|
|
9
|
+
mCurrentCancelable = undefined;
|
|
10
|
+
}
|
|
11
|
+
container.style.display = "none";
|
|
12
|
+
delete popupMessage.redraw;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// There are ways of doing this inside m.route but it is probably
|
|
16
|
+
// cleaner and faster when kept outside of the main auto
|
|
17
|
+
// rendering system
|
|
18
|
+
|
|
19
|
+
function popupMessage(message, cancelable = false, contentClass = ".modal-content") {
|
|
20
|
+
|
|
21
|
+
const container = document.getElementById("modal-container");
|
|
22
|
+
container.classList.toggle("iscustom", contentClass !== ".modal-content");
|
|
23
|
+
|
|
24
|
+
if (cancelable) {
|
|
25
|
+
|
|
26
|
+
mCurrentCancelable = cancelable;
|
|
27
|
+
|
|
28
|
+
container.onclick = (event) => {
|
|
29
|
+
if (event.target.id === "modal-container") {
|
|
30
|
+
runCancelable(container);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!message) {
|
|
36
|
+
delete popupMessage.redraw;
|
|
37
|
+
if (message === null)
|
|
38
|
+
container.style.display = "none";
|
|
39
|
+
else
|
|
40
|
+
runCancelable(container);
|
|
41
|
+
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
container.style.display = "flex";
|
|
46
|
+
|
|
47
|
+
popupMessage.redraw = () => {
|
|
48
|
+
m.render(
|
|
49
|
+
container,
|
|
50
|
+
m(contentClass, {
|
|
51
|
+
role: "alertdialog",
|
|
52
|
+
"aria-labelledby": "modaltitle",
|
|
53
|
+
"aria-modal": true
|
|
54
|
+
}, [
|
|
55
|
+
typeof message === "function" ? message() : message
|
|
56
|
+
])
|
|
57
|
+
);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
popupMessage.redraw();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function clearModal() {
|
|
64
|
+
const container = document.getElementById("modal-container");
|
|
65
|
+
if (container.style.display !== "none") {
|
|
66
|
+
runCancelable(container);
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export { popupMessage, clearModal };
|