add views and components

This commit is contained in:
Alexander Gabriel 2026-05-26 22:45:41 +02:00
parent 380742ad77
commit 079f65bc5f
5 changed files with 153 additions and 3 deletions

View File

@ -0,0 +1,11 @@
<?php
namespace App\Http\Controllers;
class PhotoController extends Controller
{
public function index()
{
return view('camera');
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace App\Livewire;
use App\Models\PhotoJob;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Livewire\Component;
class CameraCapture extends Component
{
public string $status = '';
public function capture(string $imageData): void
{
if (!preg_match('/^data:image\/(\w+);base64,/', $imageData, $type)) {
$this->status = 'Error: Invalid image data';
return;
}
$extension = strtolower($type[1]);
$filename = 'photos/' . Str::uuid() . '.' . $extension;
$raw = base64_decode(substr($imageData, strpos($imageData, ',') + 1));
Storage::disk('public')->put($filename, $raw);
$job = PhotoJob::create([
'image_path' => $filename,
'status' => 'pending',
]);
$this->status = "Job #{$job->id} created";
}
public function render()
{
$recentPhotos = PhotoJob::latest()->take(10)->get()->map(fn($job) => [
'id' => $job->id,
'url' => Storage::disk('public')->url($job->image_path),
'status' => $job->status,
]);
return view('livewire.camera-capture', compact('recentPhotos'));
}
}

View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Fotobox</title>
@if (file_exists(public_path('build/manifest.json')) || file_exists(public_path('hot')))
@vite(['resources/css/app.css', 'resources/js/app.js'])
@endif
@livewireStyles
</head>
<body class="bg-gray-950 text-white min-h-screen flex flex-col items-center justify-center p-6">
<h1 class="text-3xl font-bold mb-8 tracking-wide">Fotobox</h1>
@livewire('camera-capture')
@livewireScripts
</body>
</html>

View File

@ -0,0 +1,75 @@
<div class="flex flex-col items-center w-full">
<div class="relative w-full max-w-2xl">
<video id="video" autoplay playsinline
class="w-full rounded-2xl shadow-2xl bg-gray-900 aspect-video object-cover"></video>
<canvas id="canvas" class="hidden"></canvas>
<div id="flash" class="absolute inset-0 rounded-2xl bg-white opacity-0 pointer-events-none transition-opacity duration-150"></div>
</div>
<button id="capture-btn"
wire:loading.attr="disabled"
class="mt-8 px-10 py-4 bg-white text-gray-950 font-semibold text-lg rounded-full shadow-lg hover:bg-gray-200 active:scale-95 transition-all duration-150 disabled:opacity-50 disabled:cursor-not-allowed">
<span wire:loading.remove>Take Photo</span>
<span wire:loading>Saving…</span>
</button>
<div class="mt-4 text-sm text-gray-400 h-6">{{ $status }}</div>
@if ($recentPhotos->isNotEmpty())
<div class="mt-10 w-full max-w-2xl">
<h2 class="text-sm font-medium text-gray-400 mb-3 tracking-wide uppercase">Recent Photos</h2>
<div class="grid grid-cols-5 gap-2">
@foreach ($recentPhotos as $photo)
<div class="relative group">
<img src="{{ $photo['url'] }}" alt="Photo #{{ $photo['id'] }}"
class="w-full aspect-square object-cover rounded-lg border border-gray-700">
<span class="absolute bottom-1 left-1 text-[10px] px-1.5 py-0.5 rounded bg-black/60 text-yellow-400">
{{ $photo['status'] }}
</span>
</div>
@endforeach
</div>
</div>
@endif
@script
<script>
const video = document.getElementById('video');
const canvas = document.getElementById('canvas');
const btn = document.getElementById('capture-btn');
const flash = document.getElementById('flash');
async function startCamera() {
try {
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: false });
video.srcObject = stream;
} catch {
$wire.set('status', 'Camera access denied or unavailable.');
btn.disabled = true;
}
}
function triggerFlash() {
flash.style.opacity = '0.8';
setTimeout(() => flash.style.opacity = '0', 150);
}
btn.addEventListener('click', async () => {
if (!video.srcObject) return;
triggerFlash();
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
canvas.getContext('2d').drawImage(video, 0, 0);
const imageData = canvas.toDataURL('image/jpeg', 0.9);
await $wire.capture(imageData);
});
startCamera();
</script>
@endscript
</div>

View File

@ -1,7 +1,6 @@
<?php
use App\Http\Controllers\PhotoController;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return view('welcome');
});
Route::get('/', [PhotoController::class, 'index'])->name('camera');