]> _ Git - odl.git/commitdiff
WIP #4821 @12
authorStephen Cameron <stephen@cubedesigners.com>
Wed, 10 Nov 2021 17:20:44 +0000 (18:20 +0100)
committerStephen Cameron <stephen@cubedesigners.com>
Wed, 10 Nov 2021 17:20:44 +0000 (18:20 +0100)
package-lock.json
package.json
resources/js/alpine.js [new file with mode: 0644]
resources/js/bootstrap.js
resources/js/media-library.js [new file with mode: 0644]
resources/views/components/duration.blade.php [new file with mode: 0644]
resources/views/front/media-library.blade.php
resources/views/layouts/app.blade.php
webpack.mix.js

index 2f3f4de2d39224134f76f498db29650e8948789b..02f668802cc1fa3e1474e8b96291a4bc619b1728 100644 (file)
@@ -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",
                 "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",
                 "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",
                 "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",
                 "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",
                 "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",
                 "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",
             "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",
                 "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",
             "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",
                 "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",
                 "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",
             "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",
                 }
             }
         },
+        "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",
             "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",
index d0ffd1f07dfad3013f44566091f9522bde462fdf..1d99dfd32287ca9604df430335250bc4536716ea 100644 (file)
@@ -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 (file)
index 0000000..7df739f
--- /dev/null
@@ -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();
index f20f4b2de54da48c1a356ef4aab3182be1285d3a..5a20387bffe0eee85770f565d226e45c76a0d09e 100644 (file)
@@ -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 (file)
index 0000000..9941804
--- /dev/null
@@ -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 (file)
index 0000000..a85486a
--- /dev/null
@@ -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
+
+<span {{ $attributes }}>
+    {{ str_pad($minutes, 2, STR_PAD_LEFT) }}&rsquo;{{ str_pad($seconds, 2, STR_PAD_LEFT) }}
+</span>
index fdb4abe8ecfc7b0335b71bef53a8276f90f4b3c3..2c86c164ec730f5499865fa5f170040c7c920c00 100644 (file)
@@ -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' => [
         });
         $filters = array_merge($media_filters->toArray(), $theme_filters->toArray());
 
-        // TODO: Implement Isotope JS for class-based filtering
     @endphp
 
-    <div x-data="{ filters: {{ json_encode($filters) }}, filtersOpen: false, activeFilters: [] }" x-effect="console.log(activeFilters)">
+    {{-- Media Library widget --}}
+    {{-- See alpine.js for setup and media-library.js for code --}}
+    <div x-data="media_library({ gridSelector: '.media-grid', filters: {{ json_encode($filters) }} })">
 
         <h1 class="uppercase">Médiathèque</h1>
 
         {{-- FILTERS --}}
         <div class="mt-10 space-x-2 space-y-2">
+
+            {{-- FILTER INTERFACE BUTTON --}}
             <a @click.prevent="filtersOpen = true"
                href="#"
                class="inline-block py-4 px-6 rounded-full
                       bg-black hover:bg-blue
