WIP
This commit is contained in:
@@ -27,8 +27,7 @@ class RotateImage implements ShouldQueue
|
|||||||
$this->destination = $this->image->album_id . '/original/' . $this->image->id . '.avif';
|
$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()) {
|
if (method_exists($this, 'batch') && $this->batch()?->cancelled()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ use Illuminate\Contracts\View\View;
|
|||||||
use Illuminate\Support\Facades\Bus;
|
use Illuminate\Support\Facades\Bus;
|
||||||
use Livewire\Attributes\Locked;
|
use Livewire\Attributes\Locked;
|
||||||
use Livewire\Attributes\On;
|
use Livewire\Attributes\On;
|
||||||
use Livewire\Attributes\Title;
|
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use App\Models\Album;
|
use App\Models\Album;
|
||||||
use App\Models\Image;
|
use App\Models\Image;
|
||||||
@@ -42,6 +41,11 @@ class Show extends Component
|
|||||||
$this->redirect(route('album.show', $this->album), navigate: true);
|
$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 {
|
private function dispatchRotateJob(Image $image, int $degrees) : void {
|
||||||
$image->update([
|
$image->update([
|
||||||
'isProcessing' => true,
|
'isProcessing' => true,
|
||||||
@@ -57,9 +61,9 @@ class Show extends Component
|
|||||||
])->dispatch();
|
])->dispatch();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Title('Show Album')]
|
|
||||||
public function render(): View|Factory
|
public function render(): View|Factory
|
||||||
{
|
{
|
||||||
return view('livewire.album.show');
|
return view('livewire.album.show')
|
||||||
|
->title($this->album->name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,15 +2,22 @@
|
|||||||
|
|
||||||
namespace App\Livewire;
|
namespace App\Livewire;
|
||||||
|
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use App\Models\Tag;
|
use App\Models\Tag;
|
||||||
use App\Models\Category;
|
use App\Models\Category;
|
||||||
|
use Livewire\Attributes\Computed;
|
||||||
|
|
||||||
class CategoryFilter extends Component
|
class CategoryFilter extends Component
|
||||||
{
|
{
|
||||||
public ?Tag $filter = null;
|
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);
|
$this->filter = Tag::find($filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,7 +25,6 @@ class CategoryFilter extends Component
|
|||||||
{
|
{
|
||||||
return view('livewire.category-filter', [
|
return view('livewire.category-filter', [
|
||||||
'tags' => Tag::all(),
|
'tags' => Tag::all(),
|
||||||
'categories' => Category::all(),
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
19
app/Livewire/Menu.php
Normal file
19
app/Livewire/Menu.php
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire;
|
||||||
|
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Menu extends Component
|
||||||
|
{
|
||||||
|
public string $title;
|
||||||
|
|
||||||
|
public function mount(?string $title) : void {
|
||||||
|
$this->title = $title ?? config('app.name');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.menu');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -49,6 +49,12 @@ class Image extends Model implements HasThumbnail
|
|||||||
$this->save();
|
$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 {
|
public function getLightboxAttribute() : array {
|
||||||
return [
|
return [
|
||||||
'location' => route('image.lightbox', $this) . '?cacheBuster3000=' . $this->updated_at->timestamp,
|
'location' => route('image.lightbox', $this) . '?cacheBuster3000=' . $this->updated_at->timestamp,
|
||||||
|
|||||||
26
package-lock.json
generated
26
package-lock.json
generated
@@ -5,6 +5,7 @@
|
|||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@marcreichel/alpine-auto-animate": "^1.1.0",
|
||||||
"filepond": "^4.31.1",
|
"filepond": "^4.31.1",
|
||||||
"filepond-plugin-file-validate-size": "^2.2.8",
|
"filepond-plugin-file-validate-size": "^2.2.8",
|
||||||
"filepond-plugin-file-validate-type": "^1.2.9",
|
"filepond-plugin-file-validate-type": "^1.2.9",
|
||||||
@@ -402,6 +403,23 @@
|
|||||||
"node": ">=12"
|
"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": {
|
"node_modules/@isaacs/cliui": {
|
||||||
"version": "8.0.2",
|
"version": "8.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
||||||
@@ -467,6 +485,14 @@
|
|||||||
"@jridgewell/sourcemap-codec": "^1.4.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": {
|
"node_modules/@nodelib/fs.scandir": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
"vite": "^5.0"
|
"vite": "^5.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@marcreichel/alpine-auto-animate": "^1.1.0",
|
||||||
"filepond": "^4.31.1",
|
"filepond": "^4.31.1",
|
||||||
"filepond-plugin-file-validate-size": "^2.2.8",
|
"filepond-plugin-file-validate-size": "^2.2.8",
|
||||||
"filepond-plugin-file-validate-type": "^1.2.9",
|
"filepond-plugin-file-validate-type": "^1.2.9",
|
||||||
|
|||||||
4
resources/assets/Wildsau.svg
Normal file
4
resources/assets/Wildsau.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 14 KiB |
@@ -37,7 +37,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.pswp__top-bar {
|
.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 {
|
.pswp__button {
|
||||||
@@ -47,3 +47,51 @@
|
|||||||
.pswp--zoomed-in .pswp__zoom-icn-bar-b {
|
.pswp--zoomed-in .pswp__zoom-icn-bar-b {
|
||||||
display: block;
|
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);
|
||||||
|
}
|
||||||
@@ -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%);
|
|
||||||
}
|
|
||||||
@@ -3,20 +3,6 @@ import './lightbox';
|
|||||||
import './filepond';
|
import './filepond';
|
||||||
import './notification';
|
import './notification';
|
||||||
|
|
||||||
ToastinTakin.init();
|
document.addEventListener('livewire:init', () => {
|
||||||
|
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);
|
|
||||||
@@ -7,8 +7,8 @@ document.addEventListener('livewire:navigated', () => {
|
|||||||
bgOpacity: 1,
|
bgOpacity: 1,
|
||||||
arrowPrevSVG: '<svg class="w-12 h-12 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m15 19-7-7 7-7"/></svg>',
|
arrowPrevSVG: '<svg class="w-12 h-12 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m15 19-7-7 7-7"/></svg>',
|
||||||
arrowNextSVG: '<svg class="w-12 h-12 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m9 5 7 7-7 7"/></svg>',
|
arrowNextSVG: '<svg class="w-12 h-12 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m9 5 7 7-7 7"/></svg>',
|
||||||
closeSVG: '<div class="bg-transparent p-2 inline-flex items-center me-2 hover:bg-gray-100 rounded-lg dark:bg-gray-800 dark:hover:bg-gray-700"><svg class="w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18 17.94 6M18 18 6.06 6"/></svg></div>',
|
closeSVG: '<div class="bg-transparent w-full h-full justify-around inline-flex items-center hover:bg-gray-100 rounded-lg dark:bg-gray-800 dark:hover:bg-gray-700"><svg class="w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18 17.94 6M18 18 6.06 6"/></svg></div>',
|
||||||
zoomSVG: '<div class="bg-transparent p-2 inline-flex items-center me-2 hover:bg-gray-100 rounded-lg dark:bg-gray-800 dark:hover:bg-gray-700"><svg class="pswp__zoom-icn-bar-v w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m21 21-3.5-3.5M10 7v6m-3-3h6m4 0a7 7 0 1 1-14 0 7 7 0 0 1 14 0Z"/></svg><svg class="pswp__zoom-icn-bar-b hidden w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m21 21-3.5-3.5M7 10h6m4 0a7 7 0 1 1-14 0 7 7 0 0 1 14 0Z"/></svg></div>',
|
zoomSVG: '<div class="bg-transparent w-full h-full justify-around inline-flex items-center hover:bg-gray-100 rounded-lg dark:bg-gray-800 dark:hover:bg-gray-700"><svg class="pswp__zoom-icn-bar-v w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m21 21-3.5-3.5M10 7v6m-3-3h6m4 0a7 7 0 1 1-14 0 7 7 0 0 1 14 0Z"/></svg><svg class="pswp__zoom-icn-bar-b hidden w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="m21 21-3.5-3.5M7 10h6m4 0a7 7 0 1 1-14 0 7 7 0 0 1 14 0Z"/></svg></div>',
|
||||||
pswpModule: () => import('photoswipe')
|
pswpModule: () => import('photoswipe')
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ document.addEventListener('livewire:navigated', () => {
|
|||||||
order: 9,
|
order: 9,
|
||||||
isButton: true,
|
isButton: true,
|
||||||
tagName: 'button',
|
tagName: 'button',
|
||||||
html: '<div class="bg-transparent p-2 inline-flex items-center me-2 hover:bg-gray-100 rounded-lg dark:bg-gray-800 dark:hover:bg-gray-700"><svg class="w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 13V4M7 14H5a1 1 0 0 0-1 1v4a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1v-4a1 1 0 0 0-1-1h-2m-1-5-4 5-4-5m9 8h.01"/></svg></div>',
|
html: '<div class="bg-transparent w-full h-full justify-around inline-flex items-center hover:bg-gray-100 rounded-lg dark:bg-gray-800 dark:hover:bg-gray-700"><svg class="w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 13V4M7 14H5a1 1 0 0 0-1 1v4a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1v-4a1 1 0 0 0-1-1h-2m-1-5-4 5-4-5m9 8h.01"/></svg></div>',
|
||||||
|
|
||||||
onInit: (el, pswp) => {
|
onInit: (el, pswp) => {
|
||||||
el.setAttribute('download', '');
|
el.setAttribute('download', '');
|
||||||
@@ -32,11 +32,35 @@ document.addEventListener('livewire:navigated', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
lightbox.pswp.ui.registerElement({
|
lightbox.pswp.ui.registerElement({
|
||||||
name: 'rotate-button-cw',
|
name: 'makeCover',
|
||||||
ariaLabel: 'Rotate clockwise',
|
ariaLabel: 'Make this Image the Album Cover',
|
||||||
order: 8,
|
order: 8,
|
||||||
isButton: true,
|
isButton: true,
|
||||||
html: '<div class="bg-transparent p-2 inline-flex items-center me-2 hover:bg-gray-100 rounded-lg dark:bg-gray-800 dark:hover:bg-gray-700"><svg xmlns="http://www.w3.org/2000/svg" fill="none" aria-hidden="true" class="w-6 h-6 text-gray-800 dark:text-white" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24"><path d="M19.407 11.376a7.552 7.552 0 1 1-2.212-5.34"/><path d="M17.555 2.612v4h-4"/></svg></div>',
|
html: '<div class="w-full h-full bg-transparent inline-flex items-center justify-around hover:bg-gray-100 rounded-lg dark:bg-gray-800 dark:hover:bg-gray-700"><svg class="no-cover w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-width="2" d="M11.083 5.104c.35-.8 1.485-.8 1.834 0l1.752 4.022a1 1 0 0 0 .84.597l4.463.342c.9.069 1.255 1.2.556 1.771l-3.33 2.723a1 1 0 0 0-.337 1.016l1.03 4.119c.214.858-.71 1.552-1.474 1.106l-3.913-2.281a1 1 0 0 0-1.008 0L7.583 20.8c-.764.446-1.688-.248-1.474-1.106l1.03-4.119A1 1 0 0 0 6.8 14.56l-3.33-2.723c-.698-.571-.342-1.702.557-1.771l4.462-.342a1 1 0 0 0 .84-.597l1.753-4.022Z"/></svg><svg class="is-cover hidden w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 24 24"><path d="M13.849 4.22c-.684-1.626-3.014-1.626-3.698 0L8.397 8.387l-4.552.361c-1.775.14-2.495 2.331-1.142 3.477l3.468 2.937-1.06 4.392c-.413 1.713 1.472 3.067 2.992 2.149L12 19.35l3.897 2.354c1.52.918 3.405-.436 2.992-2.15l-1.06-4.39 3.468-2.938c1.353-1.146.633-3.336-1.142-3.477l-4.552-.36-1.754-4.17Z"/></svg></div>',
|
||||||
|
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: '<div class="bg-transparent w-full h-full justify-around inline-flex items-center hover:bg-gray-100 rounded-lg dark:bg-gray-800 dark:hover:bg-gray-700"><svg xmlns="http://www.w3.org/2000/svg" fill="none" aria-hidden="true" class="w-6 h-6 text-gray-800 dark:text-white" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="-1 -1 23 23"><path d="M19.407 11.376a7.552 7.552 0 1 1-2.212-5.34"/><path d="M17.555 2.612v4h-4"/></svg></div>',
|
||||||
onClick: (event, el) => {
|
onClick: (event, el) => {
|
||||||
pswp.close();
|
pswp.close();
|
||||||
Livewire.dispatch(`image.rotate`, {
|
Livewire.dispatch(`image.rotate`, {
|
||||||
@@ -49,9 +73,9 @@ document.addEventListener('livewire:navigated', () => {
|
|||||||
lightbox.pswp.ui.registerElement({
|
lightbox.pswp.ui.registerElement({
|
||||||
name: 'rotate-button-ccw',
|
name: 'rotate-button-ccw',
|
||||||
ariaLabel: 'Rotate counter-clockwise',
|
ariaLabel: 'Rotate counter-clockwise',
|
||||||
order: 7,
|
order: 6,
|
||||||
isButton: true,
|
isButton: true,
|
||||||
html: '<div class="bg-transparent p-2 inline-flex items-center me-2 hover:bg-gray-100 rounded-lg dark:bg-gray-800 dark:hover:bg-gray-700"><svg xmlns="http://www.w3.org/2000/svg" fill="none" aria-hidden="true" class="w-6 h-6 text-gray-800 dark:text-white scale-x-[-1]" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24"><path d="M19.407 11.376a7.552 7.552 0 1 1-2.212-5.34"/><path d="M17.555 2.612v4h-4"/></svg></div>',
|
html: '<div class="bg-transparent w-full h-full justify-around inline-flex items-center hover:bg-gray-100 rounded-lg dark:bg-gray-800 dark:hover:bg-gray-700"><svg xmlns="http://www.w3.org/2000/svg" fill="none" aria-hidden="true" class="w-6 h-6 text-gray-800 dark:text-white scale-x-[-1]" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="-1 -1 23 23"><path d="M19.407 11.376a7.552 7.552 0 1 1-2.212-5.34"/><path d="M17.555 2.612v4h-4"/></svg></div>',
|
||||||
onClick: (event, el) => {
|
onClick: (event, el) => {
|
||||||
pswp.close();
|
pswp.close();
|
||||||
Livewire.dispatch(`image.rotate`, {
|
Livewire.dispatch(`image.rotate`, {
|
||||||
|
|||||||
@@ -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<T>} promise
|
|
||||||
* @param {Object} opts options
|
|
||||||
* @param {string} opts.loading message to display while loading
|
|
||||||
* @param {string|(data : T) => string} opts.success function callback / message to show when loaded
|
|
||||||
* @param {string|(data : Error) => string} opts.success function callback / message to show when errored
|
|
||||||
*/
|
|
||||||
promise(promise, opts = {}) {
|
|
||||||
const toast = Sonner.show(opts.loading ?? 'Loading...', {
|
|
||||||
icon: 'loading',
|
|
||||||
type: 'loading',
|
|
||||||
...opts,
|
|
||||||
duration: -1,
|
|
||||||
});
|
|
||||||
|
|
||||||
promise
|
|
||||||
.then(result => {
|
|
||||||
// Update the message and start the timeout
|
|
||||||
const msg = typeof opts.success === 'string' ? opts.success : opts.success(result);
|
|
||||||
toast.setTitle(msg).setIcon('success').setDuration(opts.duration ?? TOAST_LIFETIME);
|
|
||||||
return result;
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
const msg = typeof opts.error === 'string' ? opts.error : opts.error(err);
|
|
||||||
toast.setTitle(msg).setIcon('error').setDuration(opts.duration ?? TOAST_LIFETIME);
|
|
||||||
throw err;
|
|
||||||
});
|
|
||||||
|
|
||||||
return promise;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows a new toast with a specific message, description, and type.
|
|
||||||
* @param {string} msg - The message to display in the toast.
|
|
||||||
* @param {Object} options - An object with the following properties:
|
|
||||||
* @param {string} options.type - The type of the toast. The type can be one of the following values: "success", "error", "info", "warning", or "neutral".
|
|
||||||
* @param {string} options.description - The description to display in the toast.
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
show(msg, opts = {}) {
|
|
||||||
const list = document.getElementById("sonner-toaster-list");
|
|
||||||
const { toast, id } = renderToast(list, msg, opts);
|
|
||||||
|
|
||||||
// Wait for the toast to be mounted before registering swipe events
|
|
||||||
//window.setTimeout(function () {
|
|
||||||
const el = list.children[0];
|
|
||||||
const height = el.getBoundingClientRect().height;
|
|
||||||
|
|
||||||
el.setAttribute("data-mounted", "true");
|
|
||||||
el.setAttribute("data-initial-height", height);
|
|
||||||
el.style.setProperty("--initial-height", `${height}px`);
|
|
||||||
list.style.setProperty("--front-toast-height", `${height}px`);
|
|
||||||
|
|
||||||
registerSwipe(id);
|
|
||||||
refreshProperties();
|
|
||||||
toast.setDuration(opts.duration ?? TOAST_LIFETIME);
|
|
||||||
//}, 16);
|
|
||||||
return toast;
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Removes an element with a specific id from the DOM after a delay.
|
|
||||||
* The element is marked as removed and any previous unmount timeout is cleared.
|
|
||||||
* A new timeout is set to remove the element from its parent.
|
|
||||||
* The timeout ensures that all CSS transitions complete before the element is removed.
|
|
||||||
*
|
|
||||||
* @param {string} id - The data-id attribute of the element to remove.
|
|
||||||
*/
|
|
||||||
remove(id) {
|
|
||||||
const el = document.querySelector(`[data-id="${id}"]`);
|
|
||||||
if (!el) return;
|
|
||||||
|
|
||||||
el.setAttribute("data-removed", "true");
|
|
||||||
refreshProperties();
|
|
||||||
|
|
||||||
const previousTid = el.getAttribute("data-unmount-tid");
|
|
||||||
if (previousTid) window.clearTimeout(previousTid);
|
|
||||||
|
|
||||||
const tid = window.setTimeout(function () {
|
|
||||||
el.parentElement?.removeChild(el);
|
|
||||||
}, TIME_BEFORE_UNMOUNT);
|
|
||||||
el.setAttribute("data-unmount-tid", tid);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
////////////////////////
|
|
||||||
// Assets
|
|
||||||
////////////////////////
|
|
||||||
|
|
||||||
const getIcon = (type) => {
|
|
||||||
switch (type) {
|
|
||||||
case "success":
|
|
||||||
return SuccessIcon;
|
|
||||||
|
|
||||||
case "info":
|
|
||||||
return InfoIcon;
|
|
||||||
|
|
||||||
case "warning":
|
|
||||||
return WarningIcon;
|
|
||||||
|
|
||||||
case "error":
|
|
||||||
return ErrorIcon;
|
|
||||||
|
|
||||||
case 'loading':
|
|
||||||
return Loader;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const bars = Array(12).fill(0);
|
|
||||||
|
|
||||||
const Loader = `
|
|
||||||
<div class="sonner-loading-wrapper" data-visible='${true}'>
|
|
||||||
<div class="sonner-spinner">
|
|
||||||
${bars.map((_, i) => `<div class="sonner-loading-bar" key="spinner-bar-${i}"></div>`).join('\n')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
|
|
||||||
const SuccessIcon = `
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" height="20" width="20">
|
|
||||||
<path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
/>
|
|
||||||
</svg>`;
|
|
||||||
|
|
||||||
const WarningIcon = `
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="currentColor"
|
|
||||||
height="20"
|
|
||||||
width="20"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
d="M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003zM12 8.25a.75.75 0 01.75.75v3.75a.75.75 0 01-1.5 0V9a.75.75 0 01.75-.75zm0 8.25a.75.75 0 100-1.5.75.75 0 000 1.5z"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
/>
|
|
||||||
</svg>`;
|
|
||||||
|
|
||||||
const InfoIcon = `
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 20 20"
|
|
||||||
fill="currentColor"
|
|
||||||
height="20"
|
|
||||||
width="20"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
/>
|
|
||||||
</svg>`;
|
|
||||||
|
|
||||||
const ErrorIcon = `
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewbox="0 0 20 20"
|
|
||||||
fill="currentColor"
|
|
||||||
height="20"
|
|
||||||
width="20"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-5a.75.75 0 01.75.75v4.5a.75.75 0 01-1.5 0v-4.5A.75.75 0 0110 5zm0 10a1 1 0 100-2 1 1 0 000 2z"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
/>
|
|
||||||
</svg>`;
|
|
||||||
|
|
||||||
////////////////////////
|
|
||||||
// Auxiliary functions
|
|
||||||
////////////////////////
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a unique id for a toast.
|
|
||||||
* The function generates a unique id by combining the current timestamp with a random string.
|
|
||||||
* The function returns the unique id as a string.
|
|
||||||
* @returns {string} - The unique id.
|
|
||||||
* @example
|
|
||||||
* const id = genid();
|
|
||||||
*/
|
|
||||||
function genid() {
|
|
||||||
return (
|
|
||||||
Date.now().toString(36) +
|
|
||||||
Math.random().toString(36).substring(2, 12).padStart(12, 0)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new toast element and returns it along with its id.
|
|
||||||
* The function creates a new list item element and sets its outerHTML to a string containing the toast structure.
|
|
||||||
* The function also generates a unique id for the toast and returns it along with the toast element.
|
|
||||||
* @param {Element} list - The list element to append the toast to.
|
|
||||||
* @param {string} msg - The message to display in the toast.
|
|
||||||
* @param {Object} options - An object with the following properties:
|
|
||||||
* @param {string} options.type - The type of the toast. The type can be one of the following values: "success", "error", "info", "warning", or "neutral".
|
|
||||||
* @param {string} options.description - The description to display in the toast.
|
|
||||||
* @returns {Object} - An object with the following properties:
|
|
||||||
* @returns {Element} toast - The toast element.
|
|
||||||
* @returns {string} id - The unique id of the toast.
|
|
||||||
*/
|
|
||||||
function renderToast(list, msg, opts = {}) {
|
|
||||||
const toast = document.createElement("div");
|
|
||||||
list.prepend(toast);
|
|
||||||
const id = genid();
|
|
||||||
const count = list.children.length;
|
|
||||||
const asset = getIcon(opts.icon) ?? opts.icon;
|
|
||||||
|
|
||||||
|
|
||||||
toast.outerHTML = `<li
|
|
||||||
aria-live="polite"
|
|
||||||
aria-atomic="true"
|
|
||||||
role="status"
|
|
||||||
tabindex="0"
|
|
||||||
data-id="${id}"
|
|
||||||
data-type="${opts.type}"
|
|
||||||
data-sonner-toast=""
|
|
||||||
data-mounted="false"
|
|
||||||
data-styled="true"
|
|
||||||
data-promise="false"
|
|
||||||
data-removed="false"
|
|
||||||
data-visible="true"
|
|
||||||
data-y-position="${list.getAttribute("data-y-position")}"
|
|
||||||
data-x-position="${list.getAttribute("data-x-position")}"
|
|
||||||
data-index="${0}"
|
|
||||||
data-front="true"
|
|
||||||
data-swiping="false"
|
|
||||||
data-dismissible="true"
|
|
||||||
data-swipe-out="false"
|
|
||||||
data-expanded="false"
|
|
||||||
style="--index: 0; --toasts-before: ${0}; --z-index: ${count}; --offset: 0px; --initial-height: 0px;"
|
|
||||||
>
|
|
||||||
${list.getAttribute("data-close-button") === "true"
|
|
||||||
? `<button
|
|
||||||
aria-label="Close"
|
|
||||||
data-disabled=""
|
|
||||||
class="absolute top-0.5 right-0.5 border border-neutral-800 text-neutral-800 bg-neutral-100 rounded-sm"
|
|
||||||
onclick="Sonner.remove('${id}')"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="12"
|
|
||||||
height="12"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
>
|
|
||||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
|
||||||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
`
|
|
||||||
: ""
|
|
||||||
}
|
|
||||||
${asset
|
|
||||||
? `<div data-icon="" class="">${asset}</div>`
|
|
||||||
: `<div data-icon="" class=""></div>`
|
|
||||||
}
|
|
||||||
<div
|
|
||||||
data-content=""
|
|
||||||
class="">
|
|
||||||
<div data-title="" class="">
|
|
||||||
${msg}
|
|
||||||
</div>
|
|
||||||
${opts.description
|
|
||||||
? `<div data-description="" class="">${opts.description}</div>`
|
|
||||||
: ""
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
`;
|
|
||||||
|
|
||||||
return {
|
|
||||||
id,
|
|
||||||
toast: {
|
|
||||||
target: document.querySelector(`[data-id="${id}"]`),
|
|
||||||
setTitle: function (msg, raw = false) {
|
|
||||||
const title = document.querySelector(`[data-sonner-toast][data-id=${id}] [data-title]`);
|
|
||||||
if (raw) title.innerHTML = msg;
|
|
||||||
else title.textContent = msg;
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
setIcon: function (icon) {
|
|
||||||
const ico = getIcon(icon) ?? '';
|
|
||||||
document.querySelector(`[data-sonner-toast][data-id=${id}] [data-icon]`).innerHTML = ico;
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
setDuration: function(duration) {
|
|
||||||
this.target.setAttribute('data-duration', duration);
|
|
||||||
registerRemoveTimeout(this.target);
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
dismiss: function() {
|
|
||||||
Sonner.remove(this.target.getAttribute("data-id"))
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers a new remove timeout for a specific element.
|
|
||||||
* The function sets a new timeout to remove the element from its parent after a delay.
|
|
||||||
* The timeout ensures that all CSS transitions complete before the element is removed.
|
|
||||||
* @param {Element} el - The element to register the remove timeout for.
|
|
||||||
* @param {number} lifetime - How long the toast will last for
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
function registerRemoveTimeout(el) {
|
|
||||||
if (!el.getAttribute("data-id"))
|
|
||||||
throw new Error('invalid target for removal');
|
|
||||||
|
|
||||||
const lifetime = el.getAttribute('data-duration') ?? TOAST_LIFETIME;
|
|
||||||
if (lifetime < 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Clear previous duration
|
|
||||||
if (el.getAttribute("data-remove-tid"))
|
|
||||||
window.clearTimeout(el.getAttribute("data-remove-tid"));
|
|
||||||
|
|
||||||
// Set new timeout
|
|
||||||
const tid = window.setTimeout(() => {
|
|
||||||
Sonner.remove(el.getAttribute("data-id"));
|
|
||||||
}, lifetime);
|
|
||||||
el.setAttribute("data-remove-tid", tid);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reinitializes the toaster and its children in the DOM.
|
|
||||||
* @returns {Element} - The ordered list element with the sonner-toaster-list id.
|
|
||||||
*/
|
|
||||||
function reinitializeToaster() {
|
|
||||||
const ol = document.getElementById("sonner-toaster-list");
|
|
||||||
if (!ol) return;
|
|
||||||
for (let i = 0; i < ol.children.length; i++) {
|
|
||||||
const el = ol.children[i];
|
|
||||||
const id = el.getAttribute("data-id");
|
|
||||||
registerSwipe(id);
|
|
||||||
refreshProperties();
|
|
||||||
registerRemoveTimeout(el);
|
|
||||||
}
|
|
||||||
return ol;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the toaster in the DOM.
|
|
||||||
* @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}
|
|
||||||
*/
|
|
||||||
function renderToaster({ closeButton, richColors, position }) {
|
|
||||||
const el = document.createElement("div");
|
|
||||||
document.body.appendChild(el);
|
|
||||||
position = position.split("-");
|
|
||||||
el.outerHTML = `
|
|
||||||
<section aria-label="Notifications alt+T" tabindex="-1">
|
|
||||||
<ol
|
|
||||||
dir="ltr"
|
|
||||||
tabindex="-1"
|
|
||||||
data-sonner-toaster="true"
|
|
||||||
data-theme="light"
|
|
||||||
data-close-button="${closeButton}"
|
|
||||||
data-rich-colors="${richColors}"
|
|
||||||
data-y-position="${position[0]}"
|
|
||||||
data-x-position="${position[1]}"
|
|
||||||
style="--front-toast-height: 0px; --offset: ${VIEWPORT_OFFSET}; --width: ${TOAST_WIDTH}px; --gap: ${GAP}px;"
|
|
||||||
id="sonner-toaster-list"
|
|
||||||
></ol>
|
|
||||||
</section>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads the Sonner styles in the DOM.
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
function loadSonnerStyles() {
|
|
||||||
var link = document.createElement("link");
|
|
||||||
link.href = "./sonner.css";
|
|
||||||
link.type = "text/css";
|
|
||||||
link.rel = "stylesheet";
|
|
||||||
link.media = "screen";
|
|
||||||
|
|
||||||
document.getElementsByTagName("head")[0].appendChild(link);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers mouse over events on a specific ordered list element in the DOM.
|
|
||||||
* @param {Element} ol - The ordered list element to register mouse over events on.
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
function registerMouseOver(ol) {
|
|
||||||
ol.addEventListener("mouseenter", function () {
|
|
||||||
for (let i = 0; i < ol.children.length; i++) {
|
|
||||||
const el = ol.children[i];
|
|
||||||
if (el.getAttribute("data-expanded") === "true") continue;
|
|
||||||
el.setAttribute("data-expanded", "true");
|
|
||||||
|
|
||||||
clearRemoveTimeout(el);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
ol.addEventListener("mouseleave", function () {
|
|
||||||
for (let i = 0; i < ol.children.length; i++) {
|
|
||||||
const el = ol.children[i];
|
|
||||||
if (el.getAttribute("data-expanded") === "false") continue;
|
|
||||||
el.setAttribute("data-expanded", "false");
|
|
||||||
|
|
||||||
registerRemoveTimeout(el);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers keyboard shortcuts for the ordered list element in the DOM.
|
|
||||||
* The function listens for the Alt+T key combination to expand or collapse the toasts.
|
|
||||||
* @param {Element} ol - The ordered list element to register keyboard shortcuts for.
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
function registerKeyboardShortcuts(ol) {
|
|
||||||
window.addEventListener("keydown", function (e) {
|
|
||||||
if (e.altKey && e.code === "KeyT") {
|
|
||||||
if (ol.children.length === 0) return;
|
|
||||||
const expanded = ol.children[0].getAttribute("data-expanded");
|
|
||||||
const newExpanded = expanded === "true" ? "false" : "true";
|
|
||||||
for (let i = 0; i < ol.children.length; i++) {
|
|
||||||
ol.children[i].setAttribute("data-expanded", newExpanded);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears the remove timeout for a specific element.
|
|
||||||
* @param {Element} el - The element to clear the remove timeout for.
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
function clearRemoveTimeout(el) {
|
|
||||||
const tid = el.getAttribute("data-remove-tid");
|
|
||||||
if (tid) window.clearTimeout(tid);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Refreshes the properties of the children of a specific list element in the DOM.
|
|
||||||
* The function iterates over each child of the list, skipping those marked as removed.
|
|
||||||
* For each remaining child, it updates various data attributes and CSS properties related to its index, visibility, offset, and z-index.
|
|
||||||
* The function also keeps track of the cumulative height of the elements processed so far to calculate the offset for each element.
|
|
||||||
*/
|
|
||||||
function refreshProperties() {
|
|
||||||
const list = document.getElementById("sonner-toaster-list");
|
|
||||||
let heightsBefore = 0;
|
|
||||||
let removed = 0;
|
|
||||||
for (let i = 0; i < list.children.length; i++) {
|
|
||||||
const el = list.children[i];
|
|
||||||
if (el.getAttribute("data-removed") === "true") {
|
|
||||||
removed++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const idx = i - removed;
|
|
||||||
el.setAttribute("data-index", idx);
|
|
||||||
el.setAttribute("data-front", idx === 0 ? "true" : "false");
|
|
||||||
el.setAttribute(
|
|
||||||
"data-visible",
|
|
||||||
idx < VISIBLE_TOASTS_AMOUNT ? "true" : "false",
|
|
||||||
);
|
|
||||||
el.style.setProperty("--index", idx);
|
|
||||||
el.style.setProperty("--toasts-before", idx);
|
|
||||||
el.style.setProperty("--offset", `${GAP * idx + heightsBefore}px`);
|
|
||||||
el.style.setProperty("--z-index", list.children.length - i);
|
|
||||||
heightsBefore += Number(el.getAttribute("data-initial-height"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers swipe events on an element with a specific id.
|
|
||||||
* The element is selected using the id and event listeners are added for pointerdown, pointerup, and pointermove events.
|
|
||||||
* The swipe gesture is calculated based on the movement of the pointer and the time taken for the swipe.
|
|
||||||
* If the swipe meets a certain threshold or velocity, the element is marked for removal.
|
|
||||||
* If the swipe does not meet the threshold, the swipe is reset.
|
|
||||||
* For more information on the swipe gesture, see the following article:
|
|
||||||
* https://emilkowal.ski/ui/building-a-toast-component
|
|
||||||
*
|
|
||||||
* @param {string} id - The data-id attribute of the element to register swipe events on.
|
|
||||||
*/
|
|
||||||
function registerSwipe(id) {
|
|
||||||
const el = document.querySelector(`[data-id="${id}"]`);
|
|
||||||
if (!el) return;
|
|
||||||
let dragStartTime = null;
|
|
||||||
let pointerStart = null;
|
|
||||||
const y = el.getAttribute("data-y-position");
|
|
||||||
el.addEventListener("pointerdown", function (event) {
|
|
||||||
dragStartTime = new Date();
|
|
||||||
event.target.setPointerCapture(event.pointerId);
|
|
||||||
if (event.target.tagName === "BUTTON") return;
|
|
||||||
el.setAttribute("data-swiping", "true");
|
|
||||||
pointerStart = { x: event.clientX, y: event.clientY };
|
|
||||||
});
|
|
||||||
el.addEventListener("pointerup", function (event) {
|
|
||||||
pointerStart = null;
|
|
||||||
const swipeAmount = Number(
|
|
||||||
el.style.getPropertyValue("--swipe-amount").replace("px", "") || 0,
|
|
||||||
);
|
|
||||||
const timeTaken = new Date().getTime() - dragStartTime.getTime();
|
|
||||||
const velocity = Math.abs(swipeAmount) / timeTaken;
|
|
||||||
|
|
||||||
// Remove only if threshold is met
|
|
||||||
if (Math.abs(swipeAmount) >= SWIPE_THRESHOLD || velocity > 0.11) {
|
|
||||||
el.setAttribute("data-swipe-out", "true");
|
|
||||||
Sonner.remove(id);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
el.style.setProperty("--swipe-amount", "0px");
|
|
||||||
el.setAttribute("data-swiping", "false");
|
|
||||||
});
|
|
||||||
|
|
||||||
el.addEventListener("pointermove", function (event) {
|
|
||||||
if (!pointerStart) return;
|
|
||||||
const yPosition = event.clientY - pointerStart.y;
|
|
||||||
const xPosition = event.clientX - pointerStart.x;
|
|
||||||
|
|
||||||
const clamp = y === "top" ? Math.min : Math.max;
|
|
||||||
const clampedY = clamp(0, yPosition);
|
|
||||||
const swipeStartThreshold = event.pointerType === "touch" ? 10 : 2;
|
|
||||||
const isAllowedToSwipe = Math.abs(clampedY) > swipeStartThreshold;
|
|
||||||
|
|
||||||
if (isAllowedToSwipe) {
|
|
||||||
el.style.setProperty("--swipe-amount", `${yPosition}px`);
|
|
||||||
} else if (Math.abs(xPosition) > swipeStartThreshold) {
|
|
||||||
// User is swiping in wrong direction so we disable swipe gesture
|
|
||||||
// for the current pointer down interaction
|
|
||||||
pointerStart = null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
@push('menu')
|
|
||||||
<x-menu-action tooltip="Einstellungen">
|
|
||||||
<svg class="w-5 h-5 mb-1 text-gray-500 dark:text-gray-400 group-hover:text-blue-600 dark:group-hover:text-blue-500" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
|
|
||||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 12.25V1m0 11.25a2.25 2.25 0 0 0 0 4.5m0-4.5a2.25 2.25 0 0 1 0 4.5M4 19v-2.25m6-13.5V1m0 2.25a2.25 2.25 0 0 0 0 4.5m0-4.5a2.25 2.25 0 0 1 0 4.5M10 19V7.75m6 4.5V1m0 11.25a2.25 2.25 0 1 0 0 4.5 2.25 2.25 0 0 0 0-4.5ZM16 19v-2"/>
|
|
||||||
</svg>
|
|
||||||
</x-menu-action>
|
|
||||||
<div class="flex items-center justify-center">
|
|
||||||
<x-drawer-trigger target="image-add" action="open">
|
|
||||||
<button data-tooltip-target="tooltip-new" type="button" class="inline-flex items-center justify-center w-10 h-10 font-medium bg-blue-600 rounded-full hover:bg-blue-700 group focus:ring-4 focus:ring-blue-300 focus:outline-none dark:focus:ring-blue-800">
|
|
||||||
<svg class="w-4 h-4 text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 18 18">
|
|
||||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 1v16M1 9h16"/>
|
|
||||||
</svg>
|
|
||||||
<span class="sr-only">Add Images</span>
|
|
||||||
</button>
|
|
||||||
</x-drawer-trigger>
|
|
||||||
</div>
|
|
||||||
<div id="tooltip-new" role="tooltip" class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-700">
|
|
||||||
Add images
|
|
||||||
<div class="tooltip-arrow" data-popper-arrow></div>
|
|
||||||
</div>
|
|
||||||
@endpush
|
|
||||||
|
|
||||||
<x-layout>
|
|
||||||
<x-hero-search></x-hero-search>
|
|
||||||
<h1 class="mb-4 mx-8 text-4xl font-extrabold leading-none tracking-tight text-gray-900 md:text-5xl lg:text-6xl dark:text-white">
|
|
||||||
{{ $album->name }}
|
|
||||||
</h1>
|
|
||||||
@if($album->mutations->count() > 0)
|
|
||||||
<livewire:drawer.album.progress-monitor :mutation="$album->mutations->first()"/>
|
|
||||||
@else
|
|
||||||
<livewire:image.grid :images="$album->images"></livewire:image.grid>
|
|
||||||
<x-drawer name="image-add" >
|
|
||||||
<x-slot:title>Neue Bilder zu {{ $album->name }} hinzufügen</x-slot:title>
|
|
||||||
<x-slot:content>
|
|
||||||
<livewire:drawer.album.addImage :album="$album"></livewire:drawer.album.addImage>
|
|
||||||
</x-slot:content>
|
|
||||||
</x-drawer>
|
|
||||||
@endif
|
|
||||||
</x-layout>
|
|
||||||
@@ -20,28 +20,15 @@
|
|||||||
</div>
|
</div>
|
||||||
@endpush
|
@endpush
|
||||||
|
|
||||||
<x-layouts.app>
|
<x-layouts.app :title="$category->name">
|
||||||
<x-hero-search></x-hero-search>
|
<div class="flex flex-wrap flex-row gap-4">
|
||||||
<h1 class="mb-4 mx-8 text-4xl font-extrabold leading-none tracking-tight text-gray-900 md:text-5xl lg:text-6xl dark:text-white">
|
<x-drawer-trigger target="album-add" action="open" class="relative rounded-lg cursor-pointer h-80 flex-grow flex items-center justify-center bg-gray-300 sm:w-96 dark:bg-gray-700 hover:opacity-50">
|
||||||
{{ $category->name }}
|
<svg class="w-24 h-24 text-gray-200 dark:text-gray-600" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||||
</h1>
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h14m-7 7V5"/>
|
||||||
<div class="m-8 flex flex-wrap flex-row gap-4">
|
|
||||||
@foreach ($albums as $album)
|
|
||||||
<figure class="relative rounded-lg cursor-pointer h-80 flex-grow overflow-hidden group">
|
|
||||||
<a href="{{ route('album.show', $album) }}" wire:navigate>
|
|
||||||
<img class="max-h-full min-w-full align-bottom object-cover"
|
|
||||||
src="{{ $album->thumbnail }}" alt="{{ $album->name }} Cover">
|
|
||||||
<figcaption class="absolute p-4 text-lg text-white top-1/2 bottom-0 bg-opacity-20 min-w-full bg-gradient-to-b from-transparent to-slate-900 flex flex-col-reverse">
|
|
||||||
<span class="z-10">{{ $album->name }}</span>
|
|
||||||
</figcaption>
|
|
||||||
<div class="opacity-0 z-0 group-hover:opacity-40 absolute inset-0 w-full h-full bg-black flex items-center justify-center transition-opacity">
|
|
||||||
<svg class="w-1/2 h-1/2 text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
|
|
||||||
<path stroke="currentColor" stroke-width="2" d="M21 12c0 1.2-4.03 6-9 6s-9-4.8-9-6c0-1.2 4.03-6 9-6s9 4.8 9 6Z"/>
|
|
||||||
<path stroke="currentColor" stroke-width="2" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"/>
|
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</x-drawer-trigger>
|
||||||
</a>
|
@foreach ($albums as $album)
|
||||||
</figure>
|
<x-album.element :album="$album"></x-album.element>
|
||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
<x-drawer name="album-add" >
|
<x-drawer name="album-add" >
|
||||||
|
|||||||
17
resources/views/components/album/element.blade.php
Normal file
17
resources/views/components/album/element.blade.php
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
@props(['album' => null])
|
||||||
|
|
||||||
|
<figure class="relative rounded-lg cursor-pointer h-80 flex-grow overflow-hidden group" {{ $attributes }}>
|
||||||
|
<a href="{{ route('album.show', $album) }}" wire:navigate>
|
||||||
|
<img class="max-h-full min-w-full align-bottom object-cover"
|
||||||
|
src="{{ $album->thumbnail }}" alt="{{ $album->name }} Cover">
|
||||||
|
<figcaption class="absolute p-4 text-xl text-white font-bold uppercase top-1/2 bottom-0 bg-opacity-20 min-w-full bg-gradient-to-b from-transparent to-black flex flex-col-reverse z-10">
|
||||||
|
<span class="z-1">{{ $album->name }}</span>
|
||||||
|
</figcaption>
|
||||||
|
<div class="opacity-0 z-0 group-hover:opacity-40 absolute inset-0 w-full h-full bg-black flex items-center justify-center transition-opacity">
|
||||||
|
<svg class="w-1/2 h-1/2 text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||||
|
<path stroke="currentColor" stroke-width="2" d="M21 12c0 1.2-4.03 6-9 6s-9-4.8-9-6c0-1.2 4.03-6 9-6s9 4.8 9 6Z"/>
|
||||||
|
<path stroke="currentColor" stroke-width="2" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</figure>
|
||||||
@@ -1,12 +1,14 @@
|
|||||||
@props(['active' => false ])
|
@props(['active' => false ])
|
||||||
|
|
||||||
@if($active)
|
@if($active)
|
||||||
<li {{ $attributes }} class="inline-block m-2 relative after:absolute after:left-0 after:bottom-1 after:border-yellow-pfadi after:-z-10 after:border-4 dark:after:opacity-80 after:w-full text-black dark:text-white">
|
<li {{ $attributes }} class="bg-yellow-pfadi text-gray-800 font-medium px-4 py-1 rounded-lg cursor-pointer">
|
||||||
|
<!--<li {{ $attributes }} class="inline-block m-2 relative after:absolute after:left-0 after:bottom-1 after:border-yellow-pfadi after:-z-10 after:border-4 dark:after:opacity-80 after:w-full text-black dark:text-white">-->
|
||||||
{{ $slot }}
|
{{ $slot }}
|
||||||
</li>
|
</li>
|
||||||
@else
|
@else
|
||||||
<li {{ $attributes }} class="inline-block m-2 dark:text-gray-100 dark:hover:text-white text-gray-400 relative hover:text-black after:w-0
|
<li {{ $attributes }} class="bg-gray-100 text-gray-800 font-medium px-4 py-1 rounded-lg dark:bg-gray-700 dark:text-gray-300 cursor-pointer">
|
||||||
cursor-pointer after:border-0 after:absolute after:left-0 after:bottom-1 dark:after:opacity-80 after:border-yellow-pfadi hover:after:border-4 hover:after:w-full after:transition-all after:duration-100 after:ease-out after:-z-10">
|
<!--<li {{ $attributes }} class="inline-block m-2 dark:text-gray-100 dark:hover:text-white text-gray-400 relative hover:text-black after:w-0
|
||||||
|
cursor-pointer after:border-0 after:absolute after:left-0 after:bottom-1 dark:after:opacity-80 after:border-yellow-pfadi hover:after:border-4 hover:after:w-full after:transition-all after:duration-100 after:ease-out after:-z-10">-->
|
||||||
{{ $slot }}
|
{{ $slot }}
|
||||||
</li>
|
</li>
|
||||||
@endif
|
@endif
|
||||||
|
|||||||
11
resources/views/components/category/element.blade.php
Normal file
11
resources/views/components/category/element.blade.php
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
@props(['category' => null])
|
||||||
|
|
||||||
|
<figure class="relative rounded-lg cursor-pointer h-80 flex-grow overflow-hidden" {{ $attributes }}>
|
||||||
|
<a href="{{ route('category.show', $category) }}">
|
||||||
|
<img class="max-h-full min-w-full align-bottom object-cover"
|
||||||
|
src="{{ url($category->cover) }}" alt="{{ $category->name }} Cover">
|
||||||
|
<figcaption class="absolute p-4 text-xl text-white font-bold uppercase top-1/2 bottom-0 bg-opacity-20 min-w-full bg-gradient-to-b from-transparent to-black flex flex-col-reverse">
|
||||||
|
<span>{{ $category->name }}</span>
|
||||||
|
</figcaption>
|
||||||
|
</a>
|
||||||
|
</figure>
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
<div @click="$dispatch('drawer-{{ $action }}-{{ $target }}')" x-on:touchstart="$dispatch('drawer-{{ $action }}-{{ $target }}')">
|
<div @click="$dispatch('drawer-{{ $action }}-{{ $target }}')" x-on:touchstart="$dispatch('drawer-{{ $action }}-{{ $target }}')" {{ $attributes }}>
|
||||||
{{ $slot }}
|
{{ $slot }}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template x-teleport="body">
|
<template x-teleport="body">
|
||||||
<div x-cloak
|
<div x-cloak
|
||||||
x-data="{ show: false }"
|
x-data="{ show: false }"
|
||||||
class="overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 justify-center items-center w-full md:inset-0 h-screen max-h-full flex backdrop-blur-md bg-white/30 dark:bg-gray-800/30"
|
class="overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 justify-center items-center w-full md:inset-0 h-screen max-h-full flex backdrop-blur-md bg-white/30 dark:bg-gray-800/30 z-30"
|
||||||
:class="{ 'translate-y-full': !show }"
|
:class="{ 'translate-y-full': !show }"
|
||||||
@click.self="$dispatch('drawer-close-{{ $name }}')"
|
@click.self="$dispatch('drawer-close-{{ $name }}')"
|
||||||
x-on:touchstart.self="$dispatch('drawer-close-{{ $name }}')"
|
x-on:touchstart.self="$dispatch('drawer-close-{{ $name }}')"
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
<div
|
<div
|
||||||
id="drawer-{{ $name }}"
|
id="drawer-{{ $name }}"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
class="fixed z-40 w-full overflow-y-auto bg-white border-t border-gray-200 rounded-t-lg dark:border-gray-700 dark:bg-gray-800 transition-all max-xl:left-0 max-xl:right-0 max-xl:bottom-0 max-h-full xl:m-auto xl:relative xl:p-4 xl:w-max xl:max-w-7xl xl:rounded-lg xl:border"
|
class="fixed z-40 w-full overflow-y-auto bg-white border-t border-gray-200 rounded-t-lg dark:border-gray-700 dark:bg-gray-800 transition-all max-xl:left-0 max-xl:right-0 max-xl:bottom-0 max-h-full xl:m-auto xl:relative xl:p-4 xl:w-max xl:max-w-7xl xl:rounded-lg xl:border z-50"
|
||||||
aria-labelledby="drawer-{{ $name }}"
|
aria-labelledby="drawer-{{ $name }}"
|
||||||
@drawer-open-{{ $name }}.window="$dispatch('menu-hide'); show = true"
|
@drawer-open-{{ $name }}.window="$dispatch('menu-hide'); show = true"
|
||||||
@drawer-close-{{ $name }}.window="show = false; $dispatch('menu-show')"
|
@drawer-close-{{ $name }}.window="show = false; $dispatch('menu-show')"
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
@persist('search')
|
|
||||||
<form class="flex h-[300px] relative overflow-hidden mb-8">
|
|
||||||
<img class="absolute object-cover" src="/placeholder.jpg" />
|
|
||||||
<div class="relative m-auto w-1/2">
|
|
||||||
<label for="default-search" class="mb-2 text-sm font-medium text-gray-900 sr-only dark:text-white">Suchen</label>
|
|
||||||
<div class="relative">
|
|
||||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3 pointer-events-none">
|
|
||||||
<svg class="w-4 h-4 text-gray-500 dark:text-gray-400" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
|
|
||||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<input type="search" id="default-search" class="block w-full p-4 ps-10 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="Nach Alben suchen" required />
|
|
||||||
<button type="submit" class="text-white absolute end-2.5 bottom-2.5 bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-4 py-2 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">Suchen</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
@endpersist
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html
|
|
||||||
lang="{{ str_replace('_', '-', app()->getLocale()) }}"
|
|
||||||
x-data="{ darkMode: $persist(false) }"
|
|
||||||
:class="{'dark': darkMode}"
|
|
||||||
x-init="
|
|
||||||
if (!('darkMode' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
|
||||||
localStorage.setItem('darkMode', JSON.stringify(true));
|
|
||||||
}
|
|
||||||
darkMode = JSON.parse(localStorage.getItem('darkMode'));
|
|
||||||
$watch('darkMode', value => localStorage.setItem('darkMode', JSON.stringify(value)))"
|
|
||||||
>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
@vite('resources/css/app.css')
|
|
||||||
@vite('resources/js/app.js')
|
|
||||||
<title>{{ $title ?? config('app.name') }}</title>
|
|
||||||
</head>
|
|
||||||
<body class="bg-white dark:bg-gray-800 min-h-screen flex flex-col">
|
|
||||||
@persist('theme-switcher')
|
|
||||||
<x-theme-switcher />
|
|
||||||
@endpersist
|
|
||||||
{{ $slot }}
|
|
||||||
|
|
||||||
<x-menu />
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,112 +1,22 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html
|
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||||
lang="{{ str_replace('_', '-', app()->getLocale()) }}"
|
<head>
|
||||||
x-data="{ darkMode: $persist(false) }"
|
|
||||||
:class="{'dark': darkMode}"
|
|
||||||
x-init="
|
|
||||||
if (!('darkMode' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
|
||||||
localStorage.setItem('darkMode', JSON.stringify(true));
|
|
||||||
}
|
|
||||||
darkMode = JSON.parse(localStorage.getItem('darkMode'));
|
|
||||||
$watch('darkMode', value => localStorage.setItem('darkMode', JSON.stringify(value)))"
|
|
||||||
>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<script>
|
||||||
|
const darkMode = localStorage.getItem('darkMode') || matchMedia('(prefers-color-scheme: light)').matches;
|
||||||
|
document.documentElement.dataset.dark = darkMode;
|
||||||
|
</script>
|
||||||
|
|
||||||
@vite('resources/css/app.css')
|
@vite('resources/css/app.css')
|
||||||
@vite('resources/js/app.js')
|
@vite('resources/js/app.js')
|
||||||
<title>{{ $title ?? config('app.name') }}</title>
|
<title>{{ $title ?? config('app.name') }}</title>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-white dark:bg-gray-800 min-h-screen flex flex-col">
|
|
||||||
@persist('theme-switcher')
|
<body class="bg-white dark:bg-gray-800 min-h-screen flex flex-col px-12 py-12 px-24">
|
||||||
<x-theme-switcher />
|
<livewire:menu :title="$title ?? ''"></livewire:menu>
|
||||||
@endpersist
|
|
||||||
{{ $slot }}
|
{{ $slot }}
|
||||||
|
</body>
|
||||||
|
|
||||||
<x-menu />
|
|
||||||
<style>
|
|
||||||
#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);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<!--
|
|
||||||
<div id="toaster" class="fixed right-4 top-4 z-50 group">
|
|
||||||
<div class="absolute flex items-center w-full max-w-xs p-4 mb-4 text-gray-500 bg-white rounded-lg shadow dark:text-gray-400 dark:bg-gray-800" data-first="true" style="z-index: 3; --index: 0;" role="alert">
|
|
||||||
<div class="inline-flex items-center justify-center flex-shrink-0 w-8 h-8 text-green-500 bg-green-100 rounded-lg dark:bg-green-800 dark:text-green-200">
|
|
||||||
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5Zm3.707 8.207-4 4a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L9 10.586l3.293-3.293a1 1 0 0 1 1.414 1.414Z"/>
|
|
||||||
</svg>
|
|
||||||
<span class="sr-only">Check icon</span>
|
|
||||||
</div>
|
|
||||||
<div class="ms-3 text-sm font-normal">Item moved successfully.Item moved successfully.Item moved successfully.Item moved successfully.</div>
|
|
||||||
<button type="button" class="ms-auto -mx-1.5 -my-1.5 bg-white text-gray-400 hover:text-gray-900 rounded-lg focus:ring-2 focus:ring-gray-300 p-1.5 hover:bg-gray-100 inline-flex items-center justify-center h-8 w-8 dark:text-gray-500 dark:hover:text-white dark:bg-gray-800 dark:hover:bg-gray-700" data-dismiss-target="#toast-success" aria-label="Close">
|
|
||||||
<span class="sr-only">Close</span>
|
|
||||||
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
|
|
||||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="absolute flex items-center w-full max-w-xs p-4 mb-4 text-gray-500 bg-white rounded-lg shadow dark:text-gray-400 dark:bg-gray-800" style="z-index: 2; --index: 1" role="alert">
|
|
||||||
<div class="inline-flex items-center justify-center flex-shrink-0 w-8 h-8 text-red-500 bg-red-100 rounded-lg dark:bg-red-800 dark:text-red-200">
|
|
||||||
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5Zm3.707 11.793a1 1 0 1 1-1.414 1.414L10 11.414l-2.293 2.293a1 1 0 0 1-1.414-1.414L8.586 10 6.293 7.707a1 1 0 0 1 1.414-1.414L10 8.586l2.293-2.293a1 1 0 0 1 1.414 1.414L11.414 10l2.293 2.293Z"/>
|
|
||||||
</svg>
|
|
||||||
<span class="sr-only">Error icon</span>
|
|
||||||
</div>
|
|
||||||
<div class="ms-3 text-sm font-normal">Item has been deleted.</div>
|
|
||||||
<button type="button" class="ms-auto -mx-1.5 -my-1.5 bg-white text-gray-400 hover:text-gray-900 rounded-lg focus:ring-2 focus:ring-gray-300 p-1.5 hover:bg-gray-100 inline-flex items-center justify-center h-8 w-8 dark:text-gray-500 dark:hover:text-white dark:bg-gray-800 dark:hover:bg-gray-700" data-dismiss-target="#toast-danger" aria-label="Close">
|
|
||||||
<span class="sr-only">Close</span>
|
|
||||||
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
|
|
||||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center w-full max-w-xs p-4 text-gray-500 bg-white rounded-lg shadow dark:text-gray-400 dark:bg-gray-800" style="z-index: 1; --index: 2" role="alert">
|
|
||||||
<div class="inline-flex items-center justify-center flex-shrink-0 w-8 h-8 text-orange-500 bg-orange-100 rounded-lg dark:bg-orange-700 dark:text-orange-200">
|
|
||||||
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM10 15a1 1 0 1 1 0-2 1 1 0 0 1 0 2Zm1-4a1 1 0 0 1-2 0V6a1 1 0 0 1 2 0v5Z"/>
|
|
||||||
</svg>
|
|
||||||
<span class="sr-only">Warning icon</span>
|
|
||||||
</div>
|
|
||||||
<div class="ms-3 text-sm font-normal">Improve password difficulty.</div>
|
|
||||||
<button type="button" class="ms-auto -mx-1.5 -my-1.5 bg-white text-gray-400 hover:text-gray-900 rounded-lg focus:ring-2 focus:ring-gray-300 p-1.5 hover:bg-gray-100 inline-flex items-center justify-center h-8 w-8 dark:text-gray-500 dark:hover:text-white dark:bg-gray-800 dark:hover:bg-gray-700" data-dismiss-target="#toast-warning" aria-label="Close">
|
|
||||||
<span class="sr-only">Close</span>
|
|
||||||
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
|
|
||||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>-->
|
|
||||||
</body>
|
|
||||||
</html>
|
</html>
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
@props(['tooltip' => 'Unknown' ])
|
|
||||||
|
|
||||||
<button type="button" class="inline-flex flex-col items-center justify-center px-5 hover:bg-gray-50 dark:hover:bg-gray-800 group first:rounded-s-full last:rounded-e-full">
|
|
||||||
{{ $slot }}
|
|
||||||
<span class="sr-only">{{ $tooltip }}</span>
|
|
||||||
<div role="tooltip" class="absolute z-10 group-hover:visible group-hover:opacity-100 invisible inline-block px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-sm opacity-0 dark:bg-gray-700 -translate-y-full origin-top-left -mt-2 top-0">
|
|
||||||
{{ $tooltip }}
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
<div
|
|
||||||
class="fixed z-50 w-11/12 h-16 max-w-lg -translate-x-1/2 bg-white border border-gray-200 rounded-full left-1/2 dark:bg-gray-700 dark:border-gray-600 transition-all"
|
|
||||||
x-data="{ visible: true }"
|
|
||||||
@menu-hide.window="visible = false"
|
|
||||||
@menu-show.window="visible = true"
|
|
||||||
:class="{ '-bottom-16': !visible, 'bottom-4': visible }"
|
|
||||||
x-cloak
|
|
||||||
>
|
|
||||||
<div class="grid grid-flow-col h-full max-w-lg mx-auto">
|
|
||||||
<x-menu-action tooltip="Startseite">
|
|
||||||
<svg class="w-5 h-5 mb-1 text-gray-500 dark:text-gray-400 group-hover:text-blue-600 dark:group-hover:text-blue-500" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path d="m19.707 9.293-2-2-7-7a1 1 0 0 0-1.414 0l-7 7-2 2a1 1 0 0 0 1.414 1.414L2 10.414V18a2 2 0 0 0 2 2h3a1 1 0 0 0 1-1v-4a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v4a1 1 0 0 0 1 1h3a2 2 0 0 0 2-2v-7.586l.293.293a1 1 0 0 0 1.414-1.414Z"/>
|
|
||||||
</svg>
|
|
||||||
</x-menu-action>
|
|
||||||
@stack('menu')
|
|
||||||
<x-menu-action tooltip="Profil">
|
|
||||||
<svg class="w-5 h-5 mb-1 text-gray-500 dark:text-gray-400 group-hover:text-blue-600 dark:group-hover:text-blue-500" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path d="M10 0a10 10 0 1 0 10 10A10.011 10.011 0 0 0 10 0Zm0 5a3 3 0 1 1 0 6 3 3 0 0 1 0-6Zm0 13a8.949 8.949 0 0 1-4.951-1.488A3.987 3.987 0 0 1 9 13h2a3.987 3.987 0 0 1 3.951 3.512A8.949 8.949 0 0 1 10 18Z"/>
|
|
||||||
</svg>
|
|
||||||
</x-menu-action>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
<button x-cloak id="theme-toggle" data-tooltip-target="tooltip-toggle" type="button"
|
|
||||||
class="text-gray-500 inline-flex items-center justify-center dark:text-gray-400 hover:bg-gray-100 w-10 h-10 dark:hover:bg-gray-700 focus:outline-none rounded-lg text-sm p-2.5 right-0 absolute z-10"
|
|
||||||
@click="darkMode = !darkMode"
|
|
||||||
>
|
|
||||||
<svg x-show="!darkMode" id="theme-toggle-dark-icon" class="w-8 h-8" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 18 20">
|
|
||||||
<path d="M17.8 13.75a1 1 0 0 0-.859-.5A7.488 7.488 0 0 1 10.52 2a1 1 0 0 0 0-.969A1.035 1.035 0 0 0 9.687.5h-.113a9.5 9.5 0 1 0 8.222 14.247 1 1 0 0 0 .004-.997Z"></path>
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
<svg x-show="darkMode" id="theme-toggle-light-icon" class="w-8 h-8" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
|
|
||||||
<path d="M10 15a5 5 0 1 0 0-10 5 5 0 0 0 0 10Zm0-11a1 1 0 0 0 1-1V1a1 1 0 0 0-2 0v2a1 1 0 0 0 1 1Zm0 12a1 1 0 0 0-1 1v2a1 1 0 1 0 2 0v-2a1 1 0 0 0-1-1ZM4.343 5.757a1 1 0 0 0 1.414-1.414L4.343 2.929a1 1 0 0 0-1.414 1.414l1.414 1.414Zm11.314 8.486a1 1 0 0 0-1.414 1.414l1.414 1.414a1 1 0 0 0 1.414-1.414l-1.414-1.414ZM4 10a1 1 0 0 0-1-1H1a1 1 0 0 0 0 2h2a1 1 0 0 0 1-1Zm15-1h-2a1 1 0 1 0 0 2h2a1 1 0 0 0 0-2ZM4.343 14.243l-1.414 1.414a1 1 0 1 0 1.414 1.414l1.414-1.414a1 1 0 0 0-1.414-1.414ZM14.95 6.05a1 1 0 0 0 .707-.293l1.414-1.414a1 1 0 1 0-1.414-1.414l-1.414 1.414a1 1 0 0 0 .707 1.707Z"></path>
|
|
||||||
</svg>
|
|
||||||
<span x-show="!darkMode" class="sr-only">Dunkle Theme aktivieren</span>
|
|
||||||
<span x-show="darkMode" class="sr-only">Helle Theme aktivieren</span>
|
|
||||||
</button>
|
|
||||||
@@ -1,35 +1,20 @@
|
|||||||
@push('menu')
|
<div>
|
||||||
<x-menu-action tooltip="Einstellungen">
|
<div id="album">
|
||||||
<svg class="w-5 h-5 mb-1 text-gray-500 dark:text-gray-400 group-hover:text-blue-600 dark:group-hover:text-blue-500" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
|
<div class="flex flex-wrap flex-row gap-4" @if($album->hasProcessingMedia) wire:poll @endif>
|
||||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 12.25V1m0 11.25a2.25 2.25 0 0 0 0 4.5m0-4.5a2.25 2.25 0 0 1 0 4.5M4 19v-2.25m6-13.5V1m0 2.25a2.25 2.25 0 0 0 0 4.5m0-4.5a2.25 2.25 0 0 1 0 4.5M10 19V7.75m6 4.5V1m0 11.25a2.25 2.25 0 1 0 0 4.5 2.25 2.25 0 0 0 0-4.5ZM16 19v-2"/>
|
<div class="relative rounded-lg overflow-hidden cursor-pointer h-80 flex-grow flex items-center justify-center sm:w-96 flex flex-col gap-1">
|
||||||
|
@if(!$album->hasProcessingMedia)
|
||||||
|
<x-drawer-trigger target="image-add" action="open" class="flex-grow w-full text-center bg-gray-300 dark:bg-gray-700 hover:opacity-80 flex items-center justify-center transition-opacity">
|
||||||
|
<svg class="w-24 h-24 text-gray-200 dark:text-gray-600" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||||
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h14m-7 7V5"/>
|
||||||
</svg>
|
</svg>
|
||||||
</x-menu-action>
|
</x-drawer-trigger>
|
||||||
<div class="flex items-center justify-center">
|
@endif
|
||||||
<x-drawer-trigger target="image-add" action="open">
|
<x-drawer-trigger target="image-add" action="open" class="flex-grow w-full text-center bg-gray-300 dark:bg-gray-700 hover:opacity-80 flex items-center justify-center transition-opacity">
|
||||||
<button data-tooltip-target="tooltip-new" type="button" class="inline-flex items-center justify-center w-10 h-10 font-medium bg-blue-600 rounded-full hover:bg-blue-700 group focus:ring-4 focus:ring-blue-300 focus:outline-none dark:focus:ring-blue-800">
|
<svg class="w-24 h-24 text-gray-200 dark:text-gray-600" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||||
<svg class="w-4 h-4 text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 18 18">
|
<path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="M20 6H10m0 0a2 2 0 1 0-4 0m4 0a2 2 0 1 1-4 0m0 0H4m16 6h-2m0 0a2 2 0 1 0-4 0m4 0a2 2 0 1 1-4 0m0 0H4m16 6H10m0 0a2 2 0 1 0-4 0m4 0a2 2 0 1 1-4 0m0 0H4"/>
|
||||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 1v16M1 9h16"/>
|
|
||||||
</svg>
|
</svg>
|
||||||
<span class="sr-only">Add Images</span>
|
|
||||||
</button>
|
|
||||||
</x-drawer-trigger>
|
</x-drawer-trigger>
|
||||||
</div>
|
</div>
|
||||||
@if(!$album->hasProcessingMedia)
|
|
||||||
<div id="tooltip-new" role="tooltip" class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-700">
|
|
||||||
Add images
|
|
||||||
<div class="tooltip-arrow" data-popper-arrow></div>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
@endpush
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<x-hero-search></x-hero-search>
|
|
||||||
<h1 class="mb-4 mx-8 text-4xl font-extrabold leading-none tracking-tight text-gray-900 md:text-5xl lg:text-6xl dark:text-white">
|
|
||||||
{{ $album->name }}
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<div id="album">
|
|
||||||
<div class="m-8 flex flex-wrap flex-row gap-4" @if($album->hasProcessingMedia) wire:poll @endif>
|
|
||||||
@foreach ($images as $image)
|
@foreach ($images as $image)
|
||||||
@if($image->isProcessing)
|
@if($image->isProcessing)
|
||||||
<div wire:transition wire:key="placeholder_{{ $image->id }}" role="status" class="flex items-center justify-center h-80 flex-grow bg-gray-300 rounded-lg animate-pulse dark:bg-gray-700 min-w-80">
|
<div wire:transition wire:key="placeholder_{{ $image->id }}" role="status" class="flex items-center justify-center h-80 flex-grow bg-gray-300 rounded-lg animate-pulse dark:bg-gray-700 min-w-80">
|
||||||
@@ -48,6 +33,7 @@
|
|||||||
data-pswp-download="{{ $image->getDownload() }}"
|
data-pswp-download="{{ $image->getDownload() }}"
|
||||||
data-pswp-height="{{ $image->lightbox['height'] }}"
|
data-pswp-height="{{ $image->lightbox['height'] }}"
|
||||||
data-cropped="true"
|
data-cropped="true"
|
||||||
|
data-is-cover="{{ $image->isCover ? 'true' : 'false' }}"
|
||||||
data-id="{{ $image->id }}"
|
data-id="{{ $image->id }}"
|
||||||
wire:key="image_{{ $image->id }}"
|
wire:key="image_{{ $image->id }}"
|
||||||
wire:transition
|
wire:transition
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<div class="p-2 mt-8">
|
<div>
|
||||||
<div class="text-center">
|
<div class="mb-8">
|
||||||
<ul class="uppercase font-medium">
|
<ul class="uppercase font-medium flex gap-12 justify-center">
|
||||||
<x-category-filter-pill wire:click="setFilter(-1)" :active="$filter == null">
|
<x-category-filter-pill wire:click="setFilter(-1)" :active="$filter == null">
|
||||||
Alle
|
Alle
|
||||||
</x-category-filter-pill>
|
</x-category-filter-pill>
|
||||||
@@ -13,19 +13,9 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="m-8 flex flex-wrap flex-row gap-4">
|
<div class="flex flex-wrap flex-row gap-4 transition-opacity" wire:loading.class="opacity-0">
|
||||||
@foreach ($categories as $category)
|
@foreach ($this->categories as $category)
|
||||||
@if($filter == null || $category->tags->contains($filter))
|
<x-category.element :category="$category" wire:key="{{ $category->id }}"></x-category.element>
|
||||||
<figure wire:transition wire:key="{{ $category->id }}" class="relative rounded-lg cursor-pointer h-80 flex-grow overflow-hidden">
|
|
||||||
<a href="{{ route('category.show', $category) }}">
|
|
||||||
<img class="max-h-full min-w-full align-bottom object-cover"
|
|
||||||
src="{{ url($category->cover) }}" alt="{{ $category->name }} Cover">
|
|
||||||
<figcaption class="absolute p-4 text-lg text-white top-1/2 bottom-0 bg-opacity-20 min-w-full bg-gradient-to-b from-transparent to-slate-900 flex flex-col-reverse">
|
|
||||||
<span>{{ $category->name }}</span>
|
|
||||||
</figcaption>
|
|
||||||
</a>
|
|
||||||
</figure>
|
|
||||||
@endif
|
|
||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
43
resources/views/livewire/menu.blade.php
Normal file
43
resources/views/livewire/menu.blade.php
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<div class="w-full flex flex-row items-center justify-between mb-12">
|
||||||
|
<div class="flex flex-row gap-4">
|
||||||
|
<a href="javascript:history.back();">
|
||||||
|
<svg class="w-8 h-8 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||||
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m15 19-7-7 7-7"/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
<a href="/" wire:navigate>
|
||||||
|
<svg class="w-8 h-8 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||||
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m4 12 8-8 8 8M6 10.5V19a1 1 0 0 0 1 1h3v-3a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v3h3a1 1 0 0 0 1-1v-8.5"/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<h1 class="text-4xl font-extrabold leading-none tracking-tight text-gray-900 md:text-5xl lg:text-6xl dark:text-white inline-block relative after:absolute after:left-0 after:bottom-1 after:border-yellow-pfadi after:-z-10 after:border-4 dark:after:opacity-80 after:w-full text-black dark:text-white">
|
||||||
|
{{ $title }}
|
||||||
|
</h1>
|
||||||
|
<div class="flex flex-row gap-4">
|
||||||
|
<form class="relative text-right w-48">
|
||||||
|
<input type="search" name="" placeholder="Search for..." class="peer absolute -top-1 right-0 block p-2.5 w-8 focus:w-48 text-sm text-gray-900 bg-gray-50 rounded-lg focus:ring-0 focus:border-2 focus:border-yellow-pfadi dark:bg-gray-700 dark:border-s-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white opacity-0 focus:opacity-100 transition-all">
|
||||||
|
|
||||||
|
<button type="submit" class="absolute top-0 right-0 pointer-events-none transition-colors peer-focus:opacity-0">
|
||||||
|
<svg class="w-8 h-8 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
|
||||||
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z"/>
|
||||||
|
</svg>
|
||||||
|
<span class="sr-only">Search</span>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<div @click="darkMode = !darkMode"
|
||||||
|
x-data="{darkMode: $persist(matchMedia('(prefers-color-scheme: dark)').matches).as('darkMode')}"
|
||||||
|
x-init="$watch('darkMode', darkMode => document.documentElement.dataset.dark = darkMode)"
|
||||||
|
class="w-8 h-8">
|
||||||
|
<svg x-show="!darkMode" class="w-8 h-8 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" x-cloak>
|
||||||
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 21a9 9 0 0 1-.5-17.986V3c-.354.966-.5 1.911-.5 3a9 9 0 0 0 9 9c.239 0 .254.018.488 0A9.004 9.004 0 0 1 12 21Z"/>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<svg x-show="darkMode" class="w-8 h-8 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" x-cloak>
|
||||||
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 5V3m0 18v-2M7.05 7.05 5.636 5.636m12.728 12.728L16.95 16.95M5 12H3m18 0h-2M7.05 16.95l-1.414 1.414M18.364 5.636 16.95 7.05M16 12a4 4 0 1 1-8 0 4 4 0 0 1 8 0Z"/>
|
||||||
|
</svg>
|
||||||
|
<span x-show="!darkMode" class="sr-only">Dunkle Theme aktivieren</span>
|
||||||
|
<span x-show="darkMode" class="sr-only">Helle Theme aktivieren</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
<x-layouts.app>
|
<x-layouts.app title="Pfadi Säuliamt Galerie">
|
||||||
<x-hero-search></x-hero-search>
|
|
||||||
|
|
||||||
<livewire:category-filter />
|
<livewire:category-filter />
|
||||||
</x-layouts.app>
|
</x-layouts.app>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
export default {
|
export default {
|
||||||
darkMode: 'selector',
|
darkMode: ['selector', '[data-dark="true"]'],
|
||||||
content: [
|
content: [
|
||||||
"./resources/**/*.blade.php",
|
"./resources/**/*.blade.php",
|
||||||
"./resources/**/*.js",
|
"./resources/**/*.js",
|
||||||
@@ -10,6 +10,7 @@ export default {
|
|||||||
extend: {
|
extend: {
|
||||||
colors: {
|
colors: {
|
||||||
'yellow-pfadi': '#ffdd00',
|
'yellow-pfadi': '#ffdd00',
|
||||||
|
'yellow-pfadi-dark': '#f2d40d',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user