diff --git a/app/Jobs/FetchDeviceModelsJob.php b/app/Jobs/FetchDeviceModelsJob.php index cb24d98..695041f 100644 --- a/app/Jobs/FetchDeviceModelsJob.php +++ b/app/Jobs/FetchDeviceModelsJob.php @@ -5,7 +5,6 @@ declare(strict_types=1); namespace App\Jobs; use App\Models\DeviceModel; -use App\Models\DevicePalette; use Exception; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; @@ -21,8 +20,6 @@ final class FetchDeviceModelsJob implements ShouldQueue private const API_URL = 'https://usetrmnl.com/api/models'; - private const PALETTES_API_URL = 'http://usetrmnl.com/api/palettes'; - /** * Create a new job instance. */ @@ -37,8 +34,6 @@ final class FetchDeviceModelsJob implements ShouldQueue public function handle(): void { try { - $this->processPalettes(); - $response = Http::timeout(30)->get(self::API_URL); if (! $response->successful()) { @@ -74,86 +69,6 @@ final class FetchDeviceModelsJob implements ShouldQueue } } - /** - * Process palettes from API and update/create records. - */ - private function processPalettes(): void - { - try { - $response = Http::timeout(30)->get(self::PALETTES_API_URL); - - if (! $response->successful()) { - Log::error('Failed to fetch palettes from API', [ - 'status' => $response->status(), - 'body' => $response->body(), - ]); - - return; - } - - $data = $response->json('data', []); - - if (! is_array($data)) { - Log::error('Invalid response format from palettes API', [ - 'response' => $response->json(), - ]); - - return; - } - - foreach ($data as $paletteData) { - try { - $this->updateOrCreatePalette($paletteData); - } catch (Exception $e) { - Log::error('Failed to process palette', [ - 'palette_data' => $paletteData, - 'error' => $e->getMessage(), - ]); - } - } - - Log::info('Successfully fetched and updated palettes', [ - 'count' => count($data), - ]); - - } catch (Exception $e) { - Log::error('Exception occurred while fetching palettes', [ - 'message' => $e->getMessage(), - 'trace' => $e->getTraceAsString(), - ]); - } - } - - /** - * Update or create a palette record. - */ - private function updateOrCreatePalette(array $paletteData): void - { - $name = $paletteData['id'] ?? null; - - if (! $name) { - Log::warning('Palette data missing id field', [ - 'palette_data' => $paletteData, - ]); - - return; - } - - $attributes = [ - 'name' => $name, - 'description' => $paletteData['name'] ?? '', - 'grays' => $paletteData['grays'] ?? 2, - 'colors' => $paletteData['colors'] ?? null, - 'framework_class' => $paletteData['framework_class'] ?? '', - 'source' => 'api', - ]; - - DevicePalette::updateOrCreate( - ['name' => $name], - $attributes - ); - } - /** * Process the device models data and update/create records. */ @@ -202,45 +117,9 @@ final class FetchDeviceModelsJob implements ShouldQueue 'source' => 'api', ]; - // Set palette_id to the first palette from the model's palettes array - $firstPaletteId = $this->getFirstPaletteId($modelData); - if ($firstPaletteId) { - $attributes['palette_id'] = $firstPaletteId; - } - DeviceModel::updateOrCreate( ['name' => $name], $attributes ); } - - /** - * Get the first palette ID from model data. - */ - private function getFirstPaletteId(array $modelData): ?int - { - $paletteName = null; - - // Check for palette_ids array - if (isset($modelData['palette_ids']) && is_array($modelData['palette_ids']) && $modelData['palette_ids'] !== []) { - $paletteName = $modelData['palette_ids'][0]; - } - - // Check for palettes array (array of objects with id) - if (! $paletteName && isset($modelData['palettes']) && is_array($modelData['palettes']) && $modelData['palettes'] !== []) { - $firstPalette = $modelData['palettes'][0]; - if (is_array($firstPalette) && isset($firstPalette['id'])) { - $paletteName = $firstPalette['id']; - } - } - - if (! $paletteName) { - return null; - } - - // Look up palette by name to get the integer ID - $palette = DevicePalette::where('name', $paletteName)->first(); - - return $palette?->id; - } } diff --git a/app/Models/Device.php b/app/Models/Device.php index 2eeb25b..6a99fcd 100644 --- a/app/Models/Device.php +++ b/app/Models/Device.php @@ -12,7 +12,6 @@ use Illuminate\Support\Facades\Storage; /** * @property-read DeviceModel|null $deviceModel - * @property-read DevicePalette|null $palette */ class Device extends Model { @@ -188,11 +187,6 @@ class Device extends Model return $this->belongsTo(DeviceModel::class); } - public function palette(): BelongsTo - { - return $this->belongsTo(DevicePalette::class, 'palette_id'); - } - /** * Get the color depth string (e.g., "4bit") for the associated device model. */ diff --git a/app/Models/DeviceModel.php b/app/Models/DeviceModel.php index 6132a76..4dfaf1e 100644 --- a/app/Models/DeviceModel.php +++ b/app/Models/DeviceModel.php @@ -6,11 +6,7 @@ namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; -use Illuminate\Database\Eloquent\Relations\BelongsTo; -/** - * @property-read DevicePalette|null $palette - */ final class DeviceModel extends Model { use HasFactory; @@ -39,7 +35,7 @@ final class DeviceModel extends Model return '2bit'; } - // if higher than 4 return 4bit + // if higher then 4 return 4bit if ($this->bit_depth > 4) { return '4bit'; } @@ -70,9 +66,4 @@ final class DeviceModel extends Model return null; } - - public function palette(): BelongsTo - { - return $this->belongsTo(DevicePalette::class, 'palette_id'); - } } diff --git a/app/Models/DevicePalette.php b/app/Models/DevicePalette.php deleted file mode 100644 index 54b0876..0000000 --- a/app/Models/DevicePalette.php +++ /dev/null @@ -1,23 +0,0 @@ - 'integer', - 'colors' => 'array', - ]; -} diff --git a/app/Services/ImageGenerationService.php b/app/Services/ImageGenerationService.php index 4b28e80..76be3bb 100644 --- a/app/Services/ImageGenerationService.php +++ b/app/Services/ImageGenerationService.php @@ -25,7 +25,7 @@ class ImageGenerationService { public static function generateImage(string $markup, $deviceId): string { - $device = Device::with(['deviceModel', 'palette', 'deviceModel.palette'])->find($deviceId); + $device = Device::with('deviceModel')->find($deviceId); $uuid = Uuid::uuid4()->toString(); try { @@ -61,14 +61,6 @@ class ImageGenerationService $browserStage->setBrowsershotOption('args', ['--no-sandbox', '--disable-setuid-sandbox', '--disable-gpu']); } - // Get palette from device or fallback to device model's default palette - $palette = $device->palette ?? $device->deviceModel?->palette; - $colorPalette = null; - - if ($palette && $palette->colors) { - $colorPalette = $palette->colors; - } - $imageStage = new ImageStage(); $imageStage->format($fileExtension) ->width($imageSettings['width']) @@ -80,11 +72,6 @@ class ImageGenerationService ->offsetY($imageSettings['offset_y']) ->outputPath($outputPath); - // Apply color palette if available - if ($colorPalette) { - $imageStage->colormap($colorPalette); - } - // Apply dithering if requested by markup $shouldDither = self::markupContainsDitherImage($markup); if ($shouldDither) { @@ -351,9 +338,6 @@ class ImageGenerationService $uuid = Uuid::uuid4()->toString(); try { - // Load device with relationships - $device->load(['palette', 'deviceModel.palette']); - // Get image generation settings from DeviceModel if available, otherwise use device settings $imageSettings = self::getImageSettings($device); @@ -388,14 +372,6 @@ class ImageGenerationService $browserStage->setBrowsershotOption('args', ['--no-sandbox', '--disable-setuid-sandbox', '--disable-gpu']); } - // Get palette from device or fallback to device model's default palette - $palette = $device->palette ?? $device->deviceModel?->palette; - $colorPalette = null; - - if ($palette && $palette->colors) { - $colorPalette = $palette->colors; - } - $imageStage = new ImageStage(); $imageStage->format($fileExtension) ->width($imageSettings['width']) @@ -407,11 +383,6 @@ class ImageGenerationService ->offsetY($imageSettings['offset_y']) ->outputPath($outputPath); - // Apply color palette if available - if ($colorPalette) { - $imageStage->colormap($colorPalette); - } - (new TrmnlPipeline())->pipe($browserStage) ->pipe($imageStage) ->process(); diff --git a/composer.json b/composer.json index 9d5deb5..79306ce 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "ext-simplexml": "*", "ext-zip": "*", "bnussbau/laravel-trmnl-blade": "2.0.*", - "bnussbau/trmnl-pipeline-php": "^0.5.0", + "bnussbau/trmnl-pipeline-php": "^0.4.0", "keepsuit/laravel-liquid": "^0.5.2", "laravel/framework": "^12.1", "laravel/sanctum": "^4.0", diff --git a/composer.lock b/composer.lock index b8ad138..33b43dc 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "38e8a7dd90ccc1b777a4c8a5a28f9f14", + "content-hash": "3d743ce4dc2742c59ed6f9cc8ed36e04", "packages": [ { "name": "aws/aws-crt-php", @@ -243,16 +243,16 @@ }, { "name": "bnussbau/trmnl-pipeline-php", - "version": "0.5.0", + "version": "0.4.0", "source": { "type": "git", "url": "https://github.com/bnussbau/trmnl-pipeline-php.git", - "reference": "eb55b89e1f3991764912505872bbce809874d1aa" + "reference": "b58b937f36e55aa7cbd4859cbe7a902bf1050bf8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bnussbau/trmnl-pipeline-php/zipball/eb55b89e1f3991764912505872bbce809874d1aa", - "reference": "eb55b89e1f3991764912505872bbce809874d1aa", + "url": "https://api.github.com/repos/bnussbau/trmnl-pipeline-php/zipball/b58b937f36e55aa7cbd4859cbe7a902bf1050bf8", + "reference": "b58b937f36e55aa7cbd4859cbe7a902bf1050bf8", "shasum": "" }, "require": { @@ -294,7 +294,7 @@ ], "support": { "issues": "https://github.com/bnussbau/trmnl-pipeline-php/issues", - "source": "https://github.com/bnussbau/trmnl-pipeline-php/tree/0.5.0" + "source": "https://github.com/bnussbau/trmnl-pipeline-php/tree/0.4.0" }, "funding": [ { @@ -310,7 +310,7 @@ "type": "github" } ], - "time": "2025-11-25T17:00:21+00:00" + "time": "2025-10-30T11:52:17+00:00" }, { "name": "brick/math", diff --git a/database/factories/DevicePaletteFactory.php b/database/factories/DevicePaletteFactory.php deleted file mode 100644 index a672873..0000000 --- a/database/factories/DevicePaletteFactory.php +++ /dev/null @@ -1,38 +0,0 @@ - - */ -class DevicePaletteFactory extends Factory -{ - protected $model = DevicePalette::class; - - /** - * Define the model's default state. - * - * @return array - */ - public function definition(): array - { - return [ - 'id' => 'test-' . $this->faker->unique()->slug(), - 'name' => $this->faker->words(3, true), - 'grays' => $this->faker->randomElement([2, 4, 16, 256]), - 'colors' => $this->faker->optional()->passthrough([ - '#FF0000', - '#00FF00', - '#0000FF', - '#FFFF00', - '#000000', - '#FFFFFF', - ]), - 'framework_class' => null, - 'source' => 'api', - ]; - } -} diff --git a/database/migrations/2025_08_04_064514_add_oidc_sub_to_users_table.php b/database/migrations/2025_08_04_064514_add_oidc_sub_to_users_table.php index 7ec1374..d8dba38 100644 --- a/database/migrations/2025_08_04_064514_add_oidc_sub_to_users_table.php +++ b/database/migrations/2025_08_04_064514_add_oidc_sub_to_users_table.php @@ -22,7 +22,6 @@ return new class extends Migration public function down(): void { Schema::table('users', function (Blueprint $table) { - $table->dropUnique(['oidc_sub']); $table->dropColumn('oidc_sub'); }); } diff --git a/database/migrations/2025_11_22_084119_create_device_palettes_table.php b/database/migrations/2025_11_22_084119_create_device_palettes_table.php deleted file mode 100644 index 9262dac..0000000 --- a/database/migrations/2025_11_22_084119_create_device_palettes_table.php +++ /dev/null @@ -1,33 +0,0 @@ -id(); - $table->string('name')->unique(); - $table->string('description')->nullable(); - $table->integer('grays'); - $table->json('colors')->nullable(); - $table->string('framework_class')->default(''); - $table->string('source')->default('api'); - $table->timestamps(); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::dropIfExists('device_palettes'); - } -}; diff --git a/database/migrations/2025_11_22_084208_add_palette_id_to_device_models_table.php b/database/migrations/2025_11_22_084208_add_palette_id_to_device_models_table.php deleted file mode 100644 index 1993fcf..0000000 --- a/database/migrations/2025_11_22_084208_add_palette_id_to_device_models_table.php +++ /dev/null @@ -1,29 +0,0 @@ -foreignId('palette_id')->nullable()->after('source')->constrained('device_palettes')->onDelete('set null'); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::table('device_models', function (Blueprint $table) { - $table->dropForeign(['palette_id']); - $table->dropColumn('palette_id'); - }); - } -}; diff --git a/database/migrations/2025_11_22_084211_add_palette_id_to_devices_table.php b/database/migrations/2025_11_22_084211_add_palette_id_to_devices_table.php deleted file mode 100644 index 3a47afe..0000000 --- a/database/migrations/2025_11_22_084211_add_palette_id_to_devices_table.php +++ /dev/null @@ -1,29 +0,0 @@ -foreignId('palette_id')->nullable()->constrained('device_palettes')->onDelete('set null'); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::table('devices', function (Blueprint $table) { - $table->dropForeign(['palette_id']); - $table->dropColumn('palette_id'); - }); - } -}; diff --git a/database/migrations/2025_11_22_084425_seed_palettes_and_relationships.php b/database/migrations/2025_11_22_084425_seed_palettes_and_relationships.php deleted file mode 100644 index c198d81..0000000 --- a/database/migrations/2025_11_22_084425_seed_palettes_and_relationships.php +++ /dev/null @@ -1,124 +0,0 @@ - 'bw', - 'description' => 'Black & White', - 'grays' => 2, - 'colors' => null, - 'framework_class' => 'screen--1bit', - 'source' => 'api', - ], - [ - 'name' => 'gray-4', - 'description' => '4 Grays', - 'grays' => 4, - 'colors' => null, - 'framework_class' => 'screen--2bit', - 'source' => 'api', - ], - [ - 'name' => 'gray-16', - 'description' => '16 Grays', - 'grays' => 16, - 'colors' => null, - 'framework_class' => 'screen--4bit', - 'source' => 'api', - ], - [ - 'name' => 'gray-256', - 'description' => '256 Grays', - 'grays' => 256, - 'colors' => null, - 'framework_class' => 'screen--4bit', - 'source' => 'api', - ], - [ - 'name' => 'color-6a', - 'description' => '6 Colors', - 'grays' => 2, - 'colors' => json_encode(['#FF0000', '#00FF00', '#0000FF', '#FFFF00', '#000000', '#FFFFFF']), - 'framework_class' => '', - 'source' => 'api', - ], - [ - 'name' => 'color-7a', - 'description' => '7 Colors', - 'grays' => 2, - 'colors' => json_encode(['#000000', '#FFFFFF', '#FF0000', '#00FF00', '#0000FF', '#FFFF00', '#FFA500']), - 'framework_class' => '', - 'source' => 'api', - ], - ]; - - $now = now(); - $paletteIdMap = []; - - foreach ($palettes as $paletteData) { - $paletteName = $paletteData['name']; - $paletteData['created_at'] = $now; - $paletteData['updated_at'] = $now; - - DB::table('device_palettes')->updateOrInsert( - ['name' => $paletteName], - $paletteData - ); - - // Get the ID of the palette (either newly created or existing) - $paletteRecord = DB::table('device_palettes')->where('name', $paletteName)->first(); - $paletteIdMap[$paletteName] = $paletteRecord->id; - } - - // Set default palette_id on DeviceModel based on first palette_ids entry - $models = [ - ['name' => 'og_png', 'palette_name' => 'bw'], - ['name' => 'og_plus', 'palette_name' => 'gray-4'], - ['name' => 'amazon_kindle_2024', 'palette_name' => 'gray-256'], - ['name' => 'amazon_kindle_paperwhite_6th_gen', 'palette_name' => 'gray-256'], - ['name' => 'amazon_kindle_paperwhite_7th_gen', 'palette_name' => 'gray-256'], - ['name' => 'inkplate_10', 'palette_name' => 'gray-4'], - ['name' => 'amazon_kindle_7', 'palette_name' => 'gray-256'], - ['name' => 'inky_impression_7_3', 'palette_name' => 'color-7a'], - ['name' => 'kobo_libra_2', 'palette_name' => 'gray-16'], - ['name' => 'amazon_kindle_oasis_2', 'palette_name' => 'gray-256'], - ['name' => 'kobo_aura_one', 'palette_name' => 'gray-16'], - ['name' => 'kobo_aura_hd', 'palette_name' => 'gray-16'], - ['name' => 'inky_impression_13_3', 'palette_name' => 'color-6a'], - ['name' => 'm5_paper_s3', 'palette_name' => 'gray-16'], - ['name' => 'amazon_kindle_scribe', 'palette_name' => 'gray-256'], - ['name' => 'seeed_e1001', 'palette_name' => 'gray-4'], - ['name' => 'seeed_e1002', 'palette_name' => 'gray-4'], - ['name' => 'waveshare_4_26', 'palette_name' => 'gray-4'], - ['name' => 'waveshare_7_5_bw', 'palette_name' => 'bw'], - ]; - - foreach ($models as $modelData) { - $deviceModel = DeviceModel::where('name', $modelData['name'])->first(); - if ($deviceModel && ! $deviceModel->palette_id && isset($paletteIdMap[$modelData['palette_name']])) { - $deviceModel->update(['palette_id' => $paletteIdMap[$modelData['palette_name']]]); - } - } - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - // Remove palette_id from device models but keep palettes - DeviceModel::query()->update(['palette_id' => null]); - } -}; diff --git a/resources/views/livewire/device-models/index.blade.php b/resources/views/livewire/device-models/index.blade.php index a57085b..a78f2a2 100644 --- a/resources/views/livewire/device-models/index.blade.php +++ b/resources/views/livewire/device-models/index.blade.php @@ -1,43 +1,26 @@ 'required|string|max:255|unique:device_models,name', 'label' => 'required|string|max:255', @@ -57,144 +40,42 @@ new class extends Component public function mount() { $this->deviceModels = DeviceModel::all(); - $this->devicePalettes = DevicePalette::all(); - return view('livewire.device-models.index'); } + public function createDeviceModel(): void + { + $this->validate(); + + DeviceModel::create([ + 'name' => $this->name, + 'label' => $this->label, + 'description' => $this->description, + 'width' => $this->width, + 'height' => $this->height, + 'colors' => $this->colors, + 'bit_depth' => $this->bit_depth, + 'scale_factor' => $this->scale_factor, + 'rotation' => $this->rotation, + 'mime_type' => $this->mime_type, + 'offset_x' => $this->offset_x, + 'offset_y' => $this->offset_y, + 'published_at' => $this->published_at, + ]); + + $this->reset(['name', 'label', 'description', 'width', 'height', 'colors', 'bit_depth', 'scale_factor', 'rotation', 'mime_type', 'offset_x', 'offset_y', 'published_at']); + \Flux::modal('create-device-model')->close(); + + $this->deviceModels = DeviceModel::all(); + session()->flash('message', 'Device model created successfully.'); + } + public $editingDeviceModelId; - public $viewingDeviceModelId; - - public function openDeviceModelModal(?string $deviceModelId = null, bool $viewOnly = false): void + public function editDeviceModel(DeviceModel $deviceModel): void { - if ($deviceModelId) { - $deviceModel = DeviceModel::findOrFail($deviceModelId); - - if ($viewOnly) { - $this->viewingDeviceModelId = $deviceModel->id; - $this->editingDeviceModelId = null; - } else { - $this->editingDeviceModelId = $deviceModel->id; - $this->viewingDeviceModelId = null; - } - - $this->name = $deviceModel->name; - $this->label = $deviceModel->label; - $this->description = $deviceModel->description; - $this->width = $deviceModel->width; - $this->height = $deviceModel->height; - $this->colors = $deviceModel->colors; - $this->bit_depth = $deviceModel->bit_depth; - $this->scale_factor = $deviceModel->scale_factor; - $this->rotation = $deviceModel->rotation; - $this->mime_type = $deviceModel->mime_type; - $this->offset_x = $deviceModel->offset_x; - $this->offset_y = $deviceModel->offset_y; - $this->published_at = $deviceModel->published_at?->format('Y-m-d\TH:i'); - $this->palette_id = $deviceModel->palette_id; - } else { - $this->editingDeviceModelId = null; - $this->viewingDeviceModelId = null; - $this->reset(['name', 'label', 'description', 'width', 'height', 'colors', 'bit_depth', 'scale_factor', 'rotation', 'mime_type', 'offset_x', 'offset_y', 'published_at', 'palette_id']); - $this->mime_type = 'image/png'; - $this->scale_factor = 1.0; - $this->rotation = 0; - $this->offset_x = 0; - $this->offset_y = 0; - } - } - - public function saveDeviceModel(): void - { - $rules = [ - 'name' => 'required|string|max:255', - 'label' => 'required|string|max:255', - 'description' => 'required|string', - 'width' => 'required|integer|min:1', - 'height' => 'required|integer|min:1', - 'colors' => 'required|integer|min:1', - 'bit_depth' => 'required|integer|min:1', - 'scale_factor' => 'required|numeric|min:0.1', - 'rotation' => 'required|integer', - 'mime_type' => 'required|string|max:255', - 'offset_x' => 'required|integer', - 'offset_y' => 'required|integer', - 'published_at' => 'nullable|date', - 'palette_id' => 'nullable|exists:device_palettes,id', - ]; - - if ($this->editingDeviceModelId) { - $rules['name'] = 'required|string|max:255|unique:device_models,name,'.$this->editingDeviceModelId; - } else { - $rules['name'] = 'required|string|max:255|unique:device_models,name'; - } - - $this->validate($rules); - - if ($this->editingDeviceModelId) { - $deviceModel = DeviceModel::findOrFail($this->editingDeviceModelId); - $deviceModel->update([ - 'name' => $this->name, - 'label' => $this->label, - 'description' => $this->description, - 'width' => $this->width, - 'height' => $this->height, - 'colors' => $this->colors, - 'bit_depth' => $this->bit_depth, - 'scale_factor' => $this->scale_factor, - 'rotation' => $this->rotation, - 'mime_type' => $this->mime_type, - 'offset_x' => $this->offset_x, - 'offset_y' => $this->offset_y, - 'published_at' => $this->published_at, - 'palette_id' => $this->palette_id ?: null, - ]); - $message = 'Device model updated successfully.'; - } else { - DeviceModel::create([ - 'name' => $this->name, - 'label' => $this->label, - 'description' => $this->description, - 'width' => $this->width, - 'height' => $this->height, - 'colors' => $this->colors, - 'bit_depth' => $this->bit_depth, - 'scale_factor' => $this->scale_factor, - 'rotation' => $this->rotation, - 'mime_type' => $this->mime_type, - 'offset_x' => $this->offset_x, - 'offset_y' => $this->offset_y, - 'published_at' => $this->published_at, - 'palette_id' => $this->palette_id ?: null, - 'source' => 'manual', - ]); - $message = 'Device model created successfully.'; - } - - $this->reset(['name', 'label', 'description', 'width', 'height', 'colors', 'bit_depth', 'scale_factor', 'rotation', 'mime_type', 'offset_x', 'offset_y', 'published_at', 'palette_id', 'editingDeviceModelId', 'viewingDeviceModelId']); - Flux::modal('device-model-modal')->close(); - - $this->deviceModels = DeviceModel::all(); - session()->flash('message', $message); - } - - public function deleteDeviceModel(string $deviceModelId): void - { - $deviceModel = DeviceModel::findOrFail($deviceModelId); - $deviceModel->delete(); - - $this->deviceModels = DeviceModel::all(); - session()->flash('message', 'Device model deleted successfully.'); - } - - public function duplicateDeviceModel(string $deviceModelId): void - { - $deviceModel = DeviceModel::findOrFail($deviceModelId); - - $this->editingDeviceModelId = null; - $this->viewingDeviceModelId = null; - $this->name = $deviceModel->name.' (Copy)'; + $this->editingDeviceModelId = $deviceModel->id; + $this->name = $deviceModel->name; $this->label = $deviceModel->label; $this->description = $deviceModel->description; $this->width = $deviceModel->width; @@ -207,9 +88,57 @@ new class extends Component $this->offset_x = $deviceModel->offset_x; $this->offset_y = $deviceModel->offset_y; $this->published_at = $deviceModel->published_at?->format('Y-m-d\TH:i'); - $this->palette_id = $deviceModel->palette_id; + } - $this->js('Flux.modal("device-model-modal").show()'); + public function updateDeviceModel(): void + { + $deviceModel = DeviceModel::findOrFail($this->editingDeviceModelId); + + $this->validate([ + 'name' => 'required|string|max:255|unique:device_models,name,' . $deviceModel->id, + 'label' => 'required|string|max:255', + 'description' => 'required|string', + 'width' => 'required|integer|min:1', + 'height' => 'required|integer|min:1', + 'colors' => 'required|integer|min:1', + 'bit_depth' => 'required|integer|min:1', + 'scale_factor' => 'required|numeric|min:0.1', + 'rotation' => 'required|integer', + 'mime_type' => 'required|string|max:255', + 'offset_x' => 'required|integer', + 'offset_y' => 'required|integer', + 'published_at' => 'nullable|date', + ]); + + $deviceModel->update([ + 'name' => $this->name, + 'label' => $this->label, + 'description' => $this->description, + 'width' => $this->width, + 'height' => $this->height, + 'colors' => $this->colors, + 'bit_depth' => $this->bit_depth, + 'scale_factor' => $this->scale_factor, + 'rotation' => $this->rotation, + 'mime_type' => $this->mime_type, + 'offset_x' => $this->offset_x, + 'offset_y' => $this->offset_y, + 'published_at' => $this->published_at, + ]); + + $this->reset(['name', 'label', 'description', 'width', 'height', 'colors', 'bit_depth', 'scale_factor', 'rotation', 'mime_type', 'offset_x', 'offset_y', 'published_at', 'editingDeviceModelId']); + \Flux::modal('edit-device-model-' . $deviceModel->id)->close(); + + $this->deviceModels = DeviceModel::all(); + session()->flash('message', 'Device model updated successfully.'); + } + + public function deleteDeviceModel(DeviceModel $deviceModel): void + { + $deviceModel->delete(); + + $this->deviceModels = DeviceModel::all(); + session()->flash('message', 'Device model deleted successfully.'); } } @@ -219,19 +148,10 @@ new class extends Component
-
-

