alchemy-media 0.6.4 → 0.7.1

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 CHANGED
@@ -1,3 +1,20 @@
1
+ ## 0.7.1 (2022-12-23)
2
+
3
+ * Cascade `al-svg` role attribute to child `svg` elements
4
+ * Add `img` role fallback when using `graphics-symbol`
5
+ * Also add default role to `al-icon` element
6
+ * Make `al-file` catch errors and report them to a possible `al-field` parent
7
+ * Add the `accept` attribute to `al-file`
8
+ * Add the `accept` option to `File` fields
9
+ * Add `width_hint` field to `image` widget. This field allows users to specify the approximate maximum width of an image in pixels or as a percentage of the total page width.
10
+ * Add `lazy_load` field to `image` widget
11
+
12
+ ## 0.7.0 (2022-11-02)
13
+
14
+ * Use the `al-` prefix for custom elements
15
+ * Rename `al-ico` to `al-icon`
16
+ * Allow selecting existing uploaded files in an `al-file` element
17
+
1
18
  ## 0.6.4 (2022-10-13)
2
19
 
3
20
  * Add the `Media#loadIconFont()` helper method
package/README.md CHANGED
@@ -75,7 +75,7 @@ you can do so like this on Debian/Ubuntu
75
75
 
76
76
  Install the requirements of the `veronica` module:
77
77
 
78
- apt-get install graphicsmagick webp libgif-dev
78
+ apt-get install graphicsmagick webp libgif-dev libcairo2-dev libpango1.0-dev libjpeg-dev librsvg2-dev
79
79
 
80
80
  This plugin requires exiv2
81
81
 
@@ -0,0 +1 @@
1
+ @import "media_elements/index.scss";
@@ -37,7 +37,7 @@ al-file {
37
37
  visibility: hidden;
38
38
  }
39
39
 
