mirror of
https://code.castopod.org/adaures/castopod
synced 2025-04-19 13:01:19 +00:00
feat: add DropdownMenu component + remove global audio player in admin
This commit is contained in:
parent
d60498c1be
commit
abb7fbac27
79
app/Resources/js/admin-audio-player.ts
Normal file
79
app/Resources/js/admin-audio-player.ts
Normal file
@ -0,0 +1,79 @@
|
||||
import {
|
||||
VmAudio,
|
||||
VmCaptions,
|
||||
VmClickToPlay,
|
||||
VmControl,
|
||||
VmControls,
|
||||
VmCurrentTime,
|
||||
VmDefaultControls,
|
||||
VmDefaultSettings,
|
||||
VmDefaultUi,
|
||||
VmEndTime,
|
||||
VmFile,
|
||||
VmIcon,
|
||||
VmIconLibrary,
|
||||
VmLoadingScreen,
|
||||
VmMenu,
|
||||
VmMenuItem,
|
||||
VmMenuRadio,
|
||||
VmMenuRadioGroup,
|
||||
VmMuteControl,
|
||||
VmPlaybackControl,
|
||||
VmPlayer,
|
||||
VmScrubberControl,
|
||||
VmSettings,
|
||||
VmSettingsControl,
|
||||
VmSkeleton,
|
||||
VmSlider,
|
||||
VmSubmenu,
|
||||
VmTime,
|
||||
VmTimeProgress,
|
||||
VmTooltip,
|
||||
VmUi,
|
||||
VmVolumeControl,
|
||||
} from "@vime/core";
|
||||
import "@vime/core/themes/default.css";
|
||||
import "@vime/core/themes/light.css";
|
||||
import "./modules/play-episode-button";
|
||||
|
||||
// Register Castopod's icons library
|
||||
const library: HTMLVmIconLibraryElement | null = document.querySelector(
|
||||
'vm-icon-library[name="castopod-icons"]'
|
||||
);
|
||||
if (library) {
|
||||
library.resolver = (iconName) => `/assets/icons/${iconName}.svg`;
|
||||
}
|
||||
|
||||
// Vime elements for audio player
|
||||
customElements.define("vm-player", VmPlayer);
|
||||
customElements.define("vm-file", VmFile);
|
||||
customElements.define("vm-audio", VmAudio);
|
||||
customElements.define("vm-ui", VmUi);
|
||||
customElements.define("vm-default-ui", VmDefaultUi);
|
||||
customElements.define("vm-click-to-play", VmClickToPlay);
|
||||
customElements.define("vm-captions", VmCaptions);
|
||||
customElements.define("vm-loading-screen", VmLoadingScreen);
|
||||
customElements.define("vm-default-controls", VmDefaultControls);
|
||||
customElements.define("vm-default-settings", VmDefaultSettings);
|
||||
customElements.define("vm-controls", VmControls);
|
||||
customElements.define("vm-playback-control", VmPlaybackControl);
|
||||
customElements.define("vm-volume-control", VmVolumeControl);
|
||||
customElements.define("vm-scrubber-control", VmScrubberControl);
|
||||
customElements.define("vm-current-time", VmCurrentTime);
|
||||
customElements.define("vm-end-time", VmEndTime);
|
||||
customElements.define("vm-settings-control", VmSettingsControl);
|
||||
customElements.define("vm-time-progress", VmTimeProgress);
|
||||
customElements.define("vm-control", VmControl);
|
||||
customElements.define("vm-icon", VmIcon);
|
||||
customElements.define("vm-icon-library", VmIconLibrary);
|
||||
customElements.define("vm-tooltip", VmTooltip);
|
||||
customElements.define("vm-mute-control", VmMuteControl);
|
||||
customElements.define("vm-slider", VmSlider);
|
||||
customElements.define("vm-time", VmTime);
|
||||
customElements.define("vm-menu", VmMenu);
|
||||
customElements.define("vm-menu-item", VmMenuItem);
|
||||
customElements.define("vm-submenu", VmSubmenu);
|
||||
customElements.define("vm-menu-radio-group", VmMenuRadioGroup);
|
||||
customElements.define("vm-menu-radio", VmMenuRadio);
|
||||
customElements.define("vm-settings", VmSettings);
|
||||
customElements.define("vm-skeleton", VmSkeleton);
|
51
app/Views/Components/DropdownMenu.php
Normal file
51
app/Views/Components/DropdownMenu.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Views\Components;
|
||||
|
||||
use Exception;
|
||||
use ViewComponents\Component;
|
||||
|
||||
class DropdownMenu extends Component
|
||||
{
|
||||
public string $id = '';
|
||||
|
||||
public array $items = [];
|
||||
|
||||
public function setItems(string $value): void
|
||||
{
|
||||
$this->items = json_decode(html_entity_decode($value), true);
|
||||
}
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
if ($this->items === []) {
|
||||
throw new Exception('Dropdown menu has no items');
|
||||
}
|
||||
|
||||
$menuItems = '';
|
||||
foreach ($this->items as $item) {
|
||||
switch ($item['type']) {
|
||||
case 'link':
|
||||
$menuItems .= anchor($item['uri'], $item['title'], [
|
||||
'class' => 'px-4 py-1 hover:bg-gray-100' . (array_key_exists('class', $item) ? ' ' . $item['class'] : ''),
|
||||
]);
|
||||
break;
|
||||
case 'separator':
|
||||
$menuItems .= '<hr class="my-2 border border-gray-100">';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return <<<HTML
|
||||
<nav id="{$this->id}"
|
||||
class="absolute z-50 flex flex-col py-2 text-black whitespace-no-wrap bg-white border-black rounded-lg border-3"
|
||||
aria-labelledby="{$this->labeledBy}"
|
||||
data-dropdown="menu"
|
||||
data-dropdown-placement="bottom-end">{$menuItems}</nav>
|
||||
HTML;
|
||||
}
|
||||
}
|
@ -14,7 +14,7 @@
|
||||
<?= service('vite')
|
||||
->asset('js/admin.ts', 'js') ?>
|
||||
<?= service('vite')
|
||||
->asset('js/audio-player.ts', 'js') ?>
|
||||
->asset('js/admin-audio-player.ts', 'js') ?>
|
||||
</head>
|
||||
|
||||
<body class="relative bg-pine-50 holy-grail-grid">
|
||||
@ -40,28 +40,26 @@
|
||||
data-dropdown="button"
|
||||
data-dropdown-target="my-account-dropdown-menu"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false">
|
||||
<?= icon('account-circle', 'text-2xl opacity-60 mr-2') ?>
|
||||
<?= user()
|
||||
->username ?>
|
||||
<?= icon('caret-down', 'ml-auto text-2xl') ?>
|
||||
</button>
|
||||
<nav
|
||||
id="my-account-dropdown-menu"
|
||||
class="absolute z-50 flex flex-col py-2 text-black whitespace-no-wrap bg-white border-black rounded border-[3px]"
|
||||
aria-labelledby="my-accountDropdown"
|
||||
data-dropdown="menu"
|
||||
data-dropdown-placement="bottom-end">
|
||||
<a class="px-4 py-1 hover:bg-gray-100" href="<?= route_to(
|
||||
'my-account',
|
||||
) ?>"><?= lang('AdminNavigation.account.my-account') ?></a>
|
||||
<a class="px-4 py-1 hover:bg-gray-100" href="<?= route_to(
|
||||
'change-password',
|
||||
) ?>"><?= lang('AdminNavigation.account.change-password') ?></a>
|
||||
<a class="px-4 py-1 hover:bg-gray-100" href="<?= route_to(
|
||||
'logout',
|
||||
) ?>"><?= lang('AdminNavigation.account.logout') ?></a>
|
||||
</nav>
|
||||
aria-expanded="false"><?= icon('account-circle', 'text-2xl opacity-60 mr-2') . user()->username . icon('caret-down', 'ml-auto text-2xl') ?></button>
|
||||
<DropdownMenu id="my-account-dropdown-menu" labeledBy="my-account-dropdown" items="<?= esc(json_encode([
|
||||
[
|
||||
'type' => 'link',
|
||||
'title' => lang('AdminNavigation.account.my-account'),
|
||||
'uri' => route_to('my-account'),
|
||||
],
|
||||
[
|
||||
'type' => 'link',
|
||||
'title' => lang('AdminNavigation.account.change-password'),
|
||||
'uri' => route_to('change-password'),
|
||||
],
|
||||
[
|
||||
'type' => 'separator',
|
||||
],
|
||||
[
|
||||
'type' => 'link',
|
||||
'title' => lang('AdminNavigation.account.logout'),
|
||||
'uri' => route_to('logout'),
|
||||
], ])) ?>" />
|
||||
</header>
|
||||
<aside id="admin-sidebar" class="sticky z-50 flex flex-col text-white transition duration-200 ease-in-out transform -translate-x-full border-r top-10 border-pine-900 bg-pine-800 holy-grail__sidebar md:translate-x-0">
|
||||
<?php if (isset($podcast) && isset($episode)): ?>
|
||||
@ -80,7 +78,7 @@
|
||||
</footer>
|
||||
</aside>
|
||||
<main class="relative holy-grail__main">
|
||||
<header class="z-40 flex items-center bg-white border-b sticky-header-outer border-pine-100">
|
||||
<header class="z-40 flex items-center px-4 bg-white border-b md:px-12 sticky-header-outer border-pine-100">
|
||||
<div class="container flex flex-col justify-end mx-auto -mt-4 sticky-header-inner">
|
||||
<?= render_breadcrumb('text-gray-800 text-xs items-center flex') ?>
|
||||
<div class="flex justify-between py-1">
|
||||
|
@ -74,44 +74,45 @@
|
||||
[
|
||||
'header' => lang('Episode.list.actions'),
|
||||
'cell' => function ($episode, $podcast) {
|
||||
return '<button id="more-dropdown-<?= $episode->id ?>" type="button" class="inline-flex items-center p-1 outline-none focus:ring" data-dropdown="button" data-dropdown-target="more-dropdown-<?= $episode->id ?>-menu" aria-haspopup="true" aria-expanded="false">' .
|
||||
return '<button id="more-dropdown-' . $episode->id . '" type="button" class="inline-flex items-center p-1 outline-none focus:ring" data-dropdown="button" data-dropdown-target="more-dropdown-' . $episode->id . '-menu" aria-haspopup="true" aria-expanded="false">' .
|
||||
icon('more') .
|
||||
'</button>' .
|
||||
'<nav id="more-dropdown-<?= $episode->id ?>-menu" class="flex flex-col py-2 text-black whitespace-no-wrap bg-white border rounded shadow" aria-labelledby="more-dropdown-<?= $episode->id ?>" data-dropdown="menu" data-dropdown-placement="bottom-start" data-dropdown-offset-x="0" data-dropdown-offset-y="-24">' .
|
||||
'<a class="px-4 py-1 hover:bg-gray-100" href="' . route_to(
|
||||
'episode-edit',
|
||||
$podcast->id,
|
||||
$episode->id,
|
||||
) . '">' . lang('Episode.edit') . '</a>' .
|
||||
'<a class="px-4 py-1 hover:bg-gray-100" href="' . route_to(
|
||||
'embeddable-player-add',
|
||||
$podcast->id,
|
||||
$episode->id,
|
||||
) . '">' . lang(
|
||||
'Episode.embeddable_player.title',
|
||||
) . '</a>' .
|
||||
'<a class="px-4 py-1 hover:bg-gray-100" href="' . route_to(
|
||||
'episode-persons-manage',
|
||||
$podcast->id,
|
||||
$episode->id,
|
||||
) . '">' . lang('Person.persons') . '</a>' .
|
||||
'<a class="px-4 py-1 hover:bg-gray-100" href="' . route_to(
|
||||
'soundbites-edit',
|
||||
$podcast->id,
|
||||
$episode->id,
|
||||
) . '">' . lang('Episode.soundbites') . '</a>' .
|
||||
'<a class="px-4 py-1 hover:bg-gray-100" href="' . route_to(
|
||||
'episode',
|
||||
$podcast->handle,
|
||||
$episode->slug,
|
||||
) . '">' . lang('Episode.go_to_page') . '</a>' .
|
||||
'<a class="px-4 py-1 hover:bg-gray-100" href="' . route_to(
|
||||
'episode-delete',
|
||||
$podcast->id,
|
||||
$episode->id,
|
||||
) . '">' . lang('Episode.delete') . '</a>' .
|
||||
'</nav>' .
|
||||
'</div>';
|
||||
'<DropdownMenu id="more-dropdown-' . $episode->id . '-menu" labeledBy="more-dropdown-' . $episode->id . '" items="' . esc(json_encode([
|
||||
[
|
||||
'type' => 'link',
|
||||
'title' => lang('Episode.edit'),
|
||||
'uri' => route_to('episode-edit', $podcast->id, $episode->id),
|
||||
],
|
||||
[
|
||||
'type' => 'link',
|
||||
'title' => lang('Episode.embeddable_player.title'),
|
||||
'uri' => route_to('embeddable-player-add', $podcast->id, $episode->id),
|
||||
],
|
||||
[
|
||||
'type' => 'link',
|
||||
'title' => lang('Person.persons'),
|
||||
'uri' => route_to('episode-persons-manage', $podcast->id, $episode->id),
|
||||
],
|
||||
[
|
||||
'type' => 'link',
|
||||
'title' => lang('Episode.soundbites'),
|
||||
'uri' => route_to('soundbites-edit', $podcast->id, $episode->id),
|
||||
],
|
||||
[
|
||||
'type' => 'link',
|
||||
'title' => lang('Episode.go_to_page'),
|
||||
'uri' => route_to('episode', $podcast->handle, $episode->slug),
|
||||
],
|
||||
[
|
||||
'type' => 'separator',
|
||||
],
|
||||
[
|
||||
'type' => 'link',
|
||||
'title' => lang('Episode.delete'),
|
||||
'uri' => route_to('episode-delete', $podcast->id, $episode->id),
|
||||
'class' => 'font-semibold text-red-600',
|
||||
],
|
||||
])) . '" />';
|
||||
},
|
||||
],
|
||||
],
|
||||
|
@ -52,41 +52,42 @@
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
><?= icon('more') ?></button>
|
||||
<nav
|
||||
id="more-dropdown-<?= $episode->id ?>-menu"
|
||||
class="z-50 flex flex-col py-2 text-black whitespace-no-wrap bg-white border rounded shadow"
|
||||
aria-labelledby="more-dropdown-<?= $episode->id ?>"
|
||||
data-dropdown="menu"
|
||||
data-dropdown-placement="bottom">
|
||||
<a class="px-4 py-1 hover:bg-gray-100" href="<?= route_to(
|
||||
'episode-edit',
|
||||
$podcast->id,
|
||||
$episode->id,
|
||||
) ?>"><?= lang('Episode.edit') ?></a>
|
||||
<a class="px-4 py-1 hover:bg-gray-100" href="<?= route_to(
|
||||
'embeddable-player-add',
|
||||
$podcast->id,
|
||||
$episode->id,
|
||||
) ?>"><?= lang(
|
||||
'Episode.embeddable_player.title',
|
||||
) ?></a>
|
||||
<a class="px-4 py-1 hover:bg-gray-100" href="<?= route_to(
|
||||
'episode-persons-manage',
|
||||
$podcast->id,
|
||||
$episode->id,
|
||||
) ?>"><?= lang('Person.persons') ?></a>
|
||||
<a class="px-4 py-1 hover:bg-gray-100" href="<?= route_to(
|
||||
'episode',
|
||||
$podcast->handle,
|
||||
$episode->slug,
|
||||
) ?>"><?= lang('Episode.go_to_page') ?></a>
|
||||
<hr class="my-2 border border-gray-100">
|
||||
<a class="px-4 py-1 font-semibold text-red-600 hover:bg-gray-100" href="<?= route_to(
|
||||
'episode-delete',
|
||||
$podcast->id,
|
||||
$episode->id,
|
||||
) ?>"><?= lang('Episode.delete') ?></a>
|
||||
</nav>
|
||||
<DropdownMenu id="more-dropdown-<?= $episode->id ?>-menu" labeledBy="more-dropdown-<?= $episode->id ?>" items="<?= esc(json_encode([
|
||||
[
|
||||
'type' => 'link',
|
||||
'title' => lang('Episode.edit'),
|
||||
'uri' => route_to('episode-edit', $podcast->id, $episode->id),
|
||||
],
|
||||
[
|
||||
'type' => 'link',
|
||||
'title' => lang('Episode.embeddable_player.title'),
|
||||
'uri' => route_to('embeddable-player-add', $podcast->id, $episode->id),
|
||||
],
|
||||
[
|
||||
'type' => 'link',
|
||||
'title' => lang('Person.persons'),
|
||||
'uri' => route_to('episode-persons-manage', $podcast->id, $episode->id),
|
||||
],
|
||||
[
|
||||
'type' => 'link',
|
||||
'title' => lang('Episode.soundbites'),
|
||||
'uri' => route_to('soundbites-edit', $podcast->id, $episode->id),
|
||||
],
|
||||
[
|
||||
'type' => 'link',
|
||||
'title' => lang('Episode.go_to_page'),
|
||||
'uri' => route_to('episode', $podcast->handle, $episode->slug),
|
||||
],
|
||||
[
|
||||
'type' => 'separator',
|
||||
],
|
||||
[
|
||||
'type' => 'link',
|
||||
'title' => lang('Episode.delete'),
|
||||
'uri' => route_to('episode-delete', $podcast->id, $episode->id),
|
||||
'class' => 'font-semibold text-red-600',
|
||||
],
|
||||
])) ?>" />
|
||||
</div>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
|
Loading…
x
Reference in New Issue
Block a user