mirror of
https://github.com/usetrmnl/byos_laravel.git
synced 2026-01-13 15:07:49 +00:00
Compare commits
6 commits
93406b83a5
...
b7bcaf6feb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b7bcaf6feb | ||
|
|
85e887f8a5 | ||
|
|
8791a5154e | ||
|
|
29d1838690 | ||
|
|
97e6beaee4 | ||
|
|
cc4aa0560c |
12 changed files with 392 additions and 472 deletions
|
|
@ -20,6 +20,7 @@ This application is a Laravel application and its main Laravel ecosystems packag
|
|||
- livewire/livewire (LIVEWIRE) - v3
|
||||
- livewire/volt (VOLT) - v1
|
||||
- larastan/larastan (LARASTAN) - v3
|
||||
- laravel/mcp (MCP) - v0
|
||||
- laravel/pint (PINT) - v1
|
||||
- laravel/sail (SAIL) - v1
|
||||
- pestphp/pest (PEST) - v4
|
||||
|
|
@ -231,7 +232,7 @@ avatar, badge, brand, breadcrumbs, button, callout, checkbox, dropdown, field, h
|
|||
@endforeach
|
||||
```
|
||||
|
||||
- Prefer lifecycle hooks like `mount()`, `updatedFoo()`) for initialization and reactive side effects:
|
||||
- Prefer lifecycle hooks like `mount()`, `updatedFoo()` for initialization and reactive side effects:
|
||||
|
||||
<code-snippet name="Lifecycle hook examples" lang="php">
|
||||
public function mount(User $user) { $this->user = $user; }
|
||||
|
|
@ -543,7 +544,7 @@ $pages->assertNoJavascriptErrors()->assertNoConsoleLogs();
|
|||
- `corePlugins` is not supported in Tailwind v4.
|
||||
- In Tailwind v4, you import Tailwind using a regular CSS `@import` statement, not using the `@tailwind` directives used in v3:
|
||||
|
||||
<code-snippet name="Tailwind v4 Import Tailwind Diff" lang="diff"
|
||||
<code-snippet name="Tailwind v4 Import Tailwind Diff" lang="diff">
|
||||
- @tailwind base;
|
||||
- @tailwind components;
|
||||
- @tailwind utilities;
|
||||
|
|
|
|||
5
.github/copilot-instructions.md
vendored
5
.github/copilot-instructions.md
vendored
|
|
@ -17,6 +17,7 @@ This application is a Laravel application and its main Laravel ecosystems packag
|
|||
- livewire/livewire (LIVEWIRE) - v3
|
||||
- livewire/volt (VOLT) - v1
|
||||
- larastan/larastan (LARASTAN) - v3
|
||||
- laravel/mcp (MCP) - v0
|
||||
- laravel/pint (PINT) - v1
|
||||
- laravel/sail (SAIL) - v1
|
||||
- pestphp/pest (PEST) - v4
|
||||
|
|
@ -228,7 +229,7 @@ avatar, badge, brand, breadcrumbs, button, callout, checkbox, dropdown, field, h
|
|||
@endforeach
|
||||
```
|
||||
|
||||
- Prefer lifecycle hooks like `mount()`, `updatedFoo()`) for initialization and reactive side effects:
|
||||
- Prefer lifecycle hooks like `mount()`, `updatedFoo()` for initialization and reactive side effects:
|
||||
|
||||
<code-snippet name="Lifecycle hook examples" lang="php">
|
||||
public function mount(User $user) { $this->user = $user; }
|
||||
|
|
@ -540,7 +541,7 @@ $pages->assertNoJavascriptErrors()->assertNoConsoleLogs();
|
|||
- `corePlugins` is not supported in Tailwind v4.
|
||||
- In Tailwind v4, you import Tailwind using a regular CSS `@import` statement, not using the `@tailwind` directives used in v3:
|
||||
|
||||
<code-snippet name="Tailwind v4 Import Tailwind Diff" lang="diff"
|
||||
<code-snippet name="Tailwind v4 Import Tailwind Diff" lang="diff">
|
||||
- @tailwind base;
|
||||
- @tailwind components;
|
||||
- @tailwind utilities;
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ This application is a Laravel application and its main Laravel ecosystems packag
|
|||
- livewire/livewire (LIVEWIRE) - v3
|
||||
- livewire/volt (VOLT) - v1
|
||||
- larastan/larastan (LARASTAN) - v3
|
||||
- laravel/mcp (MCP) - v0
|
||||
- laravel/pint (PINT) - v1
|
||||
- laravel/sail (SAIL) - v1
|
||||
- pestphp/pest (PEST) - v4
|
||||
|
|
@ -228,7 +229,7 @@ avatar, badge, brand, breadcrumbs, button, callout, checkbox, dropdown, field, h
|
|||
@endforeach
|
||||
```
|
||||
|
||||
- Prefer lifecycle hooks like `mount()`, `updatedFoo()`) for initialization and reactive side effects:
|
||||
- Prefer lifecycle hooks like `mount()`, `updatedFoo()` for initialization and reactive side effects:
|
||||
|
||||
<code-snippet name="Lifecycle hook examples" lang="php">
|
||||
public function mount(User $user) { $this->user = $user; }
|
||||
|
|
@ -540,7 +541,7 @@ $pages->assertNoJavascriptErrors()->assertNoConsoleLogs();
|
|||
- `corePlugins` is not supported in Tailwind v4.
|
||||
- In Tailwind v4, you import Tailwind using a regular CSS `@import` statement, not using the `@tailwind` directives used in v3:
|
||||
|
||||
<code-snippet name="Tailwind v4 Import Tailwind Diff" lang="diff"
|
||||
<code-snippet name="Tailwind v4 Import Tailwind Diff" lang="diff">
|
||||
- @tailwind base;
|
||||
- @tailwind components;
|
||||
- @tailwind utilities;
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ This application is a Laravel application and its main Laravel ecosystems packag
|
|||
- livewire/livewire (LIVEWIRE) - v3
|
||||
- livewire/volt (VOLT) - v1
|
||||
- larastan/larastan (LARASTAN) - v3
|
||||
- laravel/mcp (MCP) - v0
|
||||
- laravel/pint (PINT) - v1
|
||||
- laravel/sail (SAIL) - v1
|
||||
- pestphp/pest (PEST) - v4
|
||||
|
|
@ -228,7 +229,7 @@ avatar, badge, brand, breadcrumbs, button, callout, checkbox, dropdown, field, h
|
|||
@endforeach
|
||||
```
|
||||
|
||||
- Prefer lifecycle hooks like `mount()`, `updatedFoo()`) for initialization and reactive side effects:
|
||||
- Prefer lifecycle hooks like `mount()`, `updatedFoo()` for initialization and reactive side effects:
|
||||
|
||||
<code-snippet name="Lifecycle hook examples" lang="php">
|
||||
public function mount(User $user) { $this->user = $user; }
|
||||
|
|
@ -540,7 +541,7 @@ $pages->assertNoJavascriptErrors()->assertNoConsoleLogs();
|
|||
- `corePlugins` is not supported in Tailwind v4.
|
||||
- In Tailwind v4, you import Tailwind using a regular CSS `@import` statement, not using the `@tailwind` directives used in v3:
|
||||
|
||||
<code-snippet name="Tailwind v4 Import Tailwind Diff" lang="diff"
|
||||
<code-snippet name="Tailwind v4 Import Tailwind Diff" lang="diff">
|
||||
- @tailwind base;
|
||||
- @tailwind components;
|
||||
- @tailwind utilities;
|
||||
|
|
|
|||
|
|
@ -31,6 +31,35 @@ final class DeviceModel extends Model
|
|||
return null;
|
||||
}
|
||||
|
||||
//if higher then 4 return 4bit
|
||||
if ($this->bit_depth > 4) {
|
||||
return '4bit';
|
||||
}
|
||||
|
||||
return $this->bit_depth.'bit';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the scale level based on the device width.
|
||||
*/
|
||||
public function getScaleLevelAttribute(): ?string
|
||||
{
|
||||
if (! $this->width) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($this->width > 800 && $this->width <= 1000) {
|
||||
return 'large';
|
||||
}
|
||||
|
||||
if ($this->width > 1000 && $this->width <= 1400) {
|
||||
return 'xlarge';
|
||||
}
|
||||
|
||||
if ($this->width > 1400) {
|
||||
return 'xxlarge';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -345,6 +345,8 @@ class Plugin extends Model
|
|||
if ($standalone) {
|
||||
return view('trmnl-layouts.single', [
|
||||
'colorDepth' => $device?->deviceModel?->color_depth,
|
||||
'deviceVariant' => $device?->deviceModel?->name ?? 'og',
|
||||
'scaleLevel' => $device?->deviceModel?->scale_level,
|
||||
'slot' => $renderedContent,
|
||||
])->render();
|
||||
}
|
||||
|
|
@ -356,6 +358,8 @@ class Plugin extends Model
|
|||
if ($standalone) {
|
||||
return view('trmnl-layouts.single', [
|
||||
'colorDepth' => $device?->deviceModel?->color_depth,
|
||||
'deviceVariant' => $device?->deviceModel?->name ?? 'og',
|
||||
'scaleLevel' => $device?->deviceModel?->scale_level,
|
||||
'slot' => view($this->render_markup_view, [
|
||||
'size' => $size,
|
||||
'data' => $this->data_payload,
|
||||
|
|
|
|||
|
|
@ -6,79 +6,92 @@ use App\Enums\ImageFormat;
|
|||
use App\Models\Device;
|
||||
use App\Models\DeviceModel;
|
||||
use App\Models\Plugin;
|
||||
use Bnussbau\TrmnlPipeline\Stages\BrowserStage;
|
||||
use Bnussbau\TrmnlPipeline\Stages\ImageStage;
|
||||
use Bnussbau\TrmnlPipeline\TrmnlPipeline;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Imagick;
|
||||
use ImagickException;
|
||||
use ImagickPixel;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use RuntimeException;
|
||||
use Spatie\Browsershot\Browsershot;
|
||||
use Wnx\SidecarBrowsershot\BrowsershotLambda;
|
||||
|
||||
use function config;
|
||||
use function file_exists;
|
||||
use function filesize;
|
||||
|
||||
class ImageGenerationService
|
||||
{
|
||||
public static function generateImage(string $markup, $deviceId): string
|
||||
{
|
||||
$device = Device::with('deviceModel')->find($deviceId);
|
||||
$uuid = Uuid::uuid4()->toString();
|
||||
$pngPath = Storage::disk('public')->path('/images/generated/'.$uuid.'.png');
|
||||
$bmpPath = Storage::disk('public')->path('/images/generated/'.$uuid.'.bmp');
|
||||
|
||||
// Get image generation settings from DeviceModel if available, otherwise use device settings
|
||||
$imageSettings = self::getImageSettings($device);
|
||||
try {
|
||||
// Get image generation settings from DeviceModel if available, otherwise use device settings
|
||||
$imageSettings = self::getImageSettings($device);
|
||||
|
||||
// Generate PNG
|
||||
if (config('app.puppeteer_mode') === 'sidecar-aws') {
|
||||
try {
|
||||
$browsershot = BrowsershotLambda::html($markup)
|
||||
->windowSize(800, 480);
|
||||
$fileExtension = $imageSettings['mime_type'] === 'image/bmp' ? 'bmp' : 'png';
|
||||
$outputPath = Storage::disk('public')->path('/images/generated/'.$uuid.'.'.$fileExtension);
|
||||
|
||||
if (config('app.puppeteer_wait_for_network_idle')) {
|
||||
$browsershot->waitUntilNetworkIdle();
|
||||
}
|
||||
|
||||
$browsershot->save($pngPath);
|
||||
} catch (Exception $e) {
|
||||
Log::error('Failed to generate PNG: '.$e->getMessage());
|
||||
throw new RuntimeException('Failed to generate PNG: '.$e->getMessage(), 0, $e);
|
||||
// Create custom Browsershot instance if using AWS Lambda
|
||||
$browsershotInstance = null;
|
||||
if (config('app.puppeteer_mode') === 'sidecar-aws') {
|
||||
$browsershotInstance = new BrowsershotLambda();
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
$browsershot = Browsershot::html($markup)
|
||||
->setOption('args', config('app.puppeteer_docker') ? ['--no-sandbox', '--disable-setuid-sandbox', '--disable-gpu'] : []);
|
||||
if (config('app.puppeteer_wait_for_network_idle')) {
|
||||
$browsershot->waitUntilNetworkIdle();
|
||||
}
|
||||
if (config('app.puppeteer_window_size_strategy') === 'v2') {
|
||||
$browsershot->windowSize($imageSettings['width'], $imageSettings['height']);
|
||||
} else {
|
||||
$browsershot->windowSize(800, 480);
|
||||
}
|
||||
$browsershot->save($pngPath);
|
||||
} catch (Exception $e) {
|
||||
Log::error('Failed to generate PNG: '.$e->getMessage());
|
||||
throw new RuntimeException('Failed to generate PNG: '.$e->getMessage(), 0, $e);
|
||||
|
||||
$browserStage = new BrowserStage($browsershotInstance);
|
||||
$browserStage->html($markup);
|
||||
|
||||
if (config('app.puppeteer_window_size_strategy') === 'v2') {
|
||||
$browserStage
|
||||
->width($imageSettings['width'])
|
||||
->height($imageSettings['height']);
|
||||
} else {
|
||||
// default behaviour for Framework v1
|
||||
$browserStage->useDefaultDimensions();
|
||||
}
|
||||
|
||||
if (config('app.puppeteer_wait_for_network_idle')) {
|
||||
$browserStage->setBrowsershotOption('waitUntil', 'networkidle0');
|
||||
}
|
||||
|
||||
if (config('app.puppeteer_docker')) {
|
||||
$browserStage->setBrowsershotOption('args', ['--no-sandbox', '--disable-setuid-sandbox', '--disable-gpu']);
|
||||
}
|
||||
|
||||
$imageStage = new ImageStage();
|
||||
$imageStage->format($fileExtension)
|
||||
->width($imageSettings['width'])
|
||||
->height($imageSettings['height'])
|
||||
->colors($imageSettings['colors'])
|
||||
->bitDepth($imageSettings['bit_depth'])
|
||||
->rotation($imageSettings['rotation'])
|
||||
->offsetX($imageSettings['offset_x'])
|
||||
->offsetY($imageSettings['offset_y'])
|
||||
->outputPath($outputPath);
|
||||
|
||||
(new TrmnlPipeline())->pipe($browserStage)
|
||||
->pipe($imageStage)
|
||||
->process();
|
||||
|
||||
if (! file_exists($outputPath)) {
|
||||
throw new RuntimeException('Image file was not created: '.$outputPath);
|
||||
}
|
||||
|
||||
if (filesize($outputPath) === 0) {
|
||||
throw new RuntimeException('Image file is empty: '.$outputPath);
|
||||
}
|
||||
|
||||
$device->update(['current_screen_image' => $uuid]);
|
||||
Log::info("Device $device->id: updated with new image: $uuid");
|
||||
|
||||
return $uuid;
|
||||
|
||||
} catch (Exception $e) {
|
||||
Log::error('Failed to generate image: '.$e->getMessage());
|
||||
throw new RuntimeException('Failed to generate image: '.$e->getMessage(), 0, $e);
|
||||
}
|
||||
|
||||
// Validate that the PNG file was created and is valid
|
||||
if (! file_exists($pngPath)) {
|
||||
throw new RuntimeException('PNG file was not created: '.$pngPath);
|
||||
}
|
||||
|
||||
if (filesize($pngPath) === 0) {
|
||||
throw new RuntimeException('PNG file is empty: '.$pngPath);
|
||||
}
|
||||
|
||||
// Convert image based on DeviceModel settings or fallback to device settings
|
||||
self::convertImage($pngPath, $bmpPath, $imageSettings);
|
||||
|
||||
$device->update(['current_screen_image' => $uuid]);
|
||||
Log::info("Device $device->id: updated with new image: $uuid");
|
||||
|
||||
return $uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -107,17 +120,22 @@ class ImageGenerationService
|
|||
}
|
||||
|
||||
// Fallback to device settings
|
||||
$imageFormat = $device->image_format ?? ImageFormat::AUTO->value;
|
||||
$mimeType = self::getMimeTypeFromImageFormat($imageFormat);
|
||||
$colors = self::getColorsFromImageFormat($imageFormat);
|
||||
$bitDepth = self::getBitDepthFromImageFormat($imageFormat);
|
||||
|
||||
return [
|
||||
'width' => $device->width ?? 800,
|
||||
'height' => $device->height ?? 480,
|
||||
'colors' => 2,
|
||||
'bit_depth' => 1,
|
||||
'colors' => $colors,
|
||||
'bit_depth' => $bitDepth,
|
||||
'scale_factor' => 1.0,
|
||||
'rotation' => $device->rotate ?? 0,
|
||||
'mime_type' => 'image/png',
|
||||
'mime_type' => $mimeType,
|
||||
'offset_x' => 0,
|
||||
'offset_y' => 0,
|
||||
'image_format' => $device->image_format,
|
||||
'image_format' => $imageFormat,
|
||||
'use_model_settings' => false,
|
||||
];
|
||||
}
|
||||
|
|
@ -146,207 +164,48 @@ class ImageGenerationService
|
|||
}
|
||||
|
||||
/**
|
||||
* Convert image based on the provided settings
|
||||
* Get MIME type from ImageFormat
|
||||
*/
|
||||
private static function convertImage(string $pngPath, string $bmpPath, array $settings): void
|
||||
private static function getMimeTypeFromImageFormat(string $imageFormat): string
|
||||
{
|
||||
$imageFormat = $settings['image_format'];
|
||||
$useModelSettings = $settings['use_model_settings'] ?? false;
|
||||
|
||||
if ($useModelSettings) {
|
||||
// Use DeviceModel-specific conversion
|
||||
self::convertUsingModelSettings($pngPath, $bmpPath, $settings);
|
||||
} else {
|
||||
// Use legacy device-specific conversion
|
||||
self::convertUsingLegacySettings($pngPath, $bmpPath, $imageFormat, $settings);
|
||||
}
|
||||
return match ($imageFormat) {
|
||||
ImageFormat::BMP3_1BIT_SRGB->value => 'image/bmp',
|
||||
ImageFormat::PNG_8BIT_GRAYSCALE->value,
|
||||
ImageFormat::PNG_8BIT_256C->value,
|
||||
ImageFormat::PNG_2BIT_4C->value => 'image/png',
|
||||
ImageFormat::AUTO->value => 'image/png', // Default for AUTO
|
||||
default => 'image/png',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert image using DeviceModel settings
|
||||
* Get colors from ImageFormat
|
||||
*/
|
||||
private static function convertUsingModelSettings(string $pngPath, string $bmpPath, array $settings): void
|
||||
private static function getColorsFromImageFormat(string $imageFormat): int
|
||||
{
|
||||
try {
|
||||
$imagick = new Imagick($pngPath);
|
||||
|
||||
// Apply scale factor if needed
|
||||
if ($settings['scale_factor'] !== 1.0) {
|
||||
$newWidth = (int) ($settings['width'] * $settings['scale_factor']);
|
||||
$newHeight = (int) ($settings['height'] * $settings['scale_factor']);
|
||||
$imagick->resizeImage($newWidth, $newHeight, Imagick::FILTER_LANCZOS, 1, true);
|
||||
} else {
|
||||
// Resize to model dimensions if different from generated size
|
||||
if ($imagick->getImageWidth() !== $settings['width'] || $imagick->getImageHeight() !== $settings['height']) {
|
||||
$imagick->resizeImage($settings['width'], $settings['height'], Imagick::FILTER_LANCZOS, 1, true);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply rotation
|
||||
if ($settings['rotation'] !== 0) {
|
||||
$imagick->rotateImage(new ImagickPixel('black'), $settings['rotation']);
|
||||
}
|
||||
|
||||
// Apply offset if specified
|
||||
if ($settings['offset_x'] !== 0 || $settings['offset_y'] !== 0) {
|
||||
$imagick->rollImage($settings['offset_x'], $settings['offset_y']);
|
||||
}
|
||||
|
||||
// Handle special case for 4-color, 2-bit PNG
|
||||
if ($settings['colors'] === 4 && $settings['bit_depth'] === 2 && $settings['mime_type'] === 'image/png') {
|
||||
self::convertTo4Color2BitPng($imagick, $settings['width'], $settings['height']);
|
||||
} else {
|
||||
// Set image type and color depth based on model settings
|
||||
$imagick->setImageType(Imagick::IMGTYPE_GRAYSCALE);
|
||||
|
||||
if ($settings['bit_depth'] === 1) {
|
||||
$imagick->quantizeImage(2, Imagick::COLORSPACE_GRAY, 0, true, false);
|
||||
$imagick->setImageDepth(1);
|
||||
} else {
|
||||
$imagick->quantizeImage($settings['colors'], Imagick::COLORSPACE_GRAY, 0, true, false);
|
||||
$imagick->setImageDepth($settings['bit_depth']);
|
||||
}
|
||||
}
|
||||
|
||||
$imagick->stripImage();
|
||||
|
||||
// Save in the appropriate format
|
||||
if ($settings['mime_type'] === 'image/bmp') {
|
||||
$imagick->setFormat('BMP3');
|
||||
$imagick->writeImage($bmpPath);
|
||||
} else {
|
||||
$imagick->setFormat('png');
|
||||
$imagick->writeImage($pngPath);
|
||||
}
|
||||
|
||||
$imagick->clear();
|
||||
} catch (ImagickException $e) {
|
||||
throw new RuntimeException('Failed to convert image using model settings: '.$e->getMessage(), 0, $e);
|
||||
}
|
||||
return match ($imageFormat) {
|
||||
ImageFormat::BMP3_1BIT_SRGB->value,
|
||||
ImageFormat::PNG_8BIT_GRAYSCALE->value => 2,
|
||||
ImageFormat::PNG_8BIT_256C->value => 256,
|
||||
ImageFormat::PNG_2BIT_4C->value => 4,
|
||||
ImageFormat::AUTO->value => 2, // Default for AUTO
|
||||
default => 2,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert image to 4-color, 2-bit PNG using custom colormap and dithering
|
||||
* Get bit depth from ImageFormat
|
||||
*/
|
||||
private static function convertTo4Color2BitPng(Imagick $imagick, int $width, int $height): void
|
||||
private static function getBitDepthFromImageFormat(string $imageFormat): int
|
||||
{
|
||||
// Step 1: Create 4-color grayscale colormap in memory
|
||||
$colors = ['#000000', '#555555', '#aaaaaa', '#ffffff'];
|
||||
$colormap = new Imagick();
|
||||
|
||||
foreach ($colors as $color) {
|
||||
$swatch = new Imagick();
|
||||
$swatch->newImage(1, 1, new ImagickPixel($color));
|
||||
$swatch->setImageFormat('png');
|
||||
$colormap->addImage($swatch);
|
||||
}
|
||||
|
||||
$colormap = $colormap->appendImages(true); // horizontal
|
||||
$colormap->setType(Imagick::IMGTYPE_PALETTE);
|
||||
$colormap->setImageFormat('png');
|
||||
|
||||
// Step 2: Resize to target dimensions without keeping aspect ratio
|
||||
$imagick->resizeImage($width, $height, Imagick::FILTER_LANCZOS, 1, false);
|
||||
|
||||
// Step 3: Apply Floyd–Steinberg dithering
|
||||
$imagick->setOption('dither', 'FloydSteinberg');
|
||||
|
||||
// Step 4: Remap to our 4-color colormap
|
||||
// $imagick->remapImage($colormap, Imagick::DITHERMETHOD_FLOYDSTEINBERG);
|
||||
|
||||
// Step 5: Force 2-bit grayscale PNG
|
||||
$imagick->setImageFormat('png');
|
||||
$imagick->setImageDepth(2);
|
||||
$imagick->setType(Imagick::IMGTYPE_GRAYSCALE);
|
||||
|
||||
// Cleanup colormap
|
||||
$colormap->clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert image using legacy device settings
|
||||
*/
|
||||
private static function convertUsingLegacySettings(string $pngPath, string $bmpPath, string $imageFormat, array $settings): void
|
||||
{
|
||||
switch ($imageFormat) {
|
||||
case ImageFormat::BMP3_1BIT_SRGB->value:
|
||||
try {
|
||||
self::convertToBmpImageMagick($pngPath, $bmpPath);
|
||||
} catch (ImagickException $e) {
|
||||
throw new RuntimeException('Failed to convert image to BMP: '.$e->getMessage(), 0, $e);
|
||||
}
|
||||
break;
|
||||
case ImageFormat::PNG_8BIT_GRAYSCALE->value:
|
||||
case ImageFormat::PNG_8BIT_256C->value:
|
||||
try {
|
||||
self::convertToPngImageMagick($pngPath, $settings['width'], $settings['height'], $settings['rotation'], quantize: $imageFormat === ImageFormat::PNG_8BIT_GRAYSCALE->value);
|
||||
} catch (ImagickException $e) {
|
||||
throw new RuntimeException('Failed to convert image to PNG: '.$e->getMessage(), 0, $e);
|
||||
}
|
||||
break;
|
||||
case ImageFormat::AUTO->value:
|
||||
default:
|
||||
// For AUTO format, we need to check if this is a legacy device
|
||||
// This would require checking if the device has a firmware version
|
||||
// For now, we'll use the device's current logic
|
||||
try {
|
||||
self::convertToPngImageMagick($pngPath, $settings['width'], $settings['height'], $settings['rotation']);
|
||||
} catch (ImagickException $e) {
|
||||
throw new RuntimeException('Failed to convert image to PNG: '.$e->getMessage(), 0, $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ImagickException
|
||||
*/
|
||||
private static function convertToBmpImageMagick(string $pngPath, string $bmpPath): void
|
||||
{
|
||||
try {
|
||||
$imagick = new Imagick($pngPath);
|
||||
$imagick->setImageType(Imagick::IMGTYPE_GRAYSCALE);
|
||||
$imagick->quantizeImage(2, Imagick::COLORSPACE_GRAY, 0, true, false);
|
||||
$imagick->setImageDepth(1);
|
||||
$imagick->stripImage();
|
||||
$imagick->setFormat('BMP3');
|
||||
$imagick->writeImage($bmpPath);
|
||||
$imagick->clear();
|
||||
} catch (ImagickException $e) {
|
||||
Log::error('ImageMagick conversion failed for PNG: '.$pngPath.' - '.$e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ImagickException
|
||||
*/
|
||||
private static function convertToPngImageMagick(string $pngPath, ?int $width, ?int $height, ?int $rotate, $quantize = true): void
|
||||
{
|
||||
try {
|
||||
$imagick = new Imagick($pngPath);
|
||||
if ($width !== 800 || $height !== 480) {
|
||||
$imagick->resizeImage($width, $height, Imagick::FILTER_LANCZOS, 1, true);
|
||||
}
|
||||
if ($rotate !== null && $rotate !== 0) {
|
||||
$imagick->rotateImage(new ImagickPixel('black'), $rotate);
|
||||
}
|
||||
|
||||
$imagick->setImageType(Imagick::IMGTYPE_GRAYSCALE);
|
||||
$imagick->setOption('dither', 'FloydSteinberg');
|
||||
|
||||
if ($quantize) {
|
||||
$imagick->quantizeImage(2, Imagick::COLORSPACE_GRAY, 0, true, false);
|
||||
}
|
||||
$imagick->setImageDepth(8);
|
||||
$imagick->stripImage();
|
||||
|
||||
$imagick->setFormat('png');
|
||||
$imagick->writeImage($pngPath);
|
||||
$imagick->clear();
|
||||
} catch (ImagickException $e) {
|
||||
Log::error('ImageMagick conversion failed for PNG: '.$pngPath.' - '.$e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
return match ($imageFormat) {
|
||||
ImageFormat::BMP3_1BIT_SRGB->value,
|
||||
ImageFormat::PNG_8BIT_GRAYSCALE->value => 1,
|
||||
ImageFormat::PNG_8BIT_256C->value => 8,
|
||||
ImageFormat::PNG_2BIT_4C->value => 2,
|
||||
ImageFormat::AUTO->value => 1, // Default for AUTO
|
||||
default => 1,
|
||||
};
|
||||
}
|
||||
|
||||
public static function cleanupFolder(): void
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@
|
|||
"type": "project",
|
||||
"description": "TRMNL Server Implementation (BYOS) for Laravel",
|
||||
"keywords": [
|
||||
"laravel",
|
||||
"framework",
|
||||
"trmnl"
|
||||
"trmnl",
|
||||
"trmnl-server",
|
||||
"laravel"
|
||||
],
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
"ext-imagick": "*",
|
||||
"ext-zip": "*",
|
||||
"bnussbau/laravel-trmnl-blade": "2.0.*",
|
||||
"intervention/image": "^3.11",
|
||||
"bnussbau/trmnl-pipeline-php": "^0.2.0",
|
||||
"keepsuit/laravel-liquid": "^0.5.2",
|
||||
"laravel/framework": "^12.1",
|
||||
"laravel/sanctum": "^4.0",
|
||||
|
|
@ -73,7 +73,9 @@
|
|||
],
|
||||
"test": "vendor/bin/pest",
|
||||
"test-coverage": "vendor/bin/pest --coverage",
|
||||
"format": "vendor/bin/pint"
|
||||
"format": "vendor/bin/pint",
|
||||
"analyse": "vendor/bin/phpstan analyse",
|
||||
"analyze": "vendor/bin/phpstan analyse"
|
||||
},
|
||||
"extra": {
|
||||
"laravel": {
|
||||
|
|
|
|||
392
composer.lock
generated
392
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": "349a46b94103f479caae00ca7e6a99c2",
|
||||
"content-hash": "f8f7d3fd0eba117ddeb5463047ac5493",
|
||||
"packages": [
|
||||
{
|
||||
"name": "aws/aws-crt-php",
|
||||
|
|
@ -241,6 +241,77 @@
|
|||
],
|
||||
"time": "2025-09-14T07:54:31+00:00"
|
||||
},
|
||||
{
|
||||
"name": "bnussbau/trmnl-pipeline-php",
|
||||
"version": "0.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/bnussbau/trmnl-pipeline-php.git",
|
||||
"reference": "0a85e4c935a7009c469c014c6b7f2d9783d82523"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/bnussbau/trmnl-pipeline-php/zipball/0a85e4c935a7009c469c014c6b7f2d9783d82523",
|
||||
"reference": "0a85e4c935a7009c469c014c6b7f2d9783d82523",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-imagick": "*",
|
||||
"league/pipeline": "^1.0",
|
||||
"php": "^8.2",
|
||||
"spatie/browsershot": "^5.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "^1.0",
|
||||
"pestphp/pest": "^4.0",
|
||||
"phpstan/phpstan": "^1.10",
|
||||
"rector/rector": "^1.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Bnussbau\\TrmnlPipeline\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "bnussbau",
|
||||
"email": "bnussbau@users.noreply.github.com",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "Convert HTML content into optimized images for a range of e-ink devices.",
|
||||
"homepage": "https://github.com/bnussbau/trmnl-pipeline-php",
|
||||
"keywords": [
|
||||
"TRMNL",
|
||||
"bnussbau",
|
||||
"e-ink",
|
||||
"trmnl-pipeline-php"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/bnussbau/trmnl-pipeline-php/issues",
|
||||
"source": "https://github.com/bnussbau/trmnl-pipeline-php/tree/0.2.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://www.buymeacoffee.com/bnussbau",
|
||||
"type": "buy_me_a_coffee"
|
||||
},
|
||||
{
|
||||
"url": "https://usetrmnl.com/?ref=laravel-trmnl",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/bnussbau",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-09-18T16:40:28+00:00"
|
||||
},
|
||||
{
|
||||
"name": "brick/math",
|
||||
"version": "0.14.0",
|
||||
|
|
@ -1409,150 +1480,6 @@
|
|||
},
|
||||
"time": "2025-08-22T14:58:51+00:00"
|
||||
},
|
||||
{
|
||||
"name": "intervention/gif",
|
||||
"version": "4.2.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Intervention/gif.git",
|
||||
"reference": "5999eac6a39aa760fb803bc809e8909ee67b451a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Intervention/gif/zipball/5999eac6a39aa760fb803bc809e8909ee67b451a",
|
||||
"reference": "5999eac6a39aa760fb803bc809e8909ee67b451a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^2.1",
|
||||
"phpunit/phpunit": "^10.0 || ^11.0 || ^12.0",
|
||||
"slevomat/coding-standard": "~8.0",
|
||||
"squizlabs/php_codesniffer": "^3.8"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Intervention\\Gif\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Oliver Vogel",
|
||||
"email": "oliver@intervention.io",
|
||||
"homepage": "https://intervention.io/"
|
||||
}
|
||||
],
|
||||
"description": "Native PHP GIF Encoder/Decoder",
|
||||
"homepage": "https://github.com/intervention/gif",
|
||||
"keywords": [
|
||||
"animation",
|
||||
"gd",
|
||||
"gif",
|
||||
"image"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/Intervention/gif/issues",
|
||||
"source": "https://github.com/Intervention/gif/tree/4.2.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://paypal.me/interventionio",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/Intervention",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://ko-fi.com/interventionphp",
|
||||
"type": "ko_fi"
|
||||
}
|
||||
],
|
||||
"time": "2025-03-29T07:46:21+00:00"
|
||||
},
|
||||
{
|
||||
"name": "intervention/image",
|
||||
"version": "3.11.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Intervention/image.git",
|
||||
"reference": "8c49eb21a6d2572532d1bc425964264f3e496846"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Intervention/image/zipball/8c49eb21a6d2572532d1bc425964264f3e496846",
|
||||
"reference": "8c49eb21a6d2572532d1bc425964264f3e496846",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-mbstring": "*",
|
||||
"intervention/gif": "^4.2",
|
||||
"php": "^8.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "^1.6",
|
||||
"phpstan/phpstan": "^2.1",
|
||||
"phpunit/phpunit": "^10.0 || ^11.0 || ^12.0",
|
||||
"slevomat/coding-standard": "~8.0",
|
||||
"squizlabs/php_codesniffer": "^3.8"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-exif": "Recommended to be able to read EXIF data properly."
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Intervention\\Image\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Oliver Vogel",
|
||||
"email": "oliver@intervention.io",
|
||||
"homepage": "https://intervention.io/"
|
||||
}
|
||||
],
|
||||
"description": "PHP image manipulation",
|
||||
"homepage": "https://image.intervention.io/",
|
||||
"keywords": [
|
||||
"gd",
|
||||
"image",
|
||||
"imagick",
|
||||
"resize",
|
||||
"thumbnail",
|
||||
"watermark"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/Intervention/image/issues",
|
||||
"source": "https://github.com/Intervention/image/tree/3.11.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://paypal.me/interventionio",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/Intervention",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://ko-fi.com/interventionphp",
|
||||
"type": "ko_fi"
|
||||
}
|
||||
],
|
||||
"time": "2025-07-30T13:13:19+00:00"
|
||||
},
|
||||
{
|
||||
"name": "keepsuit/laravel-liquid",
|
||||
"version": "v0.5.4",
|
||||
|
|
@ -1691,16 +1618,16 @@
|
|||
},
|
||||
{
|
||||
"name": "laravel/framework",
|
||||
"version": "v12.29.0",
|
||||
"version": "v12.30.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/framework.git",
|
||||
"reference": "a9e4c73086f5ba38383e9c1d74b84fe46aac730b"
|
||||
"reference": "943603722fe95b69f216bdcda7d060c9a55f18fd"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/framework/zipball/a9e4c73086f5ba38383e9c1d74b84fe46aac730b",
|
||||
"reference": "a9e4c73086f5ba38383e9c1d74b84fe46aac730b",
|
||||
"url": "https://api.github.com/repos/laravel/framework/zipball/943603722fe95b69f216bdcda7d060c9a55f18fd",
|
||||
"reference": "943603722fe95b69f216bdcda7d060c9a55f18fd",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -1728,7 +1655,7 @@
|
|||
"monolog/monolog": "^3.0",
|
||||
"nesbot/carbon": "^3.8.4",
|
||||
"nunomaduro/termwind": "^2.0",
|
||||
"phiki/phiki": "v2.0.0",
|
||||
"phiki/phiki": "^2.0.0",
|
||||
"php": "^8.2",
|
||||
"psr/container": "^1.1.1|^2.0.1",
|
||||
"psr/log": "^1.0|^2.0|^3.0",
|
||||
|
|
@ -1907,7 +1834,7 @@
|
|||
"issues": "https://github.com/laravel/framework/issues",
|
||||
"source": "https://github.com/laravel/framework"
|
||||
},
|
||||
"time": "2025-09-16T14:15:03+00:00"
|
||||
"time": "2025-09-18T15:10:15+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/prompts",
|
||||
|
|
@ -2684,6 +2611,62 @@
|
|||
},
|
||||
"time": "2024-12-10T19:59:05+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/pipeline",
|
||||
"version": "1.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/pipeline.git",
|
||||
"reference": "9069ddfdbd5582f8a563e00cffdbeffb9a0acd01"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/pipeline/zipball/9069ddfdbd5582f8a563e00cffdbeffb9a0acd01",
|
||||
"reference": "9069ddfdbd5582f8a563e00cffdbeffb9a0acd01",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.0 || ^10.0 || ^11.5"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"League\\Pipeline\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Frank de Jonge",
|
||||
"email": "info@frenky.net",
|
||||
"role": "Author"
|
||||
},
|
||||
{
|
||||
"name": "Woody Gilk",
|
||||
"email": "woody.gilk@gmail.com",
|
||||
"role": "Maintainer"
|
||||
}
|
||||
],
|
||||
"description": "A plug and play pipeline implementation.",
|
||||
"keywords": [
|
||||
"composition",
|
||||
"design pattern",
|
||||
"pattern",
|
||||
"pipeline",
|
||||
"sequential"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/thephpleague/pipeline/issues",
|
||||
"source": "https://github.com/thephpleague/pipeline/tree/1.1.0"
|
||||
},
|
||||
"time": "2025-02-06T08:48:15+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/uri",
|
||||
"version": "7.5.1",
|
||||
|
|
@ -3839,16 +3822,16 @@
|
|||
},
|
||||
{
|
||||
"name": "phiki/phiki",
|
||||
"version": "v2.0.0",
|
||||
"version": "v2.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phikiphp/phiki.git",
|
||||
"reference": "461f6dd7e91dc3a95463b42f549ac7d0aab4702f"
|
||||
"reference": "6d735108238c03daaaef571448d8dee8187cab5e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phikiphp/phiki/zipball/461f6dd7e91dc3a95463b42f549ac7d0aab4702f",
|
||||
"reference": "461f6dd7e91dc3a95463b42f549ac7d0aab4702f",
|
||||
"url": "https://api.github.com/repos/phikiphp/phiki/zipball/6d735108238c03daaaef571448d8dee8187cab5e",
|
||||
"reference": "6d735108238c03daaaef571448d8dee8187cab5e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -3894,7 +3877,7 @@
|
|||
"description": "Syntax highlighting using TextMate grammars in PHP.",
|
||||
"support": {
|
||||
"issues": "https://github.com/phikiphp/phiki/issues",
|
||||
"source": "https://github.com/phikiphp/phiki/tree/v2.0.0"
|
||||
"source": "https://github.com/phikiphp/phiki/tree/v2.0.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -3906,7 +3889,7 @@
|
|||
"type": "other"
|
||||
}
|
||||
],
|
||||
"time": "2025-08-28T18:20:27+00:00"
|
||||
"time": "2025-09-17T18:32:40+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpoption/phpoption",
|
||||
|
|
@ -8545,35 +8528,35 @@
|
|||
},
|
||||
{
|
||||
"name": "laravel/boost",
|
||||
"version": "v1.1.5",
|
||||
"version": "v1.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/boost.git",
|
||||
"reference": "4bd1692c064b2135eb2f8f7bd8bcb710e5e75f86"
|
||||
"reference": "85f7de54a6b60f684fc9f7f6df5ad94f4f7d0d24"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/boost/zipball/4bd1692c064b2135eb2f8f7bd8bcb710e5e75f86",
|
||||
"reference": "4bd1692c064b2135eb2f8f7bd8bcb710e5e75f86",
|
||||
"url": "https://api.github.com/repos/laravel/boost/zipball/85f7de54a6b60f684fc9f7f6df5ad94f4f7d0d24",
|
||||
"reference": "85f7de54a6b60f684fc9f7f6df5ad94f4f7d0d24",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"guzzlehttp/guzzle": "^7.9",
|
||||
"illuminate/console": "^10.0|^11.0|^12.0",
|
||||
"illuminate/contracts": "^10.0|^11.0|^12.0",
|
||||
"illuminate/routing": "^10.0|^11.0|^12.0",
|
||||
"illuminate/support": "^10.0|^11.0|^12.0",
|
||||
"laravel/mcp": "^0.1.1",
|
||||
"laravel/prompts": "^0.1.9|^0.3",
|
||||
"laravel/roster": "^0.2.5",
|
||||
"guzzlehttp/guzzle": "^7.10",
|
||||
"illuminate/console": "^10.49.0|^11.45.3|^12.28.1",
|
||||
"illuminate/contracts": "^10.49.0|^11.45.3|^12.28.1",
|
||||
"illuminate/routing": "^10.49.0|^11.45.3|^12.28.1",
|
||||
"illuminate/support": "^10.49.0|^11.45.3|^12.28.1",
|
||||
"laravel/mcp": "^0.2.0",
|
||||
"laravel/prompts": "0.1.25|^0.3.6",
|
||||
"laravel/roster": "^0.2.6",
|
||||
"php": "^8.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "^1.14",
|
||||
"mockery/mockery": "^1.6",
|
||||
"orchestra/testbench": "^8.22.0|^9.0|^10.0",
|
||||
"pestphp/pest": "^2.0|^3.0",
|
||||
"phpstan/phpstan": "^2.0"
|
||||
"laravel/pint": "1.20",
|
||||
"mockery/mockery": "^1.6.12",
|
||||
"orchestra/testbench": "^8.36.0|^9.15.0|^10.6",
|
||||
"pestphp/pest": "^2.36.0|^3.8.4",
|
||||
"phpstan/phpstan": "^2.1.27"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
|
|
@ -8595,7 +8578,7 @@
|
|||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "Laravel Boost accelerates AI-assisted development to generate high-quality, Laravel-specific code.",
|
||||
"description": "Laravel Boost accelerates AI-assisted development by providing the essential context and structure that AI needs to generate high-quality, Laravel-specific code.",
|
||||
"homepage": "https://github.com/laravel/boost",
|
||||
"keywords": [
|
||||
"ai",
|
||||
|
|
@ -8606,35 +8589,41 @@
|
|||
"issues": "https://github.com/laravel/boost/issues",
|
||||
"source": "https://github.com/laravel/boost"
|
||||
},
|
||||
"time": "2025-09-18T07:33:27+00:00"
|
||||
"time": "2025-09-18T13:05:07+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/mcp",
|
||||
"version": "v0.1.1",
|
||||
"version": "v0.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/mcp.git",
|
||||
"reference": "6d6284a491f07c74d34f48dfd999ed52c567c713"
|
||||
"reference": "56fade6882756d5828cc90b86611d29616c2d754"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/mcp/zipball/6d6284a491f07c74d34f48dfd999ed52c567c713",
|
||||
"reference": "6d6284a491f07c74d34f48dfd999ed52c567c713",
|
||||
"url": "https://api.github.com/repos/laravel/mcp/zipball/56fade6882756d5828cc90b86611d29616c2d754",
|
||||
"reference": "56fade6882756d5828cc90b86611d29616c2d754",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"illuminate/console": "^10.0|^11.0|^12.0",
|
||||
"illuminate/contracts": "^10.0|^11.0|^12.0",
|
||||
"illuminate/http": "^10.0|^11.0|^12.0",
|
||||
"illuminate/routing": "^10.0|^11.0|^12.0",
|
||||
"illuminate/support": "^10.0|^11.0|^12.0",
|
||||
"illuminate/validation": "^10.0|^11.0|^12.0",
|
||||
"php": "^8.1|^8.2"
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"illuminate/console": "^10.49.0|^11.45.3|^12.28.1",
|
||||
"illuminate/container": "^10.49.0|^11.45.3|^12.28.1",
|
||||
"illuminate/contracts": "^10.49.0|^11.45.3|^12.28.1",
|
||||
"illuminate/http": "^10.49.0|^11.45.3|^12.28.1",
|
||||
"illuminate/json-schema": "^12.28.1",
|
||||
"illuminate/routing": "^10.49.0|^11.45.3|^12.28.1",
|
||||
"illuminate/support": "^10.49.0|^11.45.3|^12.28.1",
|
||||
"illuminate/validation": "^10.49.0|^11.45.3|^12.28.1",
|
||||
"php": "^8.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "^1.14",
|
||||
"orchestra/testbench": "^8.22.0|^9.0|^10.0",
|
||||
"phpstan/phpstan": "^2.0"
|
||||
"laravel/pint": "1.20.0",
|
||||
"orchestra/testbench": "^8.36.0|^9.15.0|^10.6.0",
|
||||
"pestphp/pest": "^2.36.0|^3.8.4|^4.1.0",
|
||||
"phpstan/phpstan": "^2.1.27",
|
||||
"rector/rector": "^2.1.7"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
|
|
@ -8650,8 +8639,6 @@
|
|||
"autoload": {
|
||||
"psr-4": {
|
||||
"Laravel\\Mcp\\": "src/",
|
||||
"Workbench\\App\\": "workbench/app/",
|
||||
"Laravel\\Mcp\\Tests\\": "tests/",
|
||||
"Laravel\\Mcp\\Server\\": "src/Server/"
|
||||
}
|
||||
},
|
||||
|
|
@ -8659,10 +8646,15 @@
|
|||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "The easiest way to add MCP servers to your Laravel app.",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Taylor Otwell",
|
||||
"email": "taylor@laravel.com"
|
||||
}
|
||||
],
|
||||
"description": "Rapidly build MCP servers for your Laravel applications.",
|
||||
"homepage": "https://github.com/laravel/mcp",
|
||||
"keywords": [
|
||||
"dev",
|
||||
"laravel",
|
||||
"mcp"
|
||||
],
|
||||
|
|
@ -8670,7 +8662,7 @@
|
|||
"issues": "https://github.com/laravel/mcp/issues",
|
||||
"source": "https://github.com/laravel/mcp"
|
||||
},
|
||||
"time": "2025-08-16T09:50:43+00:00"
|
||||
"time": "2025-09-18T12:58:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/pail",
|
||||
|
|
@ -8819,16 +8811,16 @@
|
|||
},
|
||||
{
|
||||
"name": "laravel/roster",
|
||||
"version": "v0.2.6",
|
||||
"version": "v0.2.7",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/roster.git",
|
||||
"reference": "5615acdf860c5a5c61d04aba44f2d3312550c514"
|
||||
"reference": "9de07bfb52cfe4e5a1fec10b8a446d6add8376cd"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/roster/zipball/5615acdf860c5a5c61d04aba44f2d3312550c514",
|
||||
"reference": "5615acdf860c5a5c61d04aba44f2d3312550c514",
|
||||
"url": "https://api.github.com/repos/laravel/roster/zipball/9de07bfb52cfe4e5a1fec10b8a446d6add8376cd",
|
||||
"reference": "9de07bfb52cfe4e5a1fec10b8a446d6add8376cd",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -8876,7 +8868,7 @@
|
|||
"issues": "https://github.com/laravel/roster/issues",
|
||||
"source": "https://github.com/laravel/roster"
|
||||
},
|
||||
"time": "2025-09-04T07:31:39+00:00"
|
||||
"time": "2025-09-18T13:53:41+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/sail",
|
||||
|
|
|
|||
|
|
@ -1,8 +1,25 @@
|
|||
@props(['mashupLayout' => '1Tx1B'])
|
||||
@props([
|
||||
'mashupLayout' => '1Tx1B',
|
||||
'noBleed' => false,
|
||||
'darkMode' => false,
|
||||
'deviceVariant' => 'og',
|
||||
'deviceOrientation' => null,
|
||||
'colorDepth' => '1bit',
|
||||
'scaleLevel' => null,
|
||||
])
|
||||
|
||||
<x-trmnl::screen>
|
||||
<x-trmnl::mashup mashup-layout="{{ $mashupLayout }}">
|
||||
{{-- The slot is used to pass the content of the mashup --}}
|
||||
{!! $slot !!}
|
||||
</x-trmnl::mashup>
|
||||
</x-trmnl::screen>
|
||||
@if(config('app.puppeteer_window_size_strategy') === 'v2')
|
||||
<x-trmnl::screen colorDepth="{{$colorDepth}}" no-bleed="{{$noBleed}}" dark-mode="{{$darkMode}}"
|
||||
device-variant="{{$deviceVariant}}" device-orientation="{{$deviceOrientation}}"
|
||||
scale-level="{{$scaleLevel}}">
|
||||
<x-trmnl::mashup mashup-layout="{{ $mashupLayout }}">
|
||||
{!! $slot !!}
|
||||
</x-trmnl::mashup>
|
||||
</x-trmnl::screen>
|
||||
@else
|
||||
<x-trmnl::screen colorDepth="{{$colorDepth}}">
|
||||
<x-trmnl::mashup mashup-layout="{{ $mashupLayout }}">
|
||||
{!! $slot !!}
|
||||
</x-trmnl::mashup>
|
||||
</x-trmnl::screen>
|
||||
@endif
|
||||
|
|
|
|||
|
|
@ -1,7 +1,20 @@
|
|||
@props([
|
||||
'noBleed' => false,
|
||||
'darkMode' => false,
|
||||
'deviceVariant' => 'og',
|
||||
'deviceOrientation' => null,
|
||||
'colorDepth' => '1bit',
|
||||
'scaleLevel' => null,
|
||||
])
|
||||
|
||||
<x-trmnl::screen colorDepth="{{$colorDepth}}">
|
||||
{!! $slot !!}
|
||||
</x-trmnl::screen>
|
||||
@if(config('app.puppeteer_window_size_strategy') === 'v2')
|
||||
<x-trmnl::screen colorDepth="{{$colorDepth}}" no-bleed="{{$noBleed}}" dark-mode="{{$darkMode}}"
|
||||
device-variant="{{$deviceVariant}}" device-orientation="{{$deviceOrientation}}"
|
||||
scale-level="{{$scaleLevel}}">
|
||||
{!! $slot !!}
|
||||
</x-trmnl::screen>
|
||||
@else
|
||||
<x-trmnl::screen colorDepth="{{$colorDepth}}">
|
||||
{!! $slot !!}
|
||||
</x-trmnl::screen>
|
||||
@endif
|
||||
|
|
|
|||
|
|
@ -99,8 +99,8 @@ it('get_image_settings uses defaults for missing device properties', function ()
|
|||
expect($settings['mime_type'])->toBe('image/png');
|
||||
expect($settings['offset_x'])->toBe(0);
|
||||
expect($settings['offset_y'])->toBe(0);
|
||||
// image_format will be null if the device doesn't have it set, which is the expected behavior
|
||||
expect($settings['image_format'])->toBeNull();
|
||||
// image_format defaults to 'auto' when not set
|
||||
expect($settings['image_format'])->toBe('auto');
|
||||
})->skipOnCi();
|
||||
|
||||
it('determine_image_format_from_model returns correct formats', function (): void {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue