image fixes

This commit is contained in:
2026-05-14 21:15:33 +03:00
parent ef29e4d261
commit 8cd80c016f
14 changed files with 180 additions and 38 deletions
+3 -3
View File
@@ -1,6 +1,6 @@
<script> <script>
import Icon from "../common/Icon.svelte"; import Icon from "../common/Icon.svelte";
import { imgurl } from "./imageserver.js"; import { fileurl, imgurl } from "./imageserver.js";
import { getContext } from "svelte"; import { getContext } from "svelte";
export let file; export let file;
@@ -11,7 +11,6 @@
let fileSide; let fileSide;
let fontSize; let fontSize;
console.log({ channel });
if (size == "large") { if (size == "large") {
imageSide = 256; imageSide = 256;
fileSide = 32; fileSide = 32;
@@ -36,7 +35,8 @@
{#if file.mime.startsWith("image")} {#if file.mime.startsWith("image")}
<!-- href={imgurl(record)} --> <!-- href={imgurl(record)} -->
<a <a
href="{channel.lucentUrl}/files/{file.id}" target="_blank"
href={fileurl(channel, file)}
title={file.filename} title={file.filename}
style="width:{imageSide}px;height:{imageSide}px" style="width:{imageSide}px;height:{imageSide}px"
> >
+4 -2
View File
@@ -2,7 +2,8 @@ export function imgurl(channel, file) {
if (file.mime === "image/svg+xml") { if (file.mime === "image/svg+xml") {
return fileurl(channel, file); return fileurl(channel, file);
} }
return channel.filesUrl + `/thumbs/${file.path}`; const webpPath = file.path.slice(0, file.path.lastIndexOf(".")) + ".webp";
return channel.filesUrl + `/thumbs/${webpPath}`;
} }
export function fileurl(channel, file) { export function fileurl(channel, file) {
@@ -16,7 +17,8 @@ export function htmlurl(channel, file, preset) {
if (file.width > 0) { if (file.width > 0) {
let presetUrl = url; let presetUrl = url;
if (preset) { if (preset) {
presetUrl = channel.filesUrl + `/templates/${preset}/${file.path}`; const webpPath = file.path.slice(0, file.path.lastIndexOf(".")) + ".webp";
presetUrl = channel.filesUrl + `/templates/${preset}/${webpPath}`;
} }
html = `<img src="${presetUrl}" alt="${file.path}" />`; html = `<img src="${presetUrl}" alt="${file.path}" />`;
} else if (file.mime === "image/svg+xml") { } else if (file.mime === "image/svg+xml") {
+13
View File
@@ -3,6 +3,7 @@
namespace Lucent\Channel; namespace Lucent\Channel;
use Lucent\Channel\Data\UserCommand; use Lucent\Channel\Data\UserCommand;
use Lucent\Data\ImagePreset;
use Lucent\Primitive\Collection; use Lucent\Primitive\Collection;
use Lucent\Data\Schema; use Lucent\Data\Schema;
use Lucent\Data\ChannelAuth; use Lucent\Data\ChannelAuth;
@@ -12,6 +13,10 @@ final class Channel
public string $lucentUrl; public string $lucentUrl;
public string $filesUrl; public string $filesUrl;
public string $previewTargetUrl; public string $previewTargetUrl;
/**
* @param array<ImagePreset> $imagePresets
*/
public array $imagePresets;
/** /**
* @param Collection<Schema> $schemas * @param Collection<Schema> $schemas
@@ -30,6 +35,14 @@ final class Channel
$this->lucentUrl = $url . "/lucent"; $this->lucentUrl = $url . "/lucent";
$this->filesUrl = $this->makeFilesUrl(); $this->filesUrl = $this->makeFilesUrl();
$this->previewTargetUrl = $url . "/" . $previewTarget; $this->previewTargetUrl = $url . "/" . $previewTarget;
$this->imagePresets = array_map(function ($i) {
$preset = new $i();
return new ImagePreset(
id: $i,
name: $preset->getName(),
path: $preset->getPath(),
);
}, $this->imageFilters);
} }
private function makeFilesUrl(): string private function makeFilesUrl(): string
+23 -1
View File
@@ -45,7 +45,7 @@ final class ChannelService
previewTarget: rtrim(config("lucent.previewTarget") ?? "", "/"), previewTarget: rtrim(config("lucent.previewTarget") ?? "", "/"),
commands: Collection::make($userCommands), commands: Collection::make($userCommands),
schemas: $schemasCollection, schemas: $schemasCollection,
imageFilters: config("lucent.imageFilters") ?? [], imageFilters: self::loadImageFilters(),
roles: $schemasArray["roles"] ?? [], roles: $schemasArray["roles"] ?? [],
); );
@@ -54,6 +54,28 @@ final class ChannelService
return $channelService; return $channelService;
} }
private static function loadImageFilters(): array
{
$relativePath = config("lucent.image_filter_path");
if (!$relativePath) {
return [];
}
$dir = base_path($relativePath);
if (!is_dir($dir)) {
return [];
}
$namespace = str_replace("/", "\\", ucwords($relativePath, "/"));
return array_values(
array_map(
fn(string $file) => $namespace . "\\" . basename($file, ".php"),
glob("{$dir}/*.php") ?: [],
),
);
}
/** /**
* @param string $name * @param string $name
* @return Option<Schema> * @return Option<Schema>
+60
View File
@@ -0,0 +1,60 @@
<?php
namespace Lucent\Commands;
use Illuminate\Console\Command;
class GenerateImageFilter extends Command
{
protected $signature = "lucent:generate:image_filter {name}";
protected $description = "Generate an image filter";
public function handle()
{
$name = $this->argument("name");
$relativePath = config("lucent.image_filter_path");
$dir = base_path($relativePath);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
$filePath = "{$dir}/{$name}.php";
if (file_exists($filePath)) {
$this->error("Filter {$name} already exists at {$filePath}");
return Command::FAILURE;
}
$namespace = str_replace("/", "\\", ucwords($relativePath, "/"));
$stub = <<<PHP
<?php namespace {$namespace};
use Lucent\ImageFilterInterface;
use Intervention\Image\Interfaces\ImageInterface;
class {$name} implements ImageFilterInterface
{
public function apply(ImageInterface \$image): ImageInterface
{
return \$image;
}
public function getName(): string {
return "{$name}";
}
public function getPath(): string {
return "{$name}";
}
}
PHP;
file_put_contents($filePath, $stub);
$this->info("Created {$filePath}");
return Command::SUCCESS;
}
}
+4 -2
View File
@@ -27,10 +27,12 @@ class RebuildThumbnails extends Command
$files = FileRepo::query()->get(); $files = FileRepo::query()->get();
$disk = $this->fileService->loadPublicDisk(); $disk = $this->fileService->loadPublicDisk();
foreach ($files as $file) { foreach ($files as $file) {
$this->fileService->createTemplates($disk, $file->path);
try { try {
$this->fileService->createTemplates($disk, $file->path);
} catch (Exception $e) { } catch (Exception $e) {
echo "File " . $file->filename . " could not be rebuilt \n"; $this->error(
"File " . $file->filename . " could not be rebuilt \n",
);
} }
} }
} }
+1 -11
View File
@@ -7,6 +7,7 @@ return [
"private_disk" => env("LUCENT_PRIVATE_DISK", "local"), "private_disk" => env("LUCENT_PRIVATE_DISK", "local"),
"public_disk" => env("LUCENT_PUBLIC_DISK", "public"), "public_disk" => env("LUCENT_PUBLIC_DISK", "public"),
"schemas_path" => env("LUCENT_SCHEMAS_PATH", "resources/lucent/schemas"), "schemas_path" => env("LUCENT_SCHEMAS_PATH", "resources/lucent/schemas"),
"image_filter_path" => "app/Lucent/ImageFilters",
"database" => env("LUCENT_DB_CONNECTION", env("DB_CONNECTION", "sqlite")), "database" => env("LUCENT_DB_CONNECTION", env("DB_CONNECTION", "sqlite")),
"name" => env("LUCENT_NAME", "Lucent"), "name" => env("LUCENT_NAME", "Lucent"),
"url" => env("LUCENT_URL", env("APP_URL")), "url" => env("LUCENT_URL", env("APP_URL")),
@@ -21,17 +22,6 @@ return [
* *
* */ * */
"commands" => [], "commands" => [],
/*
* Image filter will be available both for rich editor fields
* and throughout your application
*
* example:
* [
* "filterName" => Filter::class
* ]
*
* */
"imageFilters" => [],
"canInvite" => ["admin"], "canInvite" => ["admin"],
"canBuild" => ["admin"], "canBuild" => ["admin"],
"systemUserId" => "", "systemUserId" => "",
+12
View File
@@ -0,0 +1,12 @@
<?php
namespace Lucent\Data;
class ImagePreset
{
function __construct(
public string $id,
public string $name,
public string $path,
) {}
}
+31 -8
View File
@@ -6,6 +6,7 @@ use Illuminate\Contracts\Filesystem\Filesystem;
use Illuminate\Http\UploadedFile; use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Intervention\Image\Format;
use Intervention\Image\ImageManager; use Intervention\Image\ImageManager;
use Lucent\Channel\ChannelService; use Lucent\Channel\ChannelService;
use Lucent\Id\Id; use Lucent\Id\Id;
@@ -125,21 +126,43 @@ class FileService
public function createTemplates(Filesystem $disk, string $path): void public function createTemplates(Filesystem $disk, string $path): void
{ {
$originalImage = $this->imageManager->make( $originalImage = $this->imageManager->decode(
$this->loadPublicDisk()->get("lucent/" . $path), $this->loadPublicDisk()->get("lucent/" . $path),
); );
foreach (config("lucent.imageFilters") as $filterClass) { foreach ($this->channelService->channel->imageFilters as $filterClass) {
$imageClone = clone $originalImage; $imageClone = clone $originalImage;
$image = $imageClone->filter(new $filterClass()); $filterClassInstance = new $filterClass();
$image = $imageClone->modify($filterClassInstance);
$templateUri = $templateUri =
"lucent/templates/" . $filterClass->name() . "/" . $path; "lucent/templates/" .
$disk->put($templateUri, $image->encode("webp", 75)); $filterClassInstance->getPath() .
"/" .
substr($path, 0, strrpos($path, ".")) .
".webp";
$disk->put(
$templateUri,
$image->encodeUsingFormat(
Format::WEBP,
progressive: true,
quality: 80,
),
);
} }
$thumbDir = "lucent/thumbs/" . $path; $thumbDir =
"lucent/thumbs/" . substr($path, 0, strrpos($path, ".")) . ".webp";
$image = $originalImage->fit(300, 300); $image = $originalImage->cover(300, 300);
$disk->put($thumbDir, $image->encode("webp", 75)); $disk->put(
$thumbDir,
$image->encodeUsingFormat(
Format::WEBP,
progressive: true,
quality: 80,
),
);
} }
/** /**
+11
View File
@@ -0,0 +1,11 @@
<?php
namespace Lucent;
use Intervention\Image\Interfaces\ModifierInterface;
interface ImageFilterInterface extends ModifierInterface
{
public function getName(): string;
public function getPath(): string;
}
+4 -1
View File
@@ -16,6 +16,7 @@ use Lucent\Account\UserRepoLunar;
use Lucent\Channel\ChannelService; use Lucent\Channel\ChannelService;
use Lucent\Commands\CompileSchemas; use Lucent\Commands\CompileSchemas;
use Lucent\Commands\GenerateCollectionSchema; use Lucent\Commands\GenerateCollectionSchema;
use Lucent\Commands\GenerateImageFilter;
use Lucent\Commands\LiveLink; use Lucent\Commands\LiveLink;
use Lucent\Commands\RebuildThumbnails; use Lucent\Commands\RebuildThumbnails;
use Lucent\Commands\RemoveOrphanEdges; use Lucent\Commands\RemoveOrphanEdges;
@@ -27,6 +28,7 @@ use Lucent\Data\ChannelAuth;
use Lucent\File\FileService; use Lucent\File\FileService;
use Lucent\Query\DatabaseGraph\DatabaseGraph; use Lucent\Query\DatabaseGraph\DatabaseGraph;
use Lucent\Query\DatabaseGraph\PgsqlDatabaseGraph; use Lucent\Query\DatabaseGraph\PgsqlDatabaseGraph;
use Intervention\Image\Drivers\Imagick\Driver;
class LucentServiceProvider extends ServiceProvider class LucentServiceProvider extends ServiceProvider
{ {
@@ -40,7 +42,7 @@ class LucentServiceProvider extends ServiceProvider
}); });
$this->app->bind(ImageManager::class, function () { $this->app->bind(ImageManager::class, function () {
return new ImageManager(["driver" => "imagick"]); return ImageManager::usingDriver(new Driver());
}); });
$this->app->bind(DatabaseGraph::class, function () { $this->app->bind(DatabaseGraph::class, function () {
@@ -99,6 +101,7 @@ class LucentServiceProvider extends ServiceProvider
RemoveOrphanEdges::class, RemoveOrphanEdges::class,
SetupDatabase::class, SetupDatabase::class,
GenerateCollectionSchema::class, GenerateCollectionSchema::class,
GenerateImageFilter::class,
Export::class, Export::class,
Import::class, Import::class,
Setup::class, Setup::class,
+5
View File
@@ -0,0 +1,5 @@
<?php
namespace Lucent\Modules;
class ImageModule {}
+1 -5
View File
@@ -6,12 +6,10 @@ use Lucent\ArrayContainer;
class RecordData extends ArrayContainer class RecordData extends ArrayContainer
{ {
public function merge(RecordData $data): RecordData public function merge(RecordData $data): RecordData
{ {
$this->data = array_merge($this->data, $data->toArray()); $this->data = array_merge($this->data, $data->toArray());
return $this; return $this;
} }
@@ -19,6 +17,4 @@ class RecordData extends ArrayContainer
{ {
return $this->data; return $this->data;
} }
} }
+8 -5
View File
@@ -44,9 +44,9 @@ if (!function_exists("schemas_path")) {
} }
if (!function_exists("lucent_file")) { if (!function_exists("lucent_file")) {
function lucent_file(\Lucent\Data\File $file): string function lucent_file(array $file): string
{ {
$path = $file->path; $path = $file["path"];
return app()->make(\Lucent\Channel\ChannelService::class)->channel return app()->make(\Lucent\Channel\ChannelService::class)->channel
->filesUrl . ->filesUrl .
"/" . "/" .
@@ -55,10 +55,13 @@ if (!function_exists("lucent_file")) {
} }
if (!function_exists("lucent_image")) { if (!function_exists("lucent_image")) {
function lucent_image(\Lucent\Data\File $file, string $template): string function lucent_image(array $file, string $template): string
{ {
$path = $file->path; $path = $file["path"];
return app()->make(\Lucent\Channel\ChannelService::class)->channel return app()->make(\Lucent\Channel\ChannelService::class)->channel
->filesUrl . "/templates/$template/$path"; ->filesUrl .
"/templates/$template/" .
substr($path, 0, strrpos($path, ".")) .
".webp";
} }
} }