diff --git a/app/Importers/Image/Jobs/RotateImage.php b/app/Importers/Image/Jobs/RotateImage.php index c08f576..5dee8b6 100644 --- a/app/Importers/Image/Jobs/RotateImage.php +++ b/app/Importers/Image/Jobs/RotateImage.php @@ -27,8 +27,7 @@ class RotateImage implements ShouldQueue $this->destination = $this->image->album_id . '/original/' . $this->image->id . '.avif'; } - public function handle(): void - { + public function handle(): void { if (method_exists($this, 'batch') && $this->batch()?->cancelled()) { return; } diff --git a/app/Livewire/Album/Show.php b/app/Livewire/Album/Show.php index 1841d89..a3f601d 100644 --- a/app/Livewire/Album/Show.php +++ b/app/Livewire/Album/Show.php @@ -8,7 +8,6 @@ use Illuminate\Contracts\View\View; use Illuminate\Support\Facades\Bus; use Livewire\Attributes\Locked; use Livewire\Attributes\On; -use Livewire\Attributes\Title; use Livewire\Component; use App\Models\Album; use App\Models\Image; @@ -42,6 +41,11 @@ class Show extends Component $this->redirect(route('album.show', $this->album), navigate: true); } + #[On('image.makeCover')] + public function makeCover(int $image_id):void { + Image::findOrFail($image_id)->makeCover(); + } + private function dispatchRotateJob(Image $image, int $degrees) : void { $image->update([ 'isProcessing' => true, @@ -57,9 +61,9 @@ class Show extends Component ])->dispatch(); } - #[Title('Show Album')] public function render(): View|Factory { - return view('livewire.album.show'); + return view('livewire.album.show') + ->title($this->album->name); } } diff --git a/app/Livewire/CategoryFilter.php b/app/Livewire/CategoryFilter.php index ab5299d..4fbe181 100644 --- a/app/Livewire/CategoryFilter.php +++ b/app/Livewire/CategoryFilter.php @@ -2,15 +2,22 @@ namespace App\Livewire; +use Illuminate\Support\Collection; use Livewire\Component; use App\Models\Tag; use App\Models\Category; +use Livewire\Attributes\Computed; class CategoryFilter extends Component { public ?Tag $filter = null; - public function setFilter(int $filter) { + #[Computed] + public function categories() : Collection { + return $this->filter?->categories ?? Category::all(); + } + + public function setFilter(int $filter) : void { $this->filter = Tag::find($filter); } @@ -18,7 +25,6 @@ class CategoryFilter extends Component { return view('livewire.category-filter', [ 'tags' => Tag::all(), - 'categories' => Category::all(), ]); } } diff --git a/app/Livewire/Menu.php b/app/Livewire/Menu.php new file mode 100644 index 0000000..91b943a --- /dev/null +++ b/app/Livewire/Menu.php @@ -0,0 +1,19 @@ +title = $title ?? config('app.name'); + } + + public function render() + { + return view('livewire.menu'); + } +} diff --git a/app/Models/Image.php b/app/Models/Image.php index e66d42b..6fbbdab 100644 --- a/app/Models/Image.php +++ b/app/Models/Image.php @@ -49,6 +49,12 @@ class Image extends Model implements HasThumbnail $this->save(); } + public function makeCover() : void { + Image::where('isCover', 1)->where('album_id', $this->album_id)->update(['isCover' => 0]); + $this->isCover = true; + $this->save(); + } + public function getLightboxAttribute() : array { return [ 'location' => route('image.lightbox', $this) . '?cacheBuster3000=' . $this->updated_at->timestamp, diff --git a/package-lock.json b/package-lock.json index fbc6e90..f5f0184 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,6 +5,7 @@ "packages": { "": { "dependencies": { + "@marcreichel/alpine-auto-animate": "^1.1.0", "filepond": "^4.31.1", "filepond-plugin-file-validate-size": "^2.2.8", "filepond-plugin-file-validate-type": "^1.2.9", @@ -402,6 +403,23 @@ "node": ">=12" } }, + "node_modules/@formkit/auto-animate": { + "version": "1.0.0-pre-alpha.3", + "resolved": "https://registry.npmjs.org/@formkit/auto-animate/-/auto-animate-1.0.0-pre-alpha.3.tgz", + "integrity": "sha512-lMVZ3LFUIu0RIxCEwmV8nUUJQ46M2bv2NDU3hrhZivViuR1EheC8Mj5sx/ACqK5QLK8XB8z7GDIZBUGdU/9OZQ==", + "peerDependencies": { + "react": "^16.8.0", + "vue": "^3.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -467,6 +485,14 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@marcreichel/alpine-auto-animate": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@marcreichel/alpine-auto-animate/-/alpine-auto-animate-1.1.0.tgz", + "integrity": "sha512-ulKU3TAmZ/YkpO34j+tpGFSNSyvXAcprWkO6laFuLODx28zfJBi61TPkD3Tw9BQAl57jL91yybMFhRT3VHRPlQ==", + "dependencies": { + "@formkit/auto-animate": "^1.0.0-beta.3" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", diff --git a/package.json b/package.json index f860761..de52ff8 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "vite": "^5.0" }, "dependencies": { + "@marcreichel/alpine-auto-animate": "^1.1.0", "filepond": "^4.31.1", "filepond-plugin-file-validate-size": "^2.2.8", "filepond-plugin-file-validate-type": "^1.2.9", diff --git a/resources/assets/Wildsau.svg b/resources/assets/Wildsau.svg new file mode 100644 index 0000000..b8ebd3d --- /dev/null +++ b/resources/assets/Wildsau.svg @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/resources/css/app.css b/resources/css/app.css index c293f4a..7fcce12 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -37,7 +37,7 @@ } .pswp__top-bar { - @apply fixed bottom-2 top-auto w-auto left-1/2 -translate-x-1/2 text-gray-500 bg-white rounded-lg shadow dark:text-gray-400 dark:bg-gray-800; + @apply fixed bottom-2 top-auto w-auto left-1/2 -translate-x-1/2 text-gray-500 bg-white rounded-lg shadow dark:text-gray-400 dark:bg-gray-800 border border-gray-200 dark:border-gray-600; } .pswp__button { @@ -47,3 +47,51 @@ .pswp--zoomed-in .pswp__zoom-icn-bar-b { display: block; } + +.pswp__preloader { + display: none; +} + +.pswp__counter { + @apply text-base text-gray-900 dark:text-white content-center px-2; + text-shadow: none; +} + +#toaster { + z-index: 100001; +} + +#toaster[data-expanded="true"] { + height: var(--full-height); +} + +#toaster>div { + --scale: var(--index) * 0.05 + 1; + transform: translate(var(--swipe-amount, 0px), calc(14px * var(--index) + var(--front-toast-height) - 100%)) scale(calc(-1 * var(--scale))); + touch-action: none; + will-change: transform, opacity; + cursor: grab; +} + +#toaster>div[data-swiping="true"] { + cursor: grabbing; +} + +#toaster>div[data-removed="true"], +#toaster>div[data-hidden="true"] { + opacity: 0; +} + +#toaster[data-expanded="true"]>div[data-hidden="true"] { + opacity: 100; +} + +#toaster[data-expanded="true"]>div[data-front="true"], +#toaster:hover>div[data-front="true"] { + transform: translate(var(--swipe-amount, 0px), 0); +} + +#toaster[data-expanded="true"]>div, +#toaster:hover>div { + transform: translate(var(--swipe-amount, 0px), calc(var(--index) * 14px + var(--offset))) scale(1); +} \ No newline at end of file diff --git a/resources/css/sonner.css b/resources/css/sonner.css deleted file mode 100644 index 56e9dd5..0000000 --- a/resources/css/sonner.css +++ /dev/null @@ -1,682 +0,0 @@ -html[dir="ltr"], -[data-sonner-toaster][dir="ltr"] { - --toast-icon-margin-start: -3px; - --toast-icon-margin-end: 4px; - --toast-svg-margin-start: -1px; - --toast-svg-margin-end: 0px; - --toast-button-margin-start: auto; - --toast-button-margin-end: 0; - --toast-close-button-start: 0; - --toast-close-button-end: unset; - --toast-close-button-transform: translate(-35%, -35%); -} - -html[dir="rtl"], -[data-sonner-toaster][dir="rtl"] { - --toast-icon-margin-start: 4px; - --toast-icon-margin-end: -3px; - --toast-svg-margin-start: 0px; - --toast-svg-margin-end: -1px; - --toast-button-margin-start: 0; - --toast-button-margin-end: auto; - --toast-close-button-start: unset; - --toast-close-button-end: 0; - --toast-close-button-transform: translate(35%, -35%); -} - -[data-sonner-toaster] { - position: fixed; - width: var(--width); - font-family: - ui-sans-serif, - system-ui, - -apple-system, - BlinkMacSystemFont, - Segoe UI, - Roboto, - Helvetica Neue, - Arial, - Noto Sans, - sans-serif, - Apple Color Emoji, - Segoe UI Emoji, - Segoe UI Symbol, - Noto Color Emoji; - --gray1: hsl(0, 0%, 99%); - --gray2: hsl(0, 0%, 97.3%); - --gray3: hsl(0, 0%, 95.1%); - --gray4: hsl(0, 0%, 93%); - --gray5: hsl(0, 0%, 90.9%); - --gray6: hsl(0, 0%, 88.7%); - --gray7: hsl(0, 0%, 85.8%); - --gray8: hsl(0, 0%, 78%); - --gray9: hsl(0, 0%, 56.1%); - --gray10: hsl(0, 0%, 52.3%); - --gray11: hsl(0, 0%, 43.5%); - --gray12: hsl(0, 0%, 9%); - --border-radius: 8px; - box-sizing: border-box; - padding: 0; - margin: 0; - list-style: none; - outline: none; - z-index: 999999999; -} - -[data-sonner-toaster][data-x-position="right"] { - right: max(var(--offset), env(safe-area-inset-right)); -} - -[data-sonner-toaster][data-x-position="left"] { - left: max(var(--offset), env(safe-area-inset-left)); -} - -[data-sonner-toaster][data-x-position="center"] { - left: 50%; - transform: translateX(-50%); -} - -[data-sonner-toaster][data-y-position="top"] { - top: max(var(--offset), env(safe-area-inset-top)); -} - -[data-sonner-toaster][data-y-position="bottom"] { - bottom: max(var(--offset), env(safe-area-inset-bottom)); -} - -[data-sonner-toast] { - --y: translateY(100%); - --lift-amount: calc(var(--lift) * var(--gap)); - z-index: var(--z-index); - position: absolute; - opacity: 0; - transform: var(--y); - /* https://stackoverflow.com/questions/48124372/pointermove-event-not-working-with-touch-why-not */ - touch-action: none; - will-change: transform, opacity, height; - transition: - transform 400ms, - opacity 400ms, - height 400ms, - box-shadow 200ms; - box-sizing: border-box; - outline: none; - overflow-wrap: anywhere; -} - -[data-sonner-toast][data-styled="true"] { - padding: 16px; - background: var(--normal-bg); - border: 1px solid var(--normal-border); - color: var(--normal-text); - border-radius: var(--border-radius); - box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.1); - width: var(--width); - font-size: 13px; - display: flex; - align-items: center; - gap: 6px; -} - -[data-sonner-toast]:focus-visible { - box-shadow: - 0px 4px 12px rgba(0, 0, 0, 0.1), - 0 0 0 2px rgba(0, 0, 0, 0.2); -} - -[data-sonner-toast][data-y-position="top"] { - top: 0; - --y: translateY(-100%); - --lift: 1; - --lift-amount: calc(1 * var(--gap)); -} - -[data-sonner-toast][data-y-position="bottom"] { - bottom: 0; - --y: translateY(100%); - --lift: -1; - --lift-amount: calc(var(--lift) * var(--gap)); -} - -[data-sonner-toast] [data-description] { - font-weight: 400; - line-height: 1.4; - color: inherit; -} - -[data-sonner-toast] [data-title] { - font-weight: 500; - line-height: 1.5; - color: inherit; -} - -[data-sonner-toast] [data-icon] { - display: flex; - height: 16px; - width: 16px; - position: relative; - justify-content: flex-start; - align-items: center; - flex-shrink: 0; - margin-left: var(--toast-icon-margin-start); - margin-right: var(--toast-icon-margin-end); -} - -[data-sonner-toast][data-promise="true"] [data-icon] > svg { - opacity: 0; - transform: scale(0.8); - transform-origin: center; - animation: sonner-fade-in 300ms ease forwards; -} - -[data-sonner-toast] [data-icon] > * { - flex-shrink: 0; -} - -[data-sonner-toast] [data-icon] svg { - margin-left: var(--toast-svg-margin-start); - margin-right: var(--toast-svg-margin-end); -} - -[data-sonner-toast] [data-content] { - display: flex; - flex-direction: column; - gap: 2px; -} - -[data-sonner-toast] [data-button] { - border-radius: 4px; - padding-left: 8px; - padding-right: 8px; - height: 24px; - font-size: 12px; - color: var(--normal-bg); - background: var(--normal-text); - margin-left: var(--toast-button-margin-start); - margin-right: var(--toast-button-margin-end); - border: none; - cursor: pointer; - outline: none; - display: flex; - align-items: center; - flex-shrink: 0; - transition: - opacity 400ms, - box-shadow 200ms; -} - -[data-sonner-toast] [data-button]:focus-visible { - box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.4); -} - -[data-sonner-toast] [data-button]:first-of-type { - margin-left: var(--toast-button-margin-start); - margin-right: var(--toast-button-margin-end); -} - -[data-sonner-toast] [data-cancel] { - color: var(--normal-text); - background: rgba(0, 0, 0, 0.08); -} - -[data-sonner-toast][data-theme="dark"] [data-cancel] { - background: rgba(255, 255, 255, 0.3); -} - -[data-sonner-toast] [data-close-button] { - position: absolute; - left: var(--toast-close-button-start); - right: var(--toast-close-button-end); - top: 0; - height: 20px; - width: 20px; - display: flex; - justify-content: center; - align-items: center; - padding: 0; - background: var(--gray1); - color: var(--gray12); - border: 1px solid var(--gray4); - transform: var(--toast-close-button-transform); - border-radius: 50%; - cursor: pointer; - z-index: 1; - transition: - opacity 100ms, - background 200ms, - border-color 200ms; -} - -[data-sonner-toast] [data-close-button]:focus-visible { - box-shadow: - 0px 4px 12px rgba(0, 0, 0, 0.1), - 0 0 0 2px rgba(0, 0, 0, 0.2); -} - -[data-sonner-toast] [data-disabled="true"] { - cursor: not-allowed; -} - -[data-sonner-toast]:hover [data-close-button]:hover { - background: var(--gray2); - border-color: var(--gray5); -} - -/* Leave a ghost div to avoid setting hover to false when swiping out */ -[data-sonner-toast][data-swiping="true"]:before { - content: ""; - position: absolute; - left: 0; - right: 0; - height: 100%; - z-index: -1; -} - -[data-sonner-toast][data-y-position="top"][data-swiping="true"]:before { - /* y 50% needed to distribute height additional height evenly */ - bottom: 50%; - transform: scaleY(3) translateY(50%); -} - -[data-sonner-toast][data-y-position="bottom"][data-swiping="true"]:before { - /* y -50% needed to distribute height additional height evenly */ - top: 50%; - transform: scaleY(3) translateY(-50%); -} - -/* Leave a ghost div to avoid setting hover to false when transitioning out */ -[data-sonner-toast][data-swiping="false"][data-removed="true"]:before { - content: ""; - position: absolute; - inset: 0; - transform: scaleY(2); -} - -/* Needed to avoid setting hover to false when inbetween toasts */ -[data-sonner-toast]:after { - content: ""; - position: absolute; - left: 0; - height: calc(var(--gap) + 1px); - bottom: 100%; - width: 100%; -} - -[data-sonner-toast][data-mounted="true"] { - --y: translateY(0); - opacity: 1; -} - -[data-sonner-toast][data-expanded="false"][data-front="false"] { - --scale: var(--toasts-before) * 0.05; - --y: translateY(calc(var(--lift-amount) * var(--toasts-before))) - scale(calc(1 - var(--scale))); - height: var(--front-toast-height); -} - -[data-sonner-toast] > * { - transition: opacity 400ms; -} - -[data-sonner-toast][data-expanded="false"][data-front="false"][data-styled="true"] - > * { - opacity: 0; -} - -[data-sonner-toast][data-visible="false"] { - opacity: 0; - pointer-events: none; -} - -[data-sonner-toast][data-mounted="true"][data-expanded="true"] { - --y: translateY(calc(var(--lift) * var(--offset))); - height: var(--initial-height); -} - -[data-sonner-toast][data-removed="true"][data-front="true"][data-swipe-out="false"] { - --y: translateY(calc(var(--lift) * -100%)); - opacity: 0; -} - -[data-sonner-toast][data-removed="true"][data-front="false"][data-swipe-out="false"][data-expanded="true"] { - --y: translateY(calc(var(--lift) * var(--offset) + var(--lift) * -100%)); - opacity: 0; -} - -[data-sonner-toast][data-removed="true"][data-front="false"][data-swipe-out="false"][data-expanded="false"] { - --y: translateY(40%); - opacity: 0; - transition: - transform 500ms, - opacity 200ms; -} - -/* Bump up the height to make sure hover state doesn't get set to false */ -[data-sonner-toast][data-removed="true"][data-front="false"]:before { - height: calc(var(--initial-height) + 20%); -} - -[data-sonner-toast][data-swiping="true"] { - transform: var(--y) translateY(var(--swipe-amount, 0px)); - transition: none; -} - -[data-sonner-toast][data-swipe-out="true"][data-y-position="bottom"], -[data-sonner-toast][data-swipe-out="true"][data-y-position="top"] { - animation: swipe-out 200ms ease-out forwards; -} - -@keyframes swipe-out { - from { - transform: translateY( - calc(var(--lift) * var(--offset) + var(--swipe-amount)) - ); - opacity: 1; - } - - to { - transform: translateY( - calc( - var(--lift) * var(--offset) + var(--swipe-amount) + var(--lift) * -100% - ) - ); - opacity: 0; - } -} - -@media (max-width: 600px) { - [data-sonner-toaster] { - position: fixed; - --mobile-offset: 16px; - right: var(--mobile-offset); - left: var(--mobile-offset); - width: 100%; - } - - [data-sonner-toaster] [data-sonner-toast] { - left: 0; - right: 0; - width: calc(100% - 32px); - } - - [data-sonner-toaster][data-x-position="left"] { - left: var(--mobile-offset); - } - - [data-sonner-toaster][data-y-position="bottom"] { - bottom: 20px; - } - - [data-sonner-toaster][data-y-position="top"] { - top: 20px; - } - - [data-sonner-toaster][data-x-position="center"] { - left: var(--mobile-offset); - right: var(--mobile-offset); - transform: none; - } -} - -[data-sonner-toaster][data-theme="light"] { - --normal-bg: #fff; - --normal-border: var(--gray4); - --normal-text: var(--gray12); - - --success-bg: hsl(143, 85%, 96%); - --success-border: hsl(145, 92%, 91%); - --success-text: hsl(140, 100%, 27%); - - --info-bg: hsl(208, 100%, 97%); - --info-border: hsl(221, 91%, 91%); - --info-text: hsl(210, 92%, 45%); - - --warning-bg: hsl(49, 100%, 97%); - --warning-border: hsl(49, 91%, 91%); - --warning-text: hsl(31, 92%, 45%); - - --error-bg: hsl(359, 100%, 97%); - --error-border: hsl(359, 100%, 94%); - --error-text: hsl(360, 100%, 45%); -} - -[data-sonner-toaster][data-theme="light"] - [data-sonner-toast][data-invert="true"] { - --normal-bg: #000; - --normal-border: hsl(0, 0%, 20%); - --normal-text: var(--gray1); -} - -[data-sonner-toaster][data-theme="dark"] - [data-sonner-toast][data-invert="true"] { - --normal-bg: #fff; - --normal-border: var(--gray3); - --normal-text: var(--gray12); -} - -[data-sonner-toaster][data-theme="dark"] { - --normal-bg: #000; - --normal-border: hsl(0, 0%, 20%); - --normal-text: var(--gray1); - - --success-bg: hsl(150, 100%, 6%); - --success-border: hsl(147, 100%, 12%); - --success-text: hsl(150, 86%, 65%); - - --info-bg: hsl(215, 100%, 6%); - --info-border: hsl(223, 100%, 12%); - --info-text: hsl(216, 87%, 65%); - - --warning-bg: hsl(64, 100%, 6%); - --warning-border: hsl(60, 100%, 12%); - --warning-text: hsl(46, 87%, 65%); - - --error-bg: hsl(358, 76%, 10%); - --error-border: hsl(357, 89%, 16%); - --error-text: hsl(358, 100%, 81%); -} - -[data-rich-colors="true"] [data-sonner-toast][data-type="success"] { - background: var(--success-bg); - border-color: var(--success-border); - color: var(--success-text); -} - -[data-rich-colors="true"] - [data-sonner-toast][data-type="success"] - [data-close-button] { - background: var(--success-bg); - border-color: var(--success-border); - color: var(--success-text); -} - -[data-rich-colors="true"] [data-sonner-toast][data-type="info"] { - background: var(--info-bg); - border-color: var(--info-border); - color: var(--info-text); -} - -[data-rich-colors="true"] - [data-sonner-toast][data-type="info"] - [data-close-button] { - background: var(--info-bg); - border-color: var(--info-border); - color: var(--info-text); -} - -[data-rich-colors="true"] [data-sonner-toast][data-type="warning"] { - background: var(--warning-bg); - border-color: var(--warning-border); - color: var(--warning-text); -} - -[data-rich-colors="true"] - [data-sonner-toast][data-type="warning"] - [data-close-button] { - background: var(--warning-bg); - border-color: var(--warning-border); - color: var(--warning-text); -} - -[data-rich-colors="true"] [data-sonner-toast][data-type="error"] { - background: var(--error-bg); - border-color: var(--error-border); - color: var(--error-text); -} - -[data-rich-colors="true"] - [data-sonner-toast][data-type="error"] - [data-close-button] { - background: var(--error-bg); - border-color: var(--error-border); - color: var(--error-text); -} - -.sonner-loading-wrapper { - --size: 16px; - height: var(--size); - width: var(--size); - position: absolute; - inset: 0; - z-index: 10; -} - -.sonner-loading-wrapper[data-visible="false"] { - transform-origin: center; - animation: sonner-fade-out 0.2s ease forwards; -} - -.sonner-spinner { - position: relative; - top: 50%; - left: 50%; - height: var(--size); - width: var(--size); -} - -.sonner-loading-bar { - animation: sonner-spin 1.2s linear infinite; - background: var(--gray11); - border-radius: 6px; - height: 8%; - left: -10%; - position: absolute; - top: -3.9%; - width: 24%; -} - -.sonner-loading-bar:nth-child(1) { - animation-delay: -1.2s; - transform: rotate(0.0001deg) translate(146%); -} - -.sonner-loading-bar:nth-child(2) { - animation-delay: -1.1s; - transform: rotate(30deg) translate(146%); -} - -.sonner-loading-bar:nth-child(3) { - animation-delay: -1s; - transform: rotate(60deg) translate(146%); -} - -.sonner-loading-bar:nth-child(4) { - animation-delay: -0.9s; - transform: rotate(90deg) translate(146%); -} - -.sonner-loading-bar:nth-child(5) { - animation-delay: -0.8s; - transform: rotate(120deg) translate(146%); -} - -.sonner-loading-bar:nth-child(6) { - animation-delay: -0.7s; - transform: rotate(150deg) translate(146%); -} - -.sonner-loading-bar:nth-child(7) { - animation-delay: -0.6s; - transform: rotate(180deg) translate(146%); -} - -.sonner-loading-bar:nth-child(8) { - animation-delay: -0.5s; - transform: rotate(210deg) translate(146%); -} - -.sonner-loading-bar:nth-child(9) { - animation-delay: -0.4s; - transform: rotate(240deg) translate(146%); -} - -.sonner-loading-bar:nth-child(10) { - animation-delay: -0.3s; - transform: rotate(270deg) translate(146%); -} - -.sonner-loading-bar:nth-child(11) { - animation-delay: -0.2s; - transform: rotate(300deg) translate(146%); -} - -.sonner-loading-bar:nth-child(12) { - animation-delay: -0.1s; - transform: rotate(330deg) translate(146%); -} - -@keyframes sonner-fade-in { - 0% { - opacity: 0; - transform: scale(0.8); - } - 100% { - opacity: 1; - transform: scale(1); - } -} - -@keyframes sonner-fade-out { - 0% { - opacity: 1; - transform: scale(1); - } - 100% { - opacity: 0; - transform: scale(0.8); - } -} - -@keyframes sonner-spin { - 0% { - opacity: 1; - } - 100% { - opacity: 0.15; - } -} - -@media (prefers-reduced-motion) { - [data-sonner-toast], - [data-sonner-toast] > *, - .sonner-loading-bar { - transition: none !important; - animation: none !important; - } -} - -.sonner-loader { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - transform-origin: center; - transition: - opacity 200ms, - transform 200ms; -} - -.sonner-loader[data-visible="false"] { - opacity: 0; - transform: scale(0.8) translate(-50%, -50%); -} diff --git a/resources/js/app.js b/resources/js/app.js index cb49050..c29286d 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -3,20 +3,6 @@ import './lightbox'; import './filepond'; import './notification'; -ToastinTakin.init(); - -window.setTimeout(() => { - ToastinTakin.error('Bilder speichern fehlgeschlagen.'); -}, 100); -window.setTimeout(() => { - ToastinTakin.info('Bilder werden geladen, bitte warten.'); -}, 2000); -window.setTimeout(() => { - ToastinTakin.success('Bilder erfolgreich gespeichert'); -}, 3000); -window.setTimeout(() => { - ToastinTakin.error('Bilder doch nicht erfolgreich gespeichert'); -}, 4000); -window.setTimeout(() => { - ToastinTakin.info('Diese Nachrichten sind komisch ;).'); -}, 5000); \ No newline at end of file +document.addEventListener('livewire:init', () => { + ToastinTakin.init(); +}); \ No newline at end of file diff --git a/resources/js/lightbox.js b/resources/js/lightbox.js index a960025..7b69fb4 100644 --- a/resources/js/lightbox.js +++ b/resources/js/lightbox.js @@ -7,8 +7,8 @@ document.addEventListener('livewire:navigated', () => { bgOpacity: 1, arrowPrevSVG: '', arrowNextSVG: '', - closeSVG: '
', - zoomSVG: '', + closeSVG: '', + zoomSVG: '', pswpModule: () => import('photoswipe') }); @@ -18,7 +18,7 @@ document.addEventListener('livewire:navigated', () => { order: 9, isButton: true, tagName: 'button', - html: '', + html: '', onInit: (el, pswp) => { el.setAttribute('download', ''); @@ -32,11 +32,35 @@ document.addEventListener('livewire:navigated', () => { }); lightbox.pswp.ui.registerElement({ - name: 'rotate-button-cw', - ariaLabel: 'Rotate clockwise', + name: 'makeCover', + ariaLabel: 'Make this Image the Album Cover', order: 8, isButton: true, - html: '', + html: '', + onInit: (el, pswp) => { + pswp.on('change', () => { + el.querySelector('.is-cover').classList.toggle('hidden', pswp.currSlide.data.element.dataset.isCover === 'false') + el.querySelector('.no-cover').classList.toggle('hidden', pswp.currSlide.data.element.dataset.isCover === 'true') + }); + }, + onClick: (event, el) => { + Livewire.dispatch(`image.makeCover`, { + image_id: pswp.currSlide.data.element.dataset.id + }); + pswp.currSlide.data.element.dataset.isCover = 'true'; + el.querySelector('.is-cover').classList.toggle('hidden', pswp.currSlide.data.element.dataset.isCover === 'false') + el.querySelector('.no-cover').classList.toggle('hidden', pswp.currSlide.data.element.dataset.isCover === 'true') + + ToastinTakin.success("Das Cover wurde ausgetauscht"); + } + }); + + lightbox.pswp.ui.registerElement({ + name: 'rotate-button-cw', + ariaLabel: 'Rotate clockwise', + order: 7, + isButton: true, + html: '', onClick: (event, el) => { pswp.close(); Livewire.dispatch(`image.rotate`, { @@ -49,9 +73,9 @@ document.addEventListener('livewire:navigated', () => { lightbox.pswp.ui.registerElement({ name: 'rotate-button-ccw', ariaLabel: 'Rotate counter-clockwise', - order: 7, + order: 6, isButton: true, - html: '', + html: '', onClick: (event, el) => { pswp.close(); Livewire.dispatch(`image.rotate`, { diff --git a/resources/js/sonner.js b/resources/js/sonner.js deleted file mode 100644 index f2b57e7..0000000 --- a/resources/js/sonner.js +++ /dev/null @@ -1,626 +0,0 @@ -//////////////////////// -// Sonner -//////////////////////// - -//////////////////////// -// Constants -//////////////////////// -const VISIBLE_TOASTS_AMOUNT = 4; -const VIEWPORT_OFFSET = "32px"; -const TOAST_LIFETIME = 4000; -const TOAST_WIDTH = 356; -const GAP = 14; -const SWIPE_THRESHOLD = 20; -const TIME_BEFORE_UNMOUNT = 200; - -//////////////////////// -// Sonner -// The Sonner object is a singleton that provides methods to show different types of toasts. -//////////////////////// -window.Sonner = { - /** - * Initializes the toasters in the DOM. - * The function creates a new section element and an ordered list element inside it. - * @param {Object} options - An object with the following properties: - * @param {boolean} options.closeButton - A boolean to control the visibility of the close button on the toasts. - * @param {boolean} options.richColors - A boolean to control the use of rich colors for the toasts. - * @param {string} options.position - A string to control the position of the toasts. The string is a combination of two values: the vertical position (top or bottom) and the horizontal position (left or right). - * @returns {void} - * @example - * Sonner.init({ closeButton: true, richColors: true, position: "bottom-right" }); - */ - init({ - closeButton = false, - richColors = false, - position = "bottom-right", - } = {}) { - if (reinitializeToaster()) { - return; - } - - renderToaster({ closeButton, richColors, position }); - // loadSonnerStyles(); - - const ol = document.getElementById("sonner-toaster-list"); - registerMouseOver(ol); - registerKeyboardShortcuts(ol); - }, - /** - * Shows a new success toast with a specific message. - * @param {string} msg - The message to display in the toast. - * @returns {void} - */ - success(msg, opts = {}) { - return Sonner.show(msg, { icon: 'success', type: "success", ...opts }); - }, - /** - * Shows a new error toast with a specific message. - * @param {string} msg - The message to display in the toast. - * @returns {void} - */ - error(msg, opts = {}) { - return Sonner.show(msg, { icon: 'error', type: "error", ...opts }); - }, - /** - * Shows a new info toast with a specific message. - * @param {string} msg - The message to display in the toast. - * @returns {void} - */ - info(msg, opts = {}) { - return Sonner.show(msg, { icon: 'info', type: "info", ...opts }); - }, - /** - * Shows a new warning toast with a specific message. - * @param {string} msg - The message to display in the toast. - * @returns {void} - */ - warning(msg, opts = {}) { - return Sonner.show(msg, { icon: 'warning', type: "warning", ...opts }); - }, - /** - * Shows a promise loading toast - * @template T promise data type - * @param {Promise
-