@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.
@@ -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 };
@@ -0,0 +1,10 @@
1
+ export default function defineWidget (view, oninit, oncreate, onremove, onupdate) {
2
+ return {
3
+ ...view && {view},
4
+ ...oninit && {oninit},
5
+ ...oncreate && {oncreate},
6
+ ...onupdate && {onupdate},
7
+ ...onremove && {onremove}
8
+ }
9
+ }
10
+