Display CMS Optimierungen 03-06-2026
This commit is contained in:
parent
6c6d683b9a
commit
bebfa63e87
17 changed files with 925 additions and 133 deletions
84
app/Console/Commands/PublishFluxAssets.php
Normal file
84
app/Console/Commands/PublishFluxAssets.php
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use RuntimeException;
|
||||
|
||||
class PublishFluxAssets extends Command
|
||||
{
|
||||
protected $signature = 'flux:publish-assets {--force : Overwrite existing published Flux assets}';
|
||||
|
||||
protected $description = 'Publish Flux JavaScript assets to public/flux for static delivery (like livewire:publish --assets)';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$sourceDir = $this->resolveSourceDirectory();
|
||||
$targetDir = public_path('flux');
|
||||
|
||||
if (! File::isDirectory($targetDir)) {
|
||||
File::makeDirectory($targetDir, 0755, true);
|
||||
}
|
||||
|
||||
$files = [
|
||||
'flux.js',
|
||||
'flux.min.js',
|
||||
'editor.js',
|
||||
'editor.min.js',
|
||||
'editor.css',
|
||||
];
|
||||
|
||||
$published = 0;
|
||||
|
||||
foreach ($files as $file) {
|
||||
$source = $sourceDir.'/'.$file;
|
||||
|
||||
if (! is_file($source)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$target = $targetDir.'/'.$file;
|
||||
|
||||
if (is_file($target) && ! $this->option('force') && hash_file('sha256', $source) === hash_file('sha256', $target)) {
|
||||
$this->line(" <comment>•</comment> {$file} (unverändert)");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
File::copy($source, $target);
|
||||
$this->line(" <info>✓</info> {$file}");
|
||||
$published++;
|
||||
}
|
||||
|
||||
if ($published === 0) {
|
||||
$this->info('Flux-Assets sind bereits aktuell.');
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
$this->newLine();
|
||||
$this->info("{$published} Flux-Asset(s) nach public/flux/ veröffentlicht.");
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
private function resolveSourceDirectory(): string
|
||||
{
|
||||
$proDist = base_path('vendor/livewire/flux-pro/dist');
|
||||
|
||||
if (is_dir($proDist)) {
|
||||
return $proDist;
|
||||
}
|
||||
|
||||
$freeDist = base_path('vendor/livewire/flux/dist');
|
||||
|
||||
if (is_dir($freeDist)) {
|
||||
return $freeDist;
|
||||
}
|
||||
|
||||
throw new RuntimeException('Flux vendor assets not found. Run composer install first.');
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ use App\Enums\DisplayVersionType;
|
|||
use App\Models\DisplayMedia;
|
||||
use App\Models\DisplayVersion;
|
||||
use App\Models\DisplayVersionItem;
|
||||
use App\Services\DisplayQrCodeService;
|
||||
use App\Support\DisplayModuleSettings;
|
||||
use Livewire\Attributes\On;
|
||||
use Livewire\Component;
|
||||
|
|
@ -174,6 +175,7 @@ class DisplayVersionEditor extends Component
|
|||
public function saveSettings(): void
|
||||
{
|
||||
$this->normalizeBrandPositions();
|
||||
$this->settings = $this->settingsWithQrImage($this->settings);
|
||||
$this->version->update(['settings' => $this->settings]);
|
||||
$this->showSettingsModal = false;
|
||||
$this->refreshModulePreview();
|
||||
|
|
@ -463,7 +465,7 @@ class DisplayVersionEditor extends Component
|
|||
*/
|
||||
private function buildItemContent(): array
|
||||
{
|
||||
return match ($this->itemType) {
|
||||
$content = match ($this->itemType) {
|
||||
'video' => [
|
||||
'filename' => $this->videoFilename,
|
||||
'title' => $this->videoTitle,
|
||||
|
|
@ -514,6 +516,95 @@ class DisplayVersionEditor extends Component
|
|||
],
|
||||
default => [],
|
||||
};
|
||||
|
||||
return $this->contentWithQrImage($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $content
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function contentWithQrImage(array $content): array
|
||||
{
|
||||
$qrUrl = match ($this->itemType) {
|
||||
'footer' => $content['url'] ?? null,
|
||||
'slide' => $content['qr_url'] ?? null,
|
||||
default => null,
|
||||
};
|
||||
|
||||
$qrCodeService = app(DisplayQrCodeService::class);
|
||||
$qrCode = $qrCodeService->generateForUrl(is_string($qrUrl) ? $qrUrl : null);
|
||||
|
||||
if ($qrCode) {
|
||||
$content['qr_image_path'] = $qrCode['path'];
|
||||
$content['qr_image_url'] = $qrCode['url'];
|
||||
} elseif ($this->itemId && is_string($qrUrl) && $qrUrl !== '') {
|
||||
$existingContent = DisplayVersionItem::find($this->itemId)?->content ?? [];
|
||||
$existingQrUrl = match ($this->itemType) {
|
||||
'footer' => $existingContent['url'] ?? null,
|
||||
'slide' => $existingContent['qr_url'] ?? null,
|
||||
default => null,
|
||||
};
|
||||
$preservedExistingQrImage = false;
|
||||
|
||||
if (is_string($existingQrUrl)
|
||||
&& $qrCodeService->normalizeUrl($existingQrUrl) === $qrCodeService->normalizeUrl($qrUrl)
|
||||
&& ! empty($existingContent['qr_image_path'])
|
||||
&& ! empty($existingContent['qr_image_url'])) {
|
||||
$content['qr_image_path'] = $existingContent['qr_image_path'];
|
||||
$content['qr_image_url'] = $existingContent['qr_image_url'];
|
||||
$preservedExistingQrImage = true;
|
||||
}
|
||||
|
||||
if (! $preservedExistingQrImage) {
|
||||
unset($content['qr_image_path'], $content['qr_image_url']);
|
||||
}
|
||||
} else {
|
||||
unset($content['qr_image_path'], $content['qr_image_url']);
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $settings
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function settingsWithQrImage(array $settings): array
|
||||
{
|
||||
if ($this->version->type !== DisplayVersionType::B2in) {
|
||||
return $settings;
|
||||
}
|
||||
|
||||
$qrUrl = $settings['qr_url'] ?? $settings['footer_url'] ?? null;
|
||||
$qrCodeService = app(DisplayQrCodeService::class);
|
||||
$qrCode = $qrCodeService->generateForUrl(is_string($qrUrl) ? $qrUrl : null);
|
||||
|
||||
if ($qrCode) {
|
||||
$settings['qr_image_path'] = $qrCode['path'];
|
||||
$settings['qr_image_url'] = $qrCode['url'];
|
||||
} elseif (is_string($qrUrl) && $qrUrl !== '') {
|
||||
$existingSettings = $this->settingsWithDefaults();
|
||||
$existingQrUrl = $existingSettings['qr_url'] ?? $existingSettings['footer_url'] ?? null;
|
||||
$preservedExistingQrImage = false;
|
||||
|
||||
if (is_string($existingQrUrl)
|
||||
&& $qrCodeService->normalizeUrl($existingQrUrl) === $qrCodeService->normalizeUrl($qrUrl)
|
||||
&& ! empty($existingSettings['qr_image_path'])
|
||||
&& ! empty($existingSettings['qr_image_url'])) {
|
||||
$settings['qr_image_path'] = $existingSettings['qr_image_path'];
|
||||
$settings['qr_image_url'] = $existingSettings['qr_image_url'];
|
||||
$preservedExistingQrImage = true;
|
||||
}
|
||||
|
||||
if (! $preservedExistingQrImage) {
|
||||
unset($settings['qr_image_path'], $settings['qr_image_url']);
|
||||
}
|
||||
} else {
|
||||
unset($settings['qr_image_path'], $settings['qr_image_url']);
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
private function getActiveFlag(): bool
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ class QuickStatus extends Component
|
|||
public function mount(?string $k = null): void
|
||||
{
|
||||
$validKey = config('domains.cabinet_status_key');
|
||||
$key = $k ?? request()->query('k');
|
||||
$key = $k ?? request()->query('k') ?? request()->query('key');
|
||||
if (! $validKey || $key !== $validKey) {
|
||||
$this->authorized = false;
|
||||
|
||||
|
|
|
|||
|
|
@ -90,6 +90,10 @@ class DisplayPlaylistConfigBuilder
|
|||
$data['url'] = $item->content['url'];
|
||||
}
|
||||
|
||||
if (! empty($item->content['qr_image_url'])) {
|
||||
$data['qr_image_url'] = $item->content['qr_image_url'];
|
||||
}
|
||||
|
||||
return $data;
|
||||
});
|
||||
|
||||
|
|
@ -170,6 +174,7 @@ class DisplayPlaylistConfigBuilder
|
|||
'disclaimer' => $content['disclaimer'] ?? '',
|
||||
'show_disclaimer' => $content['show_disclaimer'] ?? ($content['disclaimer'] ?? '') !== '',
|
||||
'qr_url' => $content['qr_url'] ?? '',
|
||||
'qr_image_url' => $content['qr_image_url'] ?? '',
|
||||
'qr_title' => $content['qr_title'] ?? '',
|
||||
'show_qr' => $content['show_qr'] ?? ($content['qr_url'] ?? '') !== '',
|
||||
'contact' => $content['contact'] ?? '',
|
||||
|
|
|
|||
71
app/Services/DisplayQrCodeService.php
Normal file
71
app/Services/DisplayQrCodeService.php
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class DisplayQrCodeService
|
||||
{
|
||||
/**
|
||||
* @return array{path: string, url: string}|null
|
||||
*/
|
||||
public function generateForUrl(?string $url): ?array
|
||||
{
|
||||
$normalizedUrl = $this->normalizeUrl($url);
|
||||
|
||||
if ($normalizedUrl === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$path = 'display-qr-codes/'.sha1($normalizedUrl).'.png';
|
||||
$disk = Storage::disk('public');
|
||||
|
||||
if (! $disk->exists($path)) {
|
||||
try {
|
||||
$response = Http::timeout(10)
|
||||
->retry(2, 300)
|
||||
->get($this->remoteQrUrl($normalizedUrl));
|
||||
|
||||
if (! $response->successful() || $response->body() === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$disk->put($path, $response->body());
|
||||
} catch (\Throwable) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'path' => $path,
|
||||
'url' => asset('storage/'.$path),
|
||||
];
|
||||
}
|
||||
|
||||
public function normalizeUrl(?string $url): string
|
||||
{
|
||||
$url = trim((string) $url);
|
||||
|
||||
if ($url === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (str_starts_with($url, 'http://') || str_starts_with($url, 'https://')) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
return 'https://'.$url;
|
||||
}
|
||||
|
||||
private function remoteQrUrl(string $url): string
|
||||
{
|
||||
return 'https://api.qrserver.com/v1/create-qr-code/?'.http_build_query([
|
||||
'size' => '300x300',
|
||||
'margin' => '8',
|
||||
'color' => '000000',
|
||||
'bgcolor' => 'ffffff',
|
||||
'data' => $url,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -19,7 +19,7 @@ class DisplayModuleSettings
|
|||
],
|
||||
DisplayVersionType::B2in->value => [
|
||||
'theme' => 'dark',
|
||||
'header_logo_url' => '../assets/b2in-logo-positive.svg',
|
||||
'header_logo_url' => '/_cabinet/assets/b2in-logo-positive.svg',
|
||||
'header_claim' => 'Connecting Design & Property',
|
||||
'logo_position' => 'top-left',
|
||||
'claim_position' => 'top-right',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue