Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frontend/src/routes/Video.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -915,7 +915,7 @@ const Metadata: React.FC<MetadataProps> = ({ event, realmPath }) => {
"> button": { ...shrinkOnMobile },
}}>
{event.canWrite && user !== "none" && user !== "unknown" && (
<LinkButton to={ManageVideoDetailsRoute.url({ videoId: event.id })} css={{
<LinkButton to={ManageVideoDetailsRoute.url({ id: event.id })} css={{
"&:not([disabled])": { color: COLORS.primary0 },
...shrinkOnMobile,
}}>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/routes/manage/Series/Create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ const CreateSeriesPage: React.FC<CreateSeriesPageProps> = ({ knownRolesRef }) =>
},
onCompleted: response => {
const returnPath = ManageSeriesDetailsRoute.url({
seriesId: response.createSeries.id,
id: response.createSeries.id,
});
setSuccess(true);
setNotification({
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/routes/manage/Series/SeriesAccess.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const ManageSeriesAccessRoute = makeManageSeriesRoute(
(series, data) => (
<AclPage note={!isSynced(series) && <NotReadyNote kind="series" />} breadcrumbTails={[
{ label: i18n.t("manage.series.table.title"), link: ManageSeriesRoute.url },
{ label: series.title, link: ManageSeriesDetailsRoute.url({ seriesId: series.id }) },
{ label: series.title, link: ManageSeriesDetailsRoute.url({ id: series.id }) },
]}>
<SeriesAclEditor {...{ series, data }} />
</AclPage>
Expand Down
7 changes: 4 additions & 3 deletions frontend/src/routes/manage/Series/Shared.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { LuShieldCheck, LuPenLine, LuEye, LuTrash } from "react-icons/lu";
import { graphql } from "react-relay";

import { RootLoader } from "../../../layout/Root";
import { makeRoute, Route } from "../../../rauta";
import { makeRoute } from "../../../rauta";
import { loadQuery } from "../../../relay";
import { NotFound } from "../../NotFound";
import { b64regex } from "../../util";
Expand All @@ -17,6 +17,7 @@ import { COLORS } from "../../../color";
import { ThumbnailStack } from "../../../ui/ThumbnailStack";
import { ThumbnailItemState } from "../../../ui/Video";
import { MovingTruck } from "../../../ui/Waiting";
import { ManageRoute } from "../Shared/Details";


export const PAGE_WIDTH = 1100;
Expand All @@ -31,9 +32,9 @@ export const makeManageSeriesRoute = (
page: ManageSeriesSubPageType,
path: `/${string}` | "",
render: (series: Series, data: QueryResponse) => JSX.Element,
): Route & { url: (args: { seriesId: string }) => string } => (
): ManageRoute => (
makeRoute({
url: ({ seriesId }: { seriesId: string }) => `/~manage/series/${keyOfId(seriesId)}${path}`,
url: ({ id }: { id: string }) => `/~manage/series/${keyOfId(id)}${path}`,
match: url => {
const regex = new RegExp(`^/~manage/series/(${b64regex}+)${path}/?$`, "u");
const params = regex.exec(url.pathname);
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/routes/manage/Shared/Details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { ConfirmationModal, ConfirmationModalHandle } from "../../../ui/Modal";
import { Link, useRouter } from "../../../router";
import { useNotification } from "../../../ui/NotificationContext";
import { preciseDateTime, preferredLocaleForLang } from "../../../ui/time";
import { Route } from "../../../rauta";


type Item = OpencastEntity & {
Expand All @@ -38,6 +39,8 @@ type PageProps<T> = {
sections: (item: T) => ReactNode[];
};

export type ManageRoute = Route & { url: (args: { id: string }) => string };

export const DetailsPage = <T extends Item>({
item,
pageTitle,
Expand Down
7 changes: 4 additions & 3 deletions frontend/src/routes/manage/Video/Shared.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { LuInfo, LuShieldCheck, LuPlay, LuPenLine } from "react-icons/lu";
import { graphql } from "react-relay";

import { RootLoader } from "../../../layout/Root";
import { makeRoute, Route } from "../../../rauta";
import { makeRoute } from "../../../rauta";
import { loadQuery } from "../../../relay";
import { NotAuthorized } from "../../../ui/error";
import { NotFound } from "../../NotFound";
Expand All @@ -15,6 +15,7 @@ import { DirectVideoRoute, VideoRoute } from "../../Video";
import { ManageVideosRoute } from ".";
import CONFIG from "../../../config";
import { ReturnLink, ManageNav, ManageSubPageType } from "../Shared/Nav";
import { ManageRoute } from "../Shared/Details";


export const PAGE_WIDTH = 1100;
Expand All @@ -31,9 +32,9 @@ export const makeManageVideoRoute = (
path: `/${string}` | "",
render: (event: AuthorizedEvent, data: QueryResponse) => JSX.Element,
options?: { fetchWorkflowState?: boolean },
): Route & { url: (args: { videoId: string }) => string } => (
): ManageRoute => (
makeRoute({
url: ({ videoId }: { videoId: string }) => `/~manage/videos/${keyOfId(videoId)}${path}`,
url: ({ id }: { id: string }) => `/~manage/videos/${keyOfId(id)}${path}`,
match: url => {
const regex = new RegExp(`^/~manage/videos/(${b64regex}+)${path}/?$`, "u");
const params = regex.exec(url.pathname);
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/routes/manage/Video/TechnicalDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const Page: React.FC<Props> = ({ event }) => {
const breadcrumbs = [
{ label: t("user.manage"), link: ManageRoute.url },
{ label: t("manage.video.table"), link: ManageVideosRoute.url },
{ label: event.title, link: ManageVideoDetailsRoute.url({ videoId: event.id }) },
{ label: event.title, link: ManageVideoDetailsRoute.url({ id: event.id }) },
];

const user = useUser();
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/routes/manage/Video/VideoAccess.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const ManageVideoAccessRoute = makeManageVideoRoute(
(event, data) => (
<AclPage note={event.hostRealms.length < 1 && <UnlistedNote />} breadcrumbTails={[
{ label: i18n.t("manage.video.table"), link: ManageVideosRoute.url },
{ label: event.title, link: ManageVideoDetailsRoute.url({ videoId: event.id }) },
{ label: event.title, link: ManageVideoDetailsRoute.url({ id: event.id }) },
]}>
<EventAclEditor {...{ event, data }} />
</AclPage>
Expand Down Expand Up @@ -91,7 +91,7 @@ const EventAclEditor: React.FC<EventAclPageProps> = ({ event, data }) => {
<Card kind="info" iconPos="left" css={{ fontSize: 14, marginBottom: 10 }}>
<Trans i18nKey="acl.locked-to-series">
series
<Link to={ManageSeriesAccessRoute.url({ seriesId: event.series.id })} />
<Link to={ManageSeriesAccessRoute.url({ id: event.series.id })} />
</Trans>
</Card>
)}
Expand Down
22 changes: 14 additions & 8 deletions frontend/src/ui/Blocks/Playlist.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,14 +108,17 @@ export const PlaylistBlock: React.FC<Props> = ({ playlist, ...props }) => {

const playlistKey = keyOfId(playlist.id);
return <VideoListBlock
initialLayout={props.layout}
initialOrder={
(props.order === "%future added value" ? undefined : props.order) ?? "ORIGINAL"
}
allowOriginalOrder
{...{ title }}
description={(props.showMetadata && playlist.description) || undefined}
creators={props.showMetadata ? [playlist.creator] : undefined}
displayOptions={{
initialLayout: props.layout,
initialOrder: (props.order === "%future added value" ? undefined : props.order)
?? "ORIGINAL",
allowOriginalOrder: true,
}}
metadata={{
title,
description: (props.showMetadata && playlist.description) || undefined,
creators: props.showMetadata ? [playlist.creator] : undefined,
}}
activeEventId={props.activeEventId}
realmPath={props.realmPath}
isPlaylist
Expand All @@ -130,5 +133,8 @@ export const PlaylistBlock: React.FC<Props> = ({ playlist, ...props }) => {
shareUrl: `/!p/${playlistKey}`,
rssUrl: `/~rss/playlist/${playlistKey}`,
}}
// TODO: Once playlist PR is merged, add:
// (or just add that to the PR if this is merged prior)
// linkToManagePage={ManagePlaylistDetailsRoute.url({ id: playlist.id })}
/>;
};
25 changes: 16 additions & 9 deletions frontend/src/ui/Blocks/Series.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
SeriesBlockSeriesData$key,
} from "./__generated__/SeriesBlockSeriesData.graphql";
import { VideoListBlock, VideoListBlockContainer } from "./VideoList";
import { ManageSeriesDetailsRoute } from "../../routes/manage/Series/SeriesDetails";


// ==============================================================================================
Expand Down Expand Up @@ -43,6 +44,7 @@ const seriesFragment = graphql`
description
state
metadata
canWrite
entries {
__typename
...on AuthorizedEvent { id, ...VideoListEventData }
Expand Down Expand Up @@ -109,15 +111,19 @@ const SeriesBlock: React.FC<Props> = ({ series, ...props }) => {

const seriesKey = keyOfId(series.id);
return <VideoListBlock
initialLayout={props.layout}
initialOrder={
(props.order === "%future added value" ? undefined : props.order) ?? "NEW_TO_OLD"
}
allowOriginalOrder={false}
title={props.title ?? (props.showTitle ? series.title : undefined)}
description={(props.showMetadata && series.description) || undefined}
timestamp={props.showMetadata ? series.created ?? undefined : undefined}
creators={props.showMetadata ? creators : undefined}
displayOptions={{
initialLayout: props.layout,
initialOrder: (props.order === "%future added value" ? undefined : props.order)
?? "NEW_TO_OLD",
allowOriginalOrder: false,
}}
metadata={{
title: props.title ?? (props.showTitle ? series.title : undefined),
description: (props.showMetadata && series.description) || undefined,
timestamp: props.showMetadata ? series.created ?? undefined : undefined,
creators: props.showMetadata ? creators : undefined,
canWrite: series.canWrite,
}}
activeEventId={props.activeEventId}
realmPath={props.realmPath}
listEntries={series.entries}
Expand All @@ -128,5 +134,6 @@ const SeriesBlock: React.FC<Props> = ({ series, ...props }) => {
: `${props.realmPath.replace(/\/$/u, "")}/s/${seriesKey}`,
rssUrl: `/~rss/series/${seriesKey}`,
}}
linkToManagePage={ManageSeriesDetailsRoute.url({ id: series.id })}
/>;
};
71 changes: 48 additions & 23 deletions frontend/src/ui/Blocks/VideoList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { keyframes } from "@emotion/react";
import { IconType } from "react-icons";
import {
LuColumns2, LuList, LuChevronLeft, LuChevronRight, LuPlay, LuLayoutGrid, LuCircleAlert, LuInfo,
LuRss, LuLink,
LuRss, LuLink, LuSettings,
} from "react-icons/lu";
import { graphql, readInlineData } from "react-relay";

Expand All @@ -45,6 +45,7 @@ import { isRealUser, useUser } from "../../User";
import { LoginLink } from "../../routes/util";
import { QrCodeButton, ShareButton } from "../ShareButton";
import { CopyableInput } from "../Input";
import { LinkButton } from "../LinkButton";



Expand Down Expand Up @@ -95,40 +96,47 @@ type Entries = Extract<
{ __typename: "AuthorizedPlaylist" }
>["entries"];

export type VideoListBlockProps = {
listId?: string;
realmPath: string | null;
activeEventId?: string;
allowOriginalOrder: boolean;
initialOrder: Order;
initialLayout?: VideoListLayout;
export type VideoListMetadata = {
title?: string;
description?: string;
timestamp?: string;
creators?: string[];
canWrite?: boolean;
};

export type VideoListDisplayOptions = {
initialOrder: Order;
allowOriginalOrder: boolean;
initialLayout?: VideoListLayout;
};

export type VideoListBlockProps = {
listId?: string;
realmPath: string | null;
activeEventId?: string;
displayOptions: VideoListDisplayOptions;
metadata?: VideoListMetadata;
shareInfo: VideoListShareButtonProps,
isPlaylist?: boolean;
listEntries: Entries;
editMode: boolean;
linkToManagePage?: string;
}

export const VideoListBlock: React.FC<VideoListBlockProps> = ({
listId,
realmPath,
activeEventId,
allowOriginalOrder,
initialOrder,
initialLayout = "GALLERY",
title,
description,
timestamp,
creators,
displayOptions,
metadata,
shareInfo,
isPlaylist = false,
listEntries,
editMode,
linkToManagePage,
}) => {
const { t, i18n } = useTranslation();
const { initialOrder, allowOriginalOrder, initialLayout = "GALLERY" } = displayOptions;
const [eventOrder, setEventOrder] = useState<Order>(initialOrder);
const user = useUser();

Expand Down Expand Up @@ -164,11 +172,14 @@ export const VideoListBlock: React.FC<VideoListBlockProps> = ({

const eventsNotEmpty = items.length > 0;
const hasHiddenItems = missingItems + unauthorizedItems > 0;
const showManageLink = metadata?.canWrite && linkToManagePage
&& user !== "none" && user !== "unknown";

return <OrderContext.Provider value={{ eventOrder, setEventOrder, allowOriginalOrder }}>
<VideoListBlockContainer
showViewOptions={eventsNotEmpty}
{...{ title, description, timestamp, creators, shareInfo, initialLayout, isPlaylist }}
{...showManageLink && { linkToManagePage }}
{...{ metadata, shareInfo, initialLayout, isPlaylist }}
>
{(mainItems.length + upcomingLiveEvents.length === 0 && !hasHiddenItems)
? <div css={{ padding: 14 }}>{t("manage.video-list.no-content")}</div>
Expand Down Expand Up @@ -302,17 +313,14 @@ const orderItems = (
return { mainItems, upcomingLiveEvents, missingItems, unauthorizedItems };
};


type VideoListBlockContainerProps = {
title?: string;
description?: string | null;
timestamp?: string;
creators?: string[];
metadata?: VideoListMetadata;
shareInfo?: VideoListShareButtonProps,
children: ReactNode;
showViewOptions: boolean;
initialLayout?: VideoListLayout;
isPlaylist?: boolean;
linkToManagePage?: string;
};

type LayoutContext = {
Expand All @@ -326,9 +334,10 @@ const LayoutContext = createContext<LayoutContext>({
});

export const VideoListBlockContainer: React.FC<VideoListBlockContainerProps> = ({
title, description, timestamp, creators, shareInfo, children,
showViewOptions, initialLayout = "GALLERY",
metadata, shareInfo, children, showViewOptions,
initialLayout = "GALLERY", linkToManagePage,
}) => {
const { title, description, creators, timestamp } = metadata ?? {};
const [layoutState, setLayoutState] = useState<VideoListLayout>(initialLayout);
const isDark = useColorScheme().scheme === "dark";
const hasMetadata = description || timestamp || (creators && creators.length > 0);
Expand Down Expand Up @@ -400,6 +409,7 @@ export const VideoListBlockContainer: React.FC<VideoListBlockContainerProps> = (
flexWrap: "wrap",
},
}}>
{linkToManagePage && <VideoListManageButton link={linkToManagePage} />}
{shareInfo && <VideoListShareButton {...shareInfo} />}
{showViewOptions && <>
<OrderMenu />
Expand Down Expand Up @@ -455,6 +465,21 @@ export const VideoListShareButton: React.FC<VideoListShareButtonProps> = ({
};


export const VideoListManageButton: React.FC<{ link: string }> = ({ link }) => {
const { t } = useTranslation();
return <LinkButton to={link} css={{
// Todo: why/when would this be disabled?
"&:not([disabled])": { color: COLORS.primary0 },
padding: 12,
height: 31,
borderRadius: 4,
}}>
<LuSettings size={16} />
{t("user.manage")}
</LinkButton>;
};


// ==============================================================================================
// ===== The menus for choosing order and layout mode
// ==============================================================================================
Expand Down