alchemy-media 0.6.3 → 0.7.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 CHANGED
@@ -1,3 +1,17 @@
1
+ ## 0.7.0 (2022-11-02)
2
+
3
+ * Use the `al-` prefix for custom elements
4
+ * Rename `al-ico` to `al-icon`
5
+ * Allow selecting existing uploaded files in an `al-file` element
6
+
7
+ ## 0.6.4 (2022-10-13)
8
+
9
+ * Add the `Media#loadIconFont()` helper method
10
+ * Make the `image` route serve up filetype thumbnails for non-images
11
+ * Add file preview image to the chimera edit view of MediaFile
12
+ * Add thumbnail column to MediaFile chimera index view
13
+ * Add `prefix` parameter to the `MediaFile#data` route
14
+
1
15
  ## 0.6.3 (2022-07-23)
2
16
 
3
17
  * Fix `al-file` element showing wrong buttons on load
@@ -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;
@@ -92,4 +92,30 @@ al-file {
92
92
  button {
93
93
  min-height: 2rem;
94
94
  }
95
+ }
96
+
97
+ al-field[field-view="file_preview"] {
98
+ [data-he-name="field"] .field {
99
+ padding: 0;
100
+
101
+ img {
102
+ max-height: 25rem;
103
+ object-fit: contain;
104
+ }
105
+ }
106
+ }
107
+
108
+ al-field[field-view="file_preview"],
109
+ al-field[field-view="file"],
110
+ al-field[field-type="file"] {
111
+
112
+ &[mode="inline"] {
113
+ .field img {
114
+ object-fit: contain;
115
+ width: 100px;
116
+ height: 100px;
117
+ display: block;
118
+ margin: auto;
119
+ }
120
+ }
95
121
  }
@@ -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
@@ -43,9 +43,18 @@ alchemy.createDir(options.cache);
43
43
  Router.get('Media::static', /\/media\/static\/(.*)*/, 'MediaFile#serveStatic');
44
44
  Router.get('Media::image', options.url + '/{id}', 'MediaFile#image');
45
45
 
46
- Router.get('MediaFile#data', '/media/data/{[MediaFile._id]id}', 'MediaFile#data');
46
+ // The prefix is added at the end of the route so it does not
47
+ // change the user's active_prefix
48
+ Router.get('MediaFile#data', '/media/data/{prefix}/{id}', 'MediaFile#data');
47
49
  Router.get('MediaFile#info', '/media/info', 'MediaFile#info');
48
50
 
51
+ Router.add({
52
+ name : 'MediaFile#recordsource',
53
+ methods : ['get'],
54
+ paths : '/media/recordsource',
55
+ permission : 'media.recordsource',
56
+ });
57
+
49
58
  // Allow dummy extensions
50
59
  Router.get('Media::fileextension', '/media/file/{id}.{extension}', 'MediaFile#file');
51
60
 
@@ -13,16 +13,14 @@ var fs = require('fs'),
13
13
  * @since 0.0.1
14
14
  * @version 0.3.0
15
15
  */
16
- var MediaFiles = Function.inherits('Alchemy.Controller', function MediaFile(conduit, options) {
17
- MediaFile.super.call(this, conduit, options);
18
- });
16
+ const MediaFiles = Function.inherits('Alchemy.Controller', 'MediaFile');
19
17
 
20
18
  /**
21
19
  * Serve a thumbnail
22
20
  *
23
21
  * @author Jelle De Loecker <jelle@develry.be>
24
22
  * @since 0.0.1
25
- * @version 0.4.1
23
+ * @version 0.6.4
26
24
  *
27
25
  * @param {Conduit} conduit
28
26
  */
