diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 89190494..9f3604a9 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -20,7 +20,7 @@ services: - postgres volumes: - ./back:/app - - /app/out + - /app/out/ - kyoo:/var/lib/kyoo - ./video:/video front: @@ -29,7 +29,8 @@ services: dockerfile: Dockerfile.dev volumes: - ./front:/app - - /app/nodes_modules + - /app/node_modules/ + - /app/.next/ ports: - "3000:3000" restart: on-failure @@ -55,6 +56,7 @@ services: environment: - POSTGRES_USER=${POSTGRES_USER} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + - POSTGRES_DB=${POSTGRES_DB} volumes: - db:/var/lib/postgresql/data diff --git a/front/locales/en/player.json b/front/locales/en/player.json index 4b28f795..ecd1e99a 100644 --- a/front/locales/en/player.json +++ b/front/locales/en/player.json @@ -7,5 +7,6 @@ "mute": "Toggle mute", "volume": "Volume", "subtitles": "Subtitles", + "subtitle-none": "None", "fullscreen": "Fullscreen" } diff --git a/front/locales/fr/player.json b/front/locales/fr/player.json index adefcd01..d5d721e7 100644 --- a/front/locales/fr/player.json +++ b/front/locales/fr/player.json @@ -7,5 +7,6 @@ "mute": "Muet", "volume": "Volume", "subtitles": "Sous titres", + "subtitle-none": "Aucun", "fullscreen": "Plein-écran" } diff --git a/front/next.config.js b/front/next.config.js index 2b71b9a7..0f9f7f52 100755 --- a/front/next.config.js +++ b/front/next.config.js @@ -18,6 +18,8 @@ * along with Kyoo. If not, see . */ +const CopyPlugin = require("copy-webpack-plugin"); + /** * @type {import("next").NextConfig} */ @@ -25,6 +27,21 @@ const nextConfig = { reactStrictMode: true, swcMinify: true, output: "standalone", + webpack: (config) => { + config.plugins = [ + ...config.plugins, + new CopyPlugin({ + patterns: [ + { + context: "node_modules/@jellyfin/libass-wasm/dist/js/", + from: "*", + to: "static/chunks/", + }, + ], + }), + ]; + return config; + }, async redirects() { return [ { diff --git a/front/package.json b/front/package.json index 773f96dc..4f7c0c9d 100644 --- a/front/package.json +++ b/front/package.json @@ -23,6 +23,7 @@ "dependencies": { "@emotion/react": "^11.9.3", "@emotion/styled": "^11.9.3", + "@jellyfin/libass-wasm": "^4.1.1", "@mui/icons-material": "^5.8.4", "@mui/material": "^5.8.7", "next": "12.2.2", @@ -38,6 +39,7 @@ "@types/node": "18.0.3", "@types/react": "18.0.15", "@types/react-dom": "18.0.6", + "copy-webpack-plugin": "^11.0.0", "eslint": "8.19.0", "eslint-config-next": "12.2.2", "eslint-config-prettier": "^8.5.0", diff --git a/front/src/components/episode.tsx b/front/src/components/episode.tsx index 8cfd8e4d..49b87885 100644 --- a/front/src/components/episode.tsx +++ b/front/src/components/episode.tsx @@ -25,7 +25,11 @@ import { Link } from "~/utils/link"; import { Image } from "./poster"; export const episodeDisplayNumber = ( - episode: { seasonNumber?: number; episodeNumber?: number; absoluteNumber?: number }, + episode: { + seasonNumber?: number | null; + episodeNumber?: number | null; + absoluteNumber?: number | null; + }, def?: string, ) => { if (typeof episode.seasonNumber === "number" && typeof episode.episodeNumber === "number") diff --git a/front/src/models/resources/watch-item.ts b/front/src/models/resources/watch-item.ts index 1d8d5a7a..e5e9f1fd 100644 --- a/front/src/models/resources/watch-item.ts +++ b/front/src/models/resources/watch-item.ts @@ -60,6 +60,7 @@ export const TrackP = ResourceP.extend({ */ displayName: z.string(), }); +export type Track = z.infer; export const ChapterP = z.object({ /** diff --git a/front/src/pages/browse/index.tsx b/front/src/pages/browse/index.tsx index 1fc787a2..573c1415 100644 --- a/front/src/pages/browse/index.tsx +++ b/front/src/pages/browse/index.tsx @@ -228,6 +228,78 @@ const Item = ({ item, layout }: { item?: LibraryItem; layout: Layout }) => { } }; +const SortByMenu = ({ + sortKey, + setSort, + sortOrd, + setSortOrd, + anchor, + onClose, +}: { + sortKey: SortBy; + setSort: (sort: SortBy) => void; + sortOrd: SortOrd; + setSortOrd: (sort: SortOrd) => void; + anchor: HTMLElement; + onClose: () => void; +}) => { + const router = useRouter(); + const { t } = useTranslation("browse"); + + return ( + + {Object.values(SortBy).map((x) => ( + setSort(x)} + component={Link} + to={{ query: { ...router.query, sortBy: `${sortKey}-${sortOrd}` } }} + shallow + replace + > + {t(`browse.sortkey.${x}`)} + + ))} + + setSortOrd(SortOrd.Asc)} + component={Link} + to={{ query: { ...router.query, sortBy: `${sortKey}-${sortOrd}` } }} + shallow + replace + > + + + + {t("browse.sortord.asc")} + + setSortOrd(SortOrd.Desc)} + component={Link} + to={{ query: { ...router.query, sortBy: `${sortKey}-${sortOrd}` } }} + shallow + replace + > + + + + {t("browse.sortord.desc")} + + + ); +}; + const BrowseSettings = ({ sortKey, setSort, @@ -244,7 +316,6 @@ const BrowseSettings = ({ setLayout: (layout: Layout) => void; }) => { const [sortAnchor, setSortAnchor] = useState(null); - const router = useRouter(); const { t } = useTranslation("browse"); const switchViewTitle = layout === Layout.Grid @@ -265,7 +336,7 @@ const BrowseSettings = ({ aria-controls={sortAnchor ? "sorby-menu" : undefined} aria-haspopup="true" aria-expanded={sortAnchor ? "true" : undefined} - onClick={(event: MouseEvent) => setSortAnchor(event.currentTarget)} + onClick={(event) => setSortAnchor(event.currentTarget)} > {t("browse.sortby", { key: t(`browse.sortkey.${sortKey}`) })} @@ -282,56 +353,16 @@ const BrowseSettings = ({ - setSortAnchor(null)} - > - {Object.values(SortBy).map((x) => ( - setSort(x)} - component={Link} - to={{ query: { ...router.query, sortBy: `${sortKey}-${sortOrd}` } }} - shallow - replace - > - {t(`browse.sortkey.${x}`)} - - ))} - - setSortOrd(SortOrd.Asc)} - component={Link} - to={{ query: { ...router.query, sortBy: `${sortKey}-${sortOrd}` } }} - shallow - replace - > - - - - {t("browse.sortord.asc")} - - setSortOrd(SortOrd.Desc)} - component={Link} - to={{ query: { ...router.query, sortBy: `${sortKey}-${sortOrd}` } }} - shallow - replace - > - - - - {t("browse.sortord.desc")} - - + {sortAnchor && ( + setSortAnchor(null)} + /> + )} ); }; diff --git a/front/src/pages/watch/[slug].tsx b/front/src/pages/watch/[slug].tsx index be93c791..fdafa9f2 100644 --- a/front/src/pages/watch/[slug].tsx +++ b/front/src/pages/watch/[slug].tsx @@ -20,10 +20,18 @@ import { QueryIdentifier, QueryPage } from "~/utils/query"; import { withRoute } from "~/utils/router"; -import { WatchItem, WatchItemP, Chapter } from "~/models/resources/watch-item"; +import { WatchItem, WatchItemP, Chapter, Track } from "~/models/resources/watch-item"; import { useFetch } from "~/utils/query"; import { ErrorPage } from "~/components/errors"; -import { useState, useRef, useEffect, HTMLProps, memo, useMemo, useCallback } from "react"; +import { + useState, + useRef, + useEffect, + memo, + useMemo, + useCallback, + RefObject, +} from "react"; import { Box, CircularProgress, @@ -32,6 +40,10 @@ import { Typography, Skeleton, Slider, + Menu, + MenuItem, + ListItemText, + BoxProps, } from "@mui/material"; import useTranslation from "next-translate/useTranslation"; import { @@ -50,7 +62,11 @@ import { } from "@mui/icons-material"; import { Poster } from "~/components/poster"; import { episodeDisplayNumber } from "~/components/episode"; +import { Link } from "~/utils/link"; import NextLink from "next/link"; +import { useRouter } from "next/router"; +// @ts-ignore +import SubtitleOctopus from "@jellyfin/libass-wasm" const toTimerString = (timer: number, duration?: number) => { if (!duration) duration = timer; @@ -58,6 +74,74 @@ const toTimerString = (timer: number, duration?: number) => { return new Date(timer * 1000).toISOString().substring(14, 19); }; +const SubtitleMenu = ({ + subtitles, + setSubtitle, + selectedID, + anchor, + onClose, +}: { + subtitles: Track[]; + setSubtitle: (subtitle: Track | null) => void; + selectedID?: number; + anchor: HTMLElement; + onClose: () => void; +}) => { + const router = useRouter(); + const { t } = useTranslation("player"); + const { subtitle, ...queryWithoutSubs } = router.query; + + return ( + + { + setSubtitle(null); + onClose(); + }} + component={Link} + to={{ query: queryWithoutSubs }} + shallow + replace + > + {t("subtitle-none")} + + {subtitles.map((sub) => ( + { + setSubtitle(sub); + onClose(); + }} + component={Link} + to={{ query: { ...router.query, subtitle: sub.language ?? sub.id } }} + shallow + replace + > + {sub.displayName} + + ))} + + ); +}; + const LoadingIndicator = () => { return ( void; + subtitles?: Track[]; + selectedSubtitle: Track | null; + selectSubtitle: (track: Track | null) => void; }) { const { t } = useTranslation("player"); + const [subtitleAnchor, setSubtitleAnchor] = useState(null); return ( - *": { mx: "8px !important" } }}> - - - - - + *": { m: "8px !important" } }}> + {subtitles && ( + + setSubtitleAnchor(event.currentTarget)} + sx={{ color: "white" }} + > + + + + )} {isFullscreen ? : } + {subtitleAnchor && ( + setSubtitleAnchor(null)} + /> + )} ); }); @@ -356,6 +466,67 @@ const Back = memo(function Back({ name, href }: { name?: string; href: string }) ); }); +const useSubtitleController = (player: RefObject): [Track | null, (value: Track | null) => void] => { + const [selectedSubtitle, setSubtitle] = useState(null); + const [htmlTrack, setHtmlTrack] = useState(null); + const [subocto, setSubOcto] = useState(null); + + return [ + selectedSubtitle, + useCallback( + (value: Track | null) => { + const removeHtmlSubtitle = () => { + if (htmlTrack) htmlTrack.remove(); + setHtmlTrack(null); + }; + const removeOctoSub = () => { + if (subocto) { + subocto.freeTrack(); + subocto.dispose(); + } + setSubOcto(null); + }; + + if (!player.current) return; + + setSubtitle(value); + if (!value) { + removeHtmlSubtitle(); + removeOctoSub(); + } else if (value.codec === "vtt" || value.codec === "srt") { + removeOctoSub(); + const track: HTMLTrackElement = htmlTrack ?? document.createElement("track"); + track.kind = "subtitles"; + track.label = value.displayName; + if (value.language) track.srclang = value.language; + track.src = `subtitle/${value.slug}.vtt`; + track.className = "subtitle_container"; + track.default = true; + track.onload = () => { + if (player.current) player.current.textTracks[0].mode = "showing"; + }; + player.current.appendChild(track); + setHtmlTrack(track); + } else if (value.codec === "ass") { + removeHtmlSubtitle(); + removeOctoSub(); + setSubOcto( + new SubtitleOctopus({ + video: player.current, + subUrl: `/api/subtitle/${value.slug}`, + workerUrl: "/_next/static/chunks/subtitles-octopus-worker.js", + legacyWorkerUrl: "/_next/static/chunks/subtitles-octopus-worker-legacy.js", + /* fonts: */ + renderMode: "wasm-blend", + }), + ); + } + }, + [htmlTrack, subocto, player], + ), + ]; +}; + const useVideoController = () => { const player = useRef(null); const [isPlaying, setPlay] = useState(true); @@ -366,6 +537,7 @@ const useVideoController = () => { const [volume, setVolume] = useState(100); const [isMuted, setMute] = useState(false); const [isFullscreen, setFullscreen] = useState(false); + const [selectedSubtitle, selectSubtitle] = useSubtitleController(player); useEffect(() => { if (!player?.current?.duration) return; @@ -373,7 +545,7 @@ const useVideoController = () => { }, [player]); const togglePlay = useCallback(() => { - if (!player?.current) return; + if (!player.current) return; if (!isPlaying) { player.current.play(); } else { @@ -390,7 +562,7 @@ const useVideoController = () => { } }, [isFullscreen]); - const videoProps: HTMLProps = useMemo( + const videoProps: BoxProps<"video"> = useMemo( () => ({ ref: player, onClick: togglePlay, @@ -418,7 +590,17 @@ const useVideoController = () => { [player, togglePlay, toggleFullscreen], ); return { - state: { isPlaying, isLoading, progress, duration, buffered, volume, isMuted, isFullscreen }, + state: { + isPlaying, + isLoading, + progress, + duration, + buffered, + volume, + isMuted, + isFullscreen, + selectedSubtitle, + }, videoProps, togglePlay, toggleMute: useCallback(() => { @@ -439,6 +621,7 @@ const useVideoController = () => { }, [player], ), + selectSubtitle, }; }; @@ -447,19 +630,31 @@ const query = (slug: string): QueryIdentifier => ({ parser: WatchItemP, }); -// +// Callback used to hide the controls when the mouse goes iddle. This is stored globally to clear the old timeout +// if the mouse moves again let mouseCallback: NodeJS.Timeout; const Player: QueryPage<{ slug: string }> = ({ slug }) => { const { data, error } = useFetch(query(slug)); const { - state: { isPlaying, isLoading, progress, duration, buffered, volume, isMuted, isFullscreen }, + state: { + isPlaying, + isLoading, + progress, + duration, + buffered, + volume, + isMuted, + isFullscreen, + selectedSubtitle, + }, videoProps, togglePlay, toggleMute, toggleFullscreen, setProgress, setVolume, + selectSubtitle, } = useVideoController(); const [showHover, setHover] = useState(false); const [mouseMoved, setMouseMoved] = useState(false); @@ -494,7 +689,7 @@ const Player: QueryPage<{ slug: string }> = ({ slug }) => { = ({ slug }) => { setHover(true)} onMouseLeave={() => setHover(false)} - sx={ displayControls ? { - visibility: "visible", - opacity: 1, - transition: "opacity .2s ease-in", - } : { - visibility: "hidden", - opacity: 0, - transition: "opacity .4s ease-out, visibility 0s .4s", - }} + sx={ + displayControls + ? { + visibility: "visible", + opacity: 1, + transition: "opacity .2s ease-in", + } + : { + visibility: "hidden", + opacity: 0, + transition: "opacity .4s ease-out, visibility 0s .4s", + } + } > = ({ slug }) => { {toTimerString(progress, duration)} : {toTimerString(duration)} - + diff --git a/front/yarn.lock b/front/yarn.lock index 7da73d9d..5649540a 100644 --- a/front/yarn.lock +++ b/front/yarn.lock @@ -195,6 +195,11 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== +"@jellyfin/libass-wasm@^4.1.1": + version "4.1.1" + resolved "https://registry.yarnpkg.com/@jellyfin/libass-wasm/-/libass-wasm-4.1.1.tgz#d1c0e789844e1ad5d3b36acaeb7351e59f5b7d9a" + integrity sha512-xQVJw+lZUg4U1TmLS80reBECfPtpCgRF8hhUSvUUQM9g68OvINyUU3K2yqRH+8tomGpghiRaIcr/bUJ83e0veA== + "@mui/base@5.0.0-alpha.88": version "5.0.0-alpha.88" resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-alpha.88.tgz#0930d1849c74ba62a28ab2d8533de88764173ba4" @@ -404,6 +409,11 @@ dependencies: "@types/ms" "*" +"@types/json-schema@^7.0.9": + version "7.0.11" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" + integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== + "@types/json5@^0.0.29": version "0.0.29" resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" @@ -535,6 +545,20 @@ acorn@^8.7.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== +ajv-formats@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" + integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== + dependencies: + ajv "^8.0.0" + +ajv-keywords@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16" + integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw== + dependencies: + fast-deep-equal "^3.1.3" + ajv@^6.10.0, ajv@^6.12.4: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" @@ -545,6 +569,16 @@ ajv@^6.10.0, ajv@^6.12.4: json-schema-traverse "^0.4.1" uri-js "^4.2.2" +ajv@^8.0.0, ajv@^8.8.0: + version "8.11.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.11.0.tgz#977e91dd96ca669f54a11e23e378e33b884a565f" + integrity sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" @@ -774,6 +808,18 @@ copy-anything@^3.0.2: dependencies: is-what "^4.1.6" +copy-webpack-plugin@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz#96d4dbdb5f73d02dd72d0528d1958721ab72e04a" + integrity sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ== + dependencies: + fast-glob "^3.2.11" + glob-parent "^6.0.1" + globby "^13.1.1" + normalize-path "^3.0.0" + schema-utils "^4.0.0" + serialize-javascript "^6.0.0" + core-js-pure@^3.20.2: version "3.23.4" resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.23.4.tgz#aba5c7fb297063444f6bf93afb0362151679a012" @@ -1180,6 +1226,17 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== +fast-glob@^3.2.11: + version "3.2.12" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80" + integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + fast-glob@^3.2.9: version "3.2.11" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" @@ -1351,6 +1408,17 @@ globby@^11.1.0: merge2 "^1.4.1" slash "^3.0.0" +globby@^13.1.1: + version "13.1.2" + resolved "https://registry.yarnpkg.com/globby/-/globby-13.1.2.tgz#29047105582427ab6eca4f905200667b056da515" + integrity sha512-LKSDZXToac40u8Q1PQtZihbNdTYSNMuWe+K5l+oa6KgDzSvVrHXlJy40hUP522RjAIoNLJYBJi7ow+rbFpIhHQ== + dependencies: + dir-glob "^3.0.1" + fast-glob "^3.2.11" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^4.0.0" + has-bigints@^1.0.1, has-bigints@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" @@ -1580,6 +1648,11 @@ json-schema-traverse@^0.4.1: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" @@ -1981,6 +2054,11 @@ next@12.2.2: "@next/swc-win32-ia32-msvc" "12.2.2" "@next/swc-win32-x64-msvc" "12.2.2" +normalize-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -2183,6 +2261,13 @@ queue-microtask@^1.2.2: resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== +randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + react-dom@18.2.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" @@ -2260,6 +2345,11 @@ remove-accents@0.4.2: resolved "https://registry.yarnpkg.com/remove-accents/-/remove-accents-0.4.2.tgz#0a43d3aaae1e80db919e07ae254b285d9e1c7bb5" integrity sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA== +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" @@ -2309,6 +2399,11 @@ sade@^1.7.3: dependencies: mri "^1.1.0" +safe-buffer@^5.1.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" @@ -2321,6 +2416,16 @@ scheduler@^0.23.0: dependencies: loose-envify "^1.1.0" +schema-utils@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.0.0.tgz#60331e9e3ae78ec5d16353c467c34b3a0a1d3df7" + integrity sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg== + dependencies: + "@types/json-schema" "^7.0.9" + ajv "^8.8.0" + ajv-formats "^2.1.1" + ajv-keywords "^5.0.0" + semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" @@ -2333,6 +2438,13 @@ semver@^7.3.7: dependencies: lru-cache "^6.0.0" +serialize-javascript@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" + integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== + dependencies: + randombytes "^2.1.0" + shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -2359,6 +2471,11 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== +slash@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7" + integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== + source-map-js@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"