first commit
Some checks failed
Build / run (push) Has been cancelled

This commit is contained in:
maher
2025-10-29 11:42:25 +01:00
commit 703f50a09d
4595 changed files with 385164 additions and 0 deletions

View File

@@ -0,0 +1,168 @@
import {
Link,
NavLink,
useLocation,
useNavigate,
useOutletContext,
useParams,
} from 'react-router-dom';
import clsx from 'clsx';
import {Trans} from '@common/i18n/trans';
import {message} from '@common/i18n/message';
import {useIsMobileMediaQuery} from '@common/utils/hooks/is-mobile-media-query';
import {StaticPageTitle} from '@common/seo/static-page-title';
import {Option, Select} from '@common/ui/forms/select/select';
import React, {Fragment, ReactNode, useRef} from 'react';
import {useStickySentinel} from '@common/utils/hooks/sticky-sentinel';
import {getTitleLink} from '@app/titles/title-link';
import {IconButton} from '@common/ui/buttons/icon-button';
import {OpenInNewIcon} from '@common/icons/material/OpenInNew';
import {Title} from '@app/titles/models/title';
import {useScrollToTop} from '@common/ui/navigation/use-scroll-to-top';
import {InfoDialogTriggerIcon} from '@common/ui/overlays/dialog/info-dialog-trigger/info-dialog-trigger-icon';
const allMenuItems = [
{to: 'primary-facts', label: message('Primary Facts')},
{to: 'seasons', label: message('Seasons'), hideIfMovie: true},
{to: 'images', label: message('Images')},
{to: 'videos', label: message('Videos')},
{to: 'cast', label: message('Cast')},
{to: 'crew', label: message('Crew')},
{to: 'genres', label: message('Genres')},
{to: 'keywords', label: message('Keywords')},
{to: 'countries', label: message('Countries')},
{to: 'reviews', label: message('Reviews')},
{to: 'comments', label: message('Comments')},
];
function useFilteredMenuItems() {
const title = useOutletContext<Title>();
const isMovie = !title?.is_series;
return allMenuItems.filter(item => !isMovie || !item.hideIfMovie);
}
interface Props {
children: ReactNode;
actions?: ReactNode;
}
export function TitleEditorLayout({children, actions}: Props) {
const isMobile = useIsMobileMediaQuery();
const {isSticky, sentinelRef} = useStickySentinel();
const title = useOutletContext<Title>();
const {season, episode} = useParams();
const link = title ? getTitleLink(title, {season, episode}) : null;
const ref = useRef<HTMLDivElement>(null);
const heading = title ? (
<Trans values={{name: title.name}} message="Edit “:name“" />
) : (
<Trans message="New title" />
);
useScrollToTop(ref);
return (
<Fragment>
<StaticPageTitle>
<Trans message="Edit title" />
</StaticPageTitle>
<div ref={sentinelRef} />
<div
ref={ref}
className={clsx(
'sticky top-0 my-12 md:my-24 z-10 transition-shadow',
isSticky && 'bg-paper shadow'
)}
>
<div
className={clsx(
'flex items-center md:items-start gap-24 py-14 container mx-auto px-24'
)}
>
<h1 className="text-xl md:text-3xl whitespace-nowrap overflow-hidden overflow-ellipsis md:mr-64">
{heading}
</h1>
<div className="mr-auto"></div>
{link ? (
<IconButton size="sm" elementType={Link} to={link} target="_blank">
<OpenInNewIcon />
</IconButton>
) : null}
{actions}
</div>
</div>
<div className="container md:flex gap-30 items-stretch mx-auto px-24 pb-24">
{isMobile ? <MobileNav /> : <DesktopNav />}
<div className="md:pl-30 flex-auto relative">{children}</div>
</div>
</Fragment>
);
}
function MobileNav() {
const {titleId} = useParams();
const {pathname} = useLocation();
const navigate = useNavigate();
const value = titleId ? pathname.split('/').pop() : 'primary-facts';
const menuItems = useFilteredMenuItems();
return (
<Select
disabled={!titleId}
minWidth="min-w-none"
className="w-full bg-paper mb-24"
selectionMode="single"
selectedValue={value}
onSelectionChange={newPage => {
if (titleId) {
navigate(itemLink(titleId, newPage as string));
}
}}
>
{menuItems.map(item => (
<Option key={item.to} value={item.to}>
<Trans {...item.label} />
</Option>
))}
</Select>
);
}
function DesktopNav() {
const {titleId} = useParams();
const menuItems = useFilteredMenuItems();
return (
<div className="w-240 sticky top-24 flex-shrink-0">
{menuItems.map(item => {
const link = titleId ? itemLink(titleId, item.to) : '';
return (
<NavLink
key={item.to}
to={link}
aria-disabled={!titleId}
className={({isActive}) =>
clsx(
'block p-14 whitespace-nowrap mb-8 rounded border-l-4 text-sm transition-bg-color',
!link && 'pointer-events-none text-muted',
(isActive && link) || (item.to === 'primary-facts' && !link)
? 'bg-primary/selected border-l-primary font-medium'
: 'border-l-transparent hover:bg-hover'
)
}
>
<Trans {...item.label} />
</NavLink>
);
})}
{!titleId ? (
<div className="flex items-center gap-8 text-muted text-xs mt-24">
<InfoDialogTriggerIcon viewBox="0 0 16 16" size="xs" />
<Trans message="Create title to enable menu items." />
</div>
) : null}
</div>
);
}
const itemLink = (titleId: string | number, to: string) =>
`/admin/titles/${titleId}/edit/${to}`;