Merge pull request #36 from storytold/echelon-rebase-bflat-fakeyou-profile

Rebasing bflat's bflat-fakeyou-profile on master
This commit is contained in:
Brandon Thomas authored and GitHub committed 2022-07-21 23:54:08 -04:00
commit 7fc91fb18f
34 files changed
+3130 -1671

No files matched your search

@@ -1,13 +1,13 @@
import React from 'react';
import React from "react";
interface Props {
size: number,
email_hash?: string,
username?: string,
size: number;
email_hash?: string;
username?: string;
}
function Gravatar(props: Props) {
const gravatarUrl = `https://www.gravatar.com/avatar/${props.email_hash}?s=${props.size}`
const gravatarUrl = `https://www.gravatar.com/avatar/${props.email_hash}?s=${props.size}`;
let altText = "gravatar";
if (props.username !== undefined) {
@@ -15,8 +15,8 @@ function Gravatar(props: Props) {
}
return (
<img alt={altText} src={gravatarUrl} />
)
<img className="rounded border border-2" alt={altText} src={gravatarUrl} />
);
}
export { Gravatar };
+1
View File
@@ -14,6 +14,7 @@
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.5.0",
"@testing-library/user-event": "^7.2.1",
"@tippyjs/react": "^4.2.6",
"@types/axios": "^0.14.0",
"@types/howler": "^2.1.2",
"@types/jest": "^24.9.1",
Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

+1 -7
View File
@@ -2,7 +2,6 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<link
rel="apple-touch-icon"
sizes="180x180"
@@ -23,11 +22,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#209cee" />
<meta
name="description"
content="FakeYou is a text to speech wonderland where all of your dreams come true."
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<meta name="description" content="FakeYou is a text to speech wonderland where all of your dreams come true." />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
@@ -71,7 +66,6 @@
dataLayer.push(arguments);
}
gtag("js", new Date());
gtag("config", "UA-32811180-4");
</script>
Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

After

Width:  |  Height:  |  Size: 91 KiB

