This commit is contained in:
2024-06-01 03:10:30 +02:00
parent 1d41dea9fa
commit 26551964b1
25 changed files with 1808 additions and 341 deletions

View File

@@ -62,3 +62,15 @@ AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false AWS_USE_PATH_STYLE_ENDPOINT=false
VITE_APP_NAME="${APP_NAME}" VITE_APP_NAME="${APP_NAME}"
REVERB_APP_ID=937919
REVERB_APP_KEY=5ljogsvykrp9zocjl63r
REVERB_APP_SECRET=urnthocgauio2cfzdlu7
REVERB_HOST="localhost"
REVERB_PORT=8080
REVERB_SCHEME=http
VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
VITE_REVERB_HOST="${REVERB_HOST}"
VITE_REVERB_PORT="${REVERB_PORT}"
VITE_REVERB_SCHEME="${REVERB_SCHEME}"

View File

@@ -9,6 +9,12 @@
## About Laravel ## About Laravel
Requires the php gd extensions
Todo:
Cleanup deleted Photos Job
Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as: Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as:
- [Simple, fast routing engine](https://laravel.com/docs/routing). - [Simple, fast routing engine](https://laravel.com/docs/routing).

View File

@@ -39,7 +39,7 @@ class ImageController extends Controller
*/ */
public function show(Image $image) : BinaryFileResponse public function show(Image $image) : BinaryFileResponse
{ {
return response()->file(Storage::disk('images')->path($image->album->id . '/original/' . $image->id)); return response()->file(Storage::disk('images')->path($image->album->id . '/thumbnail/' . $image->id . '.avif'));
} }
/** /**

View File

@@ -2,6 +2,15 @@
namespace App\Livewire\Drawer\Album; namespace App\Livewire\Drawer\Album;
use App\Models\BatchMutation;
use App\Services\MediaImporter;
use App\Models\Album;
use Illuminate\Contracts\View\Factory;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Facades\Bus;
use Livewire\Attributes\Locked;
use Livewire\Attributes\On;
use Livewire\Attributes\Validate;
use Livewire\Component; use Livewire\Component;
use Livewire\Features\SupportFileUploads\WithFileUploads; use Livewire\Features\SupportFileUploads\WithFileUploads;
@@ -9,9 +18,37 @@ class AddImage extends Component
{ {
use WithFileUploads; use WithFileUploads;
#[Locked]
public Album $album;
#[Locked]
public bool $processing = false;
#[Validate(['media.*' => 'image|max:8192'])] // max:8MB
public $media = []; public $media = [];
public function render() public function save(MediaImporter $importer) : void {
$this->validate();
$jobs = array_map(fn($file) => $importer->import($file, $this->album), $this->media);
$batch = Bus::batch($jobs)
->name('Media import in ' . $this->album->name)
->allowFailures()
->dispatch();
BatchMutation::create([
'album_id' => $this->album->id,
'batch_id' => $batch->id,
]);
$this->redirect(route('album.show', $this->album), navigate: true);
}
public function mount(Album $album) : void {
$this->album = $album;
}
public function render() : View|Factory
{ {
return view('livewire.drawer.album.add-image'); return view('livewire.drawer.album.add-image');
} }

View File

@@ -20,6 +20,10 @@ class Album extends Model
return $this->hasMany(Image::class); return $this->hasMany(Image::class);
} }
public function mutations() : HasMany {
return $this->hasMany(BatchMutation::class);
}
public function media() : Collection { public function media() : Collection {
return $this->images; return $this->images;
} }

View File

@@ -11,6 +11,22 @@ class Image extends Model implements HasThumbnail
{ {
use HasFactory; use HasFactory;
/**
* The model's default values for attributes.
*
* @var array
*/
protected $attributes = [
'isCover' => false,
];
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = ['album_id'];
public function album(): BelongsTo public function album(): BelongsTo
{ {
return $this->belongsTo(Album::class); return $this->belongsTo(Album::class);

View File

@@ -11,7 +11,10 @@ class AppServiceProvider extends ServiceProvider
*/ */
public function register(): void public function register(): void
{ {
// if ($this->app->environment('local')) {
$this->app->register(\Laravel\Telescope\TelescopeServiceProvider::class);
$this->app->register(TelescopeServiceProvider::class);
}
} }
/** /**

View File

@@ -8,6 +8,7 @@ return Application::configure(basePath: dirname(__DIR__))
->withRouting( ->withRouting(
web: __DIR__.'/../routes/web.php', web: __DIR__.'/../routes/web.php',
commands: __DIR__.'/../routes/console.php', commands: __DIR__.'/../routes/console.php',
channels: __DIR__.'/../routes/channels.php',
health: '/up', health: '/up',
) )
->withMiddleware(function (Middleware $middleware) { ->withMiddleware(function (Middleware $middleware) {

View File

@@ -2,6 +2,7 @@
return [ return [
App\Providers\AppServiceProvider::class, App\Providers\AppServiceProvider::class,
App\Providers\HorizonServiceProvider::class,
App\Providers\LivewireAssetProvider::class, App\Providers\LivewireAssetProvider::class,
App\Providers\TelescopeServiceProvider::class, App\Providers\MediaImporterServiceProvider::class,
]; ];

View File

@@ -6,7 +6,10 @@
"license": "MIT", "license": "MIT",
"require": { "require": {
"php": "^8.2", "php": "^8.2",
"intervention/image-laravel": "^1.2",
"laravel/framework": "^11.0", "laravel/framework": "^11.0",
"laravel/horizon": "^5.24",
"laravel/reverb": "@beta",
"laravel/telescope": "^5.0", "laravel/telescope": "^5.0",
"laravel/tinker": "^2.9", "laravel/tinker": "^2.9",
"livewire/livewire": "^3.4" "livewire/livewire": "^3.4"
@@ -51,7 +54,9 @@
}, },
"extra": { "extra": {
"laravel": { "laravel": {
"dont-discover": [] "dont-discover": [
"laravel/telescope"
]
} }
}, },
"config": { "config": {

1867
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -39,7 +39,7 @@ return [
'images' => [ 'images' => [
'driver' => 'local', 'driver' => 'local',
'root' => storage_path('app/images'), 'root' => storage_path('app/images'),
'throw' => false, 'throw' => true,
], ],
'public' => [ 'public' => [

View File

@@ -7,7 +7,9 @@ use App\Models\Image;
use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str; use Intervention\Image\Laravel\Facades\Image as InterventionImage;
use \App\Importers\Image\Jobs\GenerateThumbnail;
use \App\Importers\Image\Jobs\GenerateFullscreen;
/** /**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Image> * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Image>
@@ -28,7 +30,11 @@ class ImageFactory extends Factory
$height = rand(2000, 4000); $height = rand(2000, 4000);
$width = rand(2000, 4000); $width = rand(2000, 4000);
$image_content = Http::get("https://picsum.photos/{$width}/{$height}")->body(); $image_content = Http::get("https://picsum.photos/{$width}/{$height}")->body();
Storage::disk('images')->put($image->album->id . '/original/' . $image->id, $image_content); $encoded = InterventionImage::read($image_content)->toAvif(config('gallery.image.quality'));
Storage::disk('images')->put($image->album->id . '/original/' . $image->id . '.avif', $encoded);
GenerateThumbnail::dispatch($image);
GenerateFullscreen::dispatch($image);
}); });
} }

View File

@@ -25,6 +25,6 @@ return new class extends Migration
*/ */
public function down(): void public function down(): void
{ {
Schema::dropIfExists('categories_tags'); Schema::dropIfExists('category_tag');
} }
}; };

