diff --git a/.gitignore b/.gitignore
index 9c0185e..838d9c1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -41,3 +41,4 @@ yarn-error.log
/.opencode
/build.sh
/.junie
+/.agents
diff --git a/Dockerfile b/Dockerfile
index 2d761ed..5af7b33 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -18,7 +18,7 @@ ENV TRMNL_LIQUID_ENABLED=1
# Switch to the root user so we can do root things
USER root
-COPY --chown=www-data:www-data --from=bnussbau/trmnl-liquid-cli:0.1.0 /usr/local/bin/trmnl-liquid-cli /usr/local/bin/
+COPY --chown=www-data:www-data --from=bnussbau/trmnl-liquid-cli:0.2.0 /usr/local/bin/trmnl-liquid-cli /usr/local/bin/
# Set the working directory
WORKDIR /var/www/html
diff --git a/README.md b/README.md
index 2231b24..670b62c 100644
--- a/README.md
+++ b/README.md
@@ -122,6 +122,7 @@ php artisan db:seed --class=ExampleRecipesSeeder
| `REGISTRATION_ENABLED` | Allow user registration via Webinterface | 1 |
| `SSL_MODE` | SSL Mode, if not using a Reverse Proxy ([docs](https://serversideup.net/open-source/docker-php/docs/customizing-the-image/configuring-ssl)) | `off` |
| `FORCE_HTTPS` | If your server handles SSL termination, enforce HTTPS. | 0 |
+| `TRUSTED_PROXIES` | If your server handles SSL termination, allow mixed mode. e.g. `"172.0.0.0/8"` or `*` | null |
| `PHP_OPCACHE_ENABLE` | Enable PHP Opcache | 0 |
| `TRMNL_IMAGE_URL_TIMEOUT` | How long TRMNL waits for a response on the display endpoint. (sec) | 30 |
| `APP_TIMEZONE` | Default timezone, which will be used by the PHP date functions | UTC |
diff --git a/app/Models/Plugin.php b/app/Models/Plugin.php
index bc46559..5eeeb6b 100644
--- a/app/Models/Plugin.php
+++ b/app/Models/Plugin.php
@@ -60,8 +60,14 @@ class Plugin extends Model
});
static::updating(function ($model): void {
- // Reset image cache when markup changes
- if ($model->isDirty('render_markup')) {
+ // Reset image cache when any markup changes
+ if ($model->isDirty([
+ 'render_markup',
+ 'render_markup_half_horizontal',
+ 'render_markup_half_vertical',
+ 'render_markup_quadrant',
+ 'render_markup_shared',
+ ])) {
$model->current_image = null;
}
});
@@ -421,7 +427,9 @@ class Plugin extends Model
throw new InvalidArgumentException('Render method is only applicable for recipe plugins.');
}
- if ($this->render_markup) {
+ $markup = $this->getMarkupForSize($size);
+
+ if ($markup) {
$renderedContent = '';
if ($this->markup_language === 'liquid') {
@@ -471,7 +479,7 @@ class Plugin extends Model
// Check if external renderer should be used
if ($this->preferred_renderer === 'trmnl-liquid' && config('services.trmnl.liquid_enabled')) {
// Use external Ruby renderer - pass raw template without preprocessing
- $renderedContent = $this->renderWithExternalLiquidRenderer($this->render_markup, $context);
+ $renderedContent = $this->renderWithExternalLiquidRenderer($markup, $context);
} else {
// Use PHP keepsuit/liquid renderer
// Create a custom environment with inline templates support
@@ -493,14 +501,14 @@ class Plugin extends Model
$environment->tagRegistry->register(TemplateTag::class);
// Apply Liquid replacements (including 'with' syntax conversion)
- $processedMarkup = $this->applyLiquidReplacements($this->render_markup);
+ $processedMarkup = $this->applyLiquidReplacements($markup);
$template = $environment->parseString($processedMarkup);
$liquidContext = $environment->newRenderContext(data: $context);
$renderedContent = $template->render($liquidContext);
}
} else {
- $renderedContent = Blade::render($this->render_markup, [
+ $renderedContent = Blade::render($markup, [
'size' => $size,
'data' => $this->data_payload,
'config' => $this->configuration ?? [],
@@ -581,6 +589,30 @@ class Plugin extends Model
return $this->configuration[$key] ?? $default;
}
+ /**
+ * Get the appropriate markup for a given size, including shared prepending logic
+ *
+ * @param string $size The layout size (full, half_horizontal, half_vertical, quadrant)
+ * @return string|null The markup code for the given size, with shared prepended if available
+ */
+ public function getMarkupForSize(string $size): ?string
+ {
+ $markup = match ($size) {
+ 'full' => $this->render_markup,
+ 'half_horizontal' => $this->render_markup_half_horizontal ?? $this->render_markup,
+ 'half_vertical' => $this->render_markup_half_vertical ?? $this->render_markup,
+ 'quadrant' => $this->render_markup_quadrant ?? $this->render_markup,
+ default => $this->render_markup,
+ };
+
+ // Prepend shared markup if it exists
+ if ($markup && $this->render_markup_shared) {
+ $markup = $this->render_markup_shared."\n".$markup;
+ }
+
+ return $markup;
+ }
+
public function getPreviewMashupLayoutForSize(string $size): string
{
return match ($size) {
diff --git a/app/Services/PluginExportService.php b/app/Services/PluginExportService.php
index 241764d..be98461 100644
--- a/app/Services/PluginExportService.php
+++ b/app/Services/PluginExportService.php
@@ -51,17 +51,35 @@ class PluginExportService
$settings = $this->generateSettingsYaml($plugin);
$settingsYaml = Yaml::dump($settings, 10, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK);
File::put($tempDir.'/settings.yml', $settingsYaml);
- // Generate full template content
- $fullTemplate = $this->generateFullTemplate($plugin);
+
$extension = $plugin->markup_language === 'liquid' ? 'liquid' : 'blade.php';
- File::put($tempDir.'/full.'.$extension, $fullTemplate);
- // Generate shared.liquid if needed (for liquid templates)
- if ($plugin->markup_language === 'liquid') {
- $sharedTemplate = $this->generateSharedTemplate();
- /** @phpstan-ignore-next-line */
- if ($sharedTemplate) {
- File::put($tempDir.'/shared.liquid', $sharedTemplate);
- }
+
+ // Export full template if it exists
+ if ($plugin->render_markup) {
+ $fullTemplate = $this->generateLayoutTemplate($plugin->render_markup);
+ File::put($tempDir.'/full.'.$extension, $fullTemplate);
+ }
+
+ // Export layout-specific templates if they exist
+ if ($plugin->render_markup_half_horizontal) {
+ $halfHorizontalTemplate = $this->generateLayoutTemplate($plugin->render_markup_half_horizontal);
+ File::put($tempDir.'/half_horizontal.'.$extension, $halfHorizontalTemplate);
+ }
+
+ if ($plugin->render_markup_half_vertical) {
+ $halfVerticalTemplate = $this->generateLayoutTemplate($plugin->render_markup_half_vertical);
+ File::put($tempDir.'/half_vertical.'.$extension, $halfVerticalTemplate);
+ }
+
+ if ($plugin->render_markup_quadrant) {
+ $quadrantTemplate = $this->generateLayoutTemplate($plugin->render_markup_quadrant);
+ File::put($tempDir.'/quadrant.'.$extension, $quadrantTemplate);
+ }
+
+ // Export shared template if it exists
+ if ($plugin->render_markup_shared) {
+ $sharedTemplate = $this->generateLayoutTemplate($plugin->render_markup_shared);
+ File::put($tempDir.'/shared.'.$extension, $sharedTemplate);
}
// Create ZIP file
$zipPath = $tempDir.'/plugin_'.$plugin->trmnlp_id.'.zip';
@@ -124,29 +142,21 @@ class PluginExportService
}
/**
- * Generate the full template content
+ * Generate template content from markup, removing wrapper divs if present
*/
- private function generateFullTemplate(Plugin $plugin): string
+ private function generateLayoutTemplate(?string $markup): string
{
- $markup = $plugin->render_markup;
+ if (! $markup) {
+ return '';
+ }
- // Remove the wrapper div if it exists (it will be added during import)
+ // Remove the wrapper div if it exists (it will be added during import for liquid)
$markup = preg_replace('/^
\s*/', '', $markup);
$markup = preg_replace('/\s*<\/div>\s*$/', '', $markup);
return mb_trim($markup);
}
- /**
- * Generate the shared template content (for liquid templates)
- */
- private function generateSharedTemplate(): null
- {
- // For now, we don't have a way to store shared templates separately
- // TODO - add support for shared templates
- return null;
- }
-
/**
* Add a directory and its contents to a ZIP file
*/
diff --git a/app/Services/PluginImportService.php b/app/Services/PluginImportService.php
index 51a9aee..f3e7a5c 100644
--- a/app/Services/PluginImportService.php
+++ b/app/Services/PluginImportService.php
@@ -93,37 +93,59 @@ class PluginImportService
$settings = Yaml::parse($settingsYaml);
$this->validateYAML($settings);
- // Determine which template file to use and read its content
- $templatePath = null;
+ // Determine markup language from the first available file
$markupLanguage = 'blade';
+ $firstTemplatePath = $filePaths['fullLiquidPath']
+ ?? ($filePaths['halfHorizontalLiquidPath'] ?? null)
+ ?? ($filePaths['halfVerticalLiquidPath'] ?? null)
+ ?? ($filePaths['quadrantLiquidPath'] ?? null)
+ ?? ($filePaths['sharedLiquidPath'] ?? null)
+ ?? ($filePaths['sharedBladePath'] ?? null);
- if ($filePaths['fullLiquidPath']) {
- $templatePath = $filePaths['fullLiquidPath'];
- $fullLiquid = File::get($templatePath);
+ if ($firstTemplatePath && pathinfo((string) $firstTemplatePath, PATHINFO_EXTENSION) === 'liquid') {
+ $markupLanguage = 'liquid';
+ }
- // Prepend shared.liquid or shared.blade.php content if available
- if ($filePaths['sharedLiquidPath'] && File::exists($filePaths['sharedLiquidPath'])) {
- $sharedLiquid = File::get($filePaths['sharedLiquidPath']);
- $fullLiquid = $sharedLiquid."\n".$fullLiquid;
- } elseif ($filePaths['sharedBladePath'] && File::exists($filePaths['sharedBladePath'])) {
- $sharedBlade = File::get($filePaths['sharedBladePath']);
- $fullLiquid = $sharedBlade."\n".$fullLiquid;
- }
-
- // Check if the file ends with .liquid to set markup language
- if (pathinfo((string) $templatePath, PATHINFO_EXTENSION) === 'liquid') {
- $markupLanguage = 'liquid';
+ // Read full markup (don't prepend shared - it will be prepended at render time)
+ $fullLiquid = null;
+ if (isset($filePaths['fullLiquidPath']) && $filePaths['fullLiquidPath']) {
+ $fullLiquid = File::get($filePaths['fullLiquidPath']);
+ if ($markupLanguage === 'liquid') {
$fullLiquid = '
'."\n".$fullLiquid."\n".'
';
}
- } elseif ($filePaths['sharedLiquidPath']) {
- $templatePath = $filePaths['sharedLiquidPath'];
- $fullLiquid = File::get($templatePath);
- $markupLanguage = 'liquid';
- $fullLiquid = '
'."\n".$fullLiquid."\n".'
';
- } elseif ($filePaths['sharedBladePath']) {
- $templatePath = $filePaths['sharedBladePath'];
- $fullLiquid = File::get($templatePath);
- $markupLanguage = 'blade';
+ }
+
+ // Read shared markup separately
+ $sharedMarkup = null;
+ if (isset($filePaths['sharedLiquidPath']) && $filePaths['sharedLiquidPath'] && File::exists($filePaths['sharedLiquidPath'])) {
+ $sharedMarkup = File::get($filePaths['sharedLiquidPath']);
+ } elseif (isset($filePaths['sharedBladePath']) && $filePaths['sharedBladePath'] && File::exists($filePaths['sharedBladePath'])) {
+ $sharedMarkup = File::get($filePaths['sharedBladePath']);
+ }
+
+ // Read layout-specific markups
+ $halfHorizontalMarkup = null;
+ if (isset($filePaths['halfHorizontalLiquidPath']) && $filePaths['halfHorizontalLiquidPath'] && File::exists($filePaths['halfHorizontalLiquidPath'])) {
+ $halfHorizontalMarkup = File::get($filePaths['halfHorizontalLiquidPath']);
+ if ($markupLanguage === 'liquid') {
+ $halfHorizontalMarkup = '
'."\n".$halfHorizontalMarkup."\n".'
';
+ }
+ }
+
+ $halfVerticalMarkup = null;
+ if (isset($filePaths['halfVerticalLiquidPath']) && $filePaths['halfVerticalLiquidPath'] && File::exists($filePaths['halfVerticalLiquidPath'])) {
+ $halfVerticalMarkup = File::get($filePaths['halfVerticalLiquidPath']);
+ if ($markupLanguage === 'liquid') {
+ $halfVerticalMarkup = '
'."\n".$halfVerticalMarkup."\n".'
';
+ }
+ }
+
+ $quadrantMarkup = null;
+ if (isset($filePaths['quadrantLiquidPath']) && $filePaths['quadrantLiquidPath'] && File::exists($filePaths['quadrantLiquidPath'])) {
+ $quadrantMarkup = File::get($filePaths['quadrantLiquidPath']);
+ if ($markupLanguage === 'liquid') {
+ $quadrantMarkup = '
'."\n".$quadrantMarkup."\n".'
';
+ }
}
// Ensure custom_fields is properly formatted
@@ -160,6 +182,10 @@ class PluginImportService
'polling_body' => $settings['polling_body'] ?? null,
'markup_language' => $markupLanguage,
'render_markup' => $fullLiquid ?? null,
+ 'render_markup_half_horizontal' => $halfHorizontalMarkup,
+ 'render_markup_half_vertical' => $halfVerticalMarkup,
+ 'render_markup_quadrant' => $quadrantMarkup,
+ 'render_markup_shared' => $sharedMarkup,
'configuration_template' => $configurationTemplate,
'data_payload' => json_decode($settings['static_data'] ?? '{}', true),
]);
@@ -246,37 +272,59 @@ class PluginImportService
$settings = Yaml::parse($settingsYaml);
$this->validateYAML($settings);
- // Determine which template file to use and read its content
- $templatePath = null;
+ // Determine markup language from the first available file
$markupLanguage = 'blade';
+ $firstTemplatePath = $filePaths['fullLiquidPath']
+ ?? ($filePaths['halfHorizontalLiquidPath'] ?? null)
+ ?? ($filePaths['halfVerticalLiquidPath'] ?? null)
+ ?? ($filePaths['quadrantLiquidPath'] ?? null)
+ ?? ($filePaths['sharedLiquidPath'] ?? null)
+ ?? ($filePaths['sharedBladePath'] ?? null);
- if ($filePaths['fullLiquidPath']) {
- $templatePath = $filePaths['fullLiquidPath'];
- $fullLiquid = File::get($templatePath);
+ if ($firstTemplatePath && pathinfo((string) $firstTemplatePath, PATHINFO_EXTENSION) === 'liquid') {
+ $markupLanguage = 'liquid';
+ }
- // Prepend shared.liquid or shared.blade.php content if available
- if ($filePaths['sharedLiquidPath'] && File::exists($filePaths['sharedLiquidPath'])) {
- $sharedLiquid = File::get($filePaths['sharedLiquidPath']);
- $fullLiquid = $sharedLiquid."\n".$fullLiquid;
- } elseif ($filePaths['sharedBladePath'] && File::exists($filePaths['sharedBladePath'])) {
- $sharedBlade = File::get($filePaths['sharedBladePath']);
- $fullLiquid = $sharedBlade."\n".$fullLiquid;
- }
-
- // Check if the file ends with .liquid to set markup language
- if (pathinfo((string) $templatePath, PATHINFO_EXTENSION) === 'liquid') {
- $markupLanguage = 'liquid';
+ // Read full markup (don't prepend shared - it will be prepended at render time)
+ $fullLiquid = null;
+ if (isset($filePaths['fullLiquidPath']) && $filePaths['fullLiquidPath']) {
+ $fullLiquid = File::get($filePaths['fullLiquidPath']);
+ if ($markupLanguage === 'liquid') {
$fullLiquid = '
'."\n".$fullLiquid."\n".'
';
}
- } elseif ($filePaths['sharedLiquidPath']) {
- $templatePath = $filePaths['sharedLiquidPath'];
- $fullLiquid = File::get($templatePath);
- $markupLanguage = 'liquid';
- $fullLiquid = '
'."\n".$fullLiquid."\n".'
';
- } elseif ($filePaths['sharedBladePath']) {
- $templatePath = $filePaths['sharedBladePath'];
- $fullLiquid = File::get($templatePath);
- $markupLanguage = 'blade';
+ }
+
+ // Read shared markup separately
+ $sharedMarkup = null;
+ if (isset($filePaths['sharedLiquidPath']) && $filePaths['sharedLiquidPath'] && File::exists($filePaths['sharedLiquidPath'])) {
+ $sharedMarkup = File::get($filePaths['sharedLiquidPath']);
+ } elseif (isset($filePaths['sharedBladePath']) && $filePaths['sharedBladePath'] && File::exists($filePaths['sharedBladePath'])) {
+ $sharedMarkup = File::get($filePaths['sharedBladePath']);
+ }
+
+ // Read layout-specific markups
+ $halfHorizontalMarkup = null;
+ if (isset($filePaths['halfHorizontalLiquidPath']) && $filePaths['halfHorizontalLiquidPath'] && File::exists($filePaths['halfHorizontalLiquidPath'])) {
+ $halfHorizontalMarkup = File::get($filePaths['halfHorizontalLiquidPath']);
+ if ($markupLanguage === 'liquid') {
+ $halfHorizontalMarkup = '
'."\n".$halfHorizontalMarkup."\n".'
';
+ }
+ }
+
+ $halfVerticalMarkup = null;
+ if (isset($filePaths['halfVerticalLiquidPath']) && $filePaths['halfVerticalLiquidPath'] && File::exists($filePaths['halfVerticalLiquidPath'])) {
+ $halfVerticalMarkup = File::get($filePaths['halfVerticalLiquidPath']);
+ if ($markupLanguage === 'liquid') {
+ $halfVerticalMarkup = '
'."\n".$halfVerticalMarkup."\n".'
';
+ }
+ }
+
+ $quadrantMarkup = null;
+ if (isset($filePaths['quadrantLiquidPath']) && $filePaths['quadrantLiquidPath'] && File::exists($filePaths['quadrantLiquidPath'])) {
+ $quadrantMarkup = File::get($filePaths['quadrantLiquidPath']);
+ if ($markupLanguage === 'liquid') {
+ $quadrantMarkup = '
'."\n".$quadrantMarkup."\n".'
';
+ }
}
// Ensure custom_fields is properly formatted
@@ -322,6 +370,10 @@ class PluginImportService
'polling_body' => $settings['polling_body'] ?? null,
'markup_language' => $markupLanguage,
'render_markup' => $fullLiquid ?? null,
+ 'render_markup_half_horizontal' => $halfHorizontalMarkup,
+ 'render_markup_half_vertical' => $halfVerticalMarkup,
+ 'render_markup_quadrant' => $quadrantMarkup,
+ 'render_markup_shared' => $sharedMarkup,
'configuration_template' => $configurationTemplate,
'data_payload' => json_decode($settings['static_data'] ?? '{}', true),
'preferred_renderer' => $preferredRenderer,
@@ -357,6 +409,9 @@ class PluginImportService
$fullLiquidPath = null;
$sharedLiquidPath = null;
$sharedBladePath = null;
+ $halfHorizontalLiquidPath = null;
+ $halfVerticalLiquidPath = null;
+ $quadrantLiquidPath = null;
// If zipEntryPath is specified, look for files in that specific directory first
if ($zipEntryPath) {
@@ -377,6 +432,25 @@ class PluginImportService
} elseif (File::exists($targetDir.'/shared.blade.php')) {
$sharedBladePath = $targetDir.'/shared.blade.php';
}
+
+ // Check for layout-specific files
+ if (File::exists($targetDir.'/half_horizontal.liquid')) {
+ $halfHorizontalLiquidPath = $targetDir.'/half_horizontal.liquid';
+ } elseif (File::exists($targetDir.'/half_horizontal.blade.php')) {
+ $halfHorizontalLiquidPath = $targetDir.'/half_horizontal.blade.php';
+ }
+
+ if (File::exists($targetDir.'/half_vertical.liquid')) {
+ $halfVerticalLiquidPath = $targetDir.'/half_vertical.liquid';
+ } elseif (File::exists($targetDir.'/half_vertical.blade.php')) {
+ $halfVerticalLiquidPath = $targetDir.'/half_vertical.blade.php';
+ }
+
+ if (File::exists($targetDir.'/quadrant.liquid')) {
+ $quadrantLiquidPath = $targetDir.'/quadrant.liquid';
+ } elseif (File::exists($targetDir.'/quadrant.blade.php')) {
+ $quadrantLiquidPath = $targetDir.'/quadrant.blade.php';
+ }
}
// Check if files are in src subdirectory of target directory
@@ -394,6 +468,25 @@ class PluginImportService
} elseif (File::exists($targetDir.'/src/shared.blade.php')) {
$sharedBladePath = $targetDir.'/src/shared.blade.php';
}
+
+ // Check for layout-specific files in src
+ if (File::exists($targetDir.'/src/half_horizontal.liquid')) {
+ $halfHorizontalLiquidPath = $targetDir.'/src/half_horizontal.liquid';
+ } elseif (File::exists($targetDir.'/src/half_horizontal.blade.php')) {
+ $halfHorizontalLiquidPath = $targetDir.'/src/half_horizontal.blade.php';
+ }
+
+ if (File::exists($targetDir.'/src/half_vertical.liquid')) {
+ $halfVerticalLiquidPath = $targetDir.'/src/half_vertical.liquid';
+ } elseif (File::exists($targetDir.'/src/half_vertical.blade.php')) {
+ $halfVerticalLiquidPath = $targetDir.'/src/half_vertical.blade.php';
+ }
+
+ if (File::exists($targetDir.'/src/quadrant.liquid')) {
+ $quadrantLiquidPath = $targetDir.'/src/quadrant.liquid';
+ } elseif (File::exists($targetDir.'/src/quadrant.blade.php')) {
+ $quadrantLiquidPath = $targetDir.'/src/quadrant.blade.php';
+ }
}
// If we found the required files in the target directory, return them
@@ -425,6 +518,25 @@ class PluginImportService
} elseif (File::exists($tempDir.'/src/shared.blade.php')) {
$sharedBladePath = $tempDir.'/src/shared.blade.php';
}
+
+ // Check for layout-specific files
+ if (File::exists($tempDir.'/src/half_horizontal.liquid')) {
+ $halfHorizontalLiquidPath = $tempDir.'/src/half_horizontal.liquid';
+ } elseif (File::exists($tempDir.'/src/half_horizontal.blade.php')) {
+ $halfHorizontalLiquidPath = $tempDir.'/src/half_horizontal.blade.php';
+ }
+
+ if (File::exists($tempDir.'/src/half_vertical.liquid')) {
+ $halfVerticalLiquidPath = $tempDir.'/src/half_vertical.liquid';
+ } elseif (File::exists($tempDir.'/src/half_vertical.blade.php')) {
+ $halfVerticalLiquidPath = $tempDir.'/src/half_vertical.blade.php';
+ }
+
+ if (File::exists($tempDir.'/src/quadrant.liquid')) {
+ $quadrantLiquidPath = $tempDir.'/src/quadrant.liquid';
+ } elseif (File::exists($tempDir.'/src/quadrant.blade.php')) {
+ $quadrantLiquidPath = $tempDir.'/src/quadrant.blade.php';
+ }
} else {
// Search for the files in the extracted directory structure
$directories = new RecursiveDirectoryIterator($tempDir, RecursiveDirectoryIterator::SKIP_DOTS);
@@ -442,6 +554,12 @@ class PluginImportService
$sharedLiquidPath = $filepath;
} elseif ($filename === 'shared.blade.php') {
$sharedBladePath = $filepath;
+ } elseif ($filename === 'half_horizontal.liquid' || $filename === 'half_horizontal.blade.php') {
+ $halfHorizontalLiquidPath = $filepath;
+ } elseif ($filename === 'half_vertical.liquid' || $filename === 'half_vertical.blade.php') {
+ $halfVerticalLiquidPath = $filepath;
+ } elseif ($filename === 'quadrant.liquid' || $filename === 'quadrant.blade.php') {
+ $quadrantLiquidPath = $filepath;
}
}
@@ -485,6 +603,25 @@ class PluginImportService
$sharedBladePath = $newSrcDir.'/shared.blade.php';
}
+ // Copy layout-specific files if they exist
+ if ($halfHorizontalLiquidPath) {
+ $extension = pathinfo((string) $halfHorizontalLiquidPath, PATHINFO_EXTENSION);
+ File::copy($halfHorizontalLiquidPath, $newSrcDir.'/half_horizontal.'.$extension);
+ $halfHorizontalLiquidPath = $newSrcDir.'/half_horizontal.'.$extension;
+ }
+
+ if ($halfVerticalLiquidPath) {
+ $extension = pathinfo((string) $halfVerticalLiquidPath, PATHINFO_EXTENSION);
+ File::copy($halfVerticalLiquidPath, $newSrcDir.'/half_vertical.'.$extension);
+ $halfVerticalLiquidPath = $newSrcDir.'/half_vertical.'.$extension;
+ }
+
+ if ($quadrantLiquidPath) {
+ $extension = pathinfo((string) $quadrantLiquidPath, PATHINFO_EXTENSION);
+ File::copy($quadrantLiquidPath, $newSrcDir.'/quadrant.'.$extension);
+ $quadrantLiquidPath = $newSrcDir.'/quadrant.'.$extension;
+ }
+
// Update the paths
$settingsYamlPath = $newSrcDir.'/settings.yml';
}
@@ -496,6 +633,9 @@ class PluginImportService
'fullLiquidPath' => $fullLiquidPath,
'sharedLiquidPath' => $sharedLiquidPath,
'sharedBladePath' => $sharedBladePath,
+ 'halfHorizontalLiquidPath' => $halfHorizontalLiquidPath,
+ 'halfVerticalLiquidPath' => $halfVerticalLiquidPath,
+ 'quadrantLiquidPath' => $quadrantLiquidPath,
];
}
diff --git a/composer.json b/composer.json
index d856e75..96e0079 100644
--- a/composer.json
+++ b/composer.json
@@ -15,7 +15,7 @@
"ext-imagick": "*",
"ext-simplexml": "*",
"ext-zip": "*",
- "bnussbau/laravel-trmnl-blade": "2.2.*",
+ "bnussbau/laravel-trmnl-blade": "2.1.1",
"bnussbau/trmnl-pipeline-php": "^0.6.0",
"keepsuit/laravel-liquid": "^0.5.2",
"laravel/fortify": "^1.30",
diff --git a/composer.lock b/composer.lock
index ec617d3..a61b132 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "581bacf794841fc11c540e152c704d16",
+ "content-hash": "60a7e51edd8408cffdb901e4a1c1684a",
"packages": [
{
"name": "aws/aws-crt-php",
@@ -62,16 +62,16 @@
},
{
"name": "aws/aws-sdk-php",
- "version": "3.369.27",
+ "version": "3.369.29",
"source": {
"type": "git",
"url": "https://github.com/aws/aws-sdk-php.git",
- "reference": "f844afab2a74eb3cf881970a9c31de460510eb74"
+ "reference": "068195b2980cf5cf4ade2515850d461186db3310"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/f844afab2a74eb3cf881970a9c31de460510eb74",
- "reference": "f844afab2a74eb3cf881970a9c31de460510eb74",
+ "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/068195b2980cf5cf4ade2515850d461186db3310",
+ "reference": "068195b2980cf5cf4ade2515850d461186db3310",
"shasum": ""
},
"require": {
@@ -153,9 +153,9 @@
"support": {
"forum": "https://github.com/aws/aws-sdk-php/discussions",
"issues": "https://github.com/aws/aws-sdk-php/issues",
- "source": "https://github.com/aws/aws-sdk-php/tree/3.369.27"
+ "source": "https://github.com/aws/aws-sdk-php/tree/3.369.29"
},
- "time": "2026-02-04T19:07:08+00:00"
+ "time": "2026-02-06T19:08:50+00:00"
},
{
"name": "bacon/bacon-qr-code",
@@ -214,16 +214,16 @@
},
{
"name": "bnussbau/laravel-trmnl-blade",
- "version": "2.2.1",
+ "version": "2.1.1",
"source": {
"type": "git",
"url": "https://github.com/bnussbau/laravel-trmnl-blade.git",
- "reference": "6db8a82a15ccedcaaffd3b37d0d337d276a26669"
+ "reference": "6ad96eba917ebc30ebe550e6fce4a995e94f6b35"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/bnussbau/laravel-trmnl-blade/zipball/6db8a82a15ccedcaaffd3b37d0d337d276a26669",
- "reference": "6db8a82a15ccedcaaffd3b37d0d337d276a26669",
+ "url": "https://api.github.com/repos/bnussbau/laravel-trmnl-blade/zipball/6ad96eba917ebc30ebe550e6fce4a995e94f6b35",
+ "reference": "6ad96eba917ebc30ebe550e6fce4a995e94f6b35",
"shasum": ""
},
"require": {
@@ -278,7 +278,7 @@
],
"support": {
"issues": "https://github.com/bnussbau/laravel-trmnl-blade/issues",
- "source": "https://github.com/bnussbau/laravel-trmnl-blade/tree/2.2.1"
+ "source": "https://github.com/bnussbau/laravel-trmnl-blade/tree/2.1.1"
},
"funding": [
{
@@ -294,7 +294,7 @@
"type": "github"
}
],
- "time": "2026-02-05T17:57:37+00:00"
+ "time": "2026-01-29T20:40:42+00:00"
},
{
"name": "bnussbau/trmnl-pipeline-php",
@@ -3194,16 +3194,16 @@
},
{
"name": "livewire/livewire",
- "version": "v4.1.2",
+ "version": "v4.1.3",
"source": {
"type": "git",
"url": "https://github.com/livewire/livewire.git",
- "reference": "8adef21f35f4ffa87fd2f3655b350236df0c39a8"
+ "reference": "69c871cb15fb95f10cda5acd1ee7e63cd3c494c8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/livewire/livewire/zipball/8adef21f35f4ffa87fd2f3655b350236df0c39a8",
- "reference": "8adef21f35f4ffa87fd2f3655b350236df0c39a8",
+ "url": "https://api.github.com/repos/livewire/livewire/zipball/69c871cb15fb95f10cda5acd1ee7e63cd3c494c8",
+ "reference": "69c871cb15fb95f10cda5acd1ee7e63cd3c494c8",
"shasum": ""
},
"require": {
@@ -3258,7 +3258,7 @@
"description": "A front-end framework for Laravel.",
"support": {
"issues": "https://github.com/livewire/livewire/issues",
- "source": "https://github.com/livewire/livewire/tree/v4.1.2"
+ "source": "https://github.com/livewire/livewire/tree/v4.1.3"
},
"funding": [
{
@@ -3266,7 +3266,7 @@
"type": "github"
}
],
- "time": "2026-02-03T03:01:29+00:00"
+ "time": "2026-02-06T12:19:55+00:00"
},
{
"name": "maennchen/zipstream-php",
@@ -9066,16 +9066,16 @@
},
{
"name": "laravel/boost",
- "version": "v2.0.6",
+ "version": "v2.1.1",
"source": {
"type": "git",
"url": "https://github.com/laravel/boost.git",
- "reference": "1e1cb76e8e87ca3dd3c3d64deccbc97f4de38215"
+ "reference": "1c7d6f44c96937a961056778b9143218b1183302"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/boost/zipball/1e1cb76e8e87ca3dd3c3d64deccbc97f4de38215",
- "reference": "1e1cb76e8e87ca3dd3c3d64deccbc97f4de38215",
+ "url": "https://api.github.com/repos/laravel/boost/zipball/1c7d6f44c96937a961056778b9143218b1183302",
+ "reference": "1c7d6f44c96937a961056778b9143218b1183302",
"shasum": ""
},
"require": {
@@ -9128,7 +9128,7 @@
"issues": "https://github.com/laravel/boost/issues",
"source": "https://github.com/laravel/boost"
},
- "time": "2026-02-04T10:10:48+00:00"
+ "time": "2026-02-06T10:41:29+00:00"
},
{
"name": "laravel/mcp",
@@ -10484,16 +10484,16 @@
},
{
"name": "phpunit/php-code-coverage",
- "version": "12.5.2",
+ "version": "12.5.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
- "reference": "4a9739b51cbcb355f6e95659612f92e282a7077b"
+ "reference": "b015312f28dd75b75d3422ca37dff2cd1a565e8d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/4a9739b51cbcb355f6e95659612f92e282a7077b",
- "reference": "4a9739b51cbcb355f6e95659612f92e282a7077b",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/b015312f28dd75b75d3422ca37dff2cd1a565e8d",
+ "reference": "b015312f28dd75b75d3422ca37dff2cd1a565e8d",
"shasum": ""
},
"require": {
@@ -10549,7 +10549,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
- "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.5.2"
+ "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.5.3"
},
"funding": [
{
@@ -10569,7 +10569,7 @@
"type": "tidelift"
}
],
- "time": "2025-12-24T07:03:04+00:00"
+ "time": "2026-02-06T06:01:44+00:00"
},
{
"name": "phpunit/php-file-iterator",
@@ -10935,21 +10935,21 @@
},
{
"name": "rector/rector",
- "version": "2.3.5",
+ "version": "2.3.6",
"source": {
"type": "git",
"url": "https://github.com/rectorphp/rector.git",
- "reference": "9442f4037de6a5347ae157fe8e6c7cda9d909070"
+ "reference": "ca9ebb81d280cd362ea39474dabd42679e32ca6b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/rectorphp/rector/zipball/9442f4037de6a5347ae157fe8e6c7cda9d909070",
- "reference": "9442f4037de6a5347ae157fe8e6c7cda9d909070",
+ "url": "https://api.github.com/repos/rectorphp/rector/zipball/ca9ebb81d280cd362ea39474dabd42679e32ca6b",
+ "reference": "ca9ebb81d280cd362ea39474dabd42679e32ca6b",
"shasum": ""
},
"require": {
"php": "^7.4|^8.0",
- "phpstan/phpstan": "^2.1.36"
+ "phpstan/phpstan": "^2.1.38"
},
"conflict": {
"rector/rector-doctrine": "*",
@@ -10983,7 +10983,7 @@
],
"support": {
"issues": "https://github.com/rectorphp/rector/issues",
- "source": "https://github.com/rectorphp/rector/tree/2.3.5"
+ "source": "https://github.com/rectorphp/rector/tree/2.3.6"
},
"funding": [
{
@@ -10991,7 +10991,7 @@
"type": "github"
}
],
- "time": "2026-01-28T15:22:48+00:00"
+ "time": "2026-02-06T14:25:06+00:00"
},
{
"name": "sebastian/cli-parser",
diff --git a/database/migrations/2026_01_28_143142_add_layout_markup_columns_to_plugins_table.php b/database/migrations/2026_01_28_143142_add_layout_markup_columns_to_plugins_table.php
new file mode 100644
index 0000000..e56751c
--- /dev/null
+++ b/database/migrations/2026_01_28_143142_add_layout_markup_columns_to_plugins_table.php
@@ -0,0 +1,38 @@
+text('render_markup_half_horizontal')->nullable()->after('render_markup');
+ $table->text('render_markup_half_vertical')->nullable()->after('render_markup_half_horizontal');
+ $table->text('render_markup_quadrant')->nullable()->after('render_markup_half_vertical');
+ $table->text('render_markup_shared')->nullable()->after('render_markup_quadrant');
+ $table->text('transform_code')->nullable()->after('render_markup_shared');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('plugins', function (Blueprint $table) {
+ $table->dropColumn([
+ 'render_markup_half_horizontal',
+ 'render_markup_half_vertical',
+ 'render_markup_quadrant',
+ 'render_markup_shared',
+ 'transform_code',
+ ]);
+ });
+ }
+};
diff --git a/resources/views/livewire/device-models/index.blade.php b/resources/views/livewire/device-models/index.blade.php
index 6ec4014..1aebeb1 100644
--- a/resources/views/livewire/device-models/index.blade.php
+++ b/resources/views/livewire/device-models/index.blade.php
@@ -1,5 +1,6 @@
deviceModels = DeviceModel::all();
+ $this->devicePalettes = DevicePalette::all();
+ session()->flash('message', 'Device models updated from API.');
+ }
+
public function openDeviceModelModal(?string $deviceModelId = null, bool $viewOnly = false): void
{
if ($deviceModelId) {
@@ -229,9 +238,17 @@ new class extends Component
-
diff --git a/resources/views/livewire/device-palettes/index.blade.php b/resources/views/livewire/device-palettes/index.blade.php
index 6640545..4e96c31 100644
--- a/resources/views/livewire/device-palettes/index.blade.php
+++ b/resources/views/livewire/device-palettes/index.blade.php
@@ -1,5 +1,6 @@
devicePalettes = DevicePalette::all();
+ session()->flash('message', 'Device palettes updated from API.');
+ }
+
public function openDevicePaletteModal(?string $devicePaletteId = null, bool $viewOnly = false): void
{
if ($devicePaletteId) {
@@ -202,9 +210,17 @@ new class extends Component
-