feat(plugins): handle empty states and long strings in UI

This commit is contained in:
Yassine Doghri 2024-05-15 18:30:56 +00:00
parent adbc7e78c0
commit f18d054ad1
10 changed files with 80 additions and 35 deletions

View File

@ -125,8 +125,15 @@ class PluginController extends BaseController
$data['episode'] = $episode;
}
$fields = $plugin->getSettingsFields($type);
if ($fields === []) {
throw PageNotFoundException::forPageNotFound();
}
$data['type'] = $type;
$data['context'] = $context;
$data['fields'] = $fields;
helper('form');
replace_breadcrumb_params($breadcrumbReplacements);
@ -164,8 +171,8 @@ class PluginController extends BaseController
$rules = [];
foreach ($plugin->getSettingsFields($type) as $field) {
$typeRules = $plugins::FIELDS_VALIDATIONS[$field->type];
if (! in_array('permit_empty', $typeRules, true) && ! $field->optional) {
$typeRules[] = 'required';
if (! in_array('permit_empty', $typeRules, true)) {
$typeRules[] = $field->optional ? 'permit_empty' : 'required';
}
$rules[$field->key] = $typeRules;
@ -182,7 +189,7 @@ class PluginController extends BaseController
foreach ($plugin->getSettingsFields('general') as $field) {
$value = $validatedData[$field->key] ?? null;
$fieldValue = match ($plugins::FIELDS_CASTS[$field->type] ?? 'text') {
$fieldValue = $value === '' ? null : match ($plugins::FIELDS_CASTS[$field->type] ?? 'text') {
'bool' => $value === 'yes',
'int' => (int) $value,
'uri' => new URI($value),
@ -192,7 +199,7 @@ class PluginController extends BaseController
$this->request->getPost('client_timezone')
)->setTimezone(app_timezone()),
'markdown' => new Markdown($value),
default => $value === '' ? null : $value,
default => $value,
};
$plugins->setOption($plugin, $field->key, $fieldValue, $context);
}

View File

@ -108,11 +108,6 @@ abstract class BasePlugin implements PluginInterface
return in_array($name, $this->manifest->hooks, true);
}
final public function getSettings(): ?Settings
{
return $this->manifest->settings;
}
final public function getVersion(): string
{
return $this->manifest->version;
@ -167,7 +162,7 @@ abstract class BasePlugin implements PluginInterface
*/
final public function getSettingsFields(string $type): array
{
$settings = $this->getSettings();
$settings = $this->manifest->settings;
if (! $settings instanceof Settings) {
return [];
}
@ -175,6 +170,14 @@ abstract class BasePlugin implements PluginInterface
return $settings->{$type};
}
/**
* @return list<string>
*/
final public function getHooks(): array
{
return $this->manifest->hooks;
}
final public function getKey(): string
{
return $this->key;

View File

@ -16,7 +16,13 @@ return [
'authors' => 'Authors',
'author_email' => 'Email {authorName}',
'author_homepage' => '{authorName} homepage',
'declaredHooks' => 'Declared hooks',
'settings' => 'Settings',
'settingsTitle' => '{type, select,
podcast {{pluginName} podcast settings}
episode {{pluginName} episode settings}
other {{pluginName} general settings}
}',
'view' => 'View',
'activate' => 'Activate',
'deactivate' => 'Deactivate',
@ -29,6 +35,8 @@ return [
'analytics' => 'Analytics',
'accessibility' => 'Accessibility',
],
'noDescription' => 'No description',
'noReadme' => 'No README file found.',
'messages' => [
'saveSettingsSuccess' => '{pluginName} settings were successfully saved!',
],

View File

@ -46,8 +46,8 @@ class Person extends ManifestObject
$data = [
'name' => $matches['name'],
'email' => $matches['email'],
'url' => $matches['url'],
'email' => $matches['email'] ?? null,
'url' => $matches['url'] ?? null,
];
}

View File

@ -40,10 +40,10 @@ $isEpisodeArea = isset($podcast) && isset($episode);
<?php if (($isEpisodeArea && $episode->is_premium) || ($isPodcastArea && $podcast->is_premium)): ?>
<div class="inline-flex items-center">
<x-IconButton uri="<?= route_to('subscription-list', $podcast->id) ?>" glyph="exchange-dollar-fill" variant="secondary" size="large" class="p-0 mr-2 border-0"><?= ($isEpisodeArea && $episode->is_premium) ? lang('PremiumPodcasts.episode_is_premium') : lang('PremiumPodcasts.podcast_is_premium') ?></x-IconButton>
<x-Heading tagName="h1" size="large" class="truncate"><?= $this->renderSection('pageTitle') ?></x-Heading>
<x-Heading tagName="h1" size="large" class="max-w-sm truncate"><?= $this->renderSection('pageTitle') ?></x-Heading>
</div>
<?php else: ?>
<x-Heading tagName="h1" size="large" class="truncate"><?= $this->renderSection('pageTitle') ?></x-Heading>
<x-Heading tagName="h1" size="large" class="max-w-lg truncate"><?= $this->renderSection('pageTitle') ?></x-Heading>
<?php endif; ?>
<?= $this->renderSection('headerLeft') ?>
</div>

View File

@ -49,11 +49,11 @@
?>
<li class="inline-flex">
<?php if ($isAllowed): ?>
<a class="relative w-full py-3 pl-14 pr-2 text-sm hover:opacity-100 before:content-chevronRightIcon before:absolute before:-ml-5 before:opacity-0 before:w-5 before:h-5 hover:bg-navigation-active<?= $isActive
? ' before:opacity-100 font-semibold inline-flex items-center'
<a class="line-clamp-1 leading-9 relative w-full py-1 pl-14 pr-2 text-sm hover:opacity-100 before:content-chevronRightIcon before:absolute before:top-2 before:-ml-5 before:opacity-0 before:w-5 before:h-5 hover:bg-navigation-active<?= $isActive
? ' before:opacity-100 font-semibold'
: ' hover:before:opacity-60 focus:before:opacity-60' ?>" href="<?= $href ?>"><?= $label ?></a>
<?php else: ?>
<span data-tooltip="right" title="<?= lang('Navigation.not-authorized') ?>" class="relative w-full py-3 pr-2 text-sm cursor-not-allowed before:inset-y-0 before:my-auto pl-14 hover:opacity-100 before:absolute before:content-prohibitedIcon before:-ml-5 before:opacity-60 before:w-4 before:h-4 hover:bg-navigation-active"><?= $label ?></span>
<span data-tooltip="right" title="<?= lang('Navigation.not-authorized') ?>" class="relative w-full py-3 pr-2 text-sm cursor-not-allowed line-clamp-2 before:inset-y-0 before:my-auto pl-14 hover:opacity-100 before:absolute before:content-prohibitedIcon before:-ml-5 before:opacity-60 before:w-4 before:h-4 hover:bg-navigation-active"><?= $label ?></span>
<?php endif; ?>
</li>
<?php endforeach; ?>

View File

@ -7,8 +7,8 @@
<?php endif; ?>
</div>
<img class="rounded-full min-w-16 max-w-16 aspect-square" src="<?= $plugin->getIconSrc() ?>">
<div class="flex flex-col items-start mt-2">
<h2 class="flex items-center text-xl font-bold font-display gap-x-2"><a href="<?= route_to('plugins-view', $plugin->getVendor(), $plugin->getPackage()) ?>" class="hover:underline decoration-accent"><?= $plugin->getName() ?></a></h2>
<div class="flex flex-col items-start mt-2 mb-6">
<h2 class="flex items-center text-xl font-bold font-display gap-x-2" title="<?= $plugin->getName() ?>"><a class="line-clamp-1" href="<?= route_to('plugins-view', $plugin->getVendor(), $plugin->getPackage()) ?>" class="hover:underline decoration-accent"><?= $plugin->getName() ?></a></h2>
<p class="inline-flex font-mono text-xs">
<span class="inline-flex tracking-wide bg-gray-100">
<a href="<?= route_to('plugins-vendor', $plugin->getVendor()) ?>" class="underline underline-offset-2 decoration-2 decoration-dotted hover:decoration-solid decoration-accent"><?= $plugin->getVendor() ?></a>
@ -16,9 +16,9 @@
<a class="underline underline-offset-2 decoration-2 decoration-dotted hover:decoration-solid decoration-accent" href="<?= route_to('plugins-view', $plugin->getVendor(), $plugin->getPackage()) ?>"><?= $plugin->getPackage() ?></a></span>
<span class="mx-1"></span><span class="px-1 font-mono text-xs"><?= $plugin->getVersion() ?></span>
</p>
<p class="mt-2 text-gray-600"><?= $plugin->getDescription() ?></p>
<p class="relative w-full max-w-sm mt-2 text-skin-muted line-clamp-3"><?= $plugin->getDescription() ?? '<span class="absolute inset-0 px-2 m-auto text-sm lowercase shadow-sm w-fit h-fit bg-elevated">' . lang('Plugins.noDescription') . '</span><span class="block w-full h-4 mt-1 bg-gray-100"></span><span class="block w-full h-4 mt-1 bg-gray-100"></span><span class="block w-4/5 h-4 mt-1 bg-gray-100"></span>' ?></p>
</div>
<footer class="flex items-center justify-between mt-6">
<footer class="flex items-center justify-between mt-auto">
<div class="flex gap-x-2">
<?php if ($plugin->getHomepage()): ?>
<?php // @icon('earth-fill')?>
@ -41,9 +41,9 @@
<x-Button type="submit" variant="secondary" size="small"><?= lang('Plugins.activate') ?></x-Button>
</form>
<?php endif; ?>
<?php if ($plugin->getSettings() !== []): ?>
<?php if ($plugin->getSettingsFields('general') !== []): ?>
<?php // @icon('equalizer-fill')?>
<x-IconButton uri="<?= route_to('plugins-general-settings', $plugin->getVendor(), $plugin->getPackage()) ?>" glyph="equalizer-fill"><?= lang('Plugins.settings') ?></x-IconButton>
<x-IconButton uri="<?= route_to('plugins-settings-general', $plugin->getVendor(), $plugin->getPackage()) ?>" glyph="equalizer-fill"><?= lang('Plugins.settings') ?></x-IconButton>
<?php endif; ?>
<button class="p-2 rounded-full" id="more-dropdown-<?= $plugin->getKey() ?>" data-dropdown="button" data-dropdown-target="more-dropdown-<?= $plugin->getKey() ?>-menu" aria-haspopup="true" aria-expanded="false" title="<?= lang('Common.more') ?>"><?= icon('more-2-fill') ?></button>
<?php $items = [

View File

@ -1,7 +1,7 @@
<form method="POST" action="<?= $action ?>" class="flex flex-col max-w-xl gap-4 p-4 sm:p-6 md:p-8 bg-elevated border-3 border-subtle rounded-xl" >
<?= csrf_field() ?>
<?php $hasDatetime = false; ?>
<?php foreach ($plugin->getSettingsFields($type) as $field): ?>
<?php foreach ($fields as $field): ?>
<?php switch ($field->type): case 'checkbox': ?>
<x-Forms.Checkbox
name="<?= $field->key ?>"

View File

@ -1,14 +1,14 @@
<?= $this->extend('_layout') ?>
<?= $this->section('title') ?>
<?= lang('Plugins.settings', [
<?= lang('Plugins.settingsTitle', [
'pluginName' => $plugin->getName(),
'type' => $type,
]) ?>
<?= $this->endSection() ?>
<?= $this->section('pageTitle') ?>
<?= lang('Plugins.settings', [
<?= lang('Plugins.settingsTitle', [
'pluginName' => $plugin->getName(),
'type' => $type,
]) ?>
@ -30,10 +30,10 @@ if (isset($episode)) {
?>
<?= $this->section('content') ?>
<?= view('plugins/_settings', [
<?= view('plugins/_settings_form', [
'plugin' => $plugin,
'action' => route_to(sprintf('plugins-settings-%s-action', $type), ...$params),
'type' => $type,
'fields' => $fields,
'context' => $context,
]) ?>
<?= $this->endSection() ?>

View File

@ -18,14 +18,22 @@
<?= $this->section('headerRight') ?>
<?php if($plugin->isActive()): ?>
<form class="flex justify-end" method="POST" action="<?= route_to('plugins-deactivate', $plugin->getVendor(), $plugin->getPackage()) ?>">
<form class="flex justify-end gap-x-2" method="POST" action="<?= route_to('plugins-deactivate', $plugin->getVendor(), $plugin->getPackage()) ?>">
<?= csrf_field() ?>
<x-Button type="submit" variant="danger"><?= lang('Plugins.deactivate') ?></x-Button>
<?php if ($plugin->getSettingsFields('general') !== []): ?>
<?php // @icon('equalizer-fill')?>
<x-Button class="ring-2 ring-inset ring-gray-600" iconLeft="equalizer-fill" uri="<?= route_to('plugins-settings-general', $plugin->getVendor(), $plugin->getPackage()) ?>"><?= lang('Plugins.settings') ?></x-Button>
<?php endif; ?>
</form>
<?php else: ?>
<form class="flex justify-end" method="POST" action="<?= route_to('plugins-activate', $plugin->getVendor(), $plugin->getPackage()) ?>">
<form class="flex justify-end gap-x-2" method="POST" action="<?= route_to('plugins-activate', $plugin->getVendor(), $plugin->getPackage()) ?>">
<?= csrf_field() ?>
<x-Button type="submit" variant="secondary"><?= lang('Plugins.activate') ?></x-Button>
<?php if ($plugin->getSettingsFields('general') !== []): ?>
<?php // @icon('equalizer-fill')?>
<x-Button class="ring-2 ring-inset ring-gray-600" iconLeft="equalizer-fill" uri="<?= route_to('plugins-settings-general', $plugin->getVendor(), $plugin->getPackage()) ?>"><?= lang('Plugins.settings') ?></x-Button>
<?php endif; ?>
</form>
<?php endif; ?>
<?= $this->endSection() ?>
@ -34,8 +42,10 @@
<div class="flex flex-col items-start justify-center gap-8 mx-auto xl:flex-row-reverse">
<aside class="w-full pb-8 border-b xl:sticky xl:max-w-xs top-28 border-subtle xl:border-none">
<h2 class="mb-2 text-2xl font-bold font-display"><?= lang('Plugins.about') ?></h2>
<p><?= $plugin->getDescription() ?></p>
<a href="<?= $plugin->getHomepage() ?>" class="inline-flex items-center mt-2 font-semibold hover:underline gap-x-2"><?= icon('link') . $plugin->getHomepage() ?></a>
<p class="relative max-w-sm text-skin-muted"><?= $plugin->getDescription() ?? '<span class="absolute inset-0 px-2 m-auto text-sm lowercase shadow-sm w-fit h-fit bg-base">' . lang('Plugins.noDescription') . '</span><span class="block w-full h-4 mt-1 bg-subtle"></span><span class="block w-full h-4 mt-1 bg-subtle"></span><span class="block w-4/5 h-4 mt-1 bg-subtle"></span>' ?></p>
<?php if ($plugin->getHomepage()): ?>
<a href="<?= $plugin->getHomepage() ?>" class="inline-flex items-center mt-2 font-semibold hover:underline gap-x-2"><?= icon('link') . $plugin->getHomepage() ?></a>
<?php endif; ?>
<?php if ($plugin->getKeywords() !== []): ?>
<div class="mt-2">
<?php foreach ($plugin->getKeywords() as $keyword): ?>
@ -75,9 +85,26 @@
<?php endforeach; ?>
</ul>
<?php endif; ?>
<?php if ($plugin->getHooks() !== []): ?>
<h3 class="mt-6 text-lg font-bold font-display"><?= lang('Plugins.declaredHooks') ?></h3>
<ul>
<?php foreach ($plugin->getHooks() as $hook): ?>
<li><?= $hook ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</aside>
<section class="max-w-2xl prose prose-headings:font-display">
<?= $plugin->getReadmeHTML() ?>
</section>
<?php if($plugin->getReadmeHTML()): ?>
<section class="w-full max-w-3xl p-4 prose border rounded-t-lg rounded-b-lg xl:rounded-t-none xl:-mt-12 md:p-6 xl:p-12 prose-headings:font-display bg-elevated border-subtle">
<?= $plugin->getReadmeHTML() ?>
</section>
<?php else: ?>
<section class="flex flex-col items-center justify-center w-full max-w-3xl p-4 border rounded-t-lg rounded-b-lg xl:rounded-t-none xl:-mt-12 md:p-6 xl:p-12 bg-elevated border-subtle min-h-96">
<?= icon('article-line', [
'class' => 'text-gray-300 text-6xl',
]) ?>
<p class="mt-2 font-semibold text-skin-muted"><?= lang('Plugins.noReadme') ?></p>
</section>
<?php endif; ?>
</div>
<?= $this->endSection() ?>