alchemy-widget 0.1.5 → 0.2.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.
Files changed (36) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/assets/stylesheets/alchemy_widgets.scss +426 -0
  3. package/bootstrap.js +6 -0
  4. package/controller/alchemy_widgets_controller.js +85 -9
  5. package/element/00-widget_base_element.js +154 -6
  6. package/element/05-widget_element.js +112 -19
  7. package/element/10-container_elements.js +15 -15
  8. package/element/11-alchemy_widgets_list_element.js +2 -2
  9. package/element/20-add_area_element.js +23 -23
  10. package/element/table_of_contents_element.js +104 -8
  11. package/element/widget_actionbar_element.js +92 -0
  12. package/element/widget_context_element.js +39 -23
  13. package/element/widget_toolbar_element.js +300 -38
  14. package/helper/widget_action.js +19 -18
  15. package/helper/widgets/00-widget.js +160 -37
  16. package/helper/widgets/01-container.js +12 -7
  17. package/helper/widgets/05-list.js +1 -1
  18. package/helper/widgets/alchemy_field_widget.js +112 -0
  19. package/helper/widgets/alchemy_form_widget.js +183 -0
  20. package/helper/widgets/alchemy_table_widget.js +71 -0
  21. package/helper/widgets/alchemy_tabs_widget.js +195 -0
  22. package/helper/widgets/header.js +4 -4
  23. package/helper/widgets/markdown.js +17 -10
  24. package/helper/widgets/partial.js +215 -0
  25. package/helper/widgets/sourcecode.js +4 -4
  26. package/helper/widgets/table_of_contents.js +2 -2
  27. package/helper/widgets/text.js +14 -4
  28. package/helper/widgets_helper.js +26 -0
  29. package/package.json +4 -3
  30. package/view/elements/table_of_contents.hwk +23 -9
  31. package/view/form/inputs/edit/widget.hwk +2 -2
  32. package/view/form/inputs/edit/widgets.hwk +2 -2
  33. package/view/widget/elements/al_widget_toolbar.hwk +49 -0
  34. package/view/widget/widget_config.hwk +7 -4
  35. package/assets/stylesheets/alchemy-widget-symbols.scss +0 -191
  36. package/assets/stylesheets/alchemy-widgets.scss +0 -258
