mirror of
https://code.castopod.org/adaures/castopod
synced 2025-06-06 18:31:05 +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')
|
<?= service('vite')
|
||||||
->asset('js/admin.ts', 'js') ?>
|
->asset('js/admin.ts', 'js') ?>
|
||||||
<?= service('vite')
|
<?= service('vite')
|
||||||
->asset('js/audio-player.ts', 'js') ?>
|
->asset('js/admin-audio-player.ts', 'js') ?>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="relative bg-pine-50 holy-grail-grid">
|
<body class="relative bg-pine-50 holy-grail-grid">
|
||||||
@ -40,28 +40,26 @@
|
|||||||
data-dropdown="button"
|
data-dropdown="button"
|
||||||
data-dropdown-target="my-account-dropdown-menu"
|
data-dropdown-target="my-account-dropdown-menu"
|
||||||
aria-haspopup="true"
|
aria-haspopup="true"
|
||||||
aria-expanded="false">
|
aria-expanded="false"><?= icon('account-circle', 'text-2xl opacity-60 mr-2') . user()->username . icon('caret-down', 'ml-auto text-2xl') ?></button>
|
||||||
<?= icon('account-circle', 'text-2xl opacity-60 mr-2') ?>
|
<DropdownMenu id="my-account-dropdown-menu" labeledBy="my-account-dropdown" items="<?= esc(json_encode([
|
||||||
<?= user()
|
[
|
||||||
->username ?>
|
'type' => 'link',
|
||||||
<?= icon('caret-down', 'ml-auto text-2xl') ?>
|
'title' => lang('AdminNavigation.account.my-account'),
|
||||||
</button>
|
'uri' => route_to('my-account'),
|
||||||
<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]"
|
'type' => 'link',
|
||||||
aria-labelledby="my-accountDropdown"
|
'title' => lang('AdminNavigation.account.change-password'),
|
||||||
data-dropdown="menu"
|
'uri' => route_to('change-password'),
|
||||||
data-dropdown-placement="bottom-end">
|
],
|
||||||
<a class="px-4 py-1 hover:bg-gray-100" href="<?= route_to(
|
[
|
||||||
'my-account',
|
'type' => 'separator',
|
||||||
) ?>"><?= lang('AdminNavigation.account.my-account') ?></a>
|
],
|
||||||
<a class="px-4 py-1 hover:bg-gray-100" href="<?= route_to(
|
[
|
||||||
'change-password',
|
'type' => 'link',
|
||||||
) ?>"><?= lang('AdminNavigation.account.change-password') ?></a>
|
'title' => lang('AdminNavigation.account.logout'),
|
||||||
<a class="px-4 py-1 hover:bg-gray-100" href="<?= route_to(
|
'uri' => route_to('logout'),
|
||||||
'logout',
|
], ])) ?>" />
|
||||||
) ?>"><?= lang('AdminNavigation.account.logout') ?></a>
|
|
||||||
</nav>
|
|
||||||
</header>
|
</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">
|
<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)): ?>
|
<?php if (isset($podcast) && isset($episode)): ?>
|
||||||
@ -80,7 +78,7 @@
|
|||||||
</footer>
|
</footer>
|
||||||
</aside>
|
</aside>
|
||||||
<main class="relative holy-grail__main">
|
<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">
|
<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') ?>
|
<?= render_breadcrumb('text-gray-800 text-xs items-center flex') ?>
|
||||||
<div class="flex justify-between py-1">
|
<div class="flex justify-between py-1">
|
||||||
|
@ -74,44 +74,45 @@
|
|||||||
[
|
[
|
||||||
'header' => lang('Episode.list.actions'),
|
'header' => lang('Episode.list.actions'),
|
||||||
'cell' => function ($episode, $podcast) {
|
'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') .
|
icon('more') .
|
||||||
'</button>' .
|
'</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">' .
|
'<DropdownMenu id="more-dropdown-' . $episode->id . '-menu" labeledBy="more-dropdown-' . $episode->id . '" items="' . esc(json_encode([
|
||||||
'<a class="px-4 py-1 hover:bg-gray-100" href="' . route_to(
|
[
|
||||||
'episode-edit',
|
'type' => 'link',
|
||||||
$podcast->id,
|
'title' => lang('Episode.edit'),
|
||||||
$episode->id,
|
'uri' => 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',
|
'type' => 'link',
|
||||||
$podcast->id,
|
'title' => lang('Episode.embeddable_player.title'),
|
||||||
$episode->id,
|
'uri' => route_to('embeddable-player-add', $podcast->id, $episode->id),
|
||||||
) . '">' . lang(
|
],
|
||||||
'Episode.embeddable_player.title',
|
[
|
||||||
) . '</a>' .
|
'type' => 'link',
|
||||||
'<a class="px-4 py-1 hover:bg-gray-100" href="' . route_to(
|
'title' => lang('Person.persons'),
|
||||||
'episode-persons-manage',
|
'uri' => route_to('episode-persons-manage', $podcast->id, $episode->id),
|
||||||
$podcast->id,
|
],
|
||||||
$episode->id,
|
[
|
||||||
) . '">' . lang('Person.persons') . '</a>' .
|
'type' => 'link',
|
||||||
'<a class="px-4 py-1 hover:bg-gray-100" href="' . route_to(
|
'title' => lang('Episode.soundbites'),
|
||||||
'soundbites-edit',
|
'uri' => route_to('soundbites-edit', $podcast->id, $episode->id),
|
||||||
$podcast->id,
|
],
|
||||||
$episode->id,
|
[
|
||||||
) . '">' . lang('Episode.soundbites') . '</a>' .
|
'type' => 'link',
|
||||||
'<a class="px-4 py-1 hover:bg-gray-100" href="' . route_to(
|
'title' => lang('Episode.go_to_page'),
|
||||||
'episode',
|
'uri' => route_to('episode', $podcast->handle, $episode->slug),
|
||||||
$podcast->handle,
|
],
|
||||||
$episode->slug,
|
[
|
||||||
) . '">' . lang('Episode.go_to_page') . '</a>' .
|
'type' => 'separator',
|
||||||
'<a class="px-4 py-1 hover:bg-gray-100" href="' . route_to(
|
],
|
||||||
'episode-delete',
|
[
|
||||||
$podcast->id,
|
'type' => 'link',
|
||||||
$episode->id,
|
'title' => lang('Episode.delete'),
|
||||||
) . '">' . lang('Episode.delete') . '</a>' .
|
'uri' => route_to('episode-delete', $podcast->id, $episode->id),
|
||||||
'</nav>' .
|
'class' => 'font-semibold text-red-600',
|
||||||
'</div>';
|
],
|
||||||
|
])) . '" />';
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
@ -52,41 +52,42 @@
|
|||||||
aria-haspopup="true"
|
aria-haspopup="true"
|
||||||
aria-expanded="false"
|
aria-expanded="false"
|
||||||
><?= icon('more') ?></button>
|
><?= icon('more') ?></button>
|
||||||
<nav
|
<DropdownMenu id="more-dropdown-<?= $episode->id ?>-menu" labeledBy="more-dropdown-<?= $episode->id ?>" items="<?= esc(json_encode([
|
||||||
id="more-dropdown-<?= $episode->id ?>-menu"
|
[
|
||||||
class="z-50 flex flex-col py-2 text-black whitespace-no-wrap bg-white border rounded shadow"
|
'type' => 'link',
|
||||||
aria-labelledby="more-dropdown-<?= $episode->id ?>"
|
'title' => lang('Episode.edit'),
|
||||||
data-dropdown="menu"
|
'uri' => route_to('episode-edit', $podcast->id, $episode->id),
|
||||||
data-dropdown-placement="bottom">
|
],
|
||||||
<a class="px-4 py-1 hover:bg-gray-100" href="<?= route_to(
|
[
|
||||||
'episode-edit',
|
'type' => 'link',
|
||||||
$podcast->id,
|
'title' => lang('Episode.embeddable_player.title'),
|
||||||
$episode->id,
|
'uri' => route_to('embeddable-player-add', $podcast->id, $episode->id),
|
||||||
) ?>"><?= lang('Episode.edit') ?></a>
|
],
|
||||||
<a class="px-4 py-1 hover:bg-gray-100" href="<?= route_to(
|
[
|
||||||
'embeddable-player-add',
|
'type' => 'link',
|
||||||
$podcast->id,
|
'title' => lang('Person.persons'),
|
||||||
$episode->id,
|
'uri' => route_to('episode-persons-manage', $podcast->id, $episode->id),
|
||||||
) ?>"><?= lang(
|
],
|
||||||
'Episode.embeddable_player.title',
|
[
|
||||||
) ?></a>
|
'type' => 'link',
|
||||||
<a class="px-4 py-1 hover:bg-gray-100" href="<?= route_to(
|
'title' => lang('Episode.soundbites'),
|
||||||
'episode-persons-manage',
|
'uri' => route_to('soundbites-edit', $podcast->id, $episode->id),
|
||||||
$podcast->id,
|
],
|
||||||
$episode->id,
|
[
|
||||||
) ?>"><?= lang('Person.persons') ?></a>
|
'type' => 'link',
|
||||||
<a class="px-4 py-1 hover:bg-gray-100" href="<?= route_to(
|
'title' => lang('Episode.go_to_page'),
|
||||||
'episode',
|
'uri' => route_to('episode', $podcast->handle, $episode->slug),
|
||||||
$podcast->handle,
|
],
|
||||||
$episode->slug,
|
[
|
||||||
) ?>"><?= lang('Episode.go_to_page') ?></a>
|
'type' => 'separator',
|
||||||
<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',
|
'type' => 'link',
|
||||||
$podcast->id,
|
'title' => lang('Episode.delete'),
|
||||||
$episode->id,
|
'uri' => route_to('episode-delete', $podcast->id, $episode->id),
|
||||||
) ?>"><?= lang('Episode.delete') ?></a>
|
'class' => 'font-semibold text-red-600',
|
||||||
</nav>
|
],
|
||||||
|
])) ?>" />
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user