26
package-lock.json generated
View File

@@ -16,8 +16,10 @@
"@tailwindcss/forms": "^0.5.7", "@tailwindcss/forms": "^0.5.7",
"autoprefixer": "^10.4.19", "autoprefixer": "^10.4.19",
"axios": "^1.6.4", "axios": "^1.6.4",
"laravel-echo": "^1.16.1",
"laravel-vite-plugin": "^1.0", "laravel-vite-plugin": "^1.0",
"postcss": "^8.4.38", "postcss": "^8.4.38",
"pusher-js": "^8.4.0-rc2",
"tailwindcss": "^3.4.3", "tailwindcss": "^3.4.3",
"vite": "^5.0" "vite": "^5.0"
} }
@@ -1450,6 +1452,15 @@
"jiti": "bin/jiti.js" "jiti": "bin/jiti.js"
} }
}, },
"node_modules/laravel-echo": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/laravel-echo/-/laravel-echo-1.16.1.tgz",
"integrity": "sha512-++Ylb6M3ariC9Rk5WE5gZjj6wcEV5kvLF8b+geJ5/rRIfdoOA+eG6b9qJPrarMD9rY28Apx+l3eelIrCc2skVg==",
"dev": true,
"engines": {
"node": ">=10"
}
},
"node_modules/laravel-vite-plugin": { "node_modules/laravel-vite-plugin": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-1.0.2.tgz", "resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-1.0.2.tgz",
@@ -1862,6 +1873,15 @@
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"dev": true "dev": true
}, },
"node_modules/pusher-js": {
"version": "8.4.0-rc2",
"resolved": "https://registry.npmjs.org/pusher-js/-/pusher-js-8.4.0-rc2.tgz",
"integrity": "sha512-d87GjOEEl9QgO5BWmViSqW0LOzPvybvX6WA9zLUstNdB57jVJuR27zHkRnrav2a3+zAMlHbP2Og8wug+rG8T+g==",
"dev": true,
"dependencies": {
"tweetnacl": "^1.0.3"
}
},
"node_modules/queue-microtask": { "node_modules/queue-microtask": {
"version": "1.2.3", "version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -2236,6 +2256,12 @@
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
"dev": true "dev": true
}, },
"node_modules/tweetnacl": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz",
"integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==",
"dev": true
},
"node_modules/update-browserslist-db": { "node_modules/update-browserslist-db": {
"version": "1.0.13", "version": "1.0.13",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",

View File

@@ -19,7 +19,6 @@
"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",
"filepond-plugin-image-exif-orientation": "^1.0.11", "filepond-plugin-image-exif-orientation": "^1.0.11",
"filepond-plugin-image-preview": "^4.6.12", "filepond-plugin-image-preview": "^4.6.12"
"filepond-plugin-image-transform": "^3.8.7"
} }
} }