40
- al-ico {
40
+ al-icon {
41
41
  position: absolute;
42
42
  top: 0;
43
43
  left: 0;
@@ -94,7 +94,7 @@ al-file {
94
94
  }
95
95
  }
96
96
 
97
- alchemy-field[field-view="file_preview"] {
97
+ al-field[field-view="file_preview"] {
98
98
  [data-he-name="field"] .field {
99
99
  padding: 0;
100
100
 
@@ -105,9 +105,9 @@ alchemy-field[field-view="file_preview"] {
105
105
  }
106
106
  }
107
107
 
108
- alchemy-field[field-view="file_preview"],
109
- alchemy-field[field-view="file"],
110
- alchemy-field[field-type="file"] {
108
+ al-field[field-view="file_preview"],
109
+ al-field[field-view="file"],
110
+ al-field[field-type="file"] {
111
111
 
112
112
  &[mode="inline"] {
113
113
  .field img {
@@ -1,4 +1,4 @@
1
- al-ico {
1
+ al-icon {
2
2
  // inline-flex breaks certain (duotone) icons
3
3
  //--fa-display: inline-flex;
4
4
  flex-flow: column;
@@ -0,0 +1,25 @@
1
+ al-image {
2
+ position: relative;
3
+ display: inline-block;
4
+ overflow: hidden;
5
+
6
+ .placeholder {
7
+ position: absolute;
8
+ left: 0;
9
+ top: 0;
10
+ width: 100%;
11
+ height: 100%;
12
+ transform: scale(1.03);
13
+ filter: blur(8px);
14
+ z-index: -1;
15
+ transition: 0.1s transform;
16
+ }
17
+
18
+ .final {
19
+ height: auto;
20
+ width: auto;
21
+ opacity: 0;
22
+ transition: 0.1s opacity, 0.1s transform;
23
+ transform: scale(1.03)
24
+ }
25
+ }
@@ -0,0 +1,4 @@
1
+ @import "_file.scss";
2
+ @import "_icon.scss";
3
+ @import "_svg.scss";
4
+ @import "_image.scss";
package/bootstrap.js CHANGED
@@ -48,6 +48,13 @@ Router.get('Media::image', options.url + '/{id}', 'MediaFile#image');
48
48
  Router.get('MediaFile#data', '/media/data/{prefix}/{id}', 'MediaFile#data');
49
49
  Router.get('MediaFile#info', '/media/info', 'MediaFile#info');
50
50
 
51
+ Router.add({
52
+ name : 'MediaFile#recordsource',
53
+ methods : ['get'],
54
+ paths : '/media/recordsource',
55
+ permission : 'media.recordsource',
56
+ });
57
+
51
58
  // Allow dummy extensions
52
59
  Router.get('Media::fileextension', '/media/file/{id}.{extension}', 'MediaFile#file');
53
60
 
@@ -206,7 +206,7 @@ MediaFiles.setAction(function downloadFile(conduit, id, extension) {
206
206
  *
207
207
  * @author Jelle De Loecker <jelle@develry.be>
208
208
  * @since 0.0.1
209
- * @version 0.4.2
209
+ * @version 0.7.1
210
210
  *
211
211
  * @param {Conduit} conduit
212
212
  */
@@ -215,16 +215,16 @@ MediaFiles.setAction(function upload(conduit) {
215
215
  var MediaFile = this.getModel('MediaFile'),
216
216
  files = conduit.files,
217
217
  tasks = [],
218
- file;
218
+ accept = conduit.body?.accept;
219
219
 
220
220
  if (files && files.files && typeof files.files == 'object') {
221
221
  files = files.files;
222
222
  }
223
223
 
224
- // Iterate over every file
225
- Object.each(files, function eachFile(file, key) {
224
+ for (let key in files) {
225
+ let file = files[key];
226
226
 
227
- tasks[tasks.length] = function storeFile(next) {
227
+ tasks.push(async next => {
228
228
  let options = {
229
229
  move: true,
230
230
  filename: file.name
@@ -242,9 +242,20 @@ MediaFiles.setAction(function upload(conduit) {
242
242
 
243
243
  options.name = name;
244
244
 
245
+ // @TODO: don't trust the browser to dictate the type
246
+ if (accept) {
247
+ let file_type = await file.getMimetype();
248
+
249
+ if (file_type === accept || accept.includes(file_type)) {
250
+ // OK!
251
+ } else {
252
+ return next(new Error('File upload failed: Expected filetype "' + accept + '", but got "' + file_type + '"'));
253
+ }
254
+ }
255
+
245
256
  MediaFile.addFile(file.path, options, next);
246
- };
247
- });
257
+ });
258
+ }
248
259
 
249
260
  // Store every file
250
261
  Function.parallel(tasks, function storedFiles(err, result) {
@@ -382,4 +393,74 @@ MediaFiles.setAction(function info(conduit) {
382
393
  conduit.end(size);
383
394
  });
384
395
  });
396
+ });
397
+
398
+ /**
399
+ * Recordsource action
400
+ *
401
+ * @author Jelle De Loecker <jelle@elevenways.be>
402
+ * @since 0.7.0
403
+ * @version 0.7.0
404
+ *
405
+ * @param {Conduit} conduit
406
+ */
407
+ MediaFiles.setAction(async function recordsource(conduit) {
408
+
409
+ const model = this.model;
410
+
411
+ let crit = model.find();
412
+
413
+ model.disableTranslations();
414
+
415
+ let page_size = conduit.param('page_size'),
416
+ filters = conduit.param('filters'),
417
+ fields = conduit.param('fields'),
418
+ page = conduit.param('page'),
419
+ sort = conduit.param('sort');
420
+
421
+ if (fields) {
422
+
423
+ // @TODO: fix FieldSet being sent with regular json?
424
+ if (fields.fields) {
425
+ fields = fields.fields;
426
+ }
427
+
428
+ crit.select(fields);
429
+ }
430
+
431
+ if (page_size && !page) {
432
+ page = 1;
433
+ }
434
+
435
+ if (page) {
436
+ crit.page(page, page_size);
437
+ }
438
+
439
+ if (sort?.field && sort?.dir) {
440
+ crit.sort([sort.field, sort.dir]);
441
+ }
442
+
443
+ if (filters) {
444
+ let key,
445
+ val;
446
+
447
+ for (key in filters) {
448
+ val = filters[key];
449
+
450
+ // The value should always be a string,
451
+ // so anything that is falsy can be ignored
452
+ if (!val) {
453
+ continue;
454
+ }
455
+
456
+ val = RegExp.interpretWildcard('*' + val + '*', 'i');
457
+ crit.where(key).equals(val);
458
+ }
459
+ }
460
+
461
+ let records = await model.find('all', crit);
462
+
463
+ conduit.end({
464
+ records : records
465
+ });
385
466
  });
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The base class for all other media elements
3
+ *
4
+ * @author Jelle De Loecker <jelle@elevenways.be>
5
+ * @since 0.7.0
6
+ * @version 0.7.0
7
+ */
8
+ const Base = Function.inherits('Alchemy.Element', 'Alchemy.Element.Media', 'Base');
9
+
10
+ /**
11
+ * The stylesheet to load for this element
12
+ *
13
+ * @author Jelle De Loecker <jelle@elevenways.be>
14
+ * @since 0.7.0
15
+ * @version 0.7.0
16
+ */
17
+ Base.setStylesheetFile('alchemy_media');
@@ -3,9 +3,9 @@
3
3
  *
4
4
  * @author Jelle De Loecker <jelle@elevenways.be>
5
5
  * @since 0.6.0
6
- * @version 0.6.0
6
+ * @version 0.7.0
7
7
  */
8
- const AlFile = Function.inherits('Alchemy.Element.App', 'AlFile');
8
+ const AlFile = Function.inherits('Alchemy.Element.Media.Base', 'AlFile');
9
9
 
10
10
  /**
11
11
  * The template code
@@ -16,15 +16,6 @@ const AlFile = Function.inherits('Alchemy.Element.App', 'AlFile');
16
16
  */
17
17
  AlFile.setTemplateFile('element/al_file');
18
18
 
19
- /**
20
- * The stylesheet to load for this element
21
- *
22
- * @author Jelle De Loecker <jelle@elevenways.be>
23
- * @since 0.6.0
24
- * @version 0.6.0
25
- */
26
- AlFile.setStylesheetFile('element/alchemy_file');
27
-
28
19
  /**
29
20
  * Getter for the drop target
30
21
  *
@@ -61,6 +52,15 @@ AlFile.addElementGetter('preview_element', '.al-file-preview');
61
52
  */
62
53
  AlFile.addElementGetter('remove_button', '.al-file-remove');
63
54
 
55
+ /**
56
+ * Getter for the select button
57
+ *
58
+ * @author Jelle De Loecker <jelle@elevenways.be>
59
+ * @since 0.7.0
60
+ * @version 0.7.0
61
+ */
62
+ AlFile.addElementGetter('select_button', '.al-file-select');
63
+
64
64
  /**
65
65
  * Getter for the uploading-icon
66
66
  *
@@ -79,6 +79,15 @@ AlFile.addElementGetter('icon_uploading', '.uploading-icon');
79
79
  */
80
80
  AlFile.addElementGetter('icon_empty', '.empty-icon');
81
81
 
82
+ /**
83
+ * Set the accepted types
84
+ *
85
+ * @author Jelle De Loecker <jelle@elevenways.be>
86
+ * @since 0.7.1
87
+ * @version 0.7.1
88
+ */
89
+ AlFile.setAttribute('accept');
90
+
82
91
  /**
83
92
  * Set the value
84
93
  *
@@ -135,7 +144,7 @@ AlFile.setMethod(function updatePreview(value) {
135
144
  *
136
145
  * @author Jelle De Loecker <jelle@elevenways.be>
137
146
  * @since 0.6.0
138
- * @version 0.6.2
147
+ * @version 0.7.1
139
148
  */
140
149
  AlFile.setMethod(async function uploadFile(config) {
141
150
 
@@ -159,6 +168,10 @@ AlFile.setMethod(async function uploadFile(config) {
159
168
  form_data.append('format', format);
160
169
  }
161
170
 
171
+ if (this.accept) {
172
+ form_data.append('accept', this.accept);
173
+ }
174
+
162
175
  let response;
163
176
 
164
177
  try {
@@ -167,7 +180,15 @@ AlFile.setMethod(async function uploadFile(config) {
167
180
  post : form_data,
168
181
  });
169
182
  } catch (err) {
170
- console.error('Failed to upload file:', err);
183
+
184
+ let alchemy_field = this.queryUp('al-field');
185
+
186
+ if (alchemy_field) {
187
+ alchemy_field.showError(err);
188
+ } else {
189
+ console.error('Failed to upload file:', err);
190
+ alert('Failed to upload file: ' + err);
191
+ }
171
192
  }
172
193
 
173
194
  this.classList.remove('uploading');
@@ -245,6 +266,11 @@ AlFile.setMethod(function introduced() {
245
266
  this.value = null;
246
267
  });
247
268
 
269
+ if (this.select_button) {
270
+ this.select_button.addEventListener('click', e => {
271
+ this.showExistingFileSelection();
272
+ });
273
+ }
248
274
  });
249
275
 
250
276
  /**
@@ -265,11 +291,60 @@ AlFile.setMethod(function _getContent(callback) {
265
291
  return callback(err);
266
292
  }
267
293
 
268
- console.log('Size:', info);
269
-
270
294
  that.innerHTML = '<img class="final" src="/media/static/' + src + '?width=50%25" width=' + info.width + ' height=' + info.height +' style="width:400px">'
271
295
  + '<img class="placeholder" src="/media/static/' + src + '?width=20px">';
272
296
 
273
297
  callback(null);
274
298
  });
299
+ });
300
+
301
+ /**
302
+ * Select existing files
303
+ *
304
+ * @author Jelle De Loecker <jelle@elevenways.be>
305
+ * @since 0.7.0
306
+ * @version 0.7.1
307
+ */
308
+ AlFile.setMethod(async function showExistingFileSelection() {
309
+
310
+ let variables = {};
311
+
312
+ if (this.accept) {
313
+ let filters = {
314
+ type : this.accept,
315
+ };
316
+
317
+ variables.filters = filters;
318
+ }
319
+
320
+ await hawkejs.scene.render('element/al_file_selection', variables);
321
+
322
+ let dialog_contents = document.querySelector('he-dialog [data-he-template="element/al_file_selection"]');
323
+
324
+ if (!dialog_contents) {
325
+ return;
326
+ }
327
+
328
+ let dialog = dialog_contents.queryParents('he-dialog'),
329
+ button = dialog_contents.querySelector('.btn-apply');
330
+
331
+ dialog_contents.classList.add('default-form-editor');
332
+ hawkejs.scene.enableStyle('chimera/chimera');
333
+
334
+ button.addEventListener('click', e => {
335
+ e.preventDefault();
336
+
337
+ let table = dialog.querySelector('al-table');
338
+
339
+ if (table) {
340
+ let row = table.active_row;
341
+
342
+ if (row && row.dataset.pk) {
343
+ this.value = row.dataset.pk;
344
+ }
345
+ }
346
+
347
+ dialog.remove();
348
+ });
349
+
275
350
  });
@@ -1,20 +1,13 @@
1
- /**
2
- * The al-ico element
3
- *
4
- * @author Jelle De Loecker <jelle@elevenways.be>
5
- * @since 0.6.0
6
- * @version 0.6.0
7
- */
8
- const Icon = Function.inherits('Alchemy.Element', 'AlIco');
1
+ const BUSY = Symbol('busy');
9
2
 
10
3
  /**
11
- * The stylesheet to load for this element
4
+ * The al-icon element
12
5
  *
13
- * @author Jelle De Loecker <jelle@elevenways.be>
6
+ * @author Jelle De Loecker <jelle@elevenways.be>
14
7
  * @since 0.6.0
15
- * @version 0.6.0
8
+ * @version 0.7.0
16
9
  */
17
- Icon.setStylesheetFile('alchemy_icons');
10
+ const Icon = Function.inherits('Alchemy.Element.Media.Base', 'AlIcon');
18
11
 
19
12
  /**
20
13
  * The source to use
@@ -45,15 +38,44 @@ Icon.setAttribute('icon-style');
45
38
  */
46
39
  Icon.setAttribute('icon-name');
47
40
 
41
+ /**
42
+ * Extra options/flags
43
+ *
44
+ * @author Jelle De Loecker <jelle@elevenways.be>
45
+ * @since 0.7.0
46
+ * @version 0.7.0
47
+ */
48
+ Icon.setAttribute('icon-flags');
49
+
50
+ /**
51
+ * Set the default role
52
+ *
53
+ * @author Jelle De Loecker <jelle@elevenways.be>
54
+ * @since 0.7.1
55
+ * @version 0.7.1
56
+ */
57
+ Icon.setRole('graphics-symbol');
58
+
59
+ /**
60
+ * Refresh the icon when these attributes change
61
+ *
62
+ * @author Jelle De Loecker <jelle@elevenways.be>
63
+ * @since 0.7.0
64
+ * @version 0.7.0
65
+ */
66
+ Icon.addObservedAttribute(['icon-style', 'icon-name', 'icon-flags'], function onChange() {
67
+ this.refresh();
68
+ });
69
+
48
70
  /**
49
71
  * The element is being retained
50
72
  *
51
73
  * @author Jelle De Loecker <jelle@elevenways.be>
52
74
  * @since 0.6.3
53
- * @version 0.6.3
75
+ * @version 0.7.0
54
76
  */
55
77
  Icon.setMethod(function retained() {
56
- this.setCssClasses();
78
+ this.refresh(true);
57
79
  });
58
80
 
59
81
  /**
@@ -61,10 +83,44 @@ Icon.setMethod(function retained() {
61
83
  *
62
84
  * @author Jelle De Loecker <jelle@elevenways.be>
63
85
  * @since 0.6.3
64
- * @version 0.6.3
86
+ * @version 0.7.0
65
87
  */
66
88
  Icon.setMethod(function introduced() {
89
+ this.refresh();
90
+ });
91
+
92
+ /**
93
+ * Refresh the icon
94
+ *
95
+ * @author Jelle De Loecker <jelle@elevenways.be>
96
+ * @since 0.7.0
97
+ * @version 0.7.1
98
+ *
99
+ * @param {Boolean} force
100
+ */
101
+ Icon.setMethod(function refresh(force) {
102
+
103
+ if (this[BUSY] && !force) {
104
+ return;
105
+ }
106
+
107
+ if (this.role == 'graphics-symbol') {
108
+ this.role = 'graphics-symbol img';
109
+
110
+ if (!this.hasAttribute('aria-label')) {
111
+ this.setAttribute('aria-label', '');
112
+ }
113
+ }
114
+
115
+ if (!force && Blast.isNode) {
116
+ return;
117
+ }
118
+
119
+ this[BUSY] = true;
120
+
67
121
  this.setCssClasses();
122
+
123
+ this[BUSY] = false;
68
124
  });
69
125
 
70
126
  /**
@@ -80,6 +136,8 @@ Icon.setMethod(function setIcon(info) {
80
136
  return;
81
137
  }
82
138
 
139
+ this[BUSY] = true;
140
+
83
141
  if (typeof info == 'string') {
84
142
  info = info.split(' ');
85
143
 
@@ -105,6 +163,8 @@ Icon.setMethod(function setIcon(info) {
105
163
  if (!this.icon_style) {
106
164
  this.icon_style = 'duotone';
107
165
  }
166
+
167
+ this.refresh(true);
108
168
  });
109
169
 
110
170
  /**
@@ -143,4 +203,10 @@ Icon.setMethod(function setCssClasses() {
143
203
 
144
204
  this.classList.add('fa-' + (this.icon_style || 'solid'));
145
205
  this.classList.add('fa-' + this.icon_name);
206
+
207
+ let flags = (this.icon_flags || '').split(/\s+/);
208
+
209
+ for (let flag of flags) {
210
+ this.classList.add('fa-' + flag);
211
+ }
146
212
  });
@@ -3,11 +3,9 @@
3
3
  *
4
4
  * @author Jelle De Loecker <jelle@develry.be>
5
5
  * @since 0.4.1
6
- * @version 0.4.1
6
+ * @version 0.7.0
7
7
  */
8
- var AlImage = Function.inherits('Alchemy.Element.App', function AlImage() {
9
- return AlImage.super.call(this);
10
- });
8
+ const AlImage = Function.inherits('Alchemy.Element.Media.Base', 'AlImage');
11
9
 
12
10
  /**
13
11
  * CSS
@@ -35,7 +33,7 @@ AlImage.setMethod(function introduced() {
35
33
 
36
34
  var placeholder = this.querySelector('.placeholder'),
37
35
  final = this.querySelector('.final');
38
- console.log('Waiting for onload');
36
+
39
37
  final.onload = function() {
40
38
  console.log('Final is loaded!');
41
39
  final.style.opacity = 1;
package/element/al_svg.js CHANGED
@@ -3,18 +3,9 @@
3
3
  *
4
4
  * @author Jelle De Loecker <jelle@elevenways.be>
5
5
  * @since 0.5.1
6
- * @version 0.5.1
6
+ * @version 0.7.0
7
7
  */
8
- const Svg = Function.inherits('Alchemy.Element', 'AlSvg');
9
-
10
- /**
11
- * The stylesheet to load for this element
12
- *
13
- * @author Jelle De Loecker <jelle@elevenways.be>
14
- * @since 0.5.1
15
- * @version 0.5.1
16
- */
17
- Svg.setStylesheetFile('alchemy_svg');
8
+ const Svg = Function.inherits('Alchemy.Element.Media.Base', 'AlSvg');
18
9
 
19
10
  /**
20
11
  * The location of the svg
@@ -30,7 +21,7 @@ Svg.setAttribute('src');
30
21
  *
31
22
  * @author Jelle De Loecker <jelle@elevenways.be>
32
23
  * @since 0.5.1
33
- * @version 0.5.1
24
+ * @version 0.7.1
34
25
  */
35
26
  Svg.setMethod(async function injectSvg() {
36
27
 
@@ -59,6 +50,29 @@ Svg.setMethod(async function injectSvg() {
59
50
 
60
51
  this.innerHTML = contents;
61
52
 
53
+ if (this.role) {
54
+
55
+ // Do not allow `graphics-symbol` role, because it is not widely supported
56
+ // and google doesn't know it either
57
+ if (this.role == 'graphics-symbol') {
58
+ this.role = 'img';
59
+
60
+ if (!this.hasAttribute('aria-label')) {
61
+ this.setAttribute('aria-label', '');
62
+ }
63
+ }
64
+
65
+ let svg = this.querySelector('svg');
66
+
67
+ if (svg) {
68
+ svg.setAttribute('role', this.role);
69
+
70
+ if (this.hasAttribute('aria-label')) {
71
+ svg.setAttribute('aria-label', this.getAttribute('aria-label'));
72
+ }
73
+ }
74
+ }
75
+
62
76
  if (this._resolve_me_too) {
63
77
  this._resolve_me_too.resolve();
64
78
  }
@@ -115,7 +115,7 @@ Media.setMethod(function loadIconFont() {
115
115
  *
116
116
  * @author Jelle De Loecker <jelle@develry.be>
117
117
  * @since 0.5.0
118
- * @version 0.6.2
118
+ * @version 0.7.1
119
119
  *
120
120
  * @param {Element} element The element to apply to
121
121
  * @param {String} image The image identifier
@@ -160,6 +160,12 @@ Media.setMethod(function applyDirective(element, image, options) {
160
160
  options.height = height;
161
161
  }
162
162
 
163
+ if (options.lazy_load) {
164
+ if (!element.hasAttribute('loading')) {
165
+ element.setAttribute('loading', 'lazy');
166
+ }
167
+ }
168
+
163
169
  let url = this.imageUrl(image, options),
164
170
  clone = url.clone();
165
171
 
@@ -16,12 +16,25 @@ const Image = Function.inherits('Alchemy.Widget', 'Image');
16
16
  *
17
17
  * @author Jelle De Loecker <jelle@elevenways.be>
18
18
  * @since 0.6.0
19
- * @version 0.6.0
19
+ * @version 0.7.1
20
20
  */
21
21
  Image.constitute(function prepareSchema() {
22
22
 
23
23
  this.schema.addField('image', 'File', {
24
+ description : 'Upload/select the image to display',
25
+ widget_config_editable: true,
26
+ });
27
+
28
+ this.schema.addField('width_hint', 'Number', {
29
+ description : 'Approximate maximum width of the image in pixels or as a percentage of the total page width.',
30
+ widget_config_editable: true,
31
+ default: '25%',
32
+ });
33
+
34
+ this.schema.addField('lazy_load', 'Boolean', {
35
+ description : 'Determines whether the image should be loaded lazily or not',
24
36
  widget_config_editable: true,
37
+ default: true,
25
38
  });
26
39
  });
27
40
 
@@ -30,7 +43,7 @@ Image.constitute(function prepareSchema() {
30
43
  *
31
44
  * @author Jelle De Loecker <jelle@elevenways.be>
32
45
  * @since 0.6.0
33
- * @version 0.6.4
46
+ * @version 0.7.1
34
47
  *
35
48
  * @param {HTMLElement} widget
36
49
  */
@@ -38,7 +51,18 @@ Image.setMethod(function populateWidget() {
38
51
 
39
52
  let img = this.createElement('img');
40
53
 
41
- this.hawkejs_renderer.helpers.Media.applyDirective(img, this.config.image);
54
+ let width_hint = this.config.width_hint,
55
+ lazy_load = this.config.lazy_load;
56
+
57
+ if (width_hint === null || width_hint === '' || typeof width_hint == 'undefined') {
58
+ width_hint = '25%';
59
+ }
60
+
61
+ if (lazy_load == null) {
62
+ lazy_load = true;
63
+ }
64
+
65
+ this.hawkejs_renderer.helpers.Media.applyDirective(img, this.config.image, {width: width_hint, lazy_load});
42
66
 
43
67
  this.widget.append(img);
44
68
 
@@ -16,6 +16,48 @@ var FileField = Function.inherits('Alchemy.Field.ObjectId', 'File');
16
16
  */
17
17
  FileField.setProperty('deferCast', true);
18
18
 
19
+ /**
20
+ * Get the accepted types
21
+ *
22
+ * @author Jelle De Loecker <jelle@elevenways.be>
23
+ * @since 0.7.1
24
+ * @version 0.7.1
25
+ *
26
+ * @return {Array|null}
27
+ */
28
+ FileField.setMethod(function getAcceptedTypes() {
29
+
30
+ let accept = this.options?.accept;
31
+
32
+ if (!accept) {
33
+ return null;
34
+ }
35
+
36
+ accept = Array.cast(accept);
37
+
38
+ return accept;
39
+ });
40
+
41
+ /**
42
+ * Get the accepted types string, for the input element
43
+ *
44
+ * @author Jelle De Loecker <jelle@elevenways.be>
45
+ * @since 0.7.1
46
+ * @version 0.7.1
47
+ *
48
+ * @return {String|null}
49
+ */
50
+ FileField.setMethod(function getAcceptedTypesString() {
51
+
52
+ let types = this.getAcceptedTypes();
53
+
54
+ if (!types?.length) {
55
+ return null;
56
+ }
57
+
58
+ return types.join(', ');
59
+ });
60
+
19
61
  if (Blast.isBrowser) {
20
62
  return;
21
63
  }
@@ -225,20 +225,33 @@ MediaFile.setMethod(function getFile(id, callback) {
225
225
  *
226
226
  * @author Jelle De Loecker <jelle@develry.be>
227
227
  * @since 0.0.1
228
- * @version 0.2.0
228
+ * @version 0.7.1
229
229
  *
230
230
  * @param {String} file The path to the file, can be a URL
231
231
  * @param {Object} options
232
232
  * @param {Function} callback
233
+ *
234
+ * @return {Pledge}
233
235
  */
234
236
  MediaFile.setMethod(function addFile(file, options, callback) {
235
237
 
236
- var that = this;
238
+ const that = this,
239
+ pledge = new Pledge();
240
+
241
+ pledge.done(callback);
237
242
 
238
243
  this.queue.add(function(done) {
239
244
  that.getModel('MediaRaw').addFile(file, options, function(err, response) {
240
- callback(err, response);
245
+
241
246
  done();
247
+
248
+ if (err) {
249
+ pledge.reject(err);
250
+ } else {
251
+ pledge.resolve(response);
252
+ }
242
253
  });
243
254
  });
255
+
256
+ return pledge;
244
257
  });
@@ -168,7 +168,7 @@ MediaRaw.Document.setMethod(function extraImportFromStream(input) {
168
168
  *
169
169
  * @author Jelle De Loecker <jelle@develry.be>
170
170
  * @since 0.0.1
171
- * @version 0.4.2
171
+ * @version 0.7.1
172
172
  *
173
173
  * @param {String} file The path to the file, can be a URL
174
174
  * @param {Object} options
@@ -185,6 +185,14 @@ MediaRaw.setMethod(function addFile(file, options, callback) {
185
185
  options = {};
186
186
  }
187
187
 
188
+ if (!options) {
189
+ options = {};
190
+ }
191
+
192
+ if (file && typeof file == 'object' && file instanceof Classes.Alchemy.Inode.File) {
193
+ file = file.path;
194
+ }
195
+
188
196
  // If the given file is actually a url, we'll need to download it first
189
197
  if (file.startsWith('http://') || file.startsWith('https://')) {
190
198
 
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "alchemy-media",
3
3
  "description": "The media plugin for Alchemy",
4
- "version": "0.6.4",
4
+ "version": "0.7.1",
5
5
  "repository": {
6
6
  "type" : "git",
7
- "url" : "https://github.com/skerit/alchemy-media.git"
7
+ "url" : "https://github.com/11ways/alchemy-media.git"
8
8
  },
9
9
  "dependencies": {
10
10
  "@11ways/exiv2" : "~0.6.4",
@@ -1,19 +1,52 @@
1
1
  <label class="al-file-drop-target">
2
2
  <div class="al-file-preview"></div>
3
- <al-ico class="uploading-icon fa-solid fa-spinner fa-spin-pulse"></al-ico>
4
- <al-ico class="empty-icon fa-solid fa-file-arrow-up"><% if (self.value) $0.hidden = true %></al-ico>
5
- <input type="file" tabindex="-1" class="al-file-input" hidden>
3
+
4
+ <al-icon
5
+ icon-name="spinner"
6
+ icon-style="duotone"
7
+ class="uploading-icon fa-spin-pulse"
8
+ ></al-icon>
9
+
10
+ <al-icon
11
+ icon-name="cloud-arrow-up"
12
+ icon-style="duotone"
13
+ class="empty-icon"
14
+ ><% if (self.value) $0.hidden = true %></al-icon>
15
+
16
+ <input
17
+ class="al-file-input"
18
+ type="file"
19
+ tabindex="-1"
20
+ accept={% self.accept %}
21
+ hidden
22
+ >
6
23
  </label>
7
24
 
8
25
  <div class="al-file-right">
9
26
  <button class="al-file-choose-existing">
10
- <al-ico class="fa-solid fa-file-image"></al-ico>
27
+ <al-icon
28
+ icon-name="image"
29
+ icon-style="duotone"
30
+ ></al-icon>
11
31
  {%t "select-uploaded-file" %}
12
32
  </button>
13
33
 
14
34
  <button class="al-file-remove">
15
35
  <% if (!self.value) $0.hidden = true %>
16
- <al-ico class="fa-solid fa-trash-can"></al-ico>
36
+ <al-icon
37
+ icon-name="trash-can"
38
+ icon-style="duotone"
39
+ ></al-icon>
17
40
  {%t "remove-current-file" %}
18
41
  </button>
42
+
43
+ {% if Acl.hasPermission('media.recordsource') %}
44
+ <button class="al-file-select">
45
+ <al-icon
46
+ icon-name="gallery-thumbnails"
47
+ icon-style="duotone"
48
+ ></al-icon>
49
+ {%t "select-existing-file" %}
50
+ </button>
51
+ {% /if %}
19
52
  </div>
@@ -0,0 +1,36 @@
1
+ <% makeDialog() %>
2
+ <% addClass('alchemy-file-selection-dialog') %>
3
+
4
+ <%
5
+
6
+ fieldset = new Blast.Classes.Alchemy.Criteria.FieldSet('list', 'MediaFile');
7
+
8
+ fieldset.addField('_id', {
9
+ view : 'file_preview',
10
+ wrapper : 'file_preview',
11
+ title : 'Thumbnail',
12
+ filter : false,
13
+ sortable : false,
14
+ });
15
+
16
+ fieldset.addField('filename');
17
+ fieldset.addField('type');
18
+ %>
19
+
20
+ <div class="alchemy-file-selection-wrapper">
21
+
22
+ <al-table
23
+ id="al-file-selection-table"
24
+ purpose="view"
25
+ mode="inline"
26
+ page-size=20
27
+ show-filters
28
+ #filters={% filters %}
29
+ #fieldset={% fieldset %}
30
+ #recordsource={% {route: 'MediaFile#recordsource'} %}
31
+ ></al-table>
32
+
33
+ <button class="btn btn-apply">
34
+ Apply
35
+ </button>
36
+ </div>
@@ -3,6 +3,7 @@ script('chimera/mediafield');
3
3
  style('chimera/mediafield');
4
4
  %>
5
5
  <al-file
6
- value=<% value %>
7
6
  class="alchemy-field-value"
7
+ value=<% value %>
8
+ accept={% alchemy_field.config.getAcceptedTypesString() %}
8
9
  ></al-file>
@@ -1,4 +1,4 @@
1
- <% style('element/alchemy_file') %>
1
+ <% style('alchemy_media') %>
2
2
 
3
3
  <img
4
4
  !Media={% value %}
@@ -1,4 +1,4 @@
1
- <% style('element/alchemy_file') %>
1
+ <% style('alchemy_media') %>
2
2
 
3
3
  {% if value %}
4
4
  <img
@@ -1,4 +1,4 @@
1
- <% style('element/alchemy_file') %>
1
+ <% style('alchemy_media') %>
2
2
 
3
3
  {% if value %}
4
4
  <img
@@ -1,5 +1,5 @@
1
1
  <div class="form-field-info">
2
- <alchemy-label>
2
+ <al-label>
3
3
  <span
4
4
  data-he-name="field-title"
5
5
  data-he-slot="field-title"
@@ -8,6 +8,6 @@
8
8
  data-he-name="field-description"
9
9
  data-he-slot="field-description"
10
10
  ><%= alchemy_field.field_description %></small>
11
- </alchemy-label>
11
+ </al-label>
12
12
  </div>
13
13
  <div data-he-name="field"></div>
@@ -1,59 +0,0 @@
1
- @use "fontawesome6/fontawesome.scss";
2
- @use "fontawesome6/solid.scss";
3
- @use "fontawesome6/brands.scss";
4
-
5
- al-ico {
6
- @extend %fa-icon;
7
-
8
- &[type="arrow-left"] {
9
- @extend .fas, .fa-angle-left;
10
- }
11
-
12
- &[type="arrow-left-double"] {
13
- @extend .fas, .fa-angle-double-left;
14
- }
15
-
16
- &[type="arrow-right"] {
17
- @extend .fas, .fa-angle-right;
18
- }
19
-
20
- &[type="arrow-right-double"] {
21
- @extend .fas, .fa-angle-double-right;
22
- }
23
-
24
- &[type="sorting-arrow"] {
25
- @extend .fas;
26
-
27
- &.down {
28
- @extend .fa-sort-alpha-up;
29
- }
30
-
31
- &.up {
32
- @extend .fa-sort-alpha-down;
33
- }
34
- }
35
-
36
- &[type="menu"] {
37
- @extend .fas, .fa-bars;
38
- }
39
-
40
- &[type="edit"] {
41
- @extend .fas, .fa-pencil-alt;
42
- }
43
-
44
- &[type="zoom-in"] {
45
- @extend .fas, .fa-search-plus;
46
- }
47
-
48
- &[type="microphone"] {
49
- @extend .fas, .fa-microphone-alt;
50
- }
51
-
52
- &[type="video"] {
53
- @extend .fas, .fa-video;
54
- }
55
-
56
- &[type="plus"] {
57
- @extend .fas, .fa-plus;
58
- }
59
- }