@@ -14,7 +14,16 @@ let Base = Function.inherits('Alchemy.Element', 'Alchemy.Element.Widget', 'Base'
14
14
  * @since 0.1.0
15
15
  * @version 0.1.0
16
16
  */
17
- Base.setStylesheetFile('alchemy-widgets');
17
+ Base.setStylesheetFile('alchemy_widgets');
18
+
19
+ /**
20
+ * Set the custom element prefix
21
+ *
22
+ * @author Jelle De Loecker <jelle@elevenways.be>
23
+ * @since 0.1.0
24
+ * @version 0.2.0
25
+ */
26
+ Base.setStatic('custom_element_prefix', 'al');
18
27
 
19
28
  /**
20
29
  * Don't register this as a custom element
@@ -22,9 +31,9 @@ Base.setStylesheetFile('alchemy-widgets');
22
31
  *
23
32
  * @author Jelle De Loecker <jelle@elevenways.be>
24
33
  * @since 0.1.0
25
- * @version 0.1.0
34
+ * @version 0.2.0
26
35
  */
27
- Base.setStatic('is_abstract_class', true, false);
36
+ Base.makeAbstractClass();
28
37
 
29
38
  /**
30
39
  * The Widget class instance belonging to this element
@@ -114,6 +123,46 @@ Base.setProperty(function can_be_saved() {
114
123
  return true;
115
124
  });
116
125
 
126
+ /**
127
+ * Can this widget be removed?
128
+ *
129
+ * @author Jelle De Loecker <jelle@elevenways.be>
130
+ * @since 0.1.6
131
+ * @version 0.1.6
132
+ *
133
+ * @type {Boolean}
134
+ */
135
+ Base.setProperty(function can_be_removed() {
136
+
137
+ // Widgets without a "parent_instance" are mostly
138
+ // hardcoded in some template (like in a Partial widget)
139
+ if (!this.instance?.parent_instance) {
140
+ return false;
141
+ }
142
+
143
+ return true;
144
+ });
145
+
146
+ /**
147
+ * Can this widget be moved?
148
+ *
149
+ * @author Jelle De Loecker <jelle@elevenways.be>
150
+ * @since 0.1.6
151
+ * @version 0.1.6
152
+ *
153
+ * @type {Boolean}
154
+ */
155
+ Base.setProperty(function can_be_moved() {
156
+
157
+ // Widgets without a "parent_instance" are mostly
158
+ // hardcoded in some template (like in a Partial widget)
159
+ if (!this.instance?.parent_instance) {
160
+ return false;
161
+ }
162
+
163
+ return true;
164
+ });
165
+
117
166
  /**
118
167
  * Get a sibling container
119
168
  *
@@ -143,7 +192,7 @@ Base.setMethod(function getSiblingContainer(type) {
143
192
  return next;
144
193
  }
145
194
 
146
- if (next.tagName != 'ALCHEMY-WIDGET-ADD-AREA') {
195
+ if (next.tagName != 'AL-WIDGET-ADD-AREA') {
147
196
  return false;
148
197
  }
149
198
  }
@@ -232,13 +281,26 @@ Base.setMethod(function rerender() {
232
281
  *
233
282
  * @author Jelle De Loecker <jelle@elevenways.be>
234
283
  * @since 0.1.0
235
- * @version 0.1.0
284
+ * @version 0.1.6
236
285
  */
237
286
  Base.setMethod(function introduced() {
238
287
 
239
288
  if (this.hasAttribute('editing')) {
240
289
  this.startEditor();
241
290
  }
291
+
292
+ this.addEventListener('click', e => {
293
+
294
+ let is_editing = this.instance?.editing;
295
+
296
+ if (is_editing) {
297
+ let anchor = e.target.closest('a');
298
+
299
+ if (anchor) {
300
+ e.preventDefault();
301
+ }
302
+ }
303
+ });
242
304
  });
243
305
 
244
306
  /**
@@ -246,7 +308,7 @@ Base.setMethod(function introduced() {
246
308
  *
247
309
  * @author Jelle De Loecker <jelle@elevenways.be>
248
310
  * @since 0.1.5
249
- * @version 0.1.5
311
+ * @version 0.1.6
250
312
  */
251
313
  Base.setMethod(function gatherSaveData() {
252
314
 
@@ -263,6 +325,9 @@ Base.setMethod(function gatherSaveData() {
263
325
  pk : this.record.$pk,
264
326
  field : this.field,
265
327
  value : this.value,
328
+ filter_target : this.filter_target,
329
+ filter_value : this.filter_value,
330
+ value_path : this.value_path,
266
331
  field_languages : this.record.$hold.translated_fields,
267
332
  };
268
333
 
@@ -292,4 +357,87 @@ Base.setMethod(async function save() {
292
357
  };
293
358
 
294
359
  let result = await alchemy.fetch(config);
360
+ });
361
+
362
+ /**
363
+ * Copy this widget's configuration to the clipboard
364
+ *
365
+ * @author Jelle De Loecker <jelle@elevenways.be>
366
+ * @since 0.2.0
367
+ * @version 0.2.0
368
+ */
369
+ Base.setMethod(async function copyConfigToClipboard() {
370
+
371
+ let value = this.value;
372
+
373
+ if (!value) {
374
+ return;
375
+ }
376
+
377
+ value._altype = 'widget';
378
+ value.type = this.type;
379
+
380
+ try {
381
+ await navigator.clipboard.writeText(JSON.dry(value, null, '\t'));
382
+ } catch (err) {
383
+ console.error('Failed to copy:', err);
384
+ }
385
+ });
386
+
387
+ /**
388
+ * Get configuration from the clipboard and return it if it's valid
389
+ *
390
+ * @author Jelle De Loecker <jelle@elevenways.be>
391
+ * @since 0.2.0
392
+ * @version 0.2.0
393
+ */
394
+ Base.setMethod(async function getConfigFromClipboard() {
395
+
396
+ let result;
397
+
398
+ try {
399
+ result = await navigator.clipboard.readText();
400
+ } catch (err) {
401
+ return false;
402
+ }
403
+
404
+ if (result) {
405
+ result = result.trim();
406
+ }
407
+
408
+ if (!result || result[0] != '{') {
409
+ return false;
410
+ }
411
+
412
+ try {
413
+ result = JSON.undry(result);
414
+ } catch (err) {
415
+ return false;
416
+ }
417
+
418
+ if (result._altype != 'widget') {
419
+ return false;
420
+ }
421
+
422
+ if (result.type != this.type && !this.can_be_removed) {
423
+ return false;
424
+ }
425
+
426
+ return result;
427
+ });
428
+
429
+ /**
430
+ * Read the configuration from the clipboard & apply
431
+ *
432
+ * @author Jelle De Loecker <jelle@elevenways.be>
433
+ * @since 0.2.0
434
+ * @version 0.2.0
435
+ */
436
+ Base.setMethod(async function pasteConfigFromClipboard() {
437
+
438
+ let result = await this.getConfigFromClipboard();
439
+
440
+ if (result) {
441
+ this.value = result;
442
+ }
295
443
  });
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The alchemy-widget element
2
+ * The al-widget element
3
3
  *
4
4
  * @author Jelle De Loecker <jelle@elevenways.be>
5
5
  * @since 0.1.0
@@ -7,15 +7,6 @@
7
7
  */
8
8
  let Widget = Function.inherits('Alchemy.Element.Widget.Base', 'Widget');
9
9
 
10
- /**
11
- * Set the custom element prefix
12
- *
13
- * @author Jelle De Loecker <jelle@elevenways.be>
14
- * @since 0.1.0
15
- * @version 0.1.0
16
- */
17
- Widget.setStatic('custom_element_prefix', 'alchemy');
18
-
19
10
  /**
20
11
  * The type of widget
21
12
  *
@@ -52,6 +43,42 @@ Widget.setAssignedProperty('record');
52
43
  */
53
44
  Widget.setAssignedProperty('field');
54
45
 
46
+ /**
47
+ * The path to the value inside the field
48
+ *
49
+ * @author Jelle De Loecker <jelle@elevenways.be>
50
+ * @since 0.1.6
51
+ * @version 0.1.6
52
+ */
53
+ Widget.setAssignedProperty('value_path');
54
+
55
+ /**
56
+ * The path to the field to filter on
57
+ *
58
+ * @author Jelle De Loecker <jelle@elevenways.be>
59
+ * @since 0.1.6
60
+ * @version 0.1.6
61
+ */
62
+ Widget.setAssignedProperty('filter_target');
63
+
64
+ /**
65
+ * The filter value
66
+ *
67
+ * @author Jelle De Loecker <jelle@elevenways.be>
68
+ * @since 0.1.6
69
+ * @version 0.1.6
70
+ */
71
+ Widget.setAssignedProperty('filter_value');
72
+
73
+ /**
74
+ * CSS classes to put on the direct children
75
+ *
76
+ * @author Jelle De Loecker <jelle@elevenways.be>
77
+ * @since 0.1.6
78
+ * @version 0.1.6
79
+ */
80
+ Widget.setAttribute('child-class');
81
+
55
82
  /**
56
83
  * Is this widget being edited?
57
84
  *
@@ -89,11 +116,11 @@ Widget.setProperty(function value() {
89
116
  *
90
117
  * @author Jelle De Loecker <jelle@elevenways.be>
91
118
  * @since 0.1.5
92
- * @version 0.1.5
119
+ * @version 0.1.6
93
120
  */
94
121
  Widget.setMethod(function onRecordAssignment(new_record, old_val) {
95
122
  if (new_record && this.field) {
96
- let value = new_record[this.field];
123
+ let value = this.getValueFromRecord(new_record);
97
124
  this.applyValue(value);
98
125
  }
99
126
  });
@@ -103,21 +130,83 @@ Widget.setMethod(function onRecordAssignment(new_record, old_val) {
103
130
  *
104
131
  * @author Jelle De Loecker <jelle@elevenways.be>
105
132
  * @since 0.1.5
106
- * @version 0.1.5
133
+ * @version 0.1.6
107
134
  */
108
135
  Widget.setMethod(function onFieldAssignment(new_field, old_val) {
109
136
  if (new_field && this.record) {
110
- let value = this.record[new_field];
137
+ let value = this.getValueFromRecord(this.record);
111
138
  this.applyValue(value);
112
139
  }
113
140
  });
114
141
 
142
+ /**
143
+ * Get the values to work with from the given record
144
+ *
145
+ * @author Jelle De Loecker <jelle@elevenways.be>
146
+ * @since 0.1.6
147
+ * @version 0.1.6
148
+ */
149
+ Widget.setMethod(function getValueFromRecord(record) {
150
+
151
+ if (!record) {
152
+ return;
153
+ }
154
+
155
+ let value;
156
+
157
+ if (this.field) {
158
+ value = record[this.field];
159
+ } else {
160
+ value = record;
161
+ }
162
+
163
+ if (!value) {
164
+ return;
165
+ }
166
+
167
+ if (this.filter_value) {
168
+ if (!Array.isArray(value) || !this.filter_target) {
169
+ return;
170
+ }
171
+
172
+ let inputs = value;
173
+ value = [];
174
+
175
+ for (let input of inputs) {
176
+ if (input[this.filter_target] == this.filter_value) {
177
+ value.push(input);
178
+ }
179
+ }
180
+ }
181
+
182
+ if (this.value_path) {
183
+ if (!Array.isArray(value)) {
184
+ return;
185
+ }
186
+
187
+ let inputs = value;
188
+ value = [];
189
+
190
+ for (let input of inputs) {
191
+ if (input[this.value_path]) {
192
+ value.push(input[this.value_path]);
193
+ }
194
+ }
195
+ }
196
+
197
+ if (Array.isArray(value)) {
198
+ value = value[0];
199
+ }
200
+
201
+ return value;
202
+ });
203
+
115
204
  /**
116
205
  * Apply the given value
117
206
  *
118
207
  * @author Jelle De Loecker <jelle@elevenways.be>
119
208
  * @since 0.1.5
120
- * @version 0.1.5
209
+ * @version 0.1.6
121
210
  */
122
211
  Widget.setMethod(function applyValue(value) {
123
212
 
@@ -157,7 +246,11 @@ Widget.setMethod(function applyValue(value) {
157
246
  }
158
247
 
159
248
  this.instance.config = config;
160
- this.instance.populateWidget();
249
+ let promise = this.instance.populateWidget();
250
+
251
+ if (promise) {
252
+ this.delayAssemble(promise);
253
+ }
161
254
  });
162
255
 
163
256
  /**
@@ -261,14 +354,14 @@ Widget.setMethod(function removeEditEventListeners() {
261
354
  *
262
355
  * @author Jelle De Loecker <jelle@elevenways.be>
263
356
  * @since 0.1.0
264
- * @version 0.1.0
357
+ * @version 0.2.0
265
358
  */
266
359
  Widget.setMethod(function getContextButton() {
267
360
 
268
- let button = document.querySelector('alchemy-widget-context');
361
+ let button = document.querySelector('al-widget-context');
269
362
 
270
363
  if (!button) {
271
- button = this.createElement('alchemy-widget-context');
364
+ button = this.createElement('al-widget-context');
272
365
  document.body.append(button);
273
366
  }
274
367
 
@@ -1,13 +1,13 @@
1
1
  /**
2
- * The alchemy-widgets element is the base "container" for all other widgets.
2
+ * The al-widgets element is the base "container" for all other widgets.
3
3
  * It should never be nested, though
4
4
  *
5
5
  * @author Jelle De Loecker <jelle@elevenways.be>
6
6
  * @since 0.1.0
7
7
  * @version 0.1.0
8
8
  */
9
- let AlchemyWidgets = Function.inherits('Alchemy.Element.Widget', function AlchemyWidgets() {
10
- AlchemyWidgets.super.call(this);
9
+ let AlchemyWidgets = Function.inherits('Alchemy.Element.Widget', function AlWidgets() {
10
+ AlWidgets.super.call(this);
11
11
 
12
12
  // Always create this dummy instance just in case?
13
13
  this.instance = new Classes.Alchemy.Widget.Container();
@@ -19,9 +19,9 @@ let AlchemyWidgets = Function.inherits('Alchemy.Element.Widget', function Alchem
19
19
  *
20
20
  * @author Jelle De Loecker <jelle@elevenways.be>
21
21
  * @since 0.1.0
22
- * @version 0.1.0
22
+ * @version 0.2.0
23
23
  */
24
- AlchemyWidgets.setStatic('custom_element_prefix', 'alchemy-widgets');
24
+ AlchemyWidgets.setStatic('custom_element_prefix', 'al-widgets');
25
25
 
26
26
  /**
27
27
  * Don't add the edit event listeners
@@ -69,7 +69,7 @@ AlchemyWidgets.setProperty(function value() {
69
69
  let widgets = this.getWidgetsConfig(),
70
70
  result;
71
71
 
72
- if (this.nodeName == 'ALCHEMY-WIDGETS') {
72
+ if (this.nodeName == 'AL-WIDGETS') {
73
73
  result = {widgets};
74
74
  } else {
75
75
  result = {
@@ -186,7 +186,7 @@ AlchemyWidgets.setMethod(function clear() {
186
186
  for (i = 0; i < children.length; i++) {
187
187
  child = children[i];
188
188
 
189
- if (child.nodeName == 'ALCHEMY-WIDGET-ADD-AREA') {
189
+ if (child.nodeName == 'AL-WIDGET-ADD-AREA') {
190
190
  continue;
191
191
  }
192
192
 
@@ -240,7 +240,7 @@ AlchemyWidgets.setMethod(function addWidget(type, config) {
240
240
  *
241
241
  * @author Jelle De Loecker <jelle@elevenways.be>
242
242
  * @since 0.1.0
243
- * @version 0.1.0
243
+ * @version 0.2.0
244
244
  *
245
245
  * @param {Element} element
246
246
  */
@@ -249,7 +249,7 @@ AlchemyWidgets.setMethod(function _appendWidgetElement(element) {
249
249
  let add_area;
250
250
 
251
251
  if (this.editing) {
252
- add_area = this.querySelector(':scope > alchemy-widget-add-area');
252
+ add_area = this.querySelector(':scope > al-widget-add-area');
253
253
  }
254
254
 
255
255
  if (add_area) {
@@ -270,19 +270,19 @@ AlchemyWidgets.setMethod(function _appendWidgetElement(element) {
270
270
  AlchemyWidgets.setMethod(function initEventListeners() {});
271
271
 
272
272
  /**
273
- * The alchemy-widgets-column element
273
+ * The al-widgets-column element
274
274
  *
275
275
  * @author Jelle De Loecker <jelle@elevenways.be>
276
276
  * @since 0.1.0
277
- * @version 0.1.0
277
+ * @version 0.2.0
278
278
  */
279
- Function.inherits('Alchemy.Element.Widget.AlchemyWidgets', 'Column');
279
+ Function.inherits('Alchemy.Element.Widget.AlWidgets', 'Column');
280
280
 
281
281
  /**
282
- * The alchemy-widgets-row element
282
+ * The al-widgets-row element
283
283
  *
284
284
  * @author Jelle De Loecker <jelle@elevenways.be>
285
285
  * @since 0.1.0
286
- * @version 0.1.0
286
+ * @version 0.2.0
287
287
  */
288
- Function.inherits('Alchemy.Element.Widget.AlchemyWidgets', 'Row');
288
+ Function.inherits('Alchemy.Element.Widget.AlWidgets', 'Row');
@@ -1,11 +1,11 @@
1
1
  /**
2
- * The alchemy-widgets-list element
2
+ * The al-widgets-list element
3
3
  *
4
4
  * @author Jelle De Loecker <jelle@elevenways.be>
5
5
  * @since 0.1.0
6
6
  * @version 0.1.0
7
7
  */
8
- const List = Function.inherits('Alchemy.Element.Widget.AlchemyWidgets', 'List');
8
+ const List = Function.inherits('Alchemy.Element.Widget.AlWidgets', 'List');
9
9
 
10
10
  /**
11
11
  * Get the list element
@@ -1,17 +1,17 @@
1
1
  /**
2
- * The alchemy-widgets element
2
+ * The al-widgets element
3
3
  *
4
4
  * @author Jelle De Loecker <jelle@elevenways.be>
5
5
  * @since 0.1.0
6
- * @version 0.1.0
6
+ * @version 0.2.0
7
7
  */
8
- let AddArea = Function.inherits('Alchemy.Element.Widget.Base', function AlchemyWidgetAddArea() {
9
- AlchemyWidgetAddArea.super.call(this);
8
+ let AddArea = Function.inherits('Alchemy.Element.Widget.Base', function WidgetAddArea() {
9
+ WidgetAddArea.super.call(this);
10
10
 
11
11
  this.innerHTML = `
12
12
  <div class="main-button">
13
- <button class="add-button widget-button" title="Add"><i class="gg-math-plus"></i></button>
14
- <button class="menu-button widget-button" title="Menu"><i class="gg-menu-grid-o"></i></button>
13
+ <button class="add-button widget-button" title="Add"><al-icon icon-name="plus"></al-icon></button>
14
+ <button class="menu-button widget-button" title="Menu"><al-icon icon-name="grid"></al-icon></button>
15
15
  </div>
16
16
  <div class="widget-types">
17
17
  TYPES
@@ -24,16 +24,16 @@ let AddArea = Function.inherits('Alchemy.Element.Widget.Base', function AlchemyW
24
24
  *
25
25
  * @author Jelle De Loecker <jelle@elevenways.be>
26
26
  * @since 0.1.0
27
- * @version 0.1.0
27
+ * @version 0.2.0
28
28
  */
29
29
  AddArea.setMethod(function showTypes(event) {
30
30
 
31
31
  let that = this;
32
32
 
33
- let context_button = document.querySelector('alchemy-widget-context');
33
+ let context_button = document.querySelector('al-widget-context');
34
34
 
35
- if (context_button && context_button.active_widget) {
36
- context_button.unselectedWidget();
35
+ if (context_button) {
36
+ context_button.forceUnselection();
37
37
  }
38
38
 
39
39
  let context = this.createElement('he-context-menu');
@@ -42,7 +42,7 @@ AddArea.setMethod(function showTypes(event) {
42
42
 
43
43
  for (let widget of widgets) {
44
44
 
45
- if (widget.type_name == 'container') {
45
+ if (!widget.canBeAdded(that.parentElement)) {
46
46
  continue;
47
47
  }
48
48
 
@@ -58,27 +58,27 @@ AddArea.setMethod(function showTypes(event) {
58
58
  });
59
59
 
60
60
  /**
61
- * Show the toolbar
61
+ * Show the actionbar
62
62
  *
63
63
  * @author Jelle De Loecker <jelle@elevenways.be>
64
64
  * @since 0.1.0
65
- * @version 0.1.0
65
+ * @version 0.2.0
66
66
  */
67
- AddArea.setMethod(function showToolbar() {
67
+ AddArea.setMethod(function showActionbar() {
68
68
 
69
- if (!this.toolbar) {
70
- this.toolbar = document.createElement('alchemy-widget-toolbar');
71
- this.append(this.toolbar);
69
+ if (!this.actionbar) {
70
+ this.actionbar = document.createElement('al-widget-actionbar');
71
+ this.append(this.actionbar);
72
72
  }
73
73
 
74
- if (this.toolbar.context_element == this && !this.toolbar.hidden) {
75
- this.toolbar.close();
74
+ if (this.actionbar.context_element == this && !this.actionbar.hidden) {
75
+ this.actionbar.close();
76
76
  return;
77
77
  }
78
78
 
79
- this.toolbar.hidden = false;
80
- this.toolbar.context_element = this;
81
- this.toolbar.showWidgetActions(this.parentElement);
79
+ this.actionbar.hidden = false;
80
+ this.actionbar.context_element = this;
81
+ this.actionbar.showWidgetActions(this.parentElement);
82
82
 
83
83
  });
84
84
 
@@ -106,6 +106,6 @@ AddArea.setMethod(function introduced() {
106
106
 
107
107
  context_button.addEventListener('click', function onClick(e) {
108
108
  e.preventDefault();
109
- that.showToolbar();
109
+ that.showActionbar();
110
110
  });
111
111
  });