mirror of
https://github.com/filamentphp/filament.git
synced 2026-09-24 15:42:09 +08:00
438 lines
11 KiB
PHP
438 lines
11 KiB
PHP
<?php
|
|
|
|
namespace Filament\Tables\Columns;
|
|
|
|
use Closure;
|
|
use Filament\Support\Components\Contracts\HasEmbeddedView;
|
|
use Filament\Support\Enums\Alignment;
|
|
use Illuminate\Contracts\Filesystem\Filesystem;
|
|
use Illuminate\Filesystem\FilesystemAdapter;
|
|
use Illuminate\Support\Arr;
|
|
use Illuminate\Support\Collection;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Illuminate\View\ComponentAttributeBag;
|
|
use League\Flysystem\UnableToCheckFileExistence;
|
|
use Throwable;
|
|
|
|
class ImageColumn extends Column implements HasEmbeddedView
|
|
{
|
|
use Concerns\CanWrap;
|
|
|
|
protected string | Closure | null $disk = null;
|
|
|
|
protected int | string | Closure | null $height = null;
|
|
|
|
protected bool | Closure $isCircular = false;
|
|
|
|
protected bool | Closure $isSquare = false;
|
|
|
|
protected string | Closure $visibility = 'public';
|
|
|
|
protected int | string | Closure | null $width = null;
|
|
|
|
/**
|
|
* @var array<array<mixed> | Closure>
|
|
*/
|
|
protected array $extraImgAttributes = [];
|
|
|
|
protected string | Closure | null $defaultImageUrl = null;
|
|
|
|
protected bool | Closure $isStacked = false;
|
|
|
|
protected int | Closure | null $overlap = null;
|
|
|
|
protected int | Closure | null $ring = null;
|
|
|
|
protected int | Closure | null $limit = null;
|
|
|
|
protected bool | Closure $hasLimitedRemainingText = false;
|
|
|
|
protected string | Closure | null $limitedRemainingTextSize = null;
|
|
|
|
protected bool | Closure $shouldCheckFileExistence = true;
|
|
|
|
public function disk(string | Closure | null $disk): static
|
|
{
|
|
$this->disk = $disk;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function height(int | string | Closure | null $height): static
|
|
{
|
|
$this->height = $height;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function circular(bool | Closure $condition = true): static
|
|
{
|
|
$this->isCircular = $condition;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @deprecated Use `circular()` instead.
|
|
*/
|
|
public function rounded(bool | Closure $condition = true): static
|
|
{
|
|
return $this->circular($condition);
|
|
}
|
|
|
|
public function square(bool | Closure $condition = true): static
|
|
{
|
|
$this->isSquare = $condition;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function size(int | string | Closure $size): static
|
|
{
|
|
$this->width($size);
|
|
$this->height($size);
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function visibility(string | Closure $visibility): static
|
|
{
|
|
$this->visibility = $visibility;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function width(int | string | Closure | null $width): static
|
|
{
|
|
$this->width = $width;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getDisk(): Filesystem
|
|
{
|
|
return Storage::disk($this->getDiskName());
|
|
}
|
|
|
|
public function getDiskName(): string
|
|
{
|
|
return $this->evaluate($this->disk) ?? config('filament.default_filesystem_disk');
|
|
}
|
|
|
|
public function getHeight(): ?string
|
|
{
|
|
$height = $this->evaluate($this->height);
|
|
|
|
if ($height === null) {
|
|
return null;
|
|
}
|
|
|
|
if (is_int($height)) {
|
|
return "{$height}px";
|
|
}
|
|
|
|
return $height;
|
|
}
|
|
|
|
public function defaultImageUrl(string | Closure | null $url): static
|
|
{
|
|
$this->defaultImageUrl = $url;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getImageUrl(?string $state = null): ?string
|
|
{
|
|
if ((filter_var($state, FILTER_VALIDATE_URL) !== false) || str($state)->startsWith('data:')) {
|
|
return $state;
|
|
}
|
|
|
|
/** @var FilesystemAdapter $storage */
|
|
$storage = $this->getDisk();
|
|
|
|
if ($this->shouldCheckFileExistence()) {
|
|
try {
|
|
if (! $storage->exists($state)) {
|
|
return null;
|
|
}
|
|
} catch (UnableToCheckFileExistence $exception) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
if ($this->getVisibility() === 'private') {
|
|
try {
|
|
return $storage->temporaryUrl(
|
|
$state,
|
|
now()->addMinutes(5),
|
|
);
|
|
} catch (Throwable $exception) {
|
|
// This driver does not support creating temporary URLs.
|
|
}
|
|
}
|
|
|
|
return $storage->url($state);
|
|
}
|
|
|
|
public function getDefaultImageUrl(): ?string
|
|
{
|
|
return $this->evaluate($this->defaultImageUrl);
|
|
}
|
|
|
|
public function getVisibility(): string
|
|
{
|
|
return $this->evaluate($this->visibility);
|
|
}
|
|
|
|
public function getWidth(): ?string
|
|
{
|
|
$width = $this->evaluate($this->width);
|
|
|
|
if ($width === null) {
|
|
return null;
|
|
}
|
|
|
|
if (is_int($width)) {
|
|
return "{$width}px";
|
|
}
|
|
|
|
return $width;
|
|
}
|
|
|
|
public function isCircular(): bool
|
|
{
|
|
return (bool) $this->evaluate($this->isCircular);
|
|
}
|
|
|
|
/**
|
|
* @deprecated Use `isCircular()` instead.
|
|
*/
|
|
public function isRounded(): bool
|
|
{
|
|
return $this->isCircular();
|
|
}
|
|
|
|
public function isSquare(): bool
|
|
{
|
|
return (bool) $this->evaluate($this->isSquare);
|
|
}
|
|
|
|
/**
|
|
* @param array<mixed> | Closure $attributes
|
|
*/
|
|
public function extraImgAttributes(array | Closure $attributes, bool $merge = false): static
|
|
{
|
|
if ($merge) {
|
|
$this->extraImgAttributes[] = $attributes;
|
|
} else {
|
|
$this->extraImgAttributes = [$attributes];
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @return array<mixed>
|
|
*/
|
|
public function getExtraImgAttributes(): array
|
|
{
|
|
$temporaryAttributeBag = new ComponentAttributeBag;
|
|
|
|
foreach ($this->extraImgAttributes as $extraImgAttributes) {
|
|
$temporaryAttributeBag = $temporaryAttributeBag->merge($this->evaluate($extraImgAttributes));
|
|
}
|
|
|
|
return $temporaryAttributeBag->getAttributes();
|
|
}
|
|
|
|
public function getExtraImgAttributeBag(): ComponentAttributeBag
|
|
{
|
|
return new ComponentAttributeBag($this->getExtraImgAttributes());
|
|
}
|
|
|
|
public function stacked(bool | Closure $condition = true): static
|
|
{
|
|
$this->isStacked = $condition;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function isStacked(): bool
|
|
{
|
|
return (bool) $this->evaluate($this->isStacked);
|
|
}
|
|
|
|
public function overlap(int | Closure | null $overlap): static
|
|
{
|
|
$this->overlap = $overlap;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getOverlap(): ?int
|
|
{
|
|
return $this->evaluate($this->overlap);
|
|
}
|
|
|
|
public function ring(int | Closure | null $ring): static
|
|
{
|
|
$this->ring = $ring;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getRing(): ?int
|
|
{
|
|
return $this->evaluate($this->ring);
|
|
}
|
|
|
|
public function limit(int | Closure | null $limit = 3): static
|
|
{
|
|
$this->limit = $limit;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getLimit(): ?int
|
|
{
|
|
return $this->evaluate($this->limit);
|
|
}
|
|
|
|
public function limitedRemainingText(bool | Closure $condition = true, string | Closure | null $size = null): static
|
|
{
|
|
$this->hasLimitedRemainingText = $condition;
|
|
$this->limitedRemainingTextSize($size);
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function hasLimitedRemainingText(): bool
|
|
{
|
|
return (bool) $this->evaluate($this->hasLimitedRemainingText);
|
|
}
|
|
|
|
public function limitedRemainingTextSize(string | Closure | null $size): static
|
|
{
|
|
$this->limitedRemainingTextSize = $size;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getLimitedRemainingTextSize(): ?string
|
|
{
|
|
return $this->evaluate($this->limitedRemainingTextSize);
|
|
}
|
|
|
|
public function checkFileExistence(bool | Closure $condition = true): static
|
|
{
|
|
$this->shouldCheckFileExistence = $condition;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function shouldCheckFileExistence(): bool
|
|
{
|
|
return (bool) $this->evaluate($this->shouldCheckFileExistence);
|
|
}
|
|
|
|
public function toEmbeddedHtml(): string
|
|
{
|
|
$state = $this->getState();
|
|
|
|
if ($state instanceof Collection) {
|
|
$state = $state->all();
|
|
}
|
|
|
|
$attributes = $this->getExtraAttributeBag()
|
|
->class([
|
|
'fi-ta-image',
|
|
'fi-inline' => $this->isInline(),
|
|
]);
|
|
|
|
if (empty($state)) {
|
|
$placeholder = $this->getPlaceholder();
|
|
|
|
ob_start(); ?>
|
|
|
|
<div <?= $attributes->toHtml() ?>>
|
|
<?php if (filled($placeholder !== null)) { ?>
|
|
<p class="fi-ta-placeholder">
|
|
<?= e($placeholder) ?>
|
|
</p>
|
|
<?php } ?>
|
|
</div>
|
|
|
|
<?php return ob_get_clean();
|
|
}
|
|
|
|
$state = Arr::wrap($state);
|
|
$stateCount = count($state);
|
|
|
|
$limit = $this->getLimit() ?? $stateCount;
|
|
|
|
$stateOverLimitCount = ($limit && ($stateCount > $limit))
|
|
? ($stateCount - $limit)
|
|
: 0;
|
|
|
|
if ($stateOverLimitCount) {
|
|
$state = array_slice($state, 0, $limit);
|
|
}
|
|
|
|
$alignment = $this->getAlignment();
|
|
$isCircular = $this->isCircular();
|
|
$isSquare = $this->isSquare();
|
|
$isStacked = $this->isStacked();
|
|
$hasLimitedRemainingText = $stateOverLimitCount && $this->hasLimitedRemainingText();
|
|
$limitedRemainingTextSize = $this->getLimitedRemainingTextSize();
|
|
$height = $this->getHeight() ?? ($isStacked ? '2rem' : '2.5rem');
|
|
$width = $this->getWidth() ?? (($isCircular || $isSquare) ? $height : null);
|
|
|
|
$defaultImageUrl = $this->getDefaultImageUrl();
|
|
|
|
$attributes = $attributes
|
|
->class([
|
|
'fi-circular' => $isCircular,
|
|
'fi-wrapped' => $this->canWrap(),
|
|
'fi-stacked' => $isStacked,
|
|
($isStacked && is_int($ring = $this->getRing())) ? "fi-ta-image-ring fi-ta-image-ring-{$ring}" : '',
|
|
($isStacked && ($overlap = ($this->getOverlap() ?? 2))) ? "fi-ta-image-overlap-{$overlap}" : '',
|
|
($alignment instanceof Alignment) ? "fi-align-{$alignment->value}" : (is_string($alignment) ? $alignment : ''),
|
|
]);
|
|
|
|
ob_start(); ?>
|
|
|
|
<div <?= $attributes->toHtml() ?>>
|
|
<?php foreach ($state as $stateItem) { ?>
|
|
<img
|
|
<?= $this->getExtraImgAttributeBag()
|
|
->merge([
|
|
'src' => filled($stateItem) ? $this->getImageUrl($stateItem) : $defaultImageUrl,
|
|
])
|
|
->style([
|
|
"height: {$height}" => $height,
|
|
"width: {$width}" => $width,
|
|
])
|
|
->toHtml() ?>
|
|
/>
|
|
<?php } ?>
|
|
|
|
<?php if ($hasLimitedRemainingText && $stateOverLimitCount) { ?>
|
|
<div <?= (new ComponentAttributeBag)
|
|
->class([
|
|
'fi-ta-image-limited-remaining-text',
|
|
"fi-size-{$limitedRemainingTextSize}" => $limitedRemainingTextSize,
|
|
])
|
|
->style([
|
|
"height: {$height}" => $height,
|
|
"width: {$width}" => $width,
|
|
])
|
|
->toHtml() ?>>
|
|
+<?= $stateOverLimitCount ?>
|
|
</div>
|
|
<?php } ?>
|
|
</div>
|
|
|
|
<?php return ob_get_clean();
|
|
}
|
|
}
|