Upload Vocoder Flow

This commit is contained in:
Brandon Thomas committed 2022-08-08 04:31:52 -04:00
1 parent 6e40db5c60
commit e484ba9b92
13 files changed
+531 -427

No files matched your search

+8
View File
@@ -380,6 +380,14 @@ class ApiConfig {
return `${this.getScheme()}://${this.getNewApiHost()}/voice_clone_requests/check`;
}
enqueueRetrievalJob(): string {
return `${this.getScheme()}://${this.getNewApiHost()}/retrieval/enqueue`;
}
getRetrievalJobStatus(jobToken: string): string {
return `${this.getScheme()}://${this.getNewApiHost()}/retrieval/job_status/${jobToken}`;
}
// =============== Storyteller-specific ===============
listTwitchEventRules(): string {
@@ -0,0 +1,53 @@
import { ApiConfig } from "../ApiConfig";
export interface EnqueueRetrievalRequest {
idempotency_token: string,
title: string,
download_url: string,
generic_download_type: string,
}
export interface EnqueueRetrievalSuccessResponse {
success: boolean,
job_token: string,
}
export interface EnqueueRetrievalErrorResponse {
success: boolean,
}
type EnqueueRetrievalResponse = EnqueueRetrievalSuccessResponse | EnqueueRetrievalErrorResponse;
export function EnqueueRetrievalIsOk(response: EnqueueRetrievalResponse): response is EnqueueRetrievalSuccessResponse {
return response?.success === true;
}
export function EnqueueRetrievalIsError(response: EnqueueRetrievalResponse): response is EnqueueRetrievalErrorResponse {
return response?.success === false;
}
export async function EnqueueRetrieval(request: EnqueueRetrievalRequest) : Promise<EnqueueRetrievalResponse>
{
const endpoint = new ApiConfig().enqueueRetrievalJob();
return fetch(endpoint, {
method: 'POST',
credentials: 'include',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(request),
})
.then(res => res.json())
.then(res => {
if (res && 'success' in res) {
return res;
} else {
return { success : false };
}
})
.catch(e => {
return { success : false };
});
}
@@ -0,0 +1,63 @@
import { ApiConfig } from "../ApiConfig";
export interface GetRetrievalJobStatusSuccessResponse {
success: boolean,
state: RetrievalJobStatus,
}
export interface RetrievalJobStatus {
// Job primary key
job_token: string,
// Job state machine (enum) and retry count
status: string,
attempt_count: number,
// Optional unstructured extra details during the inference process
maybe_extra_status_description?: string,
// Job completion: foreign key to entity
maybe_downloaded_entity_type?: string,
maybe_downloaded_entity_token?: string,
created_at: Date,
updated_at: Date,
}
export interface GetRetrievalJobStatusErrorResponse {
success: boolean,
}
type GetRetrievalJobStatusResponse = GetRetrievalJobStatusSuccessResponse | GetRetrievalJobStatusErrorResponse;
export function GetRetrievalJobStatusIsOk(response: GetRetrievalJobStatusResponse): response is GetRetrievalJobStatusSuccessResponse {
return response?.success === true;
}
export function GetRetrievalJobStatusIsError(response: GetRetrievalJobStatusResponse): response is GetRetrievalJobStatusErrorResponse {
return response?.success === false;
}
export async function GetRetrievalJobStatus(jobToken: string) : Promise<GetRetrievalJobStatusResponse>
{
const endpoint = new ApiConfig().getRetrievalJobStatus(jobToken);
return fetch(endpoint, {
method: 'GET',
credentials: 'include',
headers: {
'Accept': 'application/json',
},
})
.then(res => res.json())
.then(res => {
if (res && 'success' in res) {
return res;
} else {
return { success : false };
}
})
.catch(e => {
return { success : false };
});
}
@@ -0,0 +1,59 @@
import { RetrievalJobStatus } from "../api/retrieval/GetRetrievalJobStatus";
import { JobState, jobStateFromString } from "./JobStates";
export class VocoderUploadJob {
jobToken: string;
maybeDownloadedEntityType: string | undefined | null;
maybeDownloadedEntityToken: string | undefined | null;
jobState: JobState;
maybeExtraStatusDescription: string | null;
attemptCount: number;
constructor(
jobToken: string,
status: string = 'unknown',
maybeExtraStatusDescription: string | null = null,
attemptCount: number = 0,
maybeDownloadedEntityType: string | undefined | null = undefined,
maybeDownloadedEntityToken: string | undefined | null = undefined
) {
this.jobState = jobStateFromString(status);
this.maybeExtraStatusDescription = maybeExtraStatusDescription;
this.attemptCount = attemptCount;
this.jobToken = jobToken;
if (!!maybeDownloadedEntityType) {
this.maybeDownloadedEntityType = maybeDownloadedEntityType;
}
if (!!maybeDownloadedEntityToken) {
this.maybeDownloadedEntityToken = maybeDownloadedEntityToken;
}
}
static fromResponse(response: RetrievalJobStatus) : VocoderUploadJob {
return new VocoderUploadJob(
response.job_token,
response.status,
response.maybe_extra_status_description || null,
response.attempt_count || 0,
response.maybe_downloaded_entity_type,
response.maybe_downloaded_entity_token,
);
}
}
export interface VocoderUploadJobStateResponsePayload {
success: boolean,
state?: VocoderUploadJobState,
}
export interface VocoderUploadJobState {
job_token: string,
status: string,
maybe_extra_status_description: string | null,
attempt_count: number | undefined,
maybe_downloaded_entity_type: string | undefined | null,
maybe_downloaded_entity_token: string | undefined | null,
created_at: string,
updated_at: string,
}
+47
View File
@@ -31,6 +31,8 @@ import { FAKEYOU_MERGED_TRANSLATIONS } from "./_i18n/FakeYouTranslations";
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import { USE_REFRESH } from "./Refresh";
import { VocoderUploadJob } from "@storyteller/components/src/jobs/VocoderUploadJobs";
import { GetRetrievalJobStatus, GetRetrievalJobStatusIsOk } from "@storyteller/components/src/api/retrieval/GetRetrievalJobStatus";
i18n
.use(initReactI18next) // passes i18n down to react-i18next
@@ -108,6 +110,7 @@ interface State {
w2lInferenceJobs: Array<W2lInferenceJob>;
ttsModelUploadJobs: Array<TtsModelUploadJob>;
w2lTemplateUploadJobs: Array<W2lTemplateUploadJob>;
vocoderUploadJobs: Array<VocoderUploadJob>;
// Current text entered
textBuffer: string;
@@ -163,6 +166,7 @@ class App extends React.Component<Props, State> {
w2lInferenceJobs: [],
ttsModelUploadJobs: [],
w2lTemplateUploadJobs: [],
vocoderUploadJobs: [],
textBuffer: "",
};
@@ -543,6 +547,42 @@ class App extends React.Component<Props, State> {
});
};
enqueueVocoderUploadJob = (jobToken: string) => {
const newJob = new VocoderUploadJob(jobToken);
let uploadJobs = this.state.vocoderUploadJobs.concat([newJob]);
this.setState({
vocoderUploadJobs: uploadJobs,
});
};
checkVocoderUploadJob = async (jobToken: string) => {
const lookupResult = await GetRetrievalJobStatus(jobToken);
if (GetRetrievalJobStatusIsOk(lookupResult)) {
let updatedJobs: Array<VocoderUploadJob> = [];
this.state.vocoderUploadJobs.forEach((existingJob) => {
if (
existingJob.jobToken !== lookupResult.state!.job_token ||
!jobStateCanChange(existingJob.jobState)
) {
updatedJobs.push(existingJob);
return;
}
let updatedJob = VocoderUploadJob.fromResponse(
lookupResult.state!
);
updatedJobs.push(updatedJob);
});
this.setState({
vocoderUploadJobs: updatedJobs,
});
}
};
pollJobs = () => {
this.state.ttsInferenceJobs.forEach((job) => {
if (jobStateCanChange(job.jobState)) {
@@ -564,6 +604,11 @@ class App extends React.Component<Props, State> {
this.checkW2lTemplateUploadJob(job.jobToken);
}
});
this.state.vocoderUploadJobs.forEach((job) => {
if (jobStateCanChange(job.jobState)) {
this.checkVocoderUploadJob(job.jobToken);
}
});
};
setMigrationMode = (mode: MigrationMode) => {
@@ -644,6 +689,8 @@ class App extends React.Component<Props, State> {
this.enqueueW2lTemplateUploadJob
}
w2lTemplateUploadJobs={this.state.w2lTemplateUploadJobs}
enqueueVocoderUploadJob={this.enqueueVocoderUploadJob}
vocoderUploadJobs={this.state.vocoderUploadJobs}
textBuffer={this.state.textBuffer}
setTextBuffer={this.setTextBuffer}
clearTextBuffer={this.clearTextBuffer}
@@ -17,13 +17,14 @@ import { TtsInferenceJob, W2lInferenceJob } from '../../App';
import { TtsModelDeletePage } from './tts/tts_model_delete/TtsModelDeletePage';
import { TtsModelEditPage } from './tts/tts_model_edit/TtsModelEditPage';
import { TtsModelUploadJob } from '@storyteller/components/src/jobs/TtsModelUploadJobs';
import { VocoderUploadJob } from '@storyteller/components/src/jobs/VocoderUploadJobs';
import { TtsModelViewPage } from './tts/tts_model_view/TtsModelViewPage';
import { TtsResultDeletePage } from './tts/tts_result_delete/TtsResultDeletePage';
import { TtsResultViewPage } from './tts/tts_result_view/TtsResultViewPage';
import { ContributeIndexPage } from './contribute/ContributeIndexPage';
import { UploadTtsModelFc } from './upload/UploadTtsModelFc';
import { UploadW2lPhotoFc } from './upload/UploadW2lPhotoFc';
import { UploadW2lVideoFc } from './upload/UploadW2lVideoFc';
import { UploadTtsModelPage } from './upload/UploadTtsModelPage';
import { UploadW2lPhotoPage } from './upload/UploadW2lPhotoPage';
import { UploadW2lVideoPage } from './upload/UploadW2lVideoPage';
import { W2lResultViewFc } from './w2l/w2l_result_view/W2lResultViewFc';
import { W2lTemplateListFc } from './w2l/w2l_template_list/W2lTemplateListFc';
import { W2lTemplateUploadJob } from '@storyteller/components/src/jobs/W2lTemplateUploadJobs';
@@ -53,6 +54,7 @@ import ScrollToTop from './_common/ScrollToTop';
import { Language } from '@storyteller/components/src/i18n/Language';
import { VoiceCloneRequestPage } from './clone_voice_requests/VoiceCloneRequestPage';
import { VocodesPage } from './vocodes/VocodesPage';
import { UploadVocoderPage } from './upload/UploadVocoderPage';
interface Props {
sessionWrapper: SessionWrapper,
@@ -87,6 +89,9 @@ interface Props {
enqueueW2lTemplateUploadJob: (jobToken: string) => void,
w2lTemplateUploadJobs: Array<W2lTemplateUploadJob>,
enqueueVocoderUploadJob: (jobToken: string) => void,
vocoderUploadJobs: Array<VocoderUploadJob>,
textBuffer: string,
setTextBuffer: (textBuffer: string) => void,
clearTextBuffer: () => void,
@@ -293,7 +298,7 @@ class NewVocodesContainer extends React.Component<Props, State> {
</Route>
<Route path="/upload/w2l_photo">
<UploadW2lPhotoFc
<UploadW2lPhotoPage
sessionWrapper={this.props.sessionWrapper}
w2lTemplateUploadJobs={this.props.w2lTemplateUploadJobs}
enqueueW2lTemplateUploadJob={this.props.enqueueW2lTemplateUploadJob}
@@ -301,7 +306,7 @@ class NewVocodesContainer extends React.Component<Props, State> {
</Route>
<Route path="/upload/w2l_video">
<UploadW2lVideoFc
<UploadW2lVideoPage
sessionWrapper={this.props.sessionWrapper}
w2lTemplateUploadJobs={this.props.w2lTemplateUploadJobs}
enqueueW2lTemplateUploadJob={this.props.enqueueW2lTemplateUploadJob}
@@ -309,13 +314,21 @@ class NewVocodesContainer extends React.Component<Props, State> {
</Route>
<Route path="/upload/tts">
<UploadTtsModelFc
<UploadTtsModelPage
sessionWrapper={this.props.sessionWrapper}
ttsModelUploadJobs={this.props.ttsModelUploadJobs}
enqueueTtsModelUploadJob={this.props.enqueueTtsModelUploadJob}
/>
</Route>
<Route path="/upload/vocoder" exact={true}>
<UploadVocoderPage
sessionWrapper={this.props.sessionWrapper}
vocoderUploadJobs={this.props.vocoderUploadJobs}
enqueueVocoderUploadJob={this.props.enqueueVocoderUploadJob}
/>
</Route>
<Route path="/contribute">
<ContributeIndexPage
sessionWrapper={this.props.sessionWrapper}
@@ -0,0 +1,89 @@
import React from "react";
import { Link } from "react-router-dom";
import { JobState } from "@storyteller/components/src/jobs/JobStates";
import { motion } from "framer-motion";
import { container, sessionItem, item } from "../../../data/animation";
import { VocoderUploadJob } from "@storyteller/components/src/jobs/VocoderUploadJobs";
interface Props {
vocoderUploadJobs: Array<VocoderUploadJob>;
}
function SessionVocoderUploadResultListFc(props: Props) {
let results: Array<JSX.Element> = [];
props.vocoderUploadJobs.forEach((job) => {
if (!job.maybeDownloadedEntityToken) {
let stateDescription = "Pending...";
switch (job.jobState) {
case JobState.PENDING:
case JobState.UNKNOWN:
stateDescription =
job.maybeExtraStatusDescription == null
? "Pending..."
: job.maybeExtraStatusDescription;
break;
case JobState.STARTED:
stateDescription =
job.maybeExtraStatusDescription == null
? "Started..."
: job.maybeExtraStatusDescription;
break;
case JobState.ATTEMPT_FAILED:
stateDescription = `Failed ${job.attemptCount} attempt(s). Will retry...`;
break;
case JobState.COMPLETE_FAILURE:
case JobState.DEAD:
stateDescription =
"Failed Permanently. Please tell us in Discord so we can fix. :(";
break;
case JobState.COMPLETE_SUCCESS:
stateDescription = "Success!"; // Not sure why we're here instead of other branch!
break;
}
results.push(
<div key={job.jobToken}>
<div className="alert alert-primary">{stateDescription}</div>
</div>
);
} else {
let permalink = `/vocoder/${job.maybeDownloadedEntityToken}`;
results.push(
<div key={job.jobToken}>
<motion.div
className="panel py-4 p-3 p-lg-4 gap-4"
variants={sessionItem}
>
Complete!
<Link to={permalink} className="btn btn-primary ms-4">
See &amp; use vocoder model
</Link>
</motion.div>
</div>
);
}
});
let title = <span />;
if (results.length !== 0) {
title = (
<motion.h2 className="text-center text-lg-start fw-bold" variants={item}>
Vocoder Upload Status
</motion.h2>
);
}
return (
<motion.div initial="hidden" animate="visible" variants={container}>
<div className="container-panel pt-4 pb-5">
<div className="pb-4">{title}</div>
<div className="d-flex flex-column gap-4">{results}</div>
</div>
</motion.div>
);
}
export { SessionVocoderUploadResultListFc };
@@ -13,8 +13,8 @@ import {
faImage,
faTags,
faHandsHelping,
faChartArea,
} from "@fortawesome/free-solid-svg-icons";
import { USE_REFRESH } from "../../../Refresh";
import { motion } from "framer-motion";
import { container, item, panel } from "../../../data/animation";
@@ -31,96 +31,6 @@ function ContributeIndexPage(props: Props) {
? t("pages.contributeIndex.buttonCreateCategory")
: t("pages.contributeIndex.buttonSuggestCategory");
if (!USE_REFRESH) {
return (
<div>
<div>
<h1 className="title is-1">
{" "}
{t("pages.contributeIndex.heroTitle")}{" "}
</h1>
<h1 className="subtitle is-3">
<Trans i18nKey="pages.contributeIndex.heroSubtitle">
You make FakeYou <strong>better</strong> by contributing
</Trans>
</h1>
</div>
<br />
<div className="content is-medium">
<p>{t("pages.contributeIndex.introText")}</p>
<h3 className="title is-3">
{t("pages.contributeIndex.headingUploadModels")}
</h3>
<p>
<Trans i18nKey="pages.contributeIndex.describeUploadModels">
Create new voices and video templates for FakeYou.
<DiscordLink
text={t("pages.contributeIndex.discordLink1")}
iconAfterText={true}
/>
to learn how.
</Trans>
</p>
<Link
to="/upload/tts"
className="button is-link is-large is-fullwidth "
>
{t("pages.contributeIndex.buttonUploadVoice")}
</Link>
<br />
<Link
to="/upload/w2l_video"
className="button is-link is-large is-fullwidth "
>
{t("pages.contributeIndex.buttonUploadW2lVideo")}
</Link>
<br />
<Link
to="/upload/w2l_photo"
className="button is-link is-large is-fullwidth"
>
{t("pages.contributeIndex.buttonUploadW2lPhoto")}
</Link>
<h3 className="title is-3">{categoryHeading}</h3>
<p>{t("pages.contributeIndex.describeSuggest")}</p>
<Link
to={FrontendUrlConfig.createCategoryPage()}
className="button is-info is-large is-fullwidth"
>
{categoryButton}
</Link>
<h3 className="title is-3">
{t("pages.contributeIndex.headingMore")}
</h3>
<p>
<Trans i18nKey="pages.contributeIndex.describeMore">
Want to contribute code, design, or data science?
<DiscordLink
text={t("pages.contributeIndex.discordLink2")}
iconAfterText={true}
/>
!
</Trans>
</p>
</div>
</div>
);
}
return (
<motion.div initial="hidden" animate="visible" variants={container}>
<div className="container py-5 px-md-4 px-lg-5 px-xl-3">
@@ -161,6 +71,10 @@ function ContributeIndexPage(props: Props) {
<FontAwesomeIcon icon={faVolumeHigh} className="me-3" />
{t("pages.contributeIndex.buttonUploadVoice")}
</Link>
<Link to="/upload/vocoder" className="btn btn-primary w-100">
<FontAwesomeIcon icon={faChartArea} className="me-3" />
Upload Vocoder
</Link>
<Link to="/upload/w2l_video" className="btn btn-primary w-100">
<FontAwesomeIcon icon={faVideo} className="me-3" />
{t("pages.contributeIndex.buttonUploadW2lVideo")}
@@ -23,7 +23,7 @@ interface TtsModelUploadJobResponsePayload {
job_token?: string;
}
function UploadTtsModelFc(props: Props) {
function UploadTtsModelPage(props: Props) {
let history = useHistory();
const [downloadUrl, setDownloadUrl] = useState("");
@@ -232,4 +232,4 @@ function UploadTtsModelFc(props: Props) {
);
}
export { UploadTtsModelFc };
export { UploadTtsModelPage };
@@ -0,0 +1,182 @@
import React, { useState } from "react";
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
import { DiscordLink } from "@storyteller/components/src/elements/DiscordLink";
import { EnqueueRetrieval, EnqueueRetrievalIsOk } from "@storyteller/components/src/api/retrieval/EnqueueRetrieval";
import { useHistory } from "react-router-dom";
import { v4 as uuidv4 } from "uuid";
import { BackLink } from "../_common/BackLink";
import { Link } from "react-router-dom";
import { FrontendUrlConfig } from "../../../common/FrontendUrlConfig";
import { motion } from "framer-motion";
import { container, item, panel } from "../../../data/animation";
import { VocoderUploadJob } from "@storyteller/components/src/jobs/VocoderUploadJobs";
import { SessionVocoderUploadResultListFc } from "../_common/SessionVocoderUploadResultsListFc";
interface Props {
sessionWrapper: SessionWrapper;
enqueueVocoderUploadJob: (jobToken: string) => void;
vocoderUploadJobs: Array<VocoderUploadJob>;
}
function UploadVocoderPage(props: Props) {
let history = useHistory();
const [downloadUrl, setDownloadUrl] = useState("");
const [title, setTitle] = useState("");
const [downloadUrlInvalidReason] = useState("");
const [titleInvalidReason] = useState("");
if (!props.sessionWrapper.isLoggedIn()) {
return (
<div className="container py-5">
<div className="py-5">
<h1 className="fw-semibold text-center mb-4">
You need to create an account or log in.
</h1>
<div className="d-flex gap-3 justify-content-center">
<Link className="btn btn-secondary" to="/login">
Login
</Link>
<Link className="btn btn-primary" to="/signup">
Sign Up
</Link>
</div>
</div>
</div>
);
}
const handleDownloadUrlChange = (ev: React.FormEvent<HTMLInputElement>) => {
ev.preventDefault();
const downloadUrlValue = (ev.target as HTMLInputElement).value;
setDownloadUrl(downloadUrlValue);
return false;
};
const handleTitleChange = (ev: React.FormEvent<HTMLInputElement>) => {
ev.preventDefault();
const titleValue = (ev.target as HTMLInputElement).value;
setTitle(titleValue);
return false;
};
const handleFormSubmit = async (ev: React.FormEvent<HTMLFormElement>) => {
ev.preventDefault();
let idempotencyToken = uuidv4();
const request = {
idempotency_token: idempotencyToken,
title: title,
download_url: downloadUrl,
generic_download_type: 'hifigan',
};
const response = await EnqueueRetrieval(request);
if (EnqueueRetrievalIsOk(response)) {
props.enqueueVocoderUploadJob(response.job_token);
history.push("/");
}
return false;
};
return (
<motion.div initial="hidden" animate="visible" variants={container}>
<div className="container pt-5 pb-4 px-md-4 px-lg-5 px-xl-3">
<div className="d-flex flex-column">
<motion.h1 className="display-5 fw-bold" variants={item}>
Upload Vocoder Model
</motion.h1>
<motion.h4 variants={item}>Make your voice sound better!</motion.h4>
<motion.div className="my-3" variants={item}>
<BackLink
link={FrontendUrlConfig.contributePage()}
text="Back to contribute page"
/>
</motion.div>
</div>
</div>
<motion.div
className="container pt-3 d-flex flex-column gap-3 px-md-4 px-lg-5 px-xl-3"
variants={item}
>
<p>
If you're new to voice cloning, join our{" "}
<span>
<DiscordLink />
</span>{" "}
to get started. We have a friendly community that can help you start
creating your own voices of your favorite characters.
</p>
<p>
Once your vocoder is successfully uploaded, you'll be able to
add it to your voice models. Others will be able to use it too, and
you'll get credited.
</p>
{/* TODO TEMP (2022-03-08) <p>
Please do not upload voices that you didn't train yourself or voices of individuals
who wish to not be voice cloned. We'll post a list of banned voices soon.
</p>*/}
</motion.div>
<motion.form onSubmit={handleFormSubmit} variants={panel}>
<div className="container-panel py-5">
<div className="panel p-3 py-4 p-lg-4">
<div className="d-flex flex-column gap-4">
<div>
<label className="sub-title">
Vocoder Title, eg. "High Pitched Voices (trained on Powerpuff Girls, MLP, and various anime characters)"
</label>
<div className="form-group">
<input
className="form-control"
type="text"
placeholder="Title"
value={title}
onChange={handleTitleChange}
/>
</div>
<p className="help">{titleInvalidReason}</p>
</div>
{/*
https://drive.google.com/file/d/{TOKEN}/view?usp=sharing
*/}
<div>
<label className="sub-title">
Download URL, eg. Google Drive link
</label>
<div className="form-group">
<input
className="form-control"
type="text"
placeholder="Download URL"
value={downloadUrl}
onChange={handleDownloadUrlChange}
/>
</div>
<p className="help">{downloadUrlInvalidReason}</p>
</div>
</div>
</div>
</div>
<motion.div className="container pb-5" variants={item}>
<button className="btn btn-primary w-100">Upload</button>
</motion.div>
</motion.form>
<SessionVocoderUploadResultListFc
vocoderUploadJobs={props.vocoderUploadJobs}
/>
</motion.div>
);
}
export { UploadVocoderPage };
@@ -21,7 +21,7 @@ interface Props {
w2lTemplateUploadJobs: Array<W2lTemplateUploadJob>;
}
function UploadW2lPhotoFc(props: Props) {
function UploadW2lPhotoPage(props: Props) {
let history = useHistory();
const [downloadUrl, setDownloadUrl] = useState("");
@@ -174,4 +174,4 @@ function UploadW2lPhotoFc(props: Props) {
);
}
export { UploadW2lPhotoFc };
export { UploadW2lPhotoPage };
@@ -21,7 +21,7 @@ interface Props {
w2lTemplateUploadJobs: Array<W2lTemplateUploadJob>;
}
function UploadW2lVideoFc(props: Props) {
function UploadW2lVideoPage(props: Props) {
let history = useHistory();
const [downloadUrl, setDownloadUrl] = useState("");
@@ -173,4 +173,4 @@ function UploadW2lVideoFc(props: Props) {
);
}
export { UploadW2lVideoFc };
export { UploadW2lVideoPage };
-324
View File
@@ -11746,14 +11746,6 @@ react-scripts@5.0.0:
optionalDependencies:
fsevents "^2.3.2"
react-tsparticles@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/react-tsparticles/-/react-tsparticles-2.1.3.tgz#45e570d869c3b26760525d8dcc52ea5cc8095f0c"
integrity sha512-1Jc0Z8umkCHzY3qebs7nBLUXfeygEBAdubfWg7PjeYuYmjUHymneUXzXGiPLbqIMN+QQNM69485O3QLWks9lng==
dependencies:
fast-deep-equal "^3.1.3"
tsparticles-engine "^2.0.6"
react@^16.13.1:
version "16.14.0"
resolved "https://registry.yarnpkg.com/react/-/react-16.14.0.tgz#94d776ddd0aaa37da3eda8fc5b6b18a4c9a3114d"
@@ -13601,322 +13593,6 @@ tslib@^2.1.0, tslib@^2.3.1:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3"
integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==
tsparticles-engine@^2.0.6, tsparticles-engine@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/tsparticles-engine/-/tsparticles-engine-2.1.3.tgz#d8922cf7811c40e5e9b87b18f9cc86043af8e900"
integrity sha512-BmCo534tF5yRQofUczGSfCFrdeMYO6e7FXHv6AdRxL+j9NNPH3zF4HX6k24tbO4kmTH8tGX/+F9mugEQVo9AEg==
tsparticles-interaction-external-attract@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/tsparticles-interaction-external-attract/-/tsparticles-interaction-external-attract-2.1.3.tgz#9a6be2683d77f9ed214a0ca4d478c40bed87b2f4"
integrity sha512-r9DDDlmzA47j0hrmur/2ITip4Qwki3fM1yNgmro048CPamF5pnrhCZBG8zq8hy6BO8KASe55OU5KCAJ9Zt3T2w==
dependencies:
tsparticles-engine "^2.1.3"
tsparticles-interaction-external-bounce@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/tsparticles-interaction-external-bounce/-/tsparticles-interaction-external-bounce-2.1.3.tgz#69ba88c7148a1c5790b570f38ea36ca365341dc8"
integrity sha512-ueRHeuQhXMgWGhxFd3REK2/L4QJjhAuQxGZxIM6Tl80EmYNsCXSyQJ3RG/A839eLpGHDg5fLIytcilfu9o3XvA==
dependencies:
tsparticles-engine "^2.1.3"
tsparticles-interaction-external-bubble@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/tsparticles-interaction-external-bubble/-/tsparticles-interaction-external-bubble-2.1.3.tgz#27cc5350df4d968e0be8d1215de82cc173074272"
integrity sha512-0ov4Glx0xfANPX7Tw09vOGC4MjrMoQkA8dqaRuPl8Q2SPGIMpWUWrBK9LzaZdfgtCixSeI+D/c7sgwEU5NovJg==
dependencies:
tsparticles-engine "^2.1.3"
tsparticles-interaction-external-connect@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/tsparticles-interaction-external-connect/-/tsparticles-interaction-external-connect-2.1.3.tgz#bab9643e5e2b835defa90f54547091dd93d7efd1"
integrity sha512-dHbVmYboa8bCEjkCn6QA81Lam6h+2nEORwL/ALee1gAsgyHU+cHWZ9umrL5dhYVbW3uxIn03zwZEwO+tSbISLw==
dependencies:
tsparticles-engine "^2.1.3"
tsparticles-interaction-external-grab@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/tsparticles-interaction-external-grab/-/tsparticles-interaction-external-grab-2.1.3.tgz#2c0e788b37c104682d74fd7c01bdb9d362aa18ed"
integrity sha512-Q/wbjf24YozYJJ73r2ISEQ5ixjgBXEe2s0fAW+7r+p3D4MGTYUPLmFdapislO9AW9mRLif60k4T2cWzbXxmx+w==
dependencies:
tsparticles-engine "^2.1.3"
tsparticles-interaction-external-pause@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/tsparticles-interaction-external-pause/-/tsparticles-interaction-external-pause-2.1.3.tgz#86ad1cc120b79b2ce33845372915da26f1e9a898"
integrity sha512-M1OMqN1pkpUcPdXdZoqjSJNfXUZy1qCTu+Yuy6O2trQUL2fMmnqWYUxqvcOLVgGSuacC1PCnoypBXt0BIjKyhw==
dependencies:
tsparticles-engine "^2.1.3"
tsparticles-interaction-external-push@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/tsparticles-interaction-external-push/-/tsparticles-interaction-external-push-2.1.3.tgz#2d259b65397555ec12667ab5c06901cb0e462957"
integrity sha512-tRzQy8DgxPy3iQkDCARKbRy1OFST4f+WOvzzMEyphddbFZq5cllvT4s61sVrzH4gWO+6ryT/e9PQH1Q0sYG9Mw==
dependencies:
tsparticles-engine "^2.1.3"
tsparticles-interaction-external-remove@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/tsparticles-interaction-external-remove/-/tsparticles-interaction-external-remove-2.1.3.tgz#3e6fd4abbe4e4bca1aed49a228908d265d3e4ac0"
integrity sha512-9jWds7NcQmrptg+RcRAKYy/vTtwAMz5DbIu4W1WjJ13NmoGuL0opv58bADEaY3rCEXLDPk2L8YU2792rWkp5Rg==
dependencies:
tsparticles-engine "^2.1.3"
tsparticles-interaction-external-repulse@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/tsparticles-interaction-external-repulse/-/tsparticles-interaction-external-repulse-2.1.3.tgz#01f10b9a8aeb50af88dd388af5d4ddb02147ea41"
integrity sha512-MuZ1yCCxcL9h87oKtqgf+/m8kSFlo3L0J/hMK5dfGFKVhF3TR9kFb/4gCZngtCUUZh5ciWHpvnUwZ9Wk/SakEg==
dependencies:
tsparticles-engine "^2.1.3"
tsparticles-interaction-external-trail@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/tsparticles-interaction-external-trail/-/tsparticles-interaction-external-trail-2.1.3.tgz#1ed0e9f17b86bd0c25eb65eacf94f89b22793413"
integrity sha512-IhrCgksvwP1JvZ31U/G5Hypp2WQ4ID8foB4IbOVD/mOvemUQW3G8o/cohSDwKXB5jQFclGgQOizMK/k/0wVMAg==
dependencies:
tsparticles-engine "^2.1.3"
tsparticles-interaction-particles-attract@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/tsparticles-interaction-particles-attract/-/tsparticles-interaction-particles-attract-2.1.3.tgz#df28dfa4310ae8a81eab50203490b6d21c80c9bb"
integrity sha512-AG1HtpquRE5qTR2V3lnQ5fcCjlBa9RzxiiVeOX5dJDhvLrBA2NUPNRkGTMyl6PJZSnFnjMDKFQ/d24/A+ILWxQ==
dependencies:
tsparticles-engine "^2.1.3"
tsparticles-interaction-particles-collisions@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/tsparticles-interaction-particles-collisions/-/tsparticles-interaction-particles-collisions-2.1.3.tgz#6452c8044eb513102d97e315d6a1d5d4b6056383"
integrity sha512-V4x6EzjmkWZBuUfVvCBelBbxYx+eiHYTf18ebLE73atfaSKnMHo9UentU18W47Zw0Z5aur+4/V0hS2uUFmSq1Q==
dependencies:
tsparticles-engine "^2.1.3"
tsparticles-interaction-particles-links@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/tsparticles-interaction-particles-links/-/tsparticles-interaction-particles-links-2.1.3.tgz#f3a4573c130659f4da630ab0dc6a61aa05c09334"
integrity sha512-/gh3MAwQUfufkXcOggw0UwFJBXgs0hm0cvCNRFLx54qmzGFNjy7efkOFxl3YeqyQanqLiKYZmHdZEkNEYMnWIQ==
dependencies:
tsparticles-engine "^2.1.3"
tsparticles-move-base@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/tsparticles-move-base/-/tsparticles-move-base-2.1.3.tgz#b958970e07725340cd6ddbb213639173eb2b5c25"
integrity sha512-aq9k3682zaKxe6R8MwdldoYnizCTntUj7VbJ0kXQ2QV+F/Fk8y50okbYslxbpKASP6yIGE9wXK3m9QqH5VQAXQ==
dependencies:
tsparticles-engine "^2.1.3"
tsparticles-move-parallax@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/tsparticles-move-parallax/-/tsparticles-move-parallax-2.1.3.tgz#25b494e960aecff77f6f7def4f96a3bd345753a3"
integrity sha512-Miy/eMYTc+dO4n9wtd6PpONDpAy1Ta0+4tSY6K+QBse+Fp5bjokBSpnwDyAmIbgAcbj5XMlMp8yzCcE7ptlIhg==
dependencies:
tsparticles-engine "^2.1.3"
tsparticles-particles.js@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/tsparticles-particles.js/-/tsparticles-particles.js-2.1.3.tgz#431ed900210fca8fe40be0b52a06244307eef9f7"
integrity sha512-V1NZ3b8rUrRzaFixq9pUDw2OxAIYp6b8/CSmt/3SR7UZnC0q/lSAw9HDQzqhsCNOpFc0PzQa0ogB6sMY3eaEgA==
dependencies:
tsparticles-engine "^2.1.3"
tsparticles-plugin-absorbers@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/tsparticles-plugin-absorbers/-/tsparticles-plugin-absorbers-2.1.3.tgz#d8e79389287484bd7d030b6b6e32fb6b279b61be"
integrity sha512-MRkP05QVNYwA4rV0nWTKOc+dANJ+dzw+t55qI7Xh9nmUineRJc784fjgZHk+/NDjgcfNb6RN05BSDkLzlANy3g==
dependencies:
tsparticles-engine "^2.1.3"
tsparticles-plugin-emitters@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/tsparticles-plugin-emitters/-/tsparticles-plugin-emitters-2.1.3.tgz#dd7769049a59e050e5a35877809103020d7d2ba1"
integrity sha512-G5IB+06AQR066Ss9XeszXsPbikEJjmdal/IzIwALaslQUIlGgJQhlPYY3R1aUAgtY7Oit0O6gFDqURs3dj9d8Q==
dependencies:
tsparticles-engine "^2.1.3"
tsparticles-plugin-polygon-mask@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/tsparticles-plugin-polygon-mask/-/tsparticles-plugin-polygon-mask-2.1.3.tgz#ca3fbe7c8540ae018acb10549384daff66b1172e"
integrity sha512-D8uuPZ+MpO2nBjeoYThePty3G3MGmBNIlQE92anrfUTEEKYjTlczY+nHlORfdNBL9fFwMiMnr/FgJwD0D68BdQ==
dependencies:
tsparticles-engine "^2.1.3"
tsparticles-shape-circle@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/tsparticles-shape-circle/-/tsparticles-shape-circle-2.1.3.tgz#a771e0aa40060104cbc85a0a402eb1bc4aa5e10e"
integrity sha512-OA6C67QEB3lL+GWOMOAmINYkHDO9LO929OTtkce6YDtrvm44zND5VhgevVYhriPvkxE5/n85F13cWNo2rO8LRw==
dependencies:
tsparticles-engine "^2.1.3"
tsparticles-shape-image@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/tsparticles-shape-image/-/tsparticles-shape-image-2.1.3.tgz#418ccb8b9505a65b9b264d040384d7d07c1e5d48"
integrity sha512-aV9JFnM4FjVIB0fNSf3agkiXPySdiM1f+9JyUaKSfMxYZPoY7hj6Mva3hQJQ+2GLDQE5fTjj+/IH9qQuSm5eiA==
dependencies:
tsparticles-engine "^2.1.3"
tsparticles-shape-line@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/tsparticles-shape-line/-/tsparticles-shape-line-2.1.3.tgz#1761dc2081f9271ef4edc0c8bbeb2e896661a3a5"
integrity sha512-22A3VvZvTN5g1iql8hNIU6NLcT72WKBtOPwKEFBTom7Foo7xrOH/xvjbTVN/TDUDKD86dMnsKVn0xF2NHk634A==
dependencies:
tsparticles-engine "^2.1.3"
tsparticles-shape-polygon@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/tsparticles-shape-polygon/-/tsparticles-shape-polygon-2.1.3.tgz#a143f5a1eca6d702008ba509936fc22ae26fa07f"
integrity sha512-VEMD1K+uvPY7dq8JBYQn6zsuRlDiRyMGnxTy6XKTQhVp3Zs6zwZ/789WnepUc42QAYvKzofg8G/POsevhOWOvA==
dependencies:
tsparticles-engine "^2.1.3"
tsparticles-shape-square@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/tsparticles-shape-square/-/tsparticles-shape-square-2.1.3.tgz#f28d9c60a1189ea9ee1982968e6be276220adba7"
integrity sha512-/q7aOUM5Kz+SRhpyICAvyBRmZy1Xt9Z545GHy6tqmxNn4RmiWNoqUda1V2DmMBD+rRb5jan7cQb37HQvltdrPQ==
dependencies:
tsparticles-engine "^2.1.3"
tsparticles-shape-star@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/tsparticles-shape-star/-/tsparticles-shape-star-2.1.3.tgz#63860426121391941585e9adc698ee140651d405"
integrity sha512-aKsWgK1iGJSBOpWJ9u5JW9X0zAWz9G1jMYSScCbLEVAQ98WV7xSEYsNOSdanedOsXfH2PNssk4KKxb5Cwz8qcg==
dependencies:
tsparticles-engine "^2.1.3"
tsparticles-shape-text@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/tsparticles-shape-text/-/tsparticles-shape-text-2.1.3.tgz#aee95157b72d732acaefc2d6ed7d732c8a31833d"
integrity sha512-bsuHXHECRpl9Ty/70pEv1JHgdEjdq8GI2TrNCN7lyANuIFBka5gvmHqOIdKtuebqJb6OlNQB5E58EOiHvLObfw==
dependencies:
tsparticles-engine "^2.1.3"
tsparticles-slim@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/tsparticles-slim/-/tsparticles-slim-2.1.3.tgz#6f60191b91ca75ee4257de49b67662277b020a31"
integrity sha512-8FYaJt0oEWyKzLevi6dslFah5fIPAuxuqW25aJJxZJT0fu83HtHqgLinieFdoNS13afu+qKp+XlwYrkkczjStg==
dependencies:
tsparticles-engine "^2.1.3"
tsparticles-interaction-external-attract "^2.1.3"
tsparticles-interaction-external-bounce "^2.1.3"
tsparticles-interaction-external-bubble "^2.1.3"
tsparticles-interaction-external-connect "^2.1.3"
tsparticles-interaction-external-grab "^2.1.3"
tsparticles-interaction-external-pause "^2.1.3"
tsparticles-interaction-external-push "^2.1.3"
tsparticles-interaction-external-remove "^2.1.3"
tsparticles-interaction-external-repulse "^2.1.3"
tsparticles-interaction-particles-attract "^2.1.3"
tsparticles-interaction-particles-collisions "^2.1.3"
tsparticles-interaction-particles-links "^2.1.3"
tsparticles-move-base "^2.1.3"
tsparticles-move-parallax "^2.1.3"
tsparticles-particles.js "^2.1.3"
tsparticles-shape-circle "^2.1.3"
tsparticles-shape-image "^2.1.3"
tsparticles-shape-line "^2.1.3"
tsparticles-shape-polygon "^2.1.3"
tsparticles-shape-square "^2.1.3"
tsparticles-shape-star "^2.1.3"
tsparticles-shape-text "^2.1.3"
tsparticles-updater-angle "^2.1.3"
tsparticles-updater-color "^2.1.3"
tsparticles-updater-life "^2.1.3"
tsparticles-updater-opacity "^2.1.3"
tsparticles-updater-out-modes "^2.1.3"
tsparticles-updater-size "^2.1.3"
tsparticles-updater-stroke-color "^2.1.3"
tsparticles-updater-angle@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/tsparticles-updater-angle/-/tsparticles-updater-angle-2.1.3.tgz#2b34b9279d7e968717e5e86bd9e23d1f53223e76"
integrity sha512-9yiCS8iZYexSyLAaTOkge2WM5ZUDZv6B4lWpgoUQANNScnrDkCkPHOb7ZYq4fbHYA+GElppAjamDsg2WBT5bsg==
dependencies:
tsparticles-engine "^2.1.3"
tsparticles-updater-color@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/tsparticles-updater-color/-/tsparticles-updater-color-2.1.3.tgz#59e2973fd021d6cff0301b32e408083b48279301"
integrity sha512-gsCLmwJsPauVyExRMUu0IA9R5HbEKgZ4/wndLLFxpdz/+b8PElL+SMxywEUf4WWWMC8o/1JQxlQmuW0Q+EWwyQ==
dependencies:
tsparticles-engine "^2.1.3"
tsparticles-updater-life@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/tsparticles-updater-life/-/tsparticles-updater-life-2.1.3.tgz#8fc77c1ae0b4a995248a082c8abb5dd92393a6fe"
integrity sha512-de9kkz6PFDq8OSrhNdmD24SpUC92Oz15QCiu4Sxbg98ngUun6ejE/3SZku9CX0lXgDK8k9IcH1SrIT4RzM2JKA==
dependencies:
tsparticles-engine "^2.1.3"
tsparticles-updater-opacity@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/tsparticles-updater-opacity/-/tsparticles-updater-opacity-2.1.3.tgz#ae8734fa9320322447c81f27a6ece448168b9809"
integrity sha512-WQEFM4KyLCE87LWUn8Wxh/Wmwlg/GLXvTJkFYRGjD1AjW85CJO9DM0t3O+xjKq3hR4LaMFRHF4Fki/PhmnUF+Q==
dependencies:
tsparticles-engine "^2.1.3"
tsparticles-updater-out-modes@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/tsparticles-updater-out-modes/-/tsparticles-updater-out-modes-2.1.3.tgz#fef5eed3b6cb934fcb9e9b33f635d373d6adbbbf"
integrity sha512-u7JtAKyvKCq7gwuN+NlzVJNYwvVBMx3wPCyJNZvaK9umM51lQHJCRsNj57lJ7jSJlaWsEYspw3rPXRvaw/+dVA==
dependencies:
tsparticles-engine "^2.1.3"
tsparticles-updater-roll@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/tsparticles-updater-roll/-/tsparticles-updater-roll-2.1.3.tgz#ebd6d0f06d4ec552121277f5a5c90230190e739f"
integrity sha512-0T9wEuclcV1nfNlmJH4zDco+7xB/tBFzCYVbZ1qnfwT29J66gz7/9iBYk7iRr91uhxckHXO4VcyJCk+e0vVb1w==
dependencies:
tsparticles-engine "^2.1.3"
tsparticles-updater-size@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/tsparticles-updater-size/-/tsparticles-updater-size-2.1.3.tgz#42e2250c178ee192e89bc82385b92d4e7f4fe2eb"
integrity sha512-uPpSvi73saaytTSfEtiLdlaJU81uLhDWwMe8UoVEsIggJ5nUQzGU9/WP97iSYR10zr9Xzof1Zweksj4hWvf3mQ==
dependencies:
tsparticles-engine "^2.1.3"
tsparticles-updater-stroke-color@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/tsparticles-updater-stroke-color/-/tsparticles-updater-stroke-color-2.1.3.tgz#e53b4cf27fb130b2ae088c1544d2fd7f5871f082"
integrity sha512-TSZcbhtt92710fBzF19OqwDu6ZN0LccLHFNgmWPy1EFT5oULe9ZrH6aWqEJ7q4HCdbxZytW+7rv5YRsRL15M0Q==
dependencies:
tsparticles-engine "^2.1.3"
tsparticles-updater-tilt@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/tsparticles-updater-tilt/-/tsparticles-updater-tilt-2.1.3.tgz#e653c381568a7bb276f0fca04822727affa7cf58"
integrity sha512-avSUIc6QOp/caaWUNodCQhNwLdvuC8fhDn7JiUcq2Z95YwcJTfM1JsvD2J6UeNQL5Jj2+cxjtECCQ1JzK0Sutg==
dependencies:
tsparticles-engine "^2.1.3"
tsparticles-updater-twinkle@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/tsparticles-updater-twinkle/-/tsparticles-updater-twinkle-2.1.3.tgz#61d905276e92fe2282c781f172bdef90116a778f"
integrity sha512-89WwhJRCIKWNUdK56pq8DuXuDriO/1gfddEImTeFGmA/q1my1W3nGX8LvHIR0/d9kS6tuL6sQIGolXOLwgp5Yw==
dependencies:
tsparticles-engine "^2.1.3"
tsparticles-updater-wobble@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/tsparticles-updater-wobble/-/tsparticles-updater-wobble-2.1.3.tgz#dd2ada0b9efd4a5e481e5a35fe653f01eed9bac0"
integrity sha512-UpucA4hBIn/bLYZeJgCb/JPHmSfHIUpjbEenyHm7u1BK+PuiiNDVZ13o9ClIXKDiOK6ebgIMy8afxmPIHGmG1Q==
dependencies:
tsparticles-engine "^2.1.3"
tsparticles@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/tsparticles/-/tsparticles-2.1.3.tgz#c23dce62f69274872f9f1044769227c06f0daf2a"
integrity sha512-7fKNV+6B9CcKQbFVFre2Rx2BAkW86/1jXjZGh9B0u+l6EARZSwS2aMOYWP1lJuzvHSmupoG0cGmZfKTr7/AIZw==
dependencies:
tsparticles-engine "^2.1.3"
tsparticles-interaction-external-trail "^2.1.3"
tsparticles-plugin-absorbers "^2.1.3"
tsparticles-plugin-emitters "^2.1.3"
tsparticles-plugin-polygon-mask "^2.1.3"
tsparticles-slim "^2.1.3"
tsparticles-updater-roll "^2.1.3"
tsparticles-updater-tilt "^2.1.3"
tsparticles-updater-twinkle "^2.1.3"
tsparticles-updater-wobble "^2.1.3"
tsutils@^3.21.0:
version "3.21.0"
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"