init with kirby, vue and pagedjs interactive

This commit is contained in:
isUnknown 2025-11-24 14:01:48 +01:00
commit dc0ae26464
968 changed files with 211706 additions and 0 deletions

View file

@ -0,0 +1,64 @@
<?php
namespace Kirby\Panel\Ui;
use Kirby\Toolkit\I18n;
/**
* @package Kirby Panel
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
* @since 5.0.0
*/
class Button extends Component
{
public function __construct(
public string $component = 'k-button',
public array|null $badge = null,
public string|null $class = null,
public string|bool|null $current = null,
public string|null $dialog = null,
public bool $disabled = false,
public string|null $drawer = null,
public bool|null $dropdown = null,
public string|null $icon = null,
public string|null $link = null,
public bool|string $responsive = true,
public string|null $size = null,
public string|null $style = null,
public string|null $target = null,
public string|array|null $text = null,
public string|null $theme = null,
public string|array|null $title = null,
public string $type = 'button',
public string|null $variant = null,
...$attrs
) {
$this->attrs = $attrs;
}
public function props(): array
{
return [
...parent::props(),
'badge' => $this->badge,
'current' => $this->current,
'dialog' => $this->dialog,
'disabled' => $this->disabled,
'drawer' => $this->drawer,
'dropdown' => $this->dropdown,
'icon' => $this->icon,
'link' => $this->link,
'responsive' => $this->responsive,
'size' => $this->size,
'target' => $this->target,
'text' => I18n::translate($this->text, $this->text),
'theme' => $this->theme,
'title' => I18n::translate($this->title, $this->title),
'type' => $this->type,
'variant' => $this->variant,
];
}
}

View file

@ -0,0 +1,33 @@
<?php
namespace Kirby\Panel\Ui\Buttons;
use Kirby\Cms\App;
use Kirby\Toolkit\I18n;
/**
* View button to create a new language
*
* @package Kirby Panel
* @author Nico Hoffmann <nico@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
* @since 5.0.0
* @unstable
*/
class LanguageCreateButton extends ViewButton
{
public function __construct()
{
$user = App::instance()->user();
$permission = $user?->role()->permissions()->for('languages', 'create');
parent::__construct(
dialog: 'languages/create',
disabled: $permission !== true,
icon: 'add',
text: I18n::translate('language.create'),
);
}
}

View file

@ -0,0 +1,34 @@
<?php
namespace Kirby\Panel\Ui\Buttons;
use Kirby\Cms\App;
use Kirby\Cms\Language;
use Kirby\Toolkit\I18n;
/**
* View button to delete a language
*
* @package Kirby Panel
* @author Nico Hoffmann <nico@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
* @since 5.0.0
* @unstable
*/
class LanguageDeleteButton extends ViewButton
{
public function __construct(Language $language)
{
$user = App::instance()->user();
$permission = $user?->role()->permissions()->for('languages', 'delete');
parent::__construct(
dialog: 'languages/' . $language->id() . '/delete',
disabled: $permission !== true,
icon: 'trash',
title: I18n::translate('delete'),
);
}
}

View file

@ -0,0 +1,34 @@
<?php
namespace Kirby\Panel\Ui\Buttons;
use Kirby\Cms\App;
use Kirby\Cms\Language;
use Kirby\Toolkit\I18n;
/**
* View button to update settings of a language
*
* @package Kirby Panel
* @author Nico Hoffmann <nico@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
* @since 5.0.0
* @unstable
*/
class LanguageSettingsButton extends ViewButton
{
public function __construct(Language $language)
{
$user = App::instance()->user();
$permission = $user?->role()->permissions()->for('languages', 'update');
parent::__construct(
dialog: 'languages/' . $language->id() . '/update',
disabled: $permission !== true,
icon: 'cog',
title: I18n::translate('settings'),
);
}
}

View file

