alchemy-media 0.6.4 → 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,9 @@
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
+
1
7
  ## 0.6.4 (2022-10-13)
2
8
 
3
9
  * Add the `Media#loadIconFont()` helper method
@@ -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
 
@@ -382,4 +382,74 @@ MediaFiles.setAction(function info(conduit) {
382
382
  conduit.end(size);
383
383
  });
384
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
+ });
385
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
  /**
@@ -143,4 +186,10 @@ Icon.setMethod(function setCssClasses() {
143
186
 
144
187
  this.classList.add('fa-' + (this.icon_style || 'solid'));
145
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
+ }
146
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
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.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>
@@ -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
- }