From 09f756bc474d023dd439f4e98a5c9e5035d31ed4 Mon Sep 17 00:00:00 2001 From: Stephen Cameron Date: Wed, 10 Nov 2021 18:20:44 +0100 Subject: [PATCH] WIP #4821 @12 --- package-lock.json | 123 ++++++++++++++++++ package.json | 1 + resources/js/alpine.js | 13 ++ resources/js/bootstrap.js | 10 +- resources/js/media-library.js | 74 +++++++++++ resources/views/components/duration.blade.php | 10 ++ resources/views/front/media-library.blade.php | 95 +++++++++++--- resources/views/layouts/app.blade.php | 2 + webpack.mix.js | 15 +-- 9 files changed, 304 insertions(+), 39 deletions(-) create mode 100644 resources/js/alpine.js create mode 100644 resources/js/media-library.js create mode 100644 resources/views/components/duration.blade.php diff --git a/package-lock.json b/package-lock.json index 2f3f4de..02f6688 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "alpinejs": "^3.4.2", "autoprefixer": "^10.3.7", "axios": "^0.21", + "isotope-layout": "^3.0.6", "laravel-mix": "^6.0.6", "lodash": "^4.17.19", "postcss": "^8.3.10", @@ -3774,6 +3775,12 @@ "minimalistic-assert": "^1.0.0" } }, + "node_modules/desandro-matches-selector": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/desandro-matches-selector/-/desandro-matches-selector-2.0.2.tgz", + "integrity": "sha1-cXvu1NwT59jzdi9wem1YpndCGOE=", + "dev": true + }, "node_modules/destroy": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", @@ -4150,6 +4157,12 @@ "node": ">= 0.6" } }, + "node_modules/ev-emitter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ev-emitter/-/ev-emitter-1.1.1.tgz", + "integrity": "sha512-ipiDYhdQSCZ4hSbX4rMW+XzNKMD1prg/sTvoVmSLkuQ1MVlwjJQQA+sW8tMYR3BLUr9KjodFV4pvzunvRhd33Q==", + "dev": true + }, "node_modules/eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", @@ -4466,6 +4479,15 @@ "node": ">=8" } }, + "node_modules/fizzy-ui-utils": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/fizzy-ui-utils/-/fizzy-ui-utils-2.0.7.tgz", + "integrity": "sha512-CZXDVXQ1If3/r8s0T+v+qVeMshhfcuq0rqIFgJnrtd+Bu8GmDmqMjntjUePypVtjHXKJ6V4sw9zeyox34n9aCg==", + "dev": true, + "dependencies": { + "desandro-matches-selector": "^2.0.0" + } + }, "node_modules/follow-redirects": { "version": "1.14.4", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.4.tgz", @@ -4595,6 +4617,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-size": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/get-size/-/get-size-2.0.3.tgz", + "integrity": "sha512-lXNzT/h/dTjTxRbm9BXb+SGxxzkm97h/PCIKtlN/CBCxxmkkIVV21udumMS93MuVTDX583gqc94v3RjuHmI+2Q==", + "dev": true + }, "node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -5612,6 +5640,19 @@ "node": ">=0.10.0" } }, + "node_modules/isotope-layout": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/isotope-layout/-/isotope-layout-3.0.6.tgz", + "integrity": "sha512-z2ZKablhocXhoNyWwzJPFd7u7FWbYbVJA51Nvsqsod8jH2ExGc1SwDsSWKE54e3PhXzqf2yZPhFSq/c2MR1arw==", + "dev": true, + "dependencies": { + "desandro-matches-selector": "^2.0.0", + "fizzy-ui-utils": "^2.0.4", + "get-size": "^2.0.0", + "masonry-layout": "^4.1.0", + "outlayer": "^2.1.0" + } + }, "node_modules/jest-worker": { "version": "27.3.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.3.1.tgz", @@ -5945,6 +5986,16 @@ "semver": "bin/semver.js" } }, + "node_modules/masonry-layout": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/masonry-layout/-/masonry-layout-4.2.2.tgz", + "integrity": "sha512-iGtAlrpHNyxaR19CvKC3npnEcAwszXoyJiI8ARV2ePi7fmYhIud25MHK8Zx4P0LCC4d3TNO9+rFa1KoK1OEOaA==", + "dev": true, + "dependencies": { + "get-size": "^2.0.2", + "outlayer": "^2.1.0" + } + }, "node_modules/md5": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", @@ -6546,6 +6597,17 @@ "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", "dev": true }, + "node_modules/outlayer": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/outlayer/-/outlayer-2.1.1.tgz", + "integrity": "sha1-KYY7beEOpdrf/8rfoNcokHOH6aI=", + "dev": true, + "dependencies": { + "ev-emitter": "^1.0.0", + "fizzy-ui-utils": "^2.0.0", + "get-size": "^2.0.2" + } + }, "node_modules/p-event": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/p-event/-/p-event-4.2.0.tgz", @@ -12529,6 +12591,12 @@ "minimalistic-assert": "^1.0.0" } }, + "desandro-matches-selector": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/desandro-matches-selector/-/desandro-matches-selector-2.0.2.tgz", + "integrity": "sha1-cXvu1NwT59jzdi9wem1YpndCGOE=", + "dev": true + }, "destroy": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", @@ -12835,6 +12903,12 @@ "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", "dev": true }, + "ev-emitter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ev-emitter/-/ev-emitter-1.1.1.tgz", + "integrity": "sha512-ipiDYhdQSCZ4hSbX4rMW+XzNKMD1prg/sTvoVmSLkuQ1MVlwjJQQA+sW8tMYR3BLUr9KjodFV4pvzunvRhd33Q==", + "dev": true + }, "eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", @@ -13095,6 +13169,15 @@ "path-exists": "^4.0.0" } }, + "fizzy-ui-utils": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/fizzy-ui-utils/-/fizzy-ui-utils-2.0.7.tgz", + "integrity": "sha512-CZXDVXQ1If3/r8s0T+v+qVeMshhfcuq0rqIFgJnrtd+Bu8GmDmqMjntjUePypVtjHXKJ6V4sw9zeyox34n9aCg==", + "dev": true, + "requires": { + "desandro-matches-selector": "^2.0.0" + } + }, "follow-redirects": { "version": "1.14.4", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.4.tgz", @@ -13178,6 +13261,12 @@ "has-symbols": "^1.0.1" } }, + "get-size": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/get-size/-/get-size-2.0.3.tgz", + "integrity": "sha512-lXNzT/h/dTjTxRbm9BXb+SGxxzkm97h/PCIKtlN/CBCxxmkkIVV21udumMS93MuVTDX583gqc94v3RjuHmI+2Q==", + "dev": true + }, "get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -13932,6 +14021,19 @@ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", "dev": true }, + "isotope-layout": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/isotope-layout/-/isotope-layout-3.0.6.tgz", + "integrity": "sha512-z2ZKablhocXhoNyWwzJPFd7u7FWbYbVJA51Nvsqsod8jH2ExGc1SwDsSWKE54e3PhXzqf2yZPhFSq/c2MR1arw==", + "dev": true, + "requires": { + "desandro-matches-selector": "^2.0.0", + "fizzy-ui-utils": "^2.0.4", + "get-size": "^2.0.0", + "masonry-layout": "^4.1.0", + "outlayer": "^2.1.0" + } + }, "jest-worker": { "version": "27.3.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.3.1.tgz", @@ -14199,6 +14301,16 @@ } } }, + "masonry-layout": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/masonry-layout/-/masonry-layout-4.2.2.tgz", + "integrity": "sha512-iGtAlrpHNyxaR19CvKC3npnEcAwszXoyJiI8ARV2ePi7fmYhIud25MHK8Zx4P0LCC4d3TNO9+rFa1KoK1OEOaA==", + "dev": true, + "requires": { + "get-size": "^2.0.2", + "outlayer": "^2.1.0" + } + }, "md5": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", @@ -14668,6 +14780,17 @@ "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", "dev": true }, + "outlayer": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/outlayer/-/outlayer-2.1.1.tgz", + "integrity": "sha1-KYY7beEOpdrf/8rfoNcokHOH6aI=", + "dev": true, + "requires": { + "ev-emitter": "^1.0.0", + "fizzy-ui-utils": "^2.0.0", + "get-size": "^2.0.2" + } + }, "p-event": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/p-event/-/p-event-4.2.0.tgz", diff --git a/package.json b/package.json index d0ffd1f..1d99dfd 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "alpinejs": "^3.4.2", "autoprefixer": "^10.3.7", "axios": "^0.21", + "isotope-layout": "^3.0.6", "laravel-mix": "^6.0.6", "lodash": "^4.17.19", "postcss": "^8.3.10", diff --git a/resources/js/alpine.js b/resources/js/alpine.js new file mode 100644 index 0000000..7df739f --- /dev/null +++ b/resources/js/alpine.js @@ -0,0 +1,13 @@ +// AlpineJS setup - https://alpinejs.dev/ +import Alpine from 'alpinejs'; +window.Alpine = Alpine; + +// Plugins +import intersect from '@alpinejs/intersect'; +Alpine.plugin(intersect); + +// Components +import media_library from './media-library'; +Alpine.data('media_library', media_library); + +Alpine.start(); diff --git a/resources/js/bootstrap.js b/resources/js/bootstrap.js index f20f4b2..5a20387 100644 --- a/resources/js/bootstrap.js +++ b/resources/js/bootstrap.js @@ -1,12 +1,4 @@ -// Add AlpineJS - https://alpinejs.dev/ -import Alpine from 'alpinejs'; -window.Alpine = Alpine; - -import intersect from '@alpinejs/intersect'; -Alpine.plugin(intersect) - -Alpine.start(); - +require('./alpine'); window._ = require('lodash'); diff --git a/resources/js/media-library.js b/resources/js/media-library.js new file mode 100644 index 0000000..9941804 --- /dev/null +++ b/resources/js/media-library.js @@ -0,0 +1,74 @@ +// Media Library functionality +export default (options = {}) => ({ + filters: options.filters || {}, // Filters JSON array is passed in from HTML + gridSelector: options.gridSelector || false, + filtersOpen: false, + activeTypeFilters: [], + activeThemeFilters: [], + isotope: {}, + + get themeFilterList() { + // Convert active theme filters array into class string for Isotope + // There are two separate groups of filters: media types and themes + // Within each group, Isotope should match any filter ("OR") but + // between the two groups, it should match both ("AND") + // To achieve this, we combine the two lists... + + // Todo: see https://stackoverflow.com/a/20490439 for an alternative solution that supports multiple sets of filters + + let list = []; + let types = this.activeTypeFilters; + let themes = this.activeThemeFilters; + + // If no media types are defined, just return the concatenated theme list (even if it might also be empty) + if (types.length === 0) { + return themes.map(filter => `.${filter}`).join(); + } + + if (themes.length === 0) { + return types.map(filter => `.${filter}`).join(); + } + + // If we get to here, both lists are populated, so combine them... + types.forEach(function(type) { + themes.forEach(function(theme) { + list.push(`.${type}.${theme}`); + }); + }); + + return list.join(); + }, + + init() { + if (!this.gridSelector) { + console.warn('Error: gridSelector not defined for media grid'); + return false; + } + + // Initialise Isotope + this.isotope = new Isotope(this.gridSelector, { + layoutMode: 'fitRows', + percentPosition: true, + }); + + // Update Isotope whenever active filters change + this.$watch('activeTypeFilters', () => this.applyFilters()); + this.$watch('activeThemeFilters', () => this.applyFilters()); + }, + + applyFilters() { + console.log('applying filters', this.themeFilterList) + this.isotope.arrange({ filter: this.themeFilterList }) + }, + + removeTypeFilter(filterID) { + // Remove the filter from the array + this.activeTypeFilters = this.activeTypeFilters.filter(item => item !== filterID) + }, + + removeThemeFilter(filterID) { + // Remove the filter from the array + this.activeThemeFilters = this.activeThemeFilters.filter(item => item !== filterID) + }, + +}); diff --git a/resources/views/components/duration.blade.php b/resources/views/components/duration.blade.php new file mode 100644 index 0000000..a85486a --- /dev/null +++ b/resources/views/components/duration.blade.php @@ -0,0 +1,10 @@ +{{-- Duration: converts seconds to minutes + seconds with zero padding --}} +@php + $duration = intval((string) $slot); // Need to convert $slot from Illuminate\Support\HtmlString to string before using intval() + $seconds = $duration % 60; // Fractional part of any full minutes + $minutes = floor($duration / 60); // Floor the value because we already captured the extra seconds +@endphp + + + {{ str_pad($minutes, 2, STR_PAD_LEFT) }}’{{ str_pad($seconds, 2, STR_PAD_LEFT) }} + diff --git a/resources/views/front/media-library.blade.php b/resources/views/front/media-library.blade.php index fdb4abe..2c86c16 100644 --- a/resources/views/front/media-library.blade.php +++ b/resources/views/front/media-library.blade.php @@ -8,6 +8,7 @@ [ 'title' => "Qu’est ce que la gouvernance ?", 'type' => 'video', + 'duration' => '78', 'image' => 'https://odl.paris.cubedesigners.com/storage/46/conversions/VIDEO2-poster.jpg', 'file' => '#', 'theme' => [ @@ -19,6 +20,7 @@ [ 'title' => 'Les outils de communication', 'type' => 'video', + 'duration' => '192', 'image' => 'https://odl.paris.cubedesigners.com/storage/4/conversions/Big-rock-at-the-beach-poster.jpg', 'file' => '#', 'theme' => [ @@ -30,6 +32,7 @@ [ 'title' => "Une organisation à plusieurs niveaux", 'type' => 'video', + 'duration' => '322', 'image' => 'https://odl.paris.cubedesigners.com/storage/46/conversions/VIDEO2-poster.jpg', 'file' => '#', 'theme' => [ @@ -40,7 +43,8 @@ ], [ 'title' => 'Système d’information', - 'type' => 'video', + 'type' => 'audio', + 'duration' => '987', 'image' => 'https://odl.paris.cubedesigners.com/storage/4/conversions/Big-rock-at-the-beach-poster.jpg', 'file' => '#', 'theme' => [ @@ -52,6 +56,7 @@ [ 'title' => "Qu’est ce que la gouvernance ?", 'type' => 'video', + 'duration' => '414', 'image' => 'https://odl.paris.cubedesigners.com/storage/46/conversions/VIDEO2-poster.jpg', 'file' => '#', 'theme' => [ @@ -62,7 +67,8 @@ ], [ 'title' => 'Les outils de communication', - 'type' => 'video', + 'type' => 'audio', + 'duration' => '45', 'image' => 'https://odl.paris.cubedesigners.com/storage/4/conversions/Big-rock-at-the-beach-poster.jpg', 'file' => '#', 'theme' => [ @@ -98,30 +104,64 @@ }); $filters = array_merge($media_filters->toArray(), $theme_filters->toArray()); - // TODO: Implement Isotope JS for class-based filtering @endphp -
+ {{-- Media Library widget --}} + {{-- See alpine.js for setup and media-library.js for code --}} +

Médiathèque

{{-- FILTERS --}}
+ + {{-- FILTER INTERFACE BUTTON --}} Filtrer - {{-- ACTIVE FILTERS (JS) --}} -