@ -0,0 +1,120 @@
<?php
namespace Kirby\Panel\Ui\Buttons;
use Kirby\Cms\App;
use Kirby\Cms\Language;
use Kirby\Cms\Languages;
use Kirby\Cms\ModelWithContent;
use Kirby\Toolkit\Str;
/**
* View button to switch content translation languages
*
* @package Kirby Panel
* @author Nico Hoffmann <nico@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
* @since 5.0.0
* @unstable
*/
class LanguagesDropdown extends ViewButton
{
protected App $kirby;
public function __construct(
ModelWithContent $model
) {
$this->kirby = $model->kirby();
parent::__construct(
component: 'k-languages-dropdown',
model: $model,
class: 'k-languages-dropdown',
icon: 'translate',
// Fiber dropdown endpoint to load options
// only when dropdown is opened
options: $model->panel()->url(true) . '/languages',
responsive: 'text',
text: Str::upper($this->kirby->language()?->code())
);
}
/**
* Returns if any translation other than the current one has unsaved changes
* (the current language has to be handled in `k-languages-dropdown` as its
* state can change dynamically without another backend request)
*/
public function hasDiff(): bool
{
foreach (Languages::ensure() as $language) {
if ($this->kirby->language()?->code() !== $language->code()) {
if ($this->model->version('changes')->exists($language) === true) {
return true;
}
}
}
return false;
}
public function option(Language $language): array
{
$changes = $this->model->version('changes');
return [
'text' => $language->name(),
'code' => $language->code(),
'current' => $language->code() === $this->kirby->language()?->code(),
'default' => $language->isDefault(),
'changes' => $changes->exists($language),
'lock' => $changes->isLocked('*')
];
}
/**
* Options are used in the Fiber dropdown routes
*/
public function options(): array
{
$languages = $this->kirby->languages();
$options = [];
if ($this->kirby->multilang() === false) {
return $options;
}
// add the primary/default language first
if ($default = $languages->default()) {
$options[] = $this->option($default);
$options[] = '-';
$languages = $languages->not($default);
}
// add all secondary languages after the separator
foreach ($languages as $language) {
$options[] = $this->option($language);
}
return $options;
}
public function props(): array
{
return [
...parent::props(),
'hasDiff' => $this->hasDiff()
];
}
public function render(): array|null
{
// hides the language selector when there are less than 2 languages
if ($this->kirby->languages()->count() < 2) {
return null;
}
return parent::render();
}
}

View file

@ -0,0 +1,32 @@
<?php
namespace Kirby\Panel\Ui\Buttons;
use Kirby\Toolkit\I18n;
/**
* Open view button
*
* @package Kirby Panel
* @author Nico Hoffmann <nico@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
* @since 5.0.0
* @unstable
*/
class OpenButton extends ViewButton
{
public function __construct(
public string|null $link,
public string|null $target = '_blank'
) {
parent::__construct(
class: 'k-open-view-button',
icon: 'open',
link: $link,
target: $target,
title: I18n::translate('open')
);
}
}

View file