@@ -35,21 +33,15 @@ MediaFiles.setAction(function thumbnail(conduit, id) {
35
33
  // Get the requested file
36
34
  this.getModel('MediaFile').getFile(id, function gotFile(err, record) {
37
35
 
38
- var Type;
39
-
40
36
  if (err) {
41
37
  return conduit.notFound(err);
42
38
  }
43
39
 
44
- Type = MediaTypes[record.type];
45
-
46
- if (!Type) {
47
- Type = Classes.Alchemy.MediaType;
48
- }
40
+ const Type = MediaTypes[record.type] || Classes.Alchemy.MediaType;
49
41
 
50
42
  if (Type) {
51
- Type = new Type();
52
- Type.thumbnail(conduit, record);
43
+ let instance = new Type();
44
+ instance.thumbnail(conduit, record);
53
45
  } else {
54
46
  conduit.error('Error generating thumbnail of ' + record.type);
55
47
  }
@@ -100,7 +92,7 @@ MediaFiles.setAction(function serveStatic(conduit, path) {
100
92
  *
101
93
  * @author Jelle De Loecker <jelle@develry.be>
102
94
  * @since 0.0.1
103
- * @version 0.4.1
95
+ * @version 0.6.4
104
96
  *
105
97
  * @param {Conduit} conduit
106
98
  */
@@ -112,12 +104,22 @@ MediaFiles.setAction(function image(conduit, id) {
112
104
 
113
105
  this.getModel('MediaFile').getFile(id, function gotFile(err, record) {
114
106
 
115
- var Image = new MediaTypes.image;
107
+ if (err) {
108
+ return conduit.error(err);
109
+ }
110
+
111
+ const Image = new MediaTypes.image;
116
112
 
117
113
  if (!record) {
118
114
  return Image.placeholder(conduit, {text: 404, status: 404});
119
115
  }
120
116
 
117
+ if (record.type != 'image') {
118
+ const Type = MediaTypes[record.type] || Classes.Alchemy.MediaType;
119
+ let instance = new Type();
120
+ return instance.thumbnail(conduit, record);
121
+ }
122
+
121
123
  Image.serve(conduit, record);
122
124
  });
123
125
  });
@@ -315,14 +317,23 @@ MediaFiles.setAction(function uploadsingle(conduit) {
315
317
  /**
316
318
  * Get the file data
317
319
  *
318
- * @author Jelle De Loecker <jelle@develry.be>
320
+ * @author Jelle De Loecker <jelle@elevenways.be>
319
321
  * @since 0.5.1
320
- * @version 0.5.1
322
+ * @version 0.6.4
321
323
  *
322
- * @param {Conduit} conduit
323
- * @param {Document.MediaFile} media_file
324
+ * @param {Conduit} conduit
325
+ * @param {ObjectId} media_file_id
324
326
  */
325
- MediaFiles.setAction(function data(conduit, media_file) {
327
+ MediaFiles.setAction(async function data(conduit, prefix, media_file_id) {
328
+
329
+ conduit.prefix = prefix;
330
+
331
+ let media_file = await this.model.findById(media_file_id);
332
+
333
+ if (!media_file) {
334
+ return conduit.notFound();
335
+ }
336
+
326
337
  conduit.setHeader('cache-control', 'public, max-age=3600, must-revalidate');
327
338
 
328
339
  conduit.end({
@@ -371,4 +382,74 @@ MediaFiles.setAction(function info(conduit) {
371
382
  conduit.end(size);
372
383
  });
373
384
  });
385
+ });
386
+
387
+ /**
388
+ * Recordsource action
389
+ *
390
+ * @author Jelle De Loecker <jelle@elevenways.be>
391
+ * @since 0.7.0
392
+ * @version 0.7.0
393
+ *
394
+ * @param {Conduit} conduit
395
+ */
396
+ MediaFiles.setAction(async function recordsource(conduit) {
397
+
398
+ const model = this.model;
399
+
400
+ let crit = model.find();
401
+
402
+ model.disableTranslations();
403
+
404
+ let page_size = conduit.param('page_size'),
405
+ filters = conduit.param('filters'),
406
+ fields = conduit.param('fields'),
407
+ page = conduit.param('page'),
408
+ sort = conduit.param('sort');
409
+
410
+ if (fields) {
411
+
412
+ // @TODO: fix FieldSet being sent with regular json?
413
+ if (fields.fields) {
414
+ fields = fields.fields;
415
+ }
416
+
417
+ crit.select(fields);
418
+ }
419
+
420
+ if (page_size && !page) {
421
+ page = 1;
422
+ }
423
+
424
+ if (page) {
425
+ crit.page(page, page_size);
426
+ }
427
+
428
+ if (sort?.field && sort?.dir) {
429
+ crit.sort([sort.field, sort.dir]);
430
+ }
431
+
432
+ if (filters) {
433
+ let key,
434
+ val;
435
+
436
+ for (key in filters) {
437
+ val = filters[key];
438
+
439
+ // The value should always be a string,
440
+ // so anything that is falsy can be ignored
441
+ if (!val) {
442
+ continue;
443
+ }
444
+
445
+ val = RegExp.interpretWildcard('*' + val + '*', 'i');
446
+ crit.where(key).equals(val);
447
+ }
448
+ }
449
+
450
+ let records = await model.find('all', crit);
451
+
452
+ conduit.end({
453
+ records : records
454
+ });
374
455
  });
@@ -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
  *
@@ -245,6 +245,11 @@ AlFile.setMethod(function introduced() {
245
245
  this.value = null;
246
246
  });
247
247
 
248
+ if (this.select_button) {
249
+ this.select_button.addEventListener('click', e => {
250
+ this.showExistingFileSelection();
251
+ });
252
+ }
248
253
  });
249
254
 
250
255
  /**
@@ -265,11 +270,52 @@ AlFile.setMethod(function _getContent(callback) {
265
270
  return callback(err);
266
271
  }
267
272
 
268
- console.log('Size:', info);
269
-
270
273
  that.innerHTML = '<img class="final" src="/media/static/' + src + '?width=50%25" width=' + info.width + ' height=' + info.height +' style="width:400px">'
271
274
  + '<img class="placeholder" src="/media/static/' + src + '?width=20px">';
272
275
 
273
276
  callback(null);
274
277
  });
278
+ });
279
+
280
+ /**
281
+ * Select existing files
282
+ *
283
+ * @author Jelle De Loecker <jelle@elevenways.be>
284
+ * @since 0.7.0
285
+ * @version 0.7.0
286
+ */
287
+ AlFile.setMethod(async function showExistingFileSelection() {
288
+
289
+ let variables = {};
290
+
291
+ await hawkejs.scene.render('element/al_file_selection', variables);
292
+
293
+ let dialog_contents = document.querySelector('he-dialog [data-he-template="element/al_file_selection"]');
294
+
295
+ if (!dialog_contents) {
296
+ return;
297
+ }
298
+
299
+ let dialog = dialog_contents.queryParents('he-dialog'),
300
+ button = dialog_contents.querySelector('.btn-apply');
301
+
302
+ dialog_contents.classList.add('default-form-editor');
303
+ hawkejs.scene.enableStyle('chimera/chimera');
304
+
305
+ button.addEventListener('click', e => {
306
+ e.preventDefault();
307
+
308
+ let table = dialog.querySelector('al-table');
309
+
310
+ if (table) {
311
+ let row = table.active_row;
312
+
313
+ if (row && row.dataset.pk) {
314
+ this.value = row.dataset.pk;
315
+ }
316
+ }
317
+
318
+ dialog.remove();
319
+ });
320
+
275
321
  });
@@ -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,35 @@ 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
+ * Refresh the icon when these attributes change
52
+ *
53
+ * @author Jelle De Loecker <jelle@elevenways.be>
54
+ * @since 0.7.0
55
+ * @version 0.7.0
56
+ */
57
+ Icon.addObservedAttribute(['icon-style', 'icon-name', 'icon-flags'], function onChange() {
58
+ this.refresh();
59
+ });
60
+
48
61
  /**
49
62
  * The element is being retained
50
63
  *
51
64
  * @author Jelle De Loecker <jelle@elevenways.be>
52
65
  * @since 0.6.3
53
- * @version 0.6.3
66
+ * @version 0.7.0
54
67
  */
55
68
  Icon.setMethod(function retained() {
56
- this.setCssClasses();
69
+ this.refresh(true);
57
70
  });
58
71
 
59
72
  /**
@@ -61,10 +74,36 @@ Icon.setMethod(function retained() {
61
74
  *
62
75
  * @author Jelle De Loecker <jelle@elevenways.be>
63
76
  * @since 0.6.3
64
- * @version 0.6.3
77
+ * @version 0.7.0
65
78
  */
66
79
  Icon.setMethod(function introduced() {
80
+ this.refresh();
81
+ });
82
+
83
+ /**
84
+ * Refresh the icon
85
+ *
86
+ * @author Jelle De Loecker <jelle@elevenways.be>
87
+ * @since 0.7.0
88
+ * @version 0.7.0
89
+ *
90
+ * @param {Boolean} force
91
+ */
92
+ Icon.setMethod(function refresh(force) {
93
+
94
+ if (this[BUSY] && !force) {
95
+ return;
96
+ }
97
+
98
+ if (!force && Blast.isNode) {
99
+ return;
100
+ }
101
+
102
+ this[BUSY] = true;
103
+
67
104
  this.setCssClasses();
105
+
106
+ this[BUSY] = false;
68
107
  });
69
108
 
70
109
  /**
@@ -80,6 +119,8 @@ Icon.setMethod(function setIcon(info) {
80
119
  return;
81
120
  }
82
121
 
122
+ this[BUSY] = true;
123
+
83
124
  if (typeof info == 'string') {
84
125
  info = info.split(' ');
85
126
 
@@ -105,6 +146,8 @@ Icon.setMethod(function setIcon(info) {
105
146
  if (!this.icon_style) {
106
147
  this.icon_style = 'duotone';
107
148
  }
149
+
150
+ this.refresh(true);
108
151
  });
109
152
 
110
153
  /**
@@ -112,7 +155,7 @@ Icon.setMethod(function setIcon(info) {
112
155
  *
113
156
  * @author Jelle De Loecker <jelle@elevenways.be>
114
157
  * @since 0.6.3
115
- * @version 0.6.3
158
+ * @version 0.6.4
116
159
  */
117
160
  Icon.setMethod(function setCssClasses() {
118
161
 
@@ -120,14 +163,13 @@ Icon.setMethod(function setCssClasses() {
120
163
  return;
121
164
  }
122
165
 
166
+ // Load the appropriate font style
167
+ this.hawkejs_renderer.helpers.Media.loadIconFont();
168
+
123
169
  let fa_pro = this.hawkejs_renderer.expose('fontawesome_pro'),
124
170
  style = this.icon_style || 'regular';
125
171
 
126
- if (fa_pro) {
127
- this.hawkejs_renderer.style(fa_pro);
128
- } else {
129
- this.hawkejs_renderer.style('alchemy_icons_fafree');
130
-
172
+ if (!fa_pro) {
131
173
  if (style == 'duotone' || style == 'light' || style == 'thin' || style == 'regular') {
132
174
  style = 'solid';
133
175
  }
@@ -144,4 +186,10 @@ Icon.setMethod(function setCssClasses() {
144
186
 
145
187
  this.classList.add('fa-' + (this.icon_style || 'solid'));
146
188
  this.classList.add('fa-' + this.icon_name);
189
+
190
+ let flags = (this.icon_flags || '').split(/\s+/);
191
+
192
+ for (let flag of flags) {
193
+ this.classList.add('fa-' + flag);
194
+ }
147
195
  });
@@ -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
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
6
+ * @version 0.7.0
16
7
  */
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
@@ -88,6 +88,28 @@ Media.setStatic(function loadImagesBasedOnSize() {
88
88
  }
89
89
  });
90
90
 
91
+ /**
92
+ * Load the icon font
93
+ *
94
+ * @author Jelle De Loecker <jelle@elevenways.be>
95
+ * @since 0.6.4
96
+ * @version 0.6.4
97
+ *
98
+ * @return {string}
99
+ */
100
+ Media.setMethod(function loadIconFont() {
101
+
102
+ let font_style = this.hawkejs_renderer.expose('fontawesome_pro');
103
+
104
+ if (!font_style) {
105
+ font_style = 'alchemy_icons_fafree';
106
+ }
107
+
108
+ this.hawkejs_renderer.style(font_style);
109
+
110
+ return font_style;
111
+ });
112
+
91
113
  /**
92
114
  * Apply directive to an element
93
115
  *
@@ -173,7 +195,8 @@ Media.setMethod(function applyDirective(element, image, options) {
173
195
  this.view.helpers.Alchemy.getResource({
174
196
  name: 'MediaFile#data',
175
197
  params: {
176
- id: image
198
+ id : image,
199
+ prefix : this.view.expose('active_prefix') || '__',
177
200
  }
178
201
  }, function gotResult(err, data) {
179
202
 
@@ -4,8 +4,8 @@
4
4
  * @constructor
5
5
  *
6
6
  * @author Jelle De Loecker <jelle@elevenways.be>
7
- * @since 0.1.0
8
- * @version 0.1.0
7
+ * @since 0.6.0
8
+ * @version 0.6.0
9
9
  *
10
10
  * @param {Object} data
11
11
  */
@@ -15,8 +15,8 @@ const Image = Function.inherits('Alchemy.Widget', 'Image');
15
15
  * Prepare the schema
16
16
  *
17
17
  * @author Jelle De Loecker <jelle@elevenways.be>
18
- * @since 0.1.0
19
- * @version 0.1.0
18
+ * @since 0.6.0
19
+ * @version 0.6.0
20
20
  */
21
21
  Image.constitute(function prepareSchema() {
22
22
 
@@ -29,8 +29,8 @@ Image.constitute(function prepareSchema() {
29
29
  * Populate the widget
30
30
  *
31
31
  * @author Jelle De Loecker <jelle@elevenways.be>
32
- * @since 0.1.0
33
- * @version 0.1.0
32
+ * @since 0.6.0
33
+ * @version 0.6.4
34
34
  *
35
35
  * @param {HTMLElement} widget
36
36
  */
@@ -40,7 +40,7 @@ Image.setMethod(function populateWidget() {
40
40
 
41
41
  this.hawkejs_renderer.helpers.Media.applyDirective(img, this.config.image);
42
42
 
43
- populateWidget.super.call(this);
44
-
45
43
  this.widget.append(img);
44
+
45
+ return populateWidget.super.call(this);
46
46
  });
@@ -14,10 +14,10 @@ var exiv2 = alchemy.use('@11ways/exiv2'),
14
14
  * @author Jelle De Loecker <jelle@develry.be>
15
15
  * @since 0.0.1
16
16
  * @version 0.2.0
17
+ *
18
+ * @param {Object} options
17
19
  */
18
- var ImageMedia = Function.inherits('Alchemy.MediaType', function ImageMediaType(options) {
19
- ImageMediaType.super.call(this, options);
20
- });
20
+ const ImageMedia = Function.inherits('Alchemy.MediaType', 'ImageMediaType');
21
21
 
22
22
  ImageMedia.setProperty('exivPath', alchemy.plugins.media.exiv2);
23
23
  ImageMedia.setProperty('hashType', alchemy.plugins.media.hash);
@@ -19,32 +19,48 @@ var MediaFile = Function.inherits('Alchemy.Model', function MediaFile(options) {
19
19
 
20
20
  MediaFile.setProperty('types', alchemy.shared('Media.types'));
21
21
 
22
+ /**
23
+ * The default sort options
24
+ *
25
+ * @type {Object}
26
+ */
27
+ MediaFile.prepareProperty('sort', function sort() {
28
+ return {created: -1};
29
+ });
30
+
22
31
  /**
23
32
  * Constitute the class wide schema
24
33
  *
25
- * @author Jelle De Loecker <jelle@develry.be>
34
+ * @author Jelle De Loecker <jelle@elevenways.be>
26
35
  * @since 0.2.0
27
- * @version 0.5.1
36
+ * @version 0.6.4
28
37
  */
29
38
  MediaFile.constitute(function addFields() {
30
39
 
31
- this.addField('name', 'String');
32
- this.addField('filename', 'String');
40
+ this.addField('name', 'String', {
41
+ description: 'The name of the file',
42
+ });
43
+
44
+ this.addField('filename', 'String', {
45
+ description : 'The actual filename',
46
+ });
33
47
 
34
48
  this.addField('type', 'Enum', {
35
- values: alchemy.getClassGroup('media_type')
49
+ values: alchemy.getClassGroup('media_type'),
50
+ description: 'The type of file',
36
51
  });
37
52
 
38
53
  this.addField('extra', 'Object');
39
54
 
40
- let options = {};
41
-
42
- if (alchemy.plugins.media.translatable) {
43
- options.translatable = true;
44
- }
55
+ this.addField('title', 'String', {
56
+ translatable : alchemy.plugins.media.translatable,
57
+ description : 'The title of the file (will be used in the title attribute)',
58
+ });
45
59
 
46
- this.addField('title', 'String', options);
47
- this.addField('alt', 'String', options);
60
+ this.addField('alt', 'String', {
61
+ translatable : alchemy.plugins.media.translatable,
62
+ description : 'The alternative information of the file (will be used in the alt attribute)',
63
+ });
48
64
 
49
65
  this.belongsTo('MediaRaw');
50
66
  });
@@ -54,7 +70,7 @@ MediaFile.constitute(function addFields() {
54
70
  *
55
71
  * @author Jelle De Loecker <jelle@develry.be>
56
72
  * @since 0.2.0
57
- * @version 0.5.1
73
+ * @version 0.6.4
58
74
  */
59
75
  MediaFile.constitute(function chimeraConfig() {
60
76
 
@@ -69,6 +85,15 @@ MediaFile.constitute(function chimeraConfig() {
69
85
  // Get the list group
70
86
  list = this.chimera.getActionFields('list');
71
87
 
88
+ list.addField('_id', {
89
+ view : 'file_preview',
90
+ wrapper : 'file_preview',
91
+ title : 'Thumbnail',
92
+ filter : false,
93
+ sortable : false,
94
+ });
95
+
96
+ list.addField('created');
72
97
  list.addField('name');
73
98
  list.addField('filename');
74
99
  list.addField('type');
@@ -78,6 +103,12 @@ MediaFile.constitute(function chimeraConfig() {
78
103
  // Get the edit group
79
104
  edit = this.chimera.getActionFields('edit');
80
105
 
106
+ edit.addField('_id', {
107
+ view : 'file_preview',
108
+ wrapper : 'file_preview',
109
+ title : 'Preview',
110
+ });
111
+
81
112
  edit.addField('name');
82
113
  edit.addField('filename');
83
114
  edit.addField('type');
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.3",
4
+ "version": "0.7.0",
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,46 @@
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>
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
+
5
16
  <input type="file" tabindex="-1" class="al-file-input" hidden>
6
17
  </label>
7
18
 
8
19
  <div class="al-file-right">
9
20
  <button class="al-file-choose-existing">
10
- <al-ico class="fa-solid fa-file-image"></al-ico>
21
+ <al-icon
22
+ icon-name="image"
23
+ icon-style="duotone"
24
+ ></al-icon>
11
25
  {%t "select-uploaded-file" %}
12
26
  </button>
13
27
 
14
28
  <button class="al-file-remove">
15
29
  <% if (!self.value) $0.hidden = true %>
16
- <al-ico class="fa-solid fa-trash-can"></al-ico>
30
+ <al-icon
31
+ icon-name="trash-can"
32
+ icon-style="duotone"
33
+ ></al-icon>
17
34
  {%t "remove-current-file" %}
18
35
  </button>
36
+
37
+ {% if Acl.hasPermission('media.recordsource') %}
38
+ <button class="al-file-select">
39
+ <al-icon
40
+ icon-name="gallery-thumbnails"
41
+ icon-style="duotone"
42
+ ></al-icon>
43
+ {%t "select-existing-file" %}
44
+ </button>
45
+ {% /if %}
19
46
  </div>
@@ -0,0 +1,35 @@
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
+ #fieldset={% fieldset %}
29
+ #recordsource={% {route: 'MediaFile#recordsource'} %}
30
+ ></al-table>
31
+
32
+ <button class="btn btn-apply">
33
+ Apply
34
+ </button>
35
+ </div>
@@ -0,0 +1,5 @@
1
+ <% style('alchemy_media') %>
2
+
3
+ <img
4
+ !Media={% value %}
5
+ >
@@ -0,0 +1,8 @@
1
+ <% style('alchemy_media') %>
2
+
3
+ {% if value %}
4
+ <img
5
+ !Media={% value %}
6
+ +media-route="Media::thumb"
7
+ >
8
+ {% /if %}
@@ -0,0 +1,8 @@
1
+ <% style('alchemy_media') %>
2
+
3
+ {% if value %}
4
+ <img
5
+ !Media={% value %}
6
+ +media-route="Media::thumb"
7
+ >
8
+ {% /if %}
@@ -0,0 +1,13 @@
1
+ <div class="form-field-info">
2
+ <al-label>
3
+ <span
4
+ data-he-name="field-title"
5
+ data-he-slot="field-title"
6
+ ><%= alchemy_field.field_title %></span>
7
+ <small
8
+ data-he-name="field-description"
9
+ data-he-slot="field-description"
10
+ ><%= alchemy_field.field_description %></small>
11
+ </al-label>
12
+ </div>
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
- }