mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-12 04:42:16 +00:00
Merge branch 'feature/mvp' of https://github.com/storytold/storyteller-frontend into feature/mvp
This commit is contained in:
commit
e9ed1532e1
68 files changed
+1923
-291
No files matched your search
@@ -34,4 +34,3 @@ storyteller-frontend
|
||||
|
||||
Documentation [lives here](./_develop).
|
||||
|
||||
|
||||
@@ -39,6 +39,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-spring/types": "^9.7.3",
|
||||
"@typeform/embed": "^4.8.1",
|
||||
"@typeform/embed-react": "^3.15.0",
|
||||
"local-storage": "^2.0.0",
|
||||
"moment": "^2.29.4",
|
||||
"universal-cookie": "^6.1.1",
|
||||
"yarn": "^1.22.19"
|
||||
|
||||
@@ -20,6 +20,7 @@ export interface UserInfo {
|
||||
|
||||
// Rollout feature flags
|
||||
can_access_studio: boolean,
|
||||
maybe_feature_flags: string[],
|
||||
|
||||
// Usage
|
||||
can_use_tts: boolean,
|
||||
|
||||
@@ -56,10 +56,17 @@ export class SessionWrapper {
|
||||
public canAccessStudio(): boolean {
|
||||
const hostnameAllowed = StudioRolloutHostnameAllowed();
|
||||
const userAllowed =
|
||||
this.sessionStateResponse?.user?.can_access_studio || false;
|
||||
this.sessionStateResponse?.user?.can_access_studio ||
|
||||
this.sessionStateResponse?.user?.maybe_feature_flags.includes("studio") ||
|
||||
false;
|
||||
return hostnameAllowed && userAllowed;
|
||||
}
|
||||
|
||||
// Feature flag rollout for Video Style Transfer
|
||||
public canAccessVideoStyleTransfer(): boolean {
|
||||
return this.sessionStateResponse?.user?.maybe_feature_flags.includes("video_style_transfer") || false;
|
||||
}
|
||||
|
||||
// Username is all lowercase
|
||||
public getUsername(): string | undefined {
|
||||
return this.sessionStateResponse?.user?.username;
|
||||
|
||||
@@ -75,6 +75,7 @@
|
||||
"react-select": "^5.7.0",
|
||||
"react-share": "^4.4.1",
|
||||
"react-twitch-embed": "^3.0.1",
|
||||
"react-typeform-embed": "^1.0.8",
|
||||
"react-wavesurfer.js": "^0.0.8",
|
||||
"sass": "^1.57.1",
|
||||
"swiper": "^11.0.5",
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -32,9 +32,9 @@ export default function DropdownMenu({
|
||||
|
||||
return (
|
||||
<a.div style={fadeIn} className={`fy-dropdown-menu ${className}`.trim()}>
|
||||
{items.map(item => (
|
||||
<>
|
||||
{item.onClick ? (
|
||||
{items.map(item => {
|
||||
if(item.onClick){
|
||||
return(
|
||||
<div
|
||||
key={item.id}
|
||||
onClick={e => {
|
||||
@@ -53,27 +53,26 @@ export default function DropdownMenu({
|
||||
{item.name}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{item.link && (
|
||||
<Link
|
||||
key={item.id}
|
||||
to={item.link}
|
||||
className="fy-dropdown-item"
|
||||
onClick={() => onClose()}
|
||||
>
|
||||
<span>
|
||||
{item.icon && (
|
||||
<FontAwesomeIcon icon={item.icon} className="me-2 fs-7" />
|
||||
)}
|
||||
{item.name}
|
||||
</span>
|
||||
</Link>
|
||||
);
|
||||
} else if (item.link) {
|
||||
return(
|
||||
<Link
|
||||
key={item.id}
|
||||
to={item.link}
|
||||
className="fy-dropdown-item"
|
||||
onClick={() => onClose()}
|
||||
>
|
||||
<span>
|
||||
{item.icon && (
|
||||
<FontAwesomeIcon icon={item.icon} className="me-2 fs-7" />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
))}
|
||||
{item.name}
|
||||
</span>
|
||||
</Link>
|
||||
);
|
||||
}//end if
|
||||
return null;
|
||||
})} {/*end items map */}
|
||||
</a.div>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import IframeResizer from "iframe-resizer-react";
|
||||
import React, { useCallback, useEffect, useRef } from "react";
|
||||
//import IframeResizer from "iframe-resizer-react";
|
||||
import "./Scene3D.scss";
|
||||
|
||||
// See the following page for Storyteller Engine's query param documentation:
|
||||
@@ -23,7 +23,70 @@ export default function Scene3D({
|
||||
urlParams,
|
||||
className,
|
||||
}: Scene3DProps) {
|
||||
let engineUrl = `https://engine.fakeyou.com?mode=${mode}`;
|
||||
const engineBaseUrl = "https://engine.fakeyou.com";
|
||||
|
||||
const iframeRef = useRef<HTMLIFrameElement>(null);
|
||||
|
||||
const onMessage = useCallback((event: MessageEvent) => {
|
||||
console.log("engine message received", event.data, event);
|
||||
|
||||
if (event.origin !== engineBaseUrl)
|
||||
return;
|
||||
|
||||
if (event.data === "studio-ready") {
|
||||
console.log("studio-ready message (1)");
|
||||
|
||||
const studio = iframeRef.current?.contentWindow;
|
||||
if (!studio) return;
|
||||
|
||||
console.log("studio-ready message (2)");
|
||||
|
||||
studio.postMessage("save-scene", engineBaseUrl);
|
||||
} else if (
|
||||
typeof event.data === "string"
|
||||
&& event.data.startsWith("scene-saved:")
|
||||
) {
|
||||
const mediaToken = event.data.match(/scene-saved:(.+)/)?.[1];
|
||||
console.log("saved scene media token:", mediaToken);
|
||||
|
||||
} else if (event.data === "scene-save-failed") {
|
||||
console.error("Failed to save the scene!");
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
/*
|
||||
if (iframeRef && iframeRef.current) {
|
||||
console.log("installing event listener for messages");
|
||||
|
||||
// NB: Compiler complains about ref going out of scope before destructor is called.
|
||||
let ref = iframeRef.current;
|
||||
|
||||
// TODO(bt,2024-03-08): Fix typescript 'any' hack
|
||||
(ref as any).addEventListener("message", onMessage);
|
||||
|
||||
return () => {
|
||||
console.log("uninstalling event listener for messages");
|
||||
|
||||
// TODO(bt,2024-03-08): Fix typescript 'any' hack
|
||||
(ref as any).removeEventListener("message", onMessage);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
console.log("installing event listener for messages");
|
||||
window.addEventListener("message", onMessage);
|
||||
|
||||
return () => {
|
||||
console.log("uninstalling event listener for messages");
|
||||
window.removeEventListener("message", onMessage);
|
||||
}
|
||||
}, [onMessage]);
|
||||
//}, [onMessage, iframeRef]);
|
||||
|
||||
|
||||
let engineUrl = `${engineBaseUrl}/?mode=${mode}`;
|
||||
|
||||
if (sceneMediaFileToken) {
|
||||
const sceneUrlRef = `remote://${sceneMediaFileToken}.scn.ron`;
|
||||
@@ -32,13 +95,22 @@ export default function Scene3D({
|
||||
engineUrl += `&objectId=${objectId}`;
|
||||
}
|
||||
|
||||
console.log('installing iframe engine and callbacks');
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`${
|
||||
fullScreen ? "fy-scene-3d-fullscreen" : "fy-scene-3d-default"
|
||||
} ${className ? className : ""}`.trim()}
|
||||
>
|
||||
<IframeResizer src={engineUrl} width="100%" height="100%" id="" />
|
||||
{/*<IframeResizer src={engineUrl} width="100%" height="100%" id="" />*/}
|
||||
<iframe
|
||||
title="Storyteller Engine"
|
||||
ref={iframeRef}
|
||||
src={engineUrl}
|
||||
width="100%"
|
||||
height="100%"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -16,12 +16,17 @@
|
||||
padding-top: 7.25px !important;
|
||||
padding-bottom: 7.25px !important;
|
||||
border-radius: 0.5rem 0 0 0.5rem !important;
|
||||
background-color: $input-dark !important;
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
|
||||
&::placeholder {
|
||||
color: rgba($white, 0.3) !important;
|
||||
}
|
||||
|
||||
&.landing-search {
|
||||
background-color: rgba($input-dark, 0.5) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+9
-1
@@ -32,11 +32,18 @@ export default memo(function SelectModalWrapper({
|
||||
inputLabel: string;
|
||||
onSelect: (data:SelectModalData) => void;
|
||||
}) {
|
||||
const [onSelectTimeStamp, setOnSelectTimeStamp] = useState<Date>(new Date());
|
||||
const tabs = [{
|
||||
label: "All Videos",
|
||||
content: (
|
||||
<div className="searcher-container in-modal m-4" id="allVideos">
|
||||
<VideoTabsContent debug={debug} onSelect={onSelect}/>
|
||||
<VideoTabsContent
|
||||
debug={debug}
|
||||
onSelect={(data)=>{
|
||||
onSelect(data)
|
||||
setOnSelectTimeStamp(new Date());
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}]
|
||||
@@ -45,6 +52,7 @@ export default memo(function SelectModalWrapper({
|
||||
modalTitle={modalTitle}
|
||||
label={inputLabel}
|
||||
value={value}
|
||||
forcedClose={onSelectTimeStamp}
|
||||
onClear={()=>{onSelect({title:"",token:""})}}
|
||||
>
|
||||
<NonRouteTabs tabs={tabs} />
|
||||
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
@import "scss/colours.scss";
|
||||
|
||||
.selection-bubbles-wrapper {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: all 0.4s;
|
||||
|
||||
.scroll-indicator {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
top: 50%;
|
||||
right: 0px;
|
||||
transform: translateY(-50%);
|
||||
color: rgba($white, 0.3);
|
||||
pointer-events: none;
|
||||
transition: all 0.4s;
|
||||
}
|
||||
|
||||
.scroll-indicator-left {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
top: 50%;
|
||||
left: 0px;
|
||||
transform: translateY(-50%);
|
||||
color: rgba($white, 0.3);
|
||||
pointer-events: none;
|
||||
transition: all 0.4s;
|
||||
}
|
||||
|
||||
&:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 60px;
|
||||
background: linear-gradient(to right, rgba($panel, 0), $panel);
|
||||
pointer-events: none;
|
||||
display: none;
|
||||
transition: all 0.4s;
|
||||
}
|
||||
|
||||
&:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 60px;
|
||||
background: linear-gradient(to left, rgba($panel, 0), $panel);
|
||||
pointer-events: none;
|
||||
display: none;
|
||||
transition: all 0.4s;
|
||||
}
|
||||
|
||||
&.show-gradient:after {
|
||||
display: block;
|
||||
}
|
||||
|
||||
&.show-left-gradient:before {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.selection-bubbles {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
scrollbar-width: none;
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
flex-wrap: wrap;
|
||||
justify-content: start;
|
||||
overflow-x: visible;
|
||||
}
|
||||
|
||||
&.no-side-scroll {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
.bubble-button {
|
||||
flex: 0 0 auto;
|
||||
border: 2px solid darken($secondary, 3%);
|
||||
padding: 8px 16px;
|
||||
border-radius: 0.5rem;
|
||||
background-color: darken($secondary, 3%);
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
transition: all 0.2s ease;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
color: rgba($white, 0.75);
|
||||
white-space: nowrap;
|
||||
|
||||
&.bubble-card {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
aspect-ratio: 1 / 1;
|
||||
padding: 0;
|
||||
|
||||
.bubble-image {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.bubble-text {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
color: white;
|
||||
padding-left: 10px;
|
||||
padding-bottom: 5px;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 50%;
|
||||
background: linear-gradient(to top, rgba(0, 0, 0, 0.7), transparent);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
&.selected::after {
|
||||
background: linear-gradient(
|
||||
to top,
|
||||
rgba(desaturate(darken($primary, 20%), 30%), 70%),
|
||||
transparent
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $secondary;
|
||||
border-color: $secondary;
|
||||
|
||||
&.fill {
|
||||
border-color: $primary;
|
||||
background-color: $primary;
|
||||
}
|
||||
}
|
||||
|
||||
&.selected {
|
||||
color: $white;
|
||||
border-color: $primary;
|
||||
background-color: lighten($secondary, 6%);
|
||||
|
||||
&.fill {
|
||||
background-color: $primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.mobile-flex-nowrap {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
import React, { useState, useRef, useEffect } from "react";
|
||||
import "./SelectionBubblesV2.scss";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faChevronLeft,
|
||||
faChevronRight,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
|
||||
interface Option {
|
||||
label: string;
|
||||
imageUrl?: string;
|
||||
token?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
interface SelectionBubbleProps {
|
||||
options: Option[];
|
||||
onSelect: (selected: any) => void;
|
||||
selectedStyle?: "outline" | "fill";
|
||||
mobileSideScroll?: boolean;
|
||||
variant?: "default" | "card";
|
||||
}
|
||||
|
||||
export default function SelectionBubblesV2({
|
||||
options,
|
||||
onSelect,
|
||||
selectedStyle = "outline",
|
||||
mobileSideScroll = false,
|
||||
variant = "default",
|
||||
}: SelectionBubbleProps) {
|
||||
const [selectedOption, setSelectedOption] = useState<string | null>(
|
||||
options.length > 0 ? options[0].label : null
|
||||
);
|
||||
const [showGradient, setShowGradient] = useState(true);
|
||||
const [showLeftGradient, setShowLeftGradient] = useState(false);
|
||||
const bubblesRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const handleSelect = (
|
||||
event: React.MouseEvent<HTMLButtonElement> | null,
|
||||
optionLabel: string
|
||||
) => {
|
||||
event?.preventDefault();
|
||||
setSelectedOption(optionLabel);
|
||||
onSelect(optionLabel);
|
||||
};
|
||||
|
||||
const handleScroll = () => {
|
||||
if (bubblesRef.current) {
|
||||
const { scrollLeft, scrollWidth, clientWidth } = bubblesRef.current;
|
||||
const isAtEnd = scrollWidth - Math.round(scrollLeft + clientWidth) <= 1;
|
||||
const isAtStart = scrollLeft <= 1;
|
||||
const isMobile = window.innerWidth <= 768;
|
||||
setShowGradient(!isAtEnd && isMobile);
|
||||
setShowLeftGradient(!isAtStart && isMobile);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (bubblesRef.current) {
|
||||
bubblesRef.current.addEventListener("scroll", handleScroll);
|
||||
handleScroll();
|
||||
return () =>
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
bubblesRef.current?.removeEventListener("scroll", handleScroll);
|
||||
}
|
||||
}, [options]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleResize = () => handleScroll();
|
||||
window.addEventListener("resize", handleResize);
|
||||
return () => window.removeEventListener("resize", handleResize);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (options.length > 0 && selectedOption !== null) {
|
||||
onSelect(options[0].label);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`selection-bubbles-wrapper ${
|
||||
showGradient && mobileSideScroll ? "show-gradient" : ""
|
||||
} ${
|
||||
showLeftGradient && mobileSideScroll ? "show-left-gradient" : ""
|
||||
}`.trim()}
|
||||
>
|
||||
{showLeftGradient && mobileSideScroll && (
|
||||
<FontAwesomeIcon
|
||||
icon={faChevronLeft}
|
||||
className="scroll-indicator-left fs-5"
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className={`selection-bubbles ${
|
||||
!mobileSideScroll ? "no-side-scroll" : ""
|
||||
}`.trim()}
|
||||
ref={bubblesRef}
|
||||
>
|
||||
{variant === "default" ? (
|
||||
<>
|
||||
{options.map(({ label }) => (
|
||||
<button
|
||||
key={label}
|
||||
className={`bubble-button ${
|
||||
selectedOption === label
|
||||
? `selected ${selectedStyle === "fill" ? "fill" : ""}`
|
||||
: ""
|
||||
}`.trim()}
|
||||
onClick={event => handleSelect(event, label)}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
<div className="w-100">
|
||||
<div
|
||||
className={`row g-2 ${
|
||||
mobileSideScroll ? "mobile-flex-nowrap" : ""
|
||||
}`}
|
||||
>
|
||||
{options.map(({ label, imageUrl }) => (
|
||||
<div className="col-4 col-lg-3">
|
||||
<button
|
||||
key={label}
|
||||
className={`bubble-button ${
|
||||
selectedOption === label
|
||||
? `selected ${selectedStyle === "fill" ? "fill" : ""}`
|
||||
: ""
|
||||
} ${variant === "card" ? "bubble-card" : ""}`.trim()}
|
||||
onClick={event => handleSelect(event, label)}
|
||||
>
|
||||
{variant === "card" && imageUrl && (
|
||||
<img
|
||||
src={imageUrl}
|
||||
alt={label}
|
||||
className="bubble-image"
|
||||
draggable="false"
|
||||
/>
|
||||
)}
|
||||
<span className="bubble-text">{label}</span>
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{showGradient && mobileSideScroll && (
|
||||
<FontAwesomeIcon
|
||||
icon={faChevronRight}
|
||||
className="scroll-indicator fs-5"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "./SelectionBubblesV2";
|
||||
@@ -0,0 +1,44 @@
|
||||
import React from "react";
|
||||
import { PopupButton } from "@typeform/embed-react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faArrowRight, faPartyHorn } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { get, set } from "local-storage";
|
||||
import Button from "../Button";
|
||||
|
||||
interface TypeformButtonProps {
|
||||
formId: string;
|
||||
label: string;
|
||||
labelSubmitted: string;
|
||||
}
|
||||
|
||||
export default function TypeformButton({
|
||||
formId,
|
||||
label,
|
||||
labelSubmitted,
|
||||
}: TypeformButtonProps) {
|
||||
return (
|
||||
<>
|
||||
{!get<boolean>("isSubmit") ? (
|
||||
<PopupButton
|
||||
id={formId}
|
||||
className="button button-primary button-small"
|
||||
onSubmit={() => {
|
||||
set<boolean>("isSubmit", true);
|
||||
console.log("Form submitted!");
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
<FontAwesomeIcon icon={faArrowRight} className="ms-2" />
|
||||
</PopupButton>
|
||||
) : (
|
||||
<Button
|
||||
label={labelSubmitted}
|
||||
small={true}
|
||||
disabled={true}
|
||||
variant="secondary"
|
||||
icon={faPartyHorn}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "./TypeformButton";
|
||||
@@ -0,0 +1,11 @@
|
||||
.typeformcontainer {
|
||||
height: 50vh; // 50% of the viewport height
|
||||
|
||||
@media (min-width: 768px) { // Tablets and above
|
||||
height: 60vh;
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) { // Desktops and above
|
||||
height: 400px; // Fixed height for large screens
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import React from "react";
|
||||
import { Widget } from "@typeform/embed-react";
|
||||
import { get, set } from "local-storage";
|
||||
import "./TypeformContainer.scss";
|
||||
|
||||
interface TypeformWidgetProps {
|
||||
formId: string;
|
||||
}
|
||||
|
||||
export default function TypeformWidget({ formId }: TypeformWidgetProps) {
|
||||
return (
|
||||
<>
|
||||
{!get<boolean>("isSubmit") ? (
|
||||
<div className="typeformcontainer">
|
||||
<Widget
|
||||
id={formId}
|
||||
style={{ width: "100%", height: "100%" }}
|
||||
opacity={80}
|
||||
onSubmit={() => {
|
||||
set<boolean>("isSubmit", true);
|
||||
console.log("Form submitted!");
|
||||
}}
|
||||
hideHeaders
|
||||
enableSandbox={false}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "./TypeformWidget";
|
||||
@@ -1,14 +1,22 @@
|
||||
import React, { useState, forwardRef } from "react";
|
||||
import React, {
|
||||
useState,
|
||||
forwardRef,
|
||||
memo,
|
||||
useEffect,
|
||||
} from "react";
|
||||
import { useMedia } from "hooks";
|
||||
import { MediaFileType } from "@storyteller/components/src/api";
|
||||
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
|
||||
import makeClass from "resources/makeClass";
|
||||
|
||||
import { Label } from "components/common";
|
||||
import { Label, Spinner } from "components/common";
|
||||
import './styles.scss';
|
||||
|
||||
|
||||
export interface VideoFakeyouProps{
|
||||
width?: number|string;
|
||||
height?: number|string;
|
||||
hideIfNoMedia?: boolean;
|
||||
wrapperClassName?: string;
|
||||
controls?:boolean;
|
||||
muted?:boolean;
|
||||
@@ -21,9 +29,12 @@ export interface VideoFakeyouProps{
|
||||
|
||||
type Ref = HTMLVideoElement;
|
||||
|
||||
const VideoFakeyou = forwardRef<Ref, VideoFakeyouProps>(({
|
||||
const VideoFakeyou = memo(forwardRef<Ref, VideoFakeyouProps>(({
|
||||
width,
|
||||
height,
|
||||
hideIfNoMedia = false,
|
||||
wrapperClassName,
|
||||
controls,
|
||||
controls = true,
|
||||
muted,
|
||||
className,
|
||||
src,
|
||||
@@ -32,7 +43,12 @@ const VideoFakeyou = forwardRef<Ref, VideoFakeyouProps>(({
|
||||
onResponse,
|
||||
...rest
|
||||
}: VideoFakeyouProps, ref) => {
|
||||
const [mediaFile, setMediaFile] = useState<MediaFileType>();
|
||||
//console.log(`Video Player rerender: ${mediaToken}`);
|
||||
|
||||
const [mediaFile, setMediaFile] = useState<MediaFileType|null>(null);
|
||||
useEffect(()=>{
|
||||
setMediaFile(null);
|
||||
},[mediaToken])
|
||||
useMedia({
|
||||
mediaToken: mediaToken,
|
||||
onSuccess: (res: any) => {
|
||||
@@ -42,10 +58,20 @@ const VideoFakeyou = forwardRef<Ref, VideoFakeyouProps>(({
|
||||
});
|
||||
|
||||
const mediaLink = src || (mediaFile && new BucketConfig().getGcsUrl(mediaFile.public_bucket_path));
|
||||
|
||||
const sizing = {
|
||||
height: typeof height === "number" ? height+'px'
|
||||
: typeof height === "string" ? height
|
||||
:"auto",
|
||||
width: typeof width === "number" ? width+'px'
|
||||
: typeof width === "string" ? width
|
||||
:"auto",
|
||||
}
|
||||
if (mediaLink){
|
||||
return (
|
||||
<div {...{ ...makeClass("fy-video",wrapperClassName) }}>
|
||||
<div
|
||||
style={sizing}
|
||||
{...{ ...makeClass("fy-video",wrapperClassName) }}
|
||||
>
|
||||
{label && <Label label={label}/>}
|
||||
<video
|
||||
controls={controls}
|
||||
@@ -60,9 +86,17 @@ const VideoFakeyou = forwardRef<Ref, VideoFakeyouProps>(({
|
||||
<source src={mediaLink} type="video/mp4" />
|
||||
</video>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return null;
|
||||
});
|
||||
);
|
||||
}else if (!hideIfNoMedia){
|
||||
return(
|
||||
<div
|
||||
style={sizing}
|
||||
{...{ ...makeClass("fy-video",wrapperClassName) }}
|
||||
>
|
||||
<Spinner/>
|
||||
</div>
|
||||
);
|
||||
} return null;
|
||||
}));
|
||||
|
||||
export default VideoFakeyou;
|
||||
@@ -5,7 +5,15 @@
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
position: relative;
|
||||
|
||||
background-color: $bg;
|
||||
border-radius: 0.5rem;
|
||||
border: $panel-inner-border 1px solid;
|
||||
.fy-spinner{
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
&.fit-content {
|
||||
height: fit-content;
|
||||
}
|
||||
|
||||
@@ -1,47 +1,99 @@
|
||||
import React, {
|
||||
useRef,
|
||||
// useState
|
||||
useState,
|
||||
PointerEvent
|
||||
} from 'react';
|
||||
import {
|
||||
faGripDots
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { QuickTrimData } from './types';
|
||||
|
||||
export default function TrimScrubber({
|
||||
width
|
||||
trimStart,
|
||||
trimDuration,
|
||||
duration,
|
||||
width,
|
||||
boundingWidth,
|
||||
onChange
|
||||
}:{
|
||||
width:number
|
||||
trimStart:number;
|
||||
trimDuration: number;
|
||||
duration: number;
|
||||
boundingWidth: number;
|
||||
width:number;
|
||||
onChange:(val:QuickTrimData)=>void
|
||||
}){
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const [{
|
||||
left,
|
||||
leftOffset,
|
||||
pointerStart
|
||||
}, setState] = useState<{
|
||||
left: number,
|
||||
leftOffset:number
|
||||
pointerStart:number
|
||||
}>({
|
||||
left: trimStart/duration*boundingWidth,
|
||||
leftOffset: trimStart/duration*boundingWidth,
|
||||
pointerStart: -1
|
||||
});
|
||||
const ref = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
if(ref.current){
|
||||
ref.current.style.cursor = pointerStart >= 0
|
||||
? 'grabbing' : 'grab';
|
||||
}
|
||||
|
||||
function handleScrubStart(e: PointerEvent<HTMLDivElement>){
|
||||
e.persist();
|
||||
// console.log(`start: ${pointerStart}` );
|
||||
setState((curr)=>({
|
||||
...curr,
|
||||
pointerStart: e.clientX
|
||||
}));
|
||||
}
|
||||
function handleScrubEnd(e: PointerEvent<HTMLDivElement>){
|
||||
e.persist();
|
||||
// console.log(`end` );
|
||||
setState((curr)=>({
|
||||
...curr,
|
||||
pointerStart: -1,
|
||||
leftOffset: left,
|
||||
}));
|
||||
//console.log(`${duration} * ${left} / ${boundingWidth} = ${duration * left / boundingWidth}`)
|
||||
onChange(({
|
||||
trimStartSeconds: duration * left / boundingWidth,
|
||||
trimEndSeconds: duration * left / boundingWidth + trimDuration,
|
||||
}))
|
||||
}
|
||||
function handleScrubMove(e: PointerEvent<HTMLDivElement>){
|
||||
e.persist();
|
||||
if(pointerStart >= 0 && pointerStart!==null && ref.current){
|
||||
// console.log(`${e.clientX} - ${pointerStart} = ${e.clientX-pointerStart}`)
|
||||
let newLeft = leftOffset + e.clientX - pointerStart;
|
||||
// console.log(`${newLeft} + ${boundingWidth * widthPercent/100} > ${boundingWidth}`)
|
||||
if (newLeft + width > boundingWidth) {
|
||||
newLeft = boundingWidth - width;
|
||||
}else if(newLeft < 0) {
|
||||
newLeft = 0;
|
||||
}
|
||||
setState((curr)=>({
|
||||
...curr,
|
||||
left: newLeft
|
||||
}));
|
||||
}
|
||||
}
|
||||
return(
|
||||
<div className="trim-scrubber"
|
||||
ref={ref}
|
||||
style={{width: width + "%"}}
|
||||
// onPointerDown={()=>{
|
||||
// if(trimZoneRef.current){
|
||||
// trimZoneRef.current.style.cursor = 'grabbing';
|
||||
// setTrimState((curr)=>({
|
||||
// ...curr,
|
||||
// isScrubbingTrim: true,
|
||||
// }))
|
||||
// }
|
||||
// }}
|
||||
// onPointerUp={()=>{
|
||||
// if(trimZoneRef.current) {
|
||||
// trimZoneRef.current.style.cursor = 'grab';
|
||||
// setTrimState((curr)=>({
|
||||
// ...curr,
|
||||
// isScrubbingTrim: false,
|
||||
// }));
|
||||
// }
|
||||
// }}
|
||||
// onPointerMove={(e: PointerEvent<HTMLDivElement>)=>{
|
||||
// if(trimZoneRef.current && playbarRef.current && isScrubbingTrim){
|
||||
// // console.log(fractionToPercentage(
|
||||
// // (e.clientX - trimZoneRef.current.getBoundingClientRect().left) / (playbarRef.current.getBoundingClientRect().width)
|
||||
// // ) + "%");
|
||||
// }
|
||||
// }}
|
||||
style={{
|
||||
width: width + "px",
|
||||
left: left,
|
||||
}}
|
||||
onPointerDown={handleScrubStart}
|
||||
onPointerUp={handleScrubEnd}
|
||||
onPointerLeave={handleScrubEnd}
|
||||
onPointerMove={handleScrubMove}
|
||||
>
|
||||
<FontAwesomeIcon icon={faGripDots} />
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,6 @@ import React, {
|
||||
useLayoutEffect,
|
||||
useState,
|
||||
useRef,
|
||||
// PointerEvent,
|
||||
} from "react";
|
||||
import {
|
||||
faArrowsRepeat,
|
||||
@@ -35,8 +34,8 @@ const trimOptions: { [key: string]: number } = {
|
||||
|
||||
const initialTrimState: TrimStates = {
|
||||
canNotTrim: true,
|
||||
isScrubbingTrim: false,
|
||||
trimDuration: 0,
|
||||
trimReset: new Date(),
|
||||
trimStart: 0,
|
||||
trimEnd: 0,
|
||||
maxDuration: 0,
|
||||
@@ -65,16 +64,17 @@ export default memo(function VideoQuickTrim({
|
||||
const [isRepeatOn, setIsRepatOn] = useState<boolean>(true);
|
||||
const [{
|
||||
canNotTrim,
|
||||
trimReset,
|
||||
trimStart,
|
||||
trimEnd,
|
||||
trimDuration,
|
||||
// isScrubbingTrim,
|
||||
maxDuration
|
||||
}, setTrimState] = useState<TrimStates>(initialTrimState);
|
||||
|
||||
|
||||
|
||||
const videoRefCallback = useCallback(node => {
|
||||
console.log("videoRefCallback");
|
||||
//console.log("videoRefCallback");
|
||||
if (node !== null) {
|
||||
// DOM node referenced by ref has changed and exists
|
||||
videoRef.current = node;
|
||||
@@ -98,8 +98,10 @@ export default memo(function VideoQuickTrim({
|
||||
};
|
||||
|
||||
node.ontimeupdate = ()=>{
|
||||
if(isRepeatOn && node.currentTime >= trimEnd){
|
||||
node.currentTime = 0;
|
||||
if(isRepeatOn &&
|
||||
(node.currentTime >= trimEnd || node.currentTime <= trimStart)
|
||||
){
|
||||
node.currentTime = trimStart;
|
||||
}
|
||||
setPlaybarState((curr)=>({
|
||||
...curr,
|
||||
@@ -113,7 +115,7 @@ export default memo(function VideoQuickTrim({
|
||||
node.onended = ()=>{setPlaypause("ended");};
|
||||
|
||||
} // else{} DOM node referenced by ref has been unmounted
|
||||
}, [playbarWidth, onSelect, isRepeatOn, trimEnd]); //END videoRefCallback
|
||||
}, [playbarWidth, onSelect, isRepeatOn, trimStart, trimEnd]); //END videoRefCallback
|
||||
|
||||
function handleWindowResize() {
|
||||
if (playbarRef.current !== null) {
|
||||
@@ -142,22 +144,29 @@ export default memo(function VideoQuickTrim({
|
||||
}, []);
|
||||
|
||||
const handleChangeTrimDuration = (selected: string) =>{
|
||||
if(!canNotTrim){
|
||||
const newTrimEnd = trimStart+ trimOptions[selected];
|
||||
if(!canNotTrim && maxDuration > 0 && trimOptions[selected] <= maxDuration){
|
||||
let newTrimStart = trimStart;
|
||||
let newTrimEnd = trimStart+ trimOptions[selected];
|
||||
if (newTrimEnd > maxDuration){
|
||||
newTrimEnd = maxDuration;
|
||||
newTrimStart = maxDuration - trimOptions[selected];
|
||||
}
|
||||
setTrimState((curr)=>({
|
||||
...curr,
|
||||
trimReset: new Date(),
|
||||
trimDuration: trimOptions[selected],
|
||||
trimStart: newTrimStart,
|
||||
trimEnd: newTrimEnd,
|
||||
}))
|
||||
onSelect({
|
||||
trimStartSeconds: trimStart,
|
||||
trimStartSeconds: newTrimStart,
|
||||
trimEndSeconds: newTrimEnd,
|
||||
});
|
||||
if (isRepeatOn
|
||||
&& videoRef.current
|
||||
&& videoRef.current.currentTime > newTrimEnd
|
||||
){
|
||||
videoRef.current.currentTime = trimStart;
|
||||
videoRef.current.currentTime = newTrimStart;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -170,16 +179,17 @@ export default memo(function VideoQuickTrim({
|
||||
}
|
||||
};
|
||||
|
||||
const trimScrubberWidth = videoRef.current
|
||||
const trimScrubberWidth = videoRef.current && playbarWidth > 0
|
||||
? trimDuration > 0 && trimDuration < videoRef.current.duration
|
||||
? (trimDuration / videoRef.current.duration * 100)
|
||||
: 100
|
||||
? (trimDuration / videoRef.current.duration * playbarWidth)
|
||||
: playbarWidth
|
||||
: 0;
|
||||
|
||||
return (
|
||||
<div className="fy-video-quicktrim">
|
||||
<div className="video-wrapper">
|
||||
<VideoFakeyou
|
||||
height={500}
|
||||
controls={false}
|
||||
muted={isMuted}
|
||||
ref={videoRefCallback}
|
||||
@@ -212,7 +222,30 @@ export default memo(function VideoQuickTrim({
|
||||
{/* END of Video Wrapper */}
|
||||
<div className="playbar" ref={playbarRefCallback}>
|
||||
<div className="playbar-bg" />
|
||||
<TrimScrbber width={trimScrubberWidth} />
|
||||
{trimScrubberWidth > 0 &&
|
||||
playbarWidth > 0 &&
|
||||
maxDuration > 0 &&
|
||||
<TrimScrbber
|
||||
key={trimReset.toString()}
|
||||
boundingWidth={playbarWidth}
|
||||
width={trimScrubberWidth}
|
||||
trimStart={trimStart}
|
||||
trimDuration={trimDuration}
|
||||
duration={maxDuration}
|
||||
onChange={(val: QuickTrimData)=>{
|
||||
//console.log(val);
|
||||
setTrimState((curr)=>({
|
||||
...curr,
|
||||
trimStart: val.trimStartSeconds,
|
||||
trimEnd: val.trimEndSeconds
|
||||
}))
|
||||
onSelect({
|
||||
trimStartSeconds: val.trimStartSeconds,
|
||||
trimEndSeconds: val.trimEndSeconds,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
}
|
||||
<div className="playcursor" style={{left: timeCursorOffset+"px"}}/>
|
||||
</div> {/* END of Playbar */}
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@ $playbar-height: 2rem;
|
||||
max-height: 500px;
|
||||
width: 100%;
|
||||
margin-bottom: -7px;
|
||||
border-radius: 0.5rem 0.5rem 0 0;
|
||||
border: none;
|
||||
|
||||
video {
|
||||
border-radius: 0.5rem 0.5rem 0 0;
|
||||
@@ -88,7 +90,7 @@ $playbar-height: 2rem;
|
||||
outline: 0.125rem $white solid;
|
||||
border-top: 1rem $white solid;
|
||||
border-radius: 0.25rem 0.25rem 0.125rem 0.125rem;
|
||||
// cursor: grab;
|
||||
cursor: grab;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
@@ -7,7 +7,7 @@ export type QuickTrimData = {
|
||||
// INTERNALS
|
||||
export type TrimStates = {
|
||||
canNotTrim: boolean;
|
||||
isScrubbingTrim: boolean;
|
||||
trimReset: Date;
|
||||
trimDuration: number;
|
||||
trimStart: number;
|
||||
trimEnd: number;
|
||||
|
||||
@@ -53,9 +53,8 @@ export default function SideNav({
|
||||
const isMenuOpen = wrapper?.classList.contains("toggled");
|
||||
const isLoggedIn = sessionWrapper.isLoggedIn();
|
||||
const isOnLandingPage = window.location.pathname === "/";
|
||||
const isOnLoginOrSignUpPage = window.location.pathname.includes(
|
||||
"/login" || "/signup"
|
||||
);
|
||||
const isOnLoginPage = window.location.pathname.includes("/login");
|
||||
const isOnSignUpPage = window.location.pathname.includes("/signup");
|
||||
const isOnStudioPage = window.location.pathname.includes("/studio");
|
||||
|
||||
let history = useHistory();
|
||||
@@ -107,7 +106,7 @@ export default function SideNav({
|
||||
}, []);
|
||||
|
||||
const shouldNotShowSidebar =
|
||||
(!isLoggedIn && (isOnLandingPage || isOnLoginOrSignUpPage)) ||
|
||||
(!isLoggedIn && (isOnLandingPage || isOnLoginPage || isOnSignUpPage)) ||
|
||||
isOnStudioPage;
|
||||
const shouldShowSidebar = windowWidth >= 992 && !shouldNotShowSidebar;
|
||||
const sidebarClassName = `sidebar ${
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
@import "scss/colours.scss";
|
||||
|
||||
.topbar-bg-transparent {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.topbar-bg-dark {
|
||||
background-color: $bg !important;
|
||||
border-bottom: 1px solid transparent !important;
|
||||
|
||||
@@ -45,16 +45,16 @@ export default function TopNav({
|
||||
const [menuButtonIcon, setMenuButtonIcon] = useState(faBars);
|
||||
// const { t } = useLocalize("TopNav");
|
||||
const isOnLandingPage = window.location.pathname === "/";
|
||||
const isOnLoginOrSignUpPage = window.location.pathname.includes(
|
||||
"/login" || "/signup"
|
||||
);
|
||||
const isOnLoginPage = window.location.pathname.includes("/login");
|
||||
const isOnSignUpPage = window.location.pathname.includes("/signup");
|
||||
const isOnStudioPage = window.location.pathname.includes("/studio");
|
||||
|
||||
const { open } = useModal();
|
||||
const openModal = () => open({ component: InferenceJobsModal });
|
||||
const [isScrolled, setIsScrolled] = useState(false);
|
||||
const loggedIn = sessionWrapper.isLoggedIn();
|
||||
const showNavItem = !loggedIn && (isOnLandingPage || isOnLoginOrSignUpPage);
|
||||
const showNavItem =
|
||||
!loggedIn && (isOnLandingPage || isOnLoginPage || isOnSignUpPage);
|
||||
|
||||
const handleMenuButtonClick = () => {
|
||||
if (window.innerWidth < 1200) {
|
||||
@@ -95,8 +95,15 @@ export default function TopNav({
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const topBarWrapper = document.getElementById("topbar-wrapper");
|
||||
|
||||
const handleMenuToggle = (event: any) => {
|
||||
setMenuButtonIcon(event.detail.isOpen ? faXmark : faBars);
|
||||
if (event.detail.isOpen) {
|
||||
topBarWrapper?.classList.remove("topbar-wrapper-transparent");
|
||||
} else {
|
||||
topBarWrapper?.classList.add("topbar-wrapper-transparent");
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("menuToggle", handleMenuToggle);
|
||||
@@ -185,8 +192,12 @@ export default function TopNav({
|
||||
<div
|
||||
id="topbar-wrapper"
|
||||
className={`position-fixed ${
|
||||
!loggedIn && isOnLandingPage && !isScrolled ? "topbar-bg-dark" : ""
|
||||
}`}
|
||||
domain.title !== "FakeYou"
|
||||
? "topbar-bg-transparent"
|
||||
: !loggedIn && isOnLandingPage && !isScrolled
|
||||
? "topbar-bg-dark"
|
||||
: ""
|
||||
}`.trim()}
|
||||
>
|
||||
<div className="topbar-nav">
|
||||
<div className="topbar-nav-left">
|
||||
@@ -230,11 +241,18 @@ export default function TopNav({
|
||||
<div className="topbar-nav-center">
|
||||
{/* Search Bar */}
|
||||
<div className="d-none d-lg-block">
|
||||
{(!isOnLandingPage && !isOnLoginOrSignUpPage && !isOnStudioPage) ||
|
||||
(loggedIn && !isOnLoginOrSignUpPage && !isOnStudioPage) ||
|
||||
{(!isOnLandingPage &&
|
||||
!isOnLoginPage &&
|
||||
!isOnSignUpPage &&
|
||||
!isOnStudioPage) ||
|
||||
(loggedIn &&
|
||||
!isOnLoginPage &&
|
||||
!isOnSignUpPage &&
|
||||
!isOnStudioPage) ||
|
||||
(isOnLandingPage &&
|
||||
isScrolled &&
|
||||
!isOnLoginOrSignUpPage &&
|
||||
!isOnLoginPage &&
|
||||
!isOnSignUpPage &&
|
||||
!isOnStudioPage) ? (
|
||||
<SearchBar
|
||||
onFocus={onFocusHandler}
|
||||
|
||||
@@ -49,7 +49,7 @@ export default function SessionProvider({
|
||||
const canEditTtsModel = (userToken: string) =>
|
||||
user?.canEditOtherUsersTtsModels || userTokenMatch(userToken);
|
||||
const canBanUsers = () => user?.can_ban_users || false;
|
||||
const studioAccessCheck = (content: React.ElementType) => !user?.can_access_studio ? <StudioNotAvailable /> : content;
|
||||
const studioAccessCheck = (content: React.ElementType) => !sessionWrapper?.canAccessStudio() ? <StudioNotAvailable /> : content;
|
||||
|
||||
return (
|
||||
<SessionContext.Provider
|
||||
|
||||
@@ -61,6 +61,10 @@ $topbar-height: 66px;
|
||||
padding-left: 260px;
|
||||
padding-top: $topbar-height;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&.no-page-padding-top {
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Sidebar Styles */
|
||||
@@ -171,7 +175,11 @@ $topbar-height: 66px;
|
||||
background: $panel;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.04);
|
||||
z-index: 1000;
|
||||
transition: all 0.1s ease;
|
||||
transition: 0.15s;
|
||||
|
||||
&.topbar-wrapper-transparent {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
|
||||
.topbar-nav {
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
type DomainConfig = Record<string, any>;
|
||||
export enum Website {
|
||||
FakeYou,
|
||||
StorytellerAi,
|
||||
}
|
||||
|
||||
export interface DomainConfig {
|
||||
website: Website,
|
||||
logo: string,
|
||||
title: string,
|
||||
link: string,
|
||||
}
|
||||
|
||||
const domainConfigs: Record<string, DomainConfig> = {
|
||||
"storyteller.ai": {
|
||||
logo: "/fakeyou/Storyteller-Logo-5.png",
|
||||
"storyteller": {
|
||||
website: Website.StorytellerAi,
|
||||
logo: "/fakeyou/Storyteller-Logo-1.png",
|
||||
title: "Storyteller AI",
|
||||
link: "https://storyteller.ai",
|
||||
},
|
||||
"fakeyou.com": {
|
||||
logo: "/fakeyou/FakeYou-Logo-2.png",
|
||||
title: "FakeYou",
|
||||
link: "https://fakeyou.com",
|
||||
},
|
||||
// Default to FakeYou.com
|
||||
default: {
|
||||
"fakeyou": {
|
||||
website: Website.FakeYou,
|
||||
logo: "/fakeyou/FakeYou-Logo-2.png",
|
||||
title: "FakeYou",
|
||||
link: "https://fakeyou.com",
|
||||
@@ -20,16 +26,18 @@ const domainConfigs: Record<string, DomainConfig> = {
|
||||
};
|
||||
|
||||
export const getCurrentDomainConfig = (): DomainConfig => {
|
||||
const currentHostname = window.location.hostname;
|
||||
// Fast resolve without leaking domain details
|
||||
switch (window.location.hostname) {
|
||||
case "fakeyou.com":
|
||||
return domainConfigs.fakeyou;
|
||||
case "storyteller.ai":
|
||||
return domainConfigs.storyteller;
|
||||
}
|
||||
|
||||
const matchingConfigKey = Object.keys(domainConfigs).find(
|
||||
key => currentHostname === key || currentHostname.endsWith(`.${key}`)
|
||||
);
|
||||
if (window.location.hostname.includes("storyteller")) {
|
||||
return domainConfigs.storyteller;
|
||||
}
|
||||
|
||||
const domainConfig =
|
||||
matchingConfigKey !== undefined
|
||||
? domainConfigs[matchingConfigKey]
|
||||
: domainConfigs["default"];
|
||||
|
||||
return domainConfig;
|
||||
// Default fallback
|
||||
return domainConfigs.fakeyou;
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
import { DiscordLink2 } from "@storyteller/components/src/elements/DiscordLink2";
|
||||
import { Container } from "components/common";
|
||||
import PageHeader from "components/layout/PageHeader";
|
||||
import React from "react";
|
||||
|
||||
export function VideoStyleTransferNotAvailable() {
|
||||
const subText = (
|
||||
<> Please visit our <DiscordLink2 /> to join the wait list. </>
|
||||
);
|
||||
|
||||
return (
|
||||
<Container type="panel" className="mb-5">
|
||||
<PageHeader
|
||||
title={"Video Style Transfer Features Not Available"}
|
||||
subText={subText}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
@@ -127,7 +127,7 @@ export default function Dashboard(props: DashboardProps) {
|
||||
];
|
||||
|
||||
return (
|
||||
<Panel clear={true} className="mt-5">
|
||||
<Panel clear={true} className={`${!isLoggedIn ? "section" : "mt-5"}`}>
|
||||
<h1 className="fw-bold mb-4">{t("productsTitle")}</h1>
|
||||
|
||||
<DashboardRow items={dashboardProducts} />
|
||||
|
||||
@@ -7,15 +7,19 @@ import { PosthogClient } from "@storyteller/components/src/analytics/PosthogClie
|
||||
import { Container } from "components/common";
|
||||
import FakeYouLandingHeader from "./fakeyou/FakeYouLandingHeader";
|
||||
import Dashboard from "./Dashboard";
|
||||
import FakeYouLandingBody from "./fakeyou/FakeYouLandingBody";
|
||||
import { useDomainConfig } from "context/DomainConfigContext";
|
||||
import StorytellerLanding from "./storyteller/StorytellerLanding";
|
||||
import LandingVideoReel from "./components/LandingVideoReel";
|
||||
import {
|
||||
FrontendInferenceJobType,
|
||||
InferenceJob,
|
||||
} from "@storyteller/components/src/jobs/InferenceJob";
|
||||
import { TtsInferenceJob } from "@storyteller/components/src/jobs/TtsInferenceJobs";
|
||||
import "./LandingPage.scss";
|
||||
// import VstSectionV1 from "./components/VstSectionV1";
|
||||
import VstSectionV2 from "./components/VstSectionV2";
|
||||
import FakeYouLandingBody from "./fakeyou/FakeYouLandingBody";
|
||||
import { DomainConfig, Website } from "utils/domainConfig";
|
||||
|
||||
interface Props {
|
||||
sessionWrapper: SessionWrapper;
|
||||
@@ -32,27 +36,36 @@ interface Props {
|
||||
|
||||
function LandingPage(props: Props) {
|
||||
usePrefixedDocumentTitle("FakeYou Celebrity Voice Generator");
|
||||
|
||||
PosthogClient.recordPageview();
|
||||
const domain = useDomainConfig();
|
||||
|
||||
const domain : DomainConfig = useDomainConfig();
|
||||
|
||||
const isLoggedIn = props.sessionWrapper.isLoggedIn();
|
||||
|
||||
return (
|
||||
<>
|
||||
{domain.website === Website.StorytellerAi && !isLoggedIn && <LandingVideoReel />}
|
||||
<Container type="panel">
|
||||
{domain.title === "FakeYou" ? (
|
||||
{domain.website === Website.FakeYou ? (
|
||||
<>
|
||||
{/* FAKEYOU.COM */}
|
||||
{!isLoggedIn && (
|
||||
<FakeYouLandingHeader
|
||||
sessionWrapper={props.sessionWrapper}
|
||||
sessionSubscriptionsWrapper={props.sessionSubscriptionsWrapper}
|
||||
inferenceJobs={props.inferenceJobs}
|
||||
ttsInferenceJobs={props.ttsInferenceJobs}
|
||||
enqueueInferenceJob={props.enqueueInferenceJob}
|
||||
inferenceJobsByCategory={props.inferenceJobsByCategory}
|
||||
enqueueTtsJob={props.enqueueTtsJob}
|
||||
/>
|
||||
<>
|
||||
<FakeYouLandingHeader
|
||||
sessionWrapper={props.sessionWrapper}
|
||||
sessionSubscriptionsWrapper={
|
||||
props.sessionSubscriptionsWrapper
|
||||
}
|
||||
inferenceJobs={props.inferenceJobs}
|
||||
ttsInferenceJobs={props.ttsInferenceJobs}
|
||||
enqueueInferenceJob={props.enqueueInferenceJob}
|
||||
inferenceJobsByCategory={props.inferenceJobsByCategory}
|
||||
enqueueTtsJob={props.enqueueTtsJob}
|
||||
/>
|
||||
{/* <VstSectionV1 /> */}
|
||||
<VstSectionV2 />
|
||||
</>
|
||||
)}
|
||||
|
||||
<Dashboard sessionWrapper={props.sessionWrapper} />
|
||||
@@ -63,7 +76,15 @@ function LandingPage(props: Props) {
|
||||
<>
|
||||
{/* STORYTELLER,AI */}
|
||||
{!isLoggedIn ? (
|
||||
<StorytellerLanding />
|
||||
<StorytellerLanding
|
||||
sessionWrapper={props.sessionWrapper}
|
||||
sessionSubscriptionsWrapper={props.sessionSubscriptionsWrapper}
|
||||
inferenceJobs={props.inferenceJobs}
|
||||
ttsInferenceJobs={props.ttsInferenceJobs}
|
||||
enqueueInferenceJob={props.enqueueInferenceJob}
|
||||
inferenceJobsByCategory={props.inferenceJobsByCategory}
|
||||
enqueueTtsJob={props.enqueueTtsJob}
|
||||
/>
|
||||
) : (
|
||||
<Dashboard sessionWrapper={props.sessionWrapper} />
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import { Button } from "components/common";
|
||||
import React, { useEffect } from "react";
|
||||
|
||||
interface LandingVideoReelProps {}
|
||||
|
||||
export default function LandingVideoReel(props: LandingVideoReelProps) {
|
||||
const isOnLandingPage = window.location.pathname === "/";
|
||||
|
||||
useEffect(() => {
|
||||
const contentWrapper = document.getElementById("page-content-wrapper");
|
||||
const topBarWrapper = document.getElementById("topbar-wrapper");
|
||||
const searchBar = document.querySelector(".search-field .form-control");
|
||||
|
||||
const handleScroll = () => {
|
||||
if (window.scrollY > 100) {
|
||||
topBarWrapper?.classList.remove("topbar-wrapper-transparent");
|
||||
searchBar?.classList.remove("landing-search");
|
||||
} else {
|
||||
topBarWrapper?.classList.add("topbar-wrapper-transparent");
|
||||
searchBar?.classList.add("landing-search");
|
||||
}
|
||||
};
|
||||
|
||||
if (isOnLandingPage) {
|
||||
contentWrapper?.classList.add("no-page-padding-top");
|
||||
topBarWrapper?.classList.add("topbar-wrapper-transparent");
|
||||
searchBar?.classList.add("landing-search");
|
||||
window.addEventListener("scroll", handleScroll);
|
||||
} else {
|
||||
contentWrapper?.classList.remove("no-page-padding-top");
|
||||
topBarWrapper?.classList.remove("topbar-wrapper-transparent");
|
||||
searchBar?.classList.remove("landing-search");
|
||||
window.removeEventListener("scroll", handleScroll);
|
||||
}
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("scroll", handleScroll);
|
||||
topBarWrapper?.classList.remove("topbar-wrapper-transparent");
|
||||
searchBar?.classList.remove("landing-search");
|
||||
contentWrapper?.classList.remove("no-page-padding-top");
|
||||
};
|
||||
}, [isOnLandingPage]);
|
||||
|
||||
return (
|
||||
<div className="vh-100 d-flex align-items-center justify-content-center position-relative">
|
||||
<video
|
||||
autoPlay
|
||||
muted
|
||||
loop
|
||||
className="position-absolute w-100 h-100"
|
||||
style={{ objectFit: "cover" }}
|
||||
>
|
||||
<source src="/videos/landing-reel.mp4" type="video/mp4" />
|
||||
</video>
|
||||
<div
|
||||
className="position-absolute w-100 h-100"
|
||||
style={{ backgroundColor: "rgba(20, 20, 27, 0.6)", zIndex: 0 }}
|
||||
/>
|
||||
<div className="text-center text-white d-flex flex-column align-items-center zi-2">
|
||||
<h1 className="display-2 fw-bolder">Enabling Anyone to Make Professional Film, Animation, and Music</h1>
|
||||
<h5 className="fw-normal mb-3 mt-2 w-75 lh-base opacity-75">
|
||||
StorytellerAI's powerful new engine combines Generative AI and
|
||||
User Generated Content to radically democratize both audio and video production.
|
||||
</h5>
|
||||
<Button label="Sign Up Now" to="/signup" className="mt-5" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
@import "scss/colours.scss";
|
||||
|
||||
.vst-section {
|
||||
padding-top: 5rem;
|
||||
padding-bottom: 5rem;
|
||||
}
|
||||
|
||||
.vst-sbs-panel {
|
||||
background-color: $panel;
|
||||
height: 400px;
|
||||
overflow: hidden;
|
||||
border: lighten($panel, 8%) solid 2px;
|
||||
border-radius: 0.5rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.vst-sbs-video-cover {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.vst-sbs-video-overlay-container {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.vst-sbs-video-overlay-text {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
top: 0;
|
||||
left: 0;
|
||||
color: white;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.vst-sbs-video-overlay-cta {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
color: $white;
|
||||
font-weight: 500;
|
||||
padding: 0.5rem;
|
||||
|
||||
.fy-badge {
|
||||
background-color: rgba(
|
||||
darken(desaturate($primary, 25%), 10%),
|
||||
60%
|
||||
) !important;
|
||||
border: 2px solid rgba($primary, 60%) !important;
|
||||
font-size: 12px;
|
||||
padding: 0.12rem 0.36rem;
|
||||
|
||||
&:hover {
|
||||
background-color: darken(desaturate($primary, 25%), 10%) !important;
|
||||
border-color: darken(desaturate($primary, 25%), 10%) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 992px) {
|
||||
.vst-sbs-panel {
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.vst-sbs-video-overlay-cta {
|
||||
padding: 0.25rem;
|
||||
|
||||
.fy-badge {
|
||||
border-radius: 0.3rem;
|
||||
border: 1px solid rgba($primary, 60%) !important;
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.vst-sbs-panel {
|
||||
height: 240px;
|
||||
}
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
import { Badge, Button, Panel } from "components/common";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import "./VstSectionV1.scss";
|
||||
import { faArrowRight } from "@fortawesome/pro-solid-svg-icons";
|
||||
// import { Link } from "react-router-dom";
|
||||
|
||||
interface VstSectionV1Props {}
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: "Original Video",
|
||||
videoSrc: "/videos/landing/vst/1-original.mp4",
|
||||
ctaLink: "/video-styletransfer",
|
||||
},
|
||||
{
|
||||
title: "2D Flat Anime",
|
||||
videoSrc: "/videos/landing/vst/1-2d_flat_anime.mp4",
|
||||
ctaLink: "/video-styletransfer",
|
||||
},
|
||||
{
|
||||
title: "Ink B&W",
|
||||
videoSrc: "/videos/landing/vst/1-ink_bw.mp4",
|
||||
ctaLink: "/video-styletransfer",
|
||||
},
|
||||
{
|
||||
title: "Origami",
|
||||
videoSrc: "/videos/landing/vst/1-origami.mp4",
|
||||
ctaLink: "/video-styletransfer",
|
||||
},
|
||||
];
|
||||
|
||||
export default function VstSectionV1(props: VstSectionV1Props) {
|
||||
const videosRef = useRef<HTMLVideoElement[]>([]);
|
||||
const [loadedVideos, setLoadedVideos] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const checkAllVideosLoaded = () => {
|
||||
setLoadedVideos(prevLoaded => prevLoaded + 1);
|
||||
};
|
||||
|
||||
videosRef.current.forEach(video => {
|
||||
video.addEventListener("canplaythrough", checkAllVideosLoaded);
|
||||
});
|
||||
|
||||
return () => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
videosRef.current.forEach(video => {
|
||||
video.removeEventListener("canplaythrough", checkAllVideosLoaded);
|
||||
});
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (loadedVideos === columns.length) {
|
||||
videosRef.current.forEach(video => video.play());
|
||||
}
|
||||
}, [loadedVideos]);
|
||||
|
||||
return (
|
||||
<Panel clear={true} className="vst-section">
|
||||
<div className="col-12 col-lg-6 mb-5 pb-lg-3">
|
||||
<h1 className="fw-bold">Video Style Transfer</h1>
|
||||
<p className="opacity-75 mb-4">
|
||||
Transform your videos effortlessly with video style transfer. Apply
|
||||
unique styles and effects to create visually captivating content.
|
||||
</p>
|
||||
<div className="d-flex">
|
||||
<Button
|
||||
label="Try Video Style Transfer"
|
||||
small={true}
|
||||
icon={faArrowRight}
|
||||
iconFlip={true}
|
||||
to="/video-styletransfer"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="row g-3 g-lg-4">
|
||||
{columns.map((column, index) => (
|
||||
<div className={`col-6 col-lg-3 column-${index + 1}`} key={index}>
|
||||
<div className="vst-sbs-panel">
|
||||
<div className="vst-sbs-video-overlay-container">
|
||||
<video className="vst-sbs-video-cover" autoPlay loop muted>
|
||||
<source src={column.videoSrc} type="video/mp4" />
|
||||
Your browser does not support the video tag.
|
||||
</video>
|
||||
<div className="vst-sbs-video-overlay-text">
|
||||
<Badge label={column.title} color="gray" />
|
||||
</div>
|
||||
{/* <div className="vst-sbs-video-overlay-cta">
|
||||
<Link to={column.ctaLink || "/video-styletransfer"}>
|
||||
<Badge label="Try this style" />
|
||||
</Link>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "./VstSectionV1";
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
@import "scss/colours.scss";
|
||||
|
||||
.vst-section {
|
||||
padding-top: 5rem;
|
||||
padding-bottom: 5rem;
|
||||
}
|
||||
|
||||
.vst-carousel-panel {
|
||||
background-color: $panel;
|
||||
overflow: hidden;
|
||||
border: lighten($panel, 8%) solid 2px;
|
||||
border-radius: 0.5rem;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.vst-styled-videos-panel {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: $panel;
|
||||
overflow: hidden;
|
||||
border: lighten($panel, 8%) solid 2px;
|
||||
border-radius: 0.5rem;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
> div {
|
||||
border-left: lighten($panel, 8%) solid 2px;
|
||||
border-top: lighten($panel, 8%) solid 2px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
> div:nth-child(-n + 2) {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
> div:nth-child(2n + 1) {
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.vst-styled-videos-panel-overlay-text {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
top: 0;
|
||||
left: 0;
|
||||
color: white;
|
||||
padding: 0.5rem;
|
||||
display: flex;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.vst-styled-videos-panel-overlay-cta {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
color: $primary;
|
||||
padding: 0.5rem;
|
||||
display: flex;
|
||||
height: auto;
|
||||
align-items: flex-end;
|
||||
justify-content: flex-end;
|
||||
margin: 0;
|
||||
|
||||
.fy-badge {
|
||||
background-color: rgba(
|
||||
darken(desaturate($primary, 25%), 10%),
|
||||
40%
|
||||
) !important;
|
||||
border: 2px solid rgba($primary, 60%) !important;
|
||||
font-size: 12px;
|
||||
padding: 0.12rem 0.36rem;
|
||||
|
||||
&:hover {
|
||||
background-color: darken(desaturate($primary, 25%), 10%) !important;
|
||||
border-color: darken(desaturate($primary, 25%), 10%) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.swiper {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
user-select: none;
|
||||
-moz-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
|
||||
&.vst-swiper-main {
|
||||
height: auto !important;
|
||||
border-radius: 0.5rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&.vst-swiper-thumbs {
|
||||
height: auto !important;
|
||||
|
||||
.swiper-slide {
|
||||
opacity: 0.4;
|
||||
cursor: pointer;
|
||||
transition: 0.2s;
|
||||
|
||||
.vst-carousel-panel {
|
||||
border: lighten($panel, 15%) solid 2px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 0.75;
|
||||
}
|
||||
}
|
||||
|
||||
.swiper-slide-thumb-active {
|
||||
opacity: 1 !important;
|
||||
|
||||
.vst-carousel-panel {
|
||||
border: $primary solid 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.swiper-slide {
|
||||
background-size: contain;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.vst-swiper-main-overlay-text {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
top: 0;
|
||||
left: 0;
|
||||
color: white;
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
@media (max-width: 992px) {
|
||||
.vst-styled-videos-panel-overlay-cta {
|
||||
padding: 0.25rem;
|
||||
|
||||
.fy-badge {
|
||||
border-radius: 0.3rem;
|
||||
border: 1px solid rgba($primary, 60%) !important;
|
||||
font-size: 9px;
|
||||
}
|
||||
}
|
||||
|
||||
.vst-styled-videos-panel-overlay-text {
|
||||
.fy-badge {
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
}
|
||||
+246
@@ -0,0 +1,246 @@
|
||||
import { Badge, Panel } from "components/common";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import "./VstSectionV2.scss";
|
||||
import { Swiper, SwiperSlide } from "swiper/react";
|
||||
import "swiper/css";
|
||||
import "swiper/css/free-mode";
|
||||
import "swiper/css/navigation";
|
||||
import "swiper/css/thumbs";
|
||||
import { Navigation, Thumbs } from "swiper/modules";
|
||||
import { Swiper as SwiperType } from "swiper/types";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import {
|
||||
faArrowDownLong,
|
||||
faArrowRightLong,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import TypeformButton from "components/common/TypeformButton";
|
||||
// import { Link } from "react-router-dom";
|
||||
|
||||
interface VstSectionV2Props {}
|
||||
|
||||
export default function VstSectionV2(props: VstSectionV2Props) {
|
||||
const [thumbsSwiper, setThumbsSwiper] = useState<SwiperType | null>(null);
|
||||
const [activeIndex, setActiveIndex] = useState(0);
|
||||
const [isPlaying, setIsPlaying] = useState(true);
|
||||
|
||||
// const togglePlayback = () => {
|
||||
// setIsPlaying(!isPlaying);
|
||||
// };
|
||||
|
||||
const handleVideoPlay = () => {
|
||||
if (!isPlaying) {
|
||||
setIsPlaying(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleVideoPause = () => {
|
||||
if (isPlaying) {
|
||||
setIsPlaying(false);
|
||||
}
|
||||
};
|
||||
|
||||
const videoRef = useRef<HTMLVideoElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (videoRef.current) {
|
||||
if (isPlaying) {
|
||||
videoRef.current.play();
|
||||
} else {
|
||||
videoRef.current.pause();
|
||||
}
|
||||
}
|
||||
}, [isPlaying]);
|
||||
|
||||
const videoSet = [
|
||||
{
|
||||
src: "/videos/landing/vst/1-original.mp4",
|
||||
styledVideos: [
|
||||
{
|
||||
src: "/videos/landing/vst/1-2d_flat_anime.mp4",
|
||||
label: "2D Flat Anime",
|
||||
ctaLink: "/video-styletransfer",
|
||||
},
|
||||
{
|
||||
src: "/videos/landing/vst/1-3d_cartoon.mp4",
|
||||
label: "3D Cartoon",
|
||||
ctaLink: "/video-styletransfer",
|
||||
},
|
||||
{
|
||||
src: "/videos/landing/vst/1-ink_bw.mp4",
|
||||
label: "Ink B&W",
|
||||
ctaLink: "/video-styletransfer",
|
||||
},
|
||||
{
|
||||
src: "/videos/landing/vst/1-origami.mp4",
|
||||
label: "Origami",
|
||||
ctaLink: "/video-styletransfer",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
src: "/videos/landing/vst/2-original.mp4",
|
||||
styledVideos: [
|
||||
{
|
||||
src: "/videos/landing/vst/2-2d_flat_anime.mp4",
|
||||
label: "2D Flat Anime",
|
||||
ctaLink: "/video-styletransfer",
|
||||
},
|
||||
{
|
||||
src: "/videos/landing/vst/2-pop_art.mp4",
|
||||
label: "Pop Art",
|
||||
ctaLink: "/video-styletransfer",
|
||||
},
|
||||
{
|
||||
src: "/videos/landing/vst/2-ink_splash.mp4",
|
||||
label: "Ink Splash",
|
||||
ctaLink: "/video-styletransfer",
|
||||
},
|
||||
{
|
||||
src: "/videos/landing/vst/2-origami.mp4",
|
||||
label: "Origami",
|
||||
ctaLink: "/video-styletransfer",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const [currentStyledVideos, setCurrentStyledVideos] = useState(
|
||||
videoSet[0].styledVideos
|
||||
);
|
||||
|
||||
return (
|
||||
<Panel clear={true} className="vst-section">
|
||||
<div className="col-12 col-lg-6 mb-5 mb-lg-3 pb-lg-3">
|
||||
<h1 className="fw-bold">Video Style Transfer</h1>
|
||||
<p className="opacity-75 mb-4">
|
||||
Transform your videos effortlessly with video style transfer. Apply
|
||||
unique styles and effects to create visually captivating content.
|
||||
</p>
|
||||
<div className="d-flex">
|
||||
{/* <Button
|
||||
onClick={openTypeform}
|
||||
label="Join Waitlist for Video Style Transfer"
|
||||
small={true}
|
||||
icon={faArrowRight}
|
||||
iconFlip={true}
|
||||
/> */}
|
||||
<TypeformButton
|
||||
label="Join the Waitlist"
|
||||
formId="oWnV91Z9"
|
||||
labelSubmitted="You're on the waitlist!"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row gx-0">
|
||||
<div className="col-12 col-lg-4 d-flex gap-3 justify-content-center flex-column-reverse flex-lg-column">
|
||||
<Swiper
|
||||
loop={false}
|
||||
spaceBetween={10}
|
||||
thumbs={{ swiper: thumbsSwiper }}
|
||||
modules={[Navigation, Thumbs]}
|
||||
className="vst-swiper-main"
|
||||
onSlideChange={(swiper: SwiperType) => {
|
||||
setCurrentStyledVideos(videoSet[swiper.activeIndex].styledVideos);
|
||||
setActiveIndex(swiper.activeIndex);
|
||||
}}
|
||||
>
|
||||
<div className="vst-swiper-main-overlay-text">
|
||||
<Badge label="Original Video" color="gray" />
|
||||
</div>
|
||||
{videoSet.map((video, index) => (
|
||||
<SwiperSlide key={index}>
|
||||
<div className="ratio ratio-16x9 vst-carousel-panel">
|
||||
<video
|
||||
autoPlay
|
||||
loop
|
||||
muted
|
||||
key={`${index}-${activeIndex}`}
|
||||
onPlay={handleVideoPlay}
|
||||
onPause={handleVideoPause}
|
||||
ref={videoRef => {
|
||||
if (videoRef && isPlaying) {
|
||||
videoRef.play();
|
||||
} else if (videoRef) {
|
||||
videoRef.pause();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<source src={video.src} type="video/mp4" />
|
||||
Your browser does not support the video tag.
|
||||
</video>
|
||||
</div>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
<Swiper
|
||||
onSwiper={(swiper: SwiperType) => setThumbsSwiper(swiper)}
|
||||
loop={false}
|
||||
spaceBetween={10}
|
||||
slidesPerView={3}
|
||||
modules={[Navigation, Thumbs]}
|
||||
className="vst-swiper-thumbs"
|
||||
>
|
||||
{videoSet.map((video, index) => (
|
||||
<SwiperSlide key={index}>
|
||||
<div className="ratio ratio-16x9 vst-carousel-panel">
|
||||
<video loop muted>
|
||||
<source src={video.src} type="video/mp4" />
|
||||
Your browser does not support the video tag.
|
||||
</video>
|
||||
</div>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
</div>
|
||||
<div className="col-12 col-lg-1 p-3 p-lg-0 d-flex align-items-center justify-content-center">
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowRightLong}
|
||||
className="display-5 opacity-75 d-none d-lg-block"
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowDownLong}
|
||||
className="display-5 opacity-75 d-block d-lg-none"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-12 col-lg-7">
|
||||
<div className="vst-styled-videos-panel d-flex flex-wrap">
|
||||
{currentStyledVideos.map((styledVideo, index) => (
|
||||
<div
|
||||
key={`${index}-${styledVideo.src}`}
|
||||
className="ratio ratio-16x9 w-50"
|
||||
>
|
||||
<video
|
||||
autoPlay
|
||||
loop
|
||||
muted
|
||||
key={styledVideo.src}
|
||||
onPlay={handleVideoPlay}
|
||||
onPause={handleVideoPause}
|
||||
ref={videoRef => {
|
||||
if (videoRef && isPlaying) {
|
||||
videoRef.play();
|
||||
} else if (videoRef) {
|
||||
videoRef.pause();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<source src={styledVideo.src} type="video/mp4" />
|
||||
Your browser does not support the video tag.
|
||||
</video>
|
||||
<div className="vst-styled-videos-panel-overlay-text">
|
||||
<Badge label={styledVideo.label} color="gray" />
|
||||
</div>
|
||||
{/* <div className="vst-styled-videos-panel-overlay-cta">
|
||||
<Link to={styledVideo.ctaLink || "/video-styletransfer"}>
|
||||
<Badge label="Try this style" />
|
||||
</Link>
|
||||
</div> */}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "./VstSectionV2";
|
||||
@@ -1,9 +1,9 @@
|
||||
import { faDiscord, faTwitter } from "@fortawesome/free-brands-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Panel } from "components/common";
|
||||
// import { Panel } from "components/common";
|
||||
import { useLocalize } from "hooks";
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
// import { Link } from "react-router-dom";
|
||||
|
||||
interface FakeYouLandingBodyProps {}
|
||||
|
||||
@@ -12,7 +12,7 @@ export default function FakeYouLandingBody(props: FakeYouLandingBodyProps) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Panel clear={true}>
|
||||
{/* <Panel clear={true}>
|
||||
<div className="section px-md-5 px-xl-3">
|
||||
<div className="row g-4 g-lg-5 flex-row-reverse">
|
||||
<div className="col-12 col-md-6 col-lg-7">
|
||||
@@ -48,9 +48,9 @@ export default function FakeYouLandingBody(props: FakeYouLandingBodyProps) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
</Panel> */}
|
||||
|
||||
<div className="py-4 mb-4">
|
||||
<div className="section">
|
||||
<div className="container text-center community-container">
|
||||
<div className="panel px-4 py-5 d-flex flex-column align-items-center community-container rounded">
|
||||
<h2 className="fw-bold mb-2">{t("communityTitle")}</h2>
|
||||
|
||||
@@ -1,24 +1,50 @@
|
||||
import { faDiscord, faTwitter } from "@fortawesome/free-brands-svg-icons";
|
||||
import {
|
||||
faArrowRight,
|
||||
faCompass,
|
||||
faMessageDots,
|
||||
faWaveformLines,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Button, Container, Input, Panel, Select } from "components/common";
|
||||
import { useLocalize } from "hooks";
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
|
||||
import { SessionSubscriptionsWrapper } from "@storyteller/components/src/session/SessionSubscriptionsWrapper";
|
||||
import {
|
||||
FrontendInferenceJobType,
|
||||
InferenceJob,
|
||||
} from "@storyteller/components/src/jobs/InferenceJob";
|
||||
import { TtsInferenceJob } from "@storyteller/components/src/jobs/TtsInferenceJobs";
|
||||
|
||||
interface StorytellerLandingProps {}
|
||||
interface StorytellerLandingProps {
|
||||
sessionWrapper: SessionWrapper;
|
||||
sessionSubscriptionsWrapper: SessionSubscriptionsWrapper;
|
||||
inferenceJobs: Array<InferenceJob>;
|
||||
ttsInferenceJobs: Array<TtsInferenceJob>;
|
||||
enqueueInferenceJob: (
|
||||
jobToken: string,
|
||||
frontendInferenceJobType: FrontendInferenceJobType
|
||||
) => void;
|
||||
inferenceJobsByCategory: Map<FrontendInferenceJobType, Array<InferenceJob>>;
|
||||
enqueueTtsJob: (jobToken: string) => void;
|
||||
}
|
||||
|
||||
export default function StorytellerLanding(props: StorytellerLandingProps) {
|
||||
const { t } = useLocalize("LandingPage");
|
||||
export default function StorytellerLanding({
|
||||
sessionWrapper,
|
||||
sessionSubscriptionsWrapper,
|
||||
inferenceJobs,
|
||||
ttsInferenceJobs,
|
||||
enqueueInferenceJob,
|
||||
inferenceJobsByCategory,
|
||||
enqueueTtsJob,
|
||||
}: StorytellerLandingProps) {
|
||||
// const { t } = useLocalize("LandingPage");
|
||||
|
||||
return (
|
||||
<>
|
||||
<Container type="panel" className="py-5">
|
||||
{/* <Container type="panel" className="py-5">
|
||||
<FakeYouLandingHeader
|
||||
sessionWrapper={sessionWrapper}
|
||||
sessionSubscriptionsWrapper={sessionSubscriptionsWrapper}
|
||||
inferenceJobs={inferenceJobs}
|
||||
ttsInferenceJobs={ttsInferenceJobs}
|
||||
enqueueInferenceJob={enqueueInferenceJob}
|
||||
inferenceJobsByCategory={inferenceJobsByCategory}
|
||||
enqueueTtsJob={enqueueTtsJob}
|
||||
/>
|
||||
|
||||
{/* <Container type="panel" className="py-5">
|
||||
<Panel clear={true} className="py-lg-5">
|
||||
<div className="row g-5 g-lg-5 flex-row-reverse">
|
||||
<div className="col-12 col-md-6">
|
||||
@@ -65,9 +91,9 @@ export default function StorytellerLanding(props: StorytellerLandingProps) {
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
</Container>
|
||||
</Container> */}
|
||||
|
||||
<div className="panel rounded-0 py-5 my-5">
|
||||
{/* <div className="panel rounded-0 py-5 my-5">
|
||||
<Container type="panel" className="py-lg-5">
|
||||
<Panel clear={true}>
|
||||
<div className="d-flex flex-column align-items-center rounded-0 mt-lg-4 pt-lg-4">
|
||||
@@ -184,9 +210,9 @@ export default function StorytellerLanding(props: StorytellerLandingProps) {
|
||||
</div>
|
||||
</Panel>
|
||||
</Container>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
<Container type="panel" className="pt-5 my-5">
|
||||
{/* <Container type="panel" className="pt-5 my-5">
|
||||
<Panel padding={true}>
|
||||
<div className="d-flex flex-column align-items-center py-5">
|
||||
<h2 className="fw-bold mb-2">{t("communityTitle")}</h2>
|
||||
@@ -218,7 +244,7 @@ export default function StorytellerLanding(props: StorytellerLandingProps) {
|
||||
className="dots-left-bottom me-3"
|
||||
/>
|
||||
</Panel>
|
||||
</Container>
|
||||
</Container> */}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import React from "react";
|
||||
import React, { useEffect, useState, memo } from "react";
|
||||
import WaveSurfer from "wavesurfer.js";
|
||||
import { useEffect, useState } from "react";
|
||||
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
|
||||
import {
|
||||
faPlay,
|
||||
@@ -21,7 +20,7 @@ interface MediaAudioPlayerProps {
|
||||
mediaFile: MediaFile;
|
||||
}
|
||||
|
||||
export default function MediaAudioPlayer({ mediaFile }: MediaAudioPlayerProps) {
|
||||
const MediaAudioPlayer = memo(({ mediaFile }: MediaAudioPlayerProps) => {
|
||||
let [isPlaying, setIsPlaying] = useState(false);
|
||||
let [isRepeating, setIsRepeating] = useState(false);
|
||||
let [playbackSpeed, setPlaybackSpeed] = useState(PlaybackSpeed.NORMAL);
|
||||
@@ -29,7 +28,7 @@ export default function MediaAudioPlayer({ mediaFile }: MediaAudioPlayerProps) {
|
||||
|
||||
useEffect(() => {
|
||||
const wavesurferInstance = WaveSurfer.create({
|
||||
container: "#waveform", // Previousy I used 'this.ref.current' and React.createRef()
|
||||
container: "#waveform",
|
||||
height: 200,
|
||||
responsive: true,
|
||||
waveColor: "#cbcbcb",
|
||||
@@ -49,11 +48,12 @@ export default function MediaAudioPlayer({ mediaFile }: MediaAudioPlayerProps) {
|
||||
if (waveSurfer) {
|
||||
waveSurfer.load(audioLink);
|
||||
}
|
||||
}, [waveSurfer, mediaFile]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [waveSurfer, mediaFile.public_bucket_path, BucketConfig]);
|
||||
|
||||
useEffect(() => {
|
||||
if (waveSurfer) {
|
||||
waveSurfer.unAll(); // NB: Otherwise we keep reinstalling the hooks and cause chaos
|
||||
waveSurfer.unAll();
|
||||
waveSurfer.on("pause", () => {
|
||||
setIsPlaying(waveSurfer!.isPlaying());
|
||||
});
|
||||
@@ -83,7 +83,7 @@ export default function MediaAudioPlayer({ mediaFile }: MediaAudioPlayerProps) {
|
||||
switch (playbackSpeed) {
|
||||
case PlaybackSpeed.NORMAL:
|
||||
nextSpeed = PlaybackSpeed.DOUBLE;
|
||||
waveSurfer!.setPlaybackRate(1.5); // Okay, so a lie...
|
||||
waveSurfer!.setPlaybackRate(1.5);
|
||||
break;
|
||||
case PlaybackSpeed.DOUBLE:
|
||||
nextSpeed = PlaybackSpeed.HALF;
|
||||
@@ -97,51 +97,36 @@ export default function MediaAudioPlayer({ mediaFile }: MediaAudioPlayerProps) {
|
||||
setPlaybackSpeed(nextSpeed);
|
||||
};
|
||||
|
||||
let playButtonIcon = faPlay;
|
||||
if (isPlaying) {
|
||||
playButtonIcon = faPause;
|
||||
}
|
||||
|
||||
let playButtonIcon = isPlaying ? faPause : faPlay;
|
||||
let repeatButtonIcon = isRepeating ? faRepeat : faArrowRight;
|
||||
|
||||
let speedButtonText = "1x";
|
||||
switch (playbackSpeed) {
|
||||
case PlaybackSpeed.NORMAL:
|
||||
speedButtonText = "1x";
|
||||
break;
|
||||
case PlaybackSpeed.DOUBLE:
|
||||
speedButtonText = "2x";
|
||||
break;
|
||||
case PlaybackSpeed.HALF:
|
||||
speedButtonText = "0.5x";
|
||||
break;
|
||||
}
|
||||
let speedButtonText =
|
||||
playbackSpeed === PlaybackSpeed.NORMAL
|
||||
? "1x"
|
||||
: playbackSpeed === PlaybackSpeed.DOUBLE
|
||||
? "2x"
|
||||
: "0.5x";
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div id="waveform"></div>
|
||||
<div className="d-flex justify-content-center gap-2 mt-3">
|
||||
<Button
|
||||
square={true}
|
||||
icon={playButtonIcon}
|
||||
onClick={() => togglePlayPause()}
|
||||
/>
|
||||
|
||||
<Button square={true} icon={playButtonIcon} onClick={togglePlayPause} />
|
||||
<Button
|
||||
tooltip="Toggle Repeat"
|
||||
variant="secondary"
|
||||
square={true}
|
||||
icon={repeatButtonIcon}
|
||||
onClick={() => toggleIsRepeating()}
|
||||
onClick={toggleIsRepeating}
|
||||
/>
|
||||
|
||||
<Button
|
||||
tooltip="Speed"
|
||||
label={speedButtonText}
|
||||
variant="secondary"
|
||||
onClick={() => togglePlaybackSpeed()}
|
||||
onClick={togglePlaybackSpeed}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default MediaAudioPlayer;
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Link, useParams } from "react-router-dom";
|
||||
import MediaAudioComponent from "./MediaAudioComponent";
|
||||
import MediaAudioPlayer from "./MediaAudioPlayer";
|
||||
import MediaVideoComponent from "./MediaVideoComponent";
|
||||
import { MediaFile } from "@storyteller/components/src/api/media_files/GetMediaFile";
|
||||
import Container from "components/common/Container";
|
||||
@@ -64,23 +64,27 @@ export default function MediaPage() {
|
||||
},
|
||||
});
|
||||
const batchToken = mediaFile?.maybe_batch_token;
|
||||
const [images, setImages] = useState<string[]>([]);
|
||||
const [images, setImages] = useState<{ url: string; token: string }[]>([]);
|
||||
const bucketConfig = new BucketConfig();
|
||||
const timeCreated = moment(mediaFile?.created_at || "").fromNow();
|
||||
const dateCreated = moment(mediaFile?.created_at || "").format("LLL");
|
||||
const [buttonLabel, setButtonLabel] = useState("Copy");
|
||||
const [copyPositiveButtonText, setCopyPositiveButtonText] = useState("Copy");
|
||||
const [copyNegativeButtonText, setCopyNegativeButtonText] = useState("Copy");
|
||||
const [activeSlide, setActiveSlide] = useState({ url: "", token: "" });
|
||||
|
||||
// Inside MediaPage.tsx
|
||||
|
||||
useEffect(() => {
|
||||
if (batchToken) {
|
||||
GetMediaBatchImages(batchToken, {}, {})
|
||||
.then(response => {
|
||||
if (response.success) {
|
||||
const imageUrls = response.results.map(result =>
|
||||
bucketConfig.getGcsUrl(result.public_bucket_path)
|
||||
);
|
||||
setImages(imageUrls);
|
||||
const mediaItems = response.results.map(result => ({
|
||||
url: bucketConfig.getGcsUrl(result.public_bucket_path),
|
||||
token: result.token,
|
||||
}));
|
||||
setImages(mediaItems);
|
||||
} else {
|
||||
console.error("Failed to fetch batch images");
|
||||
}
|
||||
@@ -111,6 +115,10 @@ export default function MediaPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleActiveSlideChange = (image: any) => {
|
||||
setActiveSlide(image);
|
||||
};
|
||||
|
||||
const promptSection = (
|
||||
<>
|
||||
{prompt?.maybe_positive_prompt && (
|
||||
@@ -171,7 +179,9 @@ export default function MediaPage() {
|
||||
{/* Voice model name that is used to generate the audio */}
|
||||
{/*<h3 className="fw-bold mb-4">[Voice Model Name]</h3> */}
|
||||
|
||||
<MediaAudioComponent mediaFile={mediaFile} />
|
||||
<div className="w-100">
|
||||
<MediaAudioPlayer mediaFile={mediaFile} />
|
||||
</div>
|
||||
|
||||
{/* Show TTS text input if it is a TTS result */}
|
||||
{mediaFile.maybe_text_transcript && (
|
||||
@@ -198,9 +208,16 @@ export default function MediaPage() {
|
||||
);
|
||||
|
||||
case MediaFileType.Image:
|
||||
let sdMediaImage = ["/images/avatars/default-pfp.png"];
|
||||
let sdMediaImage = [
|
||||
{ url: "/images/avatars/default-pfp.png", token: "default" },
|
||||
];
|
||||
if (mediaFile.public_bucket_path) {
|
||||
sdMediaImage = [bucketConfig.getGcsUrl(mediaFile.public_bucket_path)];
|
||||
sdMediaImage = [
|
||||
{
|
||||
url: bucketConfig.getGcsUrl(mediaFile.public_bucket_path),
|
||||
token: mediaFile?.token,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -208,6 +225,7 @@ export default function MediaPage() {
|
||||
<SdBatchMediaPanel
|
||||
key={images.length}
|
||||
images={mediaFile.maybe_batch_token ? images : sdMediaImage}
|
||||
onActiveSlideChange={handleActiveSlideChange}
|
||||
/>
|
||||
{promptSection}
|
||||
</>
|
||||
@@ -285,9 +303,12 @@ export default function MediaPage() {
|
||||
? mediaTypeLabels[mediaFile?.media_type]
|
||||
: "";
|
||||
|
||||
let audioLink = new BucketConfig().getGcsUrl(mediaFile?.public_bucket_path);
|
||||
let downloadLink =
|
||||
activeSlide.url || bucketConfig.getGcsUrl(mediaFile?.public_bucket_path);
|
||||
|
||||
const shareUrl = `https://fakeyou.com/media/${mediaFile?.token || ""}`;
|
||||
const shareUrl = `https://fakeyou.com/media/${
|
||||
activeSlide.token || mediaFile?.token || ""
|
||||
}`;
|
||||
const shareText = "Check out this media on FakeYou.com!";
|
||||
|
||||
if (status < 3)
|
||||
@@ -592,8 +613,8 @@ export default function MediaPage() {
|
||||
icon={faArrowDownToLine}
|
||||
label="Download"
|
||||
className="flex-grow-1"
|
||||
href={audioLink}
|
||||
download={audioLink}
|
||||
href={downloadLink}
|
||||
download={downloadLink}
|
||||
variant="secondary"
|
||||
target="_blank"
|
||||
/>
|
||||
@@ -604,8 +625,8 @@ export default function MediaPage() {
|
||||
icon={faArrowDownToLine}
|
||||
square={true}
|
||||
variant="secondary"
|
||||
href={audioLink}
|
||||
download={audioLink}
|
||||
href={downloadLink}
|
||||
download={downloadLink}
|
||||
tooltip="Download"
|
||||
target="_blank"
|
||||
/>
|
||||
|
||||
+51
-16
@@ -9,32 +9,52 @@ import { FreeMode, Navigation, Thumbs } from "swiper/modules";
|
||||
import { Swiper as SwiperType } from "swiper/types";
|
||||
import "./SdBatchMediaPanel.scss";
|
||||
|
||||
interface SdCoverImagePanelProps {
|
||||
images: string[];
|
||||
interface ImageWithToken {
|
||||
url: string;
|
||||
token: string;
|
||||
}
|
||||
|
||||
export default function SdBatchMediaPanel({ images }: SdCoverImagePanelProps) {
|
||||
interface SdBatchMediaPanelProps {
|
||||
images: ImageWithToken[];
|
||||
onActiveSlideChange: (image: ImageWithToken) => void;
|
||||
}
|
||||
|
||||
export default function SdBatchMediaPanel({
|
||||
images,
|
||||
onActiveSlideChange,
|
||||
}: SdBatchMediaPanelProps) {
|
||||
const [thumbsSwiper, setThumbsSwiper] = useState<SwiperType | null>(null);
|
||||
const [forceUpdateKey, setForceUpdateKey] = useState(0);
|
||||
const [isPortrait, setIsPortrait] = useState(false);
|
||||
const [initialSlide, setInitialSlide] = useState(0);
|
||||
const [isFirstLoad, setIsFirstLoad] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
images.forEach(imgSrc => {
|
||||
const currentUrl = new URL(window.location.href);
|
||||
const currentToken = currentUrl.pathname.split("/media/")[1].split("?")[0];
|
||||
const initialSlideIndex = images.findIndex(
|
||||
image => image.token === currentToken
|
||||
);
|
||||
if (initialSlideIndex !== -1) {
|
||||
setInitialSlide(initialSlideIndex);
|
||||
}
|
||||
setIsFirstLoad(false);
|
||||
}, [images]);
|
||||
|
||||
useEffect(() => {
|
||||
images.forEach(image => {
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
// If any image is portrait, update isPortrait to true
|
||||
if (img.height > img.width && !isPortrait) {
|
||||
setIsPortrait(true);
|
||||
}
|
||||
};
|
||||
img.src = imgSrc;
|
||||
img.src = image.url;
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [images]);
|
||||
}, [images, isPortrait]);
|
||||
|
||||
useEffect(() => {
|
||||
if (images.length > 0) {
|
||||
// Force re-render of the Swiper component by updating a key
|
||||
setForceUpdateKey(prevKey => prevKey + 1);
|
||||
}
|
||||
}, [images]);
|
||||
@@ -43,6 +63,20 @@ export default function SdBatchMediaPanel({ images }: SdCoverImagePanelProps) {
|
||||
setThumbsSwiper(swiper);
|
||||
};
|
||||
|
||||
const handleSlideChange = (swiper: SwiperType) => {
|
||||
if (!isFirstLoad) {
|
||||
// Only update URL if it's not the first load
|
||||
const currentSlideIndex = swiper.realIndex;
|
||||
const currentImage = images[currentSlideIndex];
|
||||
const currentImageToken = images[currentSlideIndex].token;
|
||||
const baseUrl = window.location.href.split("/media/")[0]; // Get the base URL before the token
|
||||
const queryParams = window.location.search; // Get query parameters if any
|
||||
const newUrl = `${baseUrl}/media/${currentImageToken}${queryParams}`; // Construct the new URL
|
||||
window.history.replaceState(null, "", newUrl); // Replace the current history state
|
||||
onActiveSlideChange(currentImage);
|
||||
}
|
||||
};
|
||||
|
||||
const secondSwiperClass = `secondSwiper ${
|
||||
isPortrait ? "portrait" : "landscape"
|
||||
}`;
|
||||
@@ -58,29 +92,30 @@ export default function SdBatchMediaPanel({ images }: SdCoverImagePanelProps) {
|
||||
modules={[FreeMode, Navigation, Thumbs]}
|
||||
className={secondSwiperClass}
|
||||
slidesPerView={1}
|
||||
initialSlide={0}
|
||||
initialSlide={initialSlide}
|
||||
onSlideChange={handleSlideChange}
|
||||
>
|
||||
{images.map((imgSrc, index) => (
|
||||
{images.map((image, index) => (
|
||||
<SwiperSlide key={index}>
|
||||
<img src={imgSrc} alt={`Slide ${index + 1}`} />
|
||||
<img src={image.url} alt={`Slide ${index + 1}`} />
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
{images.length > 1 && (
|
||||
<Swiper
|
||||
onSwiper={handleSwiper}
|
||||
loop={true}
|
||||
loop={false}
|
||||
spaceBetween={10}
|
||||
slidesPerView={5}
|
||||
freeMode={true}
|
||||
watchSlidesProgress={true}
|
||||
modules={[FreeMode, Navigation, Thumbs]}
|
||||
className="firstSwiper"
|
||||
initialSlide={0}
|
||||
initialSlide={initialSlide}
|
||||
>
|
||||
{images.map((imgSrc, index) => (
|
||||
{images.map((image, index) => (
|
||||
<SwiperSlide key={index}>
|
||||
<img src={imgSrc} alt={`Thumbnail ${index + 1}`} />
|
||||
<img src={image.url} alt={`Thumbnail ${index + 1}`} />
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
|
||||
+40
-25
@@ -1,12 +1,6 @@
|
||||
import React, { useRef, useState } from "react";
|
||||
import { NavLink, useParams, useHistory } from "react-router-dom";
|
||||
import {
|
||||
Button,
|
||||
Container,
|
||||
Panel,
|
||||
SelectionBubbles,
|
||||
TextArea,
|
||||
} from "components/common";
|
||||
import { Button, Container, Panel, TextArea } from "components/common";
|
||||
import VideoQuickTrim, {
|
||||
QuickTrimData,
|
||||
} from "components/common/VideoQuickTrim";
|
||||
@@ -15,7 +9,7 @@ import EnqueueVideoStyleTransfer from "@storyteller/components/src/api/video_sty
|
||||
import { initialValues } from "./defaultValues";
|
||||
import { mapRequest, VSTType } from "./helpers";
|
||||
import LoadingSpinner from "components/common/LoadingSpinner";
|
||||
// import SectionAdvanceOptions from "./sectionAdvanceOptions";
|
||||
import SelectionBubblesV2 from "components/common/SelectionBubblesV2";
|
||||
|
||||
export default function PageVSTApp() {
|
||||
const { jobToken } = useParams<{ jobToken: string }>();
|
||||
@@ -29,15 +23,14 @@ export default function PageVSTApp() {
|
||||
// fileToken: job?.maybe_result?.entity_token || "",
|
||||
});
|
||||
|
||||
console.log(job);
|
||||
console.log(job?.maybe_result);
|
||||
console.log(job?.maybe_result?.entity_token);
|
||||
// console.log(job);
|
||||
// console.log(job?.maybe_result);
|
||||
// console.log(job?.maybe_result?.entity_token);
|
||||
|
||||
const handleOnChange = (val: {
|
||||
[key: string]: number | string | boolean | undefined;
|
||||
}) => {
|
||||
setVstValues(curr => ({ ...curr, ...val }));
|
||||
console.log("VST Values", vstValues);
|
||||
};
|
||||
|
||||
const handleGenerate = () => {
|
||||
@@ -54,7 +47,7 @@ export default function PageVSTApp() {
|
||||
|
||||
EnqueueVideoStyleTransfer(request).then(res => {
|
||||
if (res.success && res.inference_job_token) {
|
||||
console.log("Job enqueued successfully", res.inference_job_token);
|
||||
// console.log("Job enqueued successfully", res.inference_job_token);
|
||||
history.push(`/studio-intro/result/${res.inference_job_token}`);
|
||||
} else {
|
||||
console.log("Failed to enqueue job", res);
|
||||
@@ -80,19 +73,39 @@ export default function PageVSTApp() {
|
||||
};
|
||||
}
|
||||
|
||||
const styleMap: { [key: string]: string } = {
|
||||
Anime: "weight_yqexh77ntqyawzgh9fzash798", // set SD Model Weight Tokens, currently hardcoded all to Anime style
|
||||
Pixel: "weight_yqexh77ntqyawzgh9fzash798",
|
||||
Painting: "weight_yqexh77ntqyawzgh9fzash798",
|
||||
};
|
||||
const styleOptions = [
|
||||
{
|
||||
label: "Anime",
|
||||
imageUrl: "/images/dummy-image.jpg",
|
||||
token: "weight_yqexh77ntqyawzgh9fzash798",
|
||||
},
|
||||
{
|
||||
label: "Pixel",
|
||||
imageUrl: "/images/dummy-image-2.jpg",
|
||||
token: "weight_yqexh77ntqyawzgh9fzash798",
|
||||
},
|
||||
{
|
||||
label: "Painting",
|
||||
imageUrl: "/images/dummy-image-3.jpg",
|
||||
token: "weight_yqexh77ntqyawzgh9fzash798",
|
||||
},
|
||||
{
|
||||
label: "Ink",
|
||||
imageUrl: "/images/dummy-image-4.jpg",
|
||||
token: "weight_yqexh77ntqyawzgh9fzash798",
|
||||
},
|
||||
];
|
||||
|
||||
const handleStyleSelection = (selected: string) => {
|
||||
const selectedSdModelToken = styleMap[selected];
|
||||
setVstValues(curr => ({ ...curr, sdModelToken: selectedSdModelToken }));
|
||||
const handleStyleSelection = (selectedLabel: any) => {
|
||||
const selectedOption = styleOptions.find(
|
||||
option => option.label === selectedLabel
|
||||
);
|
||||
const selectedSdModelToken = selectedOption ? selectedOption.token : null;
|
||||
if (selectedSdModelToken) {
|
||||
setVstValues(curr => ({ ...curr, sdModelToken: selectedSdModelToken }));
|
||||
}
|
||||
};
|
||||
|
||||
const styleOptions = Object.keys(styleMap);
|
||||
|
||||
if (!jobToken) {
|
||||
history.push("/");
|
||||
}
|
||||
@@ -130,10 +143,12 @@ export default function PageVSTApp() {
|
||||
<div className="col-12 col-md-6 d-flex flex-column gap-3 justify-content-center">
|
||||
<div>
|
||||
<label className="sub-title">Select a Style</label>
|
||||
<SelectionBubbles
|
||||
options={styleOptions}
|
||||
<SelectionBubblesV2
|
||||
options={Object.values(styleOptions)}
|
||||
onSelect={handleStyleSelection}
|
||||
selectedStyle="outline"
|
||||
variant="card"
|
||||
mobileSideScroll={true}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
import { SessionSubscriptionsWrapper } from "@storyteller/components/src/session/SessionSubscriptionsWrapper";
|
||||
import { faMessageImage } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
|
||||
import { usePrefixedDocumentTitle } from "common/UsePrefixedDocumentTitle";
|
||||
//import { StudioNotAvailable } from "v2/view/_common/StudioNotAvailable";
|
||||
|
||||
interface TextToImagePageProps {
|
||||
@@ -31,6 +32,8 @@ export default function TextToImagePage({
|
||||
// return <StudioNotAvailable />;
|
||||
//}
|
||||
|
||||
usePrefixedDocumentTitle("Text to Image");
|
||||
|
||||
return (
|
||||
<Container type="panel">
|
||||
<PageHeaderWithImage
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React, { useReducer, useEffect } from "react";
|
||||
|
||||
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
|
||||
import { StudioNotAvailable } from "v2/view/_common/StudioNotAvailable";
|
||||
import { useInferenceJobs, useLocalize } from "hooks";
|
||||
import {
|
||||
FrontendInferenceJobType,
|
||||
@@ -13,6 +12,7 @@ import { states, reducer } from "./reducer";
|
||||
import SubRoutes from "./routes";
|
||||
import PageHeaderWithImage from "components/layout/PageHeaderWithImage";
|
||||
import { faFilms } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { VideoStyleTransferNotAvailable } from "v2/view/_common/VideoStyleTransferNotAvailable";
|
||||
|
||||
export default function VideoStyleTransfer(props: {
|
||||
enqueueInferenceJob: (
|
||||
@@ -48,8 +48,8 @@ export default function VideoStyleTransfer(props: {
|
||||
}
|
||||
}, [pageState, enqueueInferenceJob]);
|
||||
|
||||
if (!props.sessionWrapper.canAccessStudio()) {
|
||||
return <StudioNotAvailable />;
|
||||
if (!props.sessionWrapper.canAccessVideoStyleTransfer()) {
|
||||
return <VideoStyleTransferNotAvailable />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -11,9 +11,11 @@ export const inferenceFailures = (fail = "") => {
|
||||
|
||||
export function TableOfKeyValues(
|
||||
{
|
||||
title,
|
||||
keyValues,
|
||||
height
|
||||
}:{
|
||||
title: string;
|
||||
keyValues:{
|
||||
[key:string]:number|string|boolean|undefined
|
||||
};
|
||||
@@ -24,11 +26,11 @@ export function TableOfKeyValues(
|
||||
height: height || "100%",
|
||||
overflowY: "scroll",
|
||||
overflowX: "clip",
|
||||
border: "1px solid white",
|
||||
borderTopLeftRadius: "1rem",
|
||||
borderBottomLeftRadius: "1rem",
|
||||
// border: "1px solid white",
|
||||
// borderTopLeftRadius: "1rem",
|
||||
// borderBottomLeftRadius: "1rem",
|
||||
}}><tbody>
|
||||
<tr><th>Debug of a certain Key-Values set</th></tr>
|
||||
<tr><th>{title}</th></tr>
|
||||
{
|
||||
Object.entries(keyValues).map(([key, val], index: number)=>{
|
||||
return <tr key={index}><td>{`${key}`}</td><td>{`${val}`}</td></tr>
|
||||
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
import React, {
|
||||
useState
|
||||
} from "react";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import {
|
||||
Accordion,
|
||||
Button,
|
||||
TempTextArea as TextArea,
|
||||
} from "components/common";
|
||||
|
||||
import { mapRequest, VSTType } from './helpers';
|
||||
import { TableOfKeyValues } from "../../commons";
|
||||
import { Action } from "../../reducer";
|
||||
import EnqueueVideoStyleTransfer from "@storyteller/components/src/api/video_styleTransfer";
|
||||
|
||||
export const CompAdminPanel = ({
|
||||
parentPath,
|
||||
vstValues,
|
||||
dispatchPageState
|
||||
}:{
|
||||
parentPath: string;
|
||||
vstValues : VSTType;
|
||||
dispatchPageState: (action: Action) => void;
|
||||
})=>{
|
||||
const history = useHistory();
|
||||
const exampleJSON = JSON.stringify(mapRequest(vstValues), null, 4);
|
||||
const [jsonRequest, setJsonRequest] = useState<string>(exampleJSON);
|
||||
const handleSendRequest = ()=>{
|
||||
EnqueueVideoStyleTransfer(JSON.parse(jsonRequest)).then(res => {
|
||||
if (res.success && res.inference_job_token) {
|
||||
dispatchPageState({
|
||||
type: "enqueueJobSuccess",
|
||||
payload: {
|
||||
inferenceJobToken: res.inference_job_token,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
alert("Error, Check Console Log");
|
||||
console.log(res);
|
||||
}
|
||||
});
|
||||
dispatchPageState({ type: "enqueueJob" });
|
||||
history.push(`${parentPath}/jobs`);
|
||||
}
|
||||
|
||||
return (
|
||||
<Accordion className="mt-4">
|
||||
<Accordion.Item title={"Admin Panel"}>
|
||||
<div className="row p-3 g-3">
|
||||
<div className="col-8">
|
||||
<TextArea
|
||||
{...{
|
||||
label:"JSON Input",
|
||||
rows: 10,
|
||||
placeholder: "Paste JSON here and send request directly",
|
||||
onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) =>
|
||||
setJsonRequest(e.target.value),
|
||||
value: jsonRequest,
|
||||
required: false,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-4 pt-5">
|
||||
<Button
|
||||
label="Console Log JSON Reqest"
|
||||
variant="secondary"
|
||||
onClick={()=>{
|
||||
console.log(JSON.parse(jsonRequest));
|
||||
}}
|
||||
/>
|
||||
<br/>
|
||||
<Button
|
||||
label="Send JSON"
|
||||
onClick={handleSendRequest}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row p-3 g-3">
|
||||
<div className="col-8">
|
||||
<TableOfKeyValues
|
||||
title="All values in the VST app state"
|
||||
keyValues={vstValues}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-4">
|
||||
<p className="me-3"><b>Log Debug Values: </b></p>
|
||||
<br/>
|
||||
<Button
|
||||
label="Console Log State"
|
||||
variant="secondary"
|
||||
onClick={()=>{
|
||||
console.log(vstValues);
|
||||
}}
|
||||
/>
|
||||
<br/>
|
||||
<Button
|
||||
label="Console Log Request"
|
||||
variant="secondary"
|
||||
onClick={()=>{
|
||||
const request = mapRequest(vstValues);
|
||||
console.log(request);
|
||||
}}
|
||||
/>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</Accordion.Item>
|
||||
</Accordion>
|
||||
);
|
||||
}
|
||||
+1
-1
@@ -28,7 +28,7 @@ import {
|
||||
defaultPreset,
|
||||
} from "./dataCnPresets";
|
||||
|
||||
export default function SectionAdvanceOptions({
|
||||
export default function CompAdvanceOptions({
|
||||
debug,
|
||||
t,
|
||||
vstValues: vstVal,
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ export default function CompStyleModal({
|
||||
setOnSelectTimeStamp(new Date());
|
||||
}
|
||||
};
|
||||
console.log(data);
|
||||
//console.log(data);
|
||||
return (
|
||||
<div key={key}
|
||||
className="col-12 col-sm-6 col-xl-4 grid-item"
|
||||
|
||||
+6
-19
@@ -14,6 +14,7 @@ import { mapRequest, VSTType } from "./helpers";
|
||||
|
||||
import CompStyleModal from "./compStyleModal";
|
||||
import CompAdvanceOptions from "./compAdvanceOptions";
|
||||
import { CompAdminPanel } from "./compAdminPanel";
|
||||
|
||||
export default function PageVSTApp({
|
||||
debug: debugProps,
|
||||
@@ -137,25 +138,11 @@ export default function PageVSTApp({
|
||||
/>
|
||||
</div>
|
||||
{debug &&
|
||||
<div className="col-12 d-flex justify-content-end align-items-center">
|
||||
<p className="me-3"><b>Debug Values: </b></p>
|
||||
<Button
|
||||
className="me-3"
|
||||
label="Console Log State"
|
||||
onClick={()=>{
|
||||
console.log(vstValues);
|
||||
}}
|
||||
variant="secondary"
|
||||
/>
|
||||
<Button
|
||||
label="Console Log Request"
|
||||
onClick={()=>{
|
||||
const request = mapRequest(vstValues);
|
||||
console.log(request);
|
||||
}}
|
||||
variant="secondary"
|
||||
/>
|
||||
</div>
|
||||
<CompAdminPanel
|
||||
parentPath={parentPath}
|
||||
dispatchPageState={dispatchPageState}
|
||||
vstValues={vstValues}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</Panel>
|
||||
|
||||
+8
-3
@@ -12,6 +12,7 @@ export default function TabContentLibrary({
|
||||
pageState: State;
|
||||
dispatchPageState: (action: Action) => void;
|
||||
}){
|
||||
console.log(`Tab Rerender, selected: ${pageState.mediaFileToken}`);
|
||||
const handleProceed = ()=>{
|
||||
dispatchPageState({type: "loadFile"})
|
||||
}
|
||||
@@ -35,12 +36,16 @@ export default function TabContentLibrary({
|
||||
inputLabel="Select a Video"
|
||||
onSelect={handleOnSelect}
|
||||
/>
|
||||
{pageState.mediaFileToken &&
|
||||
<VideoFakeyou mediaToken={pageState.mediaFileToken} />
|
||||
<br/>
|
||||
{pageState.mediaFileToken &&
|
||||
<VideoFakeyou
|
||||
height={500}
|
||||
mediaToken={pageState.mediaFileToken}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
{pageState.status === states.FILE_SELECTED &&
|
||||
<div className="col-12 d-flex justify-content-center mt-5">
|
||||
<div className="col-12 d-flex justify-content-center mt-4">
|
||||
<NavLink to={`load/${pageState.mediaFileToken}`}>
|
||||
<Button
|
||||
label={t("button.proceed")}
|
||||
|
||||
@@ -48,6 +48,7 @@ export default function VideoWorkflow(props:{
|
||||
}, [pageState, enqueueInferenceJob]);
|
||||
|
||||
if (!props.sessionWrapper.canAccessStudio()) {
|
||||
//return <VideoStyleTransferNotAvailable />
|
||||
return <StudioNotAvailable />
|
||||
}
|
||||
|
||||
|
||||
@@ -270,8 +270,6 @@ function SdInferencePanel({
|
||||
internalSeed.current = generateRandomSeed();
|
||||
}
|
||||
|
||||
console.log(sdToken);
|
||||
|
||||
const request = {
|
||||
uuid_idempotency_token: uuidv4(),
|
||||
maybe_sd_model_token: sdToken || null,
|
||||
@@ -287,13 +285,9 @@ function SdInferencePanel({
|
||||
maybe_batch_count: batchCount,
|
||||
};
|
||||
|
||||
console.log("request", request);
|
||||
|
||||
const response = await EnqueueImageGen(request);
|
||||
|
||||
if (EnqueueImageGenIsSuccess(response)) {
|
||||
console.log("enqueuing...");
|
||||
|
||||
if (response.inference_job_token) {
|
||||
enqueueInferenceJob(
|
||||
response.inference_job_token,
|
||||
|
||||
@@ -4484,6 +4484,7 @@ __metadata:
|
||||
react-select: ^5.7.0
|
||||
react-share: ^4.4.1
|
||||
react-twitch-embed: ^3.0.1
|
||||
react-typeform-embed: ^1.0.8
|
||||
react-wavesurfer.js: ^0.0.8
|
||||
sass: ^1.57.1
|
||||
swiper: ^11.0.5
|
||||
@@ -4591,10 +4592,13 @@ __metadata:
|
||||
resolution: "@storyteller/root@workspace:."
|
||||
dependencies:
|
||||
"@react-spring/types": ^9.7.3
|
||||
"@typeform/embed": ^4.8.1
|
||||
"@typeform/embed-react": ^3.15.0
|
||||
craco: ^0.0.3
|
||||
cross-env: ^7.0.3
|
||||
husky: ^8.0.3
|
||||
lerna: ^4.0.0
|
||||
local-storage: ^2.0.0
|
||||
moment: ^2.29.4
|
||||
prettier: ^3.1.0
|
||||
pretty-quick: ^3.1.3
|
||||
@@ -5033,6 +5037,32 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typeform/embed-react@npm:^3.15.0":
|
||||
version: 3.15.0
|
||||
resolution: "@typeform/embed-react@npm:3.15.0"
|
||||
dependencies:
|
||||
"@typeform/embed": 4.8.1
|
||||
fast-deep-equal: ^3.1.3
|
||||
peerDependencies:
|
||||
react: ">=16.8.0"
|
||||
checksum: d5e5d4b1cdfc128de2ce6c17ba648adbe98bfecc5a2c296fbedc8fb32da0c302e813baa6ce3ecc3de9dc84f36ccfd42250a4693f846d84ce2574e6fedcaa86e5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typeform/embed@npm:4.8.1, @typeform/embed@npm:^4.8.1":
|
||||
version: 4.8.1
|
||||
resolution: "@typeform/embed@npm:4.8.1"
|
||||
checksum: 1e7ac3d0d95c1ea823160f1478a973f871550a4bab7de1501f557b1e18966ab5a740b6a913d9436b5ee59d0f25d707b66582a8c41dea195383cca3afb1955b23
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typeform/embed@npm:^1.28.2":
|
||||
version: 1.38.0
|
||||
resolution: "@typeform/embed@npm:1.38.0"
|
||||
checksum: 41115134e5cee28f5e031181b4525c7885d23707699cf183921110262717c7544bfd101428267410b812914c235687783e373ce98e37bdc447d72f8177663597
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/aria-query@npm:^4.2.0":
|
||||
version: 4.2.2
|
||||
resolution: "@types/aria-query@npm:4.2.2"
|
||||
@@ -14247,6 +14277,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"local-storage@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "local-storage@npm:2.0.0"
|
||||
checksum: 921f381cafc4c0cac263a87e9e11093adf0bfb13b3520107bb5065bbccf5e7d22eea81148a40bc10d9f44aba8a5afe0157183205714d0221e02e36cb8afaa70b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"locate-path@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "locate-path@npm:2.0.0"
|
||||
@@ -18177,6 +18214,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-typeform-embed@npm:^1.0.8":
|
||||
version: 1.0.8
|
||||
resolution: "react-typeform-embed@npm:1.0.8"
|
||||
dependencies:
|
||||
"@typeform/embed": ^1.28.2
|
||||
peerDependencies:
|
||||
react: ^17.0.2
|
||||
react-dom: ^17.0.2
|
||||
checksum: f47ee0077046e282a482ef1a9d754ff255be65174e05b58f4c51c873ce8d771d1af98c5829f8860f2a33b0f4ebabcc4557e0f1e3b14b0d8bf3b23f460f1df18a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-wavesurfer.js@npm:^0.0.5":
|
||||
version: 0.0.5
|
||||
resolution: "react-wavesurfer.js@npm:0.0.5"
|
||||
|
||||
Reference in new issue
Block a user