View File

@@ -29,4 +29,5 @@
.filepond--drop-label { .filepond--drop-label {
@apply text-gray-900 dark:text-white; @apply text-gray-900 dark:text-white;
} }

View File

@@ -4,13 +4,11 @@ import FilePondPluginImageExifOrientation from 'filepond-plugin-image-exif-orien
import FilePondPluginImagePreview from 'filepond-plugin-image-preview'; import FilePondPluginImagePreview from 'filepond-plugin-image-preview';
import FilePondPluginFileValidateSize from 'filepond-plugin-file-validate-size'; import FilePondPluginFileValidateSize from 'filepond-plugin-file-validate-size';
import FilePondPluginFileValidateType from 'filepond-plugin-file-validate-type'; import FilePondPluginFileValidateType from 'filepond-plugin-file-validate-type';
import FilePondPluginImageTransform from 'filepond-plugin-image-transform';
FilePond.registerPlugin(FilePondPluginImageExifOrientation); FilePond.registerPlugin(FilePondPluginImageExifOrientation);
FilePond.registerPlugin(FilePondPluginImagePreview); FilePond.registerPlugin(FilePondPluginImagePreview);
FilePond.registerPlugin(FilePondPluginFileValidateSize); FilePond.registerPlugin(FilePondPluginFileValidateSize);
FilePond.registerPlugin(FilePondPluginFileValidateType); FilePond.registerPlugin(FilePondPluginFileValidateType);
FilePond.registerPlugin(FilePondPluginImageTransform);
window.FilePond = FilePond; window.FilePond = FilePond;
@@ -18,8 +16,7 @@ document.addEventListener('alpine:init', () => {
Alpine.store('uploader', { Alpine.store('uploader', {
states: {}, states: {},
setState(state, value) { setState(state, value) {
console.log(state, value);
this.states[state] = value; this.states[state] = value;
}, },
}) });
}); });

View File

