Added basic video functionnality
This commit is contained in:
parent
73ad43fe7d
commit
e3c2e4da1d
|
@ -14,7 +14,7 @@ module.exports = {
|
|||
defaultLocale: "en",
|
||||
},
|
||||
images: {
|
||||
domains: ["img.accords-library.com"],
|
||||
domains: ["img.accords-library.com", "watch.accords-library.com"],
|
||||
},
|
||||
serverRuntimeConfig: {
|
||||
locales: locales,
|
||||
|
|
|
@ -3,7 +3,7 @@ import { AppStaticProps } from "queries/getAppStaticProps";
|
|||
import { prettyLanguage } from "queries/helpers";
|
||||
import Button from "./Button";
|
||||
|
||||
type HorizontalLineProps = {
|
||||
type Props = {
|
||||
className?: string;
|
||||
locales: (string | undefined)[];
|
||||
languages: AppStaticProps["languages"];
|
||||
|
@ -11,8 +11,8 @@ type HorizontalLineProps = {
|
|||
href?: string;
|
||||
};
|
||||
|
||||
export default function HorizontalLine(
|
||||
props: HorizontalLineProps
|
||||
export default function LanguageSwitcher(
|
||||
props: Props
|
||||
): JSX.Element {
|
||||
const { locales, langui, href } = props;
|
||||
const router = useRouter();
|
||||
|
|
|
@ -207,20 +207,14 @@ export default function MainPanel(props: MainPanelProps): JSX.Element {
|
|||
onClick={() => appLayout.setMainPanelOpen(false)}
|
||||
/>
|
||||
|
||||
{/*
|
||||
|
||||
<NavOption
|
||||
url="/archives"
|
||||
icon="inventory"
|
||||
title={langui.archives}
|
||||
|
||||
reduced={appLayout.mainPanelReduced && isDesktop}
|
||||
onClick={() => appLayout.setMainPanelOpen(false)}
|
||||
/>
|
||||
|
||||
|
||||
*/}
|
||||
|
||||
<NavOption
|
||||
url="/about-us"
|
||||
icon="info"
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
import Chip from "components/Chip";
|
||||
import { GetVideosPreviewQuery } from "graphql/generated";
|
||||
import Link from "next/link";
|
||||
import {
|
||||
getVideoThumbnailURL,
|
||||
prettyDate,
|
||||
prettyDuration,
|
||||
prettyShortenNumber,
|
||||
} from "queries/helpers";
|
||||
|
||||
export type Props = {
|
||||
video: Exclude<
|
||||
Exclude<
|
||||
GetVideosPreviewQuery["videos"],
|
||||
null | undefined
|
||||
>["data"][number]["attributes"],
|
||||
null | undefined
|
||||
>;
|
||||
};
|
||||
|
||||
export default function PostPreview(props: Props): JSX.Element {
|
||||
const { video } = props;
|
||||
|
||||
return (
|
||||
<Link href={`/archives/videos/${video.uid}`} passHref>
|
||||
<div className="drop-shadow-shade-xl cursor-pointer grid items-end hover:scale-[1.02] [--bg-opacity:0] hover:[--bg-opacity:0.5] [--play-opacity:0] hover:[--play-opacity:100] transition-transform">
|
||||
<div className="relative">
|
||||
<img
|
||||
className="aspect-video rounded-t-lg"
|
||||
src={getVideoThumbnailURL(video.uid)}
|
||||
alt={video.title}
|
||||
/>
|
||||
<div className="absolute inset-0 text-light grid place-content-center drop-shadow-shade-lg bg-shade bg-opacity-[var(--bg-opacity)] transition-colors">
|
||||
<span className="material-icons text-6xl opacity-[var(--play-opacity)] transition-opacity">
|
||||
play_circle_outline
|
||||
</span>
|
||||
</div>
|
||||
<div className="absolute right-2 bottom-2 text-light bg-black bg-opacity-60 px-2 rounded-full">
|
||||
{prettyDuration(video.duration)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="linearbg-obi fine:drop-shadow-shade-lg rounded-b-md top-full transition-opacity z-20 grid p-4 gap-2">
|
||||
<div className="flex flex-row flex-wrap gap-x-3 w-full">
|
||||
<p className="mobile:text-xs text-sm">
|
||||
<span className="material-icons !text-base translate-y-[.15em] mr-1">
|
||||
event
|
||||
</span>
|
||||
{prettyDate(video.published_date)}
|
||||
</p>
|
||||
<p className="mobile:text-xs text-sm">
|
||||
<span className="material-icons !text-base translate-y-[.15em] mr-1">
|
||||
visibility
|
||||
</span>
|
||||
{prettyShortenNumber(video.views)}
|
||||
</p>
|
||||
{video.channel?.data?.attributes && (
|
||||
<p className="mobile:text-xs text-sm">
|
||||
<span className="material-icons !text-base translate-y-[.15em] mr-1">
|
||||
person
|
||||
</span>
|
||||
{video.channel.data.attributes.title}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h1 className="text-xl">{video.title}</h1>
|
||||
</div>
|
||||
<div className="grid grid-flow-col gap-1 overflow-x-scroll webkit-scrollbar:w-0 [scrollbar-width:none] place-content-start">
|
||||
{video.categories?.data.map((category) => (
|
||||
<Chip key={category.id} className="text-sm">
|
||||
{category.attributes?.short}
|
||||
</Chip>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
query getVideo($uid: String) {
|
||||
videos(filters: { uid: { eq: $uid } }) {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
uid
|
||||
title
|
||||
description
|
||||
published_date {
|
||||
year
|
||||
month
|
||||
day
|
||||
}
|
||||
channel {
|
||||
data {
|
||||
attributes {
|
||||
uid
|
||||
title
|
||||
subscribers
|
||||
}
|
||||
}
|
||||
}
|
||||
categories {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
short
|
||||
}
|
||||
}
|
||||
}
|
||||
views
|
||||
likes
|
||||
source
|
||||
audio_languages {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
code
|
||||
}
|
||||
}
|
||||
}
|
||||
sub_languages {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
code
|
||||
}
|
||||
}
|
||||
}
|
||||
width
|
||||
height
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
query getVideosPreview {
|
||||
videos(pagination: { limit: -1 }) {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
uid
|
||||
title
|
||||
views
|
||||
duration
|
||||
categories {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
short
|
||||
}
|
||||
}
|
||||
}
|
||||
published_date {
|
||||
...datePicker
|
||||
}
|
||||
channel {
|
||||
data {
|
||||
attributes {
|
||||
uid
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
query getVideosSlugs {
|
||||
videos(pagination: {limit:-1}) {
|
||||
data {
|
||||
id
|
||||
attributes {
|
||||
uid
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
import AppLayout from "components/AppLayout";
|
||||
import NavOption from "components/PanelComponents/NavOption";
|
||||
import PanelHeader from "components/PanelComponents/PanelHeader";
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import { GetStaticPropsContext } from "next";
|
||||
|
@ -15,6 +16,7 @@ export default function Archives(props: ArchivesProps): JSX.Element {
|
|||
title={langui.archives}
|
||||
description={langui.archives_description}
|
||||
/>
|
||||
<NavOption title={"Videos"} url="/archives/videos/" border />
|
||||
</SubPanel>
|
||||
);
|
||||
return (
|
||||
|
|
|
@ -0,0 +1,193 @@
|
|||
import AppLayout from "components/AppLayout";
|
||||
import Button from "components/Button";
|
||||
import HorizontalLine from "components/HorizontalLine";
|
||||
import InsetBox from "components/InsetBox";
|
||||
import NavOption from "components/PanelComponents/NavOption";
|
||||
import ReturnButton, {
|
||||
ReturnButtonType,
|
||||
} from "components/PanelComponents/ReturnButton";
|
||||
import ContentPanel, {
|
||||
ContentPanelWidthSizes,
|
||||
} from "components/Panels/ContentPanel";
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import { useAppLayout } from "contexts/AppLayoutContext";
|
||||
import { GetVideoQuery } from "graphql/generated";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
import { useMediaMobile } from "hooks/useMediaQuery";
|
||||
import {
|
||||
GetStaticPathsContext,
|
||||
GetStaticPathsResult,
|
||||
GetStaticPropsContext,
|
||||
} from "next";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
import { getVideoFile, prettyDate, prettyShortenNumber } from "queries/helpers";
|
||||
|
||||
interface Props extends AppStaticProps {
|
||||
video: Exclude<
|
||||
Exclude<
|
||||
GetVideoQuery["videos"],
|
||||
null | undefined
|
||||
>["data"][number]["attributes"],
|
||||
null | undefined
|
||||
>;
|
||||
}
|
||||
|
||||
export default function Video(props: Props): JSX.Element {
|
||||
const { langui, video } = props;
|
||||
const isMobile = useMediaMobile();
|
||||
const appLayout = useAppLayout();
|
||||
const subPanel = (
|
||||
<SubPanel>
|
||||
<ReturnButton
|
||||
href="/archives/videos/"
|
||||
title={"Videos"}
|
||||
langui={langui}
|
||||
displayOn={ReturnButtonType.desktop}
|
||||
className="mb-10"
|
||||
/>
|
||||
|
||||
<HorizontalLine />
|
||||
|
||||
<NavOption
|
||||
title={langui.video}
|
||||
url="#video"
|
||||
border
|
||||
onClick={() => appLayout.setSubPanelOpen(false)}
|
||||
/>
|
||||
|
||||
<NavOption
|
||||
title={"Channel"}
|
||||
url="#channel"
|
||||
border
|
||||
onClick={() => appLayout.setSubPanelOpen(false)}
|
||||
/>
|
||||
|
||||
<NavOption
|
||||
title={"Description"}
|
||||
url="#description"
|
||||
border
|
||||
onClick={() => appLayout.setSubPanelOpen(false)}
|
||||
/>
|
||||
</SubPanel>
|
||||
);
|
||||
|
||||
const contentPanel = (
|
||||
<ContentPanel width={ContentPanelWidthSizes.large}>
|
||||
<ReturnButton
|
||||
href="/library/"
|
||||
title={langui.library}
|
||||
langui={langui}
|
||||
displayOn={ReturnButtonType.mobile}
|
||||
className="mb-10"
|
||||
/>
|
||||
|
||||
<div className="grid gap-12 place-items-center">
|
||||
<div
|
||||
id="video"
|
||||
className="w-full rounded-xl shadow-shade shadow-lg overflow-hidden"
|
||||
>
|
||||
<video className="w-full" src={getVideoFile(video.uid)} controls></video>
|
||||
<div className="p-6 mt-2">
|
||||
<h1 className="text-2xl">{video.title}</h1>
|
||||
<div className="flex flex-row flex-wrap gap-x-6 w-full">
|
||||
<p>
|
||||
<span className="material-icons !text-base translate-y-[.15em] mr-1">
|
||||
event
|
||||
</span>
|
||||
{prettyDate(video.published_date)}
|
||||
</p>
|
||||
<p>
|
||||
<span className="material-icons !text-base translate-y-[.15em] mr-1">
|
||||
visibility
|
||||
</span>
|
||||
{isMobile
|
||||
? prettyShortenNumber(video.views)
|
||||
: video.views.toLocaleString()}
|
||||
</p>
|
||||
{video.channel?.data?.attributes && (
|
||||
<p>
|
||||
<span className="material-icons !text-base translate-y-[.15em] mr-1">
|
||||
thumb_up
|
||||
</span>
|
||||
{isMobile
|
||||
? prettyShortenNumber(video.likes)
|
||||
: video.likes.toLocaleString()}
|
||||
</p>
|
||||
)}
|
||||
<Button href="" className="!py-0 !px-3">{`View on ${video.source}`}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{video.channel?.data?.attributes && (
|
||||
<InsetBox id="channel" className="grid place-items-center">
|
||||
<div className="w-[clamp(0px,100%,42rem)] grid place-items-center gap-4 text-center">
|
||||
<h2 className="text-2xl">{"Channel"}</h2>
|
||||
<div>
|
||||
<Button href="#">
|
||||
<h3>{video.channel.data.attributes.title}</h3>
|
||||
</Button>
|
||||
|
||||
<p>
|
||||
{video.channel.data.attributes.subscribers.toLocaleString()}{" "}
|
||||
subscribers
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</InsetBox>
|
||||
)}
|
||||
|
||||
<InsetBox id="description" className="grid place-items-center">
|
||||
<div className="w-[clamp(0px,100%,42rem)] grid place-items-center gap-8">
|
||||
<h2 className="text-2xl">{"Description"}</h2>
|
||||
<p className="whitespace-pre-line">{video.description}</p>
|
||||
</div>
|
||||
</InsetBox>
|
||||
</div>
|
||||
</ContentPanel>
|
||||
);
|
||||
return (
|
||||
<AppLayout
|
||||
navTitle={langui.archives}
|
||||
subPanel={subPanel}
|
||||
contentPanel={contentPanel}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getStaticProps(
|
||||
context: GetStaticPropsContext
|
||||
): Promise<{ notFound: boolean } | { props: Props }> {
|
||||
const sdk = getReadySdk();
|
||||
const videos = await sdk.getVideo({
|
||||
uid: context.params?.uid ? context.params.uid.toString() : "",
|
||||
});
|
||||
if (!videos.videos?.data[0].attributes) return { notFound: true };
|
||||
const props: Props = {
|
||||
...(await getAppStaticProps(context)),
|
||||
video: videos.videos.data[0].attributes,
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getStaticPaths(
|
||||
context: GetStaticPathsContext
|
||||
): Promise<GetStaticPathsResult> {
|
||||
const sdk = getReadySdk();
|
||||
const videos = await sdk.getVideo();
|
||||
const paths: GetStaticPathsResult["paths"] = [];
|
||||
if (videos.videos?.data)
|
||||
videos.videos.data.map((video) => {
|
||||
context.locales?.map((local) => {
|
||||
if (video.attributes)
|
||||
paths.push({ params: { uid: video.attributes.uid }, locale: local });
|
||||
});
|
||||
});
|
||||
return {
|
||||
paths,
|
||||
fallback: "blocking",
|
||||
};
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
import AppLayout from "components/AppLayout";
|
||||
import HorizontalLine from "components/HorizontalLine";
|
||||
import PanelHeader from "components/PanelComponents/PanelHeader";
|
||||
import ReturnButton, {
|
||||
ReturnButtonType,
|
||||
} from "components/PanelComponents/ReturnButton";
|
||||
import ContentPanel, {
|
||||
ContentPanelWidthSizes,
|
||||
} from "components/Panels/ContentPanel";
|
||||
import SubPanel from "components/Panels/SubPanel";
|
||||
import VideoPreview from "components/Videos/VideoPreview";
|
||||
import { GetVideosPreviewQuery } from "graphql/generated";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
import { GetStaticPropsContext } from "next";
|
||||
import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps";
|
||||
|
||||
interface Props extends AppStaticProps {
|
||||
videos: Exclude<GetVideosPreviewQuery["videos"], null | undefined>["data"];
|
||||
}
|
||||
|
||||
export default function Videos(props: Props): JSX.Element {
|
||||
const { langui, videos } = props;
|
||||
const subPanel = (
|
||||
<SubPanel>
|
||||
<ReturnButton
|
||||
href="/archives/"
|
||||
title={"Archives"}
|
||||
langui={langui}
|
||||
displayOn={ReturnButtonType.desktop}
|
||||
className="mb-10"
|
||||
/>
|
||||
|
||||
<PanelHeader
|
||||
icon="movie"
|
||||
title="Videos"
|
||||
description={langui.archives_description}
|
||||
/>
|
||||
</SubPanel>
|
||||
);
|
||||
|
||||
const contentPanel = (
|
||||
<ContentPanel width={ContentPanelWidthSizes.large}>
|
||||
<div className="grid gap-8 items-end mobile:grid-cols-2 desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] pb-12 border-b-[3px] border-dotted last-of-type:border-0">
|
||||
{videos.map((video) => (
|
||||
<>{video.attributes && <VideoPreview video={video.attributes} />}</>
|
||||
))}
|
||||
</div>
|
||||
</ContentPanel>
|
||||
);
|
||||
return (
|
||||
<AppLayout
|
||||
navTitle={langui.archives}
|
||||
subPanel={subPanel}
|
||||
contentPanel={contentPanel}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getStaticProps(
|
||||
context: GetStaticPropsContext
|
||||
): Promise<{ notFound: boolean } | { props: Props }> {
|
||||
const sdk = getReadySdk();
|
||||
const videos = await sdk.getVideosPreview();
|
||||
if (!videos.videos) return { notFound: true };
|
||||
const props: Props = {
|
||||
...(await getAppStaticProps(context)),
|
||||
videos: videos.videos.data,
|
||||
};
|
||||
return {
|
||||
props: props,
|
||||
};
|
||||
}
|
|
@ -229,6 +229,37 @@ export function prettyItemSubType(
|
|||
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||
}
|
||||
|
||||
export function prettyShortenNumber(number: number): string {
|
||||
if (number > 1000000) {
|
||||
return number.toLocaleString(undefined, {
|
||||
maximumSignificantDigits: 3,
|
||||
});
|
||||
} else if (number > 1000) {
|
||||
return (number / 1000).toLocaleString(undefined, {
|
||||
maximumSignificantDigits: 2,
|
||||
}) + "K";
|
||||
}
|
||||
return number.toLocaleString();
|
||||
}
|
||||
|
||||
export function prettyDuration(seconds: number): string {
|
||||
let hours = 0;
|
||||
let minutes = 0;
|
||||
while (seconds > 60) {
|
||||
minutes += 1;
|
||||
seconds -= 60;
|
||||
}
|
||||
while (minutes > 60) {
|
||||
hours += 1;
|
||||
minutes -= 60;
|
||||
}
|
||||
let result = "";
|
||||
if (hours) result += hours.toString().padStart(2, "0") + ":";
|
||||
result += minutes.toString().padStart(2, "0") + ":";
|
||||
result += seconds.toString().padStart(2, "0");
|
||||
return result;
|
||||
}
|
||||
|
||||
export function prettyLanguage(
|
||||
code: string,
|
||||
languages: AppStaticProps["languages"]
|
||||
|
@ -417,3 +448,11 @@ export function getLocalesFromLanguages(
|
|||
? languages.map((language) => language?.language?.data?.attributes?.code)
|
||||
: [];
|
||||
}
|
||||
|
||||
export function getVideoThumbnailURL(uid: string):string {
|
||||
return `${process.env.NEXT_PUBLIC_URL_WATCH}/videos/${uid}.webp`;
|
||||
}
|
||||
|
||||
export function getVideoFile(uid: string):string {
|
||||
return `${process.env.NEXT_PUBLIC_URL_WATCH}/videos/${uid}.mp4`;
|
||||
}
|
Loading…
Reference in New Issue