Device Models

- - - - Devices - Device Palettes - - -
- - Add Device Model - +

Device Models

+ {{-- --}} + {{-- Add Device Model--}} + {{-- --}}
@if (session()->has('message'))
@@ -244,104 +164,157 @@ new class extends Component
@endif - +
- - @if ($viewingDeviceModelId) - View Device Model - @elseif ($editingDeviceModelId) - Edit Device Model - @else - Add Device Model - @endif - + Add Device Model
-
+
+ name="name" autofocus/>
+ name="label"/>
+ class="block mt-1 w-full" name="description"/>
+ name="width"/> + name="height"/>
+ name="colors"/> + name="bit_depth"/>
+ name="scale_factor" step="0.1"/> + name="rotation"/>
- - image/png - image/bmp - +
+ name="offset_x"/> + name="offset_y"/>
-
- - None - @foreach ($devicePalettes as $palette) - {{ $palette->description ?? $palette->name }} ({{ $palette->name }}) - @endforeach - +
+ + Create Device Model
- - @if (!$viewingDeviceModelId) -
- - {{ $editingDeviceModelId ? 'Update' : 'Create' }} Device Model -
- @else -
- - Duplicate -
- @endif
+ @foreach ($deviceModels as $deviceModel) + +
+
+ Edit Device Model +
+ +
+
+ +
+ +
+ +
+ +
+ +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + image/png + image/bmp + + +
+ +
+ + +
+ +
+ + Update Device Model +
+
+
+
+ @endforeach + @@ -396,25 +369,14 @@ new class extends Component >
- @if ($deviceModel->source === 'api') - - - - - + - @else - - - - - - - @endif + + +
diff --git a/resources/views/livewire/device-palettes/index.blade.php b/resources/views/livewire/device-palettes/index.blade.php deleted file mode 100644 index 28f99c9..0000000 --- a/resources/views/livewire/device-palettes/index.blade.php +++ /dev/null @@ -1,384 +0,0 @@ - 'required|string|max:255|unique:device_palettes,name', - 'description' => 'nullable|string|max:255', - 'grays' => 'required|integer|min:1|max:256', - 'colors' => 'nullable|array', - 'colors.*' => 'string|regex:/^#[0-9A-Fa-f]{6}$/', - 'framework_class' => 'nullable|string|max:255', - ]; - - public function mount() - { - $this->devicePalettes = DevicePalette::all(); - - return view('livewire.device-palettes.index'); - } - - public function addColor(): void - { - $this->validate(['colorInput' => 'required|string|regex:/^#[0-9A-Fa-f]{6}$/'], [ - 'colorInput.regex' => 'Color must be a valid hex color (e.g., #FF0000)', - ]); - - if (! in_array($this->colorInput, $this->colors)) { - $this->colors[] = $this->colorInput; - } - - $this->colorInput = ''; - } - - public function removeColor(int $index): void - { - unset($this->colors[$index]); - $this->colors = array_values($this->colors); - } - - public $editingDevicePaletteId; - - public $viewingDevicePaletteId; - - public function openDevicePaletteModal(?string $devicePaletteId = null, bool $viewOnly = false): void - { - if ($devicePaletteId) { - $devicePalette = DevicePalette::findOrFail($devicePaletteId); - - if ($viewOnly) { - $this->viewingDevicePaletteId = $devicePalette->id; - $this->editingDevicePaletteId = null; - } else { - $this->editingDevicePaletteId = $devicePalette->id; - $this->viewingDevicePaletteId = null; - } - - $this->name = $devicePalette->name; - $this->description = $devicePalette->description; - $this->grays = $devicePalette->grays; - - // Ensure colors is always an array and properly decoded - // The model cast should handle JSON decoding, but we'll be explicit - $colors = $devicePalette->getAttribute('colors'); - - if ($colors === null) { - $this->colors = []; - } elseif (is_string($colors)) { - $decoded = json_decode($colors, true); - $this->colors = is_array($decoded) ? array_values($decoded) : []; - } elseif (is_array($colors)) { - $this->colors = array_values($colors); // Re-index array - } else { - $this->colors = []; - } - - $this->framework_class = $devicePalette->framework_class; - } else { - $this->editingDevicePaletteId = null; - $this->viewingDevicePaletteId = null; - $this->reset(['name', 'description', 'grays', 'colors', 'framework_class']); - } - - $this->colorInput = ''; - } - - public function saveDevicePalette(): void - { - $rules = [ - 'name' => 'required|string|max:255', - 'description' => 'nullable|string|max:255', - 'grays' => 'required|integer|min:1|max:256', - 'colors' => 'nullable|array', - 'colors.*' => 'string|regex:/^#[0-9A-Fa-f]{6}$/', - 'framework_class' => 'nullable|string|max:255', - ]; - - if ($this->editingDevicePaletteId) { - $rules['name'] = 'required|string|max:255|unique:device_palettes,name,'.$this->editingDevicePaletteId; - } else { - $rules['name'] = 'required|string|max:255|unique:device_palettes,name'; - } - - $this->validate($rules); - - if ($this->editingDevicePaletteId) { - $devicePalette = DevicePalette::findOrFail($this->editingDevicePaletteId); - $devicePalette->update([ - 'name' => $this->name, - 'description' => $this->description, - 'grays' => $this->grays, - 'colors' => ! empty($this->colors) ? $this->colors : null, - 'framework_class' => $this->framework_class, - ]); - $message = 'Device palette updated successfully.'; - } else { - DevicePalette::create([ - 'name' => $this->name, - 'description' => $this->description, - 'grays' => $this->grays, - 'colors' => ! empty($this->colors) ? $this->colors : null, - 'framework_class' => $this->framework_class, - 'source' => 'manual', - ]); - $message = 'Device palette created successfully.'; - } - - $this->reset(['name', 'description', 'grays', 'colors', 'framework_class', 'colorInput', 'editingDevicePaletteId', 'viewingDevicePaletteId']); - Flux::modal('device-palette-modal')->close(); - - $this->devicePalettes = DevicePalette::all(); - session()->flash('message', $message); - } - - public function deleteDevicePalette(string $devicePaletteId): void - { - $devicePalette = DevicePalette::findOrFail($devicePaletteId); - $devicePalette->delete(); - - $this->devicePalettes = DevicePalette::all(); - session()->flash('message', 'Device palette deleted successfully.'); - } - - public function duplicateDevicePalette(string $devicePaletteId): void - { - $devicePalette = DevicePalette::findOrFail($devicePaletteId); - - $this->editingDevicePaletteId = null; - $this->viewingDevicePaletteId = null; - $this->name = $devicePalette->name.' (Copy)'; - $this->description = $devicePalette->description; - $this->grays = $devicePalette->grays; - - $colors = $devicePalette->getAttribute('colors'); - if ($colors === null) { - $this->colors = []; - } elseif (is_string($colors)) { - $decoded = json_decode($colors, true); - $this->colors = is_array($decoded) ? array_values($decoded) : []; - } elseif (is_array($colors)) { - $this->colors = array_values($colors); - } else { - $this->colors = []; - } - - $this->framework_class = $devicePalette->framework_class; - $this->colorInput = ''; - - $this->js('Flux.modal("device-palette-modal").show()'); - } -} - -?> - -
-
-
-
-
-

Device Palettes

- - - - Devices - Device Models - - -
- - Add Device Palette - -
- @if (session()->has('message')) -
- - - - - -
- @endif - - -
-
- - @if ($viewingDevicePaletteId) - View Device Palette - @elseif ($editingDevicePaletteId) - Edit Device Palette - @else - Add Device Palette - @endif - -
- -
-
- -
- -
- -
- -
- -
- -
- -
- -
- Colors - @if (!$viewingDevicePaletteId) -
- - Add -
- @endif -
- @if (!empty($colors) && is_array($colors) && count($colors) > 0) - @foreach ($colors as $index => $color) - @if (!empty($color)) -
-
- {{ $color }} - @if (!$viewingDevicePaletteId) - - @endif -
- @endif - @endforeach - @endif -
- @if (!$viewingDevicePaletteId) -

Leave empty for grayscale-only palette

- @endif -
- - @if (!$viewingDevicePaletteId) -
- - {{ $editingDevicePaletteId ? 'Update' : 'Create' }} Device Palette -
- @else -
- - Duplicate -
- @endif - -
-
- -
- - - - - - - - - - - @foreach ($devicePalettes as $devicePalette) - - - - - - - @endforeach - -
-
Description
-
-
Grays
-
-
Colors
-
-
Actions
-
-
-
{{ $devicePalette->description ?? $devicePalette->name }}
-
{{ $devicePalette->name }}
-
-
- {{ $devicePalette->grays }} - - @if ($devicePalette->colors) -
- @foreach ($devicePalette->colors as $color) -
- @endforeach - ({{ count($devicePalette->colors) }}) -
- @else - Grayscale only - @endif -
-
- - @if ($devicePalette->source === 'api') - - - - - - - @else - - - - - - - @endif - -
-
-
-
-
- diff --git a/resources/views/livewire/devices/manage.blade.php b/resources/views/livewire/devices/manage.blade.php index 646adc0..d87bd1c 100644 --- a/resources/views/livewire/devices/manage.blade.php +++ b/resources/views/livewire/devices/manage.blade.php @@ -121,16 +121,7 @@ new class extends Component { {{--@dump($devices)--}}
-
-

Devices

- - - - Device Models - Device Palettes - - -
+

Devices

Add Device diff --git a/routes/web.php b/routes/web.php index 7b7868d..e6afc1a 100644 --- a/routes/web.php +++ b/routes/web.php @@ -24,7 +24,6 @@ Route::middleware(['auth'])->group(function () { Volt::route('/devices/{device}/logs', 'devices.logs')->name('devices.logs'); Volt::route('/device-models', 'device-models.index')->name('device-models.index'); - Volt::route('/device-palettes', 'device-palettes.index')->name('device-palettes.index'); Volt::route('plugins', 'plugins.index')->name('plugins.index'); diff --git a/tests/Feature/Jobs/FetchDeviceModelsJobTest.php b/tests/Feature/Jobs/FetchDeviceModelsJobTest.php index 7674d7f..1c131c4 100644 --- a/tests/Feature/Jobs/FetchDeviceModelsJobTest.php +++ b/tests/Feature/Jobs/FetchDeviceModelsJobTest.php @@ -12,13 +12,6 @@ uses(RefreshDatabase::class); beforeEach(function (): void { DeviceModel::truncate(); - - // Mock palettes API to return empty array by default - Http::fake([ - 'usetrmnl.com/api/palettes' => Http::response([ - 'data' => [], - ], 200), - ]); }); test('fetch device models job can be dispatched', function (): void { @@ -28,7 +21,6 @@ test('fetch device models job can be dispatched', function (): void { test('fetch device models job handles successful api response', function (): void { Http::fake([ - 'usetrmnl.com/api/palettes' => Http::response(['data' => []], 200), 'usetrmnl.com/api/models' => Http::response([ 'data' => [ [ @@ -50,10 +42,6 @@ test('fetch device models job handles successful api response', function (): voi ], 200), ]); - Log::shouldReceive('info') - ->once() - ->with('Successfully fetched and updated palettes', ['count' => 0]); - Log::shouldReceive('info') ->once() ->with('Successfully fetched and updated device models', ['count' => 1]); @@ -79,7 +67,6 @@ test('fetch device models job handles successful api response', function (): voi test('fetch device models job handles multiple device models', function (): void { Http::fake([ - 'usetrmnl.com/api/palettes' => Http::response(['data' => []], 200), 'usetrmnl.com/api/models' => Http::response([ 'data' => [ [ @@ -116,10 +103,6 @@ test('fetch device models job handles multiple device models', function (): void ], 200), ]); - Log::shouldReceive('info') - ->once() - ->with('Successfully fetched and updated palettes', ['count' => 0]); - Log::shouldReceive('info') ->once() ->with('Successfully fetched and updated device models', ['count' => 2]); @@ -133,16 +116,11 @@ test('fetch device models job handles multiple device models', function (): void test('fetch device models job handles empty data array', function (): void { Http::fake([ - 'usetrmnl.com/api/palettes' => Http::response(['data' => []], 200), 'usetrmnl.com/api/models' => Http::response([ 'data' => [], ], 200), ]); - Log::shouldReceive('info') - ->once() - ->with('Successfully fetched and updated palettes', ['count' => 0]); - Log::shouldReceive('info') ->once() ->with('Successfully fetched and updated device models', ['count' => 0]); @@ -155,16 +133,11 @@ test('fetch device models job handles empty data array', function (): void { test('fetch device models job handles missing data field', function (): void { Http::fake([ - 'usetrmnl.com/api/palettes' => Http::response(['data' => []], 200), 'usetrmnl.com/api/models' => Http::response([ 'message' => 'No data available', ], 200), ]); - Log::shouldReceive('info') - ->once() - ->with('Successfully fetched and updated palettes', ['count' => 0]); - Log::shouldReceive('info') ->once() ->with('Successfully fetched and updated device models', ['count' => 0]); @@ -177,16 +150,11 @@ test('fetch device models job handles missing data field', function (): void { test('fetch device models job handles non-array data', function (): void { Http::fake([ - 'usetrmnl.com/api/palettes' => Http::response(['data' => []], 200), 'usetrmnl.com/api/models' => Http::response([ 'data' => 'invalid-data', ], 200), ]); - Log::shouldReceive('info') - ->once() - ->with('Successfully fetched and updated palettes', ['count' => 0]); - Log::shouldReceive('error') ->once() ->with('Invalid response format from device models API', Mockery::type('array')); @@ -199,16 +167,11 @@ test('fetch device models job handles non-array data', function (): void { test('fetch device models job handles api failure', function (): void { Http::fake([ - 'usetrmnl.com/api/palettes' => Http::response(['data' => []], 200), 'usetrmnl.com/api/models' => Http::response([ 'error' => 'Internal Server Error', ], 500), ]); - Log::shouldReceive('info') - ->once() - ->with('Successfully fetched and updated palettes', ['count' => 0]); - Log::shouldReceive('error') ->once() ->with('Failed to fetch device models from API', [ @@ -224,16 +187,11 @@ test('fetch device models job handles api failure', function (): void { test('fetch device models job handles network exception', function (): void { Http::fake([ - 'usetrmnl.com/api/palettes' => Http::response(['data' => []], 200), 'usetrmnl.com/api/models' => function (): void { throw new Exception('Network connection failed'); }, ]); - Log::shouldReceive('info') - ->once() - ->with('Successfully fetched and updated palettes', ['count' => 0]); - Log::shouldReceive('error') ->once() ->with('Exception occurred while fetching device models', Mockery::type('array')); @@ -246,7 +204,6 @@ test('fetch device models job handles network exception', function (): void { test('fetch device models job handles device model with missing name', function (): void { Http::fake([ - 'usetrmnl.com/api/palettes' => Http::response(['data' => []], 200), 'usetrmnl.com/api/models' => Http::response([ 'data' => [ [ @@ -257,10 +214,6 @@ test('fetch device models job handles device model with missing name', function ], 200), ]); - Log::shouldReceive('info') - ->once() - ->with('Successfully fetched and updated palettes', ['count' => 0]); - Log::shouldReceive('warning') ->once() ->with('Device model data missing name field', Mockery::type('array')); @@ -277,7 +230,6 @@ test('fetch device models job handles device model with missing name', function test('fetch device models job handles device model with partial data', function (): void { Http::fake([ - 'usetrmnl.com/api/palettes' => Http::response(['data' => []], 200), 'usetrmnl.com/api/models' => Http::response([ 'data' => [ [ @@ -288,10 +240,6 @@ test('fetch device models job handles device model with partial data', function ], 200), ]); - Log::shouldReceive('info') - ->once() - ->with('Successfully fetched and updated palettes', ['count' => 0]); - Log::shouldReceive('info') ->once() ->with('Successfully fetched and updated device models', ['count' => 1]); @@ -325,7 +273,6 @@ test('fetch device models job updates existing device model', function (): void ]); Http::fake([ - 'usetrmnl.com/api/palettes' => Http::response(['data' => []], 200), 'usetrmnl.com/api/models' => Http::response([ 'data' => [ [ @@ -347,10 +294,6 @@ test('fetch device models job updates existing device model', function (): void ], 200), ]); - Log::shouldReceive('info') - ->once() - ->with('Successfully fetched and updated palettes', ['count' => 0]); - Log::shouldReceive('info') ->once() ->with('Successfully fetched and updated device models', ['count' => 1]); @@ -368,7 +311,6 @@ test('fetch device models job updates existing device model', function (): void test('fetch device models job handles processing exception for individual model', function (): void { Http::fake([ - 'usetrmnl.com/api/palettes' => Http::response(['data' => []], 200), 'usetrmnl.com/api/models' => Http::response([ 'data' => [ [ @@ -385,10 +327,6 @@ test('fetch device models job handles processing exception for individual model' ], 200), ]); - Log::shouldReceive('info') - ->once() - ->with('Successfully fetched and updated palettes', ['count' => 0]); - Log::shouldReceive('warning') ->once() ->with('Device model data missing name field', Mockery::type('array')); diff --git a/tests/Feature/Volt/CatalogTrmnlTest.php b/tests/Feature/Volt/CatalogTrmnlTest.php index ba1b722..c3f6681 100644 --- a/tests/Feature/Volt/CatalogTrmnlTest.php +++ b/tests/Feature/Volt/CatalogTrmnlTest.php @@ -7,7 +7,7 @@ use Illuminate\Support\Facades\Http; use Livewire\Livewire; use Livewire\Volt\Volt; -it('loads newest TRMNL recipes on mount', function (): void { +it('loads newest TRMNL recipes on mount', function () { Http::fake([ 'usetrmnl.com/recipes.json*' => Http::response([ 'data' => [ @@ -31,7 +31,7 @@ it('loads newest TRMNL recipes on mount', function (): void { ->assertSee('Installs: 10'); }); -it('searches TRMNL recipes when search term is provided', function (): void { +it('searches TRMNL recipes when search term is provided', function () { Http::fake([ // First call (mount -> newest) 'usetrmnl.com/recipes.json?*' => Http::sequence() @@ -71,7 +71,7 @@ it('searches TRMNL recipes when search term is provided', function (): void { ->assertSee('Install'); }); -it('installs plugin successfully when user is authenticated', function (): void { +it('installs plugin successfully when user is authenticated', function () { $user = User::factory()->create(); Http::fake([ @@ -100,7 +100,7 @@ it('installs plugin successfully when user is authenticated', function (): void ->assertSee('Error installing plugin'); // This will fail because we don't have a real zip file }); -it('shows error when user is not authenticated', function (): void { +it('shows error when user is not authenticated', function () { Http::fake([ 'usetrmnl.com/recipes.json*' => Http::response([ 'data' => [ @@ -124,7 +124,7 @@ it('shows error when user is not authenticated', function (): void { ->assertStatus(403); // This will return 403 because user is not authenticated }); -it('shows error when plugin installation fails', function (): void { +it('shows error when plugin installation fails', function () { $user = User::factory()->create(); Http::fake([ diff --git a/tests/Feature/Volt/DevicePalettesTest.php b/tests/Feature/Volt/DevicePalettesTest.php deleted file mode 100644 index 376a4a6..0000000 --- a/tests/Feature/Volt/DevicePalettesTest.php +++ /dev/null @@ -1,575 +0,0 @@ -create(); - - $this->actingAs($user); - - $this->get(route('device-palettes.index'))->assertOk(); -}); - -test('component loads all device palettes on mount', function (): void { - $user = User::factory()->create(); - $initialCount = DevicePalette::count(); - DevicePalette::create(['name' => 'palette-1', 'grays' => 2, 'framework_class' => '']); - DevicePalette::create(['name' => 'palette-2', 'grays' => 4, 'framework_class' => '']); - DevicePalette::create(['name' => 'palette-3', 'grays' => 16, 'framework_class' => '']); - - $this->actingAs($user); - - $component = Volt::test('device-palettes.index'); - - $palettes = $component->get('devicePalettes'); - expect($palettes)->toHaveCount($initialCount + 3); -}); - -test('can open modal to create new device palette', function (): void { - $user = User::factory()->create(); - - $this->actingAs($user); - - $component = Volt::test('device-palettes.index') - ->call('openDevicePaletteModal'); - - $component - ->assertSet('editingDevicePaletteId', null) - ->assertSet('viewingDevicePaletteId', null) - ->assertSet('name', null) - ->assertSet('grays', 2); -}); - -test('can create a new device palette', function (): void { - $user = User::factory()->create(); - - $this->actingAs($user); - - $component = Volt::test('device-palettes.index') - ->set('name', 'test-palette') - ->set('description', 'Test Palette Description') - ->set('grays', 16) - ->set('colors', ['#FF0000', '#00FF00']) - ->set('framework_class', 'TestFramework') - ->call('saveDevicePalette'); - - $component->assertHasNoErrors(); - - expect(DevicePalette::where('name', 'test-palette')->exists())->toBeTrue(); - - $palette = DevicePalette::where('name', 'test-palette')->first(); - expect($palette->description)->toBe('Test Palette Description'); - expect($palette->grays)->toBe(16); - expect($palette->colors)->toBe(['#FF0000', '#00FF00']); - expect($palette->framework_class)->toBe('TestFramework'); - expect($palette->source)->toBe('manual'); -}); - -test('can create a grayscale-only palette without colors', function (): void { - $user = User::factory()->create(); - - $this->actingAs($user); - - $component = Volt::test('device-palettes.index') - ->set('name', 'grayscale-palette') - ->set('grays', 256) - ->set('colors', []) - ->call('saveDevicePalette'); - - $component->assertHasNoErrors(); - - $palette = DevicePalette::where('name', 'grayscale-palette')->first(); - expect($palette->colors)->toBeNull(); - expect($palette->grays)->toBe(256); -}); - -test('can open modal to edit existing device palette', function (): void { - $user = User::factory()->create(); - $palette = DevicePalette::create([ - 'name' => 'existing-palette', - 'description' => 'Existing Description', - 'grays' => 4, - 'colors' => ['#FF0000', '#00FF00'], - 'framework_class' => 'Framework', - 'source' => 'manual', - ]); - - $this->actingAs($user); - - $component = Volt::test('device-palettes.index') - ->call('openDevicePaletteModal', $palette->id); - - $component - ->assertSet('editingDevicePaletteId', $palette->id) - ->assertSet('name', 'existing-palette') - ->assertSet('description', 'Existing Description') - ->assertSet('grays', 4) - ->assertSet('colors', ['#FF0000', '#00FF00']) - ->assertSet('framework_class', 'Framework'); -}); - -test('can update an existing device palette', function (): void { - $user = User::factory()->create(); - $palette = DevicePalette::create([ - 'name' => 'original-palette', - 'grays' => 2, - 'framework_class' => '', - 'source' => 'manual', - ]); - - $this->actingAs($user); - - $component = Volt::test('device-palettes.index') - ->call('openDevicePaletteModal', $palette->id) - ->set('name', 'updated-palette') - ->set('description', 'Updated Description') - ->set('grays', 16) - ->set('colors', ['#0000FF']) - ->call('saveDevicePalette'); - - $component->assertHasNoErrors(); - - $palette->refresh(); - expect($palette->name)->toBe('updated-palette'); - expect($palette->description)->toBe('Updated Description'); - expect($palette->grays)->toBe(16); - expect($palette->colors)->toBe(['#0000FF']); -}); - -test('can delete a device palette', function (): void { - $user = User::factory()->create(); - $palette = DevicePalette::create([ - 'name' => 'to-delete', - 'grays' => 2, - 'framework_class' => '', - 'source' => 'manual', - ]); - - $this->actingAs($user); - - $component = Volt::test('device-palettes.index') - ->call('deleteDevicePalette', $palette->id); - - expect(DevicePalette::find($palette->id))->toBeNull(); - $component->assertSet('devicePalettes', function ($palettes) use ($palette) { - return $palettes->where('id', $palette->id)->isEmpty(); - }); -}); - -test('can duplicate a device palette', function (): void { - $user = User::factory()->create(); - $palette = DevicePalette::create([ - 'name' => 'original-palette', - 'description' => 'Original Description', - 'grays' => 4, - 'colors' => ['#FF0000', '#00FF00'], - 'framework_class' => 'Framework', - 'source' => 'manual', - ]); - - $this->actingAs($user); - - $component = Volt::test('device-palettes.index') - ->call('duplicateDevicePalette', $palette->id); - - $component - ->assertSet('editingDevicePaletteId', null) - ->assertSet('name', 'original-palette (Copy)') - ->assertSet('description', 'Original Description') - ->assertSet('grays', 4) - ->assertSet('colors', ['#FF0000', '#00FF00']) - ->assertSet('framework_class', 'Framework'); -}); - -test('can add a color to the colors array', function (): void { - $user = User::factory()->create(); - - $this->actingAs($user); - - $component = Volt::test('device-palettes.index') - ->set('colorInput', '#FF0000') - ->call('addColor'); - - $component - ->assertHasNoErrors() - ->assertSet('colors', ['#FF0000']) - ->assertSet('colorInput', ''); -}); - -test('cannot add duplicate colors', function (): void { - $user = User::factory()->create(); - - $this->actingAs($user); - - $component = Volt::test('device-palettes.index') - ->set('colors', ['#FF0000']) - ->set('colorInput', '#FF0000') - ->call('addColor'); - - $component - ->assertHasNoErrors() - ->assertSet('colors', ['#FF0000']); -}); - -test('can add multiple colors', function (): void { - $user = User::factory()->create(); - - $this->actingAs($user); - - $component = Volt::test('device-palettes.index') - ->set('colorInput', '#FF0000') - ->call('addColor') - ->set('colorInput', '#00FF00') - ->call('addColor') - ->set('colorInput', '#0000FF') - ->call('addColor'); - - $component - ->assertSet('colors', ['#FF0000', '#00FF00', '#0000FF']); -}); - -test('can remove a color from the colors array', function (): void { - $user = User::factory()->create(); - - $this->actingAs($user); - - $component = Volt::test('device-palettes.index') - ->set('colors', ['#FF0000', '#00FF00', '#0000FF']) - ->call('removeColor', 1); - - $component->assertSet('colors', ['#FF0000', '#0000FF']); -}); - -test('removing color reindexes array', function (): void { - $user = User::factory()->create(); - - $this->actingAs($user); - - $component = Volt::test('device-palettes.index') - ->set('colors', ['#FF0000', '#00FF00', '#0000FF']) - ->call('removeColor', 0); - - $colors = $component->get('colors'); - expect($colors)->toBe(['#00FF00', '#0000FF']); - expect(array_keys($colors))->toBe([0, 1]); -}); - -test('can open modal in view-only mode for api-sourced palette', function (): void { - $user = User::factory()->create(); - $palette = DevicePalette::create([ - 'name' => 'api-palette', - 'grays' => 2, - 'framework_class' => '', - 'source' => 'api', - ]); - - $this->actingAs($user); - - $component = Volt::test('device-palettes.index') - ->call('openDevicePaletteModal', $palette->id, true); - - $component - ->assertSet('viewingDevicePaletteId', $palette->id) - ->assertSet('editingDevicePaletteId', null); -}); - -test('name is required when creating device palette', function (): void { - $user = User::factory()->create(); - - $this->actingAs($user); - - $component = Volt::test('device-palettes.index') - ->set('grays', 16) - ->call('saveDevicePalette'); - - $component->assertHasErrors(['name']); -}); - -test('name must be unique when creating device palette', function (): void { - $user = User::factory()->create(); - DevicePalette::create([ - 'name' => 'existing-name', - 'grays' => 2, - 'framework_class' => '', - ]); - - $this->actingAs($user); - - $component = Volt::test('device-palettes.index') - ->set('name', 'existing-name') - ->set('grays', 16) - ->call('saveDevicePalette'); - - $component->assertHasErrors(['name']); -}); - -test('name can be same when updating device palette', function (): void { - $user = User::factory()->create(); - $palette = DevicePalette::create([ - 'name' => 'original-name', - 'grays' => 2, - 'framework_class' => '', - 'source' => 'manual', - ]); - - $this->actingAs($user); - - $component = Volt::test('device-palettes.index') - ->call('openDevicePaletteModal', $palette->id) - ->set('grays', 16) - ->call('saveDevicePalette'); - - $component->assertHasNoErrors(); -}); - -test('grays is required when creating device palette', function (): void { - $user = User::factory()->create(); - - $this->actingAs($user); - - $component = Volt::test('device-palettes.index') - ->set('name', 'test-palette') - ->set('grays', null) - ->call('saveDevicePalette'); - - $component->assertHasErrors(['grays']); -}); - -test('grays must be at least 1', function (): void { - $user = User::factory()->create(); - - $this->actingAs($user); - - $component = Volt::test('device-palettes.index') - ->set('name', 'test-palette') - ->set('grays', 0) - ->call('saveDevicePalette'); - - $component->assertHasErrors(['grays']); -}); - -test('grays must be at most 256', function (): void { - $user = User::factory()->create(); - - $this->actingAs($user); - - $component = Volt::test('device-palettes.index') - ->set('name', 'test-palette') - ->set('grays', 257) - ->call('saveDevicePalette'); - - $component->assertHasErrors(['grays']); -}); - -test('colors must be valid hex format', function (): void { - $user = User::factory()->create(); - - $this->actingAs($user); - - $component = Volt::test('device-palettes.index') - ->set('name', 'test-palette') - ->set('grays', 16) - ->set('colors', ['invalid-color', '#FF0000']) - ->call('saveDevicePalette'); - - $component->assertHasErrors(['colors.0']); -}); - -test('color input must be valid hex format when adding color', function (): void { - $user = User::factory()->create(); - - $this->actingAs($user); - - $component = Volt::test('device-palettes.index') - ->set('colorInput', 'invalid-color') - ->call('addColor'); - - $component->assertHasErrors(['colorInput']); -}); - -test('color input accepts valid hex format', function (): void { - $user = User::factory()->create(); - - $this->actingAs($user); - - $component = Volt::test('device-palettes.index') - ->set('colorInput', '#FF0000') - ->call('addColor'); - - $component->assertHasNoErrors(); -}); - -test('color input accepts lowercase hex format', function (): void { - $user = User::factory()->create(); - - $this->actingAs($user); - - $component = Volt::test('device-palettes.index') - ->set('colorInput', '#ff0000') - ->call('addColor'); - - $component->assertHasNoErrors(); -}); - -test('description can be null', function (): void { - $user = User::factory()->create(); - - $this->actingAs($user); - - $component = Volt::test('device-palettes.index') - ->set('name', 'test-palette') - ->set('grays', 16) - ->set('description', null) - ->call('saveDevicePalette'); - - $component->assertHasNoErrors(); - - $palette = DevicePalette::where('name', 'test-palette')->first(); - expect($palette->description)->toBeNull(); -}); - -test('framework class can be empty string', function (): void { - $user = User::factory()->create(); - - $this->actingAs($user); - - $component = Volt::test('device-palettes.index') - ->set('name', 'test-palette') - ->set('grays', 16) - ->set('framework_class', '') - ->call('saveDevicePalette'); - - $component->assertHasNoErrors(); - - $palette = DevicePalette::where('name', 'test-palette')->first(); - expect($palette->framework_class)->toBe(''); -}); - -test('empty colors array is saved as null', function (): void { - $user = User::factory()->create(); - - $this->actingAs($user); - - $component = Volt::test('device-palettes.index') - ->set('name', 'test-palette') - ->set('grays', 16) - ->set('colors', []) - ->call('saveDevicePalette'); - - $component->assertHasNoErrors(); - - $palette = DevicePalette::where('name', 'test-palette')->first(); - expect($palette->colors)->toBeNull(); -}); - -test('component resets form after saving', function (): void { - $user = User::factory()->create(); - - $this->actingAs($user); - - $component = Volt::test('device-palettes.index') - ->set('name', 'test-palette') - ->set('description', 'Test Description') - ->set('grays', 16) - ->set('colors', ['#FF0000']) - ->set('framework_class', 'TestFramework') - ->call('saveDevicePalette'); - - $component - ->assertSet('name', null) - ->assertSet('description', null) - ->assertSet('grays', 2) - ->assertSet('colors', []) - ->assertSet('framework_class', '') - ->assertSet('colorInput', '') - ->assertSet('editingDevicePaletteId', null) - ->assertSet('viewingDevicePaletteId', null); -}); - -test('component handles palette with null colors when editing', function (): void { - $user = User::factory()->create(); - $palette = DevicePalette::create([ - 'name' => 'grayscale-palette', - 'grays' => 2, - 'colors' => null, - 'framework_class' => '', - ]); - - $this->actingAs($user); - - $component = Volt::test('device-palettes.index') - ->call('openDevicePaletteModal', $palette->id); - - $component->assertSet('colors', []); -}); - -test('component handles palette with string colors when editing', function (): void { - $user = User::factory()->create(); - $palette = DevicePalette::create([ - 'name' => 'string-colors-palette', - 'grays' => 2, - 'framework_class' => '', - ]); - // Manually set colors as JSON string to simulate edge case - $palette->setRawAttributes(array_merge($palette->getAttributes(), [ - 'colors' => json_encode(['#FF0000', '#00FF00']), - ])); - $palette->save(); - - $this->actingAs($user); - - $component = Volt::test('device-palettes.index') - ->call('openDevicePaletteModal', $palette->id); - - $component->assertSet('colors', ['#FF0000', '#00FF00']); -}); - -test('component refreshes palette list after creating', function (): void { - $user = User::factory()->create(); - $initialCount = DevicePalette::count(); - DevicePalette::create(['name' => 'palette-1', 'grays' => 2, 'framework_class' => '']); - DevicePalette::create(['name' => 'palette-2', 'grays' => 4, 'framework_class' => '']); - - $this->actingAs($user); - - $component = Volt::test('device-palettes.index') - ->set('name', 'new-palette') - ->set('grays', 16) - ->call('saveDevicePalette'); - - $palettes = $component->get('devicePalettes'); - expect($palettes)->toHaveCount($initialCount + 3); - expect(DevicePalette::count())->toBe($initialCount + 3); -}); - -test('component refreshes palette list after deleting', function (): void { - $user = User::factory()->create(); - $initialCount = DevicePalette::count(); - $palette1 = DevicePalette::create([ - 'name' => 'palette-1', - 'grays' => 2, - 'framework_class' => '', - 'source' => 'manual', - ]); - $palette2 = DevicePalette::create([ - 'name' => 'palette-2', - 'grays' => 2, - 'framework_class' => '', - 'source' => 'manual', - ]); - - $this->actingAs($user); - - $component = Volt::test('device-palettes.index') - ->call('deleteDevicePalette', $palette1->id); - - $palettes = $component->get('devicePalettes'); - expect($palettes)->toHaveCount($initialCount + 1); - expect(DevicePalette::count())->toBe($initialCount + 1); -}); diff --git a/tests/Unit/Liquid/Filters/LocalizationTest.php b/tests/Unit/Liquid/Filters/LocalizationTest.php index 3129b1e..a52623f 100644 --- a/tests/Unit/Liquid/Filters/LocalizationTest.php +++ b/tests/Unit/Liquid/Filters/LocalizationTest.php @@ -77,7 +77,7 @@ test('l_date handles null locale parameter', function (): void { $filter = new Localization(); $date = '2025-01-11'; - $result = $filter->l_date($date, 'Y-m-d'); + $result = $filter->l_date($date, 'Y-m-d', null); // Should work the same as default expect($result)->toContain('2025');