mirror of
https://code.castopod.org/adaures/castopod
synced 2025-04-19 13:01:19 +00:00
feat(plugins): handle empty states and long strings in UI
This commit is contained in:
parent
adbc7e78c0
commit
f18d054ad1
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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!',
|
||||
],
|
||||
|
@ -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,
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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; ?>
|
||||
|
@ -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 = [
|
||||
|
@ -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 ?>"
|
@ -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() ?>
|
||||
|
@ -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() ?>
|
Loading…
x
Reference in New Issue
Block a user