From de19317138a2106deb825c1eed7dda036ed7dac3 Mon Sep 17 00:00:00 2001 From: Yassine Doghri Date: Mon, 3 Jan 2022 13:52:07 +0000 Subject: [PATCH] feat(soundbites): add soundbite list and creation forms with audio-clipper component --- .../2021-12-09-130000_add_clips.php | 3 +- app/Entities/Clip/BaseClip.php | 4 +- app/Helpers/rss_helper.php | 4 +- app/Models/ClipModel.php | 76 ++++--- app/Resources/js/admin.ts | 3 +- app/Resources/js/modules/Soundbites.ts | 87 -------- app/Resources/js/modules/play-soundbite.ts | 198 ++++++++++++++++++ modules/Admin/Config/Routes.php | 21 +- .../Admin/Controllers/EpisodeController.php | 78 ------- .../Admin/Controllers/SoundbiteController.php | 163 ++++++++++++++ .../Controllers/VideoClipsController.php | 8 +- modules/Admin/Language/en/Episode.php | 19 -- .../Admin/Language/en/EpisodeNavigation.php | 3 +- modules/Admin/Language/en/Soundbite.php | 27 +++ modules/Admin/Language/fr/Episode.php | 20 -- .../Admin/Language/fr/EpisodeNavigation.php | 3 +- modules/Admin/Language/fr/Soundbite.php | 27 +++ themes/cp_admin/episode/_sidebar.php | 2 +- themes/cp_admin/episode/soundbites.php | 72 ------- themes/cp_admin/episode/soundbites_list.php | 48 +++++ themes/cp_admin/episode/soundbites_new.php | 35 ++++ themes/cp_admin/episode/video_clip.php | 4 +- themes/cp_admin/episode/video_clips_list.php | 4 +- themes/cp_admin/episode/video_clips_new.php | 4 +- 24 files changed, 582 insertions(+), 331 deletions(-) delete mode 100644 app/Resources/js/modules/Soundbites.ts create mode 100644 app/Resources/js/modules/play-soundbite.ts create mode 100644 modules/Admin/Controllers/SoundbiteController.php create mode 100644 modules/Admin/Language/en/Soundbite.php create mode 100644 modules/Admin/Language/fr/Soundbite.php delete mode 100644 themes/cp_admin/episode/soundbites.php create mode 100644 themes/cp_admin/episode/soundbites_list.php create mode 100644 themes/cp_admin/episode/soundbites_new.php diff --git a/app/Database/Migrations/2021-12-09-130000_add_clips.php b/app/Database/Migrations/2021-12-09-130000_add_clips.php index 0c42a922..cf5a24fd 100644 --- a/app/Database/Migrations/2021-12-09-130000_add_clips.php +++ b/app/Database/Migrations/2021-12-09-130000_add_clips.php @@ -39,10 +39,9 @@ class AddClips extends Migration 'type' => 'DECIMAL(7,3)', 'unsigned' => true, ], - 'label' => [ + 'title' => [ 'type' => 'VARCHAR', 'constraint' => 128, - 'null' => true, ], 'type' => [ 'type' => 'ENUM', diff --git a/app/Entities/Clip/BaseClip.php b/app/Entities/Clip/BaseClip.php index 00057c2e..e989d865 100644 --- a/app/Entities/Clip/BaseClip.php +++ b/app/Entities/Clip/BaseClip.php @@ -29,7 +29,7 @@ use Modules\Auth\Entities\User; * @property Podcast $podcast * @property int $episode_id * @property Episode $episode - * @property string $label + * @property string $title * @property double $start_time * @property double $end_time * @property double $duration @@ -68,7 +68,7 @@ class BaseClip extends Entity 'id' => 'integer', 'podcast_id' => 'integer', 'episode_id' => 'integer', - 'label' => 'string', + 'title' => 'string', 'start_time' => 'double', 'duration' => 'double', 'type' => 'string', diff --git a/app/Helpers/rss_helper.php b/app/Helpers/rss_helper.php index bee957f9..d3052098 100644 --- a/app/Helpers/rss_helper.php +++ b/app/Helpers/rss_helper.php @@ -255,7 +255,7 @@ if (! function_exists('get_rss_feed')) { $comments->addAttribute('uri', url_to('episode-comments', $podcast->handle, $episode->slug)); $comments->addAttribute('contentType', 'application/podcast-activity+json'); - if ($episode->transcript->file_url !== '') { + if ($episode->transcript !== null) { $transcriptElement = $item->addChild('transcript', null, $podcastNamespace); $transcriptElement->addAttribute('url', $episode->transcript->file_url); $transcriptElement->addAttribute( @@ -275,7 +275,7 @@ if (! function_exists('get_rss_feed')) { foreach ($episode->soundbites as $soundbite) { // TODO: differentiate video from soundbites? - $soundbiteElement = $item->addChild('soundbite', $soundbite->label, $podcastNamespace); + $soundbiteElement = $item->addChild('soundbite', $soundbite->title, $podcastNamespace); $soundbiteElement->addAttribute('start_time', (string) $soundbite->start_time); $soundbiteElement->addAttribute('duration', (string) $soundbite->duration); } diff --git a/app/Models/ClipModel.php b/app/Models/ClipModel.php index 94364cd2..fca84b0c 100644 --- a/app/Models/ClipModel.php +++ b/app/Models/ClipModel.php @@ -39,7 +39,7 @@ class ClipModel extends Model 'id', 'podcast_id', 'episode_id', - 'label', + 'title', 'start_time', 'duration', 'type', @@ -89,33 +89,6 @@ class ClipModel extends Model parent::__construct($db, $validation); } - /** - * Gets all clips for an episode - * - * @return Soundbite[] - */ - public function getEpisodeSoundbites(int $podcastId, int $episodeId): array - { - $cacheName = "podcast#{$podcastId}_episode#{$episodeId}_soundbites"; - if (! ($found = cache($cacheName))) { - $found = $this->where([ - 'episode_id' => $episodeId, - 'podcast_id' => $podcastId, - 'type' => 'audio', - ]) - ->orderBy('start_time') - ->findAll(); - - foreach ($found as $key => $soundbite) { - $found[$key] = new Soundbite($soundbite->toArray()); - } - - cache() - ->save($cacheName, $found, DECADE); - } - return $found; - } - public function getVideoClipById(int $videoClipId): ?VideoClip { $cacheName = "video-clip#{$videoClipId}"; @@ -184,6 +157,53 @@ class ClipModel extends Model return $found; } + public function getSoundbiteById(int $soundbiteId): ?Soundbite + { + $cacheName = "soundbite#{$soundbiteId}"; + if (! ($found = cache($cacheName))) { + $clip = $this->find($soundbiteId); + + if ($clip === null) { + return null; + } + + // @phpstan-ignore-next-line + $found = new Soundbite($clip->toArray()); + + cache() + ->save($cacheName, $found, DECADE); + } + + return $found; + } + + /** + * Gets all clips for an episode + * + * @return Soundbite[] + */ + public function getEpisodeSoundbites(int $podcastId, int $episodeId): array + { + $cacheName = "podcast#{$podcastId}_episode#{$episodeId}_soundbites"; + if (! ($found = cache($cacheName))) { + $found = $this->where([ + 'episode_id' => $episodeId, + 'podcast_id' => $podcastId, + 'type' => 'audio', + ]) + ->orderBy('start_time') + ->findAll(); + + foreach ($found as $key => $soundbite) { + $found[$key] = new Soundbite($soundbite->toArray()); + } + + cache() + ->save($cacheName, $found, DECADE); + } + return $found; + } + public function deleteSoundbite(int $podcastId, int $episodeId, int $clipId): BaseResult | bool { cache() diff --git a/app/Resources/js/admin.ts b/app/Resources/js/admin.ts index 310cc336..d9ee93cb 100644 --- a/app/Resources/js/admin.ts +++ b/app/Resources/js/admin.ts @@ -10,11 +10,11 @@ import "./modules/markdown-preview"; import "./modules/markdown-write-preview"; import MultiSelect from "./modules/MultiSelect"; import "./modules/permalink-edit"; +import "./modules/play-soundbite"; import PublishMessageWarning from "./modules/PublishMessageWarning"; import Select from "./modules/Select"; import SidebarToggler from "./modules/SidebarToggler"; import Slugify from "./modules/Slugify"; -import Soundbites from "./modules/Soundbites"; import ThemePicker from "./modules/ThemePicker"; import Time from "./modules/Time"; import Tooltip from "./modules/Tooltip"; @@ -31,7 +31,6 @@ SidebarToggler(); ClientTimezone(); DateTimePicker(); Time(); -Soundbites(); Clipboard(); ThemePicker(); PublishMessageWarning(); diff --git a/app/Resources/js/modules/Soundbites.ts b/app/Resources/js/modules/Soundbites.ts deleted file mode 100644 index 16fb174b..00000000 --- a/app/Resources/js/modules/Soundbites.ts +++ /dev/null @@ -1,87 +0,0 @@ -/** - * TODO: refactor file - */ -let timeout: number | null = null; - -const playSoundbite = ( - audioPlayer: HTMLAudioElement, - startTime: number, - duration: number -): void => { - audioPlayer.currentTime = startTime; - if (duration > 0) { - audioPlayer.play(); - if (timeout) { - clearTimeout(timeout); - timeout = null; - } - timeout = window.setTimeout(() => { - audioPlayer.pause(); - timeout = null; - }, duration * 1000); - } -}; - -const Soundbites = (): void => { - const audioPlayer: HTMLAudioElement | null = document.querySelector("audio"); - - if (audioPlayer) { - const soundbiteButton: HTMLButtonElement | null = document.querySelector( - "button[data-type='get-soundbite']" - ); - if (soundbiteButton) { - const startTimeField: HTMLInputElement | null = document.querySelector( - `input[name="${soundbiteButton.dataset.startTimeFieldName}"]` - ); - const durationField: HTMLInputElement | null = document.querySelector( - `input[name="${soundbiteButton.dataset.durationFieldName}"]` - ); - - if (startTimeField && durationField) { - soundbiteButton.addEventListener("click", () => { - if (startTimeField.value === "") { - startTimeField.value = ( - Math.round(audioPlayer.currentTime * 100) / 100 - ).toString(); - } else { - durationField.value = ( - Math.round( - (audioPlayer.currentTime - Number(startTimeField.value)) * 100 - ) / 100 - ).toString(); - } - }); - } - } - - const soundbitePlayButtons: NodeListOf | null = - document.querySelectorAll("button[data-type='play-soundbite']"); - if (soundbitePlayButtons) { - for (let i = 0; i < soundbitePlayButtons.length; i++) { - const soundbitePlayButton: HTMLButtonElement = soundbitePlayButtons[i]; - - soundbitePlayButton.addEventListener("click", () => { - // get values from inputs to play soundbite - const startTime: HTMLInputElement | null | undefined = - soundbitePlayButton.parentElement?.parentElement?.querySelector( - 'input[data-field-type="start_time"]' - ); - const duration: HTMLInputElement | null | undefined = - soundbitePlayButton.parentElement?.parentElement?.querySelector( - 'input[data-field-type="duration"]' - ); - - if (startTime && duration) { - playSoundbite( - audioPlayer, - parseFloat(startTime.value), - parseFloat(duration.value) - ); - } - }); - } - } - } -}; - -export default Soundbites; diff --git a/app/Resources/js/modules/play-soundbite.ts b/app/Resources/js/modules/play-soundbite.ts new file mode 100644 index 00000000..26f24086 --- /dev/null +++ b/app/Resources/js/modules/play-soundbite.ts @@ -0,0 +1,198 @@ +import { css, html, LitElement, TemplateResult } from "lit"; +import { customElement, property, state } from "lit/decorators.js"; + +@customElement("play-soundbite") +export class PlaySoundbite extends LitElement { + @property({ attribute: "audio-src" }) + audioSrc!: string; + + @property({ type: Number, attribute: "start-time" }) + startTime!: number; + + @property({ type: Number }) + duration!: number; + + @property({ attribute: "play-label" }) + playLabel!: string; + + @property({ attribute: "playing-label" }) + playingLabel!: string; + + @state() + _audio: HTMLAudioElement | null = null; + + @state() + _isPlaying = false; + + @state() + _isLoading = false; + + _audioEvents = [ + { + name: "play", + onEvent: () => { + this._isPlaying = true; + }, + }, + { + name: "pause", + onEvent: () => { + this._isPlaying = false; + }, + }, + { + name: "timeupdate", + onEvent: () => { + if (this._audio) { + console.log( + this._audio.currentTime, + this.startTime, + this.startTime + this.duration + ); + if (this._audio.currentTime < this.startTime) { + this._isLoading = true; + this._audio.currentTime = this.startTime; + } else if (this._audio.currentTime > this.startTime + this.duration) { + this.stopSoundbite(); + } else { + this._isLoading = false; + } + } + }, + }, + ]; + + playSoundbite() { + if (this._audio === null) { + this._audio = new Audio(this.audioSrc); + for (const event of this._audioEvents) { + this._audio.addEventListener(event.name, event.onEvent); + } + } + + this._audio.currentTime = this.startTime; + this._audio.play(); + } + + stopSoundbite() { + if (this._audio !== null) { + this._audio.pause(); + this._audio.currentTime = this.startTime; + } + } + + disconnectedCallback(): void { + if (this._audio) { + for (const event of this._audioEvents) { + this._audio.removeEventListener(event.name, event.onEvent); + } + } + } + + static styles = css` + button { + background-color: hsl(var(--color-accent-base)); + cursor: pointer; + display: inline-flex; + align-items: center; + padding: 0.5rem; + font-size: 0.875rem; + border: 2px solid transparent; + border-radius: 9999px; + + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + } + + button:hover { + background-color: hsl(var(--color-accent-hover)); + } + + button:focus { + outline: none; + box-shadow: 0 0 0 2px hsl(var(--color-background-base)), + 0 0 0 4px hsl(var(--color-accent-base)); + } + + button.playing { + background-color: hsl(var(--color-background-base)); + border: 2px solid hsl(var(--color-accent-base)); + } + + button.playing:hover { + background-color: hsl(var(--color-background-elevated)); + } + + button.playing svg { + color: hsl(var(--color-accent-base)); + } + + svg { + color: hsl(var(--color-accent-contrast)); + } + + @keyframes spin { + to { + transform: rotate(360deg); + } + } + + .animate-spin { + animation: spin 3s linear infinite; + } + `; + + render(): TemplateResult<1> { + return html``; + } +} diff --git a/modules/Admin/Config/Routes.php b/modules/Admin/Config/Routes.php index dcaf460e..c22fba69 100644 --- a/modules/Admin/Config/Routes.php +++ b/modules/Admin/Config/Routes.php @@ -334,24 +334,33 @@ $routes->group( ); $routes->get( 'soundbites', - 'EpisodeController::soundbitesEdit/$1/$2', + 'SoundbiteController::list/$1/$2', [ - 'as' => 'soundbites-edit', + 'as' => 'soundbites-list', + 'filter' => 'permission:podcast_episodes-edit', + ], + ); + $routes->get( + 'soundbites/new', + 'SoundbiteController::create/$1/$2', + [ + 'as' => 'soundbites-create', 'filter' => 'permission:podcast_episodes-edit', ], ); $routes->post( - 'soundbites', - 'EpisodeController::soundbitesAttemptEdit/$1/$2', + 'soundbites/new', + 'SoundbiteController::attemptCreate/$1/$2', [ + 'as' => 'soundbites-create', 'filter' => 'permission:podcast_episodes-edit', ], ); $routes->get( 'soundbites/(:num)/delete', - 'EpisodeController::soundbiteDelete/$1/$2/$3', + 'SoundbiteController::delete/$1/$2/$3', [ - 'as' => 'soundbite-delete', + 'as' => 'soundbites-delete', 'filter' => 'permission:podcast_episodes-edit', ], ); diff --git a/modules/Admin/Controllers/EpisodeController.php b/modules/Admin/Controllers/EpisodeController.php index 7cdb144a..a2a2c57e 100644 --- a/modules/Admin/Controllers/EpisodeController.php +++ b/modules/Admin/Controllers/EpisodeController.php @@ -15,7 +15,6 @@ use App\Entities\EpisodeComment; use App\Entities\Location; use App\Entities\Podcast; use App\Entities\Post; -use App\Models\ClipModel; use App\Models\EpisodeCommentModel; use App\Models\EpisodeModel; use App\Models\MediaModel; @@ -719,83 +718,6 @@ class EpisodeController extends BaseController return redirect()->route('episode-list', [$this->podcast->id]); } - public function soundbitesEdit(): string - { - helper(['form']); - - $data = [ - 'podcast' => $this->podcast, - 'episode' => $this->episode, - ]; - - replace_breadcrumb_params([ - 0 => $this->podcast->title, - 1 => $this->episode->title, - ]); - return view('episode/soundbites', $data); - } - - public function soundbitesAttemptEdit(): RedirectResponse - { - $soundbites = $this->request->getPost('soundbites'); - $rules = [ - 'soundbites.0.start_time' => - 'permit_empty|required_with[soundbites.0.duration]|decimal|greater_than_equal_to[0]', - 'soundbites.0.duration' => - 'permit_empty|required_with[soundbites.0.start_time]|decimal|greater_than_equal_to[0]', - ]; - foreach (array_keys($soundbites) as $soundbite_id) { - $rules += [ - "soundbites.{$soundbite_id}.start_time" => 'required|decimal|greater_than_equal_to[0]', - "soundbites.{$soundbite_id}.duration" => 'required|decimal|greater_than_equal_to[0]', - ]; - } - - if (! $this->validate($rules)) { - return redirect() - ->back() - ->withInput() - ->with('errors', $this->validator->getErrors()); - } - - foreach ($soundbites as $soundbite_id => $soundbite) { - $data = [ - 'podcast_id' => $this->podcast->id, - 'episode_id' => $this->episode->id, - 'start_time' => (float) $soundbite['start_time'], - 'duration' => (float) $soundbite['duration'], - 'label' => $soundbite['label'], - 'updated_by' => user_id(), - ]; - if ($soundbite_id === 0) { - $data += [ - 'created_by' => user_id(), - ]; - } else { - $data += [ - 'id' => $soundbite_id, - ]; - } - - $soundbiteModel = new SoundbiteModel(); - if (! $soundbiteModel->save($data)) { - return redirect() - ->back() - ->withInput() - ->with('errors', $soundbiteModel->errors()); - } - } - - return redirect()->route('soundbites-edit', [$this->podcast->id, $this->episode->id]); - } - - public function soundbiteDelete(string $clipId): RedirectResponse - { - (new ClipModel())->deleteClip($this->podcast->id, $this->episode->id, (int) $clipId); - - return redirect()->route('clips-edit', [$this->podcast->id, $this->episode->id]); - } - public function embed(): string { helper(['form']); diff --git a/modules/Admin/Controllers/SoundbiteController.php b/modules/Admin/Controllers/SoundbiteController.php new file mode 100644 index 00000000..c13b2453 --- /dev/null +++ b/modules/Admin/Controllers/SoundbiteController.php @@ -0,0 +1,163 @@ +getPodcastById((int) $params[0])) === null + ) { + throw PageNotFoundException::forPageNotFound(); + } + + $this->podcast = $podcast; + + if (count($params) > 1) { + if ( + ! ($episode = (new EpisodeModel()) + ->where([ + 'id' => $params[1], + 'podcast_id' => $params[0], + ]) + ->first()) + ) { + throw PageNotFoundException::forPageNotFound(); + } + + $this->episode = $episode; + + unset($params[1]); + unset($params[0]); + } + + return $this->{$method}(...$params); + } + + public function list(): string + { + $soundbitesBuilder = (new ClipModel('audio')) + ->where([ + 'podcast_id' => $this->podcast->id, + 'episode_id' => $this->episode->id, + 'type' => 'audio', + ]) + ->orderBy('created_at', 'desc'); + + $soundbites = $soundbitesBuilder->paginate(10); + + $data = [ + 'podcast' => $this->podcast, + 'episode' => $this->episode, + 'soundbites' => $soundbites, + 'pager' => $soundbitesBuilder->pager, + ]; + + replace_breadcrumb_params([ + 0 => $this->podcast->title, + 1 => $this->episode->title, + ]); + return view('episode/soundbites_list', $data); + } + + public function create(): string + { + helper(['form']); + + $data = [ + 'podcast' => $this->podcast, + 'episode' => $this->episode, + ]; + + replace_breadcrumb_params([ + 0 => $this->podcast->title, + 1 => $this->episode->title, + ]); + return view('episode/soundbites_new', $data); + } + + public function attemptCreate(): RedirectResponse + { + $rules = [ + 'title' => 'required', + 'start_time' => 'required|greater_than_equal_to[0]', + 'duration' => 'required|greater_than[0]', + ]; + + if (! $this->validate($rules)) { + return redirect() + ->back() + ->withInput() + ->with('errors', $this->validator->getErrors()); + } + + $newSoundbite = new Soundbite([ + 'title' => $this->request->getPost('title'), + 'start_time' => (float) $this->request->getPost('start_time'), + 'duration' => (float) $this->request->getPost('duration',), + 'type' => 'audio', + 'status' => '', + 'podcast_id' => $this->podcast->id, + 'episode_id' => $this->episode->id, + 'created_by' => user_id(), + 'updated_by' => user_id(), + ]); + + $clipModel = new ClipModel('audio'); + if (! $clipModel->save($newSoundbite)) { + return redirect() + ->back() + ->withInput() + ->with('errors', $clipModel->errors()); + } + + return redirect()->route('soundbites-list', [$this->podcast->id, $this->episode->id]); + } + + public function delete(string $soundbiteId): RedirectResponse + { + $soundbite = (new ClipModel())->getSoundbiteById((int) $soundbiteId); + + if ($soundbite === null) { + throw PageNotFoundException::forPageNotFound(); + } + + if ($soundbite->media === null) { + // delete Clip directly + (new ClipModel())->delete($soundbite->id); + } else { + $mediaModel = new MediaModel(); + if (! $mediaModel->deleteMedia($soundbite->media)) { + return redirect() + ->back() + ->withInput() + ->with('errors', $mediaModel->errors()); + } + } + + return redirect()->route('soundbites-list', [$this->podcast->id, $this->episode->id]); + } +} diff --git a/modules/Admin/Controllers/VideoClipsController.php b/modules/Admin/Controllers/VideoClipsController.php index 712377e8..2552b5ea 100644 --- a/modules/Admin/Controllers/VideoClipsController.php +++ b/modules/Admin/Controllers/VideoClipsController.php @@ -101,7 +101,7 @@ class VideoClipsController extends BaseController replace_breadcrumb_params([ 0 => $this->podcast->title, 1 => $this->episode->title, - 2 => $videoClip->label, + 2 => $videoClip->title, ]); return view('episode/video_clip', $data); } @@ -140,8 +140,8 @@ class VideoClipsController extends BaseController public function attemptCreate(): RedirectResponse { $rules = [ - 'label' => 'required', - 'start_time' => 'required|numeric', + 'title' => 'required', + 'start_time' => 'required|greater_than_equal_to[0]', 'duration' => 'required|greater_than[0]', 'format' => 'required|in_list[' . implode(',', array_keys(config('MediaClipper')->formats)) . ']', 'theme' => 'required|in_list[' . implode(',', array_keys(config('Colors')->themes)) . ']', @@ -163,7 +163,7 @@ class VideoClipsController extends BaseController ]; $videoClip = new VideoClip([ - 'label' => $this->request->getPost('label'), + 'title' => $this->request->getPost('title'), 'start_time' => (float) $this->request->getPost('start_time'), 'duration' => (float) $this->request->getPost('duration',), 'theme' => $theme, diff --git a/modules/Admin/Language/en/Episode.php b/modules/Admin/Language/en/Episode.php index b3b23546..7b3d3218 100644 --- a/modules/Admin/Language/en/Episode.php +++ b/modules/Admin/Language/en/Episode.php @@ -144,25 +144,6 @@ return [ 'understand' => 'I understand, I want to delete the episode', 'submit' => 'Delete', ], - 'soundbites' => 'Soundbites', - 'soundbites_form' => [ - 'title' => 'Edit soundbites', - 'info_section_title' => 'Episode soundbites', - 'info_section_subtitle' => 'Add, edit or delete soundbites', - 'start_time' => 'Start', - 'start_time_hint' => - 'The first second of the soundbite, it can be a decimal number.', - 'duration' => 'Duration', - 'duration_hint' => - 'The duration of the soundbite (in seconds), it can be a decimal number.', - 'label' => 'Label', - 'label_hint' => 'Text that will be displayed.', - 'play' => 'Play soundbite', - 'delete' => 'Delete soundbite', - 'bookmark' => - 'Click while playing to get current position, click again to get duration.', - 'submit' => 'Save soundbites', - ], 'embed' => [ 'title' => 'Embeddable player', 'label' => diff --git a/modules/Admin/Language/en/EpisodeNavigation.php b/modules/Admin/Language/en/EpisodeNavigation.php index 6511ff5c..8eb2b1b5 100644 --- a/modules/Admin/Language/en/EpisodeNavigation.php +++ b/modules/Admin/Language/en/EpisodeNavigation.php @@ -16,7 +16,8 @@ return [ 'episode-persons-manage' => 'Manage persons', 'embed-add' => 'Embeddable player', 'clips' => 'Clips', - 'soundbites-edit' => 'Soundbites', 'video-clips-list' => 'Video clips', 'video-clips-create' => 'New video clip', + 'soundbites-list' => 'Soundbites', + 'soundbites-create' => 'New soundbite', ]; diff --git a/modules/Admin/Language/en/Soundbite.php b/modules/Admin/Language/en/Soundbite.php new file mode 100644 index 00000000..8faed932 --- /dev/null +++ b/modules/Admin/Language/en/Soundbite.php @@ -0,0 +1,27 @@ + [ + 'title' => 'Soundbites', + 'soundbite' => 'Soundbite', + ], + 'form' => [ + 'title' => 'New soundbite', + 'soundbite_title' => 'Soundbite title', + 'start_time' => 'Start at', + 'duration' => 'Duration', + 'submit' => 'Create soundbite', + ], + 'play' => 'Play soundbite', + 'stop' => 'Stop soundbite', + 'create' => 'New soundbite', + 'delete' => 'Delete soundbite', +]; diff --git a/modules/Admin/Language/fr/Episode.php b/modules/Admin/Language/fr/Episode.php index 796a7aa9..38cb216d 100644 --- a/modules/Admin/Language/fr/Episode.php +++ b/modules/Admin/Language/fr/Episode.php @@ -151,26 +151,6 @@ return [ 'understand' => 'Je comprends, Je veux supprimer l’épisode', 'submit' => 'Supprimer', ], - 'soundbites' => 'Extraits sonores', - 'soundbites_form' => [ - 'title' => 'Modifier les extraits sonores', - 'info_section_title' => 'Extraits sonores de l’épisode', - 'info_section_subtitle' => - 'Ajouter, modifier ou supprimer des extraits sonores', - 'start_time' => 'Début', - 'start_time_hint' => - 'La première seconde de l’extrait sonore, cela peut être un nombre décimal.', - 'duration' => 'Durée', - 'duration_hint' => - 'La durée de l’extrait sonore (en secondes), cela peut être un nombre décimal.', - 'label' => 'Libellé', - 'label_hint' => 'Texte qui sera affiché.', - 'play' => 'Écouter l’extrait sonore', - 'delete' => 'Supprimer l’extrait sonore', - 'bookmark' => - 'Cliquez pour récupérer la position actuelle, cliquez à nouveau pour récupérer la durée.', - 'submit' => 'Enregistrer les extraits sonnores', - ], 'embed' => [ 'add' => 'Ajouter un lecteur intégré', 'title' => 'Lecteur intégré', diff --git a/modules/Admin/Language/fr/EpisodeNavigation.php b/modules/Admin/Language/fr/EpisodeNavigation.php index b8c82ff7..ac8ca8bf 100644 --- a/modules/Admin/Language/fr/EpisodeNavigation.php +++ b/modules/Admin/Language/fr/EpisodeNavigation.php @@ -16,7 +16,8 @@ return [ 'episode-persons-manage' => 'Gestion des intervenants', 'embed' => 'Lecteur intégré', 'clips' => 'Extraits', - 'soundbites-edit' => 'Extraits sonores', 'video-clips-list' => 'Extraits video', 'video-clips-create' => 'Nouvel extrait video', + 'soundbites-list' => 'Extraits sonores', + 'soundbites-create' => 'Nouvel extrait sonore', ]; diff --git a/modules/Admin/Language/fr/Soundbite.php b/modules/Admin/Language/fr/Soundbite.php new file mode 100644 index 00000000..0cb3dddb --- /dev/null +++ b/modules/Admin/Language/fr/Soundbite.php @@ -0,0 +1,27 @@ + [ + 'title' => 'Extraits sonores', + 'soundbite' => 'Extrait sonore', + ], + 'form' => [ + 'title' => 'Nouvel extrait sonore', + 'soundbite_title' => 'Titre de l’extrait', + 'start_time' => 'Début à', + 'duration' => 'Durée', + 'submit' => 'Créer l’extrait sonore', + ], + 'play' => 'Lancer l’extrait sonore', + 'stop' => 'Arrêter l’extrait sonore', + 'create' => 'Nouvel extrait sonore', + 'delete' => 'Supprimer l’extrait sonore', +]; diff --git a/themes/cp_admin/episode/_sidebar.php b/themes/cp_admin/episode/_sidebar.php index 10c0affe..50b2d703 100644 --- a/themes/cp_admin/episode/_sidebar.php +++ b/themes/cp_admin/episode/_sidebar.php @@ -7,7 +7,7 @@ $podcastNavigation = [ ], 'clips' => [ 'icon' => 'clapperboard', - 'items' => ['video-clips-list', 'video-clips-create', 'soundbites-edit'], + 'items' => ['video-clips-list', 'video-clips-create', 'soundbites-list', 'soundbites-create'], ], ]; ?> diff --git a/themes/cp_admin/episode/soundbites.php b/themes/cp_admin/episode/soundbites.php deleted file mode 100644 index 111483e3..00000000 --- a/themes/cp_admin/episode/soundbites.php +++ /dev/null @@ -1,72 +0,0 @@ -extend('_layout') ?> - -section('title') ?> - -endSection() ?> - -section('pageTitle') ?> - -endSection() ?> - -section('headerRight') ?> - -endSection() ?> - - -section('content') ?> - -
- - - - - setHeading( - lang('Episode.soundbites_form.start_time') . hint_tooltip(lang('Episode.soundbites_form.start_time_hint')), - lang('Episode.soundbites_form.duration') . hint_tooltip(lang('Episode.soundbites_form.duration_hint')), - lang('Episode.soundbites_form.label') . hint_tooltip(lang('Episode.soundbites_form.label_hint')), - '', - '' - ); - - foreach ($episode->soundbites as $soundbite) { - $table->addRow( - "", - "", - "", - "" . lang('Episode.soundbites_form.play') . '', - 'id, - $episode->id, - $soundbite->id, - ) . " variant='danger' glyph='delete-bin'>" . lang('Episode.soundbites_form.delete') . '' - ); - } - - $table->addRow( - "", - "", - "", - "" . lang('Episode.soundbites_form.play') . '', - ); - - echo $table->generate(); - - ?> - -
- - -
-
-
- -endSection() ?> diff --git a/themes/cp_admin/episode/soundbites_list.php b/themes/cp_admin/episode/soundbites_list.php new file mode 100644 index 00000000..d6ee8af1 --- /dev/null +++ b/themes/cp_admin/episode/soundbites_list.php @@ -0,0 +1,48 @@ +extend('_layout') ?> + +section('title') ?> + +endSection() ?> + +section('pageTitle') ?> + +endSection() ?> + +section('headerRight') ?> + +endSection() ?> + +section('content') ?> + + lang('Soundbite.list.soundbite'), + 'cell' => function ($soundbite): string { + return '
' . $soundbite->title . '' . format_duration((int) $soundbite->duration) . '
'; + }, + ], + [ + 'header' => lang('Common.actions'), + 'cell' => function ($soundbite): string { + return '' . + ''; + }, + ], + ], + $soundbites, + 'mb-6', +) ?> + +links() ?> + +endSection() ?> diff --git a/themes/cp_admin/episode/soundbites_new.php b/themes/cp_admin/episode/soundbites_new.php new file mode 100644 index 00000000..0452d37d --- /dev/null +++ b/themes/cp_admin/episode/soundbites_new.php @@ -0,0 +1,35 @@ +extend('_layout') ?> + +section('title') ?> + +endSection() ?> + +section('pageTitle') ?> + +endSection() ?> + + +section('content') ?> + +
+ + + + + + + + + + + + + +endSection() ?> diff --git a/themes/cp_admin/episode/video_clip.php b/themes/cp_admin/episode/video_clip.php index 0504104c..46f51a15 100644 --- a/themes/cp_admin/episode/video_clip.php +++ b/themes/cp_admin/episode/video_clip.php @@ -2,13 +2,13 @@ section('title') ?> $videoClip->label, + 'videoClipLabel' => $videoClip->title, ]) ?> endSection() ?> section('pageTitle') ?> $videoClip->label, + 'videoClipLabel' => $videoClip->title, ]) ?> endSection() ?> diff --git a/themes/cp_admin/episode/video_clips_list.php b/themes/cp_admin/episode/video_clips_list.php index f79a3b7c..6da9bc9d 100644 --- a/themes/cp_admin/episode/video_clips_list.php +++ b/themes/cp_admin/episode/video_clips_list.php @@ -62,7 +62,7 @@ use CodeIgniter\I18n\Time; 'portrait' => 'aspect-[9/16]', 'squared' => 'aspect-square', ]; - return '
#' . $videoClip->id . ' – ' . $videoClip->label . 'by ' . $videoClip->user->username . '
' . format_duration((int) $videoClip->duration) . '
'; + return '
#' . $videoClip->id . ' – ' . $videoClip->title . 'by ' . $videoClip->user->username . '
' . format_duration((int) $videoClip->duration) . '
'; }, ], [ @@ -89,7 +89,7 @@ use CodeIgniter\I18n\Time; $downloadButton = ''; if ($videoClip->media) { helper('misc'); - $filename = 'clip-' . slugify($videoClip->label) . "-{$videoClip->start_time}-{$videoClip->end_time}"; + $filename = 'clip-' . slugify($videoClip->title) . "-{$videoClip->start_time}-{$videoClip->end_time}"; $downloadButton = '' . lang('VideoClip.download_clip') . ''; } diff --git a/themes/cp_admin/episode/video_clips_new.php b/themes/cp_admin/episode/video_clips_new.php index dbfcd9fc..1c9b3796 100644 --- a/themes/cp_admin/episode/video_clips_new.php +++ b/themes/cp_admin/episode/video_clips_new.php @@ -17,7 +17,7 @@ <?= $episode->cover->description ?> -