mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-04 17:38:10 +00:00
Merge pull request #36 from storytold/echelon-rebase-bflat-fakeyou-profile
Rebasing bflat's bflat-fakeyou-profile on master
This commit is contained in:
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 };
|
||||
@@ -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 |
@@ -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 |
@@ -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}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
@@ -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";
|
||||
@@ -49,7 +49,7 @@
|
||||
},
|
||||
"move": {
|
||||
"enable": true,
|
||||
"speed": 1,
|
||||
"speed": 2.5,
|
||||
"direction": "top",
|
||||
"random": true,
|
||||
"straight": false,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
|
||||
<Link to="/video">{t('coreUi.footerNav.video')}</Link>
|
||||
<Link to="/video">{t("coreUi.footerNav.video")}</Link>
|
||||
|
|
||||
<Link to="/contribute">{t('coreUi.footerNav.upload')}</Link>
|
||||
<Link to="/contribute">{t("coreUi.footerNav.upload")}</Link>
|
||||
|
|
||||
<Link to="/leaderboard">{t('coreUi.footerNav.leaderboard')}</Link>
|
||||
<Link to="/leaderboard">{t("coreUi.footerNav.leaderboard")}</Link>
|
||||
|
|
||||
<Link to={FrontendUrlConfig.patronsPage()}>{t('coreUi.footerNav.patrons')}</Link>
|
||||
<Link to={FrontendUrlConfig.patronsPage()}>
|
||||
{t("coreUi.footerNav.patrons")}
|
||||
</Link>
|
||||
|
|
||||
<Link to="/firehose">{t('coreUi.footerNav.feed')}</Link>
|
||||
<Link to="/firehose">{t("coreUi.footerNav.feed")}</Link>
|
||||
|
|
||||
<a href={FrontendUrlConfig.developerDocs()}>{t('coreUi.footerNav.apiDocs')}</a>
|
||||
<a href={FrontendUrlConfig.developerDocs()}>
|
||||
{t("coreUi.footerNav.apiDocs")}
|
||||
</a>
|
||||
|
|
||||
<Link to="/about">{t('coreUi.footerNav.aboutUs')}</Link>
|
||||
<Link to="/about">{t("coreUi.footerNav.aboutUs")}</Link>
|
||||
|
|
||||
<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>
|
||||
|
|
||||
<Link to="/video">Video</Link>
|
||||
|
|
||||
<Link to="/contribute">Upload</Link>
|
||||
|
|
||||
<Link to="/leaderboard">Leaderboard</Link>
|
||||
|
|
||||
<Link to={FrontendUrlConfig.patronsPage()}>Patrons</Link>
|
||||
|
|
||||
<Link to="/firehose">Feed</Link>
|
||||
|
|
||||
<a href={FrontendUrlConfig.developerDocs()}>API Docs</a>
|
||||
|
|
||||
<Link to="/about">About Us</Link>
|
||||
|
|
||||
<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>
|
||||
|
|
||||
<Link to="/pricing">{t('coreUi.footerNav.pricing')}</Link>
|
||||
|
|
||||
<Link to="/video">{t('coreUi.footerNav.video')}</Link>
|
||||
|
|
||||
<div>
|
||||
<Link to="/contribute">Upload</Link>
|
||||
</div>
|
||||
|
|
||||
<div>
|
||||
<Link to="/leaderboard">Leaderboard</Link>
|
||||
</div>
|
||||
|
|
||||
<div>
|
||||
<Link to={FrontendUrlConfig.patronsPage()}>Patrons</Link>
|
||||
</div>
|
||||
|
|
||||
<div>
|
||||
<Link to="/firehose">Feed</Link>
|
||||
</div>
|
||||
|
|
||||
<div>
|
||||
<a href={FrontendUrlConfig.developerDocs()}>API Docs</a>
|
||||
</div>
|
||||
|
|
||||
<div>
|
||||
<Link to="/about">About Us</Link>
|
||||
</div>
|
||||
|
|
||||
<div>
|
||||
<Link to="/terms">Terms of Use</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="d-flex justify-content-center gap-4">
|
||||
|
||||
@@ -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 My Voice!</Link>
|
||||
>
|
||||
Clone My 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} /> {t('coreUi.topNav.contributeUpload')}</Link>
|
||||
>
|
||||
<FontAwesomeIcon icon={faUpload} />
|
||||
{t("coreUi.topNav.contributeUpload")}
|
||||
</Link>
|
||||
|
||||
<Link to="/leaderboard"
|
||||
<Link
|
||||
to="/leaderboard"
|
||||
className="navbar-item"
|
||||
onClick={() => closeHamburger()}
|
||||
><FontAwesomeIcon icon={faTrophy} /> {t('coreUi.topNav.leaderboard')}</Link>
|
||||
>
|
||||
<FontAwesomeIcon icon={faTrophy} />
|
||||
{t("coreUi.topNav.leaderboard")}
|
||||
</Link>
|
||||
|
||||
<Link to={FrontendUrlConfig.patronsPage()}
|
||||
<Link
|
||||
to={FrontendUrlConfig.patronsPage()}
|
||||
className="navbar-item"
|
||||
onClick={() => closeHamburger()}
|
||||
><FontAwesomeIcon icon={faPatreon}/> {t('coreUi.topNav.patrons')}</Link>
|
||||
>
|
||||
<FontAwesomeIcon icon={faPatreon} />
|
||||
{t("coreUi.topNav.patrons")}
|
||||
</Link>
|
||||
|
||||
<Link to="/firehose"
|
||||
<Link
|
||||
to="/firehose"
|
||||
className="navbar-item"
|
||||
onClick={() => closeHamburger()}
|
||||
><FontAwesomeIcon icon={faList} /> {t('coreUi.topNav.feed')}</Link>
|
||||
>
|
||||
<FontAwesomeIcon icon={faList} />
|
||||
{t("coreUi.topNav.feed")}
|
||||
</Link>
|
||||
|
||||
<hr className="navbar-divider" />
|
||||
|
||||
<Link to={myDataLink}
|
||||
<Link
|
||||
to={myDataLink}
|
||||
className="navbar-item"
|
||||
onClick={() => closeHamburger()}
|
||||
><FontAwesomeIcon icon={faUser} /> {t('coreUi.topNav.myData')}</Link>
|
||||
>
|
||||
<FontAwesomeIcon icon={faUser} />
|
||||
{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} /> {t('coreUi.topNav.aboutUs')}</Link>
|
||||
>
|
||||
<FontAwesomeIcon icon={faGrinBeamSweat} />
|
||||
{t("coreUi.topNav.aboutUs")}
|
||||
</Link>
|
||||
|
||||
<Link to={FrontendUrlConfig.termsPage()}
|
||||
<Link
|
||||
to={FrontendUrlConfig.termsPage()}
|
||||
className="navbar-item"
|
||||
onClick={() => closeHamburger()}
|
||||
><FontAwesomeIcon icon={faGavel} /> {t('coreUi.topNav.terms')}</Link>
|
||||
>
|
||||
<FontAwesomeIcon icon={faGavel} />
|
||||
{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>
|
||||
”Deep fakes” 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>.
|
||||
”Deep fakes” 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> and
|
||||
Hacker News. Say hi.
|
||||
Reach out to "echelon" on{" "}
|
||||
<a
|
||||
href="https://twitter.com/echelon"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Twitter
|
||||
</a>
|
||||
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… whatever you want!</h1>
|
||||
|
||||
<h1 className="subtitle is-4">
|
||||
For Music, Videos, Twitch Rewards, API, Friends, Family…
|
||||
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} />
|
||||
My own voice
|
||||
</label>
|
||||
<br />
|
||||
<label className="radio">
|
||||
<input type="radio" name="subject" value="family" onChange={handleSubjectChange} />
|
||||
A family member's voice
|
||||
</label>
|
||||
<br />
|
||||
<label className="radio">
|
||||
<input type="radio" name="subject" value="client" onChange={handleSubjectChange} />
|
||||
A client's voice
|
||||
</label>
|
||||
<br />
|
||||
<label className="radio">
|
||||
<input type="radio" name="subject" value="3rd" onChange={handleSubjectChange} />
|
||||
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} />
|
||||
For Music (for creating new songs)
|
||||
</label>
|
||||
|
||||
<br />
|
||||
|
||||
<label className="checkbox">
|
||||
<input type="checkbox" checked={isForGames} onChange={handleIsForGamesChange} />
|
||||
For Games (because NPCs won't talk by themselves)
|
||||
</label>
|
||||
|
||||
<br />
|
||||
|
||||
<label className="checkbox">
|
||||
<input type="checkbox" checked={isForTwitchTts} onChange={handleIsForTwitchTtsChange} />
|
||||
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} />
|
||||
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} />
|
||||
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" />
|
||||
This is for private use for just me
|
||||
</label>
|
||||
|
||||
<br />
|
||||
|
||||
<label className="checkbox">
|
||||
<input type="checkbox" checked={isForPrivateUse} onChange={handleIsForPrivateUseChange} />
|
||||
This is for private use amongst a group of people
|
||||
</label>
|
||||
|
||||
<br />
|
||||
|
||||
<label className="checkbox">
|
||||
<input type="checkbox" checked={isForPublicUse} onChange={handleIsForPublicUseChange} />
|
||||
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} />
|
||||
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} />
|
||||
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! <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}
|
||||
/>
|
||||
My own voice
|
||||
</label>
|
||||
<label className="form-check-label">
|
||||
<input
|
||||
type="radio"
|
||||
className="form-check-input"
|
||||
name="subject"
|
||||
value="family"
|
||||
onChange={handleSubjectChange}
|
||||
/>
|
||||
A family member's voice
|
||||
</label>
|
||||
<label className="form-check-label">
|
||||
<input
|
||||
type="radio"
|
||||
className="form-check-input"
|
||||
name="subject"
|
||||
value="client"
|
||||
onChange={handleSubjectChange}
|
||||
/>
|
||||
A client's voice
|
||||
</label>
|
||||
<label className="form-check-label">
|
||||
<input
|
||||
type="radio"
|
||||
className="form-check-input"
|
||||
name="subject"
|
||||
value="3rd"
|
||||
onChange={handleSubjectChange}
|
||||
/>
|
||||
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}
|
||||
/>
|
||||
For Music (for creating new songs)
|
||||
</label>
|
||||
|
||||
<label className="form-check-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="form-check-input"
|
||||
checked={isForGames}
|
||||
onChange={handleIsForGamesChange}
|
||||
/>
|
||||
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}
|
||||
/>
|
||||
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}
|
||||
/>
|
||||
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}
|
||||
/>
|
||||
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" />
|
||||
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}
|
||||
/>
|
||||
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}
|
||||
/>
|
||||
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}
|
||||
/>
|
||||
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}
|
||||
/>
|
||||
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!
|
||||
<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}
|
||||
|
||||
{userLink}
|
||||
signed up for FakeYou!
|
||||
signed up for FakeYou!
|
||||
</span>
|
||||
);
|
||||
break;
|
||||
case "user_badge_granted":
|
||||
inner = (
|
||||
<span>
|
||||
<FontAwesomeIcon icon={faAward} />
|
||||
{gravatar}
|
||||
|
||||
{userLink}
|
||||
got a badge!
|
||||
got a badge!
|
||||
</span>
|
||||
);
|
||||
break;
|
||||
case "tts_model_upload_started":
|
||||
inner = (
|
||||
<span>
|
||||
<FontAwesomeIcon icon={faPlay} className="me-3" />
|
||||
{gravatar}
|
||||
|
||||
{userLink}
|
||||
started TTS model upload
|
||||
started TTS model upload
|
||||
</span>
|
||||
);
|
||||
break;
|
||||
case "tts_model_upload_completed":
|
||||
inner = (
|
||||
<span>
|
||||
<FontAwesomeIcon icon={faFlagCheckered} className="me-3" />
|
||||
{gravatar}
|
||||
|
||||
{userLink}
|
||||
completed TTS model upload
|
||||
completed TTS model upload
|
||||
</span>
|
||||
);
|
||||
break;
|
||||
case "tts_inference_started":
|
||||
inner = (
|
||||
<span>
|
||||
<FontAwesomeIcon icon={faPlay} className="me-3" />
|
||||
{gravatar}
|
||||
|
||||
{userLink}
|
||||
started TTS
|
||||
started TTS
|
||||
</span>
|
||||
);
|
||||
break;
|
||||
case "tts_inference_completed":
|
||||
inner = (
|
||||
<span>
|
||||
<FontAwesomeIcon icon={faFlagCheckered} className="me-3" />
|
||||
{gravatar}
|
||||
|
||||
{userLink}
|
||||
completed TTS
|
||||
completed TTS
|
||||
</span>
|
||||
);
|
||||
break;
|
||||
case "w2l_template_upload_started":
|
||||
inner = (
|
||||
<span>
|
||||
<FontAwesomeIcon icon={faPlay} className="me-3" />
|
||||
{gravatar}
|
||||
|
||||
{userLink}
|
||||
started uploading a lipsync template.
|
||||
started uploading a lipsync template.
|
||||
</span>
|
||||
);
|
||||
break;
|
||||
case "w2l_template_upload_completed":
|
||||
inner = (
|
||||
<span>
|
||||
<FontAwesomeIcon icon={faFlagCheckered} className="me-3" />
|
||||
{gravatar}
|
||||
|
||||
{userLink}
|
||||
finished uploading a lipsync template.
|
||||
finished uploading a lipsync template.
|
||||
</span>
|
||||
);
|
||||
break;
|
||||
case "w2l_inference_started":
|
||||
inner = (
|
||||
<span>
|
||||
<FontAwesomeIcon icon={faPlay} className="me-3" />
|
||||
{gravatar}
|
||||
|
||||
{userLink}
|
||||
started a W2L lipsync video
|
||||
started a W2L lipsync video
|
||||
</span>
|
||||
);
|
||||
break;
|
||||
case "w2l_inference_completed":
|
||||
inner = (
|
||||
<span>
|
||||
<FontAwesomeIcon icon={faFlagCheckered} className="me-3" />
|
||||
{gravatar}
|
||||
|
||||
{userLink}
|
||||
completed a W2L lipsync video
|
||||
completed a W2L lipsync video
|
||||
</span>
|
||||
);
|
||||
break;
|
||||
case "twitter_mention":
|
||||
inner = (
|
||||
<span>
|
||||
<FontAwesomeIcon icon={faTwitter} />
|
||||
{gravatar}
|
||||
|
||||
{userLink}
|
||||
mentioned us on twitter!
|
||||
mentioned us on twitter!
|
||||
</span>
|
||||
);
|
||||
break;
|
||||
case "twitter_retweet":
|
||||
inner = (
|
||||
<span>
|
||||
<FontAwesomeIcon icon={faTwitter} />
|
||||
{gravatar}
|
||||
|
||||
{userLink}
|
||||
retweeted us!
|
||||
retweeted us!
|
||||
</span>
|
||||
);
|
||||
break;
|
||||
case "discord_join":
|
||||
inner = (
|
||||
<span>
|
||||
<FontAwesomeIcon icon={faDiscord} />
|
||||
{gravatar}
|
||||
|
||||
{userLink}
|
||||
joined discord!
|
||||
joined discord!
|
||||
</span>
|
||||
);
|
||||
break;
|
||||
case "discord_message":
|
||||
inner = (
|
||||
<span>
|
||||
<FontAwesomeIcon icon={faDiscord} />
|
||||
{gravatar}
|
||||
|
||||
{userLink}
|
||||
sent a discord message
|
||||
sent a discord message
|
||||
</span>
|
||||
);
|
||||
break;
|
||||
case "twitch_subscribe":
|
||||
inner = (
|
||||
<span>
|
||||
<FontAwesomeIcon icon={faTwitch} />
|
||||
{gravatar}
|
||||
|
||||
{userLink}
|
||||
subscribed to us on twitch!
|
||||
subscribed to us on twitch!
|
||||
</span>
|
||||
);
|
||||
break;
|
||||
case "twitch_follow":
|
||||
inner = (
|
||||
<span>
|
||||
<FontAwesomeIcon icon={faTwitch} />
|
||||
{gravatar}
|
||||
|
||||
{userLink}
|
||||
followed us on twitch!
|
||||
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} — ${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} — ${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} — ${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 & 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 & 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 & 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 & 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 {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 {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 {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 {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" />
|
||||
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} /> 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} /> 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} /> 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} /> 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} /> 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} /> 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} /> 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 <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 <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 <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 <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 <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 <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 <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 };
|
||||
+145
-93
@@ -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" />
|
||||
|
||||
<FontAwesomeIcon icon={faCirclePlay} className="me-2" />
|
||||
{text}
|
||||
</Link>
|
||||
|
||||
</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 & 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 & 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}>< Get newer</button>
|
||||
<button className="button is-info" onClick={() => getPage(nextCursor, false)} disabled={nextDisabled}>Get older></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 };
|
||||
+152
-97
@@ -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 & Download" />
|
||||
|
||||
<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}>< Get newer</button>
|
||||
<button className="button is-info" onClick={() => getPage(nextCursor, false)} disabled={nextDisabled}>Get older></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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in new issue
Block a user