' . icon('question') . ''; } } // ------------------------------------------------------------------------ if (! function_exists('data_table')) { /** * Data table component * * Creates a stylized table. * * @param array> $columns array of associate arrays with `header` and `cell` keys where `cell` is a function with a row of $data as parameter * @param mixed[] $data data to loop through and display in rows * @param mixed ...$rest Any other argument to pass to the `cell` function */ function data_table(array $columns, array $data = [], string $class = '', mixed ...$rest): string { $table = new Table(); $template = [ 'table_open' => '', 'thead_open' => '', 'heading_cell_start' => '', 'row_alt_start' => '', ]; $table->setTemplate($template); $tableHeaders = []; foreach ($columns as $column) { $tableHeaders[] = $column['header']; } $table->setHeading($tableHeaders); if (($dataCount = count($data)) !== 0) { for ($i = 0; $i < $dataCount; ++$i) { $row = $data[$i]; $rowData = []; foreach ($columns as $column) { $rowData[] = $column['cell']($row, ...$rest); } $table->addRow($rowData); } } else { $table->addRow([ [ 'colspan' => count($tableHeaders), 'class' => 'px-4 py-2 italic font-semibold text-center', 'data' => lang('Common.no_data'), ], ]); } return '
' . $table->generate() . '
'; } } // ------------------------------------------------------------------------ if (! function_exists('publication_pill')) { /** * Publication pill component * * Shows the stylized publication datetime in regards to current datetime. */ function publication_pill(?Time $publicationDate, string $publicationStatus, string $customClass = ''): string { $class = match ($publicationStatus) { 'published' => 'text-pine-500 border-pine-500 bg-pine-50', 'scheduled' => 'text-red-600 border-red-600 bg-red-50', 'with_podcast' => 'text-blue-600 border-blue-600 bg-blue-50', 'not_published' => 'text-gray-600 border-gray-600 bg-gray-50', default => 'text-gray-600 border-gray-600 bg-gray-50', }; $title = match ($publicationStatus) { 'published', 'scheduled' => (string) $publicationDate, 'with_podcast' => lang('Episode.with_podcast_hint'), 'not_published' => '', default => '', }; $label = lang('Episode.publication_status.' . $publicationStatus); return '' . $label . ($publicationStatus === 'with_podcast' ? '' : '') . ''; } } // ------------------------------------------------------------------------ if (! function_exists('publication_button')) { /** * Publication button component for episodes * * Displays the appropriate publication button depending on the publication post. */ function publication_button(int $podcastId, int $episodeId, string $publicationStatus): string { switch ($publicationStatus) { case 'not_published': $label = lang('Episode.publish'); $route = route_to('episode-publish', $podcastId, $episodeId); $variant = 'primary'; $iconLeft = 'upload-cloud'; break; case 'with_podcast': case 'scheduled': $label = lang('Episode.publish_edit'); $route = route_to('episode-publish_edit', $podcastId, $episodeId); $variant = 'warning'; $iconLeft = 'upload-cloud'; break; case 'published': $label = lang('Episode.unpublish'); $route = route_to('episode-unpublish', $podcastId, $episodeId); $variant = 'danger'; $iconLeft = 'cloud-off'; break; default: $label = ''; $route = ''; $variant = ''; $iconLeft = ''; break; } return <<{$label} HTML; } } // ------------------------------------------------------------------------ if (! function_exists('publication_status_banner')) { /** * Publication status banner component for podcasts * * Displays the appropriate banner depending on the podcast's publication status. */ function publication_status_banner(?Time $publicationDate, int $podcastId, string $publicationStatus): string { switch ($publicationStatus) { case 'not_published': $bannerDisclaimer = lang('Podcast.publication_status_banner.draft_mode'); $bannerText = lang('Podcast.publication_status_banner.not_published'); $linkRoute = route_to('podcast-publish', $podcastId); $linkLabel = lang('Podcast.publish'); break; case 'scheduled': $bannerDisclaimer = lang('Podcast.publication_status_banner.draft_mode'); $bannerText = lang('Podcast.publication_status_banner.scheduled', [ 'publication_date' => local_datetime($publicationDate), ], null, false); $linkRoute = route_to('podcast-publish_edit', $podcastId); $linkLabel = lang('Podcast.publish_edit'); break; default: $bannerDisclaimer = ''; $bannerText = ''; $linkRoute = ''; $linkLabel = ''; break; } return <<

{$bannerDisclaimer} {$bannerText}

{$linkLabel} HTML; } } // ------------------------------------------------------------------------ if (! function_exists('episode_numbering')) { /** * Returns relevant translated episode numbering. * * @param bool $isAbbr component will show abbreviated numbering if true */ function episode_numbering( ?int $episodeNumber = null, ?int $seasonNumber = null, string $class = '', bool $isAbbr = false ): string { if (! $episodeNumber && ! $seasonNumber) { return ''; } $transKey = ''; $args = []; if ($episodeNumber !== null) { $args['episodeNumber'] = sprintf('%02d', $episodeNumber); } if ($seasonNumber !== null) { $args['seasonNumber'] = sprintf('%02d', $seasonNumber); } if ($episodeNumber !== null && $seasonNumber !== null) { $transKey = 'Episode.season_episode'; } elseif ($episodeNumber !== null && $seasonNumber === null) { $transKey = 'Episode.number'; } elseif ($episodeNumber === null && $seasonNumber !== null) { $transKey = 'Episode.season'; } if ($isAbbr) { return '' . lang($transKey . '_abbr', $args) . ''; } return '' . lang($transKey, $args) . ''; } } // ------------------------------------------------------------------------ if (! function_exists('location_link')) { /** * Returns link to display from location info */ function location_link(?Location $location, string $class = ''): string { if (! $location instanceof Location) { return ''; } return anchor( $location->url, icon('map-pin', 'mr-2 flex-shrink-0') . '' . esc($location->name) . '', [ 'class' => 'w-full overflow-hidden inline-flex items-baseline hover:underline focus:ring-accent' . ($class === '' ? '' : " {$class}"), 'target' => '_blank', 'rel' => 'noreferrer noopener', ], ); } } // ------------------------------------------------------------------------ if (! function_exists('audio_player')) { /** * Returns audio player */ function audio_player(string $source, string $mediaType, string $class = ''): string { $language = service('request') ->getLocale(); return << HTML; } } // ------------------------------------------------------------------------ if (! function_exists('relative_time')) { function relative_time(Time $time, string $class = ''): string { $formatter = new IntlDateFormatter(service( 'request' )->getLocale(), IntlDateFormatter::MEDIUM, IntlDateFormatter::NONE); $translatedDate = $time->toLocalizedString($formatter->getPattern()); $datetime = $time->format(DateTime::ATOM); return << HTML; } } // ------------------------------------------------------------------------ if (! function_exists('local_datetime')) { function local_datetime(Time $time): string { $formatter = new IntlDateFormatter(service( 'request' )->getLocale(), IntlDateFormatter::MEDIUM, IntlDateFormatter::LONG); $translatedDate = $time->toLocalizedString($formatter->getPattern()); $datetime = $time->format(DateTime::ISO8601); return << HTML; } } // ------------------------------------------------------------------------ if (! function_exists('local_date')) { function local_date(Time $time): string { $formatter = new IntlDateFormatter(service( 'request' )->getLocale(), IntlDateFormatter::MEDIUM, IntlDateFormatter::NONE); $translatedDate = $time->toLocalizedString($formatter->getPattern()); return <<{$translatedDate} HTML; } } // ------------------------------------------------------------------------ if (! function_exists('explicit_badge')) { function explicit_badge(bool $isExplicit, string $class = ''): string { if (! $isExplicit) { return ''; } $explicitLabel = lang('Common.explicit'); return <<{$explicitLabel} HTML; } } // ------------------------------------------------------------------------ if (! function_exists('category_label')) { function category_label(Category $category): string { $categoryLabel = ''; if ($category->parent_id !== null) { $categoryLabel .= lang('Podcast.category_options.' . $category->parent->code, [], null, false) . ' › '; } return $categoryLabel . lang('Podcast.category_options.' . $category->code, [], null, false); } } // ------------------------------------------------------------------------ if (! function_exists('downloads_abbr')) { function downloads_abbr(int $downloads): string { if ($downloads < 1000) { return (string) $downloads; } $option = match (true) { $downloads < 1_000_000 => [ 'divider' => 1_000, 'suffix' => 'K', ], $downloads < 1_000_000_000 => [ 'divider' => 1_000_000, 'suffix' => 'M', ], default => [ 'divider' => 1_000_000_000, 'suffix' => 'B', ], }; $formatter = new NumberFormatter(service('request')->getLocale(), NumberFormatter::DECIMAL); $formatter->setPattern('#,##0.##'); $abbr = $formatter->format($downloads / $option['divider']) . $option['suffix']; return <<{$abbr} HTML; } }
', 'cell_start' => '', 'cell_alt_start' => '', 'row_start' => '