Compare commits
No commits in common. "46d26ce2404c8d8ec4708d6de3cfe7eb02e9a22b" and "30e4c5807cac4d0b7f59a306b71bd5b71b42e315" have entirely different histories.
46d26ce240
...
30e4c5807c
@ -1,11 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
class PhotoController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return view('camera');
|
||||
}
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class RestrictToSubnet
|
||||
{
|
||||
public function handle(Request $request, Closure $next, string $subnet = '192'): Response
|
||||
{
|
||||
if (!str_starts_with($request->ip(), $subnet) && !str_starts_with($request->ip(), "127")) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\PhotoJob;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Queue\Queueable;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class ConvertToComic implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public int $timeout = 0;
|
||||
|
||||
public function __construct(public PhotoJob $photoJob) {}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
$absolutePath = Storage::disk('public')->path($this->photoJob->image_path);
|
||||
$script = base_path('convert_to_comic.sh');
|
||||
|
||||
$result = Process::timeout(0)->run('bash ' . escapeshellarg($script) . ' ' . escapeshellarg($absolutePath));
|
||||
|
||||
$this->photoJob->update([
|
||||
'status' => $result->successful() ? 'done' : 'failed',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@ -1,61 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Jobs\ConvertToComic;
|
||||
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 delete(int $jobId): void
|
||||
{
|
||||
$job = PhotoJob::find($jobId);
|
||||
if (!$job) return;
|
||||
|
||||
Storage::disk('public')->delete($job->image_path);
|
||||
$job->delete();
|
||||
|
||||
$this->status = "Job #{$jobId} deleted";
|
||||
}
|
||||
|
||||
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' => 'processing',
|
||||
]);
|
||||
|
||||
ConvertToComic::dispatch($job);
|
||||
|
||||
$this->status = "Processing photo…";
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
$recentPhotos = PhotoJob::latest()->take(10)->get()->map(fn($job) => [
|
||||
'id' => $job->id,
|
||||
'url' => Storage::disk('public')->url($job->image_path) . '?v=' . $job->updated_at->timestamp,
|
||||
'status' => $job->status,
|
||||
]);
|
||||
|
||||
$hasProcessing = $recentPhotos->contains('status', 'processing');
|
||||
|
||||
return view('livewire.camera-capture', compact('recentPhotos', 'hasProcessing'));
|
||||
}
|
||||
}
|
||||
@ -1,10 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class PhotoJob extends Model
|
||||
{
|
||||
protected $fillable = ['image_path', 'status'];
|
||||
}
|
||||
@ -8,8 +8,7 @@
|
||||
"require": {
|
||||
"php": "^8.3",
|
||||
"laravel/framework": "^13.8",
|
||||
"laravel/tinker": "^3.0",
|
||||
"livewire/livewire": "^4.3"
|
||||
"laravel/tinker": "^3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"fakerphp/faker": "^1.23",
|
||||
|
||||
78
composer.lock
generated
78
composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "11aa1ea596cfa2f6ee1fd74fb047a5f0",
|
||||
"content-hash": "e5a7fe2e384880bae0cd88829bd166e5",
|
||||
"packages": [
|
||||
{
|
||||
"name": "brick/math",
|
||||
@ -2026,82 +2026,6 @@
|
||||
],
|
||||
"time": "2026-03-08T20:05:35+00:00"
|
||||
},
|
||||
{
|
||||
"name": "livewire/livewire",
|
||||
"version": "v4.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/livewire/livewire.git",
|
||||
"reference": "19ebb1ee4d057debceccf70ff01950e6a6114edc"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/livewire/livewire/zipball/19ebb1ee4d057debceccf70ff01950e6a6114edc",
|
||||
"reference": "19ebb1ee4d057debceccf70ff01950e6a6114edc",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"illuminate/database": "^10.0|^11.0|^12.0|^13.0",
|
||||
"illuminate/routing": "^10.0|^11.0|^12.0|^13.0",
|
||||
"illuminate/support": "^10.0|^11.0|^12.0|^13.0",
|
||||
"illuminate/validation": "^10.0|^11.0|^12.0|^13.0",
|
||||
"laravel/prompts": "^0.1.24|^0.2|^0.3",
|
||||
"league/mime-type-detection": "^1.9",
|
||||
"php": "^8.1",
|
||||
"symfony/console": "^6.0|^7.0|^8.0",
|
||||
"symfony/http-kernel": "^6.2|^7.0|^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"calebporzio/sushi": "^2.1",
|
||||
"laravel/framework": "^10.15.0|^11.0|^12.0|^13.0",
|
||||
"mockery/mockery": "^1.3.1",
|
||||
"orchestra/testbench": "^8.21.0|^9.0|^10.0|^11.0",
|
||||
"orchestra/testbench-dusk": "^8.24|^9.1|^10.0|^11.0",
|
||||
"phpunit/phpunit": "^10.4|^11.5|^12.5",
|
||||
"psy/psysh": "^0.11.22|^0.12"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"aliases": {
|
||||
"Livewire": "Livewire\\Livewire"
|
||||
},
|
||||
"providers": [
|
||||
"Livewire\\LivewireServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/helpers.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Livewire\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Caleb Porzio",
|
||||
"email": "calebporzio@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "A front-end framework for Laravel.",
|
||||
"support": {
|
||||
"issues": "https://github.com/livewire/livewire/issues",
|
||||
"source": "https://github.com/livewire/livewire/tree/v4.3.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/livewire",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2026-05-01T00:46:07+00:00"
|
||||
},
|
||||
{
|
||||
"name": "monolog/monolog",
|
||||
"version": "3.10.0",
|
||||
|
||||
@ -1,15 +0,0 @@
|
||||
#!/usr/bin/bash
|
||||
export LANGUAGE=
|
||||
|
||||
source /home/alegab/workspace/comfy_ui/venv/bin/activate
|
||||
|
||||
cp "$1" /home/alegab/comfy/ComfyUI/input/fotobox.jpg
|
||||
|
||||
echo "input: $1" >> image_log.txt
|
||||
|
||||
output=$(comfy-cli run --workflow /home/alegab/comfy/ComfyUI/user/default/workflows/comic2.json | grep "Outputs" -A 2 | tail -n 2 | head -n 1 | strings)
|
||||
output=$(tr -dc '[[:print:]]' <<< "$output" | cut -c 5-38,43-)
|
||||
|
||||
echo "output: $output" >> image_log.txt
|
||||
|
||||
cp $output "$1"
|
||||
@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('photo_jobs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('image_path');
|
||||
$table->string('status')->default('pending');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('photo_jobs');
|
||||
}
|
||||
};
|
||||
217
package-lock.json
generated
217
package-lock.json
generated
@ -4,9 +4,6 @@
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"qrcode": "^1.5.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/vite": "^4.0.0",
|
||||
"concurrently": "^9.0.1",
|
||||
@ -709,6 +706,7 @@
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
@ -718,6 +716,7 @@
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
@ -729,15 +728,6 @@
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/camelcase": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
|
||||
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
@ -787,6 +777,7 @@
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
@ -799,6 +790,7 @@
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/concurrently": {
|
||||
@ -826,15 +818,6 @@
|
||||
"url": "https://github.com/open-cli-tools/concurrently?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/decamelize": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
|
||||
"integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||
@ -845,16 +828,11 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/dijkstrajs": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
|
||||
"integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/enhanced-resolve": {
|
||||
@ -899,19 +877,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/find-up": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
|
||||
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"locate-path": "^5.0.0",
|
||||
"path-exists": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
@ -931,6 +896,7 @@
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": "6.* || 8.* || >= 10.*"
|
||||
@ -957,6 +923,7 @@
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
@ -1272,18 +1239,6 @@
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/locate-path": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
|
||||
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-locate": "^4.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.30.21",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
||||
@ -1313,51 +1268,6 @@
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/p-limit": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
||||
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-try": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/p-locate": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
|
||||
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-limit": "^2.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/p-try": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
|
||||
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/path-exists": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
@ -1378,15 +1288,6 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/pngjs": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
|
||||
"integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.15",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
|
||||
@ -1416,104 +1317,16 @@
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode": {
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz",
|
||||
"integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dijkstrajs": "^1.0.1",
|
||||
"pngjs": "^5.0.0",
|
||||
"yargs": "^15.3.1"
|
||||
},
|
||||
"bin": {
|
||||
"qrcode": "bin/qrcode"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/cliui": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
|
||||
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"string-width": "^4.2.0",
|
||||
"strip-ansi": "^6.0.0",
|
||||
"wrap-ansi": "^6.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/wrap-ansi": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
|
||||
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"string-width": "^4.1.0",
|
||||
"strip-ansi": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/y18n": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
|
||||
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/qrcode/node_modules/yargs": {
|
||||
"version": "15.4.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
|
||||
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cliui": "^6.0.0",
|
||||
"decamelize": "^1.2.0",
|
||||
"find-up": "^4.1.0",
|
||||
"get-caller-file": "^2.0.1",
|
||||
"require-directory": "^2.1.1",
|
||||
"require-main-filename": "^2.0.0",
|
||||
"set-blocking": "^2.0.0",
|
||||
"string-width": "^4.2.0",
|
||||
"which-module": "^2.0.0",
|
||||
"y18n": "^4.0.0",
|
||||
"yargs-parser": "^18.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/yargs-parser": {
|
||||
"version": "18.1.3",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
|
||||
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"camelcase": "^5.0.0",
|
||||
"decamelize": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/require-main-filename": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
|
||||
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/rolldown": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.2.tgz",
|
||||
@ -1558,12 +1371,6 @@
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/set-blocking": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/shell-quote": {
|
||||
"version": "1.8.3",
|
||||
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz",
|
||||
@ -1591,6 +1398,7 @@
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
@ -1605,6 +1413,7 @@
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
@ -1786,12 +1595,6 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/which-module": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
|
||||
"integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/wrap-ansi": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||
|
||||
@ -12,8 +12,5 @@
|
||||
"laravel-vite-plugin": "^3.1",
|
||||
"tailwindcss": "^4.0.0",
|
||||
"vite": "^8.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"qrcode": "^1.5.4"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1 @@
|
||||
import QRCode from 'qrcode';
|
||||
|
||||
window.generateQR = (url, canvas) => {
|
||||
QRCode.toCanvas(canvas, url, { width: 180, margin: 1 });
|
||||
};
|
||||
//
|
||||
|
||||
@ -1,20 +0,0 @@
|
||||
<!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>
|
||||
@ -1,143 +0,0 @@
|
||||
<div class="flex flex-col items-center w-full"
|
||||
x-data="{ lightbox: null }"
|
||||
x-on:keydown.escape.window="lightbox = null">
|
||||
|
||||
<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 id="countdown" class="absolute inset-0 rounded-2xl hidden items-center justify-center bg-black/40">
|
||||
<span id="countdown-num" class="text-white font-bold drop-shadow-lg" style="font-size: 8rem; line-height: 1;"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button id="capture-btn"
|
||||
wire:loading.attr="disabled"
|
||||
@disabled($hasProcessing)
|
||||
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>{{ $hasProcessing ? 'Erstelle Bild...' : 'Foto aufnehmen' }}</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" @if($hasProcessing) wire:poll.2000ms @endif>
|
||||
@foreach ($recentPhotos as $photo)
|
||||
<div class="relative">
|
||||
@if ($photo['status'] === 'processing')
|
||||
<div class="relative w-full aspect-square">
|
||||
<img src="{{ $photo['url'] }}" alt="Photo #{{ $photo['id'] }}"
|
||||
class="w-full aspect-square object-cover rounded-lg border border-gray-700">
|
||||
<div class="absolute inset-0 rounded-lg bg-black/50 flex items-center justify-center">
|
||||
<svg class="w-8 h-8 text-white animate-spin" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8H4z"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<img src="{{ $photo['url'] }}" alt="Photo #{{ $photo['id'] }}"
|
||||
class="w-full aspect-square object-cover rounded-lg border border-gray-700 cursor-pointer"
|
||||
x-on:click="lightbox = {{ Js::from($photo) }}">
|
||||
<button wire:click="delete({{ $photo['id'] }})" wire:confirm="Delete this photo?"
|
||||
class="absolute top-1 right-1 p-1 rounded bg-black/60 text-white hover:bg-red-600/80 transition-colors">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
</button>
|
||||
@endif
|
||||
<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
|
||||
|
||||
{{-- Lightbox --}}
|
||||
<div x-show="lightbox"
|
||||
wire:ignore
|
||||
x-on:click.self="lightbox = null"
|
||||
class="fixed inset-0 z-50 flex items-center justify-center bg-black/80 p-6"
|
||||
style="display:none">
|
||||
<div class="relative max-w-2xl w-full">
|
||||
<img x-bind:src="lightbox?.url" alt="Photo"
|
||||
class="w-full rounded-2xl shadow-2xl">
|
||||
<div class="absolute bottom-4 right-4 rounded-xl overflow-hidden shadow-2xl bg-white p-1">
|
||||
<canvas x-ref="qrCanvas"
|
||||
x-effect="lightbox && $nextTick(() => window.generateQR(lightbox.url, $refs.qrCanvas))"></canvas>
|
||||
</div>
|
||||
<button x-on:click="lightbox = null"
|
||||
class="absolute top-3 right-3 p-1.5 rounded-full bg-black/60 text-white hover:bg-black/80">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@script
|
||||
<script>
|
||||
const video = document.getElementById('video');
|
||||
const canvas = document.getElementById('canvas');
|
||||
const btn = document.getElementById('capture-btn');
|
||||
const flash = document.getElementById('flash');
|
||||
const countdown = document.getElementById('countdown');
|
||||
const countdownNum = document.getElementById('countdown-num');
|
||||
|
||||
async function startCamera() {
|
||||
try {
|
||||
const stream = await navigator.mediaDevices.getUserMedia({ video: { width: 640, height: 480 }, 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);
|
||||
}
|
||||
|
||||
function delay(ms) {
|
||||
return new Promise(r => setTimeout(r, ms));
|
||||
}
|
||||
|
||||
btn.addEventListener('click', async () => {
|
||||
if (!video.srcObject || btn.disabled) return;
|
||||
|
||||
btn.disabled = true;
|
||||
countdown.classList.remove('hidden');
|
||||
countdown.classList.add('flex');
|
||||
|
||||
for (let i = 5; i >= 1; i--) {
|
||||
countdownNum.textContent = i;
|
||||
await delay(1000);
|
||||
}
|
||||
|
||||
countdown.classList.add('hidden');
|
||||
countdown.classList.remove('flex');
|
||||
|
||||
triggerFlash();
|
||||
|
||||
canvas.width = 640;
|
||||
canvas.height = 480;
|
||||
canvas.getContext('2d').drawImage(video, 0, 0, 640, 480);
|
||||
const imageData = canvas.toDataURL('image/jpeg', 0.9);
|
||||
|
||||
await $wire.capture(imageData);
|
||||
});
|
||||
|
||||
startCamera();
|
||||
</script>
|
||||
@endscript
|
||||
</div>
|
||||
@ -1,8 +1,7 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Controllers\PhotoController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::get('/', [PhotoController::class, 'index'])
|
||||
->middleware(\App\Http\Middleware\RestrictToSubnet::class)
|
||||
->name('camera');
|
||||
Route::get('/', function () {
|
||||
return view('welcome');
|
||||
});
|
||||
|
||||
@ -20,6 +20,5 @@ export default defineConfig({
|
||||
watch: {
|
||||
ignored: ['**/storage/framework/views/**'],
|
||||
},
|
||||
host: "127.0.0.1"
|
||||
},
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user