feat: add DropdownMenu component + remove global audio player in admin

This commit is contained in:
Yassine Doghri 2021-09-28 15:47:59 +00:00
parent d60498c1be
commit abb7fbac27
5 changed files with 225 additions and 95 deletions

View 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);

View 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;
}
}

View File

@ -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">

View File

@ -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',
],
])) . '" />';
},
],
],

View File

@ -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; ?>