+11 -4
View File
@@ -17,11 +17,11 @@ $headings-font-family: "Merriweather", serif;
// Old design parents all elements with class "fakeyou-old"
// Refresh design parents all elements with class "fakeyou-refresh"
//
// The reason for this is that WebPack imports and packs all SCSS files, which
// The reason for this is that WebPack imports and packs all SCSS files, which
// makes it impossible to dynamically change styles.
//
// Once the rollout is complete, this outer wrapper class can be removed.
.fakeyou-refresh {
.fakeyou-refresh {
body {
font-family: $body-font-family;
font-weight: 300; // For Fira Sans
@@ -143,14 +143,22 @@ $headings-font-family: "Merriweather", serif;
.navbar-brand {
padding-top: 0;
}
.dropdown-item {
font-size: 15px;
font-weight: 600;
padding: 0.5rem 1rem;
color: #212529;
&:hover {
color: #212529;
}
&:focus {
background-color: $white;
}
&:active {
background-color: $primary;
color: $white;
}
}
@@ -355,7 +363,6 @@ $headings-font-family: "Merriweather", serif;
height: 100%;
pointer-events: none;
}
}
@media screen and (max-height: 740px) {
@@ -18,6 +18,7 @@ const FAKEYOU_TRANSLATIONS: any = {
textToSpeech: 'Text to Speech',
upload: 'Upload',
video: 'Video',
pricing: 'Pricing'
},
topNav: {
aboutUs: 'About Us',
@@ -4,7 +4,7 @@ class FrontendUrlConfig {
// Main
static indexPage() : string {
static indexPage(): string {
return '/';
}
@@ -16,109 +16,113 @@ class FrontendUrlConfig {
// Standalone pages
static aboutUsPage() : string {
static aboutUsPage(): string {
return '/about';
}
static termsPage() : string {
static pricingPage(): string {
return '/pricing';
}
static termsPage(): string {
return '/terms';
}
static patronsPage() : string {
static patronsPage(): string {
return '/patrons';
}
static cloneRequestPage() : string {
static cloneRequestPage(): string {
return '/clone';
}
// Other domains
static developerDocs() : string {
static developerDocs(): string {
return 'https://docs.fakeyou.com/';
}
// TTS model links
static ttsModelPage(modelToken: string) : string {
static ttsModelPage(modelToken: string): string {
return `/tts/${modelToken}`;
}
static ttsModelEditPage(modelToken: string) : string {
static ttsModelEditPage(modelToken: string): string {
return `/tts/${modelToken}/edit`;
}
static ttsModelEditCategoriesPage(modelToken: string) : string {
static ttsModelEditCategoriesPage(modelToken: string): string {
return `/tts/${modelToken}/categories`;
}
static ttsModelDeletePage(modelToken: string) : string {
static ttsModelDeletePage(modelToken: string): string {
return `/tts/${modelToken}/delete`;
}
static ttsResultPage(resultToken: string) : string {
static ttsResultPage(resultToken: string): string {
return `/tts/result/${resultToken}`;
}
static ttsResultEditPage(resultToken: string) : string {
static ttsResultEditPage(resultToken: string): string {
return `/tts/result/${resultToken}/edit`;
}
static ttsResultDeletePage(resultToken: string) : string {
static ttsResultDeletePage(resultToken: string): string {
return `/tts/result/${resultToken}/delete`;
}
// W2L template links
static w2lListPage() : string {
static w2lListPage(): string {
return '/video';
}
static w2lTemplatePage(templateToken: string) : string {
static w2lTemplatePage(templateToken: string): string {
return `/w2l/${templateToken}`;
}
static w2lTemplateEditPage(templateToken: string) : string {
static w2lTemplateEditPage(templateToken: string): string {
return `/w2l/${templateToken}/edit`;
}
static w2lTemplateApprovalPage(templateToken: string) : string {
static w2lTemplateApprovalPage(templateToken: string): string {
return `/w2l/${templateToken}/approval`;
}
static w2lTemplateDeletePage(templateToken: string) : string {
static w2lTemplateDeletePage(templateToken: string): string {
return `/w2l/${templateToken}/delete`;
}
static w2lResultPage(resultToken: string) : string {
static w2lResultPage(resultToken: string): string {
return `/w2l/result/${resultToken}`;
}
static w2lResultEditPage(resultToken: string) : string {
static w2lResultEditPage(resultToken: string): string {
return `/w2l/result/${resultToken}/edit`;
}
static w2lResultDeletePage(resultToken: string) : string {
static w2lResultDeletePage(resultToken: string): string {
return `/w2l/result/${resultToken}/delete`;
}
// User links
static userProfilePage(userDisplayName: string) : string {
static userProfilePage(userDisplayName: string): string {
return `/profile/${userDisplayName}`;
}
static userProfileEditPage(userDisplayName: string) : string {
static userProfileEditPage(userDisplayName: string): string {
return `/profile/${userDisplayName}/edit`;
}
static userProfileBanPage(userDisplayName: string) : string {
static userProfileBanPage(userDisplayName: string): string {
return `/profile/${userDisplayName}/ban`;
}
// Contribute
static contributePage() : string {
static contributePage(): string {
return '/contribute';
}
@@ -128,19 +132,19 @@ class FrontendUrlConfig {
// Moderation links
static moderationMain() : string {
static moderationMain(): string {
return '/moderation';
}
static moderationTtsCategoryList() : string {
static moderationTtsCategoryList(): string {
return '/moderation/tts_category/list';
}
static moderationTtsCategoryEdit(categoryToken: string) : string {
static moderationTtsCategoryEdit(categoryToken: string): string {
return `/moderation/tts_category/edit/${categoryToken}`;
}
static moderationCategoryDeletePage(categoryToken: string) : string {
static moderationCategoryDeletePage(categoryToken: string): string {
return `/moderation/category/delete/${categoryToken}`;
}
}
+61
View File
@@ -0,0 +1,61 @@
const FAKEYOU_PRICES = {
'free': {
tier: 'free',
price: 0,
features: {
'Extended Audio': false,
'mp3': false,
'Priority Processing': false,
'Commercial Voices': false
}
},
'basic': {
tier: 'basic',
price: 5,
features: {
'Extended Audio': true,
'mp3': false,
'Priority Processing': false,
'Commercial Voices': false
}
},
'pro': {
tier: 'pro',
price: 30,
features: {
'Extended Audio': true,
'mp3': true,
'Priority Processing': true,
'Commercial Voices': true
}
}
}
const STORYTELLER_PRICES = {
'free': {
tier: 'free',
price: 0,
features: {
'Priority Processing': false,
'Stream Labs': false
}
},
basic: {
tier: 'basic',
price: 5,
features: {
'Priority Processing': false,
'Stream Labs': false
}
},
pro: {
tier: 'premium',
price: 50,
features: {
'Priority Processing': true,
"Stream Labs": true
}
}
}
export { FAKEYOU_PRICES, STORYTELLER_PRICES }
+5
View File
@@ -3,3 +3,8 @@ export const distance = "30px";
export const delay = "100";
export const delay2 = "200";
export const duration = "400";
//pricing page
export const pricing1 = "0";
export const pricing2 = "100";
export const pricing3 = "200";
+1 -1
View File
@@ -49,7 +49,7 @@
},
"move": {
"enable": true,
"speed": 1,
"speed": 2.5,
"direction": "top",
"random": true,
"straight": false,
+179 -3
View File
@@ -6,7 +6,7 @@ styles with important tags:
// Old design parents all elements with class "fakeyou-old"
// Refresh design parents all elements with class "fakeyou-refresh"
//
// The reason for this is that WebPack imports and packs all SCSS files, which
// The reason for this is that WebPack imports and packs all SCSS files, which
// makes it impossible to dynamically change styles.
//
// Once the rollout is complete, this outer wrapper class can be removed.
@@ -54,6 +54,26 @@ styles with important tags:
box-shadow: none !important;
}
thead,
tbody {
border-color: rgba($white, 0.2) !important;
}
th {
color: $white;
font-weight: 700;
vertical-align: text-top;
}
td {
color: rgba($white, 0.8);
font-weight: 400;
}
.rounded {
border-radius: 8px !important;
}
code {
color: $primary;
background-color: rgba($white, 0.06);
@@ -67,6 +87,10 @@ styles with important tags:
color: rgba($white, 0.7) !important;
}
.fw-semibold {
font-weight: 600;
}
.container {
max-width: 1080px !important;
}
@@ -85,12 +109,12 @@ styles with important tags:
.py-6 {
padding-top: 1.6rem !important;
padding-bottom: 0rem !important;
padding-bottom: 0.4rem !important;
}
.lead {
color: rgba($white, 0.8);
font-size: 18px;
font-size: 17px;
}
.text-red {
@@ -113,9 +137,11 @@ styles with important tags:
border: 2.5px solid;
border-color: $primary !important;
transition: 0.2s;
color: $white;
}
.btn-primary:hover {
background-color: lighten($primary, 5%) !important;
color: $white;
}
.btn-secondary,
@@ -170,6 +196,18 @@ styles with important tags:
h2.panel-title {
margin-top: -32px;
}
a {
color: lighten($primary, 5%);
&:hover {
color: $primary;
}
&.btn {
color: $white;
}
}
}
.login-panel {
@@ -253,6 +291,140 @@ styles with important tags:
color: rgba($white, 0.6);
}
}
.firehose-ul {
list-style-type: none;
padding: 0;
margin: 0;
}
ul.patrons-list {
columns: 1;
list-style: none;
}
.form-check-input:checked {
background-color: $primary;
border-color: $primary;
}
.form-check-input:focus {
box-shadow: none;
}
.panel-border {
box-sizing: border-box;
box-shadow: inset 0px 0px 0px 4px lighten($primary, 5%);
}
ul.pricing-list {
list-style: none;
}
ul li {
@for $i from 1 through 20 {
&:nth-of-type(#{$i}) {
animation-delay: $i * 100ms !important;
}
}
}
dl,
ol,
ul {
margin-top: 0;
margin-bottom: 0rem;
}
.content p:not(:last-child) {
margin-bottom: 0.5rem;
}
tbody tr {
@for $i from 1 through 12 {
&:nth-of-type(#{$i}) {
animation-delay: $i * 20ms !important;
}
}
}
.video-card {
border-radius: 5px;
border: 5px solid;
border-color: #fff;
padding: 1rem;
position: relative;
width: 100%;
min-height: 220px;
transition: 0.2s;
overflow: clip;
z-index: 2;
}
.video-card:hover {
cursor: pointer;
border-color: #fc6b68;
}
.video-card:hover .video-card-body {
opacity: 1;
}
.video-card:hover .video-img {
transform: scale(1.05);
transition: 0.2s;
}
.video-img {
position: absolute;
object-fit: cover;
width: 100%;
height: 100%;
top: 0;
left: 0;
pointer-events: none;
overflow: none;
transition: 0.2s;
}
.video-card-body {
position: absolute;
z-index: 2;
left: 0;
bottom: 0;
padding: 0.75rem;
width: 100%;
background: rgb(0, 0, 0);
background: linear-gradient(
180deg,
rgba(0, 0, 0, 0) 0%,
rgba(0, 0, 0, 0.5) 32%,
rgba(0, 0, 0, 1) 100%
);
opacity: 0;
transition: 0.2s;
}
.video-card-title {
font-size: 14px;
}
.video-card-text {
font-size: 12px;
color: rgba(255, 255, 255, 0.6);
}
.profile-social-icons {
font-size: 22px;
}
.w2l-ani .w2l-ani-item {
@for $i from 1 through 100 {
&:nth-of-type(#{$i}) {
animation-delay: $i * 50ms !important;
}
}
}
}
@media (min-width: 992px) {
@@ -296,5 +468,9 @@ styles with important tags:
.panel {
border-radius: 8px;
}
ul.patrons-list {
columns: 3;
}
}
}
+87 -33
View File
@@ -40,31 +40,68 @@ function NewFooterNavFc(props: Props) {
<hr />
<div className="content has-text-centered">
<p>
<Link to="/">{t('coreUi.footerNav.textToSpeech')}</Link>
<Link to="/">{t("coreUi.footerNav.textToSpeech")}</Link>
&nbsp;|
<Link to="/video">{t('coreUi.footerNav.video')}</Link>
<Link to="/video">{t("coreUi.footerNav.video")}</Link>
&nbsp;|
<Link to="/contribute">{t('coreUi.footerNav.upload')}</Link>
<Link to="/contribute">{t("coreUi.footerNav.upload")}</Link>
&nbsp;|
<Link to="/leaderboard">{t('coreUi.footerNav.leaderboard')}</Link>
<Link to="/leaderboard">{t("coreUi.footerNav.leaderboard")}</Link>
&nbsp;|
<Link to={FrontendUrlConfig.patronsPage()}>{t('coreUi.footerNav.patrons')}</Link>
<Link to={FrontendUrlConfig.patronsPage()}>
{t("coreUi.footerNav.patrons")}
</Link>
&nbsp;|
<Link to="/firehose">{t('coreUi.footerNav.feed')}</Link>
<Link to="/firehose">{t("coreUi.footerNav.feed")}</Link>
&nbsp;|
<a href={FrontendUrlConfig.developerDocs()}>{t('coreUi.footerNav.apiDocs')}</a>
<a href={FrontendUrlConfig.developerDocs()}>
{t("coreUi.footerNav.apiDocs")}
</a>
&nbsp;|
<Link to="/about">{t('coreUi.footerNav.aboutUs')}</Link>
<Link to="/about">{t("coreUi.footerNav.aboutUs")}</Link>
&nbsp;|
<Link to="/terms">{t('coreUi.footerNav.termsOfUse')}</Link>
<Link to="/terms">{t("coreUi.footerNav.termsOfUse")}</Link>
</p>
<div className="v2_social">
<a href="https://discord.gg/H72KFXm" target="_blank" rel="noopener noreferrer"><img src="/social-icons/016-discord.png" alt="Join us on Discord!" /></a>
<a href="https://twitch.tv/FakeYouLabs" target="_blank" rel="noopener noreferrer"><img src="/social-icons/094-twitch.png" alt="Twitch" /></a>
<a href="https://twitter.com/intent/follow?screen_name=FakeYouApp" target="_blank" rel="noopener noreferrer"><img src="/social-icons/096-twitter.png" alt="Twitter" /></a>
<a href="https://facebook.com/vocodes" target="_blank" rel="noopener noreferrer"><img src="/social-icons/024-facebook.png" alt="Facething" /></a>
<a href="https://www.patreon.com/FakeYou" target="_blank" rel="noopener noreferrer"><img src="/social-icons/061-patreon.png" alt="Patreon" /></a>
<a
href="https://discord.gg/H72KFXm"
target="_blank"
rel="noopener noreferrer"
>
<img
src="/social-icons/016-discord.png"
alt="Join us on Discord!"
/>
</a>
<a
href="https://twitch.tv/FakeYouLabs"
target="_blank"
rel="noopener noreferrer"
>
<img src="/social-icons/094-twitch.png" alt="Twitch" />
</a>
<a
href="https://twitter.com/intent/follow?screen_name=FakeYouApp"
target="_blank"
rel="noopener noreferrer"
>
<img src="/social-icons/096-twitter.png" alt="Twitter" />
</a>
<a
href="https://facebook.com/vocodes"
target="_blank"
rel="noopener noreferrer"
>
<img src="/social-icons/024-facebook.png" alt="Facething" />
</a>
<a
href="https://www.patreon.com/FakeYou"
target="_blank"
rel="noopener noreferrer"
>
<img src="/social-icons/061-patreon.png" alt="Patreon" />
</a>
</div>
<p>
@@ -78,7 +115,6 @@ function NewFooterNavFc(props: Props) {
</p>
{moderationLink}
</div>
</div>
);
@@ -91,24 +127,42 @@ function NewFooterNavFc(props: Props) {
</div>
<div className="d-flex flex-column container py-5 gap-4">
<div className="text-center align-items-center justify-content-center gap-3">
<Link to="/">Text to Speech</Link>
&nbsp;|&nbsp;
<Link to="/video">Video</Link>
&nbsp;|&nbsp;
<Link to="/contribute">Upload</Link>
&nbsp;|&nbsp;
<Link to="/leaderboard">Leaderboard</Link>
&nbsp;|&nbsp;
<Link to={FrontendUrlConfig.patronsPage()}>Patrons</Link>
&nbsp;|&nbsp;
<Link to="/firehose">Feed</Link>
&nbsp;|&nbsp;
<a href={FrontendUrlConfig.developerDocs()}>API Docs</a>
&nbsp;|&nbsp;
<Link to="/about">About Us</Link>
&nbsp;|&nbsp;
<Link to="/terms">Terms of Use</Link>
<div className="text-center align-items-center justify-content-center gap-2 d-flex flex-wrap">
<div>
<Link to="/">Text to Speech</Link>
</div>
&nbsp;|
<Link to="/pricing">{t('coreUi.footerNav.pricing')}</Link>
&nbsp;|
<Link to="/video">{t('coreUi.footerNav.video')}</Link>
&nbsp;|
<div>
<Link to="/contribute">Upload</Link>
</div>
&nbsp;|
<div>
<Link to="/leaderboard">Leaderboard</Link>
</div>
&nbsp;|
<div>
<Link to={FrontendUrlConfig.patronsPage()}>Patrons</Link>
</div>
&nbsp;|
<div>
<Link to="/firehose">Feed</Link>
</div>
&nbsp;|
<div>
<a href={FrontendUrlConfig.developerDocs()}>API Docs</a>
</div>
&nbsp;|
<div>
<Link to="/about">About Us</Link>
</div>
&nbsp;|
<div>
<Link to="/terms">Terms of Use</Link>
</div>
</div>
<div className="d-flex justify-content-center gap-4">
+115 -56
View File
@@ -5,7 +5,7 @@ import { MigrationTopNavSession } from "../../migration/MigrationTopNav_Session"
import { FrontendUrlConfig } from "../../common/FrontendUrlConfig";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faGavel,
faGavel,
faGrinBeamSweat,
faMicrophone,
faVolumeUp,
@@ -35,7 +35,8 @@ function NewTopNavFc(props: Props) {
myDataLink = `/profile/${username}`;
}
const [mobileHamburgerIsActive, setMobileHamburgerIsActive] = useState<boolean>(false);
const [mobileHamburgerIsActive, setMobileHamburgerIsActive] =
useState<boolean>(false);
const toggleHamburger = () => {
setMobileHamburgerIsActive(!mobileHamburgerIsActive);
@@ -47,8 +48,12 @@ function NewTopNavFc(props: Props) {
setMobileHamburgerIsActive(false);
};
const navbarClasses = mobileHamburgerIsActive ? "navbar-menu is-active" : "navbar-menu";
const navbarBurgerClasses = mobileHamburgerIsActive ? "navbar-burger is-active" : "navbar-burger";
const navbarClasses = mobileHamburgerIsActive
? "navbar-menu is-active"
: "navbar-menu";
const navbarBurgerClasses = mobileHamburgerIsActive
? "navbar-burger is-active"
: "navbar-burger";
if (!USE_REFRESH) {
return (
@@ -56,9 +61,16 @@ function NewTopNavFc(props: Props) {
<nav className="navbar is-transparent padding-bottom-1em">
<div className="navbar-brand">
<Link className="navbar-item" to="/">
<img src="/fakeyou/fakeyou-nav-logo-mascot.webp" alt="FakeYou: Cartoon and Celebrity Text to Speech" />
<img
src="/fakeyou/fakeyou-nav-logo-mascot.webp"
alt="FakeYou: Cartoon and Celebrity Text to Speech"
/>
</Link>
<div className={navbarBurgerClasses} data-target="navbarExampleTransparentExample" onClick={() => toggleHamburger()}>
<div
className={navbarBurgerClasses}
data-target="navbarExampleTransparentExample"
onClick={() => toggleHamburger()}
>
<span></span>
<span></span>
<span></span>
@@ -67,93 +79,136 @@ function NewTopNavFc(props: Props) {
<div id="navbarExampleTransparentExample" className={navbarClasses}>
<div className="navbar-start">
<Link to={FrontendUrlConfig.cloneRequestPage()}
<Link
to={FrontendUrlConfig.cloneRequestPage()}
className="navbar-item "
onClick={() => closeHamburger()}
>Clone&nbsp;My&nbsp;Voice!</Link>
>
Clone&nbsp;My&nbsp;Voice!
</Link>
{/* DROPDOWN: CREATE */}
<div className="navbar-item has-dropdown is-hoverable">
<Link to={FrontendUrlConfig.indexPage()}
<Link
to={FrontendUrlConfig.indexPage()}
className="navbar-link"
onClick={() => closeHamburger()}
>{t('coreUi.topNav.create')}</Link>
>
{t("coreUi.topNav.create")}
</Link>
<div className="navbar-dropdown is-boxed">
{/* NB: There's an "is-active" class that looks nice. */}
<Link to="/"
<Link
to="/"
className="navbar-item"
onClick={() => closeHamburger()}
>TTS</Link>
>
TTS
</Link>
<Link to="/video"
<Link
to="/video"
className="navbar-item"
onClick={() => closeHamburger()}
>{t('coreUi.topNav.video')}</Link>
>
{t("coreUi.topNav.video")}
</Link>
</div>
</div>
{/* DROPDOWN: COMMUNITY */}
<div className="navbar-item has-dropdown is-hoverable">
<Link to={myDataLink}
<Link
to={myDataLink}
className="navbar-link"
onClick={() => closeHamburger()}
>{t('coreUi.topNav.community')}</Link>
>
{t("coreUi.topNav.community")}
</Link>
<div className="navbar-dropdown is-boxed">
{/* NB: There's an "is-active" class that looks nice. */}
<Link to="/contribute"
<Link
to="/contribute"
className="navbar-item"
onClick={() => closeHamburger()}
><FontAwesomeIcon icon={faUpload} />&nbsp;&nbsp;{t('coreUi.topNav.contributeUpload')}</Link>
>
<FontAwesomeIcon icon={faUpload} />
&nbsp;&nbsp;{t("coreUi.topNav.contributeUpload")}
</Link>
<Link to="/leaderboard"
<Link
to="/leaderboard"
className="navbar-item"
onClick={() => closeHamburger()}
><FontAwesomeIcon icon={faTrophy} />&nbsp;&nbsp;{t('coreUi.topNav.leaderboard')}</Link>
>
<FontAwesomeIcon icon={faTrophy} />
&nbsp;&nbsp;{t("coreUi.topNav.leaderboard")}
</Link>
<Link to={FrontendUrlConfig.patronsPage()}
<Link
to={FrontendUrlConfig.patronsPage()}
className="navbar-item"
onClick={() => closeHamburger()}
><FontAwesomeIcon icon={faPatreon}/>&nbsp;&nbsp;{t('coreUi.topNav.patrons')}</Link>
>
<FontAwesomeIcon icon={faPatreon} />
&nbsp;&nbsp;{t("coreUi.topNav.patrons")}
</Link>
<Link to="/firehose"
<Link
to="/firehose"
className="navbar-item"
onClick={() => closeHamburger()}
><FontAwesomeIcon icon={faList} />&nbsp;&nbsp;{t('coreUi.topNav.feed')}</Link>
>
<FontAwesomeIcon icon={faList} />
&nbsp;&nbsp;{t("coreUi.topNav.feed")}
</Link>
<hr className="navbar-divider" />
<Link to={myDataLink}
<Link
to={myDataLink}
className="navbar-item"
onClick={() => closeHamburger()}
><FontAwesomeIcon icon={faUser} />&nbsp;&nbsp;{t('coreUi.topNav.myData')}</Link>
>
<FontAwesomeIcon icon={faUser} />
&nbsp;&nbsp;{t("coreUi.topNav.myData")}
</Link>
<hr className="navbar-divider" />
<Link to={FrontendUrlConfig.aboutUsPage()}
<Link
to={FrontendUrlConfig.aboutUsPage()}
className="navbar-item"
onClick={() => closeHamburger()}
><FontAwesomeIcon icon={faGrinBeamSweat} />&nbsp;&nbsp;{t('coreUi.topNav.aboutUs')}</Link>
>
<FontAwesomeIcon icon={faGrinBeamSweat} />
&nbsp;&nbsp;{t("coreUi.topNav.aboutUs")}
</Link>
<Link to={FrontendUrlConfig.termsPage()}
<Link
to={FrontendUrlConfig.termsPage()}
className="navbar-item"
onClick={() => closeHamburger()}
><FontAwesomeIcon icon={faGavel} />&nbsp;&nbsp;{t('coreUi.topNav.terms')}</Link>
>
<FontAwesomeIcon icon={faGavel} />
&nbsp;&nbsp;{t("coreUi.topNav.terms")}
</Link>
</div>
</div>
<a href={FrontendUrlConfig.developerDocs()}
<a
href={FrontendUrlConfig.developerDocs()}
className="navbar-item"
onClick={() => closeHamburger()}
>{t('coreUi.topNav.developers')}</a>
>
{t("coreUi.topNav.developers")}
</a>
</div>
<div className="navbar-end">
@@ -165,7 +220,7 @@ function NewTopNavFc(props: Props) {
enableAlpha={true}
querySessionAction={props.querySessionCallback}
closeHamburgerAction={() => closeHamburger()}
/>
/>
</p>
</div>
</div>
@@ -211,7 +266,7 @@ function NewTopNavFc(props: Props) {
<div className="container">
<Link className="navbar-brand me-5 pr-8" to="/">
<img
src="fakeyou/FakeYou-Logo.png"
src="/fakeyou/FakeYou-Logo.png"
alt="FakeYou: Cartoon and Celebrity Text to Speech"
height="34"
/>
@@ -232,9 +287,13 @@ function NewTopNavFc(props: Props) {
aria-labelledby="offcanvasNavbar2Label"
>
<div className="offcanvas-header">
<Link className="navbar-brand me-5 pr-8" to="/">
<Link
data-bs-toggle="offcanvas"
className="navbar-brand me-5 pr-8"
to="/"
>
<img
src="fakeyou/FakeYou-Logo.png"
src="/fakeyou/FakeYou-Logo.png"
alt="FakeYou: Cartoon and Celebrity Text to Speech"
height="34"
/>
@@ -249,7 +308,7 @@ function NewTopNavFc(props: Props) {
</div>
<div className="offcanvas-body">
<ul className="navbar-nav justify-content-start align-items-lg-center flex-grow-1 gap-2 gap-lg-0">
<li className="nav-item">
<li data-bs-toggle="offcanvas" className="nav-item">
<Link
to={FrontendUrlConfig.cloneRequestPage()}
className="nav-link"
@@ -276,13 +335,13 @@ function NewTopNavFc(props: Props) {
className="dropdown-menu"
aria-labelledby="offcanvasNavbarLgDropdown"
>
<li>
<li data-bs-toggle="offcanvas">
<Link className="dropdown-item" to="/">
<FontAwesomeIcon icon={faVolumeUp} className="me-2" />
TTS
</Link>
</li>
<li>
<li data-bs-toggle="offcanvas">
<Link className="dropdown-item" to="/video">
<FontAwesomeIcon icon={faVideo} className="me-2" />
Video
@@ -307,25 +366,25 @@ function NewTopNavFc(props: Props) {
className="dropdown-menu"
aria-labelledby="offcanvasNavbarLgDropdown"
>
<li>
<li data-bs-toggle="offcanvas">
<Link className="dropdown-item" to="/contribute">
<FontAwesomeIcon icon={faUpload} className="me-2" />
Contribute/Upload
</Link>
</li>
<li>
<li data-bs-toggle="offcanvas">
<Link className="dropdown-item" to="/leaderboard">
<FontAwesomeIcon icon={faTrophy} className="me-2" />
Leaderboard
</Link>
</li>
<li>
<li data-bs-toggle="offcanvas">
<Link className="dropdown-item" to="/patrons">
<FontAwesomeIcon icon={faPatreon} className="me-2" />
Patrons
</Link>
</li>
<li>
<li data-bs-toggle="offcanvas">
<Link className="dropdown-item" to="/firehose">
<FontAwesomeIcon icon={faList} className="me-2" />
Feed
@@ -334,7 +393,7 @@ function NewTopNavFc(props: Props) {
<li>
<div className="dropdown-divider"></div>
</li>
<li>
<li data-bs-toggle="offcanvas">
<Link className="dropdown-item" to="/profile">
<FontAwesomeIcon icon={faUser} className="me-2" />
My Data
@@ -343,13 +402,13 @@ function NewTopNavFc(props: Props) {
</ul>
</li>
<li className="nav-item d-lg-none">
<li data-bs-toggle="offcanvas" className="nav-item d-lg-none">
<Link className="nav-link" aria-current="page" to="/about">
About
</Link>
</li>
<li className="nav-item d-lg-none">
<li data-bs-toggle="offcanvas" className="nav-item d-lg-none">
<Link className="nav-link" aria-current="page" to="/terms">
Terms of Use
</Link>
@@ -357,11 +416,7 @@ function NewTopNavFc(props: Props) {
<li className="nav-item d-lg-none">
{/* TODO(echelon): Fix the build warnings about href not being accessible. */}
<a
className="nav-link"
aria-current="page"
href="/"
>
<a className="nav-link" aria-current="page" href="/">
Developers
</a>
</li>
@@ -385,10 +440,14 @@ function NewTopNavFc(props: Props) {
</li>
</ul>
<div className="d-grid gap-2 d-flex justify-content-start align-items-center pt-4 ps-3 pt-lg-0 ps-lg-0">
<Link className="nav-login me-3" to="/login">
<Link
data-bs-toggle="offcanvas"
className="nav-login me-3"
to="/login"
>
Login
</Link>
<Link to="/signup">
<Link data-bs-toggle="offcanvas" to="/signup">
<button type="button" className="btn btn-primary btn-lg">
Sign up
</button>
@@ -11,6 +11,7 @@ import { ProfileEditFc } from './profile/profile_edit/ProfileEditFc';
import { ProfileFc } from './profile/profile_view/ProfileFc';
import { SessionWrapper } from '@storyteller/components/src/session/SessionWrapper';
import { SignupPage } from './signup/SignupPage';
import { PricingPage } from './pricing/Pricing';
import { Switch, Route } from 'react-router-dom';
import { TermsPage } from './about/terms_page/TermsPage';
import { TtsInferenceJob, W2lInferenceJob } from '../../App';
@@ -94,9 +95,9 @@ interface Props {
allTtsCategories: TtsCategoryType[],
setAllTtsCategories: (allTtsCategories: TtsCategoryType[]) => void,
allTtsCategoriesByTokenMap: Map<string,TtsCategoryType>,
allTtsModelsByTokenMap: Map<string,TtsModelListItem>,
ttsModelsByCategoryToken: Map<string,Set<TtsModelListItem>>,
allTtsCategoriesByTokenMap: Map<string, TtsCategoryType>,
allTtsModelsByTokenMap: Map<string, TtsModelListItem>,
ttsModelsByCategoryToken: Map<string, Set<TtsModelListItem>>,
dropdownCategories: TtsCategoryType[][],
setDropdownCategories: (dropdownCategories: TtsCategoryType[][]) => void,
@@ -131,7 +132,7 @@ class NewVocodesContainer extends React.Component<Props, State> {
logoutHandler={this.logout}
sessionWrapper={this.props.sessionWrapper}
querySessionCallback={this.props.querySessionAction}
/>
/>
<ScrollToTop />
@@ -157,9 +158,9 @@ class NewVocodesContainer extends React.Component<Props, State> {
<Route path="/profile/:username/edit">
<ProfileEditFc
sessionWrapper={this.props.sessionWrapper}
/>
<ProfileEditFc
sessionWrapper={this.props.sessionWrapper}
/>
</Route>
<Route path="/profile/:username/ban">
@@ -169,14 +170,21 @@ class NewVocodesContainer extends React.Component<Props, State> {
</Route>
<Route path="/profile/:username">
<ProfileFc
sessionWrapper={this.props.sessionWrapper}
/>
<ProfileFc
sessionWrapper={this.props.sessionWrapper}
/>
</Route>
<Route path="/signup">
<SignupPage
querySessionCallback={()=>{}}
querySessionCallback={() => { }}
sessionWrapper={this.props.sessionWrapper}
/>
</Route>
<Route path="/pricing">
<PricingPage
querySessionCallback={() => { }}
sessionWrapper={this.props.sessionWrapper}
/>
</Route>
@@ -357,7 +365,7 @@ class NewVocodesContainer extends React.Component<Props, State> {
sessionWrapper={this.props.sessionWrapper}
/>
</Route>
<Route path="/moderation/category/delete/:token">
<ModerationCategoryDeletePage
sessionWrapper={this.props.sessionWrapper}
@@ -438,7 +446,7 @@ class NewVocodesContainer extends React.Component<Props, State> {
<NewFooterNavFc
sessionWrapper={this.props.sessionWrapper}
/>
/>
</div>
</div>
@@ -1,17 +1,19 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { BackButtonIcon } from '../_icons/BackButtonIcon';
import React from "react";
import { Link } from "react-router-dom";
import { BackButtonIcon } from "../_icons/BackButtonIcon";
interface Props {
link: string,
text?: string,
link: string;
text?: string;
}
function BackLink(props: Props) {
const linkText = props.text === undefined ? 'Back' : props.text;
const linkText = props.text === undefined ? "Back" : props.text;
return (
<Link to={props.link}><BackButtonIcon title={linkText} /> {linkText}</Link>
)
<Link to={props.link}>
<BackButtonIcon title={linkText} /> {linkText}
</Link>
);
}
export { BackLink };
@@ -10,160 +10,263 @@ function AboutPage(props: Props) {
if (!USE_REFRESH) {
return (
<div className="content is-medium">
<section className="hero is-small">
<div className="hero-body">
<div className="columns is-vcentered">
<div className="column is-one-third">
<div className="mascot">
<img src="/mascot/kitsune_pose3_black_2000.webp" alt="FakeYou Kitsune Mascot!" />
<img
src="/mascot/kitsune_pose3_black_2000.webp"
alt="FakeYou Kitsune Mascot!"
/>
</div>
</div>
<div className="column">
<p className="title">
A Glimpse of the Future
</p>
<p className="title">A Glimpse of the Future</p>
<p className="subtitle">
We're building <strong>FakeYou</strong> as just one component of a
broad set of production and creative tooling.
We're building <strong>FakeYou</strong> as just one component
of a broad set of production and creative tooling.
</p>
</div>
</div>
</div>
</section>
<h1 className="title is-4">About FakeYou</h1>
<p>
Your brain was already capable of imagining things spoken in other people's voices. This is
a demonstration of how far computers have caught up. One day computers will be able to
bring all of the rich and vivid imagery of your hopes and dreams to life. There's never been a
Your brain was already capable of imagining things spoken in other
people's voices. This is a demonstration of how far computers have
caught up. One day computers will be able to bring all of the rich and
vivid imagery of your hopes and dreams to life. There's never been a
better time throughout all history to be a creative than now.
</p>
<h1 className="title is-4"> Technology disclosure </h1>
<p>
<em>We'll be happy to remove any of the voices featured here for any reason.
See our <Link to="/terms">terms page</Link> for more info.</em>
<em>
We'll be happy to remove any of the voices featured here for any
reason. See our <Link to="/terms">terms page</Link> for more info.
</em>
</p>
<p>
&rdquo;Deep fakes&rdquo; are kind of like Photoshop when it first came out. They're impressive, maybe a
little bit scary, but they're about to become the new norm. People will become accustomed to the technology,
and the results will be used mostly for creative good, unlocking previously costly and unattainable high
production values for individual creators. It's our belief that the next Hollywood will be <em>you</em>.
&rdquo;Deep fakes&rdquo; are kind of like Photoshop when it first came
out. They're impressive, maybe a little bit scary, but they're about
to become the new norm. People will become accustomed to the
technology, and the results will be used mostly for creative good,
unlocking previously costly and unattainable high production values
for individual creators. It's our belief that the next Hollywood will
be <em>you</em>.
</p>
<p>
The technology to clone voices is already out in the open, and the voices here are built by a
community of contributors. We're not the only website doing this, and plenty of people are producing
these same results on their own at home, independent of our work. You can see thousands of examples
on YouTube and social media.
The technology to clone voices is already out in the open, and the
voices here are built by a community of contributors. We're not the
only website doing this, and plenty of people are producing these same
results on their own at home, independent of our work. You can see
thousands of examples on YouTube and social media.
</p>
<p>
Even if the United States chooses to ban this technology, institutions in China, Japan, Canada,
and other countries all over the world are rapidly conducting and publishing research
on this topic. People everywhere have access to this easy-to-use technology. State actors
Even if the United States chooses to ban this technology, institutions
in China, Japan, Canada, and other countries all over the world are
rapidly conducting and publishing research on this topic. People
everywhere have access to this easy-to-use technology. State actors
probably have even more sophisiticated versions available.
</p>
<p>
As an interesting point, most if not all of the voices produced by this website are actually
As an interesting point, most if not all of the voices produced by
this website are actually
<em>Linda Johnson</em> with a little bit layered on top.
</p>
<p>
<a href="https://github.com/NVIDIA/tacotron2" target="_blank" rel="noopener noreferrer">NVIDIA
makes these tools publicly available for you to make your own voice models</a>. Check it out!
<a
href="https://github.com/NVIDIA/tacotron2"
target="_blank"
rel="noopener noreferrer"
>
NVIDIA makes these tools publicly available for you to make your own
voice models
</a>
. Check it out!
</p>
<h1 className="title is-4">FakeYou was previously Vocodes</h1>
<p>FakeYou.com was previously known as <strong>https://vo.codes</strong>, but has been rebranded
to better reflect its purpose and direction. (Not many people know what a <em>vocoder</em> is
anyway, so it didn't make sense to keep the name.)</p>
<p>
FakeYou.com was previously known as <strong>https://vo.codes</strong>,
but has been rebranded to better reflect its purpose and direction.
(Not many people know what a <em>vocoder</em> is anyway, so it didn't
make sense to keep the name.)
</p>
<h1 className="title is-4">Thanks</h1>
<p>
Thanks to the following individuals (in no particular order) for help with data
gathering and annotation, Discord moderation, ML advice, etc.
Thanks to the following individuals (in no particular order) for help
with data gathering and annotation, Discord moderation, ML advice,
etc.
</p>
<p>
Vegito1089,
Shin,
Ashurath,
MakaveliGH,
Blutarch Mann,
Yahia,
Tim Squid,
Seuneramet,
Matt,
Seth,
CookiePPP,
<a href="https://twitter.com/r9y9/" target="_blank" rel="noopener noreferrer">@r9y9</a>.
Vegito1089, Shin, Ashurath, MakaveliGH, Blutarch Mann, Yahia, Tim
Squid, Seuneramet, Matt, Seth, CookiePPP,
<a
href="https://twitter.com/r9y9/"
target="_blank"
rel="noopener noreferrer"
>
@r9y9
</a>
.
</p>
<p>
The following papers, models, and resources were used:
<a href="https://github.com/NVIDIA/tacotron2" target="_blank" rel="noopener noreferrer">Tacotron2</a> (BSD-3 license),
<a href="https://github.com/jaywalnut310/glow-tts" target="_blank" rel="noopener noreferrer">glow-tts</a> (MIT license), {/* MIT */}
<a href="https://github.com/seungwonpark/melgan" target="_blank" rel="noopener noreferrer">MelGAN</a> (BSD-3 license), {/* BSD3 */}
<a href="https://arxiv.org/pdf/2005.05106.pdf" target="_blank" rel="noopener noreferrer">Multi-band MelGAN</a> (paper),
<a href="https://arxiv.org/abs/2010.05646" target="_blank" rel="noopener noreferrer">HiFi-GAN</a> (paper),
<a href="https://github.com/Rudrabha/Wav2Lip" target="_blank" rel="noopener noreferrer">Wav2Lip</a> (MIT license), {/* Non-commercial */}
<a href="http://www.speech.cs.cmu.edu/cgi-bin/cmudict" target="_blank" rel="noopener noreferrer">CMUDict</a> (BSD-2 license),
<a href="https://keithito.com/LJ-Speech-Dataset/" target="_blank" rel="noopener noreferrer">LJSpeech</a> (public domain),
<a href="https://datashare.is.ed.ac.uk/handle/10283/3443" target="_blank" rel="noopener noreferrer">VCTK</a> (CC BY 4.0).
<a
href="https://github.com/NVIDIA/tacotron2"
target="_blank"
rel="noopener noreferrer"
>
Tacotron2
</a>{" "}
(BSD-3 license),
<a
href="https://github.com/jaywalnut310/glow-tts"
target="_blank"
rel="noopener noreferrer"
>
glow-tts
</a>{" "}
(MIT license), {/* MIT */}
<a
href="https://github.com/seungwonpark/melgan"
target="_blank"
rel="noopener noreferrer"
>
MelGAN
</a>{" "}
(BSD-3 license), {/* BSD3 */}
<a
href="https://arxiv.org/pdf/2005.05106.pdf"
target="_blank"
rel="noopener noreferrer"
>
Multi-band MelGAN
</a>{" "}
(paper),
<a
href="https://arxiv.org/abs/2010.05646"
target="_blank"
rel="noopener noreferrer"
>
HiFi-GAN
</a>{" "}
(paper),
<a
href="https://github.com/Rudrabha/Wav2Lip"
target="_blank"
rel="noopener noreferrer"
>
Wav2Lip
</a>{" "}
(MIT license), {/* Non-commercial */}
<a
href="http://www.speech.cs.cmu.edu/cgi-bin/cmudict"
target="_blank"
rel="noopener noreferrer"
>
CMUDict
</a>{" "}
(BSD-2 license),
<a
href="https://keithito.com/LJ-Speech-Dataset/"
target="_blank"
rel="noopener noreferrer"
>
LJSpeech
</a>{" "}
(public domain),
<a
href="https://datashare.is.ed.ac.uk/handle/10283/3443"
target="_blank"
rel="noopener noreferrer"
>
VCTK
</a>{" "}
(CC BY 4.0).
</p>
<p>
Videos are generated by the amazing Wav2Lip system <a
href="https://doi.org/10.1145/3394171.3413532" target="_blank" rel="noopener noreferrer">(paper)</a>,
by Prajwal <a href="https://twitter.com/prajwalkr14" target="_blank" rel="noopener noreferrer">(@prajwalkr14)</a>,
K R and Mukhopadhyay, Rudrabha and Namboodiri, Vinay P. and Jawahar, C.V.
Videos are generated by the amazing Wav2Lip system{" "}
<a
href="https://doi.org/10.1145/3394171.3413532"
target="_blank"
rel="noopener noreferrer"
>
(paper)
</a>
, by Prajwal{" "}
<a
href="https://twitter.com/prajwalkr14"
target="_blank"
rel="noopener noreferrer"
>
(@prajwalkr14)
</a>
, K R and Mukhopadhyay, Rudrabha and Namboodiri, Vinay P. and Jawahar,
C.V.
</p>
<h1 className="title is-4">Contact</h1>
<p>
Reach out to "echelon" on <a href="https://twitter.com/echelon" target="_blank" rel="noopener noreferrer">Twitter</a>&nbsp;and
Hacker News. Say hi.
Reach out to "echelon" on{" "}
<a
href="https://twitter.com/echelon"
target="_blank"
rel="noopener noreferrer"
>
Twitter
</a>
&nbsp;and Hacker News. Say hi.
</p>
<p>
The Storyteller Company (registered as Learning Machines, LLC)
</p>
<p>The Storyteller Company (registered as Learning Machines, LLC)</p>
<h1 className="title is-4"> Built With </h1>
<div className="columns is-mobile is-gapless">
<div className="column">
<figure className="image is-square is-fullwidth vocodes-grid-marginless">
<img src="/logos/pytorch.png" alt="models are written in pytorch" />
<img
src="/logos/pytorch.png"
alt="models are written in pytorch"
/>
</figure>
</div>
<div className="column">
<figure className="image is-square is-fullwidth vocodes-grid-marginless">
<img src="/logos/rust.png" alt="core server components are written in Rust" />
<img
src="/logos/rust.png"
alt="core server components are written in Rust"
/>
</figure>
</div>
<div className="column">
<figure className="image is-square is-fullwidth vocodes-grid-marginless">
<img src="/logos/kubernetes.png" alt="the cluster scales with k8s" />
<img
src="/logos/kubernetes.png"
alt="the cluster scales with k8s"
/>
</figure>
</div>
</div>
</div>
);
}
@@ -426,7 +529,7 @@ function AboutPage(props: Props) {
</div>
<div>
<h2 className="mb-4">Built With</h2>
<div className="row justify-content-between">
<div className="row gx-3 gx-lg-4">
<div className="col-4 col-md-3">
<img
className="rounded img-fluid"
@@ -65,7 +65,8 @@ function VoiceCloneRequestPage(props: Props) {
const [formWasSubmitted, setFormWasSubmitted] = useState(false);
// Previous form submission state
const [formWasPrevioiuslySubmitted, setFormWasPreviouslySubmitted] = useState(false);
const [formWasPrevioiuslySubmitted, setFormWasPreviouslySubmitted] =
useState(false);
const checkPreviousApplication = useCallback(async () => {
const request: CheckVoiceCloneApplicationRequest = {};
@@ -248,25 +249,20 @@ function VoiceCloneRequestPage(props: Props) {
<>
<section className="hero is-small">
<div className="hero-body">
<div className="columns is-vcentered">
<div className="column is-one-third">
<div className="mascot">
<img src="/mascot/kitsune_pose4_black_2000.webp" alt="FakeYou's mascot!" />
<img
src="/mascot/kitsune_pose4_black_2000.webp"
alt="FakeYou's mascot!"
/>
</div>
</div>
<div className="column">
<p className="title is-1">
Professionally Clone Your Voice
</p>
<p className="subtitle is-3">
(or any voice) for just $70
</p>
<p className="title is-1">Professionally Clone Your Voice</p>
<p className="subtitle is-3">(or any voice) for just $70</p>
</div>
</div>
</div>
</section>
@@ -279,7 +275,12 @@ function VoiceCloneRequestPage(props: Props) {
<div className="row gx-3 flex-lg-row-reverse align-items-center">
<div className="col-lg-6">
<div className="d-flex justify-content-center">
<Fade right distance={distance} duration={duration} delay={delay}>
<Fade
right
distance={distance}
duration={duration}
delay={delay}
>
<img
src="/mascot/kitsune_pose4.png"
className="ani4 load-hidden img-fluid"
@@ -290,7 +291,7 @@ function VoiceCloneRequestPage(props: Props) {
</Fade>
</div>
</div>
<div className="col-lg-6 px-md-2 px-lg-5 px-xl-2">
<div className="col-lg-6 px-3 px-md-2 px-lg-5 px-xl-2">
<Fade bottom cascade distance={distance} duration={duration}>
<div className="text-center text-lg-start">
<h1 className="display-5 fw-bold lh-1">
@@ -298,7 +299,9 @@ function VoiceCloneRequestPage(props: Props) {
</h1>
<h3 className="mb-4">(or any voice) for just $70</h3>
<div className="d-flex flex-column justify-content-center">
<h3>Want a Custom Voice You Can Use?</h3>
<h3 className="px-5 px-md-0">
Want a Custom Voice You Can Use?
</h3>
<p className="lead">
For Music, Videos, Twitch Rewards, API, Friends, Family…
whatever you want!
@@ -362,7 +365,7 @@ function VoiceCloneRequestPage(props: Props) {
<>
<article className="message is-danger">
<div className="message-body">
<strong>Error with form:</strong> {formErrorMessage}
<strong className="red">Error with form:</strong> {formErrorMessage}
</div>
</article>
</>
@@ -372,37 +375,39 @@ function VoiceCloneRequestPage(props: Props) {
if (!USE_REFRESH) {
return (
<div>
{header}
<section className="section">
<div className="container">
<h1 className="title is-2">Want a Custom Voice You Can Use?</h1>
<h1 className="subtitle is-4">For Music, Videos, Twitch Rewards, API, Friends, Family&hellip; whatever you want!</h1>
<h1 className="subtitle is-4">
For Music, Videos, Twitch Rewards, API, Friends, Family&hellip;
whatever you want!
</h1>
{previouslySubmittedNote}
<p>
We have an extremely talented staff that will personally handle your voice clone request.
Please help us understand more about your voice and how you want to use it, and we'll be in touch
We have an extremely talented staff that will personally handle
your voice clone request. Please help us understand more about
your voice and how you want to use it, and we'll be in touch
shortly.
</p>
<br />
<h1 className="title is-3">First, how should we get in touch?</h1>
<div className="field">
<label className="label">Email Address</label>
<div className="control has-icons-left">
<input
className="input is-medium is-fullwidth"
type="text"
placeholder="Email Address"
value={emailAddress}
onChange={handleEmailAddressChange}
/>
<input
className="input is-medium is-fullwidth"
type="text"
placeholder="Email Address"
value={emailAddress}
onChange={handleEmailAddressChange}
/>
<span className="icon is-small is-left">
<FontAwesomeIcon icon={faEnvelope} />
</span>
@@ -410,19 +415,20 @@ function VoiceCloneRequestPage(props: Props) {
<p className="help is-success"></p>
<p className="help is-danger"></p>
</div>
<div className="field">
<label className="label">
Discord Username (Optional, but an alternate way to reach you.)
<br />
</label>
<div className="control has-icons-left">
<input className="input is-medium is-fullwidth"
type="text"
placeholder="Discord"
value={discord}
onChange={handleDiscordChange}
/>
<input
className="input is-medium is-fullwidth"
type="text"
placeholder="Discord"
value={discord}
onChange={handleDiscordChange}
/>
<span className="icon is-small is-left">
<FontAwesomeIcon icon={faDiscord} />
</span>
@@ -432,296 +438,18 @@ function VoiceCloneRequestPage(props: Props) {
</p>
<p className="help is-danger"></p>
</div>
<br />
<h1 className="title is-3">Who's voice is this?</h1>
<p>If it's your voice, you'll be able to do anything you want with it. If it's another person's voice, you may have
limits on how you can use it (eg. limits on commercialization.) That doesn't mean we can't help you. Even if you
just want your favorite character so you can make Twitter memes, that's fine!</p>
<br />
<div className="checkbox-block">
<div className="control">
<label className="radio">
<input type="radio" name="subject" value="mine" onChange={handleSubjectChange} />
&nbsp;My own voice
</label>
<br />
<label className="radio">
<input type="radio" name="subject" value="family" onChange={handleSubjectChange} />
&nbsp;A family member's voice
</label>
<br />
<label className="radio">
<input type="radio" name="subject" value="client" onChange={handleSubjectChange} />
&nbsp;A client's voice
</label>
<br />
<label className="radio">
<input type="radio" name="subject" value="3rd" onChange={handleSubjectChange} />
&nbsp;Another person's voice
</label>
</div>
</div>
<br />
<div className="field">
<label className="label">If it isn't your voice, tell us about who it is!</label>
<div className="control has-icons-left">
<input
className="input is-medium is-fullwidth"
type="text"
placeholder="Notes on the person"
value={notesOnSubject}
onChange={handleSubjectNotesChange}
/>
<span className="icon is-small is-left">
<FontAwesomeIcon icon={faUser} />
</span>
</div>
<p className="help is-success"></p>
<p className="help is-danger"></p>
</div>
<br />
<h1 className="title is-3">How will you use it?</h1>
<p>Click as many as you plan to use!</p>
<br />
<div className="checkbox-block">
<label className="checkbox">
<input type="checkbox" checked={isForMusic} onChange={handleIsForMusicChange} />
&nbsp;For Music (for creating new songs)
</label>
<br />
<label className="checkbox">
<input type="checkbox" checked={isForGames} onChange={handleIsForGamesChange} />
&nbsp;For Games (because NPCs won't talk by themselves)
</label>
<br />
<label className="checkbox">
<input type="checkbox" checked={isForTwitchTts} onChange={handleIsForTwitchTtsChange} />
&nbsp;For Twitch TTS (creating rewards for my stream, helping me engage and monetize)
</label>
<br />
<label className="checkbox">
<input type="checkbox" checked={isForApiUse} onChange={handleIsForApiUseChange} />
&nbsp;For API use (unlimited use of the FakeYou.com API for anything you want to build)
</label>
<br />
<label className="checkbox">
<input type="checkbox" checked={isForOther} onChange={handleIsForOtherChange} />
&nbsp;Other (Now we're curious!)
</label>
</div>
<br />
<div className="field">
<label className="label">Let us know more about your use (optional)</label>
<div className="control has-icons-left">
<input
className="input is-medium is-fullwidth"
type="text"
placeholder="Optional details"
value={optionalNotesOnUse}
onChange={handleOptionalNotesOnUseChange}
/>
<span className="icon is-small is-left">
<FontAwesomeIcon icon={faEnvelope} />
</span>
</div>
<p className="help is-success"></p>
<p className="help is-danger"></p>
</div>
<br />
<h1 className="title is-3">Do you want it to be private?</h1>
<p>(We'll accept multiple answers here, because maybe you want to use it multiple ways.)</p>
<br />
<div className="checkbox-block">
<label className="checkbox">
<input type="checkbox" />
&nbsp;This is for private use for just me
</label>
<br />
<label className="checkbox">
<input type="checkbox" checked={isForPrivateUse} onChange={handleIsForPrivateUseChange} />
&nbsp;This is for private use amongst a group of people
</label>
<br />
<label className="checkbox">
<input type="checkbox" checked={isForPublicUse} onChange={handleIsForPublicUseChange} />
&nbsp;This is for public use
</label>
</div>
<br />
<h1 className="title is-3">How's your audio quality?</h1>
<p>It's okay if you don't have a quality source of audio, but the results are top notch only with a good recording setup.</p>
<br />
<div className="checkbox-block">
<label className="checkbox">
<input type="checkbox" checked={hasGoodMicrophone} onChange={handleHasGoodMicrophoneChange} />
&nbsp;I have a good microphone (and I know what a condenser microphone is)
</label>
<br />
<label className="checkbox">
<input type="checkbox" checked={hasCleanAudioRecordings} onChange={handleHasCleanAudioRecordingsChange} />
&nbsp;I have really good preexisting recordings.
</label>
</div>
<br />
<h1 className="title is-3">Anything else?</h1>
<div className="field">
<label className="label">Do you have any questions for us?</label>
<div className="control">
<textarea
className="textarea"
placeholder="Optional Questions"
onChange={handleOptionalQuestionsChange}
value={optionalQuestions}
></textarea>
</div>
</div>
<div className="field">
<label className="label">Do you have any important notes or details?</label>
<div className="control">
<textarea
className="textarea"
placeholder="Optional Notes"
onChange={handleOptionalExtraCommentsChange}
value={optionalExtraComments}
></textarea>
</div>
</div>
<br />
{errorMessage}
<button
className="button is-link is-large is-fullwidth"
onClick={handleSubmit}
>
Clone my voice!&nbsp;<FontAwesomeIcon icon={faMicrophone} />
</button>
</div>
</section>
</div>
);
}
return (
<div>
{header}
<Fade bottom duration={duration} distance={distance} delay={delay2}>
<div className="container-panel pt-4 pb-5">
<div className="panel p-3 p-lg-4 load-hidden mt-5 mt-lg-0">
<h1 className="panel-title fw-bold">Clone Your Voice</h1>
<div className="py-6">
{previouslySubmittedNote}
<div className="d-flex flex-column gap-4">
<p>
We have an extremely talented staff that will personally
handle your voice clone request. Please help us understand
more about your voice and how you want to use it, and we'll be
in touch shortly.
</p>
<h2>First, how should we get in touch?</h2>
<div>
<label className="sub-title">Email Address</label>
<div className="form-group input-icon">
<span className="form-control-feedback">
<FontAwesomeIcon icon={faEnvelope} />
</span>
<input
className="form-control"
type="text"
placeholder="Email Address"
value={emailAddress}
onChange={handleEmailAddressChange}
/>
</div>
<p className="form-text"></p>
<p className="form-text red"></p>
</div>
<div>
<label className="sub-title">
Discord Username (Optional, but an alternate way to reach
you.)
<br />
</label>
<div className="form-group input-icon">
<span className="form-control-feedback">
<FontAwesomeIcon icon={faDiscord} />
</span>
<input
className="form-control"
type="text"
placeholder="Discord"
value={discord}
onChange={handleDiscordChange}
/>
</div>
<p className="form-text">
Don't forget the <em>#0000</em> part of your username!
</p>
<p className="form-text red"></p>
</div>
<div>
<h2 className="mb-3">Who's voice is this?</h2>
<p>
If it's your voice, you'll be able to do anything you want
with it. If it's another person's voice, you may have limits
on how you can use it (eg. limits on commercialization.)
That doesn't mean we can't help you. Even if you just want
your favorite character so you can make Twitter memes,
that's fine!
</p>
</div>
</div>
</div>
<p>
If it's your voice, you'll be able to do anything you want with
it. If it's another person's voice, you may have limits on how you
can use it (eg. limits on commercialization.) That doesn't mean we
can't help you. Even if you just want your favorite character so
you can make Twitter memes, that's fine!
</p>
<br />
@@ -994,6 +722,330 @@ function VoiceCloneRequestPage(props: Props) {
<FontAwesomeIcon icon={faMicrophone} />
</button>
</div>
</section>
</div>
);
}
return (
<div>
{header}
<Fade bottom duration={duration} distance={distance} delay={delay2}>
<div className="container-panel pt-4 pb-5">
<div className="panel p-3 p-lg-4 load-hidden mt-5 mt-lg-0">
<h1 className="panel-title fw-bold">Clone Your Voice</h1>
<div className="py-6">
{previouslySubmittedNote}
<div className="d-flex flex-column gap-4">
<p>
We have an extremely talented staff that will personally
handle your voice clone request. Please help us understand
more about your voice and how you want to use it, and we'll be
in touch shortly.
</p>
<h2>First, how should we get in touch?</h2>
<div>
<label className="sub-title">Email Address</label>
<div className="form-group input-icon">
<span className="form-control-feedback">
<FontAwesomeIcon icon={faEnvelope} />
</span>
<input
className="form-control"
type="text"
placeholder="Email Address"
value={emailAddress}
onChange={handleEmailAddressChange}
/>
</div>
<p className="form-text"></p>
<p className="form-text red"></p>
</div>
<div>
<label className="sub-title">
Discord Username (Optional, but an alternate way to reach
you.)
<br />
</label>
<div className="form-group input-icon">
<span className="form-control-feedback">
<FontAwesomeIcon icon={faDiscord} />
</span>
<input
className="form-control"
type="text"
placeholder="Discord"
value={discord}
onChange={handleDiscordChange}
/>
</div>
<p className="form-text">
Don't forget the <em>#0000</em> part of your username!
</p>
<p className="form-text red"></p>
</div>
<div>
<h2 className="my-3">Who's voice is this?</h2>
<p>
If it's your voice, you'll be able to do anything you want
with it. If it's another person's voice, you may have limits
on how you can use it (eg. limits on commercialization.)
That doesn't mean we can't help you. Even if you just want
your favorite character so you can make Twitter memes,
that's fine!
</p>
</div>
<div className="form-check d-flex flex-column gap-2">
<label className="form-check-label">
<input
type="radio"
className="form-check-input"
name="subject"
value="mine"
onChange={handleSubjectChange}
/>
&nbsp;My own voice
</label>
<label className="form-check-label">
<input
type="radio"
className="form-check-input"
name="subject"
value="family"
onChange={handleSubjectChange}
/>
&nbsp;A family member's voice
</label>
<label className="form-check-label">
<input
type="radio"
className="form-check-input"
name="subject"
value="client"
onChange={handleSubjectChange}
/>
&nbsp;A client's voice
</label>
<label className="form-check-label">
<input
type="radio"
className="form-check-input"
name="subject"
value="3rd"
onChange={handleSubjectChange}
/>
&nbsp;Another person's voice
</label>
</div>
<div>
<label className="sub-title">
If it isn't your voice, tell us about who it is!
</label>
<div className="form-group">
<input
className="form-control"
type="text"
placeholder="Notes on the person"
value={notesOnSubject}
onChange={handleSubjectNotesChange}
/>
</div>
<p className="form-text"></p>
<p className="form-text red"></p>
</div>
<div>
<h2 className="my-3">How will you use it?</h2>
<p>Click as many as you plan to use!</p>
</div>
<div className="form-check d-flex flex-column gap-2">
<label className="form-check-label">
<input
type="checkbox"
className="form-check-input"
checked={isForMusic}
onChange={handleIsForMusicChange}
/>
&nbsp;For Music (for creating new songs)
</label>
<label className="form-check-label">
<input
type="checkbox"
className="form-check-input"
checked={isForGames}
onChange={handleIsForGamesChange}
/>
&nbsp;For Games (because NPCs won't talk by themselves)
</label>
<label className="form-check-label">
<input
type="checkbox"
className="form-check-input"
checked={isForTwitchTts}
onChange={handleIsForTwitchTtsChange}
/>
&nbsp;For Twitch TTS (creating rewards for my stream,
helping me engage and monetize)
</label>
<label className="form-check-label">
<input
type="checkbox"
className="form-check-input"
checked={isForApiUse}
onChange={handleIsForApiUseChange}
/>
&nbsp;For API use (unlimited use of the FakeYou.com API for
anything you want to build)
</label>
<label className="form-check-label">
<input
type="checkbox"
className="form-check-input"
checked={isForOther}
onChange={handleIsForOtherChange}
/>
&nbsp;Other (Now we're curious!)
</label>
</div>
<div>
<label className="sub-title">
Let us know more about your use (optional)
</label>
<div className="form-group">
<input
type="text"
className="form-control"
placeholder="Optional details"
value={optionalNotesOnUse}
onChange={handleOptionalNotesOnUseChange}
/>
</div>
<p className="form-text"></p>
<p className="form-text red"></p>
</div>
<div>
<h2 className="my-3">Do you want it to be private?</h2>
<p>
(We'll accept multiple answers here, because maybe you want
to use it multiple ways.)
</p>
</div>
<div className="form-check d-flex flex-column gap-2">
<label className="form-check-label">
<input type="checkbox" className="form-check-input" />
&nbsp;This is for private use for just me
</label>
<label className="form-check-label">
<input
type="checkbox"
className="form-check-input"
checked={isForPrivateUse}
onChange={handleIsForPrivateUseChange}
/>
&nbsp;This is for private use amongst a group of people
</label>
<label className="form-check-label">
<input
type="checkbox"
className="form-check-input"
checked={isForPublicUse}
onChange={handleIsForPublicUseChange}
/>
&nbsp;This is for public use
</label>
</div>
<div>
<h2 className="my-3">How's your audio quality?</h2>
<p>
It's okay if you don't have a quality source of audio, but
the results are top notch only with a good recording setup.
</p>
</div>
<div className="form-check d-flex flex-column gap-2">
<label className="form-check-label">
<input
type="checkbox"
className="form-check-input"
checked={hasGoodMicrophone}
onChange={handleHasGoodMicrophoneChange}
/>
&nbsp;I have a good microphone (and I know what a condenser
microphone is)
</label>
<label className="form-check-label">
<input
type="checkbox"
className="form-check-input"
checked={hasCleanAudioRecordings}
onChange={handleHasCleanAudioRecordingsChange}
/>
&nbsp;I have really good preexisting recordings.
</label>
</div>
<h2 className="mt-3">Anything else?</h2>
<div>
<label className="sub-title">
Do you have any questions for us?
</label>
<div className="form-group">
<textarea
rows={4}
className="form-control"
placeholder="Optional Questions"
onChange={handleOptionalQuestionsChange}
value={optionalQuestions}
></textarea>
</div>
</div>
<div>
<label className="sub-title">
Do you have any important notes or details?
</label>
<div className="form-group">
<textarea
rows={4}
className="form-control"
placeholder="Optional Notes"
onChange={handleOptionalExtraCommentsChange}
value={optionalExtraComments}
></textarea>
</div>
</div>
{errorMessage}
<button
className="btn btn-primary btn-lg w-100 mt-2"
onClick={handleSubmit}
>
Clone my voice!&nbsp;
<FontAwesomeIcon icon={faMicrophone} />
</button>
</div>
</div>
</div>
</div>
</Fade>
</div>
@@ -1,94 +1,226 @@
import React from 'react';
import { SessionWrapper } from '@storyteller/components/src/session/SessionWrapper';
import React from "react";
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
import { Link } from "react-router-dom";
import { DiscordLink } from '@storyteller/components/src/elements/DiscordLink';
import { FrontendUrlConfig } from '../../../common/FrontendUrlConfig';
import { t } from 'i18next';
import { Trans } from 'react-i18next';
import { DiscordLink } from "@storyteller/components/src/elements/DiscordLink";
import { FrontendUrlConfig } from "../../../common/FrontendUrlConfig";
import { t } from "i18next";
import { Trans } from "react-i18next";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faVolumeHigh,
faVideo,
faUpload,
faImage,
faTags,
faHandsHelping,
} from "@fortawesome/free-solid-svg-icons";
import { distance, delay2, duration } from "../../../data/animation";
import { USE_REFRESH } from "../../../Refresh";
const Fade = require("react-reveal/Fade");
interface Props {
sessionWrapper: SessionWrapper,
sessionWrapper: SessionWrapper;
}
function ContributeIndexPage(props: Props) {
const categoryHeading = props.sessionWrapper.canEditCategories() ?
t('pages.contributeIndex.headingCreateCategory') :
t('pages.contributeIndex.headingSuggestCategory');
const categoryHeading = props.sessionWrapper.canEditCategories()
? t("pages.contributeIndex.headingCreateCategory")
: t("pages.contributeIndex.headingSuggestCategory");
const categoryButton = props.sessionWrapper.canEditCategories() ?
t('pages.contributeIndex.buttonCreateCategory') :
t('pages.contributeIndex.buttonSuggestCategory');
const categoryButton = props.sessionWrapper.canEditCategories()
? 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 (
<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 className="container py-5 pb-4 pb-lg-5 px-md-4 px-lg-5 px-xl-3">
<Fade bottom cascade duration={duration} distance={distance}>
<div className="d-flex flex-column">
<h1 className="display-5 fw-bold">
{t("pages.contributeIndex.heroTitle")}
</h1>
<h3 className="mb-4">
<Trans i18nKey="pages.contributeIndex.heroSubtitle">
You make FakeYou <strong>better</strong> by contributing
</Trans>
</h3>
<p className="lead">{t("pages.contributeIndex.introText")}</p>
</div>
</Fade>
</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>
<Fade
bottom
cascade
distance={distance}
delay={delay2}
duration={duration}
>
<div className="container-panel pt-5 pb-5">
<div className="panel p-3 p-lg-4 load-hidden mt-3">
<h1 className="panel-title fw-bold">
<FontAwesomeIcon icon={faUpload} className="me-3" />
{t("pages.contributeIndex.headingUploadModels")}
</h1>
<div className="py-6 d-flex flex-column gap-4">
<p className="text-center text-lg-start">
<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>
<div className="d-flex flex-column flex-lg-row gap-3">
<Link to="/upload/tts" className="btn btn-primary w-100">
<FontAwesomeIcon icon={faVolumeHigh} className="me-3" />
{t("pages.contributeIndex.buttonUploadVoice")}
</Link>
<Link to="/upload/w2l_video" className="btn btn-primary w-100">
<FontAwesomeIcon icon={faVideo} className="me-3" />
{t("pages.contributeIndex.buttonUploadW2lVideo")}
</Link>
<Link to="/upload/w2l_photo" className="btn btn-primary w-100">
<FontAwesomeIcon icon={faImage} className="me-3" />
{t("pages.contributeIndex.buttonUploadW2lPhoto")}
</Link>
</div>
</div>
</div>
</div>
<div className="container-panel pt-4 pb-4">
<div className="panel p-3 p-lg-4 load-hidden mt-lg-0">
<h2 className="panel-title fw-bold">
<FontAwesomeIcon icon={faTags} className="me-3" />
{categoryHeading}
</h2>
<div className="py-6 d-flex flex-column gap-4">
<p className="text-center text-lg-start">
{t("pages.contributeIndex.describeSuggest")}
</p>
<div className="d-flex gap-3">
<button className="btn btn-secondary w-100">
Suggest category
</button>
</div>
</div>
</div>
</div>
<div className="container-panel py-5">
<div className="panel p-3 p-lg-4 load-hidden mt-lg-0">
<h2 className="panel-title fw-bold">
<FontAwesomeIcon icon={faHandsHelping} className="me-3" />
{t("pages.contributeIndex.headingMore")}
</h2>
<div className="py-6 d-flex flex-column gap-4">
<p className="text-center text-lg-start">
<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>
</div>
</Fade>
</div>
)
);
}
export { ContributeIndexPage };
@@ -3,7 +3,22 @@ import { ApiConfig } from "@storyteller/components";
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
import { Gravatar } from "@storyteller/components/src/elements/Gravatar";
import { Link } from "react-router-dom";
import { distance, duration, delay } from "../../../data/animation";
import { USE_REFRESH } from "../../../Refresh";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faPlay,
faFlagCheckered,
faAward,
faPenToSquare,
} from "@fortawesome/free-solid-svg-icons";
import {
faTwitter,
faDiscord,
faTwitch,
} from "@fortawesome/free-brands-svg-icons";
const Fade = require("react-reveal/Fade");
interface FirehoseEventListResponsePayload {
success: boolean;
@@ -27,7 +42,9 @@ interface Props {
}
function FirehoseEventListFc(props: Props) {
const [firehoseEvents, setFirehoseEvents] = useState<Array<FirehoseEvent>>([]);
const [firehoseEvents, setFirehoseEvents] = useState<Array<FirehoseEvent>>(
[]
);
const fetchEvents = () => {
const api = new ApiConfig();
@@ -74,7 +91,7 @@ function FirehoseEventListFc(props: Props) {
let eventItems: Array<JSX.Element> = [];
firehoseEvents.forEach((event) => {
firehoseEvents.slice(0, 16).forEach((event) => {
let inner = <span />;
let userLink = <span>Anonymous user</span>;
let gravatar = <span />;
@@ -100,160 +117,176 @@ function FirehoseEventListFc(props: Props) {
case "user_sign_up":
inner = (
<span>
<FontAwesomeIcon icon={faPenToSquare} />
{gravatar}
&nbsp;
{userLink}
&nbsp; signed up for FakeYou!
&nbsp;signed up for FakeYou!
</span>
);
break;
case "user_badge_granted":
inner = (
<span>
<FontAwesomeIcon icon={faAward} />
{gravatar}
&nbsp;
{userLink}
&nbsp; got a badge!
&nbsp;got a badge!
</span>
);
break;
case "tts_model_upload_started":
inner = (
<span>
<FontAwesomeIcon icon={faPlay} className="me-3" />
{gravatar}
&nbsp;
{userLink}
&nbsp; started TTS model upload
&nbsp;started TTS model upload
</span>
);
break;
case "tts_model_upload_completed":
inner = (
<span>
<FontAwesomeIcon icon={faFlagCheckered} className="me-3" />
{gravatar}
&nbsp;
{userLink}
&nbsp; completed TTS model upload
&nbsp;completed TTS model upload
</span>
);
break;
case "tts_inference_started":
inner = (
<span>
<FontAwesomeIcon icon={faPlay} className="me-3" />
{gravatar}
&nbsp;
{userLink}
&nbsp; started TTS
&nbsp;started TTS
</span>
);
break;
case "tts_inference_completed":
inner = (
<span>
<FontAwesomeIcon icon={faFlagCheckered} className="me-3" />
{gravatar}
&nbsp;
{userLink}
&nbsp; completed TTS
&nbsp;completed TTS
</span>
);
break;
case "w2l_template_upload_started":
inner = (
<span>
<FontAwesomeIcon icon={faPlay} className="me-3" />
{gravatar}
&nbsp;
{userLink}
&nbsp; started uploading a lipsync template.
&nbsp;started uploading a lipsync template.
</span>
);
break;
case "w2l_template_upload_completed":
inner = (
<span>
<FontAwesomeIcon icon={faFlagCheckered} className="me-3" />
{gravatar}
&nbsp;
{userLink}
&nbsp; finished uploading a lipsync template.
&nbsp;finished uploading a lipsync template.
</span>
);
break;
case "w2l_inference_started":
inner = (
<span>
<FontAwesomeIcon icon={faPlay} className="me-3" />
{gravatar}
&nbsp;
{userLink}
&nbsp; started a W2L lipsync video
&nbsp;started a W2L lipsync video
</span>
);
break;
case "w2l_inference_completed":
inner = (
<span>
<FontAwesomeIcon icon={faFlagCheckered} className="me-3" />
{gravatar}
&nbsp;
{userLink}
&nbsp; completed a W2L lipsync video
&nbsp;completed a W2L lipsync video
</span>
);
break;
case "twitter_mention":
inner = (
<span>
<FontAwesomeIcon icon={faTwitter} />
{gravatar}
&nbsp;
{userLink}
&nbsp; mentioned us on twitter!
&nbsp;mentioned us on twitter!
</span>
);
break;
case "twitter_retweet":
inner = (
<span>
<FontAwesomeIcon icon={faTwitter} />
{gravatar}
&nbsp;
{userLink}
&nbsp; retweeted us!
&nbsp;retweeted us!
</span>
);
break;
case "discord_join":
inner = (
<span>
<FontAwesomeIcon icon={faDiscord} />
{gravatar}
&nbsp;
{userLink}
&nbsp; joined discord!
&nbsp;joined discord!
</span>
);
break;
case "discord_message":
inner = (
<span>
<FontAwesomeIcon icon={faDiscord} />
{gravatar}
&nbsp;
{userLink}
&nbsp; sent a discord message
&nbsp;sent a discord message
</span>
);
break;
case "twitch_subscribe":
inner = (
<span>
<FontAwesomeIcon icon={faTwitch} />
{gravatar}
&nbsp;
{userLink}
&nbsp; subscribed to us on twitch!
&nbsp;subscribed to us on twitch!
</span>
);
break;
case "twitch_follow":
inner = (
<span>
<FontAwesomeIcon icon={faTwitch} />
{gravatar}
&nbsp;
{userLink}
&nbsp; followed us on twitch!
&nbsp;followed us on twitch!
</span>
);
break;
@@ -261,20 +294,32 @@ function FirehoseEventListFc(props: Props) {
return;
}
eventItems.push(<li key={event.event_token}>{inner}</li>);
if (!USE_REFRESH) {
eventItems.push(<li key={event.event_token}>{inner}</li>);
} else {
eventItems.push(
<li className="panel p-3 p-lg-3" key={event.event_token}>
{inner}
</li>
);
}
});
if (!USE_REFRESH) {
return (
<div>
<h1 className="title is-1"> Firehose event feed </h1>
<h1 className="subtitle is-3"> The latest <em>FakeYou</em> events refreshed every few seconds</h1>
<h1 className="subtitle is-3">
{" "}
The latest <em>FakeYou</em> events refreshed every few seconds
</h1>
<div className="content is-large">
<p>As you can see, we're <em>really</em> popular. But we owe it to you, our users. Thank you!</p>
<ul>
{eventItems}
</ul>
<p>
As you can see, we're <em>really</em> popular. But we owe it to you,
our users. Thank you!
</p>
<ul>{eventItems}</ul>
</div>
</div>
);
@@ -282,21 +327,25 @@ function FirehoseEventListFc(props: Props) {
return (
<div>
<div className="container py-5">
<div className="d-flex flex-column">
<h1 className="display-5 fw-bold">Firehose Event Feed</h1>
<h4 className="mb-4">
The latest FakeYou events refreshed every few seconds.
</h4>
<p className="lead">
As you can see, we're really popular. But we owe it to you, our
users. Thank you!
</p>
</div>
<div className="container py-5 px-md-4 px-lg-5 px-xl-3">
<Fade bottom cascade duration={duration} distance={distance}>
<div className="d-flex flex-column">
<h1 className="display-5 fw-bold">Firehose Event Feed</h1>
<h4 className="mb-4">
The latest FakeYou events refreshed every few seconds.
</h4>
<p className="lead">
As you can see, we're really popular. But we owe it to you, our
users. Thank you!
</p>
</div>
</Fade>
</div>
<div className="container-panel pb-5 d-flex flex-column gap-3">
<ul>{eventItems}</ul>
<div className="container-panel pb-5">
<Fade right cascade delay={delay} duration={duration} distance="100px">
<ul className="firehose-ul d-flex flex-column gap-4">{eventItems}</ul>
</Fade>
</div>
</div>
);
@@ -1,64 +1,163 @@
import React from 'react';
import { BackLink } from '../_common/BackLink';
import { FrontendUrlConfig } from '../../../common/FrontendUrlConfig';
import { SessionWrapper } from '@storyteller/components/src/session/SessionWrapper';
import { PATRONS } from '../../../data/Patrons';
import { PatreonLink } from '@storyteller/components/src/elements/PatreonLink';
import React from "react";
import { BackLink } from "../_common/BackLink";
import { FrontendUrlConfig } from "../../../common/FrontendUrlConfig";
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
import { PATRONS } from "../../../data/Patrons";
import { PatreonLink } from "@storyteller/components/src/elements/PatreonLink";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faPatreon } from "@fortawesome/free-brands-svg-icons";
import { distance, delay, delay2, duration } from "../../../data/animation";
import { USE_REFRESH } from "../../../Refresh";
const Fade = require("react-reveal/Fade");
interface Props {
sessionWrapper: SessionWrapper,
sessionWrapper: SessionWrapper;
}
<script src="https://fonts.googleapis.com"></script>;
function PatronPage(props: Props) {
return (
<>
<section className="hero is-small">
<div className="hero-body">
if (!USE_REFRESH) {
return (
<>
<section className="hero is-small">
<div className="hero-body">
<div className="columns is-vcentered">
<div className="column is-one-third">
<div className="mascot">
<img
src="/mascot/kitsune_pose7_black_2000.webp"
alt="FakeYou's mascot!"
/>
</div>
</div>
<div className="columns is-vcentered">
<div className="column is-one-third">
<div className="mascot">
<img src="/mascot/kitsune_pose7_black_2000.webp" alt="FakeYou's mascot!" />
<div className="column">
<p className="title">Thanks to our Patrons!</p>
<p className="subtitle">Our Patrons help support our work.</p>
</div>
</div>
<div className="column">
<p className="title">
Thanks to our Patrons!
</p>
<p className="subtitle">
Our Patrons help support our work.
</p>
</div>
</div>
</section>
<div>
<div className="content">
<p>
Our Patrons help pay offset (but not completely cover) our
expensive server bills.
</p>
<ul>
{PATRONS.map((patron) => {
return (
<li>
{patron.username} &mdash; ${patron.donationTotal}
</li>
);
})}
</ul>
<p>
Patrons will get first looks at new features, get dedicated access
to Patron-only Discord channels, can ask for specific voices from
our in-house audio engineers, and more!
</p>
<p>
Please consider{" "}
<PatreonLink text="donating on Patreon" iconAfterText={true} />.
</p>
<BackLink
link={FrontendUrlConfig.indexPage()}
text="Back to main"
/>
</div>
</div>
</section>
</>
);
}
<div>
<div className="content">
<p>Our Patrons help pay offset (but not completely cover) our expensive server bills.</p>
return (
<div>
<div className="container py-5 pt-lg-0">
<div className="row">
<Fade bottom cascade duration={duration} distance={distance}>
<div className="col-12 col-lg-7 d-flex flex-column justify-content-center text-center text-lg-start px-3 px-md-2 px-lg-5 px-xl-3">
<h1 className="display-5 fw-bold">Thanks to our Patrons!</h1>
<h3 className="mb-4 px-4 px-md-0">
Our Patrons help support our work.
</h3>
<p className="lead mb-5">
Our Patrons help pay offset (but not completely cover) our
expensive server bills.
</p>
<ul>
{PATRONS.map(patron => {
return (<li>{patron.username} &mdash; ${patron.donationTotal}</li>);
})}
</ul>
<p>Patrons will get first looks at new features, get dedicated access to Patron-only
Discord channels, can ask for specific voices from our in-house audio engineers,
and more!</p>
<p>Please consider <PatreonLink text="donating on Patreon" iconAfterText={true} />.</p>
<BackLink link={FrontendUrlConfig.indexPage()} text='Back to main' />
<div>
<a
href="https://www.patreon.com/fakeyou"
target="_blank"
rel="noreferrer"
className="btn btn-primary"
>
<FontAwesomeIcon icon={faPatreon} className="me-2" />
Support us on Patreon
</a>
</div>
</div>
</Fade>
<div className="col-12 col-lg-5 d-flex flex-column align-items-center">
<Fade right distance={distance} delay={delay} duration={duration}>
<img
className="img-fluid mt-5 mw-50"
src="/mascot/kitsune_pose7.png"
alt=""
width="560"
/>
</Fade>
</div>
</div>
</div>
</>
)
<div className="container-panel pb-5">
<Fade bottom distance={distance} delay={delay2} duration={duration}>
<div className="panel p-3 p-lg-4">
<h1 className="panel-title fw-bold">Our Patrons</h1>
<div className="py-6">
<div className="row text-center">
<ul className="patrons-list col-12 col-md-4 w-100">
{PATRONS.map((patron) => {
return (
<li>
{patron.username} &mdash; ${patron.donationTotal}
</li>
);
})}
</ul>
<div className="col-12 col-md-4"></div>
<div className="col-12 col-md-4"></div>
</div>
</div>
</div>
</Fade>
</div>
<div className="container pb-5">
<Fade bottom distance={distance} delay={delay2} duration={duration}>
<div>
<p>
Patrons will get first looks at new features, get dedicated access
to Patron-only Discord channels, can ask for specific voices from
our in-house audio engineers, and more!
<br />
<br />
Please consider{" "}
<PatreonLink text="donating on Patreon" iconAfterText={true} />
</p>
</div>
</Fade>
</div>
</div>
);
}
export { PatronPage }
export { PatronPage };
@@ -0,0 +1,264 @@
import React from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link } from "react-router-dom";
import {
faCheckCircle as TRUE,
faTimesCircle as FALSE,
faCheck,
} from "@fortawesome/free-solid-svg-icons";
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
import {
FAKEYOU_PRICES as FYP,
STORYTELLER_PRICES as STP,
} from "@storyteller/fakeyou/src/data/PriceTiers";
import {
distance,
pricing1,
pricing2,
pricing3,
delay2,
duration,
} from "../../../data/animation";
const Fade = require("react-reveal/Fade");
interface Props {
sessionWrapper: SessionWrapper;
querySessionCallback: () => void;
}
function PricingPage(props: Props) {
function capitalize(word: string) {
return word.charAt(0).toUpperCase() + word.slice(1);
}
const [FY_TIERS, ST_TIERS] = [Object.keys(FYP), Object.keys(STP)];
const [FY_FEATURES, ST_FEATURES] = [
Object.keys(FYP.free.features),
Object.keys(STP.free.features),
];
let current_tiers: string[], current_features: string[], current_list: any;
const site = window.location.hostname;
switch (site) {
case "fakeyou":
[current_tiers, current_features, current_list] = [
FY_TIERS,
FY_FEATURES,
FYP,
];
break;
case "storyteller":
[current_tiers, current_features, current_list] = [
ST_TIERS,
ST_FEATURES,
STP,
];
break;
default:
[current_tiers, current_features, current_list] = [
FY_TIERS,
FY_FEATURES,
FYP,
];
break;
}
return (
<div>
<Fade bottom duration={duration} distance={distance}>
<div className="container py-5 text-center">
<h1 className="display-5 fw-bold">FakeYou Pricing</h1>
</div>
</Fade>
<div className="container mt-3">
<div className="row gy-4">
<div className="col-12 col-lg-4">
<Fade
bottom
duration={duration}
distance={distance}
delay={pricing1}
>
<div className="rounded panel p-4 h-100">
<h2 className="text-center my-2 fw-bold">Free</h2>
<p className="mb-4 text-center">Great for hobbyists</p>
<Link to="/" className="btn btn-secondary w-100 fs-5">
Create free TTS
</Link>
<h2 className="display-5 fw-bold text-center my-5">
${current_list.free.price}
<span className="fs-5 opacity-75 fw-normal"> /month</span>
</h2>
<ul className="pricing-list d-flex flex-column gap-3">
<li>
<FontAwesomeIcon icon={faCheck} className="text-red me-3" />
Unlimited TTS Generation
</li>
<li>
<FontAwesomeIcon icon={faCheck} className="text-red me-3" />
Up to 10 seconds audio
</li>
</ul>
</div>
</Fade>
</div>
<div className="col-12 col-lg-4">
<Fade
bottom
duration={duration}
distance={distance}
delay={pricing2}
>
<div className="rounded panel p-4 panel-border h-100">
<h2 className="text-center my-2 fw-bold">Basic</h2>
<p className="mb-4 text-center">Great for hobbyists</p>
<Link to="/" className="btn btn-primary w-100 fs-5">
Buy Basic
</Link>
<h2 className="display-5 fw-bold text-center my-5">
${current_list.basic.price}
<span className="fs-5 opacity-75 fw-normal"> /month</span>
</h2>
<ul className="pricing-list d-flex flex-column gap-3">
<li>
<FontAwesomeIcon icon={faCheck} className="text-red me-3" />
Unlimited TTS Generation
</li>
<li>
<FontAwesomeIcon icon={faCheck} className="text-red me-3" />
Up to 30 seconds audio
</li>
<li>
<FontAwesomeIcon icon={faCheck} className="text-red me-3" />
Priority processing
</li>
</ul>
</div>
</Fade>
</div>
<div className="col-12 col-lg-4">
<Fade
bottom
duration={duration}
distance={distance}
delay={pricing3}
>
<div className="rounded panel p-4 h-100">
<h2 className="text-center my-2 fw-bold">Pro</h2>
<p className="mb-4 text-center">Great for hobbyists</p>
<Link to="/" className="btn btn-primary w-100 fs-5">
Buy Pro
</Link>
<h2 className="display-5 fw-bold text-center my-5">
${current_list.pro.price}
<span className="fs-5 opacity-75 fw-normal"> /month</span>
</h2>
<ul className="pricing-list d-flex flex-column gap-3">
<li>
<FontAwesomeIcon icon={faCheck} className="text-red me-3" />
Unlimited TTS Generation
</li>
<li>
<FontAwesomeIcon icon={faCheck} className="text-red me-3" />
Up to 1 minute audio
</li>
<li>
<FontAwesomeIcon icon={faCheck} className="text-red me-3" />
Priority processing
</li>
<li>
<FontAwesomeIcon icon={faCheck} className="text-red me-3" />
Generate mp3 file
</li>
<li>
<FontAwesomeIcon icon={faCheck} className="text-red me-3" />
For Commercial Use
</li>
</ul>
</div>
</Fade>
</div>
</div>
</div>
<Fade bottom duration={duration} distance={distance} delay={delay2}>
<div className="container-panel mt-5 py-5">
<div className="panel p-4">
<table className="table">
<thead>
<tr>
<th className="">Feature List</th>
{current_tiers.map((e) => {
return (
<th key={e} className="">
{capitalize(e)}
</th>
);
})}
</tr>
</thead>
<tbody>
<tr>
<td className="has-text-weight-bold">Price</td>
<td>${current_list.free.price}/month</td>
<td>${current_list.basic.price}/month</td>
<td>${current_list.pro.price}/month</td>
</tr>
{current_features.map((e, i) => {
return (
<tr key={i}>
<td>{e}</td>
<td>
<FontAwesomeIcon
icon={current_list.free.features[e] ? TRUE : FALSE}
className={`fa-2x ${
current_list.free.features[e]
? "has-text-success"
: "has-text-danger"
}`}
/>
</td>
<td>
<FontAwesomeIcon
icon={current_list.basic.features[e] ? TRUE : FALSE}
className={`fa-2x ${
current_list.basic.features[e]
? "has-text-success"
: "has-text-danger"
}`}
/>
</td>
<td>
<FontAwesomeIcon
icon={current_list.pro.features[e] ? TRUE : FALSE}
className={`fa-2x ${
current_list.pro.features[e]
? "has-text-success"
: "has-text-danger"
}`}
/>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
</Fade>
</div>
);
}
export { PricingPage };
@@ -1,23 +1,30 @@
import React, { useCallback, useEffect, useState } from 'react';
import { ApiConfig } from '@storyteller/components';
import { useHistory } from 'react-router-dom';
import { SessionWrapper } from '@storyteller/components/src/session/SessionWrapper';
import { useParams } from 'react-router-dom';
import { GetUserByUsername, GetUserByUsernameIsOk, User } from '../../../api/user/GetUserByUsername';
import { BackLink } from '../../_common/BackLink';
import React, { useCallback, useEffect, useState } from "react";
import { ApiConfig } from "@storyteller/components";
import { useHistory } from "react-router-dom";
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
import { useParams } from "react-router-dom";
import {
GetUserByUsername,
GetUserByUsernameIsOk,
User,
} from "../../../api/user/GetUserByUsername";
import { BackLink } from "../../_common/BackLink";
import { distance, duration, delay, delay2 } from "../../../../data/animation";
const Fade = require("react-reveal/Fade");
interface Props {
sessionWrapper: SessionWrapper,
sessionWrapper: SessionWrapper;
}
function ProfileBanFc(props: Props) {
const { username } : { username: string }= useParams();
const { username }: { username: string } = useParams();
const userProfilePage = `/profile/${username}`;
const history = useHistory();
// From endpoint
const [userData, setUserData] = useState<User|undefined>(undefined);
const [userData, setUserData] = useState<User | undefined>(undefined);
// Form values
const [modComments, setModComments] = useState<string>("");
@@ -36,7 +43,7 @@ function ProfileBanFc(props: Props) {
getUserProfile(username);
}, [username, getUserProfile]);
const handleFormSubmit = (ev: React.FormEvent<HTMLFormElement>) : boolean => {
const handleFormSubmit = (ev: React.FormEvent<HTMLFormElement>): boolean => {
ev.preventDefault();
const api = new ApiConfig();
@@ -46,29 +53,27 @@ function ProfileBanFc(props: Props) {
username: userData?.username,
is_banned: isBanned,
mod_notes: modComments,
}
};
fetch(endpointUrl, {
method: 'POST',
method: "POST",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
Accept: "application/json",
"Content-Type": "application/json",
},
credentials: 'include',
credentials: "include",
body: JSON.stringify(request),
})
.then(res => res.json())
.then(res => {
if (res.success) {
history.push(userProfilePage);
}
})
.catch(e => {
});
.then((res) => res.json())
.then((res) => {
if (res.success) {
history.push(userProfilePage);
}
})
.catch((e) => {});
return false;
}
};
if (!userData) {
// Waiting for load.
@@ -80,8 +85,7 @@ function ProfileBanFc(props: Props) {
history.push(userProfilePage);
}
const handleModCommentsChange = (ev: React.FormEvent<HTMLInputElement>) => {
const handleModCommentsChange = (ev: React.FormEvent<HTMLInputElement>) => {
ev.preventDefault();
const textValue = (ev.target as HTMLInputElement).value;
setModComments(textValue);
@@ -98,75 +102,95 @@ function ProfileBanFc(props: Props) {
bannedState = true;
break;
}
setIsBanned(bannedState)
setIsBanned(bannedState);
};
let viewLinkUrl = `/profile/${userData?.username}`;
let isDisabled = userData === undefined;
return (
<div>
<h2 className="subtitle is-2">Profile &amp; Preferences</h2>
<BackLink link={viewLinkUrl} text="Back to profile" />
<br />
<br />
<Fade bottom cascade duration={duration} distance={distance}>
<div className="container pt-5 pb-4 px-lg-5 px-xl-3">
<h2 className="display-5 fw-bold mb-3">Profile &amp; Preferences</h2>
<div>
<BackLink link={viewLinkUrl} text="Back to profile" />
</div>
</div>
</Fade>
<form onSubmit={handleFormSubmit}>
<fieldset disabled={isDisabled}>
<Fade
bottom
cascade
duration={duration}
distance={distance}
delay={delay}
>
<div className="container-panel py-5">
<div className="panel p-3 p-lg-4">
<h2 className="panel-title fw-bold">Ban/Unban User</h2>
<div className="py-6">
<div className="d-flex flex-column gap-4">
<div>
<label className="sub-title">Is Banned?</label>
<div className="form-group">
<select
name="default_pretrained_vocoder"
onChange={handleIsBannedChange}
value={isBanned ? "true" : "false"}
className="form-select"
>
<option value="true">Banned</option>
<option value="false">Not Banned</option>
</select>
</div>
</div>
<div className="field">
<label className="label">Is Banned?</label>
<div className="control select">
<select
name="default_pretrained_vocoder"
onChange={handleIsBannedChange}
value={isBanned ? "true" : "false"}
>
<option value="true">Banned</option>
<option value="false">Not Banned</option>
</select>
<div>
<label className="sub-title">
Moderator Comments (Short)
</label>
<div className="control has-icons-left has-icons-right">
<input
onChange={handleModCommentsChange}
className="form-control"
type="text"
placeholder="Moderator Comments"
value={modComments}
/>
</div>
</div>
{/*<p className="help">{invalidReason}</p>*/}
</div>
</div>
</div>
</div>
</div>
</Fade>
<div className="field">
<label className="label">Moderator Comments (Short)</label>
<div className="control has-icons-left has-icons-right">
<input
onChange={handleModCommentsChange}
className="input"
type="text"
placeholder="Moderator Comments"
value={modComments}
/>
<Fade bottom duration={duration} distance={distance} delay={delay2}>
<div className="container">
<button className="btn btn-primary w-100">Update Ban</button>
</div>
{/*<p className="help">{invalidReason}</p>*/}
</div>
<br />
<button className="button is-danger is-large is-fullwidth">Update Ban</button>
</Fade>
</fieldset>
</form>
<br />
<div>
<p>Notes on banned users:</p>
<ul>
<li></li>
<li></li>
<li></li>
</ul>
</div>
<Fade bottom duration={duration} distance={distance} delay={delay2}>
<div className="container py-5">
<p>Notes on banned users:</p>
<ul>
<li></li>
<li></li>
<li></li>
</ul>
</div>
</Fade>
</div>
)
);
}
export { ProfileBanFc };
@@ -1,51 +1,62 @@
import React, { useEffect, useState } from 'react';
import { ApiConfig } from '@storyteller/components';
import { useHistory } from 'react-router-dom';
import { SessionWrapper } from '@storyteller/components/src/session/SessionWrapper';
import { Gravatar } from '@storyteller/components/src/elements/Gravatar';
import { useParams } from 'react-router-dom';
import { VisibleIconFc } from '../../_icons/VisibleIcon';
import { HiddenIconFc } from '../../_icons/HiddenIcon';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faDiscord, faFirefox, faGithub, faTwitch, faTwitter } from '@fortawesome/free-brands-svg-icons';
import { faDollarSign, faUser } from '@fortawesome/free-solid-svg-icons';
import { BackLink } from '../../_common/BackLink';
import React, { useEffect, useState } from "react";
import { ApiConfig } from "@storyteller/components";
import { useHistory } from "react-router-dom";
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
import { Gravatar } from "@storyteller/components/src/elements/Gravatar";
import { useParams } from "react-router-dom";
import { VisibleIconFc } from "../../_icons/VisibleIcon";
import { HiddenIconFc } from "../../_icons/HiddenIcon";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faDiscord,
faGithub,
faTwitch,
faTwitter,
} from "@fortawesome/free-brands-svg-icons";
import {
faDollarSign,
faUser,
faGlobe,
} from "@fortawesome/free-solid-svg-icons";
import { BackLink } from "../../_common/BackLink";
import { distance, duration, delay, delay2 } from "../../../../data/animation";
const Fade = require("react-reveal/Fade");
const DEFAULT_VISIBILITY = 'public';
const DEFAULT_VISIBILITY = "public";
interface Props {
sessionWrapper: SessionWrapper,
sessionWrapper: SessionWrapper;
}
interface ProfileResponsePayload {
success: boolean,
error_reason?: string,
user?: UserPayload,
success: boolean;
error_reason?: string;
user?: UserPayload;
}
interface UserPayload {
user_token: string,
username: string,
display_name: string,
email_gravatar_hash: string,
profile_markdown: string,
profile_rendered_html: string,
user_role_slug: string,
banned: boolean,
dark_mode: string,
avatar_public_bucket_hash: string,
disable_gravatar: boolean,
hide_results_preference: boolean,
website_url?: string,
discord_username?: string,
twitch_username?: string,
twitter_username?: string,
github_username?: string,
user_token: string;
username: string;
display_name: string;
email_gravatar_hash: string;
profile_markdown: string;
profile_rendered_html: string;
user_role_slug: string;
banned: boolean;
dark_mode: string;
avatar_public_bucket_hash: string;
disable_gravatar: boolean;
hide_results_preference: boolean;
website_url?: string;
discord_username?: string;
twitch_username?: string;
twitter_username?: string;
github_username?: string;
//patreon_username?: string,
cashapp_username?: string,
created_at: string,
preferred_tts_result_visibility: string,
preferred_w2l_result_visibility: string,
cashapp_username?: string;
created_at: string;
preferred_tts_result_visibility: string;
preferred_w2l_result_visibility: string;
}
function ProfileEditFc(props: Props) {
@@ -55,7 +66,7 @@ function ProfileEditFc(props: Props) {
const history = useHistory();
// From endpoint
const [userData, setUserData] = useState<UserPayload|undefined>(undefined);
const [userData, setUserData] = useState<UserPayload | undefined>(undefined);
// Form values
const [profileMarkdown, setProfileMarkdown] = useState<string>("");
@@ -66,85 +77,97 @@ function ProfileEditFc(props: Props) {
const [github, setGithub] = useState<string>("");
const [cashApp, setCashApp] = useState<string>("");
const [websiteUrl, setWebsiteUrl] = useState<string>("");
const [preferredTtsResultVisibility, setPreferredTtsResultVisibility] = useState<string>(DEFAULT_VISIBILITY);
const [preferredW2lResultVisibility, setPreferredW2lResultVisibility] = useState<string>(DEFAULT_VISIBILITY);
const [preferredTtsResultVisibility, setPreferredTtsResultVisibility] =
useState<string>(DEFAULT_VISIBILITY);
const [preferredW2lResultVisibility, setPreferredW2lResultVisibility] =
useState<string>(DEFAULT_VISIBILITY);
useEffect(() => {
const api = new ApiConfig();
const endpointUrl = api.getProfile(username);
fetch(endpointUrl, {
method: 'GET',
method: "GET",
headers: {
'Accept': 'application/json',
Accept: "application/json",
},
credentials: 'include',
credentials: "include",
})
.then(res => res.json())
.then(res => {
const profileResponse : ProfileResponsePayload = res;
.then((res) => res.json())
.then((res) => {
const profileResponse: ProfileResponsePayload = res;
if (profileResponse === undefined ||
!profileResponse.success) {
return; // Endpoint error?
}
if (profileResponse === undefined || !profileResponse.success) {
return; // Endpoint error?
}
setUserData(profileResponse.user);
setProfileMarkdown(profileResponse.user?.profile_markdown || "");
setTwitter(profileResponse.user?.twitter_username || "");
setTwitch(profileResponse.user?.twitch_username || "");
setDiscord(profileResponse.user?.discord_username || "");
setCashApp(profileResponse.user?.cashapp_username || "");
//setPatreon(profileResponse.user?.patreon_username || "");
setGithub(profileResponse.user?.github_username || "");
setWebsiteUrl(profileResponse.user?.website_url || "");
setPreferredTtsResultVisibility(profileResponse.user?.preferred_tts_result_visibility || DEFAULT_VISIBILITY);
setPreferredW2lResultVisibility(profileResponse.user?.preferred_w2l_result_visibility || DEFAULT_VISIBILITY);
})
.catch(e => {
//this.props.onSpeakErrorCallback();
});
setUserData(profileResponse.user);
setProfileMarkdown(profileResponse.user?.profile_markdown || "");
setTwitter(profileResponse.user?.twitter_username || "");
setTwitch(profileResponse.user?.twitch_username || "");
setDiscord(profileResponse.user?.discord_username || "");
setCashApp(profileResponse.user?.cashapp_username || "");
//setPatreon(profileResponse.user?.patreon_username || "");
setGithub(profileResponse.user?.github_username || "");
setWebsiteUrl(profileResponse.user?.website_url || "");
setPreferredTtsResultVisibility(
profileResponse.user?.preferred_tts_result_visibility ||
DEFAULT_VISIBILITY
);
setPreferredW2lResultVisibility(
profileResponse.user?.preferred_w2l_result_visibility ||
DEFAULT_VISIBILITY
);
})
.catch((e) => {
//this.props.onSpeakErrorCallback();
});
}, [username]); // NB: Empty array dependency sets to run ONLY on mount
const handleProfileMarkdownChange = (ev: React.FormEvent<HTMLTextAreaElement>) => {
setProfileMarkdown((ev.target as HTMLTextAreaElement).value)
const handleProfileMarkdownChange = (
ev: React.FormEvent<HTMLTextAreaElement>
) => {
setProfileMarkdown((ev.target as HTMLTextAreaElement).value);
};
const handleTwitterChange = (ev: React.FormEvent<HTMLInputElement>) => {
setTwitter((ev.target as HTMLInputElement).value)
setTwitter((ev.target as HTMLInputElement).value);
};
const handleTwitchChange = (ev: React.FormEvent<HTMLInputElement>) => {
setTwitch((ev.target as HTMLInputElement).value)
setTwitch((ev.target as HTMLInputElement).value);
};
const handleGithubChange = (ev: React.FormEvent<HTMLInputElement>) => {
setGithub((ev.target as HTMLInputElement).value)
setGithub((ev.target as HTMLInputElement).value);
};
const handleDiscordChange = (ev: React.FormEvent<HTMLInputElement>) => {
setDiscord((ev.target as HTMLInputElement).value)
setDiscord((ev.target as HTMLInputElement).value);
};
const handleCashAppChange = (ev: React.FormEvent<HTMLInputElement>) => {
setCashApp((ev.target as HTMLInputElement).value)
setCashApp((ev.target as HTMLInputElement).value);
};
const handleWebsiteUrlChange = (ev: React.FormEvent<HTMLInputElement>) => {
setWebsiteUrl((ev.target as HTMLInputElement).value)
setWebsiteUrl((ev.target as HTMLInputElement).value);
};
const handlePreferredTtsResultVisibilityChange = (ev: React.FormEvent<HTMLSelectElement>) => {
setPreferredTtsResultVisibility((ev.target as HTMLSelectElement).value)
const handlePreferredTtsResultVisibilityChange = (
ev: React.FormEvent<HTMLSelectElement>
) => {
setPreferredTtsResultVisibility((ev.target as HTMLSelectElement).value);
};
const handlePreferredW2lResultVisibilityChange = (ev: React.FormEvent<HTMLSelectElement>) => {
setPreferredW2lResultVisibility((ev.target as HTMLSelectElement).value)
const handlePreferredW2lResultVisibilityChange = (
ev: React.FormEvent<HTMLSelectElement>
) => {
setPreferredW2lResultVisibility((ev.target as HTMLSelectElement).value);
};
const handleFormSubmit = (ev: React.FormEvent<HTMLFormElement>) : boolean => {
const handleFormSubmit = (ev: React.FormEvent<HTMLFormElement>): boolean => {
ev.preventDefault();
const api = new ApiConfig();
@@ -161,29 +184,27 @@ function ProfileEditFc(props: Props) {
website_url: websiteUrl,
preferred_tts_result_visibility: preferredTtsResultVisibility,
preferred_w2l_result_visibility: preferredW2lResultVisibility,
}
};
fetch(endpointUrl, {
method: 'POST',
method: "POST",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
Accept: "application/json",
"Content-Type": "application/json",
},
credentials: 'include',
credentials: "include",
body: JSON.stringify(request),
})
.then(res => res.json())
.then(res => {
if (res.success) {
history.push(userProfilePage);
}
})
.catch(e => {
});
.then((res) => res.json())
.then((res) => {
if (res.success) {
history.push(userProfilePage);
}
})
.catch((e) => {});
return false;
}
};
if (!userData) {
// Waiting for load.
@@ -199,112 +220,142 @@ function ProfileEditFc(props: Props) {
let isDisabled = userData === undefined;
const ttsVisibilityIcon = (preferredTtsResultVisibility === 'public') ? <VisibleIconFc /> : <HiddenIconFc />;
const w2lVisibilityIcon = (preferredW2lResultVisibility === 'public') ? <VisibleIconFc /> : <HiddenIconFc />;
const ttsVisibilityIcon =
preferredTtsResultVisibility === "public" ? (
<VisibleIconFc />
) : (
<HiddenIconFc />
);
const w2lVisibilityIcon =
preferredW2lResultVisibility === "public" ? (
<VisibleIconFc />
) : (
<HiddenIconFc />
);
return (
<div>
<h2 className="subtitle is-2">Profile &amp; Preferences</h2>
<BackLink link={viewLinkUrl} text="Back to profile" />
<br />
<br />
<Fade bottom cascade duration={duration} distance={distance}>
<div className="container pt-5 pb-4 px-lg-5 px-xl-3">
<h1 className="display-5 fw-bold mb-3">Profile &amp; Preferences</h1>
<div>
<BackLink link={viewLinkUrl} text="Back to profile" />
</div>
</div>
</Fade>
<form onSubmit={handleFormSubmit}>
<fieldset disabled={isDisabled}>
<Fade
bottom
cascade
delay={delay}
duration={duration}
distance={distance}
>
<div className="container-panel py-5">
<div className="panel p-3 p-lg-4">
<h2 className="panel-title fw-bold">Preferences</h2>
<div className="py-6">
<div className="d-flex flex-column gap-4">
<p>Control how the site functions.</p>
<div>
<label className="sub-title">
Audio Result Privacy&nbsp;{ttsVisibilityIcon}
</label>
<div className="form-group">
<select
name="preferred_tts_result_visibility"
onChange={handlePreferredTtsResultVisibilityChange}
value={preferredTtsResultVisibility}
className="form-select"
>
<option value="public">
Public (visible from your profile)
</option>
<option value="hidden">
Unlisted (shareable URLs)
</option>
</select>
</div>
</div>
<h4 className="subtitle is-4">Preferences</h4>
<div className="content">
<p>
Control how the site functions.
</p>
</div>
<div className="field">
<label className="label">
Audio Result Privacy&nbsp;{ttsVisibilityIcon}
</label>
<div className="control select">
<select
name="preferred_tts_result_visibility"
onChange={handlePreferredTtsResultVisibilityChange}
value={preferredTtsResultVisibility}
>
<option value="public">Public (visible from your profile)</option>
<option value="hidden">Unlisted (shareable URLs)</option>
</select>
</div>
</div>
<div className="field">
<label className="label">
Video Result Privacy&nbsp;{w2lVisibilityIcon}
</label>
<div className="control select">
<select
name="preferred_w2l_result_visibility"
onChange={handlePreferredW2lResultVisibilityChange}
value={preferredW2lResultVisibility}
>
<option value="public">Public (visible from your profile)</option>
<option value="hidden">Unlisted (shareable URLs)</option>
</select>
</div>
</div>
<br />
<h4 className="subtitle is-4">Profile Picture</h4>
<div className="card">
<div className="card-content">
<div className="media">
<div className="media-left">
<figure className="image is-48x48">
<Gravatar size={48} email_hash={userData.email_gravatar_hash} />
</figure>
</div>
<div className="media-content">
<p className="title is-4">{userData.username}</p>
<p className="subtitle is-6">@{userData.username}</p>
<div>
<label className="sub-title">
Video Result Privacy&nbsp;{w2lVisibilityIcon}
</label>
<div className="control select">
<select
name="preferred_w2l_result_visibility"
onChange={handlePreferredW2lResultVisibilityChange}
value={preferredW2lResultVisibility}
className="form-select"
>
<option value="public">
Public (visible from your profile)
</option>
<option value="hidden">
Unlisted (shareable URLs)
</option>
</select>
</div>
</div>
</div>
</div>
</div>
</div>
<div className="content">
You can set your profile picture on <a href="https://gravatar.com">gravatar.com</a>.
Use the same email address you did to sign up for FakeYou. (In the future, we'll support
image uploads.)
<br />
<div className="container-panel pt-3 pb-5">
<div className="panel p-3 p-lg-4">
<h2 className="panel-title fw-bold">Profile Picture</h2>
<div className="py-6">
<div className="d-flex align-items-center gap-3">
<Gravatar
size={48}
email_hash={userData.email_gravatar_hash}
/>
<div>
<h4 className="mb-0">{userData.username}</h4>
<p>@{userData.username}</p>
</div>
</div>
<p className="mt-4">
You can set your profile picture on{" "}
<a href="https://gravatar.com">gravatar.com</a>. Use the
same email address you did to sign up for FakeYou. (In the
future, we'll support image uploads.)
</p>
</div>
</div>
</div>
</div>
</div>
<br />
<div className="container-panel pt-3 pb-5">
<div className="panel p-3 p-lg-4">
<h2 className="panel-title fw-bold">Profile Details</h2>
<div className="py-6">
<div className="d-flex flex-column gap-4">
<div>
<label className="sub-title">
<FontAwesomeIcon icon={faUser} className="me-2" />
&nbsp;Bio or whatever (supports Markdown)
</label>
<div className="form-group">
<textarea
onChange={handleProfileMarkdownChange}
className="form-control"
placeholder="Profile (about you)"
value={profileMarkdown}
rows={5}
/>
</div>
</div>
<h4 className="subtitle is-4">Profile</h4>
<div className="field">
<label className="label">
<FontAwesomeIcon icon={faUser} />&nbsp;Bio or whatever (supports Markdown)
</label>
<div className="control">
<textarea
onChange={handleProfileMarkdownChange}
className="textarea is-large"
placeholder="Profile (about you)"
value={profileMarkdown}
/>
</div>
</div>
{/*<div className="field">
<label className="label">FakeYou Display Name</label>
<div className="control has-icons-left has-icons-right">
{/*<div className="field">
<label className="sub-title">FakeYou Display Name</label>
<div className="form-group">
//value={downloadUrl} onChange={handleDownloadUrlChange}
<input
className="input"
className="form-control"
type="text"
placeholder="Display Name"
value={userData?.profile_markdown || ""}
@@ -319,160 +370,172 @@ function ProfileEditFc(props: Props) {
//<p className="help">{titleInvalidReason}</p>
</div>*/}
<div className="field">
<label className="label">
<FontAwesomeIcon icon={faTwitter} />&nbsp;Twitter Username
</label>
<div className="control has-icons-left has-icons-right">
<input
onChange={handleTwitterChange}
className="input"
type="text"
placeholder="Twitter"
value={twitter}
/>
<span className="icon is-small is-left">
<i className="fas fa-envelope"></i>
</span>
<span className="icon is-small is-right">
<i className="fas fa-exclamation-triangle"></i>
</span>
<div className="field">
<label className="sub-title">
<FontAwesomeIcon icon={faTwitter} className="me-2" />
Twitter Username
</label>
<div className="form-group">
<input
onChange={handleTwitterChange}
className="form-control"
type="text"
placeholder="Twitter"
value={twitter}
/>
<span className="icon is-small is-left">
<i className="fas fa-envelope"></i>
</span>
<span className="icon is-small is-right">
<i className="fas fa-exclamation-triangle"></i>
</span>
</div>
{/*<p className="help">{downloadUrlInvalidReason}</p>*/}
</div>
<div className="field">
<label className="sub-title">
<FontAwesomeIcon icon={faDiscord} className="me-2" />
Discord Username (don't forget the #0000)
</label>
<div className="form-group">
<input
onChange={handleDiscordChange}
className="form-control"
type="text"
placeholder="Discord"
value={discord}
/>
<span className="icon is-small is-left">
<i className="fas fa-envelope"></i>
</span>
<span className="icon is-small is-right">
<i className="fas fa-exclamation-triangle"></i>
</span>
</div>
{/*<p className="help">{downloadUrlInvalidReason}</p>*/}
</div>
<div className="field">
<label className="sub-title">
<FontAwesomeIcon icon={faTwitch} className="me-2" />
Twitch Username
</label>
<div className="form-group">
<input
onChange={handleTwitchChange}
className="form-control"
type="text"
placeholder="Twitch"
value={twitch}
/>
<span className="icon is-small is-left">
<i className="fas fa-envelope"></i>
</span>
<span className="icon is-small is-right">
<i className="fas fa-exclamation-triangle"></i>
</span>
</div>
{/*<p className="help">{downloadUrlInvalidReason}</p>*/}
</div>
<div className="field">
<label className="sub-title">
<FontAwesomeIcon icon={faDollarSign} className="me-2" />
CashApp $CashTag (for reward payouts)
</label>
<div className="form-group">
<input
onChange={handleCashAppChange}
className="form-control"
type="text"
placeholder="CashApp"
value={cashApp}
/>
<span className="icon is-small is-left">
<i className="fas fa-envelope"></i>
</span>
<span className="icon is-small is-right">
<i className="fas fa-exclamation-triangle"></i>
</span>
</div>
{/*<p className="help">{downloadUrlInvalidReason}</p>*/}
</div>
<div className="field">
<label className="sub-title">
<FontAwesomeIcon icon={faGithub} className="me-2" />
Github Username (I'm hiring engineers and data
scientists!)
</label>
<div className="form-group">
<input
onChange={handleGithubChange}
className="form-control"
type="text"
placeholder="Github"
value={github}
/>
<span className="icon is-small is-left">
<i className="fas fa-envelope"></i>
</span>
<span className="icon is-small is-right">
<i className="fas fa-exclamation-triangle"></i>
</span>
</div>
{/*<p className="help">{downloadUrlInvalidReason}</p>*/}
</div>
<div className="field">
<label className="sub-title">
<FontAwesomeIcon icon={faGlobe} className="me-2" />
Personal Website URL
</label>
<div className="form-group">
<input
onChange={handleWebsiteUrlChange}
className="form-control"
type="text"
placeholder="Website URL"
value={websiteUrl}
/>
<span className="icon is-small is-left">
<i className="fas fa-envelope"></i>
</span>
<span className="icon is-small is-right">
<i className="fas fa-exclamation-triangle"></i>
</span>
</div>
{/*<p className="help">{downloadUrlInvalidReason}</p>*/}
</div>
</div>
</div>
</div>
</div>
{/*<p className="help">{downloadUrlInvalidReason}</p>*/}
</div>
</Fade>
<div className="field">
<label className="label">
<FontAwesomeIcon icon={faDiscord} />&nbsp;Discord Username (don't forget the #0000)
</label>
<div className="control has-icons-left has-icons-right">
<input
onChange={handleDiscordChange}
className="input"
type="text"
placeholder="Discord"
value={discord}
/>
<span className="icon is-small is-left">
<i className="fas fa-envelope"></i>
</span>
<span className="icon is-small is-right">
<i className="fas fa-exclamation-triangle"></i>
</span>
<Fade bottom duration={duration} distance={distance} delay={delay2}>
<div className="container">
<button className="btn btn-primary w-100">Update</button>
</div>
{/*<p className="help">{downloadUrlInvalidReason}</p>*/}
</div>
<div className="field">
<label className="label">
<FontAwesomeIcon icon={faTwitch} />&nbsp;Twitch Username
</label>
<div className="control has-icons-left has-icons-right">
<input
onChange={handleTwitchChange}
className="input"
type="text"
placeholder="Twitch"
value={twitch}
/>
<span className="icon is-small is-left">
<i className="fas fa-envelope"></i>
</span>
<span className="icon is-small is-right">
<i className="fas fa-exclamation-triangle"></i>
</span>
</div>
{/*<p className="help">{downloadUrlInvalidReason}</p>*/}
</div>
<div className="field">
<label className="label">
<FontAwesomeIcon icon={faDollarSign} />&nbsp;CashApp $CashTag (for reward payouts)
</label>
<div className="control has-icons-left has-icons-right">
<input
onChange={handleCashAppChange}
className="input"
type="text"
placeholder="CashApp"
value={cashApp}
/>
<span className="icon is-small is-left">
<i className="fas fa-envelope"></i>
</span>
<span className="icon is-small is-right">
<i className="fas fa-exclamation-triangle"></i>
</span>
</div>
{/*<p className="help">{downloadUrlInvalidReason}</p>*/}
</div>
<div className="field">
<label className="label">
<FontAwesomeIcon icon={faGithub} />&nbsp;Github Username (I'm hiring engineers and data scientists!)
</label>
<div className="control has-icons-left has-icons-right">
<input
onChange={handleGithubChange}
className="input"
type="text"
placeholder="Github"
value={github}
/>
<span className="icon is-small is-left">
<i className="fas fa-envelope"></i>
</span>
<span className="icon is-small is-right">
<i className="fas fa-exclamation-triangle"></i>
</span>
</div>
{/*<p className="help">{downloadUrlInvalidReason}</p>*/}
</div>
<div className="field">
<label className="label">
<FontAwesomeIcon icon={faFirefox} />&nbsp;Personal Website URL
</label>
<div className="control has-icons-left has-icons-right">
<input
onChange={handleWebsiteUrlChange}
className="input"
type="text"
placeholder="Website URL"
value={websiteUrl}
/>
<span className="icon is-small is-left">
<i className="fas fa-envelope"></i>
</span>
<span className="icon is-small is-right">
<i className="fas fa-exclamation-triangle"></i>
</span>
</div>
{/*<p className="help">{downloadUrlInvalidReason}</p>*/}
</div>
<br />
<button className="button is-link is-large is-fullwidth">Update</button>
</Fade>
</fieldset>
</form>
<br />
<div className="content">
<p>More profile and account features coming soon:</p>
<ul>
<li>Fully private models and templates</li>
<li>Sharing preferences (private, friends, auto-expire, etc.)</li>
<li>Website preferences</li>
<li>Custom avatar / profile pic uploads</li>
<li>Email change, password change, and password reset</li>
</ul>
</div>
<Fade bottom duration={duration} distance={distance} delay={delay}>
<div className="container py-5">
<p>More profile and account features coming soon:</p>
<ul>
<li>Fully private models and templates</li>
<li>Sharing preferences (private, friends, auto-expire, etc.)</li>
<li>Website preferences</li>
<li>Custom avatar / profile pic uploads</li>
<li>Email change, password change, and password reset</li>
</ul>
</div>
</Fade>
</div>
)
);
}
export { ProfileEditFc };
@@ -1,27 +1,59 @@
import React, { useCallback, useEffect, useState } from 'react';
import { Gravatar } from '@storyteller/components/src/elements/Gravatar';
import { Link } from 'react-router-dom';
import { ProfileTtsInferenceResultsListFc } from './Profile_TtsInferenceResultListFc';
import { ProfileTtsModelListFc } from './Profile_TtsModelListFc';
import { ProfileW2lInferenceResultsListFc } from './Profile_W2lInferenceResultListFc';
import { ProfileW2lTemplateListFc } from './Profile_W2lTemplateListFc';
import { SessionWrapper } from '@storyteller/components/src/session/SessionWrapper';
import { useParams } from 'react-router-dom';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faDiscord, faFirefox, faGithub, faTwitch, faTwitter } from '@fortawesome/free-brands-svg-icons';
import { faClock, faDollarSign } from '@fortawesome/free-solid-svg-icons';
import { GetUserByUsername, GetUserByUsernameIsErr, GetUserByUsernameIsOk, User, UserLookupError } from '../../../api/user/GetUserByUsername';
import { format } from 'date-fns';
import { FrontendUrlConfig } from '../../../../common/FrontendUrlConfig';
/* eslint-disable jsx-a11y/anchor-is-valid */
import React, { useCallback, useEffect, useState } from "react";
import { Gravatar } from "@storyteller/components/src/elements/Gravatar";
import { Link } from "react-router-dom";
import { ProfileTtsInferenceResultsListFc } from "./Profile_TtsInferenceResultListFc";
import { ProfileTtsModelListFc } from "./Profile_TtsModelListFc";
import { ProfileW2lInferenceResultsListFc } from "./Profile_W2lInferenceResultListFc";
import { ProfileW2lTemplateListFc } from "./Profile_W2lTemplateListFc";
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
import { useParams } from "react-router-dom";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faDiscord,
faGithub,
faTwitch,
faTwitter,
} from "@fortawesome/free-brands-svg-icons";
import {
faClock,
faBan,
faGear,
faAward,
faVolumeHigh,
faVideo,
faGlobe,
faDollarSign,
} from "@fortawesome/free-solid-svg-icons";
import {
GetUserByUsername,
GetUserByUsernameIsErr,
GetUserByUsernameIsOk,
User,
UserLookupError,
} from "../../../api/user/GetUserByUsername";
import { format } from "date-fns";
import { FrontendUrlConfig } from "../../../../common/FrontendUrlConfig";
import { USE_REFRESH } from "../../../../Refresh";
import { distance, duration, delay, delay2 } from "../../../../data/animation";
import Tippy from "@tippyjs/react";
import "tippy.js/dist/tippy.css";
import "tippy.js/animations/shift-away.css";
const Fade = require("react-reveal/Fade");
interface Props {
sessionWrapper: SessionWrapper,
sessionWrapper: SessionWrapper;
}
function ProfileFc(props: Props) {
const { username } : { username : string } = useParams();
// function copyToClipboard(username: string): any {
// navigator.clipboard.writeText(username);
// }
const [userData, setUserData] = useState<User|undefined>(undefined);
function ProfileFc(props: Props) {
const { username }: { username: string } = useParams();
const [userData, setUserData] = useState<User | undefined>(undefined);
const [notFoundState, setNotFoundState] = useState<boolean>(false);
const getUser = useCallback(async (username) => {
@@ -29,7 +61,7 @@ function ProfileFc(props: Props) {
if (GetUserByUsernameIsOk(response)) {
setUserData(response);
} else if (GetUserByUsernameIsErr(response)) {
switch(response) {
switch (response) {
case UserLookupError.NotFound:
setNotFoundState(true);
break;
@@ -43,235 +75,336 @@ function ProfileFc(props: Props) {
if (notFoundState) {
return (
<h1 className="title is-1">User not found</h1>
<div className="container py-5">
<h1 className="text-center fw-bold">User not found</h1>
</div>
);
}
if (!userData) {
return <div />
return <div />;
}
let userEmailHash = userData.email_gravatar_hash;
let editProfileButton = <span />
let editProfileButton = <span />;
if (props.sessionWrapper.canEditUserProfile(userData.username)) {
const editLinkUrl = FrontendUrlConfig.userProfileEditPage(userData.username);
const editLinkUrl = FrontendUrlConfig.userProfileEditPage(
userData.username
);
// Mods shouldn't edit preferences.
const buttonLabel = props.sessionWrapper.userTokenMatches(userData.user_token) ?
"Edit Profile & Preferences" : "Edit Profile";
const buttonLabel = props.sessionWrapper.userTokenMatches(
userData.user_token
)
? "Edit Profile & Preferences"
: "Edit Profile";
editProfileButton = (
<>
<Link
className={"button is-large is-fullwidth is-info"}
to={editLinkUrl}>{buttonLabel}</Link>
<br />
<Link className={"btn btn-secondary"} to={editLinkUrl}>
<FontAwesomeIcon icon={faGear} className="me-2" />
{buttonLabel}
</Link>
</>
);
}
let banUserButton = <span />
let banUserButton = <span />;
if (props.sessionWrapper.canBanUsers()) {
const currentlyBanned = userData.maybe_moderator_fields?.is_banned;
const banLinkUrl = FrontendUrlConfig.userProfileBanPage(userData.username);
const buttonLabel = currentlyBanned ? "Unban User" : "Ban User";
const banButtonCss = currentlyBanned ?
"button is-warning is-large is-fullwidth" :
"button is-danger is-large is-fullwidth";
const banButtonCss = currentlyBanned
? "btn btn-secondary"
: "btn btn-destructive";
banUserButton = (
<>
<Link
className={banButtonCss}
to={banLinkUrl}>{buttonLabel}</Link>
<br />
<Link className={banButtonCss} to={banLinkUrl}>
<FontAwesomeIcon icon={faBan} className="me-2" />
{buttonLabel}
</Link>
</>
);
}
let profileRows : Array<JSX.Element> = [];
let profileRows: Array<JSX.Element> = [];
let profileJoinDate: Array<JSX.Element> = [];
const createdAt = new Date(userData.created_at);
const joinDate = format(createdAt, "LLLL y");
profileJoinDate.push(
<div
key="created"
className="d-flex align-items-center justify-content-center fs-6 me-3"
>
<FontAwesomeIcon icon={faClock} className="me-2" />
<p className="fw-bold">Joined {joinDate}</p>
</div>
);
if (userData.website_url !== undefined && userData.website_url !== null) {
let websiteUrl = <span>{userData.website_url}</span>;
if (userData.website_url?.startsWith("http://") || userData.website_url?.startsWith("https://")) {
if (
userData.website_url?.startsWith("http://") ||
userData.website_url?.startsWith("https://")
) {
websiteUrl = (
<a
href={userData.website_url}
target="_blank"
rel="noopener noreferrer nofollow"
>{userData.website_url}</a>
<Tippy content="Website" animation="shift-away">
<a
href={userData.website_url}
target="_blank"
rel="noopener noreferrer nofollow"
>
<FontAwesomeIcon icon={faGlobe} />
</a>
</Tippy>
);
}
profileRows.push(
<tr key="website">
<th>
Website&nbsp;<FontAwesomeIcon icon={faFirefox} />
</th>
<td>{websiteUrl}</td>
</tr>
)
profileRows.push(<div key="website">{websiteUrl}</div>);
}
if (userData.twitch_username) {
let twitchUrl = `https://twitch.com/${userData.twitch_username}`;
let twitchLink = (
<a
href={twitchUrl}
target="_blank"
rel="noopener noreferrer nofollow"
>{userData.twitch_username}</a>
<Tippy content="Twitch" animation="shift-away">
<a href={twitchUrl} target="_blank" rel="noopener noreferrer nofollow">
<FontAwesomeIcon icon={faTwitch} />
</a>
</Tippy>
);
profileRows.push(
<tr key="twitch">
<th>
Twitch&nbsp;<FontAwesomeIcon icon={faTwitch} />
</th>
<td>{twitchLink}</td>
</tr>
)
profileRows.push(<div key="twitch">{twitchLink}</div>);
}
if (userData.twitter_username) {
let twitterUrl = `https://twitter.com/${userData.twitter_username}`;
let twitterLink = (
<a
href={twitterUrl}
target="_blank"
rel="noopener noreferrer nofollow"
>@{userData.twitter_username}</a>
<Tippy content="Twitter" animation="shift-away">
<a href={twitterUrl} target="_blank" rel="noopener noreferrer nofollow">
<FontAwesomeIcon icon={faTwitter} />
</a>
</Tippy>
);
profileRows.push(
<tr key="twitter">
<th>
Twitter&nbsp;<FontAwesomeIcon icon={faTwitter} />
</th>
<td>{twitterLink}</td>
</tr>
)
profileRows.push(<div key="twitter">{twitterLink}</div>);
}
if (userData.discord_username) {
profileRows.push(
<tr key="discord">
<th>
Discord&nbsp;<FontAwesomeIcon icon={faDiscord} />
</th>
<td>{userData.discord_username}</td>
</tr>
)
<Tippy
allowHTML
content={
<div className="text-center">
Discord
<br />
<p className="fw-semibold fs-6">{userData.discord_username}</p>
</div>
}
interactive
animation="shift-away"
>
<a
// eslint-disable-next-line no-script-url
href="javascript:;"
// onClick={copyToClipboard(userData.discord_username)}
>
<FontAwesomeIcon icon={faDiscord} />
</a>
</Tippy>
);
}
if (userData.github_username) {
let githubUrl = `https://github.com/${userData.github_username}`;
let githubLink = (
<a
href={githubUrl}
target="_blank"
rel="noopener noreferrer nofollow"
>{userData.github_username}</a>
<Tippy content="GitHub" animation="shift-away">
<a href={githubUrl} target="_blank" rel="noopener noreferrer nofollow">
<FontAwesomeIcon icon={faGithub} />
</a>
</Tippy>
);
profileRows.push(
<tr key="github">
<th>
Github&nbsp;<FontAwesomeIcon icon={faGithub} />
</th>
<td>{githubLink}</td>
</tr>
)
profileRows.push(<div key="github">{githubLink}</div>);
}
if (userData.cashapp_username) {
// NB: URL includes a dollar sign
let cashAppUrl = `https://cash.me/$${userData.cashapp_username}`;
let cashAppLink = (
<a
href={cashAppUrl}
target="_blank"
rel="noopener noreferrer nofollow"
>${userData.cashapp_username}</a>
<Tippy content="CashApp" animation="shift-away">
<a href={cashAppUrl} target="_blank" rel="noopener noreferrer nofollow">
<FontAwesomeIcon icon={faDollarSign} />
</a>
</Tippy>
);
profileRows.push(
<tr key="cashapp">
<th>
CashApp&nbsp;<FontAwesomeIcon icon={faDollarSign} />
</th>
<td>{cashAppLink}</td>
</tr>
)
profileRows.push(<div key="cashapp">{cashAppLink}</div>);
}
const createdAt = new Date(userData.created_at);
const joinDate = format(createdAt, 'LLLL y')
profileRows.push(
<tr key="created">
<th>
Joined&nbsp;<FontAwesomeIcon icon={faClock} />
</th>
<td>{joinDate}</td>
</tr>
);
let badges = <div>None yet</div>;
if (userData.badges.length !== 0) {
let badgeList : Array<JSX.Element> = [];
userData.badges.forEach(badge => {
badgeList.push((
<li key={badge.slug}>
{badge.title}
</li>
));
})
badges = (
<ul>{badgeList}</ul>
)
let badgeList: Array<JSX.Element> = [];
userData.badges.forEach((badge) => {
badgeList.push(<li key={badge.slug}>{badge.title}</li>);
});
badges = <ul>{badgeList}</ul>;
}
if (!USE_REFRESH) {
return (
<div className="content">
<h1 className="title is-1">
<Gravatar
size={45}
username={userData.display_name}
email_hash={userEmailHash}
/>
{userData.display_name}
</h1>
{editProfileButton}
{banUserButton}
<div
className="profile content is-medium"
dangerouslySetInnerHTML={{
__html: userData.profile_rendered_html || "",
}}
/>
<table className="table">
<tbody>{profileRows}</tbody>
</table>
<h3 className="title is-3"> Badges (images coming soon) </h3>
{badges}
<h3 className="title is-3"> TTS Results </h3>
<ProfileTtsInferenceResultsListFc username={userData.username} />
<h3 className="title is-3"> Lipsync Results </h3>
<ProfileW2lInferenceResultsListFc username={userData.username} />
<h3 className="title is-3"> Uploaded TTS Models </h3>
<ProfileTtsModelListFc username={userData.username} />
<h3 className="title is-3"> Uploaded Templates </h3>
<ProfileW2lTemplateListFc username={userData.username} />
</div>
);
}
return (
<div className="content">
<h1 className="title is-1">
<Gravatar
size={45}
username={userData.display_name}
email_hash={userEmailHash} />
{userData.display_name}
</h1>
<div>
<Fade bottom cascade duration={duration} distance={distance}>
<div className="container pt-5 pb-4 px-lg-5 px-xl-3">
<div className="d-flex flex-column flex-lg-row align-items-center">
<div className="mb-3 me-lg-4 mb-lg-0">
<Gravatar
size={45}
username={userData.display_name}
email_hash={userEmailHash}
/>
</div>
<h1 className="display-5 fw-bold text-center text-lg-start w-100 mb-0">
{userData.display_name}
</h1>
<div className="w-100 justify-content-end d-none d-lg-flex">
<div className="d-flex gap-3">
{editProfileButton}
{banUserButton}
</div>
</div>
</div>
<div className="d-flex flex-column flex-lg-row gap-4 gap-lg-3 mt-3">
{profileJoinDate}
<div className="d-flex justify-content-center gap-4 gap-lg-3 profile-social-icons">
{profileRows}
</div>
</div>
</div>
</Fade>
{editProfileButton}
{banUserButton}
<div
className="profile content is-medium"
dangerouslySetInnerHTML={{__html: userData.profile_rendered_html || ""}}
<Fade bottom duration={duration} delay={delay} distance={distance}>
<div
className="container content mb-4 mb-lg-5 text-center text-lg-start px-4 px-md-5 px-lg-5 px-xl-3"
dangerouslySetInnerHTML={{
__html: userData.profile_rendered_html || "",
}}
/>
<div className="container d-flex d-lg-none my-5">
<div className="d-flex w-100 gap-3 justify-content-center flex-column flex-md-row">
{editProfileButton}
{banUserButton}
</div>
</div>
</Fade>
<table className="table">
<tbody>
{profileRows}
</tbody>
</table>
<Fade
bottom
cascade
duration={duration}
delay={delay2}
distance={distance}
>
<div className="container-panel py-5">
<div className="panel p-3 p-lg-4">
<h2 className="panel-title fw-bold">
<FontAwesomeIcon icon={faAward} className="me-3" />
Badges{" "}
<span className="fs-5 fw-normal ms-2">(images coming soon)</span>
</h2>
<div className="py-6">{badges}</div>
</div>
</div>
<h3 className="title is-3"> Badges (images coming soon) </h3>
{badges}
<div className="container-panel pt-3 pb-5">
<div className="panel p-3 p-lg-4">
<h2 className="panel-title fw-bold">
<FontAwesomeIcon icon={faVolumeHigh} className="me-3" />
TTS Results
</h2>
<div className="py-6">
<ProfileTtsInferenceResultsListFc username={userData.username} />
</div>
</div>
</div>
<h3 className="title is-3"> TTS Results </h3>
<ProfileTtsInferenceResultsListFc username={userData.username} />
<div className="container-panel pt-3 pb-5">
<div className="panel p-3 p-lg-4">
<h2 className="panel-title fw-bold">
<FontAwesomeIcon icon={faVideo} className="me-3" />
Lipsync Results
</h2>
<div className="py-6">
<ProfileW2lInferenceResultsListFc username={userData.username} />
</div>
</div>
</div>
<h3 className="title is-3"> Lipsync Results </h3>
<ProfileW2lInferenceResultsListFc username={userData.username} />
<h3 className="title is-3"> Uploaded TTS Models </h3>
<ProfileTtsModelListFc username={userData.username} />
<h3 className="title is-3"> Uploaded Templates </h3>
<ProfileW2lTemplateListFc username={userData.username} />
<div className="container-panel pt-3 pb-5">
<div className="panel p-3 p-lg-4">
<h2 className="panel-title fw-bold">Uploaded TTS Models </h2>
<div className="py-6">
<ProfileTtsModelListFc username={userData.username} />
</div>
</div>
</div>
<div className="container-panel pt-3 pb-5">
<div className="panel p-3 p-lg-4">
<h2 className="panel-title fw-bold">Uploaded Templates </h2>
<div className="py-6">
<ProfileW2lTemplateListFc username={userData.username} />
</div>
</div>
</div>
</Fade>
</div>
)
);
}
export { ProfileFc };
@@ -1,88 +1,100 @@
import React, { useState, useEffect, useCallback } from 'react';
import { ApiConfig, ListTtsInferenceResultsForUserArgs } from '@storyteller/components';
import { Link } from 'react-router-dom';
import { formatDistance } from 'date-fns';
import { VisibleIconFc } from '../../_icons/VisibleIcon';
import { HiddenIconFc } from '../../_icons/HiddenIcon';
import { PlayCircleIcon } from '../../_icons/PlayCircleIcon';
import React, { useState, useEffect, useCallback } from "react";
import {
ApiConfig,
ListTtsInferenceResultsForUserArgs,
} from "@storyteller/components";
import { Link } from "react-router-dom";
import { formatDistance } from "date-fns";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faCirclePlay,
faArrowLeft,
faArrowRight,
faEye,
faEyeSlash,
} from "@fortawesome/free-solid-svg-icons";
const Fade = require("react-reveal/Fade");
interface TtsInferenceResultListResponsePayload {
success: boolean,
results: Array<TtsInferenceResult>,
cursor_next: string | null | undefined,
cursor_previous: string | null | undefined,
success: boolean;
results: Array<TtsInferenceResult>;
cursor_next: string | null | undefined;
cursor_previous: string | null | undefined;
}
interface TtsInferenceResult {
tts_result_token: string,
tts_result_token: string;
tts_model_token: string,
tts_model_title: string,
tts_model_token: string;
tts_model_title: string;
raw_inference_text: string,
raw_inference_text: string;
maybe_creator_user_token?: string,
maybe_creator_username?: string,
maybe_creator_display_name?: string,
maybe_creator_user_token?: string;
maybe_creator_username?: string;
maybe_creator_display_name?: string;
maybe_creator_result_id: number | null,
maybe_creator_result_id: number | null;
file_size_bytes: number,
duration_millis: number,
file_size_bytes: number;
duration_millis: number;
visibility: string,
visibility: string;
created_at: string,
updated_at: string,
created_at: string;
updated_at: string;
}
interface Props {
username: string,
username: string;
}
function ProfileTtsInferenceResultsListFc(props: Props) {
const [ttsResults, setTtsResults] = useState<Array<TtsInferenceResult>>([]);
const [nextCursor, setNextCursor] = useState<string|null>(null);
const [previousCursor, setPreviousCursor] = useState<string|null>(null);
const [nextCursor, setNextCursor] = useState<string | null>(null);
const [previousCursor, setPreviousCursor] = useState<string | null>(null);
const getPage = useCallback((cursor : string|null, reverse: boolean) => {
let args : ListTtsInferenceResultsForUserArgs = {
username: props.username,
limit: 25,
};
const getPage = useCallback(
(cursor: string | null, reverse: boolean) => {
let args: ListTtsInferenceResultsForUserArgs = {
username: props.username,
limit: 25,
};
if (cursor !== null) {
args.cursor = cursor;
if (reverse) {
args.cursor_is_reversed = true;
}
}
const api = new ApiConfig();
const endpointUrl = api.listTtsInferenceResultsForUser(args);
fetch(endpointUrl, {
method: 'GET',
headers: {
'Accept': 'application/json',
},
credentials: 'include',
})
.then(res => res.json())
.then(res => {
const modelsResponse : TtsInferenceResultListResponsePayload = res;
if (!modelsResponse.success) {
return;
if (cursor !== null) {
args.cursor = cursor;
if (reverse) {
args.cursor_is_reversed = true;
}
}
setTtsResults(modelsResponse.results)
setNextCursor(modelsResponse.cursor_next || null)
setPreviousCursor(modelsResponse.cursor_previous || null)
})
.catch(e => {
});
}, [props.username]);
const api = new ApiConfig();
const endpointUrl = api.listTtsInferenceResultsForUser(args);
fetch(endpointUrl, {
method: "GET",
headers: {
Accept: "application/json",
},
credentials: "include",
})
.then((res) => res.json())
.then((res) => {
const modelsResponse: TtsInferenceResultListResponsePayload = res;
if (!modelsResponse.success) {
return;
}
setTtsResults(modelsResponse.results);
setNextCursor(modelsResponse.cursor_next || null);
setPreviousCursor(modelsResponse.cursor_previous || null);
})
.catch((e) => {});
},
[props.username]
);
useEffect(() => {
getPage(null, false);
@@ -90,35 +102,45 @@ function ProfileTtsInferenceResultsListFc(props: Props) {
const now = new Date();
let rows : Array<JSX.Element> = [];
ttsResults.forEach(result => {
let rows: Array<JSX.Element> = [];
ttsResults.slice(0, 10).forEach((result) => {
const duration_seconds = result.duration_millis / 1000;
const inferenceLink = `/tts/result/${result.tts_result_token}`;
const modelLink = `/tts/${result.tts_model_token}`;
const text = result.raw_inference_text.length < 5 ? `Result: ${result.raw_inference_text}` : result.raw_inference_text;
const text =
result.raw_inference_text.length < 5
? `Result: ${result.raw_inference_text}`
: result.raw_inference_text;
const createTime = new Date(result.created_at);
const relativeCreateTime = formatDistance(createTime, now, { addSuffix: true });
const relativeCreateTime = formatDistance(createTime, now, {
addSuffix: true,
});
const visibilityIcon = (result.visibility === 'public') ? <VisibleIconFc /> : <HiddenIconFc /> ;
const visibilityIcon =
result.visibility === "public" ? (
<FontAwesomeIcon icon={faEye} />
) : (
<FontAwesomeIcon icon={faEyeSlash} />
);
rows.push(
<tr key={result.tts_result_token}>
<td>{result.maybe_creator_result_id}</td>
<td>{visibilityIcon}</td>
<th className="overflow-fix">
<Link to={inferenceLink}>
<PlayCircleIcon title="play" />
&nbsp;
<FontAwesomeIcon icon={faCirclePlay} className="me-2" />
{text}
</Link>
&nbsp;
</th>
<th><Link to={modelLink}>Model: {result.tts_model_title}</Link></th>
<th>
<Link to={modelLink}>Model: {result.tts_model_title}</Link>
</th>
<td>{duration_seconds} s</td>
<td>{relativeCreateTime}</td>
</tr>
@@ -126,40 +148,70 @@ function ProfileTtsInferenceResultsListFc(props: Props) {
});
// Disable if there is no "next" or "previous" cursor.
// However, let the buttosn show up if there are no results (empty payload)
// However, let the buttosn show up if there are no results (empty payload)
// to get unstuck. Come up with a better fix for this.
let prevDisabled = !previousCursor && rows.length !== 0;
let nextDisabled = !nextCursor && rows.length !== 0;
return (
<div>
<table className="table">
<thead>
<tr>
<th><abbr title="Number">#</abbr></th>
<th><abbr title="Visibility"><VisibleIconFc /></abbr></th>
<th><abbr title="Download">Download &amp; Play Link</abbr></th>
<th><abbr title="Model">Model</abbr></th>
<th><abbr title="Duration">Duration</abbr></th>
<th><abbr title="Created">Creation Time</abbr></th>
</tr>
</thead>
<tbody>
{rows}
</tbody>
</table>
<div className="table-responsive">
<table className="table">
<thead>
<tr>
<th>
<abbr title="Number">#</abbr>
</th>
<th>
<abbr title="Visibility">
<FontAwesomeIcon icon={faEye} />
</abbr>
</th>
<th>
<abbr title="Download">Download &amp; Play Link</abbr>
</th>
<th>
<abbr title="Model">Model</abbr>
</th>
<th>
<abbr title="Duration">Duration</abbr>
</th>
<th>
<abbr title="Created">Creation Time</abbr>
</th>
</tr>
</thead>
<Fade cascade bottom duration="200" distance="10px">
<tbody>{rows}</tbody>
</Fade>
</table>
</div>
<p>
Note: Results marked public (<VisibleIconFc />) are visible by anyone visiting your profile.
<p className="text-center py-3">
Note: Results marked public (<FontAwesomeIcon icon={faEye} />) are
visible by anyone visiting your profile.
{/*You can change this by editing results. You can also set a default preference for new results.*/}
</p>
<div className="columns is-centered">
<button className="button is-info" onClick={() => getPage(previousCursor, true)} disabled={prevDisabled}>&lt; Get newer</button> &nbsp;
<button className="button is-info" onClick={() => getPage(nextCursor, false)} disabled={nextDisabled}>Get older&gt;</button>
<div className="justify-content-center d-flex gap-3">
<button
className="btn btn-secondary"
onClick={() => getPage(previousCursor, true)}
disabled={prevDisabled}
>
<FontAwesomeIcon icon={faArrowLeft} className="me-2" /> Get newer
</button>
<button
className="btn btn-secondary"
onClick={() => getPage(nextCursor, false)}
disabled={nextDisabled}
>
Get older
<FontAwesomeIcon icon={faArrowRight} className="ms-2" />
</button>
</div>
</div>
)
);
}
export { ProfileTtsInferenceResultsListFc };
@@ -1,26 +1,26 @@
import React, { useState, useEffect } from 'react';
import { ApiConfig } from '@storyteller/components';
import { Link } from 'react-router-dom';
import { formatDistance } from 'date-fns';
import React, { useState, useEffect } from "react";
import { ApiConfig } from "@storyteller/components";
import { Link } from "react-router-dom";
import { formatDistance } from "date-fns";
interface TtsModelListResponsePayload {
success: boolean,
models: Array<TtsModel>,
success: boolean;
models: Array<TtsModel>;
}
interface TtsModel {
model_token: string,
tts_model_type: string,
title: string,
updatable_slug: string,
model_token: string;
tts_model_type: string;
title: string;
updatable_slug: string;
// TODO: No need for "creator_*" fields. Remove them from backend.
is_mod_disabled: boolean,
created_at: string,
updated_at: string,
is_mod_disabled: boolean;
created_at: string;
updated_at: string;
}
interface Props {
username: string,
username: string;
}
function ProfileTtsModelListFc(props: Props) {
@@ -31,40 +31,44 @@ function ProfileTtsModelListFc(props: Props) {
const endpointUrl = api.listTtsModelsForUser(props.username);
fetch(endpointUrl, {
method: 'GET',
method: "GET",
headers: {
'Accept': 'application/json',
Accept: "application/json",
},
credentials: 'include',
credentials: "include",
})
.then(res => res.json())
.then(res => {
const modelsResponse : TtsModelListResponsePayload = res;
if (!modelsResponse.success) {
return;
}
.then((res) => res.json())
.then((res) => {
const modelsResponse: TtsModelListResponsePayload = res;
if (!modelsResponse.success) {
return;
}
setTtsModels(modelsResponse.models)
})
.catch(e => {
});
setTtsModels(modelsResponse.models);
})
.catch((e) => {});
}, [props.username]); // NB: Empty array dependency sets to run ONLY on mount
const now = new Date();
let rows : Array<JSX.Element> = [];
ttsModels.forEach(model => {
const modelTitle = model.title.length < 5 ? `Model: ${model.title}` : model.title;
let rows: Array<JSX.Element> = [];
ttsModels.slice(0, 10).forEach((model) => {
const modelTitle =
model.title.length < 5 ? `Model: ${model.title}` : model.title;
const modelLink = `/tts/${model.model_token}`;
const createTime = new Date(model.created_at);
const relativeCreateTime = formatDistance(createTime, now, { addSuffix: true });
const relativeCreateTime = formatDistance(createTime, now, {
addSuffix: true,
});
rows.push(
<tr key={model.model_token}>
<th><Link to={modelLink}>{modelTitle}</Link></th>
<th>
<Link to={modelLink}>{modelTitle}</Link>
</th>
<td>{relativeCreateTime}</td>
</tr>
);
@@ -75,16 +79,19 @@ function ProfileTtsModelListFc(props: Props) {
<table className="table">
<thead>
<tr>
<th><abbr title="Model Name">Model Name</abbr></th>
<th><abbr title="Creation Date">Creation Time</abbr></th>
<th>
<abbr title="Model Name">Model Name</abbr>
</th>
<th>
<abbr title="Creation Date">Creation Time</abbr>
</th>
</tr>
</thead>
<tbody>
{rows}
</tbody>
<tbody>{rows}</tbody>
</table>
<div className="text-center">ADD OLDER/NEWER PAGINATION BUTTON HERE</div>
</div>
)
);
}
export { ProfileTtsModelListFc };
@@ -1,90 +1,102 @@
import React, { useState, useEffect, useCallback } from 'react';
import { ApiConfig, ListW2lInferenceResultsForUserArgs } from '@storyteller/components';
import { Link } from 'react-router-dom';
import { formatDistance } from 'date-fns'
import { VisibleIconFc } from '../../_icons/VisibleIcon';
import { HiddenIconFc } from '../../_icons/HiddenIcon';
import { FilmIcon } from '../../_icons/FilmIcon';
import React, { useState, useEffect, useCallback } from "react";
import {
ApiConfig,
ListW2lInferenceResultsForUserArgs,
} from "@storyteller/components";
import { Link } from "react-router-dom";
import { formatDistance } from "date-fns";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faFilm,
faArrowLeft,
faArrowRight,
faEye,
faEyeSlash,
} from "@fortawesome/free-solid-svg-icons";
const Fade = require("react-reveal/Fade");
interface W2lInferenceResultListResponsePayload {
success: boolean,
results: Array<W2lInferenceResult>,
cursor_next: string | null | undefined,
cursor_previous: string | null | undefined,
success: boolean;
results: Array<W2lInferenceResult>;
cursor_next: string | null | undefined;
cursor_previous: string | null | undefined;
}
interface W2lInferenceResult {
w2l_result_token: string,
maybe_w2l_template_token?: string,
maybe_tts_inference_result_token?: string,
w2l_result_token: string;
maybe_w2l_template_token?: string;
maybe_tts_inference_result_token?: string;
template_type: string,
template_title: string,
template_type: string;
template_title: string;
maybe_creator_user_token?: string,
maybe_creator_username?: string,
maybe_creator_display_name?: string,
maybe_creator_user_token?: string;
maybe_creator_username?: string;
maybe_creator_display_name?: string;
maybe_creator_result_id: number | null,
file_size_bytes: number,
frame_width: number,
frame_height: number,
duration_millis: number,
maybe_creator_result_id: number | null;
visibility: string,
file_size_bytes: number;
frame_width: number;
frame_height: number;
duration_millis: number;
created_at: string,
updated_at: string,
visibility: string;
created_at: string;
updated_at: string;
}
interface Props {
username: string,
username: string;
}
function ProfileW2lInferenceResultsListFc(props: Props) {
const [w2lResults, setW2lResults] = useState<Array<W2lInferenceResult>>([]);
const [nextCursor, setNextCursor] = useState<string|null>(null);
const [previousCursor, setPreviousCursor] = useState<string|null>(null);
const [nextCursor, setNextCursor] = useState<string | null>(null);
const [previousCursor, setPreviousCursor] = useState<string | null>(null);
const getPage = useCallback((cursor : string|null, reverse: boolean) => {
let args : ListW2lInferenceResultsForUserArgs = {
username: props.username,
limit: 5,
};
const getPage = useCallback(
(cursor: string | null, reverse: boolean) => {
let args: ListW2lInferenceResultsForUserArgs = {
username: props.username,
limit: 5,
};
if (cursor !== null) {
args.cursor = cursor;
if (reverse) {
args.cursor_is_reversed = true;
}
}
const api = new ApiConfig();
const endpointUrl = api.listW2lInferenceResultsForUser(args);
fetch(endpointUrl, {
method: 'GET',
headers: {
'Accept': 'application/json',
},
credentials: 'include',
})
.then(res => res.json())
.then(res => {
const templatesResponse : W2lInferenceResultListResponsePayload = res;
if (!templatesResponse.success) {
return;
if (cursor !== null) {
args.cursor = cursor;
if (reverse) {
args.cursor_is_reversed = true;
}
}
setW2lResults(templatesResponse.results)
setNextCursor(templatesResponse.cursor_next || null)
setPreviousCursor(templatesResponse.cursor_previous || null)
})
.catch(e => {
});
}, [props.username]);
const api = new ApiConfig();
const endpointUrl = api.listW2lInferenceResultsForUser(args);
fetch(endpointUrl, {
method: "GET",
headers: {
Accept: "application/json",
},
credentials: "include",
})
.then((res) => res.json())
.then((res) => {
const templatesResponse: W2lInferenceResultListResponsePayload = res;
if (!templatesResponse.success) {
return;
}
setW2lResults(templatesResponse.results);
setNextCursor(templatesResponse.cursor_next || null);
setPreviousCursor(templatesResponse.cursor_previous || null);
})
.catch((e) => {});
},
[props.username]
);
useEffect(() => {
getPage(null, false);
@@ -92,19 +104,29 @@ function ProfileW2lInferenceResultsListFc(props: Props) {
const now = new Date();
let rows : Array<JSX.Element> = [];
w2lResults.forEach(result => {
let rows: Array<JSX.Element> = [];
w2lResults.slice(0, 5).forEach((result) => {
const duration_seconds = result.duration_millis / 1000;
const templateTitle = result.template_title.length < 5 ? `Title: ${result.template_title}` : result.template_title;
const templateTitle =
result.template_title.length < 5
? `Title: ${result.template_title}`
: result.template_title;
const inferenceLink = `/w2l/result/${result.w2l_result_token}`;
const templateLink = `/w2l/${result.maybe_w2l_template_token}`;
const createTime = new Date(result.created_at);
const relativeCreateTime = formatDistance(createTime, now, { addSuffix: true });
const relativeCreateTime = formatDistance(createTime, now, {
addSuffix: true,
});
const visibilityIcon = (result.visibility === 'public') ? <VisibleIconFc /> : <HiddenIconFc /> ;
const visibilityIcon =
result.visibility === "public" ? (
<FontAwesomeIcon icon={faEye} />
) : (
<FontAwesomeIcon icon={faEyeSlash} />
);
rows.push(
<tr key={result.w2l_result_token}>
@@ -112,12 +134,13 @@ function ProfileW2lInferenceResultsListFc(props: Props) {
<td>{visibilityIcon}</td>
<th>
<Link to={inferenceLink}>
<FilmIcon title="Play &amp; Download" />
&nbsp;
<FontAwesomeIcon icon={faFilm} className="me-2" />
Result
</Link>
</th>
<th><Link to={templateLink}>{templateTitle}</Link></th>
<th>
<Link to={templateLink}>{templateTitle}</Link>
</th>
<td>(custom audio)</td>
<td>{duration_seconds} s</td>
<td>{relativeCreateTime}</td>
@@ -126,41 +149,73 @@ function ProfileW2lInferenceResultsListFc(props: Props) {
});
// Disable if there is no "next" or "previous" cursor.
// However, let the buttosn show up if there are no results (empty payload)
// However, let the buttosn show up if there are no results (empty payload)
// to get unstuck. Come up with a better fix for this.
let prevDisabled = !previousCursor && rows.length !== 0;
let nextDisabled = !nextCursor && rows.length !== 0;
return (
<div>
<table className="table">
<thead>
<tr>
<th><abbr title="Number">#</abbr></th>
<th><abbr title="Visibility"><VisibleIconFc /></abbr></th>
<th><abbr title="Result View">Result Link</abbr></th>
<th><abbr title="Template">Template</abbr></th>
<th><abbr title="Source">Audio Source</abbr></th>
<th><abbr title="Duration">Duration</abbr></th>
<th><abbr title="Created">Creation Time</abbr></th>
</tr>
</thead>
<tbody>
{rows}
</tbody>
</table>
<div className="table-responsive">
<table className="table">
<thead>
<tr>
<th>
<abbr title="Number">#</abbr>
</th>
<th>
<abbr title="Visibility">
<FontAwesomeIcon icon={faEye} />
</abbr>
</th>
<th>
<abbr title="Result View">Result Link</abbr>
</th>
<th>
<abbr title="Template">Template</abbr>
</th>
<th>
<abbr title="Source">Audio Source</abbr>
</th>
<th>
<abbr title="Duration">Duration</abbr>
</th>
<th>
<abbr title="Created">Creation Time</abbr>
</th>
</tr>
</thead>
<Fade cascade bottom duration="200" distance="10px">
<tbody>{rows}</tbody>
</Fade>
</table>
</div>
<p>
Note: Results marked public (<VisibleIconFc />) are visible by anyone visiting your profile.
<p className="text-center py-3">
Note: Results marked public (<FontAwesomeIcon icon={faEye} />) are
visible by anyone visiting your profile.
{/*You can change this by editing results. You can also set a default preference for new results.*/}
</p>
<div className="columns is-centered">
<button className="button is-info" onClick={() => getPage(previousCursor, true)} disabled={prevDisabled}>&lt; Get newer</button> &nbsp;
<button className="button is-info" onClick={() => getPage(nextCursor, false)} disabled={nextDisabled}>Get older&gt;</button>
<div className="justify-content-center d-flex gap-3">
<button
className="btn btn-secondary"
onClick={() => getPage(previousCursor, true)}
disabled={prevDisabled}
>
<FontAwesomeIcon icon={faArrowLeft} className="me-2" /> Get newer
</button>
<button
className="btn btn-secondary"
onClick={() => getPage(nextCursor, false)}
disabled={nextDisabled}
>
Get older
<FontAwesomeIcon icon={faArrowRight} className="ms-2" />
</button>
</div>
</div>
)
);
}
export { ProfileW2lInferenceResultsListFc };
@@ -1,32 +1,34 @@
import React, { useState, useEffect } from 'react';
import { ApiConfig } from '@storyteller/components';
import React, { useState, useEffect } from "react";
import { ApiConfig } from "@storyteller/components";
import { Link } from "react-router-dom";
//import { getRandomInt } from '../../../../v1/api/Utils';
import { BucketConfig } from '@storyteller/components/src/api/BucketConfig';
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
import { distance, duration } from "../../../../data/animation";
const Fade = require("react-reveal/Fade");
interface W2lTemplateListResponsePayload {
success: boolean,
templates: Array<W2lTemplate>,
success: boolean;
templates: Array<W2lTemplate>;
}
interface W2lTemplate {
template_token: string,
template_type: string,
creator_user_token: string,
username: string,
display_name: string,
title: string,
frame_width: number,
frame_height: number,
duration_millis: number,
maybe_image_object_name: string,
maybe_video_object_name: string,
created_at: string,
updated_at: string,
template_token: string;
template_type: string;
creator_user_token: string;
username: string;
display_name: string;
title: string;
frame_width: number;
frame_height: number;
duration_millis: number;
maybe_image_object_name: string;
maybe_video_object_name: string;
created_at: string;
updated_at: string;
}
interface Props {
username: string,
username: string;
}
function ProfileW2lTemplateListFc(props: Props) {
@@ -37,35 +39,40 @@ function ProfileW2lTemplateListFc(props: Props) {
const endpointUrl = api.listW2lTemplatesForUser(props.username);
fetch(endpointUrl, {
method: 'GET',
method: "GET",
headers: {
'Accept': 'application/json',
Accept: "application/json",
},
credentials: 'include',
credentials: "include",
})
.then(res => res.json())
.then(res => {
const templatesResponse : W2lTemplateListResponsePayload = res;
if (!templatesResponse.success) {
return;
}
.then((res) => res.json())
.then((res) => {
const templatesResponse: W2lTemplateListResponsePayload = res;
if (!templatesResponse.success) {
return;
}
setW2lTemplates(templatesResponse.templates)
})
.catch(e => {
//this.props.onSpeakErrorCallback();
});
setW2lTemplates(templatesResponse.templates);
})
.catch((e) => {
//this.props.onSpeakErrorCallback();
});
}, [props.username]); // NB: Empty array dependency sets to run ONLY on mount
let templateElements : Array<JSX.Element> = [];
let templateElements: Array<JSX.Element> = [];
w2lTemplates.forEach(t => {
w2lTemplates.slice(0, 8).forEach((t) => {
let object = null;
if (t.maybe_image_object_name !== undefined && t.maybe_image_object_name !== null) {
if (
t.maybe_image_object_name !== undefined &&
t.maybe_image_object_name !== null
) {
object = t.maybe_image_object_name;
} else if (t.maybe_video_object_name !== undefined && t.maybe_video_object_name !== null) {
} else if (
t.maybe_video_object_name !== undefined &&
t.maybe_video_object_name !== null
) {
object = t.maybe_video_object_name;
} else {
console.warn(`No image for template ${t.template_token}`);
@@ -75,34 +82,40 @@ function ProfileW2lTemplateListFc(props: Props) {
let url = new BucketConfig().getGcsUrl(object);
let link = `/w2l/${t.template_token}`;
templateElements.push((
<div className="tile is-parent" key={t.template_token}>
<article className="tile is-child box">
{/*<p className="title">One</p>*/}
<Link to={link}><img src={url} alt="" /></Link>
</article>
templateElements.push(
<div className="video-card">
<div className="video-card-body d-flex flex-column">
<h6 className="video-card-title mb-1">Title</h6>
<p className="video-card-text">by NAME</p>
</div>
<Link to={link}>
<img className="video-img" src={url} alt="" />
</Link>
</div>
));
);
});
let allRowsOfTemplateElements : Array<JSX.Element> = [];
let rowOfTemplateElements : Array<JSX.Element> = [];
let allRowsOfTemplateElements: Array<JSX.Element> = [];
let rowOfTemplateElements: Array<JSX.Element> = [];
//let nextRowSize = getRandomInt(3, 4);
let nextRowSize = 3;
let nextRowSize = 1;
// NB: To prevent React spamming about children having unique key props
let rowKey = "row0";
let rowIndex = 0;
templateElements.forEach(el => {
templateElements.forEach((el) => {
rowOfTemplateElements.push(el);
if (rowOfTemplateElements.length === nextRowSize) {
allRowsOfTemplateElements.push(
<div className="tile is-ancestor" key={rowKey}>
{rowOfTemplateElements.map(el => el)}
<div
className="col-sm-6 col-md-4 col-lg-3 d-flex w2l-ani-item"
key={rowKey}
>
{rowOfTemplateElements.map((el) => el)}
</div>
);
rowOfTemplateElements = [];
@@ -120,18 +133,23 @@ function ProfileW2lTemplateListFc(props: Props) {
// Make sure last row is built.
if (rowOfTemplateElements.length !== 0) {
allRowsOfTemplateElements.push(
<div className="tile is-ancestor" key={rowKey}>
{rowOfTemplateElements.map(el => el)}
<div
className="col-sm-6 col-md-4 col-lg-3 d-flex w2l-ani-item"
key={rowKey}
>
{rowOfTemplateElements.map((el) => el)}
</div>
);
rowOfTemplateElements = [];
}
return (
<div>
{allRowsOfTemplateElements.map(el => el)}
</div>
)
<Fade bottom cascade duration={duration} distance={distance}>
<div className="row gy-4 w2l-ani">
{allRowsOfTemplateElements.map((el) => el)}
</div>
</Fade>
);
}
export { ProfileW2lTemplateListFc };
@@ -281,32 +281,28 @@ function TtsModelListFc(props: Props) {
if (!USE_REFRESH) {
return (
<div>
<section className="hero is-small">
<div className="hero-body">
<div className="columns is-vcentered">
<div className="column is-one-third">
<div className="mascot">
<img src="/mascot/kitsune_pose2_black_2000.webp" alt="FakeYou's mascot!" />
<img
src="/mascot/kitsune_pose2_black_2000.webp"
alt="FakeYou's mascot!"
/>
</div>
</div>
<div className="column">
<p className="title">
{t('pages.ttsList.heroTitle')}
</p>
<p className="title">{t("pages.ttsList.heroTitle")}</p>
<p className="subtitle">
<Trans i18nKey="pages.ttsList.heroSubtitle">
Use <strong>FakeYou</strong> deep fake tech to say stuff with your
favorite characters.
Use <strong>FakeYou</strong> deep fake tech to say stuff
with your favorite characters.
</Trans>
</p>
</div>
</div>
</div>
</section>
@@ -321,9 +317,8 @@ function TtsModelListFc(props: Props) {
{twitchTtsNotice}
<form onSubmit={handleFormSubmit} className="main-form">
<MultiDropdownSearch
allTtsCategories={props.allTtsCategories}
<MultiDropdownSearch
allTtsCategories={props.allTtsCategories}
allTtsModels={props.ttsModels}
allTtsCategoriesByTokenMap={props.allTtsCategoriesByTokenMap}
allTtsModelsByTokenMap={props.allTtsModelsByTokenMap}
@@ -334,10 +329,10 @@ function TtsModelListFc(props: Props) {
setSelectedCategories={props.setSelectedCategories}
maybeSelectedTtsModel={props.maybeSelectedTtsModel}
setMaybeSelectedTtsModel={props.setMaybeSelectedTtsModel}
/>
/>
<AutocompleteSearch
allTtsCategories={props.allTtsCategories}
allTtsCategories={props.allTtsCategories}
allTtsModels={props.ttsModels}
allTtsModelsByTokenMap={props.allTtsModelsByTokenMap}
dropdownCategories={props.dropdownCategories}
@@ -346,7 +341,7 @@ function TtsModelListFc(props: Props) {
setSelectedCategories={props.setSelectedCategories}
maybeSelectedTtsModel={props.maybeSelectedTtsModel}
setMaybeSelectedTtsModel={props.setMaybeSelectedTtsModel}
/>
/>
{directViewLink}
@@ -355,11 +350,12 @@ function TtsModelListFc(props: Props) {
<div className="field">
<div className="control">
<textarea
<textarea
onChange={handleChangeText}
className="textarea is-large"
className="textarea is-large"
value={props.textBuffer}
placeholder={t('pages.ttsList.placeholderTextGoesHere')}></textarea>
placeholder={t("pages.ttsList.placeholderTextGoesHere")}
></textarea>
</div>
</div>
@@ -368,44 +364,50 @@ function TtsModelListFc(props: Props) {
<div className="button-group">
<div className="columns is-mobile">
<div className="column has-text-centered">
<button
className="button is-danger is-large"
disabled={remainingCharactersButtonDisabled}>{t('pages.ttsList.buttonSpeak')}</button>
<button
className="button is-danger is-large"
disabled={remainingCharactersButtonDisabled}
>
{t("pages.ttsList.buttonSpeak")}
</button>
</div>
<div className="column has-text-centered">
<button
className="button is-danger is-light is-large"
onClick={handleClearClick}>{t('pages.ttsList.buttonClear')}</button>
<button
className="button is-danger is-light is-large"
onClick={handleClearClick}
>
{t("pages.ttsList.buttonClear")}
</button>
</div>
</div>
</div>
</form>
<br />
<br />
<SessionTtsInferenceResultListFc ttsInferenceJobs={props.ttsInferenceJobs} />
<SessionTtsInferenceResultListFc
ttsInferenceJobs={props.ttsInferenceJobs}
/>
<br />
<br />
<SessionW2lInferenceResultListFc
w2lInferenceJobs={props.w2lInferenceJobs}
/>
/>
<br />
<br />
<SessionW2lTemplateUploadResultListFc
w2lTemplateUploadJobs={props.w2lTemplateUploadJobs}
/>
/>
<br />
<br />
<SessionTtsModelUploadResultListFc
modelUploadJobs={props.ttsModelUploadJobs}
/>
/>
</div>
);
}
@@ -470,7 +472,7 @@ function TtsModelListFc(props: Props) {
<div className="container-panel pt-4 pb-5">
<Fade bottom distance={distance} delay={delay2} duration={duration}>
<div className="panel p-3 p-lg-4 load-hidden mt-5 mt-lg-0">
<div className="panel p-3 p-lg-4 mt-5 mt-lg-0">
<i className="fas fa-volume-high"></i>
<h1 className="panel-title fw-bold">
<FontAwesomeIcon icon={faVolumeUp} className="me-3" />
@@ -4,7 +4,7 @@ import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapp
import { Link } from "react-router-dom";
//import { getRandomInt } from '../../../../v1/api/Utils';
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
import { distance, duration } from "../../../../data/animation";
import { distance, duration, delay } from "../../../../data/animation";
import { USE_REFRESH } from "../../../../Refresh";
const Fade = require("react-reveal/Fade");
@@ -87,12 +87,14 @@ function W2lTemplateListFc(props: Props) {
let link = `/w2l/${t.template_token}`;
templateElements.push(
<div className="tile is-parent" key={t.template_token}>
<article className="tile is-child box">
<Link to={link}>
<img src={url} alt="template" />
</Link>
</article>
<div className="video-card">
<div className="video-card-body d-flex flex-column">
<h6 className="video-card-title mb-1">Title</h6>
<p className="video-card-text">by (name))</p>
</div>
<Link to={link}>
<img className="video-img" src={url} alt="" />
</Link>
</div>
);
});
@@ -105,14 +107,17 @@ function W2lTemplateListFc(props: Props) {
let rowIndex = 0;
//let nextRowSize = getRandomInt(3, 4);
let nextRowSize = 3;
let nextRowSize = 1;
templateElements.forEach((el) => {
templateElements.slice(0, 16).forEach((el) => {
rowOfTemplateElements.push(el);
if (rowOfTemplateElements.length === nextRowSize) {
allRowsOfTemplateElements.push(
<div className="tile is-ancestor" key={rowKey}>
<div
className="col-sm-6 col-md-4 col-lg-3 d-flex w2l-ani-item"
key={rowKey}
>
{rowOfTemplateElements.map((el) => el)}
</div>
);
@@ -131,7 +136,10 @@ function W2lTemplateListFc(props: Props) {
// Make sure last row is built.
if (rowOfTemplateElements.length !== 0) {
allRowsOfTemplateElements.push(
<div className="tile is-ancestor" key={rowKey}>
<div
className="col-sm-6 col-md-4 col-lg-3 d-flex w2l-ani-item"
key={rowKey}
>
{rowOfTemplateElements.map((el) => el)}
</div>
);
@@ -145,7 +153,7 @@ function W2lTemplateListFc(props: Props) {
<p className="lead mb-4">
Pick a template, then you can make it lip sync. If you want to use your
own video or image, you can
<Link to="/contribute">upload it as a template</Link>. You'll then be
<Link to="/contribute"> upload it as a template</Link>. You'll then be
able to use it whenever you want!
</p>
);
@@ -170,42 +178,49 @@ function W2lTemplateListFc(props: Props) {
<br />
{allRowsOfTemplateElements.map(el => el)}
{allRowsOfTemplateElements.map((el) => el)}
<br />
<p>This feature is based on Wav2Lip by by Prajwal, K R and Mukhopadhyay,
Rudrabha and Namboodiri, Vinay P. and Jawahar, C.V.</p>
<p>
This feature is based on Wav2Lip by by Prajwal, K R and Mukhopadhyay,
Rudrabha and Namboodiri, Vinay P. and Jawahar, C.V.
</p>
<br />
</div>
);
}
return (
<div>
<Fade bottom cascade duration={duration} distance={distance}>
<div className="container py-5">
<div className="container py-5 px-md-4 px-lg-5 px-xl-3">
<Fade bottom cascade duration={duration} distance={distance}>
<div>
<h1 className="display-5 fw-bold mb-3">Video Lip Sync Templates</h1>
{extraDetails}
</div>
<div>{extraDetails}</div>
</div>
</Fade>
<div className="container">
{allRowsOfTemplateElements.map((el) => el)}
</Fade>
</div>
<div className="container">
<div className="container-panel pb-4">
<Fade bottom duration={duration} distance={distance} delay={delay}>
<div className="panel p-3 p-lg-4">
<Fade bottom cascade duration={duration} distance={distance}>
<div className="row gy-4 w2l-ani">
{allRowsOfTemplateElements.map((el) => el)}
</div>
</Fade>
</div>
</Fade>
</div>
<div className="container pb-5">
<p>
This feature is based on Wav2Lip by by Prajwal, K R and Mukhopadhyay,
Rudrabha and Namboodiri, Vinay P. and Jawahar, C.V.
</p>
</div>
<br />
</div>
);
}
+19
View File
@@ -2335,6 +2335,11 @@
schema-utils "^3.0.0"
source-map "^0.7.3"
"@popperjs/core@^2.9.0":
version "2.11.5"
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.5.tgz#db5a11bf66bdab39569719555b0f76e138d7bd64"
integrity sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==
"@rollup/plugin-babel@^5.2.0":
version "5.3.0"
resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.0.tgz#9cb1c5146ddd6a4968ad96f209c50c62f92f9879"
@@ -2587,6 +2592,13 @@
resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-7.2.1.tgz#2ad4e844175a3738cb9e7064be5ea070b8863a1c"
integrity sha512-oZ0Ib5I4Z2pUEcoo95cT1cr6slco9WY7yiPpG+RGNkj8YcYgJnM7pXmYmorNOReh8MIGcKSqXyeGjxnr8YiZbA==
"@tippyjs/react@^4.2.6":
version "4.2.6"
resolved "https://registry.yarnpkg.com/@tippyjs/react/-/react-4.2.6.tgz#971677a599bf663f20bb1c60a62b9555b749cc71"
integrity sha512-91RicDR+H7oDSyPycI13q3b7o4O60wa2oRbjlz2fyRLmHImc4vyDwuUP8NtZaN0VARJY5hybvDYrFzhY9+Lbyw==
dependencies:
tippy.js "^6.3.1"
"@tootallnate/once@1":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
@@ -13313,6 +13325,13 @@ tiny-warning@^1.0.0, tiny-warning@^1.0.3:
resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
tippy.js@^6.3.1:
version "6.3.7"
resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-6.3.7.tgz#8ccfb651d642010ed9a32ff29b0e9e19c5b8c61c"
integrity sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==
dependencies:
"@popperjs/core" "^2.9.0"
tmp@^0.0.33:
version "0.0.33"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"