Not gonna work

This commit is contained in:
Fergal Moran
2022-04-09 15:30:26 +01:00
parent 02d2432419
commit f44fc3e5cc
16 changed files with 575 additions and 110 deletions

View File

@@ -12,6 +12,7 @@
"postcss": "^8.4.12", "postcss": "^8.4.12",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-helmet": "^6.1.0",
"react-icons": "^4.3.1", "react-icons": "^4.3.1",
"react-router-dom": "6", "react-router-dom": "6",
"react-scripts": "5.0.0", "react-scripts": "5.0.0",
@@ -24,7 +25,8 @@
"@types/jest": "^27.0.1", "@types/jest": "^27.0.1",
"@types/node": "^16.7.13", "@types/node": "^16.7.13",
"@types/react": "^17.0.20", "@types/react": "^17.0.20",
"@types/react-dom": "^17.0.9" "@types/react-dom": "^17.0.9",
"@types/react-helmet": "^6.1.5"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",

View File

@@ -24,7 +24,7 @@
<script src="https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1"></script> <!-- <script src="https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1"></script>
<script> <script>
window['__onGCastApiAvailable'] = function (isAvailable) { window['__onGCastApiAvailable'] = function (isAvailable) {
if (isAvailable) { if (isAvailable) {
@@ -37,7 +37,7 @@
autoJoinPolicy: chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED autoJoinPolicy: chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED
}); });
}; };
</script> </script> -->
</body> </body>
</html> </html>

View File

