mirror of
https://github.com/usetrmnl/byos_laravel.git
synced 2026-03-14 12:23:33 +00:00
Compare commits
13 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7301cac8ca | ||
|
|
5ca028b885 | ||
|
|
7864c8b7ab | ||
|
|
433bda9639 | ||
|
|
3abc67ff67 | ||
|
|
9df538de16 | ||
|
|
ba541f62f1 | ||
|
|
26b5f3ceb1 | ||
|
|
c194ab5db1 | ||
|
|
d246ac2c59 | ||
|
|
d96fb297bc | ||
|
|
d17fa1eedb | ||
|
|
00d242dac1 |
29 changed files with 1024 additions and 580 deletions
5
.github/workflows/docker-build.yml
vendored
5
.github/workflows/docker-build.yml
vendored
|
|
@ -6,7 +6,6 @@ on:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
REGISTRY: ghcr.io
|
REGISTRY: ghcr.io
|
||||||
IMAGE_NAME: ${{ github.repository }}
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-and-push:
|
build-and-push:
|
||||||
|
|
@ -40,7 +39,9 @@ jobs:
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v5
|
||||||
with:
|
with:
|
||||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
images: |
|
||||||
|
${{ env.REGISTRY }}/usetrmnl/byos_laravel
|
||||||
|
${{ env.REGISTRY }}/usetrmnl/larapaper
|
||||||
tags: |
|
tags: |
|
||||||
type=semver,pattern={{version}}
|
type=semver,pattern={{version}}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@
|
||||||
########################
|
########################
|
||||||
FROM bnussbau/serversideup-php:8.4-fpm-nginx-alpine-imagick-chromium@sha256:ed705a4060d50143ddc538c1288afff217eaf76ad5791f7556a97943854cf745 AS base
|
FROM bnussbau/serversideup-php:8.4-fpm-nginx-alpine-imagick-chromium@sha256:ed705a4060d50143ddc538c1288afff217eaf76ad5791f7556a97943854cf745 AS base
|
||||||
|
|
||||||
LABEL org.opencontainers.image.source=https://github.com/usetrmnl/byos_laravel
|
LABEL org.opencontainers.image.source=https://github.com/usetrmnl/larapaper
|
||||||
LABEL org.opencontainers.image.description="TRMNL BYOS Laravel"
|
LABEL org.opencontainers.image.description="LaraPaper"
|
||||||
LABEL org.opencontainers.image.licenses=MIT
|
LABEL org.opencontainers.image.licenses=MIT
|
||||||
|
|
||||||
ARG APP_VERSION
|
ARG APP_VERSION
|
||||||
|
|
|
||||||
22
README.md
22
README.md
|
|
@ -1,8 +1,8 @@
|
||||||
## TRMNL BYOS (PHP/Laravel)
|
## LaraPaper (PHP/Laravel)
|
||||||
|
|
||||||
[](https://github.com/usetrmnl/byos_laravel/actions/workflows/test.yml)
|
[](https://github.com/usetrmnl/larapaper/actions/workflows/test.yml)
|
||||||
|
|
||||||
TRMNL BYOS Laravel is a self-hostable implementation of a TRMNL server, built with Laravel.
|
LaraPaper is a self-hostable implementation of a TRMNL server (BYOS), built with Laravel.
|
||||||
It allows you to manage TRMNL devices, generate screens using **native plugins** (Screens API, Markup), **recipes** (130+ from the [OSS community catalog](https://bnussbau.github.io/trmnl-recipe-catalog/), 700+ from the [TRMNL catalog](https://trmnl.com/recipes), or your own), or the **API**, and can also act as a **proxy** for the native cloud service (Core). With over 50k downloads and 200+ stars, it’s the most popular community-driven BYOS.
|
It allows you to manage TRMNL devices, generate screens using **native plugins** (Screens API, Markup), **recipes** (130+ from the [OSS community catalog](https://bnussbau.github.io/trmnl-recipe-catalog/), 700+ from the [TRMNL catalog](https://trmnl.com/recipes), or your own), or the **API**, and can also act as a **proxy** for the native cloud service (Core). With over 50k downloads and 200+ stars, it’s the most popular community-driven BYOS.
|
||||||
|
|
||||||

|

|
||||||
|
|
@ -26,7 +26,7 @@ It allows you to manage TRMNL devices, generate screens using **native plugins**
|
||||||
* Custom ESP32 with TRMNL firmware
|
* Custom ESP32 with TRMNL firmware
|
||||||
* E-Reader Devices
|
* E-Reader Devices
|
||||||
* KOReader ([trmnl-koreader](https://github.com/usetrmnl/trmnl-koreader))
|
* KOReader ([trmnl-koreader](https://github.com/usetrmnl/trmnl-koreader))
|
||||||
* Kindle ([trmnl-kindle](https://github.com/usetrmnl/byos_laravel/pull/27))
|
* Kindle ([trmnl-kindle](https://github.com/usetrmnl/larapaper/pull/27))
|
||||||
* Nook ([trmnl-nook](https://github.com/usetrmnl/trmnl-nook))
|
* Nook ([trmnl-nook](https://github.com/usetrmnl/trmnl-nook))
|
||||||
* Kobo ([trmnl-kobo](https://github.com/usetrmnl/trmnl-kobo))
|
* Kobo ([trmnl-kobo](https://github.com/usetrmnl/trmnl-kobo))
|
||||||
* Android Devices with [trmnl-android](https://github.com/usetrmnl/trmnl-android)
|
* Android Devices with [trmnl-android](https://github.com/usetrmnl/trmnl-android)
|
||||||
|
|
@ -61,7 +61,7 @@ Docker Compose file located at: [docker/prod/docker-compose.yml](docker/prod/doc
|
||||||
|
|
||||||
##### Backup Database
|
##### Backup Database
|
||||||
```sh
|
```sh
|
||||||
docker ps #find container id of byos_laravel container
|
docker ps #find container id of larapaper container
|
||||||
docker cp {{CONTAINER_ID}}:/var/www/html/database/storage/database.sqlite database_backup.sqlite
|
docker cp {{CONTAINER_ID}}:/var/www/html/database/storage/database.sqlite database_backup.sqlite
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -73,11 +73,11 @@ docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
#### VPS
|
#### VPS
|
||||||
If you’re using a VPS (e.g., Hetzner) and prefer an alternative to native Docker, you can install Dokploy and deploy BYOS Laravel using the integrated [Template](https://templates.dokploy.com/?q=trmnl+byos+laravel).
|
If you’re using a VPS (e.g., Hetzner) and prefer an alternative to native Docker, you can install Dokploy and deploy LaraPaper using the integrated [Template](https://templates.dokploy.com/?q=trmnl+byos+laravel).
|
||||||
It’s a quick way to get started without having to manually manage Docker setup.
|
It’s a quick way to get started without having to manually manage Docker setup.
|
||||||
|
|
||||||
#### PikaPods
|
#### PikaPods
|
||||||
You can vote for TRMNL BYOS Laravel to be included as PikaPods Template here: [feedback.pikapods.com](https://feedback.pikapods.com/posts/842/add-app-trmnl-byos-laravel)
|
You can vote for LaraPaper to be included as PikaPods Template here: [feedback.pikapods.com](https://feedback.pikapods.com/posts/842/add-app-trmnl-byos-laravel)
|
||||||
|
|
||||||
#### Umbrel
|
#### Umbrel
|
||||||
Umbrel is supported through a community store, [see](http://github.com/bnussbau/umbrel-store).
|
Umbrel is supported through a community store, [see](http://github.com/bnussbau/umbrel-store).
|
||||||
|
|
@ -173,13 +173,13 @@ See this YouTube guide: [https://www.youtube.com/watch?v=3xehPW-PCOM](https://ww
|
||||||
### ☁️ Activate fresh TRMNL Device with Cloud Proxy
|
### ☁️ Activate fresh TRMNL Device with Cloud Proxy
|
||||||
|
|
||||||
1) Setup the TRMNL as in the official docs with the cloud service (connect one of the plugins to later verify it works)
|
1) Setup the TRMNL as in the official docs with the cloud service (connect one of the plugins to later verify it works)
|
||||||
2) Setup Laravel BYOS, create a user and login
|
2) Setup LaraPaper, create a user and login
|
||||||
3) In Laravel BYOS in the header bar, activate the toggle "Permit Auto-Join"
|
3) In LaraPaper in the header bar, activate the toggle "Permit Auto-Join"
|
||||||
4) Press and hold the button on the back of your TRMNL for 5 seconds to reactivate the captive portal (or reflash).
|
4) Press and hold the button on the back of your TRMNL for 5 seconds to reactivate the captive portal (or reflash).
|
||||||
5) Go through the setup process again, in the screen where you provide the Wi-Fi credentials there is also option to set the Server URL. Use the local address of your Laravel BYOS
|
5) Go through the setup process again, in the screen where you provide the Wi-Fi credentials there is also option to set the Server URL. Use the local address of LaraPaper.
|
||||||
6) The device should automatically appear in the device list; you can deactivate the "Permit Auto-Join" toggle again.
|
6) The device should automatically appear in the device list; you can deactivate the "Permit Auto-Join" toggle again.
|
||||||
7) In the devices list, activate the toggle "☁️ Proxy" for your device. (Make sure that the queue worker is active. In the docker image it should be running automatically.)
|
7) In the devices list, activate the toggle "☁️ Proxy" for your device. (Make sure that the queue worker is active. In the docker image it should be running automatically.)
|
||||||
8) As long as no Laravel BYOS plugin is scheduled, the device will show your cloud plugins.
|
8) As long as no LaraPaper plugin is scheduled, the device will show your cloud plugins.
|
||||||
|
|
||||||
###### Troubleshooting
|
###### Troubleshooting
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,13 @@ class GenerateScreenJob implements ShouldQueue
|
||||||
Device::find($this->deviceId)->update(['current_screen_image' => $newImageUuid]);
|
Device::find($this->deviceId)->update(['current_screen_image' => $newImageUuid]);
|
||||||
|
|
||||||
if ($this->pluginId) {
|
if ($this->pluginId) {
|
||||||
// cache current image
|
$plugin = Plugin::find($this->pluginId);
|
||||||
Plugin::find($this->pluginId)->update(['current_image' => $newImageUuid]);
|
$update = ['current_image' => $newImageUuid];
|
||||||
|
if ($plugin->plugin_type === 'recipe') {
|
||||||
|
$device = Device::with(['deviceModel', 'deviceModel.palette'])->find($this->deviceId);
|
||||||
|
$update['current_image_metadata'] = ImageGenerationService::buildImageMetadataFromDevice($device);
|
||||||
|
}
|
||||||
|
$plugin->update($update);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageGenerationService::cleanupFolder();
|
ImageGenerationService::cleanupFolder();
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ class Plugin extends Model
|
||||||
'preferred_renderer' => 'string',
|
'preferred_renderer' => 'string',
|
||||||
'plugin_type' => 'string',
|
'plugin_type' => 'string',
|
||||||
'alias' => 'boolean',
|
'alias' => 'boolean',
|
||||||
|
'current_image_metadata' => 'array',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected static function boot()
|
protected static function boot()
|
||||||
|
|
@ -71,6 +72,7 @@ class Plugin extends Model
|
||||||
'render_markup_shared',
|
'render_markup_shared',
|
||||||
])) {
|
])) {
|
||||||
$model->current_image = null;
|
$model->current_image = null;
|
||||||
|
$model->current_image_metadata = null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -222,7 +224,7 @@ class Plugin extends Model
|
||||||
if ($this->data_strategy !== 'polling' || ! $this->polling_url) {
|
if ($this->data_strategy !== 'polling' || ! $this->polling_url) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$headers = ['User-Agent' => 'usetrmnl/byos_laravel', 'Accept' => 'application/json'];
|
$headers = ['User-Agent' => 'usetrmnl/larapaper', 'Accept' => 'application/json'];
|
||||||
|
|
||||||
// resolve headers
|
// resolve headers
|
||||||
if ($this->polling_header) {
|
if ($this->polling_header) {
|
||||||
|
|
@ -573,10 +575,45 @@ class Plugin extends Model
|
||||||
$renderedContent = $template->render($liquidContext);
|
$renderedContent = $template->render($liquidContext);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Get timezone from user or fall back to app timezone
|
||||||
|
$timezone = $this->user->timezone ?? config('app.timezone');
|
||||||
|
|
||||||
|
// Calculate UTC offset in seconds
|
||||||
|
$utcOffset = (string) Carbon::now($timezone)->getOffset();
|
||||||
|
|
||||||
$renderedContent = Blade::render($markup, [
|
$renderedContent = Blade::render($markup, [
|
||||||
'size' => $size,
|
'size' => $size,
|
||||||
'data' => $this->data_payload,
|
'data' => $this->data_payload,
|
||||||
'config' => $this->configuration ?? [],
|
'config' => $this->configuration ?? [],
|
||||||
|
'trmnl' => [
|
||||||
|
'system' => [
|
||||||
|
'timestamp_utc' => now()->utc()->timestamp,
|
||||||
|
],
|
||||||
|
'user' => [
|
||||||
|
'utc_offset' => $utcOffset,
|
||||||
|
'name' => $this->user->name ?? 'Unknown User',
|
||||||
|
'locale' => 'en',
|
||||||
|
'time_zone_iana' => $timezone,
|
||||||
|
],
|
||||||
|
'device' => [
|
||||||
|
'friendly_id' => $device?->friendly_id,
|
||||||
|
'percent_charged' => $device?->battery_percent,
|
||||||
|
'wifi_strength' => $device?->wifi_strength,
|
||||||
|
'height' => $device?->height,
|
||||||
|
'width' => $device?->width,
|
||||||
|
],
|
||||||
|
'plugin_settings' => [
|
||||||
|
'instance_name' => $this->name,
|
||||||
|
'strategy' => $this->data_strategy,
|
||||||
|
'dark_mode' => $this->dark_mode ? 'yes' : 'no',
|
||||||
|
'no_screen_padding' => $this->no_bleed ? 'yes' : 'no',
|
||||||
|
'polling_headers' => $this->polling_header,
|
||||||
|
'polling_url' => $this->polling_url,
|
||||||
|
'custom_fields_values' => [
|
||||||
|
...(is_array($this->configuration) ? $this->configuration : []),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -331,36 +331,88 @@ class ImageGenerationService
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function resetIfNotCacheable(?Plugin $plugin): void
|
/**
|
||||||
|
* Ensure plugin image cache is valid for the current context. No-op for image_webhook.
|
||||||
|
* When deviceOrModel is provided (recipe only), clears cache if stored metadata does not match.
|
||||||
|
*/
|
||||||
|
public static function resetIfNotCacheable(?Plugin $plugin, Device|DeviceModel|null $deviceOrModel = null): void
|
||||||
{
|
{
|
||||||
if ($plugin?->id) {
|
if (! $plugin?->id || $plugin->plugin_type === 'image_webhook') {
|
||||||
// Image webhook plugins have finalized images that shouldn't be reset
|
return;
|
||||||
if ($plugin->plugin_type === 'image_webhook') {
|
}
|
||||||
return;
|
if ($deviceOrModel === null || $plugin->plugin_type !== 'recipe') {
|
||||||
}
|
return;
|
||||||
// Check if any devices have custom dimensions or use non-standard DeviceModels
|
}
|
||||||
$hasCustomDimensions = Device::query()
|
if ($plugin->current_image === null) {
|
||||||
->where(function ($query): void {
|
return;
|
||||||
$query->where('width', '!=', 800)
|
}
|
||||||
->orWhere('height', '!=', 480)
|
if (self::imageMetadataMatches($plugin->current_image_metadata, $deviceOrModel)) {
|
||||||
->orWhere('rotate', '!=', 0);
|
return;
|
||||||
})
|
}
|
||||||
->orWhereHas('deviceModel', function ($query): void {
|
$plugin->update([
|
||||||
// Only allow caching if all device models have standard dimensions (800x480, rotation=0)
|
'current_image' => null,
|
||||||
$query->where(function ($subQuery): void {
|
'current_image_metadata' => null,
|
||||||
$subQuery->where('width', '!=', 800)
|
]);
|
||||||
->orWhere('height', '!=', 480)
|
Log::debug("Plugin {$plugin->id}: cleared image cache due to metadata mismatch");
|
||||||
->orWhere('rotation', '!=', 0);
|
}
|
||||||
});
|
|
||||||
})
|
|
||||||
->exists();
|
|
||||||
|
|
||||||
if ($hasCustomDimensions) {
|
/**
|
||||||
// TODO cache image per device
|
* Build canonical image metadata from a Device for cache comparison.
|
||||||
$plugin->update(['current_image' => null]);
|
*
|
||||||
Log::debug('Skip cache as devices with custom dimensions or non-standard DeviceModels exist');
|
* @return array{width: int, height: int, rotation: int, palette_id: int|null, mime_type: string}
|
||||||
|
*/
|
||||||
|
public static function buildImageMetadataFromDevice(Device $device): array
|
||||||
|
{
|
||||||
|
$device->loadMissing(['deviceModel', 'deviceModel.palette']);
|
||||||
|
$settings = self::getImageSettings($device);
|
||||||
|
$paletteId = $device->palette_id ?? $device->deviceModel?->palette_id;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'width' => $settings['width'],
|
||||||
|
'height' => $settings['height'],
|
||||||
|
'rotation' => $settings['rotation'] ?? 0,
|
||||||
|
'palette_id' => $paletteId,
|
||||||
|
'mime_type' => $settings['mime_type'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build canonical image metadata from a DeviceModel for cache comparison.
|
||||||
|
*
|
||||||
|
* @return array{width: int, height: int, rotation: int, palette_id: int|null, mime_type: string}
|
||||||
|
*/
|
||||||
|
public static function buildImageMetadataFromDeviceModel(DeviceModel $model): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'width' => $model->width,
|
||||||
|
'height' => $model->height,
|
||||||
|
'rotation' => $model->rotation ?? 0,
|
||||||
|
'palette_id' => $model->palette_id,
|
||||||
|
'mime_type' => $model->mime_type,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if stored metadata matches the current device or device model.
|
||||||
|
* Returns false if stored is null or empty so cache is regenerated and metadata is stored.
|
||||||
|
*/
|
||||||
|
public static function imageMetadataMatches(?array $stored, Device|DeviceModel $deviceOrModel): bool
|
||||||
|
{
|
||||||
|
if ($stored === null || $stored === []) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$current = $deviceOrModel instanceof Device
|
||||||
|
? self::buildImageMetadataFromDevice($deviceOrModel)
|
||||||
|
: self::buildImageMetadataFromDeviceModel($deviceOrModel);
|
||||||
|
|
||||||
|
foreach (['width', 'height', 'rotation', 'palette_id', 'mime_type'] as $key) {
|
||||||
|
if (($stored[$key] ?? null) !== ($current[$key] ?? null)) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,12 @@ class IcalResponseParser implements ResponseParser
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$this->parser->parseString($body);
|
// Workaround for om/icalparser v4.0.0 bug where it fails if ORGANIZER or ATTENDEE has no parameters.
|
||||||
|
// When ORGANIZER or ATTENDEE has no parameters (no semicolon after the key),
|
||||||
|
// IcalParser::parseRow returns an empty string for $middle instead of an array,
|
||||||
|
// which causes a type error in a foreach loop in IcalParser::parseString.
|
||||||
|
$normalizedBody = preg_replace('/^(ORGANIZER|ATTENDEE):/m', '$1;CN=Unknown:', $body);
|
||||||
|
$this->parser->parseString($normalizedBody);
|
||||||
|
|
||||||
$events = $this->parser->getEvents()->sorted()->getArrayCopy();
|
$events = $this->parser->getEvents()->sorted()->getArrayCopy();
|
||||||
$windowStart = now()->subDays(7);
|
$windowStart = now()->subDays(7);
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^8.2",
|
"php": "^8.4",
|
||||||
"ext-imagick": "*",
|
"ext-imagick": "*",
|
||||||
"ext-simplexml": "*",
|
"ext-simplexml": "*",
|
||||||
"ext-zip": "*",
|
"ext-zip": "*",
|
||||||
|
|
@ -25,7 +25,7 @@
|
||||||
"laravel/tinker": "^2.10.1",
|
"laravel/tinker": "^2.10.1",
|
||||||
"livewire/flux": "^2.0",
|
"livewire/flux": "^2.0",
|
||||||
"livewire/livewire": "^4.0",
|
"livewire/livewire": "^4.0",
|
||||||
"om/icalparser": "^3.2",
|
"om/icalparser": "^4.0",
|
||||||
"spatie/browsershot": "^5.0",
|
"spatie/browsershot": "^5.0",
|
||||||
"spatie/laravel-settings": "^3.6",
|
"spatie/laravel-settings": "^3.6",
|
||||||
"stevebauman/purify": "^6.3",
|
"stevebauman/purify": "^6.3",
|
||||||
|
|
|
||||||
659
composer.lock
generated
659
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -13,7 +13,7 @@ return [
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'name' => env('APP_NAME', 'Laravel'),
|
'name' => env('APP_NAME', 'LaraPaper'),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
@ -127,6 +127,8 @@ return [
|
||||||
'enabled' => env('REGISTRATION_ENABLED', true),
|
'enabled' => env('REGISTRATION_ENABLED', true),
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'pixel_logo_enabled' => env('PIXELLOGO_ENABLED', true),
|
||||||
|
|
||||||
'force_https' => env('FORCE_HTTPS', false),
|
'force_https' => env('FORCE_HTTPS', false),
|
||||||
'puppeteer_docker' => env('PUPPETEER_DOCKER', false),
|
'puppeteer_docker' => env('PUPPETEER_DOCKER', false),
|
||||||
'puppeteer_mode' => env('PUPPETEER_MODE', 'local'),
|
'puppeteer_mode' => env('PUPPETEER_MODE', 'local'),
|
||||||
|
|
@ -154,5 +156,5 @@ return [
|
||||||
|
|
||||||
'catalog_url' => env('CATALOG_URL', 'https://raw.githubusercontent.com/bnussbau/trmnl-recipe-catalog/refs/heads/main/catalog.yaml'),
|
'catalog_url' => env('CATALOG_URL', 'https://raw.githubusercontent.com/bnussbau/trmnl-recipe-catalog/refs/heads/main/catalog.yaml'),
|
||||||
|
|
||||||
'github_repo' => env('GITHUB_REPO', 'usetrmnl/byos_laravel'),
|
'github_repo' => env('GITHUB_REPO', 'usetrmnl/larapaper'),
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('plugins', function (Blueprint $table) {
|
||||||
|
$table->json('current_image_metadata')->nullable()->after('current_image');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('plugins', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('current_image_metadata');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
namespace Database\Seeders;
|
namespace Database\Seeders;
|
||||||
|
|
||||||
use App\Models\Device;
|
use App\Models\Device;
|
||||||
|
use App\Models\Playlist;
|
||||||
use App\Models\Plugin;
|
use App\Models\Plugin;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Database\Seeder;
|
use Illuminate\Database\Seeder;
|
||||||
|
|
@ -23,9 +24,19 @@ class DatabaseSeeder extends Seeder
|
||||||
'password' => bcrypt('admin@example.com'),
|
'password' => bcrypt('admin@example.com'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
Device::factory(1)->create([
|
$device = Device::factory()->create([
|
||||||
'mac_address' => '00:00:00:00:00:00',
|
'mac_address' => '00:00:00:00:00:00',
|
||||||
'api_key' => 'test-api-key',
|
'api_key' => 'test-api-key',
|
||||||
|
'proxy_cloud' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
|
Playlist::factory()->create([
|
||||||
|
'device_id' => $device->id,
|
||||||
|
'name' => 'Default',
|
||||||
|
'is_active' => true,
|
||||||
|
'active_from' => null,
|
||||||
|
'active_until' => null,
|
||||||
|
'weekdays' => null
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Device::factory(5)->create();
|
// Device::factory(5)->create();
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
services:
|
services:
|
||||||
app:
|
app:
|
||||||
image: ghcr.io/usetrmnl/byos_laravel:latest
|
image: ghcr.io/usetrmnl/larapaper:latest
|
||||||
ports:
|
ports:
|
||||||
- "4567:8080"
|
- "4567:8080"
|
||||||
environment:
|
environment:
|
||||||
|
# Generate the APP_KEY with `echo "base64:$(openssl rand -base64 32)"`
|
||||||
#- APP_KEY=
|
#- APP_KEY=
|
||||||
- PHP_OPCACHE_ENABLE=1
|
- PHP_OPCACHE_ENABLE=1
|
||||||
- TRMNL_PROXY_REFRESH_MINUTES=15
|
- TRMNL_PROXY_REFRESH_MINUTES=15
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
#### Clone the repository
|
#### Clone the repository
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone git@github.com:usetrmnl/byos_laravel.git
|
git clone git@github.com:usetrmnl/larapaper.git
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Copy environment file
|
#### Copy environment file
|
||||||
|
|
|
||||||
213
package-lock.json
generated
213
package-lock.json
generated
|
|
@ -4,6 +4,7 @@
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
|
"name": "laravel-trmnl-server",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/commands": "^6.9.0",
|
"@codemirror/commands": "^6.9.0",
|
||||||
"@codemirror/lang-css": "^6.3.1",
|
"@codemirror/lang-css": "^6.3.1",
|
||||||
|
|
@ -816,9 +817,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz",
|
||||||
"integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==",
|
"integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
|
@ -829,9 +830,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm64": {
|
"node_modules/@rollup/rollup-android-arm64": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz",
|
||||||
"integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==",
|
"integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -842,9 +843,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz",
|
||||||
"integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==",
|
"integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -855,9 +856,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-x64": {
|
"node_modules/@rollup/rollup-darwin-x64": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz",
|
||||||
"integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==",
|
"integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -868,9 +869,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz",
|
||||||
"integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==",
|
"integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -881,9 +882,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz",
|
||||||
"integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==",
|
"integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -894,9 +895,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz",
|
||||||
"integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==",
|
"integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
|
@ -907,9 +908,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz",
|
||||||
"integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==",
|
"integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
|
@ -920,9 +921,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz",
|
||||||
"integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==",
|
"integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -933,9 +934,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz",
|
||||||
"integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==",
|
"integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -946,9 +947,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-loong64-gnu": {
|
"node_modules/@rollup/rollup-linux-loong64-gnu": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz",
|
||||||
"integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==",
|
"integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
|
|
@ -959,9 +960,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-loong64-musl": {
|
"node_modules/@rollup/rollup-linux-loong64-musl": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz",
|
||||||
"integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==",
|
"integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
|
|
@ -972,9 +973,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz",
|
||||||
"integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==",
|
"integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
|
|
@ -985,9 +986,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-ppc64-musl": {
|
"node_modules/@rollup/rollup-linux-ppc64-musl": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz",
|
||||||
"integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==",
|
"integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
|
|
@ -998,9 +999,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz",
|
||||||
"integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==",
|
"integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
|
|
@ -1011,9 +1012,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz",
|
||||||
"integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==",
|
"integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
|
|
@ -1024,9 +1025,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz",
|
||||||
"integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==",
|
"integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
|
|
@ -1050,9 +1051,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz",
|
||||||
"integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==",
|
"integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -1063,9 +1064,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-openbsd-x64": {
|
"node_modules/@rollup/rollup-openbsd-x64": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz",
|
||||||
"integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==",
|
"integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -1076,9 +1077,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-openharmony-arm64": {
|
"node_modules/@rollup/rollup-openharmony-arm64": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz",
|
||||||
"integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==",
|
"integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -1089,9 +1090,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz",
|
||||||
"integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==",
|
"integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -1102,9 +1103,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz",
|
||||||
"integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==",
|
"integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
|
|
@ -1115,9 +1116,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz",
|
||||||
"integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==",
|
"integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -1128,9 +1129,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz",
|
||||||
"integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==",
|
"integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -1652,9 +1653,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/basic-ftp": {
|
"node_modules/basic-ftp": {
|
||||||
"version": "5.1.0",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.2.0.tgz",
|
||||||
"integrity": "sha512-RkaJzeJKDbaDWTIPiJwubyljaEPwpVWkm9Rt5h9Nd6h7tEXTJ3VB4qxdZBioV7JO5yLUaOKwz7vDOzlncUsegw==",
|
"integrity": "sha512-VoMINM2rqJwJgfdHq6RiUudKt2BV+FY5ZFezP/ypmwayk68+NzzAQy4XXLlqsGD4MCzq3DrmNFD/uUmBJuGoXw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.0.0"
|
"node": ">=10.0.0"
|
||||||
|
|
@ -3149,9 +3150,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rollup": {
|
"node_modules/rollup": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz",
|
||||||
"integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==",
|
"integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/estree": "1.0.8"
|
"@types/estree": "1.0.8"
|
||||||
|
|
@ -3164,38 +3165,38 @@
|
||||||
"npm": ">=8.0.0"
|
"npm": ">=8.0.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@rollup/rollup-android-arm-eabi": "4.57.1",
|
"@rollup/rollup-android-arm-eabi": "4.59.0",
|
||||||
"@rollup/rollup-android-arm64": "4.57.1",
|
"@rollup/rollup-android-arm64": "4.59.0",
|
||||||
"@rollup/rollup-darwin-arm64": "4.57.1",
|
"@rollup/rollup-darwin-arm64": "4.59.0",
|
||||||
"@rollup/rollup-darwin-x64": "4.57.1",
|
"@rollup/rollup-darwin-x64": "4.59.0",
|
||||||
"@rollup/rollup-freebsd-arm64": "4.57.1",
|
"@rollup/rollup-freebsd-arm64": "4.59.0",
|
||||||
"@rollup/rollup-freebsd-x64": "4.57.1",
|
"@rollup/rollup-freebsd-x64": "4.59.0",
|
||||||
"@rollup/rollup-linux-arm-gnueabihf": "4.57.1",
|
"@rollup/rollup-linux-arm-gnueabihf": "4.59.0",
|
||||||
"@rollup/rollup-linux-arm-musleabihf": "4.57.1",
|
"@rollup/rollup-linux-arm-musleabihf": "4.59.0",
|
||||||
"@rollup/rollup-linux-arm64-gnu": "4.57.1",
|
"@rollup/rollup-linux-arm64-gnu": "4.59.0",
|
||||||
"@rollup/rollup-linux-arm64-musl": "4.57.1",
|
"@rollup/rollup-linux-arm64-musl": "4.59.0",
|
||||||
"@rollup/rollup-linux-loong64-gnu": "4.57.1",
|
"@rollup/rollup-linux-loong64-gnu": "4.59.0",
|
||||||
"@rollup/rollup-linux-loong64-musl": "4.57.1",
|
"@rollup/rollup-linux-loong64-musl": "4.59.0",
|
||||||
"@rollup/rollup-linux-ppc64-gnu": "4.57.1",
|
"@rollup/rollup-linux-ppc64-gnu": "4.59.0",
|
||||||
"@rollup/rollup-linux-ppc64-musl": "4.57.1",
|
"@rollup/rollup-linux-ppc64-musl": "4.59.0",
|
||||||
"@rollup/rollup-linux-riscv64-gnu": "4.57.1",
|
"@rollup/rollup-linux-riscv64-gnu": "4.59.0",
|
||||||
"@rollup/rollup-linux-riscv64-musl": "4.57.1",
|
"@rollup/rollup-linux-riscv64-musl": "4.59.0",
|
||||||
"@rollup/rollup-linux-s390x-gnu": "4.57.1",
|
"@rollup/rollup-linux-s390x-gnu": "4.59.0",
|
||||||
"@rollup/rollup-linux-x64-gnu": "4.57.1",
|
"@rollup/rollup-linux-x64-gnu": "4.59.0",
|
||||||
"@rollup/rollup-linux-x64-musl": "4.57.1",
|
"@rollup/rollup-linux-x64-musl": "4.59.0",
|
||||||
"@rollup/rollup-openbsd-x64": "4.57.1",
|
"@rollup/rollup-openbsd-x64": "4.59.0",
|
||||||
"@rollup/rollup-openharmony-arm64": "4.57.1",
|
"@rollup/rollup-openharmony-arm64": "4.59.0",
|
||||||
"@rollup/rollup-win32-arm64-msvc": "4.57.1",
|
"@rollup/rollup-win32-arm64-msvc": "4.59.0",
|
||||||
"@rollup/rollup-win32-ia32-msvc": "4.57.1",
|
"@rollup/rollup-win32-ia32-msvc": "4.59.0",
|
||||||
"@rollup/rollup-win32-x64-gnu": "4.57.1",
|
"@rollup/rollup-win32-x64-gnu": "4.59.0",
|
||||||
"@rollup/rollup-win32-x64-msvc": "4.57.1",
|
"@rollup/rollup-win32-x64-msvc": "4.59.0",
|
||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rollup/node_modules/@rollup/rollup-linux-x64-gnu": {
|
"node_modules/rollup/node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||||
"version": "4.57.1",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz",
|
||||||
"integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==",
|
"integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -2,5 +2,9 @@
|
||||||
<x-app-logo-icon class="size-5 fill-current text-white dark:text-black" />
|
<x-app-logo-icon class="size-5 fill-current text-white dark:text-black" />
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-1 grid flex-1 text-left text-sm">
|
<div class="ml-1 grid flex-1 text-left text-sm">
|
||||||
<span class="mb-0.5 truncate leading-none font-semibold">TRMNL BYOS Laravel</span>
|
@if(config('app.pixel_logo_enabled'))
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-7 pt-1.5 dark:fill-white" viewBox="0 0 1000 150"><path fill-rule="evenodd" d="M894.75 119.01V47.28h16.1V31.54h55.63v15.74h16.29v24.7h-23.43V56.43H918V119Zm-90.59 0v-15.55h-16.1V47.28h16.1V31.54h55.63v15.74h16.29v24.7h-23.42V56.43H811.3v6.22h32.39v25.07h-32.4v6.4h41.37V78.2h23.42v25.07h-16.29v15.74Zm-122.8 30.38V47.28h16.11V31.54h55.63v15.74h16.3v56.18h-16.38V119H704.6v30.4Zm23.25-55.26h41.36v-37.7H704.6ZM574.5 119v-15.55h-16.1V47.28h16.1V31.54h55.63v15.73h16.37v46.85h16.2v24.9h-23.42v-15.56h-9.15V119Zm7.13-24.89H623v-37.7h-41.36Zm-124.44 24.9V15.97h16.1V.25h55.64v15.73h16.29v56h-16.38v15.74h-48.4v31.3Zm23.24-56.37h41.36V25.32h-41.36ZM357.64 119v-15.55h-16.1V47.28h16.1V31.54h55.63v15.73h16.38v46.85h16.2v24.9h-23.43v-15.56h-9.15V119Zm7.14-24.89h41.36v-37.7h-41.36Zm-124.44 24.9V47.27h16.1V31.54h55.63v15.74h16.3v24.7h-23.43V56.43h-41.36V119Zm-106.87 0v-15.56h-16.1V47.28h16.1V31.54h55.63v15.73h16.37v46.85h16.2v24.9h-23.42v-15.56h-9.15V119Zm7.13-24.9h41.36v-37.7H140.6Zm-108.33 24.9v-15.56h-16.1V.25H39.4v93.88h41.36V78.39h23.43v25.07h-16.3V119Z"/></svg>
|
||||||
|
@else
|
||||||
|
<span class="mb-0.5 truncate leading-none font-semibold">LaraPaper</span>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,10 @@
|
||||||
])
|
])
|
||||||
|
|
||||||
<div class="flex w-full flex-col text-center">
|
<div class="flex w-full flex-col text-center">
|
||||||
<flux:heading size="xl">{{ $title }}</flux:heading>
|
@if(config('app.pixel_logo_enabled'))
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 dark:fill-white" viewBox="0 0 1000 150"><path fill-rule="evenodd" d="M894.75 119.01V47.28h16.1V31.54h55.63v15.74h16.29v24.7h-23.43V56.43H918V119Zm-90.59 0v-15.55h-16.1V47.28h16.1V31.54h55.63v15.74h16.29v24.7h-23.42V56.43H811.3v6.22h32.39v25.07h-32.4v6.4h41.37V78.2h23.42v25.07h-16.29v15.74Zm-122.8 30.38V47.28h16.11V31.54h55.63v15.74h16.3v56.18h-16.38V119H704.6v30.4Zm23.25-55.26h41.36v-37.7H704.6ZM574.5 119v-15.55h-16.1V47.28h16.1V31.54h55.63v15.73h16.37v46.85h16.2v24.9h-23.42v-15.56h-9.15V119Zm7.13-24.89H623v-37.7h-41.36Zm-124.44 24.9V15.97h16.1V.25h55.64v15.73h16.29v56h-16.38v15.74h-48.4v31.3Zm23.24-56.37h41.36V25.32h-41.36ZM357.64 119v-15.55h-16.1V47.28h16.1V31.54h55.63v15.73h16.38v46.85h16.2v24.9h-23.43v-15.56h-9.15V119Zm7.14-24.89h41.36v-37.7h-41.36Zm-124.44 24.9V47.27h16.1V31.54h55.63v15.74h16.3v24.7h-23.43V56.43h-41.36V119Zm-106.87 0v-15.56h-16.1V47.28h16.1V31.54h55.63v15.73h16.37v46.85h16.2v24.9h-23.42v-15.56h-9.15V119Zm7.13-24.9h41.36v-37.7H140.6Zm-108.33 24.9v-15.56h-16.1V.25H39.4v93.88h41.36V78.39h23.43v25.07h-16.3V119Z"/></svg>
|
||||||
|
@else
|
||||||
|
<flux:heading size="xl">LaraPaper</flux:heading>
|
||||||
|
@endif
|
||||||
<flux:subheading>{{ $description }}</flux:subheading>
|
<flux:subheading>{{ $description }}</flux:subheading>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,10 @@
|
||||||
<x-trmnl::view>
|
<x-trmnl::view>
|
||||||
<x-trmnl::layout>
|
<x-trmnl::layout>
|
||||||
<x-trmnl::richtext gapSize="large" align="center">
|
<x-trmnl::richtext gapSize="large" align="center">
|
||||||
<x-trmnl::title>Welcome to BYOS Laravel!</x-trmnl::title>
|
<x-trmnl::title>Welcome to LaraPaper!</x-trmnl::title>
|
||||||
<x-trmnl::content>Your device is connected.</x-trmnl::content>
|
<x-trmnl::content>Your device is connected.</x-trmnl::content>
|
||||||
</x-trmnl::richtext>
|
</x-trmnl::richtext>
|
||||||
</x-trmnl::layout>
|
</x-trmnl::layout>
|
||||||
<x-trmnl::title-bar title="byos_laravel"/>
|
<x-trmnl::title-bar title="LaraPaper"/>
|
||||||
</x-trmnl::view>
|
</x-trmnl::view>
|
||||||
</x-trmnl::screen>
|
</x-trmnl::screen>
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,6 @@
|
||||||
<x-trmnl::title>Sleep Mode</x-trmnl::title>
|
<x-trmnl::title>Sleep Mode</x-trmnl::title>
|
||||||
</x-trmnl::richtext>
|
</x-trmnl::richtext>
|
||||||
</x-trmnl::layout>
|
</x-trmnl::layout>
|
||||||
<x-trmnl::title-bar title="byos_laravel"/>
|
<x-trmnl::title-bar title="LaraPaper"/>
|
||||||
</x-trmnl::view>
|
</x-trmnl::view>
|
||||||
</x-trmnl::screen>
|
</x-trmnl::screen>
|
||||||
|
|
|
||||||
|
|
@ -264,7 +264,7 @@ new class extends Component
|
||||||
{{-- <li><flux:text><code>date: "%N"</code> is unsupported. Use <code>date: "u"</code> instead </flux:text></li>--}}
|
{{-- <li><flux:text><code>date: "%N"</code> is unsupported. Use <code>date: "u"</code> instead </flux:text></li>--}}
|
||||||
{{-- </ul>--}}
|
{{-- </ul>--}}
|
||||||
</ul>
|
</ul>
|
||||||
<flux:text class="mt-1">Please report <a href="https://github.com/usetrmnl/byos_laravel/issues/new" target="_blank" class="underline">issues on GitHub</a>. Include your example zip file.</flux:text></li>
|
<flux:text class="mt-1">Please report <a href="https://github.com/usetrmnl/larapaper/issues/new" target="_blank" class="underline">issues on GitHub</a>. Include your example zip file.</flux:text></li>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form wire:submit="importZip">
|
<form wire:submit="importZip">
|
||||||
|
|
@ -315,7 +315,7 @@ new class extends Component
|
||||||
<li><flux:text>API responses in formats other than <span class="font-mono">JSON</span> are not yet fully supported.</flux:text></li>
|
<li><flux:text>API responses in formats other than <span class="font-mono">JSON</span> are not yet fully supported.</flux:text></li>
|
||||||
<li><flux:text>There are limitations in payload size (Data Payload, Template).</flux:text></li>
|
<li><flux:text>There are limitations in payload size (Data Payload, Template).</flux:text></li>
|
||||||
</ul>
|
</ul>
|
||||||
<flux:text class="mt-1">Please report issues, aside from the known limitations, on <a href="https://github.com/usetrmnl/byos_laravel/issues/new" target="_blank" class="underline">GitHub</a>. Include the recipe URL.</flux:text></li>
|
<flux:text class="mt-1">Please report issues, aside from the known limitations, on <a href="https://github.com/usetrmnl/larapaper/issues/new" target="_blank" class="underline">GitHub</a>. Include the recipe URL.</flux:text></li>
|
||||||
</flux:callout>
|
</flux:callout>
|
||||||
</div>
|
</div>
|
||||||
<livewire:catalog.trmnl />
|
<livewire:catalog.trmnl />
|
||||||
|
|
|
||||||
|
|
@ -72,8 +72,8 @@ new class extends Component
|
||||||
<x-trmnl::view>
|
<x-trmnl::view>
|
||||||
<x-trmnl::layout>
|
<x-trmnl::layout>
|
||||||
<x-trmnl::richtext gapSize="large" align="center">
|
<x-trmnl::richtext gapSize="large" align="center">
|
||||||
<x-trmnl::title>TRMNL BYOS Laravel</x-trmnl::title>
|
<x-trmnl::title>LaraPaper</x-trmnl::title>
|
||||||
<x-trmnl::content>“This screen was rendered by BYOS Laravel”</x-trmnl::content>
|
<x-trmnl::content>“This screen was rendered by BYOS LaraPaper”</x-trmnl::content>
|
||||||
<x-trmnl::label variant="underline">Benjamin Nussbaum</x-trmnl::label>
|
<x-trmnl::label variant="underline">Benjamin Nussbaum</x-trmnl::label>
|
||||||
</x-trmnl::richtext>
|
</x-trmnl::richtext>
|
||||||
</x-trmnl::layout>
|
</x-trmnl::layout>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
|
||||||
<title>{{ $title ?? 'TRMNL BYOS Laravel' }}</title>
|
<title>{{ $title ?? 'LaraPaper' }}</title>
|
||||||
|
|
||||||
<link rel="preconnect" href="https://fonts.bunny.net">
|
<link rel="preconnect" href="https://fonts.bunny.net">
|
||||||
<link href="https://fonts.bunny.net/css?family=inter:400,500,600&display=swap" rel="stylesheet" />
|
<link href="https://fonts.bunny.net/css?family=inter:400,500,600&display=swap" rel="stylesheet" />
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<x-layouts::auth.card>
|
<x-layouts::auth.card>
|
||||||
<div class="flex flex-col gap-6">
|
<div class="flex flex-col gap-6">
|
||||||
<x-auth-header title="TRMNL BYOS Laravel" description="Server is up and running."/>
|
<x-auth-header title="LaraPaper" description="Server is up and running."/>
|
||||||
</div>
|
</div>
|
||||||
<header class="w-full lg:max-w-4xl max-w-[335px] text-sm mt-6 not-has-[nav]:hidden">
|
<header class="w-full lg:max-w-4xl max-w-[335px] text-sm mt-6 not-has-[nav]:hidden">
|
||||||
@if (Route::has('login'))
|
@if (Route::has('login'))
|
||||||
|
|
@ -33,7 +33,7 @@
|
||||||
</header>
|
</header>
|
||||||
@auth
|
@auth
|
||||||
@if(config('app.version'))
|
@if(config('app.version'))
|
||||||
<flux:text class="text-xs">Version: <a href="https://github.com/usetrmnl/byos_laravel/releases/tag/{{ config('app.version') }}"
|
<flux:text class="text-xs">Version: <a href="https://github.com/{{ config('app.github_repo') }}/releases/tag/{{ config('app.version') }}"
|
||||||
target="_blank">{{ config('app.version') }}</a>
|
target="_blank">{{ config('app.version') }}</a>
|
||||||
</flux:text>
|
</flux:text>
|
||||||
@endif
|
@endif
|
||||||
|
|
|
||||||
|
|
@ -88,8 +88,8 @@ Route::get('/display', function (Request $request) {
|
||||||
$refreshTimeOverride = $playlistItem->playlist()->first()->refresh_time;
|
$refreshTimeOverride = $playlistItem->playlist()->first()->refresh_time;
|
||||||
$plugin = $playlistItem->plugin;
|
$plugin = $playlistItem->plugin;
|
||||||
|
|
||||||
// Reset cache if Devices with different dimensions exist
|
ImageGenerationService::resetIfNotCacheable($plugin, $device);
|
||||||
ImageGenerationService::resetIfNotCacheable($plugin);
|
$plugin->refresh();
|
||||||
|
|
||||||
// Check and update stale data if needed
|
// Check and update stale data if needed
|
||||||
if ($plugin->isDataStale() || $plugin->current_image === null) {
|
if ($plugin->isDataStale() || $plugin->current_image === null) {
|
||||||
|
|
@ -699,6 +699,9 @@ Route::get('/display/{uuid}/alias', function (Request $request, string $uuid) {
|
||||||
], 404);
|
], 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImageGenerationService::resetIfNotCacheable($plugin, $deviceModel);
|
||||||
|
$plugin->refresh();
|
||||||
|
|
||||||
// Check if we can use cached image (only for og_png and if data is not stale)
|
// Check if we can use cached image (only for og_png and if data is not stale)
|
||||||
$useCache = $deviceModelName === 'og_png' && ! $plugin->isDataStale() && $plugin->current_image !== null;
|
$useCache = $deviceModelName === 'og_png' && ! $plugin->isDataStale() && $plugin->current_image !== null;
|
||||||
|
|
||||||
|
|
@ -744,9 +747,13 @@ Route::get('/display/{uuid}/alias', function (Request $request, string $uuid) {
|
||||||
palette: $deviceModel->palette
|
palette: $deviceModel->palette
|
||||||
);
|
);
|
||||||
|
|
||||||
// Update plugin cache if using og_png
|
// Update plugin cache if using og_png (recipes only get metadata for cache comparison)
|
||||||
if ($deviceModelName === 'og_png') {
|
if ($deviceModelName === 'og_png') {
|
||||||
$plugin->update(['current_image' => $imageUuid]);
|
$update = ['current_image' => $imageUuid];
|
||||||
|
if ($plugin->plugin_type === 'recipe') {
|
||||||
|
$update['current_image_metadata'] = ImageGenerationService::buildImageMetadataFromDeviceModel($deviceModel);
|
||||||
|
}
|
||||||
|
$plugin->update($update);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the generated image
|
// Return the generated image
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
use App\Jobs\GenerateScreenJob;
|
use App\Jobs\GenerateScreenJob;
|
||||||
use App\Models\Device;
|
use App\Models\Device;
|
||||||
|
use App\Models\DeviceModel;
|
||||||
|
use App\Models\Plugin;
|
||||||
use Bnussbau\TrmnlPipeline\TrmnlPipeline;
|
use Bnussbau\TrmnlPipeline\TrmnlPipeline;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
|
@ -58,3 +60,26 @@ test('it preserves gitignore file during cleanup', function (): void {
|
||||||
|
|
||||||
Storage::disk('public')->assertExists('/images/generated/.gitignore');
|
Storage::disk('public')->assertExists('/images/generated/.gitignore');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('it saves current_image_metadata for recipe plugins', function (): void {
|
||||||
|
$deviceModel = DeviceModel::factory()->create([
|
||||||
|
'width' => 800,
|
||||||
|
'height' => 480,
|
||||||
|
'rotation' => 0,
|
||||||
|
'mime_type' => 'image/png',
|
||||||
|
'palette_id' => null,
|
||||||
|
]);
|
||||||
|
$device = Device::factory()->create(['device_model_id' => $deviceModel->id]);
|
||||||
|
$plugin = Plugin::factory()->create(['plugin_type' => 'recipe']);
|
||||||
|
|
||||||
|
$job = new GenerateScreenJob($device->id, $plugin->id, '<div>Test</div>');
|
||||||
|
$job->handle();
|
||||||
|
|
||||||
|
$plugin->refresh();
|
||||||
|
expect($plugin->current_image)->not->toBeNull();
|
||||||
|
expect($plugin->current_image_metadata)->toBeArray();
|
||||||
|
expect($plugin->current_image_metadata)->toHaveKeys(['width', 'height', 'rotation', 'palette_id', 'mime_type']);
|
||||||
|
expect($plugin->current_image_metadata['width'])->toBe(800);
|
||||||
|
expect($plugin->current_image_metadata['height'])->toBe(480);
|
||||||
|
expect($plugin->current_image_metadata['mime_type'])->toBe('image/png');
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ use App\Models\DeviceModel;
|
||||||
use App\Services\ImageGenerationService;
|
use App\Services\ImageGenerationService;
|
||||||
use Bnussbau\TrmnlPipeline\TrmnlPipeline;
|
use Bnussbau\TrmnlPipeline\TrmnlPipeline;
|
||||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
uses(RefreshDatabase::class);
|
uses(RefreshDatabase::class);
|
||||||
|
|
@ -22,6 +23,10 @@ afterEach(function (): void {
|
||||||
TrmnlPipeline::restore();
|
TrmnlPipeline::restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('plugins table has current_image_metadata column', function (): void {
|
||||||
|
expect(Schema::hasColumn('plugins', 'current_image_metadata'))->toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
it('generates image for device without device model', function (): void {
|
it('generates image for device without device model', function (): void {
|
||||||
// Create a device without a DeviceModel (legacy behavior)
|
// Create a device without a DeviceModel (legacy behavior)
|
||||||
$device = Device::factory()->create([
|
$device = Device::factory()->create([
|
||||||
|
|
@ -270,39 +275,15 @@ it('cleanupFolder preserves .gitignore', function (): void {
|
||||||
Storage::disk('public')->assertExists('/images/generated/.gitignore');
|
Storage::disk('public')->assertExists('/images/generated/.gitignore');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('resetIfNotCacheable resets when device models exist', function (): void {
|
it('resetIfNotCacheable does not reset recipe cache based on other devices', function (): void {
|
||||||
// Create a plugin
|
// Cache validity is now determined at use-time via current_image_metadata
|
||||||
$plugin = App\Models\Plugin::factory()->create(['current_image' => 'test-uuid']);
|
$plugin = App\Models\Plugin::factory()->create(['current_image' => 'test-uuid', 'plugin_type' => 'recipe']);
|
||||||
|
|
||||||
// Create a device with DeviceModel (should trigger cache reset)
|
Device::factory()->create(['device_model_id' => DeviceModel::factory()->create()->id]);
|
||||||
Device::factory()->create([
|
|
||||||
'device_model_id' => DeviceModel::factory()->create()->id,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Run reset check
|
|
||||||
ImageGenerationService::resetIfNotCacheable($plugin);
|
ImageGenerationService::resetIfNotCacheable($plugin);
|
||||||
|
|
||||||
// Assert plugin image was reset
|
|
||||||
$plugin->refresh();
|
$plugin->refresh();
|
||||||
expect($plugin->current_image)->toBeNull();
|
expect($plugin->current_image)->toBe('test-uuid');
|
||||||
});
|
|
||||||
|
|
||||||
it('resetIfNotCacheable resets when custom dimensions exist', function (): void {
|
|
||||||
// Create a plugin
|
|
||||||
$plugin = App\Models\Plugin::factory()->create(['current_image' => 'test-uuid']);
|
|
||||||
|
|
||||||
// Create a device with custom dimensions (should trigger cache reset)
|
|
||||||
Device::factory()->create([
|
|
||||||
'width' => 1024, // Different from default 800
|
|
||||||
'height' => 768, // Different from default 480
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Run reset check
|
|
||||||
ImageGenerationService::resetIfNotCacheable($plugin);
|
|
||||||
|
|
||||||
// Assert plugin image was reset
|
|
||||||
$plugin->refresh();
|
|
||||||
expect($plugin->current_image)->toBeNull();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('resetIfNotCacheable preserves image for standard devices', function (): void {
|
it('resetIfNotCacheable preserves image for standard devices', function (): void {
|
||||||
|
|
@ -325,27 +306,122 @@ it('resetIfNotCacheable preserves image for standard devices', function (): void
|
||||||
});
|
});
|
||||||
|
|
||||||
it('cache is reset when plugin markup changes', function (): void {
|
it('cache is reset when plugin markup changes', function (): void {
|
||||||
// Create a plugin with cached image
|
// Create a plugin with cached image and metadata
|
||||||
$plugin = App\Models\Plugin::factory()->create([
|
$plugin = App\Models\Plugin::factory()->create([
|
||||||
'current_image' => 'cached-uuid',
|
'current_image' => 'cached-uuid',
|
||||||
|
'current_image_metadata' => ['width' => 800, 'height' => 480, 'rotation' => 0, 'palette_id' => null, 'mime_type' => 'image/png'],
|
||||||
'render_markup' => '<div>Original markup</div>',
|
'render_markup' => '<div>Original markup</div>',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Create devices with standard dimensions (cacheable)
|
$plugin->update(['render_markup' => '<div>Updated markup</div>']);
|
||||||
Device::factory()->count(2)->create([
|
|
||||||
'width' => 800,
|
|
||||||
'height' => 480,
|
|
||||||
'rotate' => 0,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Update the plugin markup
|
|
||||||
$plugin->update([
|
|
||||||
'render_markup' => '<div>Updated markup</div>',
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Assert cache was reset when markup changed
|
|
||||||
$plugin->refresh();
|
$plugin->refresh();
|
||||||
expect($plugin->current_image)->toBeNull();
|
expect($plugin->current_image)->toBeNull();
|
||||||
|
expect($plugin->current_image_metadata)->toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('buildImageMetadataFromDevice returns canonical metadata shape', function (): void {
|
||||||
|
$deviceModel = DeviceModel::factory()->create([
|
||||||
|
'width' => 800,
|
||||||
|
'height' => 480,
|
||||||
|
'rotation' => 0,
|
||||||
|
'mime_type' => 'image/png',
|
||||||
|
'palette_id' => null,
|
||||||
|
]);
|
||||||
|
$device = Device::factory()->create(['device_model_id' => $deviceModel->id]);
|
||||||
|
|
||||||
|
$meta = ImageGenerationService::buildImageMetadataFromDevice($device);
|
||||||
|
|
||||||
|
expect($meta)->toHaveKeys(['width', 'height', 'rotation', 'palette_id', 'mime_type']);
|
||||||
|
expect($meta['width'])->toBe(800);
|
||||||
|
expect($meta['height'])->toBe(480);
|
||||||
|
expect($meta['rotation'])->toBe(0);
|
||||||
|
expect($meta['mime_type'])->toBe('image/png');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('buildImageMetadataFromDeviceModel returns canonical metadata shape', function (): void {
|
||||||
|
$model = DeviceModel::factory()->create([
|
||||||
|
'width' => 1024,
|
||||||
|
'height' => 768,
|
||||||
|
'rotation' => 90,
|
||||||
|
'mime_type' => 'image/bmp',
|
||||||
|
'palette_id' => null,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$meta = ImageGenerationService::buildImageMetadataFromDeviceModel($model);
|
||||||
|
|
||||||
|
expect($meta)->toHaveKeys(['width', 'height', 'rotation', 'palette_id', 'mime_type']);
|
||||||
|
expect($meta['width'])->toBe(1024);
|
||||||
|
expect($meta['height'])->toBe(768);
|
||||||
|
expect($meta['rotation'])->toBe(90);
|
||||||
|
expect($meta['mime_type'])->toBe('image/bmp');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('imageMetadataMatches returns false when stored is null or empty', function (): void {
|
||||||
|
$device = Device::factory()->create(['width' => 800, 'height' => 480, 'rotate' => 0]);
|
||||||
|
|
||||||
|
expect(ImageGenerationService::imageMetadataMatches(null, $device))->toBeFalse();
|
||||||
|
expect(ImageGenerationService::imageMetadataMatches([], $device))->toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('imageMetadataMatches returns true when metadata matches device', function (): void {
|
||||||
|
$deviceModel = DeviceModel::factory()->create([
|
||||||
|
'width' => 800,
|
||||||
|
'height' => 480,
|
||||||
|
'rotation' => 0,
|
||||||
|
'mime_type' => 'image/png',
|
||||||
|
'palette_id' => null,
|
||||||
|
]);
|
||||||
|
$device = Device::factory()->create(['device_model_id' => $deviceModel->id]);
|
||||||
|
$stored = ImageGenerationService::buildImageMetadataFromDevice($device);
|
||||||
|
|
||||||
|
expect(ImageGenerationService::imageMetadataMatches($stored, $device))->toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('imageMetadataMatches returns false when metadata differs', function (): void {
|
||||||
|
$device = Device::factory()->create(['width' => 800, 'height' => 480, 'rotate' => 0]);
|
||||||
|
$stored = ['width' => 800, 'height' => 480, 'rotation' => 0, 'palette_id' => null, 'mime_type' => 'image/png'];
|
||||||
|
|
||||||
|
$device->update(['width' => 1024]);
|
||||||
|
$device->refresh();
|
||||||
|
|
||||||
|
expect(ImageGenerationService::imageMetadataMatches($stored, $device))->toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resetIfNotCacheable clears recipe cache when metadata does not match', function (): void {
|
||||||
|
$plugin = App\Models\Plugin::factory()->create([
|
||||||
|
'plugin_type' => 'recipe',
|
||||||
|
'current_image' => 'cached-uuid',
|
||||||
|
'current_image_metadata' => ['width' => 800, 'height' => 480, 'rotation' => 0, 'palette_id' => null, 'mime_type' => 'image/png'],
|
||||||
|
]);
|
||||||
|
$device = Device::factory()->create(['width' => 1024, 'height' => 768, 'rotate' => 0]);
|
||||||
|
|
||||||
|
ImageGenerationService::resetIfNotCacheable($plugin, $device);
|
||||||
|
|
||||||
|
$plugin->refresh();
|
||||||
|
expect($plugin->current_image)->toBeNull();
|
||||||
|
expect($plugin->current_image_metadata)->toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resetIfNotCacheable preserves cache when metadata matches', function (): void {
|
||||||
|
$deviceModel = DeviceModel::factory()->create([
|
||||||
|
'width' => 800,
|
||||||
|
'height' => 480,
|
||||||
|
'rotation' => 0,
|
||||||
|
'mime_type' => 'image/png',
|
||||||
|
]);
|
||||||
|
$device = Device::factory()->create(['device_model_id' => $deviceModel->id]);
|
||||||
|
$meta = ImageGenerationService::buildImageMetadataFromDevice($device);
|
||||||
|
$plugin = App\Models\Plugin::factory()->create([
|
||||||
|
'plugin_type' => 'recipe',
|
||||||
|
'current_image' => 'cached-uuid',
|
||||||
|
'current_image_metadata' => $meta,
|
||||||
|
]);
|
||||||
|
|
||||||
|
ImageGenerationService::resetIfNotCacheable($plugin, $device);
|
||||||
|
|
||||||
|
$plugin->refresh();
|
||||||
|
expect($plugin->current_image)->toBe('cached-uuid');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('determines correct image format from device model', function (): void {
|
it('determines correct image format from device model', function (): void {
|
||||||
|
|
|
||||||
46
tests/Feature/PixelLogoConfigTest.php
Normal file
46
tests/Feature/PixelLogoConfigTest.php
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Support\Facades\Config;
|
||||||
|
|
||||||
|
uses(Illuminate\Foundation\Testing\RefreshDatabase::class);
|
||||||
|
|
||||||
|
test('auth pages show pixel logo SVG when pixel_logo_enabled is true', function (): void {
|
||||||
|
Config::set('app.pixel_logo_enabled', true);
|
||||||
|
|
||||||
|
$response = $this->get('/login');
|
||||||
|
|
||||||
|
$response->assertStatus(200);
|
||||||
|
$response->assertSee('viewBox="0 0 1000 150"', false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('auth pages show heading instead of pixel logo when pixel_logo_enabled is false', function (): void {
|
||||||
|
Config::set('app.pixel_logo_enabled', false);
|
||||||
|
|
||||||
|
$response = $this->get('/login');
|
||||||
|
|
||||||
|
$response->assertStatus(200);
|
||||||
|
$response->assertDontSee('viewBox="0 0 1000 150"', false);
|
||||||
|
$response->assertSee('LaraPaper');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('app logo shows text when pixel_logo_enabled is false', function (): void {
|
||||||
|
Config::set('app.pixel_logo_enabled', false);
|
||||||
|
|
||||||
|
$user = User::factory()->create();
|
||||||
|
$response = $this->actingAs($user)->get(route('dashboard'));
|
||||||
|
|
||||||
|
$response->assertStatus(200);
|
||||||
|
$response->assertSee('LaraPaper');
|
||||||
|
$response->assertDontSee('viewBox="0 0 1000 150"', false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('app logo shows pixel logo SVG when pixel_logo_enabled is true', function (): void {
|
||||||
|
Config::set('app.pixel_logo_enabled', true);
|
||||||
|
|
||||||
|
$user = User::factory()->create();
|
||||||
|
$response = $this->actingAs($user)->get(route('dashboard'));
|
||||||
|
|
||||||
|
$response->assertStatus(200);
|
||||||
|
$response->assertSee('viewBox="0 0 1000 150"', false);
|
||||||
|
});
|
||||||
163
tests/Feature/Plugins/Ical/IcalParserTest.php
Normal file
163
tests/Feature/Plugins/Ical/IcalParserTest.php
Normal file
|
|
@ -0,0 +1,163 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use App\Models\Plugin;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
|
||||||
|
test('iCal plugin parses Google Calendar invitation event', function (): void {
|
||||||
|
// Set test time close to the event in the issue
|
||||||
|
Carbon::setTestNow(Carbon::parse('2026-03-10 12:00:00', 'Europe/Budapest'));
|
||||||
|
|
||||||
|
$icalContent = <<<'ICS'
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
PRODID:-//Example Corp.//EN
|
||||||
|
BEGIN:VEVENT
|
||||||
|
DTSTART;TZID=Europe/Budapest:20260311T100000
|
||||||
|
DTEND;TZID=Europe/Budapest:20260311T110000
|
||||||
|
DTSTAMP:20260301T100000Z
|
||||||
|
ORGANIZER:mailto:organizer@example.com
|
||||||
|
UID:xxxxxxxxxxxxxxxxxxx@google.com
|
||||||
|
SEQUENCE:0
|
||||||
|
DESCRIPTION:-::~:~::~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~
|
||||||
|
·:~:~:~:~:~:~:~:~::~:~::-
|
||||||
|
Csatlakozás a Google Meet szolgáltatással: https://meet.google.com/xxx-xxxx-xxx
|
||||||
|
|
||||||
|
További információ a Meetről: https://support.google.com/a/users/answer/9282720
|
||||||
|
|
||||||
|
Kérjük, ne szerkeszd ezt a szakaszt.
|
||||||
|
-::~:~::~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~
|
||||||
|
·:~:~:~:~:~:~:~:~:~:~:~:~::~:~::-
|
||||||
|
LOCATION:Meet XY Street, ZIP; https://meet.google.com/xxx-xxxx-xxx
|
||||||
|
SUMMARY:Meeting
|
||||||
|
STATUS:CONFIRMED
|
||||||
|
ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;RSVP=TRUE;X-NUM-GUESTS=0;X-PM-TOKEN=REDACTED;PARTSTAT=ACCEPTED:mailto:participant1@example.com
|
||||||
|
ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;RSVP=TRUE;CN=participant2@example.com;X-NUM-GUESTS=0;X-PM-TOKEN=REDACTED;PARTSTAT=ACCEPTED:mailto:participant2@example.com
|
||||||
|
ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;RSVP=TRUE;CN=participant3@example.com;X-NUM-GUESTS=0;X-PM-TOKEN=REDACTED;PARTSTAT=NEEDS-ACTION:mailto:participant3@example.com
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
||||||
|
ICS;
|
||||||
|
|
||||||
|
Http::fake([
|
||||||
|
'example.com/calendar.ics' => Http::response($icalContent, 200, ['Content-Type' => 'text/calendar']),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$plugin = Plugin::factory()->create([
|
||||||
|
'data_strategy' => 'polling',
|
||||||
|
'polling_url' => 'https://example.com/calendar.ics',
|
||||||
|
'polling_verb' => 'get',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$plugin->updateDataPayload();
|
||||||
|
$plugin->refresh();
|
||||||
|
|
||||||
|
expect($plugin->data_payload)->not->toHaveKey('error');
|
||||||
|
expect($plugin->data_payload)->toHaveKey('ical');
|
||||||
|
expect($plugin->data_payload['ical'])->toHaveCount(1);
|
||||||
|
expect($plugin->data_payload['ical'][0]['SUMMARY'])->toBe('Meeting');
|
||||||
|
|
||||||
|
Carbon::setTestNow();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('iCal plugin parses recurring events with multiple BYDAY correctly', function (): void {
|
||||||
|
// Set test now to Monday 2024-03-25
|
||||||
|
Carbon::setTestNow(Carbon::parse('2024-03-25 12:00:00', 'UTC'));
|
||||||
|
|
||||||
|
$icalContent = <<<'ICS'
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
PRODID:-//Example Corp.//EN
|
||||||
|
BEGIN:VEVENT
|
||||||
|
DESCRIPTION:XXX-REDACTED
|
||||||
|
RRULE:FREQ=WEEKLY;UNTIL=20250604T220000Z;INTERVAL=1;BYDAY=TU,TH;WKST=MO
|
||||||
|
UID:040000008200E00074C5B7101A82E00800000000E07AF34F937EDA01000000000000000
|
||||||
|
01000000061F3E918C753424E8154B36E55452933
|
||||||
|
SUMMARY:Recurring Meeting
|
||||||
|
DTSTART;VALUE=DATE:20240326
|
||||||
|
DTEND;VALUE=DATE:20240327
|
||||||
|
DTSTAMP:20240605T082436Z
|
||||||
|
CLASS:PUBLIC
|
||||||
|
STATUS:CONFIRMED
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
||||||
|
ICS;
|
||||||
|
|
||||||
|
Http::fake([
|
||||||
|
'example.com/recurring.ics' => Http::response($icalContent, 200, ['Content-Type' => 'text/calendar']),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$plugin = Plugin::factory()->create([
|
||||||
|
'data_strategy' => 'polling',
|
||||||
|
'polling_url' => 'https://example.com/recurring.ics',
|
||||||
|
'polling_verb' => 'get',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$plugin->updateDataPayload();
|
||||||
|
$plugin->refresh();
|
||||||
|
|
||||||
|
$ical = $plugin->data_payload['ical'];
|
||||||
|
|
||||||
|
// Week of March 25, 2024:
|
||||||
|
// Tue March 26: 2024-03-26 (DTSTART)
|
||||||
|
// Thu March 28: 2024-03-28 (Recurrence)
|
||||||
|
|
||||||
|
// The parser window is now-7 days to now+30 days.
|
||||||
|
// Window: 2024-03-18 to 2024-04-24.
|
||||||
|
|
||||||
|
$summaries = collect($ical)->pluck('SUMMARY');
|
||||||
|
expect($summaries)->toContain('Recurring Meeting');
|
||||||
|
|
||||||
|
$dates = collect($ical)->map(fn ($event) => Carbon::parse($event['DTSTART'])->format('Y-m-d'))->values();
|
||||||
|
|
||||||
|
// Check if Tuesday March 26 is present
|
||||||
|
expect($dates)->toContain('2024-03-26');
|
||||||
|
|
||||||
|
// Check if Thursday March 28 is present (THIS IS WHERE IT IS EXPECTED TO FAIL BASED ON THE ISSUE)
|
||||||
|
expect($dates)->toContain('2024-03-28');
|
||||||
|
|
||||||
|
Carbon::setTestNow();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('iCal plugin parses recurring events with multiple BYDAY and specific DTSTART correctly', function (): void {
|
||||||
|
// Set test now to Monday 2024-03-25
|
||||||
|
Carbon::setTestNow(Carbon::parse('2024-03-25 12:00:00', 'UTC'));
|
||||||
|
|
||||||
|
$icalContent = <<<'ICS'
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
X-WR-TIMEZONE:UTC
|
||||||
|
PRODID:-//Example Corp.//EN
|
||||||
|
BEGIN:VEVENT
|
||||||
|
RRULE:FREQ=WEEKLY;UNTIL=20250604T220000Z;INTERVAL=1;BYDAY=TU,TH;WKST=MO
|
||||||
|
UID:recurring-event-2
|
||||||
|
SUMMARY:Recurring Meeting 2
|
||||||
|
DTSTART:20240326T100000
|
||||||
|
DTEND:20240326T110000
|
||||||
|
DTSTAMP:20240605T082436Z
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
||||||
|
ICS;
|
||||||
|
|
||||||
|
Http::fake([
|
||||||
|
'example.com/recurring2.ics' => Http::response($icalContent, 200, ['Content-Type' => 'text/calendar']),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$plugin = Plugin::factory()->create([
|
||||||
|
'data_strategy' => 'polling',
|
||||||
|
'polling_url' => 'https://example.com/recurring2.ics',
|
||||||
|
'polling_verb' => 'get',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$plugin->updateDataPayload();
|
||||||
|
$plugin->refresh();
|
||||||
|
|
||||||
|
$ical = $plugin->data_payload['ical'];
|
||||||
|
$dates = collect($ical)->map(fn ($event) => Carbon::parse($event['DTSTART'])->format('Y-m-d'))->values();
|
||||||
|
|
||||||
|
expect($dates)->toContain('2024-03-26');
|
||||||
|
expect($dates)->toContain('2024-03-28');
|
||||||
|
|
||||||
|
Carbon::setTestNow();
|
||||||
|
});
|
||||||
|
|
@ -176,37 +176,15 @@ it('cleanup_folder identifies active images correctly', function (): void {
|
||||||
expect($activeImageUuids)->not->toContain(null);
|
expect($activeImageUuids)->not->toContain(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('reset_if_not_cacheable detects device models', function (): void {
|
it('reset_if_not_cacheable does not reset recipe cache when other devices exist', function (): void {
|
||||||
// Create a plugin
|
// Cache validity is now determined at use-time via metadata
|
||||||
$plugin = App\Models\Plugin::factory()->create(['current_image' => 'test-uuid']);
|
$plugin = App\Models\Plugin::factory()->create(['current_image' => 'test-uuid', 'plugin_type' => 'recipe']);
|
||||||
|
Device::factory()->create(['device_model_id' => DeviceModel::factory()->create()->id]);
|
||||||
|
|
||||||
// Create a device with DeviceModel
|
|
||||||
Device::factory()->create([
|
|
||||||
'device_model_id' => DeviceModel::factory()->create()->id,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Test that the method detects DeviceModels and resets cache
|
|
||||||
ImageGenerationService::resetIfNotCacheable($plugin);
|
ImageGenerationService::resetIfNotCacheable($plugin);
|
||||||
|
|
||||||
$plugin->refresh();
|
$plugin->refresh();
|
||||||
expect($plugin->current_image)->toBeNull();
|
expect($plugin->current_image)->toBe('test-uuid');
|
||||||
});
|
|
||||||
|
|
||||||
it('reset_if_not_cacheable detects custom dimensions', function (): void {
|
|
||||||
// Create a plugin
|
|
||||||
$plugin = App\Models\Plugin::factory()->create(['current_image' => 'test-uuid']);
|
|
||||||
|
|
||||||
// Create a device with custom dimensions
|
|
||||||
Device::factory()->create([
|
|
||||||
'width' => 1024, // Different from default 800
|
|
||||||
'height' => 768, // Different from default 480
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Test that the method detects custom dimensions and resets cache
|
|
||||||
ImageGenerationService::resetIfNotCacheable($plugin);
|
|
||||||
|
|
||||||
$plugin->refresh();
|
|
||||||
expect($plugin->current_image)->toBeNull();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('reset_if_not_cacheable preserves cache for standard devices', function (): void {
|
it('reset_if_not_cacheable preserves cache for standard devices', function (): void {
|
||||||
|
|
@ -258,26 +236,21 @@ it('reset_if_not_cacheable preserves cache for og_png and og_plus device models'
|
||||||
expect($plugin->current_image)->toBe('test-uuid');
|
expect($plugin->current_image)->toBe('test-uuid');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('reset_if_not_cacheable resets cache for non-standard device models', function (): void {
|
it('reset_if_not_cacheable does not reset cache for non-standard device models', function (): void {
|
||||||
// Create a plugin
|
// Cache is now validated at use-time via metadata comparison
|
||||||
$plugin = App\Models\Plugin::factory()->create(['current_image' => 'test-uuid']);
|
$plugin = App\Models\Plugin::factory()->create(['current_image' => 'test-uuid', 'plugin_type' => 'recipe']);
|
||||||
|
|
||||||
// Create a non-standard device model (e.g., kindle)
|
|
||||||
$kindleModel = DeviceModel::factory()->create([
|
$kindleModel = DeviceModel::factory()->create([
|
||||||
'name' => 'test_amazon_kindle_2024',
|
'name' => 'test_amazon_kindle_2024',
|
||||||
'width' => 1400,
|
'width' => 1400,
|
||||||
'height' => 840,
|
'height' => 840,
|
||||||
'rotation' => 90,
|
'rotation' => 90,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Create a device with the non-standard device model
|
|
||||||
Device::factory()->create(['device_model_id' => $kindleModel->id]);
|
Device::factory()->create(['device_model_id' => $kindleModel->id]);
|
||||||
|
|
||||||
// Test that the method resets cache for non-standard device models
|
|
||||||
ImageGenerationService::resetIfNotCacheable($plugin);
|
ImageGenerationService::resetIfNotCacheable($plugin);
|
||||||
|
|
||||||
$plugin->refresh();
|
$plugin->refresh();
|
||||||
expect($plugin->current_image)->toBeNull();
|
expect($plugin->current_image)->toBe('test-uuid');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('reset_if_not_cacheable handles null plugin', function (): void {
|
it('reset_if_not_cacheable handles null plugin', function (): void {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue