alchemy-widget 0.3.0-alpha.1 → 0.3.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/CHANGELOG.md +16 -0
- package/CLAUDE.md +160 -0
- package/assets/stylesheets/alchemy_widgets.scss +0 -0
- package/assets/stylesheets/widget_picker.scss +283 -0
- package/element/20-add_area_element.js +19 -21
- package/element/30-base_toolbar_element.js +189 -2
- package/element/editor_toolbar_element.js +121 -1
- package/element/user_avatar_group_element.js +5 -0
- package/element/widget_picker_element.js +333 -0
- package/element/widget_toolbar_element.js +22 -137
- package/helper/document_watcher.js +41 -6
- package/helper/editor_toolbar_manager.js +112 -20
- package/helper/widgets/00-widget.js +256 -1
- package/helper/widgets/05-column.js +4 -0
- package/helper/widgets/05-list.js +55 -0
- package/helper/widgets/05-row.js +4 -0
- package/helper/widgets/alchemy_field_widget.js +5 -0
- package/helper/widgets/alchemy_form_widget.js +5 -0
- package/helper/widgets/alchemy_table_widget.js +4 -0
- package/helper/widgets/alchemy_tabs_widget.js +5 -0
- package/helper/widgets/hawkejs_template.js +4 -0
- package/helper/widgets/header.js +4 -0
- package/helper/widgets/html.js +4 -0
- package/helper/widgets/markdown.js +4 -0
- package/helper/widgets/sourcecode.js +4 -0
- package/helper/widgets/table_of_contents.js +4 -0
- package/helper/widgets/text.js +4 -0
- package/lib/conduit_extras.js +15 -4
- package/package.json +2 -2
- package/view/widget/elements/al_editor_toolbar.hwk +49 -1
- package/view/widget/elements/al_widget_toolbar.hwk +1 -1
- package/view/widget/elements/widget_picker.hwk +55 -0
|
@@ -57,7 +57,7 @@ Toolbar.addElementGetter('button_save_all', 'al-button.save-all');
|
|
|
57
57
|
*
|
|
58
58
|
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
59
59
|
* @since 0.2.0
|
|
60
|
-
* @version 0.
|
|
60
|
+
* @version 0.3.0
|
|
61
61
|
*/
|
|
62
62
|
Toolbar.setStatic(function show() {
|
|
63
63
|
|
|
@@ -71,6 +71,11 @@ Toolbar.setStatic(function show() {
|
|
|
71
71
|
return;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
// Don't show if an al-editor-toolbar exists (e.g., in Chimera)
|
|
75
|
+
if (document.querySelector('al-editor-toolbar')) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
74
79
|
toolbar = hawkejs.createElement('al-widget-toolbar');
|
|
75
80
|
|
|
76
81
|
hawkejs.scene.bottom_element.append(toolbar);
|
|
@@ -105,27 +110,27 @@ Toolbar.setMethod(function getAllRootWidgets() {
|
|
|
105
110
|
return result;
|
|
106
111
|
});
|
|
107
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Get the target widgets (all root widgets for this toolbar)
|
|
115
|
+
*
|
|
116
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
117
|
+
* @since 0.3.0
|
|
118
|
+
* @version 0.3.0
|
|
119
|
+
*/
|
|
120
|
+
Toolbar.setMethod(function getTargetWidgets() {
|
|
121
|
+
return this.getAllRootWidgets();
|
|
122
|
+
});
|
|
123
|
+
|
|
108
124
|
/**
|
|
109
125
|
* Start editing all the widgets
|
|
110
126
|
*
|
|
111
127
|
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
112
128
|
* @since 0.2.0
|
|
113
|
-
* @version 0.
|
|
129
|
+
* @version 0.3.0
|
|
114
130
|
*/
|
|
115
131
|
Toolbar.setMethod(function startEditing() {
|
|
116
|
-
|
|
117
|
-
let i;
|
|
118
|
-
|
|
119
132
|
Blast.editing = true;
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
let elements = this.getAllRootWidgets();
|
|
123
|
-
|
|
124
|
-
for (i = 0; i < elements.length; i++) {
|
|
125
|
-
elements[i].startEditor();
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
this.setState('editing');
|
|
133
|
+
startEditing.super.call(this);
|
|
129
134
|
});
|
|
130
135
|
|
|
131
136
|
/**
|
|
@@ -133,7 +138,7 @@ Toolbar.setMethod(function startEditing() {
|
|
|
133
138
|
*
|
|
134
139
|
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
135
140
|
* @since 0.2.0
|
|
136
|
-
* @version 0.
|
|
141
|
+
* @version 0.3.0
|
|
137
142
|
*/
|
|
138
143
|
Toolbar.setMethod(function stopEditing() {
|
|
139
144
|
|
|
@@ -141,123 +146,8 @@ Toolbar.setMethod(function stopEditing() {
|
|
|
141
146
|
return;
|
|
142
147
|
}
|
|
143
148
|
|
|
144
|
-
let i;
|
|
145
|
-
|
|
146
149
|
Blast.editing = false;
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
let elements = this.getAllRootWidgets();
|
|
150
|
-
|
|
151
|
-
for (i = 0; i < elements.length; i++) {
|
|
152
|
-
elements[i].stopEditor();
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
this.setState('ready');
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Save all the widgets
|
|
160
|
-
*
|
|
161
|
-
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
162
|
-
* @since 0.2.0
|
|
163
|
-
* @version 0.2.0
|
|
164
|
-
*/
|
|
165
|
-
Toolbar.setMethod(async function saveAll() {
|
|
166
|
-
|
|
167
|
-
if (this._saving) {
|
|
168
|
-
try {
|
|
169
|
-
await this._saving;
|
|
170
|
-
} catch (err) {
|
|
171
|
-
// Ignore;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
this._saving = null;
|
|
176
|
-
|
|
177
|
-
let elements = this.getAllRootWidgets();
|
|
178
|
-
let widget_data = [];
|
|
179
|
-
let pledge;
|
|
180
|
-
|
|
181
|
-
for (let element of elements) {
|
|
182
|
-
let entry = element.gatherSaveData();
|
|
183
|
-
|
|
184
|
-
if (entry) {
|
|
185
|
-
widget_data.push(entry);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
if (widget_data.length) {
|
|
190
|
-
let config = {
|
|
191
|
-
href : alchemy.routeUrl('AlchemyWidgets#save'),
|
|
192
|
-
post : {
|
|
193
|
-
widgets: widget_data
|
|
194
|
-
}
|
|
195
|
-
};
|
|
196
|
-
|
|
197
|
-
pledge = alchemy.fetch(config);
|
|
198
|
-
this._saving = pledge;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
return pledge;
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Save all and update the states
|
|
206
|
-
*
|
|
207
|
-
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
208
|
-
* @since 0.2.0
|
|
209
|
-
* @version 0.2.0
|
|
210
|
-
*
|
|
211
|
-
* @param {Boolean} before_stop
|
|
212
|
-
*/
|
|
213
|
-
Toolbar.setMethod(async function saveAllAndUpdateButtonStates(before_stop) {
|
|
214
|
-
|
|
215
|
-
let state = 'saving',
|
|
216
|
-
button;
|
|
217
|
-
|
|
218
|
-
if (before_stop) {
|
|
219
|
-
button = this.button_stop_and_save;
|
|
220
|
-
state += '-before-stop';
|
|
221
|
-
} else {
|
|
222
|
-
button = this.button_save_all;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
this.setState(state);
|
|
226
|
-
button.setState(state);
|
|
227
|
-
|
|
228
|
-
let save_error = null;
|
|
229
|
-
|
|
230
|
-
let restore_toolbar_state = this.wrapForCurrentState(() => {
|
|
231
|
-
if (save_error) {
|
|
232
|
-
this.setState('error');
|
|
233
|
-
} else {
|
|
234
|
-
this.setState('editing')
|
|
235
|
-
}
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
let restore_button_state = button.wrapForCurrentState(() => {
|
|
239
|
-
|
|
240
|
-
if (save_error) {
|
|
241
|
-
button.setState('error');
|
|
242
|
-
} else {
|
|
243
|
-
button.setState('saved', 2500, 'ready');
|
|
244
|
-
}
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
try {
|
|
248
|
-
await this.saveAll();
|
|
249
|
-
} catch (err) {
|
|
250
|
-
save_error = err;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
restore_toolbar_state();
|
|
254
|
-
restore_button_state();
|
|
255
|
-
|
|
256
|
-
if (save_error) {
|
|
257
|
-
return false;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
return true;
|
|
150
|
+
stopEditing.super.call(this);
|
|
261
151
|
});
|
|
262
152
|
|
|
263
153
|
/**
|
|
@@ -284,11 +174,6 @@ Toolbar.setMethod(function introduced() {
|
|
|
284
174
|
|
|
285
175
|
if (this.toolbar_manager) {
|
|
286
176
|
this.prepareToolbarManager(this.toolbar_manager);
|
|
287
|
-
} else {
|
|
288
|
-
let manager = hawkejs.scene.exposed.toolbar_manager;
|
|
289
|
-
if (manager) {
|
|
290
|
-
this.prepareToolbarManager(manager);
|
|
291
|
-
}
|
|
292
177
|
}
|
|
293
178
|
|
|
294
179
|
this.button_start.addEventListener('activate', async e => {
|
|
@@ -354,4 +239,4 @@ Toolbar.setMethod(function connected() {
|
|
|
354
239
|
Toolbar.setMethod(function disconnected() {
|
|
355
240
|
let html = document.querySelector('html');
|
|
356
241
|
html.classList.remove('with-al-widget-toolbar');
|
|
357
|
-
});
|
|
242
|
+
});
|
|
@@ -24,11 +24,9 @@ const COLOURS = [
|
|
|
24
24
|
*
|
|
25
25
|
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
26
26
|
* @since 0.2.7
|
|
27
|
-
* @version 0.
|
|
27
|
+
* @version 0.3.0
|
|
28
28
|
*/
|
|
29
|
-
const DocumentWatcher = Function.inherits('Alchemy.Syncable', 'Alchemy.Widget',
|
|
30
|
-
DocumentWatcher.super.call(this, 'document_watcher');
|
|
31
|
-
});
|
|
29
|
+
const DocumentWatcher = Function.inherits('Alchemy.Syncable.Specialized', 'Alchemy.Widget', 'DocumentWatcher');
|
|
32
30
|
|
|
33
31
|
/**
|
|
34
32
|
* Create a watcher for the given document
|
|
@@ -60,6 +58,42 @@ DocumentWatcher.setStatic(function create(model, pk) {
|
|
|
60
58
|
|
|
61
59
|
if (Blast.isNode) {
|
|
62
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Recreate a watcher after server restart.
|
|
63
|
+
* Called by Syncable.tryRecreate() when client reconnects.
|
|
64
|
+
*
|
|
65
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
66
|
+
* @since 0.3.0
|
|
67
|
+
* @version 0.3.0
|
|
68
|
+
*
|
|
69
|
+
* @param {Conduit} conduit
|
|
70
|
+
* @param {Object} config Contains type, id, and version from the client
|
|
71
|
+
*
|
|
72
|
+
* @return {DocumentWatcher}
|
|
73
|
+
*/
|
|
74
|
+
DocumentWatcher.setStatic(async function recreate(conduit, config) {
|
|
75
|
+
|
|
76
|
+
// The config.id follows the pattern "Model:pk"
|
|
77
|
+
let parts = config.id.split(':');
|
|
78
|
+
|
|
79
|
+
if (parts.length < 2) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let model = parts[0],
|
|
84
|
+
pk = parts.slice(1).join(':'); // Handle pks with colons
|
|
85
|
+
|
|
86
|
+
// Use the existing create method which handles caching
|
|
87
|
+
let watcher = this.create(model, pk);
|
|
88
|
+
|
|
89
|
+
if (watcher) {
|
|
90
|
+
// Re-add this conduit as a watcher
|
|
91
|
+
await watcher.addWatcher(conduit);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return watcher;
|
|
95
|
+
});
|
|
96
|
+
|
|
63
97
|
/**
|
|
64
98
|
* Add a viewer based on the conduit
|
|
65
99
|
*
|
|
@@ -69,12 +103,13 @@ if (Blast.isNode) {
|
|
|
69
103
|
*
|
|
70
104
|
* @param {Alchemy.Conduit} conduit
|
|
71
105
|
*/
|
|
72
|
-
DocumentWatcher.setTypedMethod([Types.Alchemy.Conduit], function addWatcher(conduit) {
|
|
106
|
+
DocumentWatcher.setTypedMethod([Types.Alchemy.Conduit], async function addWatcher(conduit) {
|
|
73
107
|
|
|
74
108
|
let user_id = '' + conduit.getUserId(),
|
|
75
109
|
scene_id = conduit.scene_id;
|
|
76
110
|
|
|
77
|
-
|
|
111
|
+
// IMPORTANT: await the async addWatcher method!
|
|
112
|
+
await this.addWatcher(user_id, scene_id);
|
|
78
113
|
|
|
79
114
|
// Watchers should also be registered as a client
|
|
80
115
|
this.registerClient(conduit);
|
|
@@ -9,10 +9,64 @@ const SCENE_MAP = new Map(),
|
|
|
9
9
|
*
|
|
10
10
|
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
11
11
|
* @since 0.2.7
|
|
12
|
-
* @version 0.
|
|
12
|
+
* @version 0.3.0
|
|
13
|
+
*/
|
|
14
|
+
const EditorToolbarManager = Function.inherits('Alchemy.Syncable.Specialized', 'Alchemy.Widget', 'EditorToolbarManager');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Storage for document button providers
|
|
18
|
+
*
|
|
19
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
20
|
+
* @since 0.3.0
|
|
21
|
+
* @version 0.3.0
|
|
22
|
+
*/
|
|
23
|
+
EditorToolbarManager.setStatic('document_button_providers', []);
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Storage for model button providers
|
|
27
|
+
*
|
|
28
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
29
|
+
* @since 0.3.0
|
|
30
|
+
* @version 0.3.0
|
|
31
|
+
*/
|
|
32
|
+
EditorToolbarManager.setStatic('model_button_providers', []);
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Register a callback that can add toolbar buttons when a document is set.
|
|
36
|
+
* This allows plugins to add their own buttons without modifying this file.
|
|
37
|
+
*
|
|
38
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
39
|
+
* @since 0.3.0
|
|
40
|
+
* @version 0.3.0
|
|
41
|
+
*
|
|
42
|
+
* @param {Function} callback Function(manager, doc, model, model_name, pk_val)
|
|
43
|
+
*/
|
|
44
|
+
EditorToolbarManager.setStatic(function registerDocumentButtonProvider(callback) {
|
|
45
|
+
|
|
46
|
+
if (typeof callback !== 'function') {
|
|
47
|
+
throw new Error('Button provider must be a function');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
this.document_button_providers.push(callback);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Register a callback that can add toolbar buttons when a model is set.
|
|
55
|
+
* This allows plugins to add their own buttons without modifying this file.
|
|
56
|
+
*
|
|
57
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
58
|
+
* @since 0.3.0
|
|
59
|
+
* @version 0.3.0
|
|
60
|
+
*
|
|
61
|
+
* @param {Function} callback Function(manager, model_name)
|
|
13
62
|
*/
|
|
14
|
-
|
|
15
|
-
|
|
63
|
+
EditorToolbarManager.setStatic(function registerModelButtonProvider(callback) {
|
|
64
|
+
|
|
65
|
+
if (typeof callback !== 'function') {
|
|
66
|
+
throw new Error('Button provider must be a function');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
this.model_button_providers.push(callback);
|
|
16
70
|
});
|
|
17
71
|
|
|
18
72
|
if (Blast.isNode) {
|
|
@@ -61,6 +115,36 @@ if (Blast.isNode) {
|
|
|
61
115
|
|
|
62
116
|
return manager;
|
|
63
117
|
});
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Recreate a manager after server restart.
|
|
121
|
+
* Called by Syncable.tryRecreate() when client reconnects.
|
|
122
|
+
*
|
|
123
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
124
|
+
* @since 0.3.0
|
|
125
|
+
* @version 0.3.0
|
|
126
|
+
*
|
|
127
|
+
* @param {Conduit} conduit
|
|
128
|
+
* @param {Object} config Contains type, id, and version from the client
|
|
129
|
+
*
|
|
130
|
+
* @return {EditorToolbarManager}
|
|
131
|
+
*/
|
|
132
|
+
EditorToolbarManager.setStatic(async function recreate(conduit, config) {
|
|
133
|
+
|
|
134
|
+
// The config.id is the scene_id from the client.
|
|
135
|
+
// Set it on the conduit if not already set, so create() can use it.
|
|
136
|
+
if (!conduit.scene_id) {
|
|
137
|
+
conduit.scene_id = config.id;
|
|
138
|
+
} else if (conduit.scene_id !== config.id) {
|
|
139
|
+
// Scene ID mismatch - this shouldn't happen but log if it does
|
|
140
|
+
log.warning('EditorToolbarManager recreate: scene_id mismatch', {
|
|
141
|
+
conduit_scene_id : conduit.scene_id,
|
|
142
|
+
config_id : config.id,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return this.create(conduit);
|
|
147
|
+
});
|
|
64
148
|
}
|
|
65
149
|
|
|
66
150
|
/**
|
|
@@ -99,6 +183,24 @@ EditorToolbarManager.setStateProperty('title', {allow_client_set: false});
|
|
|
99
183
|
*/
|
|
100
184
|
EditorToolbarManager.setStateProperty('scenario');
|
|
101
185
|
|
|
186
|
+
/**
|
|
187
|
+
* Whether this is a user-specific dashboard
|
|
188
|
+
*
|
|
189
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
190
|
+
* @since 0.3.0
|
|
191
|
+
* @version 0.3.0
|
|
192
|
+
*/
|
|
193
|
+
EditorToolbarManager.setStateProperty('is_user_dashboard', {allow_client_set: false});
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Whether the user has an ID (is logged in)
|
|
197
|
+
*
|
|
198
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
199
|
+
* @since 0.3.0
|
|
200
|
+
* @version 0.3.0
|
|
201
|
+
*/
|
|
202
|
+
EditorToolbarManager.setStateProperty('has_user_id', {allow_client_set: false});
|
|
203
|
+
|
|
102
204
|
/**
|
|
103
205
|
* Clear the model fallback
|
|
104
206
|
*
|
|
@@ -162,10 +264,11 @@ EditorToolbarManager.setTypedMethod([Types.String.optional().nullable()], functi
|
|
|
162
264
|
this.emitPropertyChange('model_name');
|
|
163
265
|
}
|
|
164
266
|
|
|
267
|
+
// Call registered model button providers
|
|
165
268
|
if (Blast.isNode && model_name) {
|
|
166
|
-
this.
|
|
167
|
-
|
|
168
|
-
}
|
|
269
|
+
for (let provider of this.constructor.model_button_providers) {
|
|
270
|
+
provider(this, model_name);
|
|
271
|
+
}
|
|
169
272
|
}
|
|
170
273
|
});
|
|
171
274
|
|
|
@@ -200,20 +303,9 @@ EditorToolbarManager.setMethod(function setDocument(doc) {
|
|
|
200
303
|
|
|
201
304
|
this.setDocumentWatcher(document_watcher);
|
|
202
305
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
record_pk: pk_val,
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
if (model.chimera.record_preview) {
|
|
211
|
-
if (this.scenario == 'chimera') {
|
|
212
|
-
this.addTemplateToRender('buttons', 'chimera/toolbar/preview_button', {
|
|
213
|
-
model_name: model_name.underscore(),
|
|
214
|
-
record_pk: pk_val,
|
|
215
|
-
});
|
|
216
|
-
}
|
|
306
|
+
// Call registered document button providers
|
|
307
|
+
for (let provider of this.constructor.document_button_providers) {
|
|
308
|
+
provider(this, doc, model, model_name, pk_val);
|
|
217
309
|
}
|
|
218
310
|
|
|
219
311
|
return document_watcher;
|