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.
@@ -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.2.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.2.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
- document.body.classList.add('editing-blocks');
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.2.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
- document.body.classList.remove('editing-blocks');
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.2.7
27
+ * @version 0.3.0
28
28
  */
29
- const DocumentWatcher = Function.inherits('Alchemy.Syncable', 'Alchemy.Widget', function DocumentWatcher() {
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
- this.addWatcher(user_id, scene_id);
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.2.7
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
- const EditorToolbarManager = Function.inherits('Alchemy.Syncable', 'Alchemy.Widget', function EditorToolbarManager() {
15
- EditorToolbarManager.super.call(this, 'editor_toolbar_manager');
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.addTemplateToRender('buttons', 'chimera/toolbar/create_button', {
167
- model_name: Blast.parseClassPath(model_name).map(entry => entry.underscore()).join('.'),
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
- if (this.scenario != 'chimera') {
204
- this.addTemplateToRender('buttons', 'chimera/toolbar/edit_in_chimera_button', {
205
- model_name: model_name.underscore(),
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;