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" | ||||
|  | ||||
							
								
								
									
										80
									
								
								src/components/Videos/VideoPreview.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								src/components/Videos/VideoPreview.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -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> | ||||
|   ); | ||||
| } | ||||
							
								
								
									
										55
									
								
								src/graphql/operations/getVideo.graphql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/graphql/operations/getVideo.graphql
									
									
									
									
									
										Normal file
									
								
							| @ -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 | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
							
								
								
									
										32
									
								
								src/graphql/operations/getVideosPreview.graphql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/graphql/operations/getVideosPreview.graphql
									
									
									
									
									
										Normal file
									
								
							| @ -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 | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
							
								
								
									
										10
									
								
								src/graphql/operations/getVideosSlugs.graphql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/graphql/operations/getVideosSlugs.graphql
									
									
									
									
									
										Normal file
									
								
							| @ -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 ( | ||||
|  | ||||
							
								
								
									
										193
									
								
								src/pages/archives/videos/[uid].tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										193
									
								
								src/pages/archives/videos/[uid].tsx
									
									
									
									
									
										Normal file
									
								
							| @ -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", | ||||
|   }; | ||||
| } | ||||
							
								
								
									
										73
									
								
								src/pages/archives/videos/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								src/pages/archives/videos/index.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 DrMint
						DrMint