@ -0,0 +1,50 @@
<?php
namespace Kirby\Panel\Ui\Buttons;
use Kirby\Cms\Page;
use Kirby\Toolkit\I18n;
/**
* Status view button for pages
*
* @package Kirby Panel
* @author Nico Hoffmann <nico@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
* @since 5.0.0
* @unstable
*/
class PageStatusButton extends ViewButton
{
public function __construct(
Page $page
) {
$status = $page->status();
$blueprint = $page->blueprint()->status()[$status] ?? null;
$disabled = $page->permissions()->cannot('changeStatus');
$text = $blueprint['label'] ?? I18n::translate('page.status.' . $status);
$title = I18n::translate('page.status') . ': ' . $text;
if ($disabled === true) {
$title .= ' (' . I18n::translate('disabled') . ')';
}
parent::__construct(
class: 'k-status-view-button k-page-status-button',
component: 'k-status-view-button',
dialog: $page->panel()->url(true) . '/changeStatus',
disabled: $disabled,
icon: 'status-' . $status,
style: '--icon-size: 15px',
text: $text,
title: $title,
theme: match($status) {
'draft' => 'negative-icon',
'unlisted' => 'info-icon',
'listed' => 'positive-icon'
}
);
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace Kirby\Panel\Ui\Buttons;
use Kirby\Toolkit\I18n;
/**
* Preview view button
*
* @package Kirby Panel
* @author Nico Hoffmann <nico@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
* @since 5.0.0
* @unstable
*/
class PreviewButton extends ViewButton
{
public function __construct(
public string|null $link
) {
parent::__construct(
class: 'k-preview-view-button',
icon: 'window',
link: $link,
title: I18n::translate('preview')
);
}
}

View file

@ -0,0 +1,32 @@
<?php
namespace Kirby\Panel\Ui\Buttons;
use Kirby\Cms\ModelWithContent;
use Kirby\Toolkit\I18n;
/**
* Settings view button for models
*
* @package Kirby Panel
* @author Nico Hoffmann <nico@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
* @since 5.0.0
* @unstable
*/
class SettingsButton extends ViewButton
{
public function __construct(
ModelWithContent $model
) {
parent::__construct(
component: 'k-settings-view-button',
class: 'k-settings-view-button',
icon: 'cog',
options: $model->panel()->url(true),
title: I18n::translate('settings'),
);
}
}

View file

@ -0,0 +1,57 @@
<?php
namespace Kirby\Panel\Ui\Buttons;
use Kirby\Cms\ModelWithContent;
use Kirby\Content\VersionId;
use Kirby\Toolkit\I18n;
/**
* Versions view button for models
*
* @package Kirby Panel
* @author Nico Hoffmann <nico@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
* @since 5.0.0
* @unstable
*/
class VersionsButton extends ViewButton
{
public function __construct(
ModelWithContent $model,
VersionId|string $versionId = 'latest'
) {
$versionId = $versionId === 'compare' ? 'compare' : VersionId::from($versionId)->value();
$viewUrl = $model->panel()->url(true) . '/preview';
parent::__construct(
class: 'k-versions-view-button',
icon: $versionId === 'compare' ? 'layout-columns' : 'git-branch',
options: [
[
'label' => I18n::translate('version.latest'),
'icon' => 'git-branch',
'link' => $viewUrl . '/latest',
'current' => $versionId === 'latest'
],
[
'label' => I18n::translate('version.changes'),
'icon' => 'git-branch',
'link' => $viewUrl . '/changes',
'current' => $versionId === 'changes'
],
'-',
[
'label' => I18n::translate('version.compare'),
'icon' => 'layout-columns',
'link' => $viewUrl . '/compare',
'current' => $versionId === 'compare'
],
],
text: I18n::translate('version.' . $versionId),
);
}
}

View file

@ -0,0 +1,215 @@
<?php
namespace Kirby\Panel\Ui\Buttons;
use Closure;
use Kirby\Cms\App;
use Kirby\Cms\Language;
use Kirby\Cms\ModelWithContent;
use Kirby\Panel\Panel;
use Kirby\Panel\Ui\Button;
use Kirby\Toolkit\Controller;
/**
* A view button is a UI button, by default small in size and filles,
* that optionally defines options for a dropdown
*
* @package Kirby Panel
* @author Nico Hoffmann <nico@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
* @since 5.0.0
*/
class ViewButton extends Button
{
public function __construct(
public string $component = 'k-view-button',
public readonly ModelWithContent|Language|null $model = null,
public array|null $badge = null,
public string|null $class = null,
public string|bool|null $current = null,
public string|null $dialog = null,
public bool $disabled = false,
public string|null $drawer = null,
public bool|null $dropdown = null,
public string|null $icon = null,
public string|null $link = null,
public array|string|null $options = null,
public bool|string $responsive = true,
public string|null $size = 'sm',
public string|null $style = null,
public string|null $target = null,
public string|array|null $text = null,
public string|null $theme = null,
public string|array|null $title = null,
public string $type = 'button',
public string|null $variant = 'filled',
...$attrs
) {
$this->attrs = $attrs;
}
/**
* Creates new view button by looking up
* the button in all areas, if referenced by name
* and resolving to proper instance
*/
public static function factory(
string|array|Closure|bool $button = true,
string|int|null $name = null,
string|null $view = null,
ModelWithContent|Language|null $model = null,
array $data = []
): static|null {
// if referenced by name (`name: false`),
// don't render anything
if ($button === false) {
return null;
}
// transform `- name` notation to `name: true`
if (
is_string($name) === false &&
is_string($button) === true
) {
$name = $button;
$button = true;
}
// if referenced by name (`name: true`),
// try to get button definition from areas or config
if ($button === true) {
$button = static::find($name, $view);
}
// resolve Closure to button object or array
if ($button instanceof Closure) {
$button = static::resolve($button, $model, $data);
}
if (
$button === null ||
$button instanceof ViewButton
) {
return $button;
}
// flatten array into list of arguments for this class
$button = static::normalize($button);
// if button definition has a name, use it for the component name
if (is_string($name) === true) {
// if this specific component does not exist,
// `k-view-buttons` will fall back to `k-view-button` again
$button['component'] ??= 'k-' . $name . '-view-button';
}
return new static(...$button, model: $model);
}
/**
* Finds a view button by name
* among the defined buttons from all areas
* @unstable
*/
public static function find(
string $name,
string|null $view = null
): array|Closure {
// collect all buttons from areas and config
$buttons = [
...Panel::buttons(),
...App::instance()->option('panel.viewButtons.' . $view, [])
];
// try to find by full name (view-prefixed)
if ($view && $button = $buttons[$view . '.' . $name] ?? null) {
return $button;
}
// try to find by just name
if ($button = $buttons[$name] ?? null) {
return $button;
}
// assume it must be a custom view button component
return ['component' => 'k-' . $name . '-view-button'];
}
/**
* Transforms an array to be used as
* named arguments in the constructor
* @unstable
*/
public static function normalize(array $button): array
{
// if component and props are both not set, assume shortcut
// where props were directly passed on top-level
if (
isset($button['component']) === false &&
isset($button['props']) === false
) {
return $button;
}
// flatten array
if ($props = $button['props'] ?? null) {
$button = [...$props, ...$button];
unset($button['props']);
}
return $button;
}
public function props(): array
{
// helper for props that support Kirby queries
$resolve = fn ($value) =>
$value ?
$this->model?->toSafeString($value) ?? $value :
null;
return [
...$props = parent::props(),
'dialog' => $resolve($props['dialog']),
'drawer' => $resolve($props['drawer']),
'icon' => $resolve($props['icon']),
'link' => $resolve($props['link']),
'text' => $resolve($props['text']),
'theme' => $resolve($props['theme']),
'options' => $this->options
];
}
/**
* Transforms a closure to the actual view button
* by calling it with the provided arguments
*/
public static function resolve(
Closure $button,
ModelWithContent|Language|null $model = null,
array $data = []
): static|array|null {
$kirby = App::instance();
$controller = new Controller($button);
if (
$model instanceof ModelWithContent ||
$model instanceof Language
) {
$data = [
'model' => $model,
$model::CLASS_ALIAS => $model,
...$data
];
}
return $controller->call(data: [
'kirby' => $kirby,
'site' => $kirby->site(),
'user' => $kirby->user(),
...$data
]);
}
}

View file

@ -0,0 +1,104 @@
<?php
namespace Kirby\Panel\Ui\Buttons;
use Kirby\Cms\App;
use Kirby\Cms\Language;
use Kirby\Cms\ModelWithContent;
use Kirby\Panel\Model;
/**
* Collects view buttons for a specific view
*
* @package Kirby Panel
* @author Nico Hoffmann <nico@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
* @since 5.0.0
* @unstable
*/
class ViewButtons
{
public function __construct(
public readonly string $view,
public readonly ModelWithContent|Language|null $model = null,
public array|false|null $buttons = null,
public array $data = []
) {
// if no specific buttons are passed,
// use default buttons for this view from config
$this->buttons ??= App::instance()->option(
'panel.viewButtons.' . $view
);
}
/**
* Adds data passed to view button closures
*
* @return $this
*/
public function bind(array $data): static
{
$this->data = [...$this->data, ...$data];
return $this;
}
/**
* Sets the default buttons
*
* @return $this
*/
public function defaults(string ...$defaults): static
{
$this->buttons ??= $defaults;
return $this;
}
/**
* Returns array of button component-props definitions
*/
public function render(): array
{
// hides all buttons when `buttons: false` set
if ($this->buttons === false) {
return [];
}
$buttons = [];
foreach ($this->buttons ?? [] as $name => $button) {
$buttons[] = ViewButton::factory(
button: $button,
name: $name,
view: $this->view,
model: $this->model,
data: $this->data
)?->render();
}
return array_values(array_filter($buttons));
}
/**
* Creates new instance for a view
* with special support for model views
*/
public static function view(
string|Model $view,
ModelWithContent|Language|null $model = null
): static {
if ($view instanceof Model) {
$model = $view->model();
$blueprint = $model->blueprint()->buttons();
$view = $model::CLASS_ALIAS;
}
return new static(
view: $view,
model: $model ?? null,
buttons: $blueprint ?? null
);
}
}

View file

@ -0,0 +1,93 @@
<?php
namespace Kirby\Panel\Ui;
use Kirby\Exception\LogicException;
use Kirby\Toolkit\Str;
/**
* Component that can be passed as component-props array
* to the Vue Panel frontend
*
* @package Kirby Panel
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
* @since 5.0.0
*/
abstract class Component
{
protected string $key;
public array $attrs = [];
public function __construct(
public string $component,
public string|null $class = null,
public string|null $style = null,
...$attrs
) {
$this->attrs = $attrs;
}
/**
* Magic setter and getter for component properties
*
* ```php
* $component->class('my-class')
* ```
*/
public function __call(string $name, array $args = [])
{
if (property_exists($this, $name) === false) {
throw new LogicException(
message: 'The property "' . $name . '" does not exist on the UI component "' . $this->component . '"'
);
}
// getter
if ($args === []) {
return $this->$name;
}
// setter
$this->$name = $args[0];
return $this;
}
/**
* Returns a (unique) key that can be used
* for Vue's `:key` attribute
*/
public function key(): string
{
return $this->key ??= Str::random(10, 'alphaNum');
}
/**
* Returns the props that will be passed to the Vue component
*/
public function props(): array
{
return [
'class' => $this->class,
'style' => $this->style,
...$this->attrs
];
}
/**
* Returns array with the Vue component name and props array
*/
public function render(): array|null
{
return [
'component' => $this->component,
'key' => $this->key(),
'props' => array_filter(
$this->props(),
fn ($prop) => $prop !== null
)
];
}
}

View file

@ -0,0 +1,105 @@
<?php
namespace Kirby\Panel\Ui;
use Kirby\Cms\App;
use Kirby\Cms\File;
use Kirby\Exception\InvalidArgumentException;
use Kirby\Panel\Ui\FilePreviews\DefaultFilePreview;
use Kirby\Toolkit\I18n;
/**
* Defines a component that implements a file preview
*
* @package Kirby Panel
* @author Nico Hoffmann <nico@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
* @since 5.0.0
*/
abstract class FilePreview extends Component
{
public function __construct(
public File $file,
public string $component
) {
}
/**
* Returns true if this class should
* handle the preview of this file
*/
abstract public static function accepts(File $file): bool;
/**
* Returns detail information about the file
*/
public function details(): array
{
return [
[
'title' => I18n::translate('template'),
'text' => $this->file->template() ?? '—'
],
[
'title' => I18n::translate('mime'),
'text' => $this->file->mime()
],
[
'title' => I18n::translate('url'),
'link' => $link = $this->file->previewUrl(),
'text' => $link,
],
[
'title' => I18n::translate('size'),
'text' => $this->file->niceSize()
],
];
}
/**
* Returns a file preview instance by going through all
* available handler classes and finding the first that
* accepts the file
*/
final public static function factory(File $file): static
{
// get file preview classes providers from plugins
$handlers = App::instance()->extensions('filePreviews');
foreach ($handlers as $handler) {
if (is_subclass_of($handler, self::class) === false) {
throw new InvalidArgumentException(
message: 'File preview handler "' . $handler . '" must extend ' . self::class
);
}
if ($handler::accepts($file) === true) {
return new $handler($file);
}
}
return new DefaultFilePreview($file);
}
/**
* Icon or image to display as thumbnail
*/
public function image(): array|null
{
return $this->file->panel()->image([
'back' => 'transparent',
'ratio' => '1/1'
], 'cards');
}
public function props(): array
{
return [
'details' => $this->details(),
'image' => $this->image(),
'url' => $this->file->previewUrl()
];
}
}

View file

@ -0,0 +1,29 @@
<?php
namespace Kirby\Panel\Ui\FilePreviews;
use Kirby\Cms\File;
use Kirby\Panel\Ui\FilePreview;
/**
* @package Kirby Panel
* @author Nico Hoffmann <nico@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
* @since 5.0.0
* @unstable
*/
class AudioFilePreview extends FilePreview
{
public function __construct(
public File $file,
public string $component = 'k-audio-file-preview'
) {
}
public static function accepts(File $file): bool
{
return $file->type() === 'audio';
}
}

View file

@ -0,0 +1,42 @@
<?php
namespace Kirby\Panel\Ui\FilePreviews;
use Kirby\Cms\File;
use Kirby\Panel\Ui\FilePreview;
/**
* Fallback file preview component
*
* @package Kirby Panel
* @author Nico Hoffmann <nico@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
* @since 5.0.0
* @unstable
*/
class DefaultFilePreview extends FilePreview
{
public function __construct(
public File $file,
public string $component = 'k-default-file-preview'
) {
}
/**
* Accepts any file as last resort
*/
public static function accepts(File $file): bool
{
return true;
}
public function props(): array
{
return [
...parent::props(),
'image' => $this->image()
];
}
}

View file

@ -0,0 +1,53 @@
<?php
namespace Kirby\Panel\Ui\FilePreviews;
use Kirby\Cms\File;
use Kirby\Panel\Ui\FilePreview;
use Kirby\Toolkit\I18n;
/**
* @package Kirby Panel
* @author Nico Hoffmann <nico@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
* @since 5.0.0
* @unstable
*/
class ImageFilePreview extends FilePreview
{
public function __construct(
public File $file,
public string $component = 'k-image-file-preview'
) {
}
public static function accepts(File $file): bool
{
return $file->type() === 'image';
}
public function details(): array
{
return [
...parent::details(),
[
'title' => I18n::translate('dimensions'),
'text' => $this->file->dimensions() . ' ' . I18n::translate('pixel')
],
[
'title' => I18n::translate('orientation'),
'text' => I18n::translate('orientation.' . $this->file->dimensions()->orientation())
]
];
}
public function props(): array
{
return [
...parent::props(),
'focusable' => $this->file->panel()->isFocusable()
];
}
}

View file

@ -0,0 +1,29 @@
<?php
namespace Kirby\Panel\Ui\FilePreviews;
use Kirby\Cms\File;
use Kirby\Panel\Ui\FilePreview;
/**
* @package Kirby Panel
* @author Nico Hoffmann <nico@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
* @since 5.0.0
* @unstable
*/
class PdfFilePreview extends FilePreview
{
public function __construct(
public File $file,
public string $component = 'k-pdf-file-preview'
) {
}
public static function accepts(File $file): bool
{
return $file->extension() === 'pdf';
}
}

View file

@ -0,0 +1,29 @@
<?php
namespace Kirby\Panel\Ui\FilePreviews;
use Kirby\Cms\File;
use Kirby\Panel\Ui\FilePreview;
/**
* @package Kirby Panel
* @author Nico Hoffmann <nico@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
* @since 5.0.0
* @unstable
*/
class VideoFilePreview extends FilePreview
{
public function __construct(
public File $file,
public string $component = 'k-video-file-preview'
) {
}
public static function accepts(File $file): bool
{
return $file->type() === 'video';
}
}

View file

@ -0,0 +1,74 @@
<?php
namespace Kirby\Panel\Ui\Item;
use Kirby\Cms\File;
use Kirby\Cms\ModelWithContent;
use Kirby\Panel\Model;
/**
* @package Kirby Panel
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
* @since 5.1.0
*/
class FileItem extends ModelItem
{
/**
* @var \Kirby\Cms\File
*/
protected ModelWithContent $model;
/**
* @var \Kirby\Panel\File
*/
protected Model $panel;
public function __construct(
File $file,
protected bool $dragTextIsAbsolute = false,
string|array|false|null $image = [],
string|null $info = null,
string|null $layout = null,
string|null $text = null,
) {
parent::__construct(
model: $file,
image: $image,
info: $info,
layout: $layout,
text: $text ?? '{{ file.filename }}',
);
}
protected function dragText(): string
{
return $this->panel->dragText(absolute: $this->dragTextIsAbsolute);
}
protected function permissions(): array
{
$permissions = $this->model->permissions();
return [
'delete' => $permissions->can('delete'),
'sort' => $permissions->can('sort'),
];
}
public function props(): array
{
return [
...parent::props(),
'dragText' => $this->dragText(),
'extension' => $this->model->extension(),
'filename' => $this->model->filename(),
'mime' => $this->model->mime(),
'parent' => $this->model->parent()->panel()->path(),
'template' => $this->model->template(),
'url' => $this->model->url(),
];
}
}

View file

@ -0,0 +1,74 @@
<?php
namespace Kirby\Panel\Ui\Item;
use Kirby\Cms\ModelWithContent;
use Kirby\Panel\Model as Panel;
use Kirby\Panel\Ui\Component;
/**
* @package Kirby Panel
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
* @since 5.1.0
*/
class ModelItem extends Component
{
protected string $layout;
protected Panel $panel;
protected string $text;
public function __construct(
protected ModelWithContent $model,
protected string|array|false|null $image = [],
protected string|null $info = null,
string|null $layout = null,
string|null $text = null,
) {
parent::__construct(component: 'k-item');
$this->layout = $layout ?? 'list';
$this->panel = $this->model->panel();
$this->text = $text ?? '{{ model.title }}';
}
protected function info(): string|null
{
return $this->model->toSafeString($this->info ?? false);
}
protected function image(): array|null
{
return $this->panel->image($this->image, $this->layout);
}
protected function link(): string
{
return $this->panel->url(true);
}
protected function permissions(): array
{
return $this->model->permissions()->toArray();
}
public function props(): array
{
return [
'id' => $this->model->id(),
'image' => $this->image(),
'info' => $this->info(),
'link' => $this->link(),
'permissions' => $this->permissions(),
'text' => $this->text(),
'uuid' => $this->model->uuid()?->toString(),
];
}
protected function text(): string
{
return $this->model->toSafeString($this->text);
}
}

View file

@ -0,0 +1,74 @@
<?php
namespace Kirby\Panel\Ui\Item;
use Kirby\Cms\ModelWithContent;
use Kirby\Cms\Page;
use Kirby\Panel\Model;
/**
* @package Kirby Panel
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
* @since 5.1.0
*/
class PageItem extends ModelItem
{
/**
* @var \Kirby\Cms\Page
*/
protected ModelWithContent $model;
/**
* @var \Kirby\Panel\Page
*/
protected Model $panel;
public function __construct(
Page $page,
string|array|false|null $image = [],
string|null $info = null,
string|null $layout = null,
string|null $text = null,
) {
parent::__construct(
model: $page,
image: $image,
info: $info,
layout: $layout,
text: $text ?? '{{ page.title }}',
);
}
protected function dragText(): string
{
return $this->panel->dragText();
}
protected function permissions(): array
{
$permissions = $this->model->permissions();
return [
'changeSlug' => $permissions->can('changeSlug'),
'changeStatus' => $permissions->can('changeStatus'),
'changeTitle' => $permissions->can('changeTitle'),
'delete' => $permissions->can('delete'),
'sort' => $permissions->can('sort'),
];
}
public function props(): array
{
return [
...parent::props(),
'dragText' => $this->dragText(),
'parent' => $this->model->parentId(),
'status' => $this->model->status(),
'template' => $this->model->intendedTemplate()->name(),
'url' => $this->model->url(),
];
}
}

View file

@ -0,0 +1,38 @@
<?php
namespace Kirby\Panel\Ui\Item;
use Kirby\Cms\ModelWithContent;
use Kirby\Cms\User;
/**
* @package Kirby Panel
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
* @since 5.1.0
*/
class UserItem extends ModelItem
{
/**
* @var \Kirby\Cms\User
*/
protected ModelWithContent $model;
public function __construct(
User $user,
string|array|false|null $image = [],
string|null $info = '{{ user.role.title }}',
string|null $layout = null,
string|null $text = null,
) {
parent::__construct(
model: $user,
image: $image,
info: $info,
layout: $layout,
text: $text ?? '{{ user.username }}',
);
}
}

View file

@ -0,0 +1,140 @@
<?php
namespace Kirby\Panel\Ui;
use Kirby\Cms\ModelWithContent;
use Kirby\Exception\InvalidArgumentException;
use Kirby\Toolkit\I18n;
/**
* @package Kirby Panel
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
* @since 5.1.0
*/
class Stat extends Component
{
public function __construct(
public array|string $label,
public array|string $value,
public string $component = 'k-stat',
public array|string|null $dialog = null,
public array|string|null $drawer = null,
public string|null $icon = null,
public array|string|null $info = null,
public array|string|null $link = null,
public ModelWithContent|null $model = null,
public string|null $theme = null,
) {
}
public function dialog(): string|null
{
return $this->stringTemplate(
$this->i18n($this->dialog)
);
}
public function drawer(): string|null
{
return $this->stringTemplate(
$this->i18n($this->drawer)
);
}
/**
* @psalm-suppress TooFewArguments
*/
public static function from(
array|string $input,
ModelWithContent|null $model = null,
): static {
if ($model !== null) {
if (is_string($input) === true) {
$input = $model->query($input);
if (is_array($input) === false) {
throw new InvalidArgumentException(
message: 'Invalid data from stat query. The query must return an array.'
);
}
}
$input['model'] = $model;
}
return new static(...$input);
}
public function icon(): string|null
{
return $this->stringTemplate($this->icon);
}
public function info(): string|null
{
return $this->stringTemplate(
$this->i18n($this->info)
);
}
public function label(): string
{
return $this->stringTemplate(
$this->i18n($this->label)
);
}
public function link(): string|null
{
return $this->stringTemplate(
$this->i18n($this->link)
);
}
public function props(): array
{
return [
'dialog' => $this->dialog(),
'drawer' => $this->drawer(),
'icon' => $this->icon(),
'info' => $this->info(),
'label' => $this->label(),
'link' => $this->link(),
'theme' => $this->theme(),
'value' => $this->value(),
];
}
protected function stringTemplate(string|null $string = null): string|null
{
if ($this->model === null) {
return $string;
}
if ($string !== null) {
return $this->model->toString($string);
}
return null;
}
public function theme(): string|null
{
return $this->stringTemplate($this->theme);
}
protected function i18n(string|array|null $param = null): string|null
{
return empty($param) === false ? I18n::translate($param, $param) : null;
}
public function value(): string
{
return $this->stringTemplate(
$this->i18n($this->value)
);
}
}

View file

@ -0,0 +1,83 @@
<?php
namespace Kirby\Panel\Ui;
use Kirby\Cms\ModelWithContent;
use Kirby\Exception\InvalidArgumentException;
/**
* @package Kirby Panel
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
* @since 5.1.0
*/
class Stats extends Component
{
public function __construct(
public string $component = 'k-stats',
public ModelWithContent|null $model = null,
public array $reports = [],
public string $size = 'large',
) {
}
public static function from(
ModelWithContent $model,
array|string $reports,
string $size = 'large'
): static {
if (is_string($reports) === true) {
$reports = $model->query($reports);
if (is_array($reports) === false) {
throw new InvalidArgumentException(
message: 'Invalid data from stats query. The query must return an array.'
);
}
}
return new static(
model: $model,
reports: $reports,
size: $size
);
}
public function props(): array
{
return [
'reports' => $this->reports(),
'size' => $this->size(),
];
}
public function reports(): array
{
$reports = [];
foreach ($this->reports as $stat) {
// if not already a Stat object, convert it
if ($stat instanceof Stat === false) {
try {
$stat = Stat::from(
input: $stat,
model: $this->model
);
} catch (InvalidArgumentException) {
continue;
}
}
$reports[] = $stat->props();
}
return $reports;
}
public function size(): string
{
return $this->size;
}
}

View file

@ -0,0 +1,62 @@
<?php
namespace Kirby\Panel\Ui;
/**
* @package Kirby Panel
* @author Bastian Allgeier <bastian@getkirby.com>
* @link https://getkirby.com
* @copyright Bastian Allgeier
* @license https://getkirby.com/license
* @since 5.1.0
*/
class Upload
{
public function __construct(
protected string $api,
protected string|null $accept = null,
protected array $attributes = [],
protected int|null $max = null,
protected bool $multiple = true,
protected array|bool|null $preview = null,
protected int|null $sort = null,
protected string|null $template = null,
) {
}
protected function attributes(): array
{
return [
...$this->attributes,
'sort' => $this->sort,
'template' => $this->template()
];
}
protected function max(): int|null
{
return $this->multiple() === false ? 1 : $this->max;
}
protected function multiple(): bool
{
return $this->multiple === true && ($this->max === null || $this->max > 1);
}
public function props(): array
{
return [
'accept' => $this->accept,
'api' => $this->api,
'attributes' => $this->attributes(),
'max' => $this->max(),
'multiple' => $this->multiple(),
'preview' => $this->preview,
];
}
protected function template(): string|null
{
return $this->template === 'default' ? null : $this->template;
}
}