@@ -37,7 +37,6 @@ const Header = () => {
return ( return (
<header className="z-40 py-4 bg-white shadow-bottom dark:bg-gray-800"> <header className="z-40 py-4 bg-white shadow-bottom dark:bg-gray-800">
<div className="container flex items-center justify-between h-full px-6 mx-auto text-purple-600 dark:text-purple-300"> <div className="container flex items-center justify-between h-full px-6 mx-auto text-purple-600 dark:text-purple-300">
{/* <!-- Mobile hamburger --> */}
<button <button
className="p-1 mr-5 -ml-1 rounded-md lg:hidden focus:outline-none focus:shadow-outline-purple" className="p-1 mr-5 -ml-1 rounded-md lg:hidden focus:outline-none focus:shadow-outline-purple"
onClick={toggleSidebar} onClick={toggleSidebar}
@@ -45,7 +44,6 @@ const Header = () => {
> >
<MenuIcon className="w-6 h-6" aria-hidden="true" /> <MenuIcon className="w-6 h-6" aria-hidden="true" />
</button> </button>
{/* <!-- Search input --> */}
<div className="flex justify-center flex-1 lg:mr-32"> <div className="flex justify-center flex-1 lg:mr-32">
<div className="relative w-full max-w-xl mr-6 focus-within:text-purple-500"> <div className="relative w-full max-w-xl mr-6 focus-within:text-purple-500">
<div className="absolute inset-y-0 flex items-center pl-2"> <div className="absolute inset-y-0 flex items-center pl-2">
@@ -60,7 +58,6 @@ const Header = () => {
</div> </div>
</div> </div>
<ul className="flex items-center flex-shrink-0 space-x-6"> <ul className="flex items-center flex-shrink-0 space-x-6">
{/* <!-- Theme toggler --> */}
<li className="flex"> <li className="flex">
<button <button
className="rounded-md focus:outline-none focus:shadow-outline-purple" className="rounded-md focus:outline-none focus:shadow-outline-purple"

View File

@@ -1,4 +1,4 @@
import { SidebarContext } from "./sidebar.context"; import { SidebarContext, SidebarProvider } from "./sidebar.context";
import { ThemeContext } from "./theme.context"; import { ThemeContext, ThemeProvider } from "./theme.context";
export { SidebarContext, ThemeContext }; export { SidebarContext, ThemeContext, SidebarProvider, ThemeProvider };

View File

@@ -9,26 +9,26 @@ interface ISidebarProviderContext {
closeSidebar: () => void; closeSidebar: () => void;
} }
export const SidebarContext = React.createContext<ISidebarProviderContext>({ export const SidebarContext = React.createContext<ISidebarProviderContext>({
isSidebarOpen: false, isSidebarOpen: true,
toggleSidebar: () => {}, toggleSidebar: () => {},
closeSidebar: () => {}, closeSidebar: () => {},
}); });
export const SidebarProvider = ({ children }: ISidebarProvider) => { export const SidebarProvider = ({ children }: ISidebarProvider) => {
const [isSidebarOpen, setIsSidebarOpen] = React.useState(false); const [isSidebarOpen, setIsSidebarOpen] = React.useState(true);
const toggleSidebar = () => { const _toggleSidebar = () => {
setIsSidebarOpen(!isSidebarOpen); setIsSidebarOpen(!isSidebarOpen);
}; };
const closeSidebar = () => { const _closeSidebar = () => {
setIsSidebarOpen(false); setIsSidebarOpen(false);
}; };
const value = React.useMemo( const value = React.useMemo(
() => ({ () => ({
isSidebarOpen, isSidebarOpen,
toggleSidebar, toggleSidebar: _toggleSidebar,
closeSidebar, closeSidebar: _closeSidebar,
}), }),
[isSidebarOpen] [isSidebarOpen]
); );

View File

@@ -3,10 +3,16 @@ import ReactDOM from "react-dom";
import "./index.css"; import "./index.css";
import App from "./App"; import App from "./App";
import reportWebVitals from "./reportWebVitals"; import reportWebVitals from "./reportWebVitals";
import { SidebarProvider } from "./context";
import { Windmill } from "@windmill/react-ui";
ReactDOM.render( ReactDOM.render(
<React.StrictMode> <React.StrictMode>
<SidebarProvider>
<Windmill usePreferences>
<App /> <App />
</Windmill>
</SidebarProvider>
</React.StrictMode>, </React.StrictMode>,
document.getElementById("root") document.getElementById("root")
); );

View File

@@ -16,11 +16,21 @@ import { FaChromecast } from "react-icons/fa";
import { Stream } from "../models/stream"; import { Stream } from "../models/stream";
import { convertEpochToSpecificTimezone } from "../utils/date-utils"; import { convertEpochToSpecificTimezone } from "../utils/date-utils";
import { EPGComponent } from "../components"; import { EPGComponent } from "../components";
import {
CastButton,
CastProvider,
useCast,
useMedia,
} from "../utils/chromecast";
const ChannelPage = () => { const ChannelPage = () => {
let params = useParams(); let params = useParams();
const cast = useCast();
const media = useMedia();
const [streams, setStreams] = React.useState<Stream[]>([]); const [streams, setStreams] = React.useState<Stream[]>([]);
const [currentVideoUrl, setCurrentVideoUrl] = React.useState("");
React.useEffect(() => { React.useEffect(() => {
const fetchChannels = async () => { const fetchChannels = async () => {
const res = await fetch( const res = await fetch(
@@ -33,10 +43,7 @@ const ChannelPage = () => {
fetchChannels().catch(console.error); fetchChannels().catch(console.error);
}, [params.channelId]); }, [params.channelId]);
const handleXHR = (...args: any[]) => { const _getStreamUrl = async (streamId: number) => {
console.log("channel.page", "handleXHR", args);
};
const playStream = async (streamId: number) => {
const res = await fetch( const res = await fetch(
`${process.env.REACT_APP_API_URL}/live/stream/url/${streamId}` `${process.env.REACT_APP_API_URL}/live/stream/url/${streamId}`
); );
@@ -45,7 +52,25 @@ const ChannelPage = () => {
return; return;
} }
const data = await res.json(); const data = await res.json();
if (data.url) { return data?.url;
};
const _cast = React.useCallback(
async (streamId: number) => {
const streamUrl = await _getStreamUrl(streamId);
if (streamUrl) {
await media.playMedia(streamUrl);
}
},
[media]
);
const handleXHR = (...args: any[]) => {
console.log("channel.page", "handleXHR", args);
};
const playStream = async (streamId: number) => {
const url = await _getStreamUrl(streamId);
if (url) {
const mpv_args = const mpv_args =
"--keep-open=yes\n--geometry=1024x768-0-0\n--ontop\n--screen=2\n--ytdl-format=bestvideo[ext=mp4][height<=?720]+bestaudio[ext=m4a]\n--border=no".split( "--keep-open=yes\n--geometry=1024x768-0-0\n--ontop\n--screen=2\n--ytdl-format=bestvideo[ext=mp4][height<=?720]+bestaudio[ext=m4a]\n--border=no".split(
/\n/ /\n/
@@ -53,7 +78,7 @@ const ChannelPage = () => {
const query = const query =
`?play_url=` + `?play_url=` +
encodeURIComponent(data.url) + encodeURIComponent(url) +
[""].concat(mpv_args.map(encodeURIComponent)).join("&mpv_args="); [""].concat(mpv_args.map(encodeURIComponent)).join("&mpv_args=");
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
@@ -63,6 +88,7 @@ const ChannelPage = () => {
} }
}; };
return ( return (
<CastProvider>
<TableContainer className="mb-8"> <TableContainer className="mb-8">
<Table> <Table>
<TableHeader> <TableHeader>
@@ -96,40 +122,13 @@ const ChannelPage = () => {
<TableCell> <TableCell>
<div className="flex items-center space-x-4"> <div className="flex items-center space-x-4">
<Button <Button
icon={AiOutlinePlayCircle}
layout="link" layout="link"
size="small"
aria-label="Edit" aria-label="Edit"
onClick={() => playStream(stream.stream_id)} onClick={() => playStream(stream.stream_id)}
> >
<AiOutlinePlayCircle
className="w-6 h-6"
aria-hidden="true"
/>
</Button>
<Button
layout="link"
size="small"
aria-label="Delete"
onClick={() => {
var mediaInfo = new chrome.cast.media.MediaInfo(
currentMediaURL,
contentType
);
var request = new chrome.cast.media.LoadRequest(
mediaInfo
);
castSession.loadMedia(request).then(
function () {
console.log("Load succeed");
},
function (errorCode) {
console.log("Error code: " + errorCode);
}
);
}}
>
<FaChromecast className="w-5 h-5" aria-hidden="true" />
</Button> </Button>
<CastButton streamId={stream.stream_id} onPlay={_cast} />
</div> </div>
</TableCell> </TableCell>
</TableRow>, </TableRow>,
@@ -146,6 +145,7 @@ const ChannelPage = () => {
</TableBody> </TableBody>
</Table> </Table>
</TableContainer> </TableContainer>
</CastProvider>
); );
}; };

View File

@@ -0,0 +1,33 @@
import React from "react";
import useCast from "./useCast";
import { Button } from "@windmill/react-ui";
import { FaChromecast } from "react-icons/fa";
interface ICastButtonProps {
streamId: number;
onPlay: (streamId: number) => void;
}
const CastButton = ({ streamId, onPlay }: ICastButtonProps) => {
const cast = useCast({
initialize_media_player: "DEFAULT_MEDIA_RECEIVER_APP_ID",
auto_initialize: true,
});
const handleClick = React.useCallback(async () => {
if (cast.castReceiver) {
await cast.handleConnection();
onPlay(streamId);
}
}, [cast.castReceiver, cast.handleConnection]);
return (
<Button
layout="link"
size="small"
aria-label="Delete"
onClick={handleClick}
>
<FaChromecast className="w-5 h-5" aria-hidden="true" />
</Button>
);
};
export default CastButton;

View File

@@ -0,0 +1,72 @@
import * as React from "react";
import { Helmet } from "react-helmet";
import castContext from "./castContext";
import CastReceiver from "./CastReceiver";
const { useState, useEffect } = React;
const wait = (time: number) =>
new Promise((res) => {
setTimeout(res, time);
});
function CastProvider({ children }: { children: any }) {
const [cast, setCast] = useState<{
castReceiver?: CastReceiver;
castSender?: any;
}>({});
const [session, setSession] = useState<any>({});
useEffect(() => {
(async () => {
let toBreak = false;
let tries = 15;
let castReceiver: CastReceiver;
let castSender: any;
while (true) {
try {
// @ts-ignore
castReceiver = window.chrome.cast as CastReceiver;
// @ts-ignore
castSender = window.cast.framework as any;
toBreak = true;
} catch (err) {
tries--;
if (!tries) {
toBreak = true;
}
} finally {
if (toBreak) break;
}
await wait(95);
}
// @ts-ignore
if (tries !== 0 && !!castReceiver) {
setCast({
castReceiver,
castSender,
});
} else {
throw new Error("Can't Load castReceiver and\\or castSender");
}
})();
}, []);
return (
<>
<Helmet>
<script src="//www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1" />
<script src="//www.gstatic.com/cast/sdk/libs/receiver/2.0.0/cast_receiver.js" />
</Helmet>
<castContext.Provider
value={{
...cast,
session,
setSession,
}}
>
{children}
</castContext.Provider>
</>
);
}
export default CastProvider;

View File

@@ -0,0 +1,21 @@
export default interface CastReceiver {
SessionRequest: new (...args: Array<any>) => any;
media: {
MediaInfo: new (p: string) => any;
LoadRequest: new (p: string) => any;
[key: string]: any;
};
Capability: {
[key: string]: string;
};
ApiConfig: new (...args: Array<any>) => any;
initialize: (
ApiConfig: any,
initSucess: (e: any) => void,
initError: (e: any) => void
) => void;
requestSession: (
initSucess: (e: any) => void,
initError: (e: any) => void
) => void;
}

View File

@@ -0,0 +1,11 @@
import { createContext } from "react";
import CastReceiver from "./CastReceiver";
const castContext = createContext<{
castReceiver?: CastReceiver;
castSender?: any;
setSession?: (p: any) => void;
session?: any;
}>({});
export default castContext;

View File

@@ -0,0 +1,6 @@
import CastButton from "./CastButton";
import CastProvider from "./CastProvider";
import useCast from "./useCast";
import useMedia from "./useMedia";
export { CastProvider, CastButton, useMedia, useCast };

View File

@@ -0,0 +1,143 @@
import { useEffect, useContext, useCallback, useState } from "react";
import castContext from "./castContext";
import CastReceiver from "./CastReceiver";
interface Props {
/**
* @param initialize_media_player - is media_receiver id
*
* if you pass auto_initialize as true you should pass this
*
* you should pass media_receiver id or 'DEFAULT_MEDIA_RECEIVER_APP_ID' to use default media receiver
*/
initialize_media_player?: string;
/**
* @param auto_initialize - you can use this to auto initialize cast media player when castReceiver was define
*
* you not need to pass nothing to this if you go initialize by yourself
*/
auto_initialize?: boolean;
}
interface Cast {
/**
* Function to initialize cast player before connect to chromecast
*
* This function should wait for castReceiver
*
* @param media_player - you should pass media_receiver id or 'DEFAULT_MEDIA_RECEIVER_APP_ID' to use default media receiver
*
* Example:
* ```jsx
* const cast = useCast()
* useEffect(() => {
* if(cast.castReceiver){
* cast.initializeCast('DEFAULT_MEDIA_RECEIVER_APP_ID')
* }
* }, [cast.castReceiver])
* ```
*/
initializeCast?: (media_player: string) => void;
/**
* Function to connect and disconnect client to chromecast
*/
handleConnection: () => Promise<any>;
/**
* castReceiver object, from cast_receiver google lib
*/
castReceiver?: CastReceiver;
/**
* castSender object, from cast_sender google lib
*/
castSender?: any;
/**
* this inidicate if client is connected with chromecast or not
*/
isConnect: boolean;
}
function useCast(props?: Props) {
const { initialize_media_player, auto_initialize } = props || {};
const { castReceiver, castSender, setSession } = useContext(castContext);
const [cast, setCast] = useState({});
const [isConnect, setIsConnect] = useState(false);
const initiliazeCast = useCallback(
(media_player: string) => {
if (!castReceiver) return;
const sessionRequest = new castReceiver.SessionRequest(
castReceiver.media[media_player]
);
const apiConfig = new castReceiver.ApiConfig(
// @ts-ignore
sessionRequest,
(e: any) => {
// console.log("ss listener", e);
if (setSession) setSession(e);
setIsConnect(true);
},
(e: any) => {
// console.log("rc listener", e);
}
);
castReceiver.initialize(
apiConfig,
(e: any) => {
// console.log("init success", e);
},
(e: any) => {
// console.log("init error", e);
}
);
setCast({
castReceiver,
});
},
[castReceiver, setSession]
);
const handleConnection = useCallback(
() =>
new Promise((res, rej) => {
if (castReceiver) {
// @ts-ignore
castReceiver.requestSession(
(e: any) => {
if (setSession) setSession(e);
setIsConnect(true);
res(e);
},
(e: any) => {
setIsConnect(false);
if (!isConnect) return rej(e);
return res(null);
}
);
}
}),
[castReceiver, setSession]
);
useEffect(() => {
if (castReceiver) {
setCast({ castReceiver });
if (auto_initialize && !initialize_media_player)
throw new Error(
"if you pass auto_initialize: true, you should pass initialize_media_player"
);
else if (auto_initialize && initialize_media_player)
initiliazeCast(initialize_media_player);
}
}, [castReceiver, castSender]);
const Cast = { ...cast, handleConnection, isConnect } as Cast;
if (!auto_initialize) {
Cast["initializeCast"] = initiliazeCast;
}
return Cast;
}
export default useCast;

View File

@@ -0,0 +1,134 @@
import { useCallback, useContext, useState, useEffect } from "react";
import castContext from "./castContext";
interface Media {
/**
* @function playMedia - function to add an media url to chromecast to play, you should use this to first media before add more with the add function, and before use play function
* @param src - this should be an media url acessible by chromecast
* @param autoplay - this inidicate if media will play after defined, default is true
*/
playMedia: (src: string, autoplay?: boolean) => Promise<any>;
/**
* @function addMedia - function to add an media url to chromecast queue
* @param src - this should be an media url acessible by chromecast
*/
addMedia: (src: string) => Promise<any>;
/**
* @function play - function to play media in chromecast
*/
play: () => Promise<any>;
/**
* @function pause - function to pause media in chromecast
*/
pause: () => Promise<any>;
/**
* this inidicate if is a media connected to chromecast
*/
isMedia: boolean;
/**
* @function next - function to jump to next video in chromecast queue
*/
next: () => Promise<any>;
/**
* @function prev - function to jump to prev video in chromecast queue
*/
prev: () => Promise<any>;
/**
* @function to - function to jump to the time passed in seconds in chromecast playing video
* @param seconds - time in seconds to jump to
*/
to: (seconds: number) => Promise<any>;
}
function useMedia() {
const { session, castReceiver } = useContext(castContext);
const [media, setMedia] = useState<any>(null);
const [isMedia, setIsMedia] = useState(false);
useEffect(() => {
if (!session && isMedia) setIsMedia(false);
}, [session, isMedia]);
const playMedia = useCallback(
(src: string, autoplay?: boolean) =>
new Promise((res, rej) => {
if (!castReceiver || !session)
return rej(new Error("An Error occurred"));
// @ts-ignore
const mediaInfo = new castReceiver.media.MediaInfo(src);
// @ts-ignore
const request = new castReceiver.media.LoadRequest(mediaInfo);
request.autoplay = autoplay || true;
session.loadMedia(
request,
(media: any) => {
setMedia(media);
setIsMedia(true);
res(media);
},
(err: any) => rej(err)
);
}),
[castReceiver, session]
);
const addMedia = useCallback(
async (src: string) => {
if (!castReceiver && !media) return;
// @ts-ignore
const mediaInfo = new castReceiver.media.MediaInfo(src);
// @ts-ignore
const queueItem = new castReceiver.media.QueueItem(mediaInfo);
await media.queueAppendItem(queueItem);
},
[media, castReceiver]
);
const play = useCallback(async () => {
if (!media) return;
await media.play();
}, [media]);
const pause = useCallback(async () => {
if (!media) return;
await media.pause();
}, [media]);
const prev = useCallback(async () => {
if (!media) return;
await media.queuePrev();
}, [media]);
const next = useCallback(async () => {
if (!media) return;
await media.queueNext();
}, [media]);
const to = useCallback(
async (seconds: number) => {
if (!media && !castReceiver) return;
// @ts-ignore
const seek = new castReceiver.media.SeekRequest();
seek.currentTime = seconds;
await media.seek(seek);
},
[media, castReceiver]
);
return {
playMedia,
addMedia,
play,
pause,
isMedia,
next,
prev,
to,
} as Media;
}
export default useMedia;

View File

@@ -1,18 +1,19 @@
const defaultTheme = require("tailwindcss/defaultTheme"); const defaultTheme = require("tailwindcss/defaultTheme");
const windmill = require("@windmill/react-ui/config"); const windmill = require("@windmill/react-ui/config");
module.exports = { const config = {
content: ["./src/**/*.{js,jsx,ts,tsx}"], content: ["./src/**/*.{js,jsx,ts,tsx}"],
theme: { theme: {
extend: { extend: {
fontFamily: { fontFamily: {
Rampart: ["Raleway", "sans-serif"], Rampart: ["Raleway", "sans-serif"],
}, },
boxShadow: {
bottom:
"0 5px 6px -7px rgba(0, 0, 0, 0.6), 0 2px 4px -5px rgba(0, 0, 0, 0.06)",
},
}, },
}, },
plugins: [], plugins: [],
}; };
module.exports = {
...windmill(config),
...config,
};

View File

@@ -1760,6 +1760,13 @@
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
"@types/helmet@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@types/helmet/-/helmet-4.0.0.tgz#af7af46de26abe368b85360769ae9938bfb2318a"
integrity sha512-ONIn/nSNQA57yRge3oaMQESef/6QhoeX7llWeDli0UZIfz8TQMkfNPTXA8VnnyeA1WUjG2pGqdjEIueYonMdfQ==
dependencies:
helmet "*"
"@types/hls.js@^1.0.0": "@types/hls.js@^1.0.0":
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/@types/hls.js/-/hls.js-1.0.0.tgz#47a03f97217461d279e4dc89481ddff86def5a36" resolved "https://registry.yarnpkg.com/@types/hls.js/-/hls.js-1.0.0.tgz#47a03f97217461d279e4dc89481ddff86def5a36"
@@ -1868,6 +1875,13 @@
dependencies: dependencies:
"@types/react" "*" "@types/react" "*"
"@types/react-helmet@^6.1.5":
version "6.1.5"
resolved "https://registry.yarnpkg.com/@types/react-helmet/-/react-helmet-6.1.5.tgz#35f89a6b1646ee2bc342a33a9a6c8777933f9083"
integrity sha512-/ICuy7OHZxR0YCAZLNg9r7I9aijWUWvxaPR6uTuyxe8tAj5RL4Sw1+R6NhXUtOsarkGYPmaHdBDvuXh2DIN/uA==
dependencies:
"@types/react" "*"
"@types/react@*", "@types/react@^17.0.20": "@types/react@*", "@types/react@^17.0.20":
version "17.0.43" version "17.0.43"
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.43.tgz#4adc142887dd4a2601ce730bc56c3436fdb07a55" resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.43.tgz#4adc142887dd4a2601ce730bc56c3436fdb07a55"
@@ -4545,6 +4559,11 @@ he@^1.2.0:
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
helmet@*:
version "5.0.2"
resolved "https://registry.yarnpkg.com/helmet/-/helmet-5.0.2.tgz#3264ec6bab96c82deaf65e3403c369424cb2366c"
integrity sha512-QWlwUZZ8BtlvwYVTSDTBChGf8EOcQ2LkGMnQJxSzD1mUu8CCjXJZq/BXP8eWw4kikRnzlhtYo3lCk0ucmYA3Vg==
history@^5.2.0: history@^5.2.0:
version "5.3.0" version "5.3.0"
resolved "https://registry.yarnpkg.com/history/-/history-5.3.0.tgz#1548abaa245ba47992f063a0783db91ef201c73b" resolved "https://registry.yarnpkg.com/history/-/history-5.3.0.tgz#1548abaa245ba47992f063a0783db91ef201c73b"
@@ -6960,7 +6979,7 @@ prompts@^2.0.1, prompts@^2.4.2:
kleur "^3.0.3" kleur "^3.0.3"
sisteransi "^1.0.5" sisteransi "^1.0.5"
prop-types@^15.6.2, prop-types@^15.8.1: prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
version "15.8.1" version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@@ -7099,6 +7118,11 @@ react-error-overlay@^6.0.10:
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.10.tgz#0fe26db4fa85d9dbb8624729580e90e7159a59a6" resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.10.tgz#0fe26db4fa85d9dbb8624729580e90e7159a59a6"
integrity sha512-mKR90fX7Pm5seCOfz8q9F+66VCc1PGsWSBxKbITjfKVQHMNF2zudxHnMdJiB1fRCb+XsbQV9sO9DCkgsMQgBIA== integrity sha512-mKR90fX7Pm5seCOfz8q9F+66VCc1PGsWSBxKbITjfKVQHMNF2zudxHnMdJiB1fRCb+XsbQV9sO9DCkgsMQgBIA==
react-fast-compare@^3.1.1:
version "3.2.0"
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb"
integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==
react-focus-lock@2.4.1: react-focus-lock@2.4.1:
version "2.4.1" version "2.4.1"
resolved "https://registry.yarnpkg.com/react-focus-lock/-/react-focus-lock-2.4.1.tgz#e842cc93da736b5c5d331799012544295cbcee4f" resolved "https://registry.yarnpkg.com/react-focus-lock/-/react-focus-lock-2.4.1.tgz#e842cc93da736b5c5d331799012544295cbcee4f"
@@ -7111,6 +7135,16 @@ react-focus-lock@2.4.1:
use-callback-ref "^1.2.1" use-callback-ref "^1.2.1"
use-sidecar "^1.0.1" use-sidecar "^1.0.1"
react-helmet@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/react-helmet/-/react-helmet-6.1.0.tgz#a750d5165cb13cf213e44747502652e794468726"
integrity sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw==
dependencies:
object-assign "^4.1.1"
prop-types "^15.7.2"
react-fast-compare "^3.1.1"
react-side-effect "^2.1.0"
react-icons@^4.3.1: react-icons@^4.3.1:
version "4.3.1" version "4.3.1"
resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-4.3.1.tgz#2fa92aebbbc71f43d2db2ed1aed07361124e91ca" resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-4.3.1.tgz#2fa92aebbbc71f43d2db2ed1aed07361124e91ca"
@@ -7201,6 +7235,11 @@ react-scripts@5.0.0:
optionalDependencies: optionalDependencies:
fsevents "^2.3.2" fsevents "^2.3.2"
react-side-effect@^2.1.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-2.1.1.tgz#66c5701c3e7560ab4822a4ee2742dee215d72eb3"
integrity sha512-2FoTQzRNTncBVtnzxFOk2mCpcfxQpenBMbk5kSVBg5UcPqV9fRbgY2zhb7GTWWOlpFmAxhClBDlIq8Rsubz1yQ==
react-transition-group@4.4.1: react-transition-group@4.4.1:
version "4.4.1" version "4.4.1"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9"