@@ -25,24 +25,28 @@
<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"> <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 }} {{ $album->name }}
</h1> </h1>
<div class="m-8 flex flex-wrap flex-row gap-4"> @if($album->mutations->count() > 0)
@foreach ($album->images as $image) <livewire:drawer.album.progress-monitor :mutation="$album->mutations->first()"/>
<figure class="relative group rounded-lg cursor-pointer h-80 flex-grow overflow-hidden"> @else
<img class="max-h-full min-w-full align-bottom object-cover" <div class="m-8 flex flex-wrap flex-row gap-4">
src="{{ $image->getThumbnail() }}" alt="{{ $album->name }} Bild"> @foreach ($album->images as $image)
<div class="opacity-0 group-hover:opacity-40 absolute inset-0 w-full h-full bg-black flex items-center justify-center transition-opacity"> <figure class="relative group rounded-lg cursor-pointer h-80 flex-grow overflow-hidden">
<svg class="w-1/2 h-1/2 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"> <img class="max-h-full min-w-full align-bottom object-cover"
<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"/> src="{{ $image->getThumbnail() }}" alt="{{ $album->name }} Bild">
<path stroke="currentColor" stroke-width="2" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"/> <div class="opacity-0 group-hover:opacity-40 absolute inset-0 w-full h-full bg-black flex items-center justify-center transition-opacity">
</svg> <svg class="w-1/2 h-1/2 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
</div> <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"/>
</figure> <path stroke="currentColor" stroke-width="2" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"/>
@endforeach </svg>
</div> </div>
<x-drawer name="image-add" > </figure>
<x-slot:title>Neue Bilder zu {{ $album->name }} hinzufügen</x-slot:title> @endforeach
<x-slot:content> </div>
<livewire:drawer.album.addImage></livewire:drawer.album.addImage> <x-drawer name="image-add" >
</x-slot:content> <x-slot:title>Neue Bilder zu {{ $album->name }} hinzufügen</x-slot:title>
</x-drawer> <x-slot:content>
<livewire:drawer.album.addImage :album="$album"></livewire:drawer.album.addImage>
</x-slot:content>
</x-drawer>
@endif
</x-layout> </x-layout>

View File

@@ -3,8 +3,8 @@
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"
:class="{ 'translate-y-full': !show }" :class="{ 'translate-y-full': !show }"
@click="$dispatch('drawer-close-{{ $name }}')" @click.self="$dispatch('drawer-close-{{ $name }}')"
x-on:touchstart="$dispatch('drawer-close-{{ $name }}')" x-on:touchstart.self="$dispatch('drawer-close-{{ $name }}')"
> >
<div <div
id="drawer-{{ $name }}" id="drawer-{{ $name }}"
@@ -16,16 +16,21 @@
:class="{ 'translate-y-full': !show, 'xl:-bottom-full': !show }" :class="{ 'translate-y-full': !show, 'xl:-bottom-full': !show }"
> >
<div <div
class="p-4 cursor-pointer max-xl:hover:bg-gray-50 max-xl:dark:hover:bg-gray-700 xl:border-b dark:border-gray-600 xl:flex xl:items-center xl:justify-between xl:p-4" class="p-4 cursor-pointer max-xl:hover:bg-gray-50 max-xl:dark:hover:bg-gray-700 xl:border-b dark:border-gray-600 xl:flex xl:items-center xl:justify-between xl:p-4">
@click="$dispatch('drawer-close-{{ $name }}')" <span
x-on:touchstart="$dispatch('drawer-close-{{ $name }}')" class="xl:hidden absolute w-8 h-1 -translate-x-1/2 bg-gray-300 rounded-lg top-3 left-1/2 dark:bg-gray-600"
> @click="$dispatch('drawer-close-{{ $name }}')"
<span class="xl:hidden absolute w-8 h-1 -translate-x-1/2 bg-gray-300 rounded-lg top-3 left-1/2 dark:bg-gray-600"> x-on:touchstart="$dispatch('drawer-close-{{ $name }}')"
>
</span> </span>
<h5 class="inline-flex items-center text-base text-gray-500 dark:text-gray-400 font-medium"> <h5 class="inline-flex items-center text-base text-gray-500 dark:text-gray-400 font-medium">
{{ $title }} {{ $title }}
</h5> </h5>
<button class="end-2.5 text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white"> <button
class="end-2.5 text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white"
@click="$dispatch('drawer-close-{{ $name }}')"
x-on:touchstart="$dispatch('drawer-close-{{ $name }}')"
>
<svg class="max-xl:hidden w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14"> <svg class="max-xl:hidden 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"/> <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> </svg>

View File