+                      mt-2 {{-- Margin top added here so layout doesn't shift when last filter is removed --}}
+                      border border-black hover:border-blue {{-- Needs border so it matches the height of filter buttons --}}
                       text-white font-secondary font-medium leading-none">
                 Filtrer
             </a>
 
-            {{-- ACTIVE FILTERS (JS) --}}
-            <template x-for="activeFilter in activeFilters">
-                <div class="relative inline-flex py-4 pl-4 pr-12 leading-none rounded-full border border-grey-200 text-black">
-                    <span x-text="filters[activeFilter]" class="whitespace-nowrap"></span>
+            {{-- ACTIVE [MEDIA TYPE] FILTERS (JS) --}}
+            {{-- Media types must be filtered separately from themes --}}
+            <template x-for="activeTypeFilter in activeTypeFilters">
+                <div class="relative inline-flex
+                            py-4 pl-4 pr-12
+                            rounded-full border border-grey-200
+                            leading-none
+                            font-secondary font-medium text-black">
+                    <span x-text="filters[activeTypeFilter]" class="whitespace-nowrap"></span>
                     {{-- REMOVE (X) ICON --}}
                     <a href="#"
-                       @click.prevent="activeFilters = activeFilters.filter(item => item !== activeFilter)"
+                       @click.prevent="removeTypeFilter(activeTypeFilter)"
+                       class="absolute w-7 h-7 right-2 top-1/2 transform -translate-y-1/2
+                              flex items-center justify-center
+                              rounded-full bg-grey-200 text-current">
+                        <svg class="stroke-current" xmlns="http://www.w3.org/2000/svg" width="11" height="11" viewBox="0 0 10.828 10.828">
+                            <g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
+                                <path d="m1.414 9.414 8-8"/>
+                                <path data-name="Path 46" d="m1.414 1.414 8 8"/>
+                            </g>
+                        </svg>
+                    </a>
+                </div>
+            </template>
+
+            {{-- ACTIVE [THEME] FILTERS (JS) --}}
+            <template x-for="activeThemeFilter in activeThemeFilters">
+                <div class="relative inline-flex
+                            py-4 pl-4 pr-12
+                            rounded-full border border-grey-200
+                            leading-none
+                            font-secondary font-medium text-black">
+                    <span x-text="filters[activeThemeFilter]" class="whitespace-nowrap"></span>
+                    {{-- REMOVE (X) ICON --}}
+                    <a href="#"
+                       @click.prevent="removeThemeFilter(activeThemeFilter)"
                        class="absolute w-7 h-7 right-2 top-1/2 transform -translate-y-1/2
                               flex items-center justify-center
                               rounded-full bg-grey-200 text-current">
         </div>
 
         {{-- MEDIA LIBRARY GRID --}}
-        <div class="mt-10 grid grid-cols-4 gap-x-5 gap-y-16">
+        {{-- Negative margins applied here to offset margins used in Isotope grid --}}
+        <div class="media-grid mt-10 -mb-16 -mx-2.5">
             @foreach ($media as $item)
-                <div class="media-item media-{{ $item['type'] }} theme-{{ $item['theme']['id'] }}">
+                <div class="media-item
+                            {{-- Width is 25% minus the gutters (2 * 0.625rem that comes from mx-2.5) --}}
+                            float-left w-[calc(25%-1.25rem)] mx-2.5 mb-16
+                            media-{{ $item['type'] }}
+                            theme-{{ $item['theme']['id'] }}">
                     <div class="media-item-image relative bg-cover bg-no-repeat rounded-md pb-[56.25%]" style="background-image:url({{ $item['image'] }})">
-                        {{-- TODO: play button --}}
-                        {{-- TODO: media length --}}
+
+                        {{-- Play Icon --}}
+                        <svg class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2"
+                             xmlns="http://www.w3.org/2000/svg" width="60" height="60" viewBox="0 0 59.999 59.999">
+                            <g fill="#0725e2">
+                                <path d="M0 29.999a30 30 0 1 0 30-30 30 30 0 0 0-30 30Z"/>
+                                <path d="m26.5 38.5 12-8-12-8Z" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
+                            </g>
+                        </svg>
+
+                        <x-duration class="absolute bottom-2 right-2 font-secondary text-white">{{ $item['duration'] }}</x-duration>
                     </div>
+                    {{-- THEME LABEL --}}
                     <div class="mt-2.5 font-secondary font-medium text-xs leading-none" style="color:{{ $item['theme']['color'] }}">
-                        {{ $item['theme']['title'] }}
+                        {{ $item['theme']['title'] }}  [{{ strtoupper($item['type']) }}]
                     </div>
                     <div class="mt-1.5 font-semibold leading-snug">
                         {{ $item['title'] }}
             @endforeach
         </div>
 
-        {{-- FILTERING INTERFACE --}}
+        {{-- FILTER SELECTION INTERFACE (full screen overlay) --}}
         <div class="filters-overlay
                     fixed top-0 left-0 w-screen h-screen
                     flex items-center
                                 <label>
                                     <input type="checkbox"
                                            value="media-{{ $media_type_id }}"
-                                           x-model="activeFilters"
+                                           x-model="activeTypeFilters"
                                            class="peer sr-only">
                                     <span class="inline-block cursor-pointer
                                                  font-secondary font-medium
                                 <label>
                                     <input type="checkbox"
                                            value="theme-{{ $theme_id }}"
-                                           x-model="activeFilters"
+                                           x-model="activeThemeFilters"
                                            class="peer sr-only">
                                     <span class="inline-block cursor-pointer
                                                  font-secondary font-medium
 
     </div>
 
-
-
 @endsection
+
+@push('before_scripts')
+    <script src="{{ asset('js/isotope.js') }}"></script>
+@endpush
index 868f39ff5de6dd4677d6c7aedb1a4546b6fd6108..4722bff26c813623d37a5849c4607c24371758be 100644 (file)
         </ul>
     </div>
 
+    @stack('before_scripts')
     <script src="{{ asset('js/app.js') }}"></script>
+    @stack('after_scripts')
 
 </body>
 </html>
index 215373c93f55baff95ab18c209b48a59b3a136ba..eacbded548ab081a1ddf7a96056ebc4d05f9a16a 100644 (file)
@@ -1,18 +1,11 @@
 const mix = require('laravel-mix');
 
-/*
- |--------------------------------------------------------------------------
- | Mix Asset Management
- |--------------------------------------------------------------------------
- |
- | Mix provides a clean, fluent API for defining some Webpack build steps
- | for your Laravel applications. By default, we are compiling the CSS
- | file for the application as well as bundling up all the JS files.
- |
- */
 
 mix.js('resources/js/app.js', 'public/js')
-    .postCss('resources/css/app.css', 'public/css', [
+   .postCss('resources/css/app.css', 'public/css', [
         require('postcss-import'), // Enables build-time imports
         require("tailwindcss"),
     ]);
+
+// Copy pre-compiled Isotope JS package
+mix.copy('node_modules/isotope-layout/dist/isotope.pkgd.min.js', 'public/js/isotope.js');