@@ -68,6 +68,28 @@
load(); load();
}, },
}, },
imageResizeTargetWidth: 600,
imageCropAspectRatio: 1,
imageTransformVariants: {
thumb_medium_: (transforms) => {
transforms.resize = {
size: {
width: 384,
height: 384,
},
};
return transforms;
},
thumb_small_: (transforms) => {
transforms.resize = {
size: {
width: 128,
height: 128,
},
};
return transforms;
},
},
allowImagePreview: {{ $preview ? 'true' : 'false' }}, allowImagePreview: {{ $preview ? 'true' : 'false' }},
styleItemPanelAspectRatio: '0.5625', styleItemPanelAspectRatio: '0.5625',
allowFileTypeValidation: {{ $validate ? 'true' : 'false' }}, allowFileTypeValidation: {{ $validate ? 'true' : 'false' }},

View File

@@ -1,14 +1,17 @@
<form class="flex h-1/3 bg-cover relative"> @persist('search')
<div class="relative m-auto w-1/2"> <form class="flex h-[300px] relative overflow-hidden mb-8">
<label for="default-search" class="mb-2 text-sm font-medium text-gray-900 sr-only dark:text-white">Suchen</label> <img class="absolute object-cover" src="/placeholder.jpg" />
<div class="relative"> <div class="relative m-auto w-1/2">
<div class="absolute inset-y-0 start-0 flex items-center ps-3 pointer-events-none"> <label for="default-search" class="mb-2 text-sm font-medium text-gray-900 sr-only dark:text-white">Suchen</label>
<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"> <div class="relative">
<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"/> <div class="absolute inset-y-0 start-0 flex items-center ps-3 pointer-events-none">
</svg> <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>
<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>
</div> </form>
</form> @endpersist

View File

@@ -2,7 +2,7 @@
<html <html
lang="{{ str_replace('_', '-', app()->getLocale()) }}" lang="{{ str_replace('_', '-', app()->getLocale()) }}"
x-data="{ darkMode: $persist(false) }" x-data="{ darkMode: $persist(false) }"
:class="{'dark': darkMode }" :class="{'dark': darkMode}"
x-init=" x-init="
if (!('darkMode' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches) { if (!('darkMode' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches) {
localStorage.setItem('darkMode', JSON.stringify(true)); localStorage.setItem('darkMode', JSON.stringify(true));
@@ -17,7 +17,7 @@
@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"> <body class="bg-white dark:bg-gray-800 min-h-screen flex flex-col">
@persist('theme-switcher') @persist('theme-switcher')
<x-theme-switcher /> <x-theme-switcher />
@endpersist @endpersist

View File

@@ -1,4 +1,9 @@
<form wire:submit="save" class="p-4"> <form
wire:submit="save"
class="p-4"
x-data="{loading: false, message: ''}"
x-on:import-progress-report="message = $event.detail.message"
>
<x-form.upload wire:model="media" name="media" label="Medien" multiple /> <x-form.upload wire:model="media" name="media" label="Medien" multiple />
<button x-transition x-show="$store.uploader.states && $store.uploader.states.media == 3" disabled type="button" class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center me-2 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800 inline-flex items-center"> <button x-transition x-show="$store.uploader.states && $store.uploader.states.media == 3" disabled type="button" class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center me-2 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800 inline-flex items-center">
@@ -17,5 +22,5 @@
<span class="font-medium">Dateien mit Fehlern vorhanden!</span> Du kannst nicht speichern, bis die Fehler behoben sind. <span class="font-medium">Dateien mit Fehlern vorhanden!</span> Du kannst nicht speichern, bis die Fehler behoben sind.
</div> </div>
</div> </div>
<button x-transition.delay.500ms x-show="$store.uploader.states && $store.uploader.states.media == 4" type="submit" class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800">Speichern</button> <button x-transition.delay.500ms x-show="$store.uploader.states && $store.uploader.states.media == 4" type="submit" @click="loading = true" class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800">Speichern</button>
</div> </form>

View File

@@ -1,8 +1,7 @@
<?php <?php
use Illuminate\Foundation\Inspiring;
use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Schedule;
Artisan::command('inspire', function () { Schedule::command('queue:prune-batches')->daily();
$this->comment(Inspiring::quote()); Schedule::command('horizon:snapshot')->everyFiveMinutes();
})->purpose('Display an inspiring quote')->hourly();