mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-20 17:02:53 -04:00
Refactor: replace fontawesome icons with lucide icons (#1873)
Removes Font Awesome entirely
### Changes
- All ~1,000 icon call sites now import Lucide components directly from
`lucide-react` (`<PlayIcon className="..." />`); no FA packages, tokens,
or `fa*` identifiers remain anywhere.
- New `@storyteller/icons` lib holds the pieces Lucide doesn't ship:
`DynamicIcon` for runtime-chosen icons (`icon={loading ?
LoaderCircleIcon : SaveIcon}`), 11 brand logos (Discord, TikTok, ...) as
inlined CC BY 4.0 paths, and HD/SD quality badges drawn in Lucide's
stroke style.
- Global `.lucide { display: inline-block; width/height: 1.2em }` rule
in each app stylesheet: keeps icons font-relative like FA (1.2em
compensates Lucide's 24px-viewBox/20px-grid padding) and undoes Tailwind
preflight's `svg { display: block }`. Explicit `w-*`/`size-*` utilities
still win.
- FA-only props migrated: `spin` → `animate-spin`, FA size strings → em
values.
- Dropped `@fortawesome/*` + `@awesome.me` kit deps, both `.npmrc`
files, and
the FA rule in `shared-vite-config.ts` (now externalizes
`lucide-react`).
This commit is contained in:
@@ -1,3 +0,0 @@
|
||||
@awesome.me:registry=https://npm.fontawesome.com/
|
||||
@fortawesome:registry=https://npm.fontawesome.com/
|
||||
//npm.fontawesome.com/:_authToken=104779DB-FD41-4BEA-BDB4-B1845D758A79
|
||||
@@ -7,8 +7,7 @@ import {
|
||||
useLocation,
|
||||
useNavigationType,
|
||||
} from "react-router-dom";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faSpinnerThird } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { LoaderCircleIcon } from "lucide-react";
|
||||
import Home from "../pages/home";
|
||||
import Media from "../pages/media";
|
||||
import { ToastContainer } from "../components/toast/toast";
|
||||
@@ -63,10 +62,9 @@ function ScrollToTop() {
|
||||
function AuthCheckSpinner() {
|
||||
return (
|
||||
<div className="flex h-screen items-center justify-center bg-[#101014]">
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinnerThird}
|
||||
className="animate-spin text-4xl text-primary/80"
|
||||
/>
|
||||
<LoaderCircleIcon
|
||||
|
||||
className="animate-spin text-4xl text-primary/80" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,8 +6,7 @@ import {
|
||||
getReferralUsername,
|
||||
getReferrer,
|
||||
} from "@storyteller/common";
|
||||
import { faSpinnerThird } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { LoaderCircleIcon } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
interface GoogleLoginButtonProps {
|
||||
@@ -63,10 +62,9 @@ export function GoogleLoginButton({
|
||||
{/* Custom visual button - shown behind */}
|
||||
<div className="absolute inset-0 w-full h-full flex items-center justify-center gap-3 bg-white text-black rounded-full font-medium border border-gray-200 group-hover:border-white/80 group-hover:bg-white/80 transition-colors">
|
||||
{isLoading ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinnerThird}
|
||||
className="animate-spin text-lg text-gray-600"
|
||||
/>
|
||||
<LoaderCircleIcon
|
||||
|
||||
className="animate-spin text-lg text-gray-600" />
|
||||
) : (
|
||||
<>
|
||||
<GoogleIcon />
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { ReactNode } from "react";
|
||||
import { Outlet, useNavigate, Navigate } from "react-router-dom";
|
||||
import { faArrowLeft, faSpinnerThird } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { ArrowLeftIcon, LoaderCircleIcon } from "lucide-react";
|
||||
import { TruchetPattern } from "@storyteller/ui-vfx";
|
||||
import { AuthShowcase } from "./auth-showcase";
|
||||
import { useMediaQuery } from "../ui/use-media-query";
|
||||
@@ -69,7 +68,7 @@ export const AuthLayout = () => {
|
||||
aria-label="Go back"
|
||||
className="absolute left-5 top-5 z-20 flex h-9 w-9 items-center justify-center rounded-full text-white/60 transition-colors hover:bg-white/10 hover:text-white"
|
||||
>
|
||||
<FontAwesomeIcon icon={faArrowLeft} />
|
||||
<ArrowLeftIcon />
|
||||
</button>
|
||||
|
||||
<div className="flex flex-1 flex-col justify-center px-8 py-10 sm:px-10">
|
||||
@@ -78,10 +77,9 @@ export const AuthLayout = () => {
|
||||
<Outlet />
|
||||
) : (
|
||||
<div className="flex justify-center py-12">
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinnerThird}
|
||||
className="animate-spin text-2xl text-white/40"
|
||||
/>
|
||||
<LoaderCircleIcon
|
||||
|
||||
className="animate-spin text-2xl text-white/40" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import {
|
||||
faEye,
|
||||
faEyeSlash,
|
||||
faSpinnerThird,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { EyeIcon, EyeOffIcon, LoaderCircleIcon } from "lucide-react";
|
||||
import { DynamicIcon } from "@storyteller/icons";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { Input } from "@storyteller/ui-input";
|
||||
import { useState } from "react";
|
||||
@@ -160,7 +156,7 @@ export const SignupForm = ({
|
||||
className="absolute right-4 top-1/2 -translate-y-1/2 text-white/30 hover:text-white/60 transition-colors"
|
||||
tabIndex={-1}
|
||||
>
|
||||
<FontAwesomeIcon icon={showPassword ? faEyeSlash : faEye} />
|
||||
<DynamicIcon icon={showPassword ? EyeOffIcon : EyeIcon} />
|
||||
</button>
|
||||
</div>
|
||||
</Reveal>
|
||||
@@ -172,7 +168,7 @@ export const SignupForm = ({
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isLoading ? (
|
||||
<FontAwesomeIcon icon={faSpinnerThird} className="animate-spin" />
|
||||
<LoaderCircleIcon className="animate-spin" />
|
||||
) : (
|
||||
"Create account"
|
||||
)}
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import { useState } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { Modal } from "@storyteller/ui-modal";
|
||||
import {
|
||||
faCoins,
|
||||
faSpinnerThird,
|
||||
faArrowRight,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { ArrowRightIcon, CoinsIcon, LoaderCircleIcon } from "lucide-react";
|
||||
import { BillingApi } from "@storyteller/api";
|
||||
import { toast } from "./toast/toast";
|
||||
|
||||
@@ -108,10 +103,9 @@ export function CreditsModal({ isOpen, onClose }: CreditsModalProps) {
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="flex h-12 w-12 shrink-0 items-center justify-center rounded-xl bg-primary/15">
|
||||
<FontAwesomeIcon
|
||||
icon={faCoins}
|
||||
className="text-primary text-lg"
|
||||
/>
|
||||
<CoinsIcon
|
||||
|
||||
className="text-primary text-lg" />
|
||||
</span>
|
||||
<div className="min-w-0">
|
||||
<div className="text-4xl font-bold leading-none tracking-tight text-white">
|
||||
@@ -127,17 +121,15 @@ export function CreditsModal({ isOpen, onClose }: CreditsModalProps) {
|
||||
</span>
|
||||
<span className="flex items-center gap-1.5 text-base font-semibold text-primary-400">
|
||||
{isLoading ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinnerThird}
|
||||
className="animate-spin"
|
||||
/>
|
||||
<LoaderCircleIcon
|
||||
|
||||
className="animate-spin" />
|
||||
) : (
|
||||
<>
|
||||
Buy
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowRight}
|
||||
className="text-xs transition-transform group-hover:translate-x-0.5"
|
||||
/>
|
||||
<ArrowRightIcon
|
||||
|
||||
className="text-xs transition-transform group-hover:translate-x-0.5" />
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
|
||||
+4
-6
@@ -1,6 +1,5 @@
|
||||
import { type ReactNode, useState } from "react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faSpinnerThird } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { LoaderCircleIcon } from "lucide-react";
|
||||
import { isMobile } from "react-device-detect";
|
||||
import { type PopoverItem } from "@storyteller/ui-popover";
|
||||
import { TabSelector } from "@storyteller/ui-tab-selector";
|
||||
@@ -82,10 +81,9 @@ export function CreateMediaPageShell({
|
||||
if (!authChecked) {
|
||||
return (
|
||||
<div className="flex h-full items-center justify-center bg-[#101014]">
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinnerThird}
|
||||
className="animate-spin text-4xl text-primary/80"
|
||||
/>
|
||||
<LoaderCircleIcon
|
||||
|
||||
className="animate-spin text-4xl text-primary/80" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
+4
-7
@@ -1,9 +1,6 @@
|
||||
import { useMemo, useState } from "react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faArrowDownToLine,
|
||||
faSpinnerThird,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { ArrowDownToLineIcon, LoaderCircleIcon } from "lucide-react";
|
||||
import { DynamicIcon } from "@storyteller/icons";
|
||||
import {
|
||||
SelectionActionBar,
|
||||
useGallerySelectionStore,
|
||||
@@ -88,8 +85,8 @@ export function GallerySelectionDownloadBar({
|
||||
disabled={isDownloading || selectedItems.length === 0}
|
||||
className="flex items-center gap-2 rounded-full bg-ui-controls/60 px-3 py-1.5 text-sm font-medium text-white transition-colors hover:bg-ui-controls/90 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary disabled:opacity-60"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={isDownloading ? faSpinnerThird : faArrowDownToLine}
|
||||
<DynamicIcon
|
||||
icon={isDownloading ? LoaderCircleIcon : ArrowDownToLineIcon}
|
||||
className={`text-xs ${isDownloading ? "animate-spin" : ""}`}
|
||||
/>
|
||||
{isDownloading && progress
|
||||
|
||||
+13
-20
@@ -1,12 +1,5 @@
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faArrowDownToLine,
|
||||
faArrowRotateRight,
|
||||
faCheck,
|
||||
faLink,
|
||||
faSpinnerThird,
|
||||
faVideo,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { ArrowDownToLineIcon, CheckIcon, LinkIcon, LoaderCircleIcon, RotateCwIcon, VideoIcon } from "lucide-react";
|
||||
import { DynamicIcon } from "@storyteller/icons";
|
||||
import { Tooltip } from "@storyteller/ui-tooltip";
|
||||
import {
|
||||
GenerationGridView,
|
||||
@@ -94,8 +87,8 @@ function CardRecreateButton({
|
||||
disabled={isRecreating}
|
||||
className="flex items-center gap-1.5 rounded-md bg-white/5 px-3 py-1.5 text-xs text-white/70 transition-colors hover:bg-white/10 hover:text-white disabled:opacity-60"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={isRecreating ? faSpinnerThird : faArrowRotateRight}
|
||||
<DynamicIcon
|
||||
icon={isRecreating ? LoaderCircleIcon : RotateCwIcon}
|
||||
className={isRecreating ? "animate-spin" : ""}
|
||||
/>
|
||||
Recreate
|
||||
@@ -112,8 +105,8 @@ function CardRecreateButton({
|
||||
aria-label="Recreate"
|
||||
className="flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-white/70 opacity-0 transition hover:bg-white/15 hover:text-white focus-visible:opacity-100 group-hover:opacity-100 disabled:opacity-60"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={isRecreating ? faSpinnerThird : faArrowRotateRight}
|
||||
<DynamicIcon
|
||||
icon={isRecreating ? LoaderCircleIcon : RotateCwIcon}
|
||||
className={`text-sm ${isRecreating ? "animate-spin" : ""}`}
|
||||
/>
|
||||
</button>
|
||||
@@ -156,8 +149,8 @@ function CardActions({
|
||||
disabled={isRecreating}
|
||||
aria-label="Recreate"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={isRecreating ? faSpinnerThird : faArrowRotateRight}
|
||||
<DynamicIcon
|
||||
icon={isRecreating ? LoaderCircleIcon : RotateCwIcon}
|
||||
className={`text-sm ${isRecreating ? "animate-spin" : ""}`}
|
||||
/>
|
||||
</button>
|
||||
@@ -171,7 +164,7 @@ function CardActions({
|
||||
onClick={handleMakeVideo}
|
||||
aria-label="Make Video"
|
||||
>
|
||||
<FontAwesomeIcon icon={faVideo} className="text-sm" />
|
||||
<VideoIcon className="text-sm" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
)}
|
||||
@@ -182,8 +175,8 @@ function CardActions({
|
||||
onClick={handleShare}
|
||||
aria-label="Share"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={shareCopied ? faCheck : faLink}
|
||||
<DynamicIcon
|
||||
icon={shareCopied ? CheckIcon : LinkIcon}
|
||||
className="text-sm"
|
||||
/>
|
||||
</button>
|
||||
@@ -197,8 +190,8 @@ function CardActions({
|
||||
className={buttonClass}
|
||||
aria-label="Download"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={isDownloading ? faSpinnerThird : faArrowDownToLine}
|
||||
<DynamicIcon
|
||||
icon={isDownloading ? LoaderCircleIcon : ArrowDownToLineIcon}
|
||||
className={`text-sm ${isDownloading ? "animate-spin" : ""}`}
|
||||
/>
|
||||
</button>
|
||||
|
||||
+11
-18
@@ -1,12 +1,5 @@
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faArrowDownToLine,
|
||||
faArrowRotateRight,
|
||||
faCheck,
|
||||
faLink,
|
||||
faSpinnerThird,
|
||||
faVideo,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { ArrowDownToLineIcon, CheckIcon, LinkIcon, LoaderCircleIcon, RotateCwIcon, VideoIcon } from "lucide-react";
|
||||
import { DynamicIcon } from "@storyteller/icons";
|
||||
import { Tooltip } from "@storyteller/ui-tooltip";
|
||||
import {
|
||||
GenerationListView,
|
||||
@@ -114,8 +107,8 @@ function RowRecreateButton({
|
||||
: "flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-white/40 transition-colors hover:bg-white/10 hover:text-white disabled:opacity-60"
|
||||
}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={isRecreating ? faSpinnerThird : faArrowRotateRight}
|
||||
<DynamicIcon
|
||||
icon={isRecreating ? LoaderCircleIcon : RotateCwIcon}
|
||||
className={`text-sm ${isRecreating ? "animate-spin" : ""}`}
|
||||
/>
|
||||
</button>
|
||||
@@ -172,8 +165,8 @@ function GalleryRowActions({
|
||||
aria-label="Recreate"
|
||||
className={buttonClass}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={isRecreating ? faSpinnerThird : faArrowRotateRight}
|
||||
<DynamicIcon
|
||||
icon={isRecreating ? LoaderCircleIcon : RotateCwIcon}
|
||||
className={`text-sm ${isRecreating ? "animate-spin" : ""}`}
|
||||
/>
|
||||
</button>
|
||||
@@ -187,7 +180,7 @@ function GalleryRowActions({
|
||||
aria-label="Make Video"
|
||||
className={buttonClass}
|
||||
>
|
||||
<FontAwesomeIcon icon={faVideo} className="text-sm" />
|
||||
<VideoIcon className="text-sm" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
)}
|
||||
@@ -198,8 +191,8 @@ function GalleryRowActions({
|
||||
aria-label="Share"
|
||||
className={buttonClass}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={shareCopied ? faCheck : faLink}
|
||||
<DynamicIcon
|
||||
icon={shareCopied ? CheckIcon : LinkIcon}
|
||||
className="text-sm"
|
||||
/>
|
||||
</button>
|
||||
@@ -213,8 +206,8 @@ function GalleryRowActions({
|
||||
aria-label="Download"
|
||||
className={buttonClass}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={isDownloading ? faSpinnerThird : faArrowDownToLine}
|
||||
<DynamicIcon
|
||||
icon={isDownloading ? LoaderCircleIcon : ArrowDownToLineIcon}
|
||||
className={`text-sm ${isDownloading ? "animate-spin" : ""}`}
|
||||
/>
|
||||
</button>
|
||||
|
||||
+7
-12
@@ -1,13 +1,8 @@
|
||||
import { useCallback, useState } from "react";
|
||||
import { is3DMediaClass } from "@storyteller/ui-generation-list";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import type { IconDefinition } from "@fortawesome/fontawesome-svg-core";
|
||||
import {
|
||||
faCube,
|
||||
faImage,
|
||||
faMusic,
|
||||
faVideo,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { BoxIcon, ImageIcon, MusicIcon, VideoIcon } from "lucide-react";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import { toast } from "../toast/toast";
|
||||
import { downloadMediaFile } from "../../lib/download-media";
|
||||
import {
|
||||
@@ -30,7 +25,7 @@ export interface GalleryItemActions {
|
||||
isVideo: boolean;
|
||||
is3D: boolean;
|
||||
isAudio: boolean;
|
||||
mediaIcon: IconDefinition;
|
||||
mediaIcon: LucideIcon;
|
||||
mediaLabel: string;
|
||||
modelDisplayName: string | null;
|
||||
modelIconPath: string | null;
|
||||
@@ -74,12 +69,12 @@ export function useGalleryItemActions(
|
||||
const canMakeVideo = enableMakeVideo && !isVideo && !is3D && !isAudio;
|
||||
|
||||
const mediaIcon = isVideo
|
||||
? faVideo
|
||||
? VideoIcon
|
||||
: is3D
|
||||
? faCube
|
||||
? BoxIcon
|
||||
: isAudio
|
||||
? faMusic
|
||||
: faImage;
|
||||
? MusicIcon
|
||||
: ImageIcon;
|
||||
const mediaLabel = isVideo ? "Video" : is3D ? "3D" : isAudio ? "Audio" : "Image";
|
||||
|
||||
const effectiveModelId = modelId ?? item.modelId;
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { useState, type ReactNode } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { create } from "zustand";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faCheck } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { CheckIcon } from "lucide-react";
|
||||
import { Modal } from "@storyteller/ui-modal";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { BillingApi } from "@storyteller/api";
|
||||
@@ -163,10 +162,9 @@ export function InsufficientCreditsModal() {
|
||||
className="flex items-start gap-3 text-[14px] text-white/75"
|
||||
>
|
||||
<span className="mt-[2px] flex h-4 w-4 shrink-0 items-center justify-center rounded-full bg-primary/15">
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className="text-[9px] text-primary"
|
||||
/>
|
||||
<CheckIcon
|
||||
|
||||
className="text-[9px] text-primary" />
|
||||
</span>
|
||||
<span>{perk}</span>
|
||||
</li>
|
||||
|
||||
@@ -4,25 +4,8 @@ import { Button } from "@storyteller/ui-button";
|
||||
import { toast } from "../toast/toast";
|
||||
import { Gravatar } from "@storyteller/ui-gravatar";
|
||||
import type { UserInfo } from "@storyteller/api";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faArrowDownToLine,
|
||||
faArrowRotateRight,
|
||||
faCheck,
|
||||
faCircleInfo,
|
||||
faCopy,
|
||||
faImage,
|
||||
faLink,
|
||||
faPause,
|
||||
faPencil,
|
||||
faPhotoFilm,
|
||||
faPlay,
|
||||
faSpinnerThird,
|
||||
faTrashCan,
|
||||
faUser,
|
||||
faVideo,
|
||||
faXmark,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { ArrowDownToLineIcon, CheckIcon, CopyIcon, ImageIcon, ImagesIcon, InfoIcon, LinkIcon, LoaderCircleIcon, PauseIcon, PencilIcon, PlayIcon, RotateCwIcon, Trash2Icon, UserIcon, VideoIcon, XIcon } from "lucide-react";
|
||||
import { DynamicIcon } from "@storyteller/icons";
|
||||
import {
|
||||
getContextImageThumbnail,
|
||||
THUMBNAIL_SIZES,
|
||||
@@ -198,7 +181,7 @@ export function LightboxDetails({
|
||||
className="absolute top-3 right-3 z-20 flex h-8 w-8 items-center justify-center rounded-full bg-black/40 text-white/70 transition-colors hover:bg-black/60 hover:text-white"
|
||||
aria-label="Close"
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} className="h-4 w-4" />
|
||||
<XIcon className="h-4 w-4" />
|
||||
</button>
|
||||
)}
|
||||
|
||||
@@ -234,7 +217,7 @@ export function LightboxDetails({
|
||||
/>
|
||||
) : (
|
||||
<div className="h-9 w-9 shrink-0 flex items-center justify-center rounded-xl bg-white/10 text-white/50 border border-white/5">
|
||||
<FontAwesomeIcon icon={faUser} />
|
||||
<UserIcon />
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col gap-1">
|
||||
@@ -252,7 +235,7 @@ export function LightboxDetails({
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2 text-xs font-medium text-white/60">
|
||||
<FontAwesomeIcon icon={faPencil} />
|
||||
<PencilIcon />
|
||||
<span>Prompt</span>
|
||||
</div>
|
||||
{promptData.text && (
|
||||
@@ -260,8 +243,8 @@ export function LightboxDetails({
|
||||
onClick={handleCopyPrompt}
|
||||
className="flex items-center gap-1.5 text-xs text-white/60 hover:text-white transition-colors"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={promptCopy.copied ? faCheck : faCopy}
|
||||
<DynamicIcon
|
||||
icon={promptCopy.copied ? CheckIcon : CopyIcon}
|
||||
className="h-3 w-3"
|
||||
/>
|
||||
<span>{promptCopy.copied ? "Copied" : "Copy"}</span>
|
||||
@@ -294,7 +277,7 @@ export function LightboxDetails({
|
||||
{promptData.contextImages && promptData.contextImages.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2 text-xs font-medium text-white/60">
|
||||
<FontAwesomeIcon icon={faImage} />
|
||||
<ImageIcon />
|
||||
<span>Reference Media</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-5 gap-2">
|
||||
@@ -315,8 +298,8 @@ export function LightboxDetails({
|
||||
onClick={() => handleAudioToggle(contextImage.media_token, contextImage.media_links.cdn_url)}
|
||||
className="relative aspect-square overflow-hidden rounded-lg border border-white/10 hover:border-white/40 transition-colors flex items-center justify-center bg-white/5"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={isPlayingThis ? faPause : faPlay}
|
||||
<DynamicIcon
|
||||
icon={isPlayingThis ? PauseIcon : PlayIcon}
|
||||
className="text-white/70 text-lg"
|
||||
/>
|
||||
</button>
|
||||
@@ -338,10 +321,9 @@ export function LightboxDetails({
|
||||
/>
|
||||
{isVideoRef && (
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<FontAwesomeIcon
|
||||
icon={faVideo}
|
||||
className="text-white/80 text-sm drop-shadow-lg"
|
||||
/>
|
||||
<VideoIcon
|
||||
|
||||
className="text-white/80 text-sm drop-shadow-lg" />
|
||||
</div>
|
||||
)}
|
||||
</a>
|
||||
@@ -354,7 +336,7 @@ export function LightboxDetails({
|
||||
{hasInfoSection && (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2 text-xs font-medium text-white/60">
|
||||
<FontAwesomeIcon icon={faCircleInfo} />
|
||||
<InfoIcon />
|
||||
<span>Information</span>
|
||||
</div>
|
||||
|
||||
@@ -443,7 +425,7 @@ export function LightboxDetails({
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<Button
|
||||
className="w-full border border-ui-panel-border bg-ui-controls/40 hover:bg-ui-controls/60 text-white"
|
||||
icon={shareCopy.copied ? faCheck : faLink}
|
||||
icon={shareCopy.copied ? CheckIcon : LinkIcon}
|
||||
variant="secondary"
|
||||
onClick={handleCopyShareLink}
|
||||
>
|
||||
@@ -470,8 +452,8 @@ export function LightboxDetails({
|
||||
: "bg-ui-controls/20 text-white/60 cursor-not-allowed"
|
||||
}`}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={isDownloading ? faSpinnerThird : faArrowDownToLine}
|
||||
<DynamicIcon
|
||||
icon={isDownloading ? LoaderCircleIcon : ArrowDownToLineIcon}
|
||||
className={isDownloading ? "animate-spin" : ""}
|
||||
/>
|
||||
{isDownloading ? "Downloading…" : "Download"}
|
||||
@@ -479,7 +461,7 @@ export function LightboxDetails({
|
||||
</div>
|
||||
{onMakeVideo && (
|
||||
<Button
|
||||
icon={faVideo}
|
||||
icon={VideoIcon}
|
||||
className="w-full shadow-lg shadow-brand-primary/20"
|
||||
variant="primary"
|
||||
onClick={onMakeVideo}
|
||||
@@ -489,7 +471,7 @@ export function LightboxDetails({
|
||||
)}
|
||||
{onEditOnCanvas && (
|
||||
<Button
|
||||
icon={faPencil}
|
||||
icon={PencilIcon}
|
||||
className="w-full border border-ui-panel-border bg-ui-controls/40 hover:bg-ui-controls/60 text-white"
|
||||
variant="secondary"
|
||||
onClick={onEditOnCanvas}
|
||||
@@ -499,7 +481,7 @@ export function LightboxDetails({
|
||||
)}
|
||||
{onExtractFrames && (
|
||||
<Button
|
||||
icon={faPhotoFilm}
|
||||
icon={ImagesIcon}
|
||||
className="w-full border border-ui-panel-border bg-ui-controls/40 hover:bg-ui-controls/60 text-white"
|
||||
variant="secondary"
|
||||
onClick={onExtractFrames}
|
||||
@@ -509,7 +491,7 @@ export function LightboxDetails({
|
||||
)}
|
||||
{onRecreate && promptData.hasToken && (
|
||||
<Button
|
||||
icon={faArrowRotateRight}
|
||||
icon={RotateCwIcon}
|
||||
className="w-full border border-ui-panel-border bg-ui-controls/40 hover:bg-ui-controls/60 text-white"
|
||||
variant="secondary"
|
||||
onClick={onRecreate}
|
||||
@@ -519,7 +501,7 @@ export function LightboxDetails({
|
||||
)}
|
||||
{onDelete && mediaToken && (
|
||||
<Button
|
||||
icon={faTrashCan}
|
||||
icon={Trash2Icon}
|
||||
className="w-full bg-red-500/10 hover:bg-red-500/20 text-red-500 border border-red-500/20"
|
||||
variant="destructive"
|
||||
onClick={onDelete}
|
||||
@@ -529,7 +511,7 @@ export function LightboxDetails({
|
||||
)}
|
||||
{showDownloadAppCta && (
|
||||
<Button
|
||||
icon={faArrowDownToLine}
|
||||
icon={ArrowDownToLineIcon}
|
||||
className="w-full shadow-lg shadow-brand-primary/20"
|
||||
variant="primary"
|
||||
onClick={() => {
|
||||
|
||||
@@ -5,13 +5,7 @@ import { useEffect, useState, useMemo, useCallback } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { MediaFilesApi, PromptsApi, type UserInfo } from "@storyteller/api";
|
||||
import { is3DMediaClass } from "@storyteller/ui-generation-list";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faChevronLeft,
|
||||
faChevronRight,
|
||||
faMusic,
|
||||
faTrashCan,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { ChevronLeftIcon, ChevronRightIcon, MusicIcon, Trash2Icon } from "lucide-react";
|
||||
import { addCorsParam, PLACEHOLDER_IMAGES } from "@storyteller/common";
|
||||
import { ActionReminderModal } from "@storyteller/ui-action-reminder-modal";
|
||||
import { Viewer3D } from "@storyteller/ui-viewer-3d";
|
||||
@@ -427,10 +421,9 @@ export function Lightbox({
|
||||
<div className="flex h-full w-full items-center justify-center px-4 sm:px-8">
|
||||
<div className="w-full max-w-xl rounded-2xl border border-white/10 bg-gradient-to-br from-white/[0.08] via-white/[0.04] to-white/[0.02] px-4 py-10 sm:px-6">
|
||||
<div className="mx-auto mb-6 flex h-16 w-16 items-center justify-center rounded-full bg-white/10 ring-1 ring-white/15">
|
||||
<FontAwesomeIcon
|
||||
icon={faMusic}
|
||||
className="text-2xl text-white/70"
|
||||
/>
|
||||
<MusicIcon
|
||||
|
||||
className="text-2xl text-white/70" />
|
||||
</div>
|
||||
<WaveformAudioPlayer
|
||||
key={selectedImageUrl}
|
||||
@@ -528,7 +521,7 @@ export function Lightbox({
|
||||
className="absolute left-2 sm:left-3 top-1/2 -translate-y-1/2 z-30 flex h-9 w-9 sm:h-10 sm:w-10 items-center justify-center rounded-full bg-black/50 text-white/70 sm:opacity-0 transition-opacity duration-200 hover:bg-black/70 hover:text-white sm:group-hover/nav:opacity-100"
|
||||
aria-label="Previous item"
|
||||
>
|
||||
<FontAwesomeIcon icon={faChevronLeft} className="text-lg" />
|
||||
<ChevronLeftIcon className="text-lg" />
|
||||
</button>
|
||||
)}
|
||||
{onNavigateNext && (
|
||||
@@ -540,7 +533,7 @@ export function Lightbox({
|
||||
className="absolute right-2 sm:right-3 top-1/2 -translate-y-1/2 z-30 flex h-9 w-9 sm:h-10 sm:w-10 items-center justify-center rounded-full bg-black/50 text-white/70 sm:opacity-0 transition-opacity duration-200 hover:bg-black/70 hover:text-white sm:group-hover/nav:opacity-100"
|
||||
aria-label="Next item"
|
||||
>
|
||||
<FontAwesomeIcon icon={faChevronRight} className="text-lg" />
|
||||
<ChevronRightIcon className="text-lg" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -590,7 +583,7 @@ export function Lightbox({
|
||||
onPrimaryAction={handleDelete}
|
||||
primaryActionText="Delete"
|
||||
secondaryActionText="Cancel"
|
||||
primaryActionIcon={faTrashCan}
|
||||
primaryActionIcon={Trash2Icon}
|
||||
primaryActionBtnClassName="bg-red-500/10 hover:bg-red-500/20 text-red-500"
|
||||
/>
|
||||
</>
|
||||
|
||||
@@ -1,17 +1,6 @@
|
||||
import { useState, useRef, useCallback, useEffect } from "react";
|
||||
import { Modal } from "@storyteller/ui-modal";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faPlus,
|
||||
faArrowLeft,
|
||||
faUpload,
|
||||
faUserGroup,
|
||||
faSpinnerThird,
|
||||
faXmark,
|
||||
faPen,
|
||||
faTrashAlt,
|
||||
faEye,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { ArrowLeftIcon, EyeIcon, LoaderCircleIcon, PenIcon, PlusIcon, Trash2Icon, UploadIcon, UsersIcon, XIcon } from "lucide-react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import {
|
||||
CharactersApi,
|
||||
@@ -370,7 +359,7 @@ const CharacterListView = ({
|
||||
className="flex flex-col items-center justify-center gap-2 overflow-hidden rounded-lg border-2 border-dashed border-white/10 bg-white/[0.05] text-white/60 transition-colors hover:border-white/25 hover:text-white/80"
|
||||
>
|
||||
<div className="flex aspect-square w-full flex-col items-center justify-center gap-2">
|
||||
<FontAwesomeIcon icon={faPlus} className="text-lg" />
|
||||
<PlusIcon className="text-lg" />
|
||||
<span className="text-sm font-medium">Create New</span>
|
||||
</div>
|
||||
</button>
|
||||
@@ -390,14 +379,13 @@ const CharacterListView = ({
|
||||
/>
|
||||
) : (
|
||||
<div className="flex h-full w-full items-center justify-center text-white/20">
|
||||
<FontAwesomeIcon icon={faUserGroup} className="text-2xl" />
|
||||
<UsersIcon className="text-2xl" />
|
||||
</div>
|
||||
)}
|
||||
<div className="absolute inset-0 flex flex-col items-center justify-center gap-2 bg-black/40">
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinnerThird}
|
||||
className="text-lg text-white/80 animate-spin"
|
||||
/>
|
||||
<LoaderCircleIcon
|
||||
|
||||
className="text-lg text-white/80 animate-spin" />
|
||||
<span className="text-xs font-medium text-white/80">
|
||||
Creating...
|
||||
</span>
|
||||
@@ -433,10 +421,9 @@ const CharacterListView = ({
|
||||
/>
|
||||
) : (
|
||||
<div className="flex h-full w-full items-center justify-center text-white/20">
|
||||
<FontAwesomeIcon
|
||||
icon={faUserGroup}
|
||||
className="text-2xl"
|
||||
/>
|
||||
<UsersIcon
|
||||
|
||||
className="text-2xl" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -460,7 +447,7 @@ const CharacterListView = ({
|
||||
title="View full size"
|
||||
className="flex h-7 w-7 items-center justify-center rounded-full bg-black/60 text-white/80 transition-colors hover:bg-black/80"
|
||||
>
|
||||
<FontAwesomeIcon icon={faEye} className="text-[10px]" />
|
||||
<EyeIcon className="text-[10px]" />
|
||||
</button>
|
||||
)}
|
||||
{isUserCreated && (
|
||||
@@ -472,10 +459,9 @@ const CharacterListView = ({
|
||||
}}
|
||||
className="flex h-7 w-7 items-center justify-center rounded-full bg-black/60 text-white/80 transition-colors hover:bg-black/80"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faPen}
|
||||
className="text-[10px]"
|
||||
/>
|
||||
<PenIcon
|
||||
|
||||
className="text-[10px]" />
|
||||
</button>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
@@ -484,10 +470,9 @@ const CharacterListView = ({
|
||||
}}
|
||||
className="flex h-7 w-7 items-center justify-center rounded-full bg-black/60 text-white/80 transition-colors hover:bg-red-500"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faTrashAlt}
|
||||
className="text-[10px]"
|
||||
/>
|
||||
<Trash2Icon
|
||||
|
||||
className="text-[10px]" />
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
@@ -616,7 +601,7 @@ const EditCharacterView = ({
|
||||
onClick={onBack}
|
||||
className="flex items-center justify-center text-white/60 transition-colors hover:text-white"
|
||||
>
|
||||
<FontAwesomeIcon icon={faArrowLeft} />
|
||||
<ArrowLeftIcon />
|
||||
</button>
|
||||
<h2 className="text-xl font-bold text-white">Edit Character</h2>
|
||||
</div>
|
||||
@@ -865,7 +850,7 @@ const NewCharacterView = ({
|
||||
onClick={onBack}
|
||||
className="flex items-center justify-center text-white/60 transition-colors hover:text-white"
|
||||
>
|
||||
<FontAwesomeIcon icon={faArrowLeft} />
|
||||
<ArrowLeftIcon />
|
||||
</button>
|
||||
<h2 className="text-xl font-bold text-white">New Character</h2>
|
||||
</div>
|
||||
@@ -895,10 +880,9 @@ const NewCharacterView = ({
|
||||
/>
|
||||
{!images[0]!.mediaToken && (
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-black/50">
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinnerThird}
|
||||
className="text-white animate-spin"
|
||||
/>
|
||||
<LoaderCircleIcon
|
||||
|
||||
className="text-white animate-spin" />
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
@@ -908,15 +892,14 @@ const NewCharacterView = ({
|
||||
}}
|
||||
className="absolute right-2 top-2 flex h-7 w-7 items-center justify-center rounded-full bg-black/60 text-white/80 opacity-0 transition-all group-hover:opacity-100 hover:bg-red-500"
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} className="text-sm" />
|
||||
<XIcon className="text-sm" />
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex h-full w-full flex-col items-center justify-center text-white/60">
|
||||
<FontAwesomeIcon
|
||||
icon={faUpload}
|
||||
className="mb-2 text-xl text-white/40"
|
||||
/>
|
||||
<UploadIcon
|
||||
|
||||
className="mb-2 text-xl text-white/40" />
|
||||
<p className="text-sm">Upload reference image</p>
|
||||
<p className="mb-3 text-xs text-white/40">
|
||||
Click or drag an image here
|
||||
|
||||
@@ -6,14 +6,7 @@ import {
|
||||
type CSSProperties,
|
||||
type SyntheticEvent,
|
||||
} from "react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faImages,
|
||||
faPlus,
|
||||
faSpinnerThird,
|
||||
faXmark,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { faImage } from "@fortawesome/pro-regular-svg-icons";
|
||||
import { ImageIcon, ImagesIcon, LoaderCircleIcon, PlusIcon, XIcon } from "lucide-react";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { Tooltip } from "@storyteller/ui-tooltip";
|
||||
import { Modal } from "@storyteller/ui-modal";
|
||||
@@ -252,7 +245,7 @@ export const ImagePromptRow = ({
|
||||
<div className="flex min-w-0 flex-1 gap-2 px-3 py-2">
|
||||
<div className="flex grow flex-col gap-1 min-w-32">
|
||||
<div className="flex items-center gap-2 text-white/90">
|
||||
<FontAwesomeIcon icon={faImage} className="h-3.5 w-3.5" />
|
||||
<ImageIcon className="h-3.5 w-3.5" />
|
||||
<span className="flex items-center gap-1.5 text-sm font-medium">
|
||||
{sectionLabel}
|
||||
{showCount && (
|
||||
@@ -329,7 +322,7 @@ export const ImagePromptRow = ({
|
||||
<div className="hidden sm:block w-[1px] bg-white/10" />
|
||||
<div className="flex grow flex-col gap-1 sm:p-2">
|
||||
<div className="flex items-center gap-2 text-white/90">
|
||||
<FontAwesomeIcon icon={faImage} className="h-3.5 w-3.5" />
|
||||
<ImageIcon className="h-3.5 w-3.5" />
|
||||
<span className="flex items-center gap-1.5 text-sm font-medium">
|
||||
End Frame{" "}
|
||||
<span className="text-xs text-white/60">(optional)</span>
|
||||
@@ -358,16 +351,15 @@ export const ImagePromptRow = ({
|
||||
}}
|
||||
className="absolute right-[2px] top-[2px] flex h-5 w-5 cursor-pointer items-center justify-center rounded-full bg-black/50 text-white sm:opacity-0 backdrop-blur-md transition-colors hover:bg-black sm:group-hover:opacity-100"
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} className="h-2.5 w-2.5" />
|
||||
<XIcon className="h-2.5 w-2.5" />
|
||||
</button>
|
||||
</div>
|
||||
) : uploadingEndFrame ? (
|
||||
<div className="flex aspect-square w-10 sm:w-14 items-center justify-center overflow-hidden rounded-lg border-2 border-white/30 bg-white/5">
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinnerThird}
|
||||
<LoaderCircleIcon
|
||||
|
||||
spin
|
||||
className="h-5 w-5 text-white"
|
||||
/>
|
||||
className="h-5 w-5 text-white" />
|
||||
</div>
|
||||
) : (
|
||||
<AddButton
|
||||
@@ -423,7 +415,7 @@ export const AddButton = ({
|
||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||
|
||||
const plus = (
|
||||
<FontAwesomeIcon icon={faPlus} className="text-2xl text-white/80" />
|
||||
<PlusIcon className="text-2xl text-white/80" />
|
||||
);
|
||||
|
||||
if (!onPickFromLibrary) {
|
||||
@@ -456,7 +448,7 @@ export const AddButton = ({
|
||||
<div className="flex flex-col gap-2 pb-2">
|
||||
<Button
|
||||
variant="primary"
|
||||
icon={faPlus}
|
||||
icon={PlusIcon}
|
||||
className="w-full"
|
||||
onClick={() => {
|
||||
onUpload();
|
||||
@@ -467,7 +459,7 @@ export const AddButton = ({
|
||||
</Button>
|
||||
<Button
|
||||
variant="action"
|
||||
icon={faImages}
|
||||
icon={ImagesIcon}
|
||||
className="w-full bg-white/15 hover:bg-white/20"
|
||||
onClick={() => {
|
||||
setDrawerOpen(false);
|
||||
@@ -494,7 +486,7 @@ export const AddButton = ({
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={onUpload}
|
||||
icon={faPlus}
|
||||
icon={PlusIcon}
|
||||
className="w-full"
|
||||
>
|
||||
Upload
|
||||
@@ -502,7 +494,7 @@ export const AddButton = ({
|
||||
<Button
|
||||
variant="action"
|
||||
onClick={onPickFromLibrary}
|
||||
icon={faImages}
|
||||
icon={ImagesIcon}
|
||||
className="w-full bg-white/15 hover:bg-white/20"
|
||||
>
|
||||
Pick from library
|
||||
@@ -542,7 +534,7 @@ const ImageThumbnail = ({
|
||||
}}
|
||||
className="absolute right-[2px] top-[2px] flex h-5 w-5 cursor-pointer items-center justify-center rounded-full bg-black/50 text-white sm:opacity-0 backdrop-blur-md transition-colors hover:bg-black sm:group-hover:opacity-100"
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} className="h-2.5 w-2.5" />
|
||||
<XIcon className="h-2.5 w-2.5" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
@@ -601,7 +593,7 @@ const SortableImage = ({
|
||||
onPointerDown={(e) => e.stopPropagation()}
|
||||
className="absolute right-[2px] top-[2px] flex h-5 w-5 cursor-pointer items-center justify-center rounded-full bg-black/50 text-white sm:opacity-0 backdrop-blur-md transition-colors hover:bg-black sm:group-hover:opacity-100"
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} className="h-2.5 w-2.5" />
|
||||
<XIcon className="h-2.5 w-2.5" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
@@ -620,11 +612,10 @@ const UploadingThumbnail = ({ file }: { file: File }) => {
|
||||
/>
|
||||
</div>
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-black/20">
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinnerThird}
|
||||
<LoaderCircleIcon
|
||||
|
||||
spin
|
||||
className="h-6 w-6 text-white"
|
||||
/>
|
||||
className="h-6 w-6 text-white" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faMusic,
|
||||
faPlay,
|
||||
faSpinnerThird,
|
||||
faStop,
|
||||
faVideo,
|
||||
faXmark,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { LoaderCircleIcon, MusicIcon, PlayIcon, SquareIcon, VideoIcon, XIcon } from "lucide-react";
|
||||
import { DynamicIcon } from "@storyteller/icons";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { UploaderStates } from "@storyteller/common";
|
||||
import {
|
||||
@@ -234,7 +227,7 @@ export const MediaReferenceRow = ({
|
||||
<div className="flex grow gap-2 px-3 py-2">
|
||||
<div className="flex grow flex-col gap-1">
|
||||
<div className="flex items-center gap-2 text-white/90">
|
||||
<FontAwesomeIcon icon={faVideo} className="h-3.5 w-3.5" />
|
||||
<VideoIcon className="h-3.5 w-3.5" />
|
||||
<span className="flex items-center gap-1.5 text-sm font-medium">
|
||||
Video Ref
|
||||
<span className="font-semibold text-white/60">
|
||||
@@ -256,11 +249,10 @@ export const MediaReferenceRow = ({
|
||||
))}
|
||||
{uploadingVideo && (
|
||||
<div className="flex aspect-square w-10 sm:w-14 items-center justify-center overflow-hidden rounded-lg border-2 border-white/30 bg-white/5">
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinnerThird}
|
||||
<LoaderCircleIcon
|
||||
|
||||
spin
|
||||
className="h-5 w-5 text-white/60"
|
||||
/>
|
||||
className="h-5 w-5 text-white/60" />
|
||||
</div>
|
||||
)}
|
||||
{canAddVideo && (
|
||||
@@ -283,7 +275,7 @@ export const MediaReferenceRow = ({
|
||||
<div className="flex grow gap-2 px-3 py-2">
|
||||
<div className="flex grow flex-col gap-1">
|
||||
<div className="flex items-center gap-2 text-white/90">
|
||||
<FontAwesomeIcon icon={faMusic} className="h-3.5 w-3.5" />
|
||||
<MusicIcon className="h-3.5 w-3.5" />
|
||||
<span className="flex items-center gap-1.5 text-sm font-medium">
|
||||
Audio Ref
|
||||
<span className="font-semibold text-white/60">
|
||||
@@ -306,11 +298,10 @@ export const MediaReferenceRow = ({
|
||||
))}
|
||||
{uploadingAudio && (
|
||||
<div className="flex aspect-square w-10 sm:w-14 items-center justify-center overflow-hidden rounded-lg border-2 border-white/30 bg-white/5">
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinnerThird}
|
||||
<LoaderCircleIcon
|
||||
|
||||
spin
|
||||
className="h-5 w-5 text-white/60"
|
||||
/>
|
||||
className="h-5 w-5 text-white/60" />
|
||||
</div>
|
||||
)}
|
||||
{canAddAudio && (
|
||||
@@ -354,7 +345,7 @@ const VideoRefTile = ({
|
||||
}}
|
||||
className="absolute right-[2px] top-[2px] flex h-5 w-5 cursor-pointer items-center justify-center rounded-full bg-black/50 text-white sm:opacity-0 backdrop-blur-md transition-colors hover:bg-black sm:group-hover:opacity-100"
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} className="h-2.5 w-2.5" />
|
||||
<XIcon className="h-2.5 w-2.5" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
@@ -396,8 +387,8 @@ const AudioRefTile = ({
|
||||
onClick={handleTogglePlay}
|
||||
className="flex h-full w-full items-center justify-center"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={isPlaying ? faStop : faPlay}
|
||||
<DynamicIcon
|
||||
icon={isPlaying ? SquareIcon : PlayIcon}
|
||||
className={twMerge(
|
||||
"h-5 w-5 transition-colors",
|
||||
isPlaying ? "text-red-400" : "text-white/60 group-hover:text-white",
|
||||
@@ -414,7 +405,7 @@ const AudioRefTile = ({
|
||||
}}
|
||||
className="absolute right-[2px] top-[2px] flex h-5 w-5 cursor-pointer items-center justify-center rounded-full bg-black/50 text-white sm:opacity-0 backdrop-blur-md transition-colors hover:bg-black sm:group-hover:opacity-100"
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} className="h-2.5 w-2.5" />
|
||||
<XIcon className="h-2.5 w-2.5" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -8,14 +8,8 @@ import {
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faChevronDown,
|
||||
faChevronUp,
|
||||
faMusic,
|
||||
faUserGroup,
|
||||
faVideo,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { ChevronDownIcon, ChevronUpIcon, MusicIcon, UsersIcon, VideoIcon } from "lucide-react";
|
||||
import { DynamicIcon } from "@storyteller/icons";
|
||||
import { GenerateIconButton } from "@storyteller/ui-button";
|
||||
import { Tooltip } from "@storyteller/ui-tooltip";
|
||||
import {
|
||||
@@ -891,14 +885,13 @@ export const PromptBox = forwardRef<HTMLDivElement, PromptBoxProps>(
|
||||
className="h-full w-full object-cover"
|
||||
/>
|
||||
) : item.type === "character" ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faUserGroup}
|
||||
className="h-3.5 w-3.5 text-white/60"
|
||||
/>
|
||||
<UsersIcon
|
||||
|
||||
className="h-3.5 w-3.5 text-white/60" />
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
<DynamicIcon
|
||||
icon={
|
||||
item.type === "video" ? faVideo : faMusic
|
||||
item.type === "video" ? VideoIcon : MusicIcon
|
||||
}
|
||||
className="h-3.5 w-3.5 text-white/60"
|
||||
/>
|
||||
@@ -978,8 +971,8 @@ export const PromptBox = forwardRef<HTMLDivElement, PromptBoxProps>(
|
||||
onClick={toggleExpand}
|
||||
className="px-3 py-0.5 text-white/30 transition-colors hover:text-white/90"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={isExpanded ? faChevronUp : faChevronDown}
|
||||
<DynamicIcon
|
||||
icon={isExpanded ? ChevronUpIcon : ChevronDownIcon}
|
||||
className="text-xs"
|
||||
/>
|
||||
</button>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faImages, faMusic, faVideo } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { ImagesIcon, MusicIcon, VideoIcon } from "lucide-react";
|
||||
import { DynamicIcon } from "@storyteller/icons";
|
||||
import { toast } from "../toast/toast";
|
||||
|
||||
// ── Types ───────────────────────────────────────────────────────────────
|
||||
@@ -235,10 +235,10 @@ export function PromptBoxDropOverlay({
|
||||
|
||||
const kinds = [
|
||||
...(acceptsImages
|
||||
? [{ icon: faImages, label: keyframeMode ? "Frames" : "Images" }]
|
||||
? [{ icon: ImagesIcon, label: keyframeMode ? "Frames" : "Images" }]
|
||||
: []),
|
||||
...(acceptsVideos ? [{ icon: faVideo, label: "Video" }] : []),
|
||||
...(acceptsAudio ? [{ icon: faMusic, label: "Audio" }] : []),
|
||||
...(acceptsVideos ? [{ icon: VideoIcon, label: "Video" }] : []),
|
||||
...(acceptsAudio ? [{ icon: MusicIcon, label: "Audio" }] : []),
|
||||
];
|
||||
const rejected = dragState === "reject";
|
||||
|
||||
@@ -256,7 +256,7 @@ export function PromptBoxDropOverlay({
|
||||
className="promptbox-drop-chip flex h-9 w-9 items-center justify-center rounded-lg bg-white/10 text-base-fg/90"
|
||||
style={{ animationDelay: `${i * 50}ms` }}
|
||||
>
|
||||
<FontAwesomeIcon icon={kind.icon} className="text-sm" />
|
||||
<DynamicIcon icon={kind.icon} className="text-sm" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -2,8 +2,7 @@ import { ReactNode, useCallback, useEffect, useRef, useState } from "react";
|
||||
import { Modal } from "@storyteller/ui-modal";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { Tooltip } from "@storyteller/ui-tooltip";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faUpRightAndDownLeftFromCenter } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { Maximize2Icon } from "lucide-react";
|
||||
|
||||
// Webapp "focus mode" for the prompt editor. Mirrors the desktop lib's
|
||||
// (@storyteller/ui-promptbox) fullscreen feature but lives here because the
|
||||
@@ -35,10 +34,9 @@ export const PromptFullscreenButton = ({
|
||||
onClick={onClick}
|
||||
className="flex h-6 w-6 items-center justify-center rounded-full bg-base-fg/5 text-base-fg/50 transition-colors hover:bg-base-fg/10 hover:text-base-fg/90 focus:outline-none"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faUpRightAndDownLeftFromCenter}
|
||||
className="h-3 w-3"
|
||||
/>
|
||||
<Maximize2Icon
|
||||
|
||||
className="h-3 w-3" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faCheck } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { CheckIcon } from "lucide-react";
|
||||
import { type PopoverItem } from "@storyteller/ui-popover";
|
||||
|
||||
interface DrawerOptionListProps {
|
||||
@@ -42,10 +41,9 @@ export function DrawerOptionList({ items, onSelect }: DrawerOptionListProps) {
|
||||
</span>
|
||||
{item.trailing}
|
||||
{item.selected && (
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className="h-3.5 w-3.5 shrink-0 text-primary"
|
||||
/>
|
||||
<CheckIcon
|
||||
|
||||
className="h-3.5 w-3.5 shrink-0 text-primary" />
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
|
||||
+3
-4
@@ -1,5 +1,4 @@
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faMinus, faPlus } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { MinusIcon, PlusIcon } from "lucide-react";
|
||||
|
||||
interface MobileCountStepperProps {
|
||||
value: number;
|
||||
@@ -41,7 +40,7 @@ export function MobileCountStepper({
|
||||
disabled={index <= 0}
|
||||
className="flex h-9 w-8 items-center justify-center rounded-lg text-base-fg/80 transition-colors hover:bg-white/10 disabled:opacity-30"
|
||||
>
|
||||
<FontAwesomeIcon icon={faMinus} className="h-3 w-3" />
|
||||
<MinusIcon className="h-3 w-3" />
|
||||
</button>
|
||||
<span className="min-w-10 text-center text-sm font-semibold tabular-nums text-base-fg">
|
||||
{value}/{maxValue}
|
||||
@@ -52,7 +51,7 @@ export function MobileCountStepper({
|
||||
disabled={index === allowed.length - 1}
|
||||
className="flex h-9 w-8 items-center justify-center rounded-lg text-base-fg/80 transition-colors hover:bg-white/10 disabled:opacity-30"
|
||||
>
|
||||
<FontAwesomeIcon icon={faPlus} className="h-3 w-3" />
|
||||
<PlusIcon className="h-3 w-3" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
+4
-6
@@ -1,7 +1,6 @@
|
||||
import { type ReactNode } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faChevronRight } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { ChevronRightIcon } from "lucide-react";
|
||||
|
||||
interface MobileFieldButtonProps {
|
||||
label: string;
|
||||
@@ -44,10 +43,9 @@ export function MobileFieldButton({
|
||||
<span className="truncate text-sm font-semibold text-base-fg">
|
||||
{value}
|
||||
</span>
|
||||
<FontAwesomeIcon
|
||||
icon={faChevronRight}
|
||||
className="h-3 w-3 shrink-0 text-base-fg/40"
|
||||
/>
|
||||
<ChevronRightIcon
|
||||
|
||||
className="h-3 w-3 shrink-0 text-base-fg/40" />
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useRef, type ReactNode } from "react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faXmark } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { XIcon } from "lucide-react";
|
||||
import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
@@ -73,7 +72,7 @@ export function SettingsDrawer({
|
||||
onClick={() => onOpenChange(false)}
|
||||
className="absolute right-3 top-3 z-10 flex h-8 w-8 items-center justify-center rounded-full bg-white/10 text-white/70 transition-colors hover:bg-white/20 hover:text-white"
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} className="text-sm" />
|
||||
<XIcon className="text-sm" />
|
||||
</button>
|
||||
{/* Drag zone: the handle pill + header pull the sheet down; past the
|
||||
threshold, releasing dismisses. `touch-none` keeps the browser
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { useState } from "react";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { Input } from "@storyteller/ui-input";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faSpinnerThird } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { LoaderCircleIcon } from "lucide-react";
|
||||
import { UsersApi, type UserInfo } from "@storyteller/api";
|
||||
import { refreshSession, updateSessionUser } from "../../lib/session";
|
||||
import { toast } from "../toast/toast";
|
||||
@@ -516,7 +515,7 @@ function FormActions({
|
||||
disabled={submitting || disabled}
|
||||
>
|
||||
{submitting ? (
|
||||
<FontAwesomeIcon icon={faSpinnerThird} className="animate-spin" />
|
||||
<LoaderCircleIcon className="animate-spin" />
|
||||
) : (
|
||||
"Save"
|
||||
)}
|
||||
|
||||
@@ -2,14 +2,8 @@ import { useEffect, useState } from "react";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { Input } from "@storyteller/ui-input";
|
||||
import { Modal } from "@storyteller/ui-modal";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faSpinnerThird,
|
||||
faCopy,
|
||||
faCheck,
|
||||
faKey,
|
||||
faTrash,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { CheckIcon, CopyIcon, KeyIcon, LoaderCircleIcon, TrashIcon } from "lucide-react";
|
||||
import { DynamicIcon } from "@storyteller/icons";
|
||||
import {
|
||||
UserApiKeysApi,
|
||||
type ApiKeyInfo,
|
||||
@@ -137,7 +131,7 @@ export function ApiKeySection(_props: ApiKeySectionProps) {
|
||||
|
||||
{loading ? (
|
||||
<div className="py-6 text-center text-xs opacity-60">
|
||||
<FontAwesomeIcon icon={faSpinnerThird} className="animate-spin" />
|
||||
<LoaderCircleIcon className="animate-spin" />
|
||||
</div>
|
||||
) : keys.length === 0 ? (
|
||||
<div className="py-6 text-center text-xs opacity-60">
|
||||
@@ -175,7 +169,7 @@ export function ApiKeySection(_props: ApiKeySectionProps) {
|
||||
onClick={() => load(false)}
|
||||
>
|
||||
{loadingMore ? (
|
||||
<FontAwesomeIcon icon={faSpinnerThird} className="animate-spin" />
|
||||
<LoaderCircleIcon className="animate-spin" />
|
||||
) : (
|
||||
"Load more"
|
||||
)}
|
||||
@@ -287,7 +281,7 @@ function NewKeyReveal({
|
||||
return (
|
||||
<div className="flex flex-col gap-2 rounded-lg border border-primary/40 bg-primary/10 p-3">
|
||||
<div className="flex items-center gap-2 text-sm font-medium">
|
||||
<FontAwesomeIcon icon={faKey} className="opacity-80" />
|
||||
<KeyIcon className="opacity-80" />
|
||||
<span>API key created</span>
|
||||
</div>
|
||||
<p className="text-xs opacity-80">
|
||||
@@ -303,8 +297,8 @@ function NewKeyReveal({
|
||||
className="h-8 px-3 shrink-0"
|
||||
onClick={handleCopy}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={copied ? faCheck : faCopy}
|
||||
<DynamicIcon
|
||||
icon={copied ? CheckIcon : CopyIcon}
|
||||
className="mr-1.5"
|
||||
/>
|
||||
{copied ? "Copied" : "Copy"}
|
||||
@@ -377,7 +371,7 @@ function ApiKeyRow({
|
||||
className="h-8 px-2.5 text-red-400"
|
||||
onClick={onRequestDelete}
|
||||
>
|
||||
<FontAwesomeIcon icon={faTrash} />
|
||||
<TrashIcon />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
@@ -509,7 +503,7 @@ function DeleteConfirmModal({
|
||||
disabled={deleting}
|
||||
>
|
||||
{deleting ? (
|
||||
<FontAwesomeIcon icon={faSpinnerThird} className="animate-spin" />
|
||||
<LoaderCircleIcon className="animate-spin" />
|
||||
) : (
|
||||
"Delete"
|
||||
)}
|
||||
@@ -549,7 +543,7 @@ function FormActions({
|
||||
disabled={submitting || disabled}
|
||||
>
|
||||
{submitting ? (
|
||||
<FontAwesomeIcon icon={faSpinnerThird} className="animate-spin" />
|
||||
<LoaderCircleIcon className="animate-spin" />
|
||||
) : (
|
||||
submitLabel
|
||||
)}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Modal } from "@storyteller/ui-modal";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faCog, faUser, faKey } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { KeyIcon, SettingsIcon, UserIcon } from "lucide-react";
|
||||
import { DynamicIcon } from "@storyteller/icons";
|
||||
import { Switch } from "@storyteller/ui-switch";
|
||||
import { useModelPickerStyleStore } from "@storyteller/ui-popover";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
@@ -18,10 +18,10 @@ interface SettingsModalProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const TABS: { id: Tab; label: string; icon: typeof faCog }[] = [
|
||||
{ id: "general", label: "General", icon: faCog },
|
||||
{ id: "account", label: "Account", icon: faUser },
|
||||
{ id: "apiKeys", label: "API Keys", icon: faKey },
|
||||
const TABS: { id: Tab; label: string; icon: typeof SettingsIcon }[] = [
|
||||
{ id: "general", label: "General", icon: SettingsIcon },
|
||||
{ id: "account", label: "Account", icon: UserIcon },
|
||||
{ id: "apiKeys", label: "API Keys", icon: KeyIcon },
|
||||
];
|
||||
|
||||
export function SettingsModal({ isOpen, onClose }: SettingsModalProps) {
|
||||
@@ -58,7 +58,7 @@ export function SettingsModal({ isOpen, onClose }: SettingsModalProps) {
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-2.5 whitespace-nowrap text-sm">
|
||||
<FontAwesomeIcon icon={t.icon} />
|
||||
<DynamicIcon icon={t.icon} />
|
||||
{t.label}
|
||||
</div>
|
||||
</button>
|
||||
|
||||
@@ -1,26 +1,9 @@
|
||||
import { useMemo } from "react";
|
||||
import { Link, useLocation } from "react-router-dom";
|
||||
import { motion, useReducedMotion } from "framer-motion";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faHouse,
|
||||
faVideo,
|
||||
faImage,
|
||||
faMusic,
|
||||
faCube,
|
||||
faGlobe,
|
||||
faFilm,
|
||||
faObjectGroup,
|
||||
faWandMagicSparkles,
|
||||
faGraduationCap,
|
||||
faNewspaper,
|
||||
faCircleQuestion,
|
||||
faDownload,
|
||||
faGift,
|
||||
faPencil,
|
||||
faPhotoFilm,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { faDiscord } from "@fortawesome/free-brands-svg-icons";
|
||||
import { BoxIcon, CircleHelpIcon, DownloadIcon, FilmIcon, GiftIcon, GlobeIcon, GraduationCapIcon, GroupIcon, HouseIcon, ImageIcon, ImagesIcon, MusicIcon, NewspaperIcon, PencilIcon, VideoIcon, WandSparklesIcon } from "lucide-react";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import { DynamicIcon, DiscordIcon } from "@storyteller/icons";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { USER_FEATURE_FLAGS } from "@storyteller/api";
|
||||
import { useSession } from "../../lib/session";
|
||||
@@ -38,7 +21,6 @@ import {
|
||||
SidebarTrigger,
|
||||
useSidebar,
|
||||
} from "../ui/sidebar";
|
||||
import type { IconDefinition } from "@fortawesome/fontawesome-svg-core";
|
||||
import { MARKETING_SITE, SOCIAL_LINKS } from "../../config/links";
|
||||
import { useSceneCacheStore } from "../../pages/pagescene/scene-cache-store";
|
||||
import { LibraryFoldersNav } from "./library-folders-nav";
|
||||
@@ -46,43 +28,43 @@ import { LibraryFoldersNav } from "./library-folders-nav";
|
||||
type NavItem = {
|
||||
label: string;
|
||||
href: string;
|
||||
icon: IconDefinition;
|
||||
icon: LucideIcon;
|
||||
external?: boolean;
|
||||
badge?: string;
|
||||
};
|
||||
|
||||
const PRIMARY_ITEMS: NavItem[] = [{ label: "Home", href: "/", icon: faHouse }];
|
||||
const PRIMARY_ITEMS: NavItem[] = [{ label: "Home", href: "/", icon: HouseIcon }];
|
||||
|
||||
// Generation entries — make something from a prompt.
|
||||
const CREATE_ITEMS_STATIC: NavItem[] = [
|
||||
{ label: "Image", href: "/create-image", icon: faImage },
|
||||
{ label: "Video", href: "/create-video", icon: faVideo },
|
||||
{ label: "Audio", href: "/create-audio", icon: faMusic },
|
||||
{ label: "3D Object", href: "/create-object", icon: faCube },
|
||||
{ label: "3D World", href: "/create-world", icon: faGlobe },
|
||||
{ label: "Image", href: "/create-image", icon: ImageIcon },
|
||||
{ label: "Video", href: "/create-video", icon: VideoIcon },
|
||||
{ label: "Audio", href: "/create-audio", icon: MusicIcon },
|
||||
{ label: "3D Object", href: "/create-object", icon: BoxIcon },
|
||||
{ label: "3D World", href: "/create-world", icon: GlobeIcon },
|
||||
];
|
||||
|
||||
// "Studio" entries — edit, compose, and refine existing content. "Edit 3D"'s
|
||||
// href is computed at render time from the scene-cache store (see useStudioItems).
|
||||
const STUDIO_ITEMS_STATIC: NavItem[] = [
|
||||
{ label: "Edit Image", href: "/edit-image", icon: faPencil },
|
||||
{ label: "Edit 3D", href: "/edit-3d", icon: faCube },
|
||||
{ label: "Edit Video", href: "/video-editor", icon: faFilm, badge: "BETA" },
|
||||
{ label: "Edit Image", href: "/edit-image", icon: PencilIcon },
|
||||
{ label: "Edit 3D", href: "/edit-3d", icon: BoxIcon },
|
||||
{ label: "Edit Video", href: "/video-editor", icon: FilmIcon, badge: "BETA" },
|
||||
{
|
||||
label: "BG Change",
|
||||
href: "/background-change",
|
||||
icon: faWandMagicSparkles,
|
||||
icon: WandSparklesIcon,
|
||||
},
|
||||
{
|
||||
label: "Moodboard",
|
||||
href: "/moodboard",
|
||||
icon: faObjectGroup,
|
||||
icon: GroupIcon,
|
||||
badge: "BETA",
|
||||
},
|
||||
{
|
||||
label: "Frame Extract",
|
||||
href: "/frame-extractor",
|
||||
icon: faPhotoFilm,
|
||||
icon: ImagesIcon,
|
||||
badge: "NEW",
|
||||
},
|
||||
];
|
||||
@@ -107,26 +89,26 @@ function useStudioItems(): NavItem[] {
|
||||
const REFERRALS_ITEM: NavItem = {
|
||||
label: "Referrals",
|
||||
href: "/referrals",
|
||||
icon: faGift,
|
||||
icon: GiftIcon,
|
||||
};
|
||||
|
||||
const RESOURCES_ITEMS: NavItem[] = [
|
||||
{
|
||||
label: "Tutorials",
|
||||
href: `${MARKETING_SITE}/tutorials`,
|
||||
icon: faGraduationCap,
|
||||
icon: GraduationCapIcon,
|
||||
external: true,
|
||||
},
|
||||
{
|
||||
label: "News",
|
||||
href: `${MARKETING_SITE}/news`,
|
||||
icon: faNewspaper,
|
||||
icon: NewspaperIcon,
|
||||
external: true,
|
||||
},
|
||||
{
|
||||
label: "FAQ",
|
||||
href: `${MARKETING_SITE}/faq`,
|
||||
icon: faCircleQuestion,
|
||||
icon: CircleHelpIcon,
|
||||
external: true,
|
||||
},
|
||||
];
|
||||
@@ -135,7 +117,7 @@ const SUPPORT_ITEMS: NavItem[] = [
|
||||
{
|
||||
label: "Join Discord",
|
||||
href: SOCIAL_LINKS.DISCORD,
|
||||
icon: faDiscord,
|
||||
icon: DiscordIcon,
|
||||
external: true,
|
||||
},
|
||||
];
|
||||
@@ -166,7 +148,7 @@ function NavMenuItem({
|
||||
<>
|
||||
{/* Icon nudges up in scale on hover — a small tactile cue that the row is
|
||||
interactive, kept subtle to stay within the "restrained chrome" lane. */}
|
||||
<FontAwesomeIcon
|
||||
<DynamicIcon
|
||||
icon={item.icon}
|
||||
className="transition-transform duration-200 ease-out group-hover/menu-item:scale-110"
|
||||
/>
|
||||
@@ -332,7 +314,7 @@ export function AppSidebar() {
|
||||
<SidebarFooter className="group-data-[collapsible=icon]:hidden">
|
||||
<Button
|
||||
variant="primary"
|
||||
icon={faDownload}
|
||||
icon={DownloadIcon}
|
||||
onClick={() =>
|
||||
window.open(DOWNLOAD_URL, "_blank", "noopener,noreferrer")
|
||||
}
|
||||
|
||||
@@ -1,15 +1,7 @@
|
||||
import { useMemo, useState } from "react";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faBorderAll,
|
||||
faChevronRight,
|
||||
faFolder,
|
||||
faFolderOpen,
|
||||
faPlus,
|
||||
faStar,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { ChevronRightIcon, FolderIcon, FolderOpenIcon, Grid3x3Icon, PlusIcon, StarIcon } from "lucide-react";
|
||||
import { compareFolders } from "@storyteller/ui-gallery-modal";
|
||||
import { EASE_EMPHASIS } from "../../lib/motion";
|
||||
import {
|
||||
@@ -91,7 +83,7 @@ export function LibraryFoldersNav({
|
||||
aria-label="New folder"
|
||||
className="mr-1 flex h-5 w-5 items-center justify-center rounded text-sidebar-foreground/50 hover:bg-sidebar-accent hover:text-sidebar-foreground transition-colors group-data-[collapsible=icon]:hidden"
|
||||
>
|
||||
<FontAwesomeIcon icon={faPlus} className="text-xs" />
|
||||
<PlusIcon className="text-xs" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -100,7 +92,7 @@ export function LibraryFoldersNav({
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild isActive={onUnsorted} tooltip="Library">
|
||||
<Link to="/library" onClick={onNavClick}>
|
||||
<FontAwesomeIcon icon={faBorderAll} />
|
||||
<Grid3x3Icon />
|
||||
<span>Library</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
@@ -112,7 +104,7 @@ export function LibraryFoldersNav({
|
||||
tooltip="Unfoldered"
|
||||
>
|
||||
<Link to="/library/folderless" onClick={onNavClick}>
|
||||
<FontAwesomeIcon icon={faFolderOpen} />
|
||||
<FolderOpenIcon />
|
||||
<span>Unfoldered</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
@@ -124,7 +116,7 @@ export function LibraryFoldersNav({
|
||||
tooltip="Folders"
|
||||
>
|
||||
<Link to="/library/folders" onClick={onNavClick}>
|
||||
<FontAwesomeIcon icon={faFolder} />
|
||||
<FolderIcon />
|
||||
<span>Folders</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
@@ -135,10 +127,9 @@ export function LibraryFoldersNav({
|
||||
aria-label={expanded ? "Collapse folders" : "Expand folders"}
|
||||
className="absolute right-1 top-1/2 -translate-y-1/2 flex h-5 w-5 items-center justify-center rounded text-sidebar-foreground/50 hover:bg-sidebar-accent hover:text-sidebar-foreground transition-colors group-data-[collapsible=icon]:hidden"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faChevronRight}
|
||||
className={`text-[10px] transition-transform duration-200 ${expanded ? "rotate-90" : ""}`}
|
||||
/>
|
||||
<ChevronRightIcon
|
||||
|
||||
className={`text-[10px] transition-transform duration-200 ${expanded ? "rotate-90" : ""}`} />
|
||||
</button>
|
||||
)}
|
||||
</SidebarMenuItem>
|
||||
@@ -171,21 +162,19 @@ export function LibraryFoldersNav({
|
||||
}}
|
||||
className="pl-5 [&.folder-drag-over]:bg-primary/20 [&.folder-drag-over]:text-sidebar-foreground"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faFolder}
|
||||
<FolderIcon
|
||||
|
||||
className={folder.colorCode ? "" : "text-primary"}
|
||||
style={
|
||||
folder.colorCode
|
||||
? { color: folder.colorCode }
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
} />
|
||||
<span className="truncate">{folder.name}</span>
|
||||
{folder.hasStar && (
|
||||
<FontAwesomeIcon
|
||||
icon={faStar}
|
||||
className="ml-auto text-[10px] text-amber-400"
|
||||
/>
|
||||
<StarIcon
|
||||
|
||||
className="ml-auto text-[10px] text-amber-400" />
|
||||
)}
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
import { useMemo, useState } from "react";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faChevronRight,
|
||||
faEllipsis,
|
||||
faTag,
|
||||
faTags,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { ChevronRightIcon, EllipsisIcon, TagIcon, TagsIcon } from "lucide-react";
|
||||
import { SidebarMenuButton, SidebarMenuItem } from "../ui/sidebar";
|
||||
import {
|
||||
compareTagsByUseCount,
|
||||
@@ -60,7 +54,7 @@ export function LibraryTagsNav({
|
||||
tooltip="Tags"
|
||||
>
|
||||
<Link to="/library/tags" onClick={onNavClick}>
|
||||
<FontAwesomeIcon icon={faTags} />
|
||||
<TagsIcon />
|
||||
<span>Tags</span>
|
||||
</Link>
|
||||
</SidebarMenuButton>
|
||||
@@ -71,10 +65,9 @@ export function LibraryTagsNav({
|
||||
aria-label={expanded ? "Collapse tags" : "Expand tags"}
|
||||
className="absolute right-1 top-1/2 -translate-y-1/2 flex h-5 w-5 items-center justify-center rounded text-sidebar-foreground/50 hover:bg-sidebar-accent hover:text-sidebar-foreground transition-colors group-data-[collapsible=icon]:hidden"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faChevronRight}
|
||||
className={`text-[10px] transition-transform duration-200 ${expanded ? "rotate-90" : ""}`}
|
||||
/>
|
||||
<ChevronRightIcon
|
||||
|
||||
className={`text-[10px] transition-transform duration-200 ${expanded ? "rotate-90" : ""}`} />
|
||||
</button>
|
||||
)}
|
||||
</SidebarMenuItem>
|
||||
@@ -109,10 +102,9 @@ export function LibraryTagsNav({
|
||||
}}
|
||||
className="pl-5"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faTag}
|
||||
className="text-violet-400"
|
||||
/>
|
||||
<TagIcon
|
||||
|
||||
className="text-violet-400" />
|
||||
<span className="truncate">{tag.value}</span>
|
||||
<span className="ml-auto text-[10px] text-sidebar-foreground/40 transition-opacity group-hover/menu-item:opacity-0">
|
||||
{tag.useCount}
|
||||
@@ -133,7 +125,7 @@ export function LibraryTagsNav({
|
||||
aria-label={`Options for tag "${tag.value}"`}
|
||||
className="absolute right-1 top-1/2 -translate-y-1/2 flex h-5 w-5 items-center justify-center rounded text-sidebar-foreground/50 opacity-0 transition-opacity group-hover/menu-item:opacity-100 hover:bg-sidebar-accent hover:text-sidebar-foreground"
|
||||
>
|
||||
<FontAwesomeIcon icon={faEllipsis} className="text-xs" />
|
||||
<EllipsisIcon className="text-xs" />
|
||||
</button>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
@@ -145,7 +137,7 @@ export function LibraryTagsNav({
|
||||
onClick={onNavClick}
|
||||
className="pl-5 text-sidebar-foreground/60"
|
||||
>
|
||||
<FontAwesomeIcon icon={faEllipsis} />
|
||||
<EllipsisIcon />
|
||||
<span>See all tags</span>
|
||||
<span className="ml-auto text-[10px] text-sidebar-foreground/40">
|
||||
{tags.length}
|
||||
|
||||
@@ -1,21 +1,16 @@
|
||||
import { Link, useLocation } from "react-router-dom";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faHouse,
|
||||
faImage,
|
||||
faVideo,
|
||||
faGrid2,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import type { IconDefinition } from "@fortawesome/fontawesome-svg-core";
|
||||
import { HouseIcon, ImageIcon, LayoutGridIcon, VideoIcon } from "lucide-react";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import { DynamicIcon } from "@storyteller/icons";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
type NavItem = { label: string; href: string; icon: IconDefinition };
|
||||
type NavItem = { label: string; href: string; icon: LucideIcon };
|
||||
|
||||
const ITEMS: NavItem[] = [
|
||||
{ label: "Home", href: "/", icon: faHouse },
|
||||
{ label: "Image", href: "/create-image", icon: faImage },
|
||||
{ label: "Video", href: "/create-video", icon: faVideo },
|
||||
{ label: "Library", href: "/library", icon: faGrid2 },
|
||||
{ label: "Home", href: "/", icon: HouseIcon },
|
||||
{ label: "Image", href: "/create-image", icon: ImageIcon },
|
||||
{ label: "Video", href: "/create-video", icon: VideoIcon },
|
||||
{ label: "Library", href: "/library", icon: LayoutGridIcon },
|
||||
];
|
||||
|
||||
function isActive(pathname: string, href: string): boolean {
|
||||
@@ -43,7 +38,7 @@ export function MobileBottomNav() {
|
||||
: "text-base-fg/55 hover:text-base-fg/80",
|
||||
)}
|
||||
>
|
||||
<FontAwesomeIcon icon={item.icon} className="h-5 w-5" />
|
||||
<DynamicIcon icon={item.icon} className="h-5 w-5" />
|
||||
<span>{item.label}</span>
|
||||
</Link>
|
||||
);
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||
import { useState } from "react";
|
||||
import { create } from "zustand";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faCheck } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { CheckIcon } from "lucide-react";
|
||||
import { Modal } from "@storyteller/ui-modal";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { GoogleLoginButton } from "./auth";
|
||||
@@ -107,10 +106,9 @@ export function SignupCtaModal() {
|
||||
className="flex items-start gap-3 text-[14px] text-white/75"
|
||||
>
|
||||
<span className="mt-[2px] flex h-4 w-4 shrink-0 items-center justify-center rounded-full bg-primary/15">
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className="text-[9px] text-primary"
|
||||
/>
|
||||
<CheckIcon
|
||||
|
||||
className="text-[9px] text-primary" />
|
||||
</span>
|
||||
<span>{perk}</span>
|
||||
</li>
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faCheck,
|
||||
faExclamation,
|
||||
faXmark,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { CheckIcon, CircleAlertIcon, XIcon } from "lucide-react";
|
||||
import { DynamicIcon } from "@storyteller/icons";
|
||||
|
||||
// ── Types ──────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -99,8 +95,8 @@ export const ToastContainer = () => {
|
||||
toast.type === "success" ? "bg-green-500" : "bg-red-500"
|
||||
}`}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={toast.type === "success" ? faCheck : faExclamation}
|
||||
<DynamicIcon
|
||||
icon={toast.type === "success" ? CheckIcon : CircleAlertIcon}
|
||||
className="h-3 w-3 text-white"
|
||||
/>
|
||||
</div>
|
||||
@@ -111,7 +107,7 @@ export const ToastContainer = () => {
|
||||
onClick={() => dismiss(toast.id)}
|
||||
className="ml-1 text-white/40 transition-colors hover:text-white/80"
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} className="h-3 w-3" />
|
||||
<XIcon className="h-3 w-3" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -17,8 +17,7 @@
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faPencil, faSpinnerThird } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { LoaderCircleIcon, PencilIcon } from "lucide-react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { MediaFilesApi } from "@storyteller/api";
|
||||
import { usePageSceneStore } from "@storyteller/ui-pagescene";
|
||||
@@ -125,10 +124,9 @@ export function ActiveSceneTitle() {
|
||||
className="flex min-w-0 max-w-[280px] items-center rounded-lg border border-transparent px-3 py-1.5 text-sm font-semibold text-white transition-all hover:cursor-text hover:bg-white/[0.08]"
|
||||
>
|
||||
<span className="truncate">{title}</span>
|
||||
<FontAwesomeIcon
|
||||
icon={faPencil}
|
||||
className="ml-2 shrink-0 text-sm opacity-50"
|
||||
/>
|
||||
<PencilIcon
|
||||
|
||||
className="ml-2 shrink-0 text-sm opacity-50" />
|
||||
</button>
|
||||
) : (
|
||||
<div className="max-w-[280px] truncate rounded-lg border border-transparent px-3 py-1.5 text-sm font-semibold text-white/80">
|
||||
@@ -167,11 +165,10 @@ export function ActiveSceneTitle() {
|
||||
)}
|
||||
|
||||
{isSubmitting && (
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinnerThird}
|
||||
<LoaderCircleIcon
|
||||
|
||||
spin
|
||||
className="shrink-0 text-sm opacity-70"
|
||||
/>
|
||||
className="shrink-0 text-sm opacity-70" />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -14,16 +14,8 @@ import {
|
||||
Transition,
|
||||
} from "@headlessui/react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faCoins,
|
||||
faGem,
|
||||
faCog,
|
||||
faLifeRing,
|
||||
faGift,
|
||||
faImages,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { faDiscord } from "@fortawesome/free-brands-svg-icons";
|
||||
import { CoinsIcon, GemIcon, GiftIcon, ImagesIcon, LifeBuoyIcon, SettingsIcon } from "lucide-react";
|
||||
import { DiscordIcon } from "@storyteller/icons";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { PopoverMenu } from "@storyteller/ui-popover";
|
||||
import {
|
||||
@@ -109,7 +101,7 @@ export function TopBarActions({ className }: { className?: string }) {
|
||||
to="/pricing"
|
||||
className="hidden lg:flex h-8 items-center gap-1.5 px-3 rounded-lg text-sm font-medium text-white/85 hover:bg-white/[0.04] transition-colors"
|
||||
>
|
||||
<FontAwesomeIcon icon={faGem} className="text-[11px]" />
|
||||
<GemIcon className="text-[11px]" />
|
||||
Pricing
|
||||
</Link>
|
||||
|
||||
@@ -124,7 +116,7 @@ export function TopBarActions({ className }: { className?: string }) {
|
||||
{hasPaidPlan === false && (
|
||||
<Button
|
||||
variant="primary"
|
||||
icon={faGem}
|
||||
icon={GemIcon}
|
||||
onClick={() => navigate("/pricing")}
|
||||
className="h-8 px-3 text-sm font-semibold rounded-lg"
|
||||
>
|
||||
@@ -138,7 +130,7 @@ export function TopBarActions({ className }: { className?: string }) {
|
||||
to="/library"
|
||||
className="hidden sm:flex h-8 items-center gap-1.5 px-3 rounded-lg bg-white/[0.04] hover:bg-white/[0.08] border border-white/[0.08] shadow-none text-sm font-medium text-white/80 transition-colors"
|
||||
>
|
||||
<FontAwesomeIcon icon={faImages} className="text-[11px]" />
|
||||
<ImagesIcon className="text-[11px]" />
|
||||
My Library
|
||||
</Link>
|
||||
|
||||
@@ -178,10 +170,9 @@ export function TopBarActions({ className }: { className?: string }) {
|
||||
"flex w-full items-center gap-2 px-4 py-2 text-sm text-white/70 transition-colors",
|
||||
)}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faCog}
|
||||
className="text-[11px] text-white/50"
|
||||
/>
|
||||
<SettingsIcon
|
||||
|
||||
className="text-[11px] text-white/50" />
|
||||
Settings
|
||||
</button>
|
||||
)}
|
||||
@@ -195,10 +186,9 @@ export function TopBarActions({ className }: { className?: string }) {
|
||||
"flex w-full items-center gap-2 px-4 py-2 text-sm text-white/70 transition-colors",
|
||||
)}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faLifeRing}
|
||||
className="text-[11px] text-white/50"
|
||||
/>
|
||||
<LifeBuoyIcon
|
||||
|
||||
className="text-[11px] text-white/50" />
|
||||
Support
|
||||
</button>
|
||||
)}
|
||||
@@ -215,10 +205,9 @@ export function TopBarActions({ className }: { className?: string }) {
|
||||
"flex w-full items-center gap-2 px-4 py-2 text-sm text-white/70 transition-colors",
|
||||
)}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faGift}
|
||||
className="text-[11px] text-white/50"
|
||||
/>
|
||||
<GiftIcon
|
||||
|
||||
className="text-[11px] text-white/50" />
|
||||
Referrals
|
||||
</button>
|
||||
)}
|
||||
@@ -235,10 +224,9 @@ export function TopBarActions({ className }: { className?: string }) {
|
||||
"flex w-full items-center gap-2 px-4 py-2 text-sm text-white/70 transition-colors",
|
||||
)}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faDiscord}
|
||||
className="text-[11px] text-white/50"
|
||||
/>
|
||||
<DiscordIcon
|
||||
|
||||
className="text-[11px] text-white/50" />
|
||||
Join Discord
|
||||
</a>
|
||||
)}
|
||||
@@ -266,7 +254,7 @@ export function TopBarActions({ className }: { className?: string }) {
|
||||
to="/pricing"
|
||||
className="hidden lg:flex h-8 items-center gap-1.5 px-3 rounded-lg text-sm font-medium text-white/85 hover:bg-white/[0.04] transition-colors"
|
||||
>
|
||||
<FontAwesomeIcon icon={faGem} className="text-[11px]" />
|
||||
<GemIcon className="text-[11px]" />
|
||||
Pricing
|
||||
</Link>
|
||||
<Link
|
||||
@@ -310,7 +298,7 @@ function CreditsChip({
|
||||
position="bottom"
|
||||
align="end"
|
||||
triggerIcon={
|
||||
<FontAwesomeIcon icon={faCoins} className="text-primary text-[11px]" />
|
||||
<CoinsIcon className="text-primary text-[11px]" />
|
||||
}
|
||||
triggerLabel={
|
||||
<span className="whitespace-nowrap text-sm font-medium">
|
||||
@@ -337,7 +325,7 @@ function CreditsChip({
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-3xl font-semibold text-white tracking-tight">
|
||||
<FontAwesomeIcon icon={faCoins} className="text-xl text-primary" />
|
||||
<CoinsIcon className="text-xl text-primary" />
|
||||
{credits.toLocaleString()}
|
||||
</div>
|
||||
<div className="mt-3 flex gap-2">
|
||||
@@ -348,7 +336,7 @@ function CreditsChip({
|
||||
close();
|
||||
onUpgrade();
|
||||
}}
|
||||
icon={faGem}
|
||||
icon={GemIcon}
|
||||
>
|
||||
Upgrade
|
||||
</Button>
|
||||
|
||||
@@ -1,19 +1,7 @@
|
||||
import { Tooltip } from "@storyteller/ui-tooltip";
|
||||
import { PopoverMenu } from "@storyteller/ui-popover";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faListCheck,
|
||||
faSpinnerThird,
|
||||
faXmark,
|
||||
faTrashAlt,
|
||||
faTasks,
|
||||
faBroom,
|
||||
faBomb,
|
||||
faCircleExclamation,
|
||||
faCopy,
|
||||
faCheck,
|
||||
faArrowRotateRight,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { BombIcon, BrushIcon, CheckIcon, CircleAlertIcon, CopyIcon, ListChecksIcon, LoaderCircleIcon, RotateCwIcon, Trash2Icon, XIcon } from "lucide-react";
|
||||
import { DynamicIcon } from "@storyteller/icons";
|
||||
import { Modal } from "@storyteller/ui-modal";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { JobsApi, JobStatus } from "@storyteller/api";
|
||||
@@ -201,8 +189,8 @@ const CopyPromptButton = ({ prompt }: { prompt: string }) => {
|
||||
setTimeout(() => setCopied(false), 3000);
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={copied ? faCheck : faCopy}
|
||||
<DynamicIcon
|
||||
icon={copied ? CheckIcon : CopyIcon}
|
||||
className={copied ? "text-green-400" : ""}
|
||||
/>
|
||||
</button>
|
||||
@@ -237,8 +225,8 @@ const RecreateTaskButton = ({
|
||||
disabled={isRecreating}
|
||||
onClick={handleRecreate}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={isRecreating ? faSpinnerThird : faArrowRotateRight}
|
||||
<DynamicIcon
|
||||
icon={isRecreating ? LoaderCircleIcon : RotateCwIcon}
|
||||
className={isRecreating ? "animate-spin" : ""}
|
||||
/>
|
||||
</button>
|
||||
@@ -270,11 +258,10 @@ const InProgressCard = ({
|
||||
<div className="rounded-md p-2 transition-colors hover:bg-ui-controls/40">
|
||||
<div className="flex items-center gap-2.5">
|
||||
<div className="flex h-[72px] w-[72px] shrink-0 items-center justify-center overflow-hidden rounded bg-ui-controls">
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinnerThird}
|
||||
<LoaderCircleIcon
|
||||
|
||||
className="animate-spin text-base-fg/60"
|
||||
size="lg"
|
||||
/>
|
||||
size="lg" />
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
@@ -327,7 +314,7 @@ const InProgressCard = ({
|
||||
onDismiss();
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} />
|
||||
<XIcon />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -399,7 +386,7 @@ const CompletedCard = ({
|
||||
onDismiss();
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} />
|
||||
<XIcon />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -419,11 +406,10 @@ const FailedCard = ({
|
||||
<div className="rounded-md p-2 transition-colors hover:bg-ui-controls/40">
|
||||
<div className="flex items-center gap-2.5">
|
||||
<div className="flex h-[72px] w-[72px] shrink-0 items-center justify-center overflow-hidden rounded bg-red-500/10">
|
||||
<FontAwesomeIcon
|
||||
icon={faCircleExclamation}
|
||||
<CircleAlertIcon
|
||||
|
||||
className="text-red-400"
|
||||
size="lg"
|
||||
/>
|
||||
size="lg" />
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
@@ -492,7 +478,7 @@ const FailedCard = ({
|
||||
onDismiss();
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} />
|
||||
<XIcon />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -761,7 +747,7 @@ export const TaskQueue = () => {
|
||||
title: "",
|
||||
message: null,
|
||||
primaryActionText: "",
|
||||
primaryActionIcon: faTrashAlt,
|
||||
primaryActionIcon: Trash2Icon,
|
||||
primaryActionBtnClassName: "",
|
||||
onConfirm: async () => {},
|
||||
});
|
||||
@@ -778,7 +764,7 @@ export const TaskQueue = () => {
|
||||
</span>
|
||||
),
|
||||
primaryActionText: "Clear completed",
|
||||
primaryActionIcon: faBroom,
|
||||
primaryActionIcon: BrushIcon,
|
||||
primaryActionBtnClassName:
|
||||
"bg-green-500/10 hover:bg-green-500/20 text-green-500",
|
||||
onConfirm: async () => {
|
||||
@@ -797,7 +783,7 @@ export const TaskQueue = () => {
|
||||
</span>
|
||||
),
|
||||
primaryActionText: "Clear stale",
|
||||
primaryActionIcon: faTrashAlt,
|
||||
primaryActionIcon: Trash2Icon,
|
||||
primaryActionBtnClassName:
|
||||
"bg-orange-500/10 hover:bg-orange-500/20 text-orange-500",
|
||||
onConfirm: async () => {
|
||||
@@ -816,7 +802,7 @@ export const TaskQueue = () => {
|
||||
</span>
|
||||
),
|
||||
primaryActionText: "Clear failed",
|
||||
primaryActionIcon: faTrashAlt,
|
||||
primaryActionIcon: Trash2Icon,
|
||||
primaryActionBtnClassName:
|
||||
"bg-red-500/10 hover:bg-red-500/20 text-red-500",
|
||||
onConfirm: async () => {
|
||||
@@ -836,7 +822,7 @@ export const TaskQueue = () => {
|
||||
</span>
|
||||
),
|
||||
primaryActionText: "Nuke all",
|
||||
primaryActionIcon: faBomb,
|
||||
primaryActionIcon: BombIcon,
|
||||
primaryActionBtnClassName:
|
||||
"bg-red-500/10 hover:bg-red-500/20 text-red-500",
|
||||
onConfirm: async () => {
|
||||
@@ -1049,7 +1035,7 @@ export const TaskQueue = () => {
|
||||
{hasNothing ? (
|
||||
<div className="flex w-full flex-col items-center justify-center p-5 text-base-fg/60">
|
||||
<div className="flex items-center gap-2.5 text-sm opacity-60">
|
||||
<FontAwesomeIcon icon={faTasks} /> No tasks yet
|
||||
<ListChecksIcon /> No tasks yet
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
@@ -1078,7 +1064,7 @@ export const TaskQueue = () => {
|
||||
className="text-xs tracking-wide text-red-400/70 transition-colors hover:text-red-300"
|
||||
onClick={() => handleClearFailed()}
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} className="mr-1" />
|
||||
<XIcon className="mr-1" />
|
||||
Clear failed
|
||||
</button>
|
||||
</div>
|
||||
@@ -1101,7 +1087,7 @@ export const TaskQueue = () => {
|
||||
className="text-xs tracking-wide text-base-fg/50 transition-colors hover:text-base-fg/100"
|
||||
onClick={() => handleClearCompleted()}
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} className="mr-1" />
|
||||
<XIcon className="mr-1" />
|
||||
Clear completed
|
||||
</button>
|
||||
</div>
|
||||
@@ -1148,12 +1134,11 @@ export const TaskQueue = () => {
|
||||
align="end"
|
||||
triggerIcon={
|
||||
inProgressCount > 0 ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinnerThird}
|
||||
className="animate-spin text-[11px]"
|
||||
/>
|
||||
<LoaderCircleIcon
|
||||
|
||||
className="animate-spin text-[11px]" />
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faListCheck} className="text-[11px]" />
|
||||
<ListChecksIcon className="text-[11px]" />
|
||||
)
|
||||
}
|
||||
onOpenChange={handleOpenChange}
|
||||
@@ -1198,28 +1183,28 @@ export const TaskQueue = () => {
|
||||
className="rounded-full flex h-9 items-center justify-center bg-green-500/10 px-3 text-green-500 hover:bg-green-500/20"
|
||||
onClick={() => handleClearCompleted()}
|
||||
>
|
||||
<FontAwesomeIcon icon={faBroom} className="mr-1.5" />
|
||||
<BrushIcon className="mr-1.5" />
|
||||
Clear completed
|
||||
</Button>
|
||||
<Button
|
||||
className="rounded-full flex h-9 items-center justify-center bg-orange-500/10 px-3 text-orange-500 hover:bg-orange-500/20"
|
||||
onClick={() => handleClearStale()}
|
||||
>
|
||||
<FontAwesomeIcon icon={faTrashAlt} className="mr-1.5" />
|
||||
<Trash2Icon className="mr-1.5" />
|
||||
Clear stale
|
||||
</Button>
|
||||
<Button
|
||||
className="rounded-full flex h-9 items-center justify-center bg-red-500/10 px-3 text-red-400 hover:bg-red-500/20"
|
||||
onClick={() => handleClearFailed()}
|
||||
>
|
||||
<FontAwesomeIcon icon={faTrashAlt} className="mr-1.5" />
|
||||
<Trash2Icon className="mr-1.5" />
|
||||
Clear failed
|
||||
</Button>
|
||||
<Button
|
||||
className="rounded-full flex h-9 items-center justify-center bg-red-500/10 px-3 text-red-500 hover:bg-red-500/20"
|
||||
onClick={() => handleRemoveAll()}
|
||||
>
|
||||
<FontAwesomeIcon icon={faBomb} className="mr-1.5" />
|
||||
<BombIcon className="mr-1.5" />
|
||||
Remove all
|
||||
</Button>
|
||||
<div className="mr-2 h-4 w-[1px] bg-base-fg/10" />
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { faXmarkCircle } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { faDiscord } from "@fortawesome/free-brands-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { CircleXIcon } from "lucide-react";
|
||||
import { DiscordIcon } from "@storyteller/icons";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { Link } from "react-router-dom";
|
||||
import { SOCIAL_LINKS } from "../../config/links";
|
||||
@@ -26,10 +25,9 @@ const CheckoutCancel = () => {
|
||||
{/* Cancel Icon */}
|
||||
<div className="mb-6">
|
||||
<div className="w-20 h-20 mx-auto rounded-full bg-white/10 flex items-center justify-center">
|
||||
<FontAwesomeIcon
|
||||
icon={faXmarkCircle}
|
||||
className="text-5xl text-white/50"
|
||||
/>
|
||||
<CircleXIcon
|
||||
|
||||
className="text-5xl text-white/50" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -77,7 +75,7 @@ const CheckoutCancel = () => {
|
||||
target="_blank"
|
||||
className="rounded-full bg-white text-black hover:bg-white/80 px-4 py-2 text-sm font-bold rounded-xl justify-center border-transparent"
|
||||
>
|
||||
<FontAwesomeIcon icon={faDiscord} />
|
||||
<DiscordIcon />
|
||||
Join Discord
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -1,16 +1,5 @@
|
||||
import {
|
||||
faCheckCircle,
|
||||
faDownload,
|
||||
faDesktop,
|
||||
faRocket,
|
||||
faImage,
|
||||
faVideo,
|
||||
faWandMagicSparkles,
|
||||
faCube,
|
||||
faArrowRight,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { faDiscord } from "@fortawesome/free-brands-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { ArrowRightIcon, BoxIcon, CircleCheckIcon, DownloadIcon, ImageIcon, MonitorIcon, RocketIcon, VideoIcon, WandSparklesIcon } from "lucide-react";
|
||||
import { DynamicIcon, DiscordIcon } from "@storyteller/icons";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { Link, useSearchParams } from "react-router-dom";
|
||||
import { isMobile, isMacOs } from "react-device-detect";
|
||||
@@ -96,10 +85,9 @@ const CheckoutSuccess = () => {
|
||||
{/* Success Icon */}
|
||||
<div className="mb-6">
|
||||
<div className="w-20 h-20 mx-auto rounded-full bg-green-500/20 flex items-center justify-center">
|
||||
<FontAwesomeIcon
|
||||
icon={faCheckCircle}
|
||||
className="text-5xl text-green-400"
|
||||
/>
|
||||
<CircleCheckIcon
|
||||
|
||||
className="text-5xl text-green-400" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -117,10 +105,9 @@ const CheckoutSuccess = () => {
|
||||
<div className="bg-[#252529] rounded-2xl p-6 mb-8 text-left">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<div className="w-8 h-8 rounded-full bg-primary/20 flex items-center justify-center">
|
||||
<FontAwesomeIcon
|
||||
icon={faWandMagicSparkles}
|
||||
className="text-primary text-sm"
|
||||
/>
|
||||
<WandSparklesIcon
|
||||
|
||||
className="text-primary text-sm" />
|
||||
</div>
|
||||
<h2 className="text-lg font-medium text-white">
|
||||
Start creating right here
|
||||
@@ -131,31 +118,30 @@ const CheckoutSuccess = () => {
|
||||
</p>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||||
{[
|
||||
{ to: "/create-image", icon: faImage, label: "Image" },
|
||||
{ to: "/create-video", icon: faVideo, label: "Video" },
|
||||
{ to: "/create-image", icon: ImageIcon, label: "Image" },
|
||||
{ to: "/create-video", icon: VideoIcon, label: "Video" },
|
||||
{
|
||||
to: "/background-change",
|
||||
icon: faWandMagicSparkles,
|
||||
icon: WandSparklesIcon,
|
||||
label: "BG Change",
|
||||
},
|
||||
{ to: "/edit-3d", icon: faCube, label: "Edit 3D" },
|
||||
{ to: "/edit-3d", icon: BoxIcon, label: "Edit 3D" },
|
||||
].map((item) => (
|
||||
<Link
|
||||
key={item.to}
|
||||
to={item.to}
|
||||
className="group flex items-center gap-3 bg-[#1A1A1E] hover:bg-[#222227] border border-white/[0.06] hover:border-white/[0.12] rounded-xl px-4 py-3 transition-all"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
<DynamicIcon
|
||||
icon={item.icon}
|
||||
className="text-primary text-base shrink-0"
|
||||
/>
|
||||
<span className="text-white/90 font-medium flex-1">
|
||||
{item.label}
|
||||
</span>
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowRight}
|
||||
className="text-white/30 text-[11px] transition-transform group-hover:translate-x-0.5"
|
||||
/>
|
||||
<ArrowRightIcon
|
||||
|
||||
className="text-white/30 text-[11px] transition-transform group-hover:translate-x-0.5" />
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
@@ -165,10 +151,9 @@ const CheckoutSuccess = () => {
|
||||
<div className="bg-[#252529] rounded-2xl p-6 mb-8 text-left">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="w-8 h-8 rounded-full bg-primary/20 flex items-center justify-center">
|
||||
<FontAwesomeIcon
|
||||
icon={faRocket}
|
||||
className="text-primary text-sm"
|
||||
/>
|
||||
<RocketIcon
|
||||
|
||||
className="text-primary text-sm" />
|
||||
</div>
|
||||
<h2 className="text-lg font-medium text-white">Next Steps</h2>
|
||||
</div>
|
||||
@@ -198,14 +183,14 @@ const CheckoutSuccess = () => {
|
||||
href={downloadUrl}
|
||||
className="rounded-full bg-primary hover:bg-primary-600 px-8 py-3 text-sm font-bold rounded-xl justify-center"
|
||||
>
|
||||
<FontAwesomeIcon icon={faDownload} className="mr-2" />
|
||||
<DownloadIcon className="mr-2" />
|
||||
Download ArtCraft
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="bg-[#431407] border border-orange-900/50 rounded-2xl p-6 text-orange-200 text-sm leading-relaxed">
|
||||
<div className="flex items-center justify-center mb-3 text-orange-400">
|
||||
<FontAwesomeIcon icon={faDesktop} className="text-2xl" />
|
||||
<MonitorIcon className="text-2xl" />
|
||||
</div>
|
||||
ArtCraft is a desktop application. <br />
|
||||
Please head to your computer to download and start creating.
|
||||
@@ -245,7 +230,7 @@ const CheckoutSuccess = () => {
|
||||
target="_blank"
|
||||
className="rounded-full bg-white text-black hover:bg-white/80 px-4 py-2 text-sm font-bold rounded-xl justify-center border-transparent"
|
||||
>
|
||||
<FontAwesomeIcon icon={faDiscord} />
|
||||
<DiscordIcon />
|
||||
Join Discord
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -65,12 +65,7 @@ import {
|
||||
import { toast } from "../../components/toast/toast";
|
||||
import { useSignupCta } from "../../components/signup-cta-modal";
|
||||
import { useInsufficientCredits } from "../../components/insufficient-credits-modal";
|
||||
import {
|
||||
faMicrophoneLines,
|
||||
faMicrophoneSlash,
|
||||
faRepeat,
|
||||
faSparkles,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { MicIcon, MicOffIcon, RepeatIcon, SparklesIcon } from "lucide-react";
|
||||
|
||||
// ── Constants ────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -492,8 +487,8 @@ export default function CreateAudio() {
|
||||
>
|
||||
<ToggleButton
|
||||
isActive={ui.isInstrumental}
|
||||
icon={faMicrophoneSlash}
|
||||
activeIcon={faMicrophoneSlash}
|
||||
icon={MicOffIcon}
|
||||
activeIcon={MicOffIcon}
|
||||
label="Instrumental"
|
||||
onClick={() => setUi({ isInstrumental: !ui.isInstrumental })}
|
||||
/>
|
||||
@@ -508,8 +503,8 @@ export default function CreateAudio() {
|
||||
>
|
||||
<ToggleButton
|
||||
isActive={ui.keepLyrics}
|
||||
icon={faMicrophoneLines}
|
||||
activeIcon={faMicrophoneLines}
|
||||
icon={MicIcon}
|
||||
activeIcon={MicIcon}
|
||||
label="Keep lyrics"
|
||||
onClick={() => setUi({ keepLyrics: !ui.keepLyrics })}
|
||||
/>
|
||||
@@ -524,8 +519,8 @@ export default function CreateAudio() {
|
||||
>
|
||||
<ToggleButton
|
||||
isActive={ui.isLoopable}
|
||||
icon={faRepeat}
|
||||
activeIcon={faRepeat}
|
||||
icon={RepeatIcon}
|
||||
activeIcon={RepeatIcon}
|
||||
label="Loop"
|
||||
onClick={() => setUi({ isLoopable: !ui.isLoopable })}
|
||||
/>
|
||||
@@ -778,7 +773,7 @@ export default function CreateAudio() {
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={openSignupCta}
|
||||
icon={faSparkles}
|
||||
icon={SparklesIcon}
|
||||
className="h-12 px-6 text-base font-semibold rounded-full"
|
||||
>
|
||||
Sign up to create
|
||||
|
||||
+2
-3
@@ -1,5 +1,4 @@
|
||||
import { faCopy } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { CopyIcon } from "lucide-react";
|
||||
import { PopoverMenu, PopoverItem } from "@storyteller/ui-popover";
|
||||
import { Tooltip } from "@storyteller/ui-tooltip";
|
||||
import { PROMPT_TOOLBAR_ICON_BUTTON_CLASSES } from "@storyteller/ui-promptbox";
|
||||
@@ -79,7 +78,7 @@ export const GenerationCountPicker = ({
|
||||
onSelect={onSelect}
|
||||
mode="toggle"
|
||||
panelTitle={panelTitle}
|
||||
triggerIcon={<FontAwesomeIcon icon={faCopy} className="h-4 w-4" />}
|
||||
triggerIcon={<CopyIcon className="h-4 w-4" />}
|
||||
buttonClassName={twMerge(PROMPT_TOOLBAR_ICON_BUTTON_CLASSES, "w-11")}
|
||||
/>
|
||||
</Tooltip>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faGem } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { GemIcon } from "lucide-react";
|
||||
import { PopoverItem, PopoverMenu } from "@storyteller/ui-popover";
|
||||
import { Tooltip } from "@storyteller/ui-tooltip";
|
||||
|
||||
@@ -63,7 +62,7 @@ export const QualityPicker = ({
|
||||
mode="toggle"
|
||||
panelTitle="Quality"
|
||||
triggerIcon={
|
||||
<FontAwesomeIcon icon={faGem} className="h-3.5 w-3.5" />
|
||||
<GemIcon className="h-3.5 w-3.5" />
|
||||
}
|
||||
/>
|
||||
</Tooltip>
|
||||
|
||||
+17
-21
@@ -1,10 +1,6 @@
|
||||
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
|
||||
import { faSquare } from "@fortawesome/pro-regular-svg-icons";
|
||||
import {
|
||||
faHighDefinition,
|
||||
faStandardDefinition,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { SquareIcon } from "lucide-react";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import { DynamicIcon, HighDefinitionIcon, StandardDefinitionIcon } from "@storyteller/icons";
|
||||
import { PopoverItem, PopoverMenu } from "@storyteller/ui-popover";
|
||||
import { Tooltip } from "@storyteller/ui-tooltip";
|
||||
|
||||
@@ -15,15 +11,15 @@ interface ResolutionPickerProps {
|
||||
handleResolutionSelect: (selected: string) => void;
|
||||
}
|
||||
|
||||
const RESOLUTION_ICONS: Record<string, IconDefinition> = {
|
||||
half_k: faStandardDefinition,
|
||||
four_eighty_p: faStandardDefinition,
|
||||
seven_twenty_p: faStandardDefinition,
|
||||
one_k: faStandardDefinition,
|
||||
ten_eighty_p: faHighDefinition,
|
||||
two_k: faHighDefinition,
|
||||
three_k: faHighDefinition,
|
||||
four_k: faHighDefinition,
|
||||
const RESOLUTION_ICONS: Record<string, LucideIcon> = {
|
||||
half_k: StandardDefinitionIcon,
|
||||
four_eighty_p: StandardDefinitionIcon,
|
||||
seven_twenty_p: StandardDefinitionIcon,
|
||||
one_k: StandardDefinitionIcon,
|
||||
ten_eighty_p: HighDefinitionIcon,
|
||||
two_k: HighDefinitionIcon,
|
||||
three_k: HighDefinitionIcon,
|
||||
four_k: HighDefinitionIcon,
|
||||
};
|
||||
|
||||
const RESOLUTION_LABELS: Record<string, string> = {
|
||||
@@ -54,8 +50,8 @@ export function buildResolutionItems(
|
||||
label: RESOLUTION_LABELS[resolution] ?? resolution,
|
||||
selected: selected === resolution,
|
||||
icon: (
|
||||
<FontAwesomeIcon
|
||||
icon={RESOLUTION_ICONS[resolution] ?? faStandardDefinition}
|
||||
<DynamicIcon
|
||||
icon={RESOLUTION_ICONS[resolution] ?? StandardDefinitionIcon}
|
||||
className="h-4 w-4"
|
||||
/>
|
||||
),
|
||||
@@ -96,11 +92,11 @@ export const ResolutionPicker = ({
|
||||
panelTitle="Resolution"
|
||||
showIconsInList
|
||||
triggerIcon={
|
||||
<FontAwesomeIcon
|
||||
<DynamicIcon
|
||||
icon={
|
||||
activeResolution
|
||||
? (RESOLUTION_ICONS[activeResolution] ?? faSquare)
|
||||
: faSquare
|
||||
? (RESOLUTION_ICONS[activeResolution] ?? SquareIcon)
|
||||
: SquareIcon
|
||||
}
|
||||
className="h-4 w-4"
|
||||
/>
|
||||
|
||||
@@ -75,7 +75,7 @@ import {
|
||||
import { toast } from "../../components/toast/toast";
|
||||
import { useSignupCta } from "../../components/signup-cta-modal";
|
||||
import { useInsufficientCredits } from "../../components/insufficient-credits-modal";
|
||||
import { faSparkles } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { SparklesIcon } from "lucide-react";
|
||||
|
||||
// ── Constants ────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -679,7 +679,7 @@ export default function CreateImage() {
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={openSignupCta}
|
||||
icon={faSparkles}
|
||||
icon={SparklesIcon}
|
||||
className="h-12 px-6 text-base font-semibold rounded-full"
|
||||
>
|
||||
Sign up to create
|
||||
|
||||
+14
-21
@@ -1,11 +1,6 @@
|
||||
import { useRef, useState } from "react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faSpinnerThird,
|
||||
faXmark,
|
||||
faCube,
|
||||
faImage,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { BoxIcon, ImageIcon, LoaderCircleIcon, XIcon } from "lucide-react";
|
||||
import { DynamicIcon } from "@storyteller/icons";
|
||||
import { AddButton, type RefImage } from "../../../components/prompt-box";
|
||||
import { MESH_FILE_ACCEPT, uploadMeshFile, uploadViewImage } from "./mesh-upload";
|
||||
|
||||
@@ -53,7 +48,7 @@ export function MeshInputsRow({
|
||||
const subtitle = showMultiView
|
||||
? "Reference angles (optional)"
|
||||
: "Mesh file to process";
|
||||
const titleIcon = showMultiView ? faImage : faCube;
|
||||
const titleIcon = showMultiView ? ImageIcon : BoxIcon;
|
||||
|
||||
return (
|
||||
// Title on the left, upload slots right-aligned + top-aligned (matches
|
||||
@@ -61,7 +56,7 @@ export function MeshInputsRow({
|
||||
<div className="glass flex items-start gap-3 rounded-2xl px-3 py-2">
|
||||
<div className="flex grow flex-col gap-1 min-w-32">
|
||||
<div className="flex items-center gap-2 text-white/90">
|
||||
<FontAwesomeIcon icon={titleIcon} className="h-3.5 w-3.5" />
|
||||
<DynamicIcon icon={titleIcon} className="h-3.5 w-3.5" />
|
||||
<span className="text-sm font-medium">{title}</span>
|
||||
</div>
|
||||
<span className="text-[13px] text-white/60">{subtitle}</span>
|
||||
@@ -186,16 +181,15 @@ function ImageSlot({
|
||||
onClick={() => onChange(undefined)}
|
||||
className="absolute right-[2px] top-[2px] flex h-5 w-5 items-center justify-center rounded-full bg-black/50 text-white backdrop-blur-md transition-colors hover:bg-black"
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} className="h-2.5 w-2.5" />
|
||||
<XIcon className="h-2.5 w-2.5" />
|
||||
</button>
|
||||
</div>
|
||||
) : uploading ? (
|
||||
<div className={SLOT_CLASS}>
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinnerThird}
|
||||
<LoaderCircleIcon
|
||||
|
||||
spin
|
||||
className="h-5 w-5 text-white"
|
||||
/>
|
||||
className="h-5 w-5 text-white" />
|
||||
</div>
|
||||
) : (
|
||||
<AddButton
|
||||
@@ -238,28 +232,27 @@ function MeshFileSlot({
|
||||
/>
|
||||
{mesh ? (
|
||||
<div className="group relative flex aspect-square w-14 items-center justify-center overflow-hidden rounded-lg border-2 border-white/30 bg-white/10">
|
||||
<FontAwesomeIcon icon={faCube} className="h-5 w-5 text-white/80" />
|
||||
<BoxIcon className="h-5 w-5 text-white/80" />
|
||||
<button
|
||||
onClick={() => onChange(undefined)}
|
||||
className="absolute right-[2px] top-[2px] flex h-5 w-5 items-center justify-center rounded-full bg-black/50 text-white backdrop-blur-md transition-colors hover:bg-black"
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} className="h-2.5 w-2.5" />
|
||||
<XIcon className="h-2.5 w-2.5" />
|
||||
</button>
|
||||
</div>
|
||||
) : uploading ? (
|
||||
<div className={SLOT_CLASS}>
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinnerThird}
|
||||
<LoaderCircleIcon
|
||||
|
||||
spin
|
||||
className="h-5 w-5 text-white"
|
||||
/>
|
||||
className="h-5 w-5 text-white" />
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
onClick={() => inputRef.current?.click()}
|
||||
className={SLOT_CLASS}
|
||||
>
|
||||
<FontAwesomeIcon icon={faCube} className="text-xl text-white/80" />
|
||||
<BoxIcon className="text-xl text-white/80" />
|
||||
</button>
|
||||
)}
|
||||
</SlotShell>
|
||||
|
||||
+10
-16
@@ -1,12 +1,6 @@
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faCube,
|
||||
faObjectGroup,
|
||||
faWandMagicSparkles,
|
||||
faImage,
|
||||
faGem,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import type { IconDefinition } from "@fortawesome/fontawesome-svg-core";
|
||||
import { BoxIcon, GemIcon, GroupIcon, ImageIcon, WandSparklesIcon } from "lucide-react";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import { DynamicIcon } from "@storyteller/icons";
|
||||
import { PopoverItem, PopoverMenu } from "@storyteller/ui-popover";
|
||||
import { Tooltip } from "@storyteller/ui-tooltip";
|
||||
|
||||
@@ -41,7 +35,7 @@ interface EnumPickerProps {
|
||||
labels: Record<string, string>;
|
||||
tooltip: string;
|
||||
panelTitle: string;
|
||||
icon: IconDefinition;
|
||||
icon: LucideIcon;
|
||||
}
|
||||
|
||||
function EnumPicker({
|
||||
@@ -72,7 +66,7 @@ function EnumPicker({
|
||||
}}
|
||||
mode="toggle"
|
||||
panelTitle={panelTitle}
|
||||
triggerIcon={<FontAwesomeIcon icon={icon} className="h-3.5 w-3.5" />}
|
||||
triggerIcon={<DynamicIcon icon={icon} className="h-3.5 w-3.5" />}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
@@ -90,7 +84,7 @@ export const MeshOutputTypePicker = (props: {
|
||||
labels={MESH_OUTPUT_TYPE_LABELS}
|
||||
tooltip="Output type"
|
||||
panelTitle="Output type"
|
||||
icon={faCube}
|
||||
icon={BoxIcon}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -104,7 +98,7 @@ export const PolygonTypePicker = (props: {
|
||||
labels={POLYGON_TYPE_LABELS}
|
||||
tooltip="Polygon type"
|
||||
panelTitle="Polygon type"
|
||||
icon={faObjectGroup}
|
||||
icon={GroupIcon}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -118,7 +112,7 @@ export const GeometryQualityPicker = (props: {
|
||||
labels={MESH_QUALITY_LABELS}
|
||||
tooltip="Geometry quality"
|
||||
panelTitle="Geometry quality"
|
||||
icon={faWandMagicSparkles}
|
||||
icon={WandSparklesIcon}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -132,7 +126,7 @@ export const TextureQualityPicker = (props: {
|
||||
labels={MESH_QUALITY_LABELS}
|
||||
tooltip="Texture quality"
|
||||
panelTitle="Texture quality"
|
||||
icon={faImage}
|
||||
icon={ImageIcon}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -168,7 +162,7 @@ export const FaceCountPicker = ({
|
||||
}}
|
||||
mode="toggle"
|
||||
panelTitle="Face count"
|
||||
triggerIcon={<FontAwesomeIcon icon={faGem} className="h-3.5 w-3.5" />}
|
||||
triggerIcon={<GemIcon className="h-3.5 w-3.5" />}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
@@ -4,7 +4,7 @@ import { PopoverMenu, type PopoverItem } from "@storyteller/ui-popover";
|
||||
import { Tooltip } from "@storyteller/ui-tooltip";
|
||||
import { Button, ToggleButton } from "@storyteller/ui-button";
|
||||
import { GalleryModal, type GalleryItem } from "@storyteller/ui-gallery-modal";
|
||||
import { faSparkles, faGem, faImage } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { GemIcon, ImageIcon, SparklesIcon } from "lucide-react";
|
||||
import {
|
||||
PromptBox,
|
||||
ImagePromptRow,
|
||||
@@ -555,8 +555,8 @@ export default function CreateObject() {
|
||||
<Tooltip content="PBR materials" position="top" closeOnClick>
|
||||
<ToggleButton
|
||||
isActive={enablePbr}
|
||||
icon={faGem}
|
||||
activeIcon={faGem}
|
||||
icon={GemIcon}
|
||||
activeIcon={GemIcon}
|
||||
label={enablePbr ? "PBR" : "PBR off"}
|
||||
onClick={() => setUi({ enablePbr: !enablePbr })}
|
||||
/>
|
||||
@@ -566,8 +566,8 @@ export default function CreateObject() {
|
||||
<Tooltip content="Generate textures" position="top" closeOnClick>
|
||||
<ToggleButton
|
||||
isActive={enableTexture}
|
||||
icon={faImage}
|
||||
activeIcon={faImage}
|
||||
icon={ImageIcon}
|
||||
activeIcon={ImageIcon}
|
||||
label={enableTexture ? "Texture" : "No texture"}
|
||||
onClick={() => setUi({ enableTexture: !enableTexture })}
|
||||
/>
|
||||
@@ -624,7 +624,7 @@ export default function CreateObject() {
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={openSignupCta}
|
||||
icon={faSparkles}
|
||||
icon={SparklesIcon}
|
||||
className="h-12 px-6 text-base font-semibold rounded-full"
|
||||
>
|
||||
Sign up to create
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faSpinnerThird, faSparkles } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { LoaderCircleIcon, SparklesIcon } from "lucide-react";
|
||||
import {
|
||||
PromptBoxVFX,
|
||||
VFXResultCard,
|
||||
@@ -249,10 +248,9 @@ export default function CreateVFX() {
|
||||
if (!authChecked) {
|
||||
return (
|
||||
<div className="flex h-screen items-center justify-center bg-[#101014]">
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinnerThird}
|
||||
className="animate-spin text-4xl text-primary/80"
|
||||
/>
|
||||
<LoaderCircleIcon
|
||||
|
||||
className="animate-spin text-4xl text-primary/80" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -341,7 +339,7 @@ interface EmptyStateProps {
|
||||
const EmptyState = ({ title, subtitle }: EmptyStateProps) => (
|
||||
<div className="flex max-w-md flex-col items-center gap-4 text-center">
|
||||
<div className="flex h-12 w-12 items-center justify-center rounded-2xl bg-white/5 ring-1 ring-white/10">
|
||||
<FontAwesomeIcon icon={faSparkles} className="text-2xl text-white/40" />
|
||||
<SparklesIcon className="text-2xl text-white/40" />
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold text-white">{title}</h3>
|
||||
<p className="text-sm text-white/60 max-w-xs">{subtitle}</p>
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { flushSync } from "react-dom";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faCircleInfo,
|
||||
faClock,
|
||||
faSparkles,
|
||||
faWaveformLines,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { AudioLinesIcon, ClockIcon, InfoIcon, SparklesIcon } from "lucide-react";
|
||||
import { DynamicIcon } from "@storyteller/icons";
|
||||
import { CharactersApi, FilterMediaClasses } from "@storyteller/api";
|
||||
import type { OmniGenVideoModelInfo } from "@storyteller/api";
|
||||
import { Button, ToggleButton } from "@storyteller/ui-button";
|
||||
@@ -1489,10 +1484,9 @@ export default function CreateVideo() {
|
||||
banner={
|
||||
requiresImageInput ? (
|
||||
<div className="flex items-start gap-2.5 rounded-lg border border-amber-500/40 bg-amber-900/80 px-3.5 py-2.5 text-xs text-amber-100">
|
||||
<FontAwesomeIcon
|
||||
icon={faCircleInfo}
|
||||
className="mt-0.5 h-3.5 w-3.5 flex-shrink-0 text-amber-400"
|
||||
/>
|
||||
<InfoIcon
|
||||
|
||||
className="mt-0.5 h-3.5 w-3.5 flex-shrink-0 text-amber-400" />
|
||||
<span>
|
||||
This model can't generate from text alone — add a starting
|
||||
frame to animate your prompt.
|
||||
@@ -1570,13 +1564,13 @@ export default function CreateVideo() {
|
||||
{hasSound && (
|
||||
<div className="flex items-center justify-between rounded-xl border border-ui-panel-border bg-ui-controls px-3.5 py-2.5">
|
||||
<span className="flex items-center gap-2 text-sm font-medium text-base-fg/70">
|
||||
<FontAwesomeIcon icon={faWaveformLines} className="h-4 w-4" />
|
||||
<AudioLinesIcon className="h-4 w-4" />
|
||||
Audio
|
||||
</span>
|
||||
<ToggleButton
|
||||
isActive={generateWithSound}
|
||||
icon={faWaveformLines}
|
||||
activeIcon={faWaveformLines}
|
||||
icon={AudioLinesIcon}
|
||||
activeIcon={AudioLinesIcon}
|
||||
label={generateWithSound ? "On" : "Off"}
|
||||
onClick={() => setUi({ generateWithSound: !generateWithSound })}
|
||||
/>
|
||||
@@ -1586,7 +1580,7 @@ export default function CreateVideo() {
|
||||
<>
|
||||
<MobileFieldButton
|
||||
label="Output"
|
||||
icon={<FontAwesomeIcon icon={faClock} className="h-4 w-4" />}
|
||||
icon={<ClockIcon className="h-4 w-4" />}
|
||||
value={outputSummary}
|
||||
onClick={() => setIsOutputDrawerOpen(true)}
|
||||
/>
|
||||
@@ -1689,7 +1683,7 @@ export default function CreateVideo() {
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={openSignupCta}
|
||||
icon={faSparkles}
|
||||
icon={SparklesIcon}
|
||||
className="h-12 px-6 text-base font-semibold rounded-full"
|
||||
>
|
||||
Sign up to create
|
||||
@@ -1729,7 +1723,7 @@ export default function CreateVideo() {
|
||||
>
|
||||
{/* {selectedModel?.model === "seedance_2p0" && (
|
||||
<div className="mb-2 flex items-start gap-2.5 rounded-lg border border-yellow-500/40 px-3.5 py-2.5 text-xs text-yellow-200 shadow-lg backdrop-blur-xl bg-yellow-800/60">
|
||||
<FontAwesomeIcon icon={faTriangleExclamation} className="mt-0.5 h-3.5 w-3.5 flex-shrink-0 text-yellow-400" />
|
||||
<TriangleAlertIcon className="mt-0.5 h-3.5 w-3.5 flex-shrink-0 text-yellow-400" />
|
||||
<span>
|
||||
Seedance 2.0 is in Early Alpha. Generations may be slow and may experience outages.
|
||||
Seedance may reject safe inputs unexpectedly. Try several short generations before longer ones.
|
||||
@@ -1738,10 +1732,9 @@ export default function CreateVideo() {
|
||||
)} */}
|
||||
{requiresImageInput && (
|
||||
<div className="mb-2 flex items-start gap-2.5 rounded-lg border border-amber-500/40 bg-amber-900/80 px-3.5 py-2.5 text-xs text-amber-100 shadow-lg backdrop-blur-sm">
|
||||
<FontAwesomeIcon
|
||||
icon={faCircleInfo}
|
||||
className="mt-0.5 h-3.5 w-3.5 flex-shrink-0 text-amber-400"
|
||||
/>
|
||||
<InfoIcon
|
||||
|
||||
className="mt-0.5 h-3.5 w-3.5 flex-shrink-0 text-amber-400" />
|
||||
<span>
|
||||
This model can't generate from text alone — add a starting
|
||||
frame to animate your prompt.
|
||||
@@ -1904,10 +1897,9 @@ export default function CreateVideo() {
|
||||
mode="default"
|
||||
panelTitle="Duration"
|
||||
triggerIcon={
|
||||
<FontAwesomeIcon
|
||||
icon={faClock}
|
||||
className="h-3.5 w-3.5"
|
||||
/>
|
||||
<ClockIcon
|
||||
|
||||
className="h-3.5 w-3.5" />
|
||||
}
|
||||
triggerLabel={`${effectiveDuration}s`}
|
||||
>
|
||||
@@ -1945,8 +1937,8 @@ export default function CreateVideo() {
|
||||
>
|
||||
<ToggleButton
|
||||
isActive={generateWithSound}
|
||||
icon={faWaveformLines}
|
||||
activeIcon={faWaveformLines}
|
||||
icon={AudioLinesIcon}
|
||||
activeIcon={AudioLinesIcon}
|
||||
onClick={() =>
|
||||
setUi({ generateWithSound: !generateWithSound })
|
||||
}
|
||||
|
||||
+5
-11
@@ -1,11 +1,5 @@
|
||||
import { useRef, useState } from "react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faPlus,
|
||||
faSpinnerThird,
|
||||
faXmark,
|
||||
faVideo,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { LoaderCircleIcon, PlusIcon, VideoIcon, XIcon } from "lucide-react";
|
||||
import { UploaderStates } from "@storyteller/common";
|
||||
import {
|
||||
uploadVideo,
|
||||
@@ -71,7 +65,7 @@ export function ReferenceVideoSlot({
|
||||
<div className="glass flex items-start gap-3 rounded-2xl px-3 py-2">
|
||||
<div className="flex grow flex-col gap-1 min-w-32">
|
||||
<div className="flex items-center gap-2 text-white/90">
|
||||
<FontAwesomeIcon icon={faVideo} className="h-3.5 w-3.5" />
|
||||
<VideoIcon className="h-3.5 w-3.5" />
|
||||
<span className="text-sm font-medium">Reference video</span>
|
||||
</div>
|
||||
<span className="text-[13px] text-white/60">Guide the world (optional)</span>
|
||||
@@ -95,16 +89,16 @@ export function ReferenceVideoSlot({
|
||||
onClick={handleRemove}
|
||||
className="absolute right-[2px] top-[2px] flex h-5 w-5 items-center justify-center rounded-full bg-black/50 text-white backdrop-blur-md transition-colors hover:bg-black"
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} className="h-2.5 w-2.5" />
|
||||
<XIcon className="h-2.5 w-2.5" />
|
||||
</button>
|
||||
</div>
|
||||
) : uploading ? (
|
||||
<div className={SLOT_CLASS}>
|
||||
<FontAwesomeIcon icon={faSpinnerThird} spin className="h-5 w-5 text-white" />
|
||||
<LoaderCircleIcon className="h-5 w-5 animate-spin text-white" />
|
||||
</div>
|
||||
) : (
|
||||
<button onClick={() => inputRef.current?.click()} className={SLOT_CLASS}>
|
||||
<FontAwesomeIcon icon={faPlus} className="text-xl text-white/80" />
|
||||
<PlusIcon className="text-xl text-white/80" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -4,11 +4,7 @@ import { PopoverMenu, type PopoverItem } from "@storyteller/ui-popover";
|
||||
import { Tooltip } from "@storyteller/ui-tooltip";
|
||||
import { Button, ToggleButton } from "@storyteller/ui-button";
|
||||
import { GalleryModal, type GalleryItem } from "@storyteller/ui-gallery-modal";
|
||||
import {
|
||||
faSparkles,
|
||||
faPanorama,
|
||||
faWandMagicSparkles,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { RectangleHorizontalIcon, SparklesIcon, WandSparklesIcon } from "lucide-react";
|
||||
import {
|
||||
PromptBox,
|
||||
ImagePromptRow,
|
||||
@@ -400,8 +396,8 @@ export default function CreateWorld() {
|
||||
<Tooltip content="360° panorama input" position="top" closeOnClick>
|
||||
<ToggleButton
|
||||
isActive={isPanoramic}
|
||||
icon={faPanorama}
|
||||
activeIcon={faPanorama}
|
||||
icon={RectangleHorizontalIcon}
|
||||
activeIcon={RectangleHorizontalIcon}
|
||||
label={isPanoramic ? "Panorama" : "Panorama off"}
|
||||
onClick={() => setUi({ isPanoramic: !isPanoramic })}
|
||||
/>
|
||||
@@ -415,8 +411,8 @@ export default function CreateWorld() {
|
||||
>
|
||||
<ToggleButton
|
||||
isActive={disableRecaption}
|
||||
icon={faWandMagicSparkles}
|
||||
activeIcon={faWandMagicSparkles}
|
||||
icon={WandSparklesIcon}
|
||||
activeIcon={WandSparklesIcon}
|
||||
label={disableRecaption ? "No recaption" : "Recaption"}
|
||||
onClick={() => setUi({ disableRecaption: !disableRecaption })}
|
||||
/>
|
||||
@@ -481,7 +477,7 @@ export default function CreateWorld() {
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={openSignupCta}
|
||||
icon={faSparkles}
|
||||
icon={SparklesIcon}
|
||||
className="h-12 px-6 text-base font-semibold rounded-full"
|
||||
>
|
||||
Sign up to create
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
import {
|
||||
faArrowLeft,
|
||||
faEnvelope,
|
||||
faSpinnerThird,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { ArrowLeftIcon, LoaderCircleIcon, MailIcon } from "lucide-react";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { Input } from "@storyteller/ui-input";
|
||||
import { useState } from "react";
|
||||
@@ -112,10 +107,9 @@ const ForgotPassword = () => {
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isLoading ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinnerThird}
|
||||
className="animate-spin"
|
||||
/>
|
||||
<LoaderCircleIcon
|
||||
|
||||
className="animate-spin" />
|
||||
) : (
|
||||
"Send Reset Code"
|
||||
)}
|
||||
@@ -126,7 +120,7 @@ const ForgotPassword = () => {
|
||||
) : (
|
||||
<div className="text-center py-8">
|
||||
<div className="w-16 h-16 bg-green-500/20 rounded-full flex items-center justify-center mx-auto mb-4 text-green-500">
|
||||
<FontAwesomeIcon icon={faEnvelope} className="text-2xl" />
|
||||
<MailIcon className="text-2xl" />
|
||||
</div>
|
||||
<h3 className="text-xl font-medium mb-2">Check your email</h3>
|
||||
<p className="text-white/60 text-sm mb-6">
|
||||
@@ -152,7 +146,7 @@ const ForgotPassword = () => {
|
||||
to="/login"
|
||||
className="text-white/40 hover:text-white transition-colors flex items-center justify-center gap-2"
|
||||
>
|
||||
<FontAwesomeIcon icon={faArrowLeft} /> Back to Log in
|
||||
<ArrowLeftIcon /> Back to Log in
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import {
|
||||
faArrowLeft,
|
||||
faEye,
|
||||
faEyeSlash,
|
||||
faSpinnerThird,
|
||||
faCheckCircle,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { ArrowLeftIcon, CircleCheckIcon, EyeIcon, EyeOffIcon, LoaderCircleIcon } from "lucide-react";
|
||||
import { DynamicIcon } from "@storyteller/icons";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { Input } from "@storyteller/ui-input";
|
||||
import { useState } from "react";
|
||||
@@ -189,8 +183,8 @@ const VerifyReset = () => {
|
||||
onClick={() => setShowNewPassword(!showNewPassword)}
|
||||
className="absolute right-4 top-1/2 -translate-y-1/2 text-white/30 hover:text-white/60 transition-colors"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={showNewPassword ? faEyeSlash : faEye}
|
||||
<DynamicIcon
|
||||
icon={showNewPassword ? EyeOffIcon : EyeIcon}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
@@ -223,8 +217,8 @@ const VerifyReset = () => {
|
||||
}
|
||||
className="absolute right-4 top-1/2 -translate-y-1/2 text-white/30 hover:text-white/60 transition-colors"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={showConfirmPassword ? faEyeSlash : faEye}
|
||||
<DynamicIcon
|
||||
icon={showConfirmPassword ? EyeOffIcon : EyeIcon}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
@@ -243,10 +237,9 @@ const VerifyReset = () => {
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isLoading ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinnerThird}
|
||||
className="animate-spin"
|
||||
/>
|
||||
<LoaderCircleIcon
|
||||
|
||||
className="animate-spin" />
|
||||
) : (
|
||||
"Change Password"
|
||||
)}
|
||||
@@ -259,7 +252,7 @@ const VerifyReset = () => {
|
||||
to="/forgot-password"
|
||||
className="text-white/40 hover:text-white transition-colors flex items-center justify-center gap-2"
|
||||
>
|
||||
<FontAwesomeIcon icon={faArrowLeft} /> Request a new code
|
||||
<ArrowLeftIcon /> Request a new code
|
||||
</Link>
|
||||
</div>
|
||||
</>
|
||||
@@ -267,7 +260,7 @@ const VerifyReset = () => {
|
||||
<>
|
||||
<div className="text-center py-8">
|
||||
<div className="w-16 h-16 bg-green-500/20 rounded-full flex items-center justify-center mx-auto mb-4 text-green-500">
|
||||
<FontAwesomeIcon icon={faCheckCircle} className="text-2xl" />
|
||||
<CircleCheckIcon className="text-2xl" />
|
||||
</div>
|
||||
<h3 className="text-xl font-medium mb-2">
|
||||
Password Reset Successfully
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faCamera,
|
||||
faChevronDown,
|
||||
faLayerGroup,
|
||||
faXmark,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { CameraIcon, ChevronDownIcon, LayersIcon, XIcon } from "lucide-react";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { EASE_EMPHASIS } from "../../lib/motion";
|
||||
import { formatTime, formatTimePrecise } from "./lib/frame-actions";
|
||||
@@ -59,7 +53,7 @@ export const ExtractionPanel = ({
|
||||
</div>
|
||||
<Button
|
||||
variant="primary"
|
||||
icon={faCamera}
|
||||
icon={CameraIcon}
|
||||
onClick={onCaptureCurrent}
|
||||
disabled={disabled || isExtracting}
|
||||
className="w-full py-2 text-sm font-medium"
|
||||
@@ -78,16 +72,14 @@ export const ExtractionPanel = ({
|
||||
className="flex w-full items-center justify-between px-4 py-3 text-sm font-medium text-base-fg/75 transition-colors hover:text-base-fg disabled:cursor-default disabled:opacity-40"
|
||||
>
|
||||
<span className="flex items-center gap-2">
|
||||
<FontAwesomeIcon
|
||||
icon={faLayerGroup}
|
||||
className="text-xs text-base-fg/40"
|
||||
/>
|
||||
<LayersIcon
|
||||
|
||||
className="text-xs text-base-fg/40" />
|
||||
Burst capture
|
||||
</span>
|
||||
<FontAwesomeIcon
|
||||
icon={faChevronDown}
|
||||
className={`text-xs text-base-fg/40 transition-transform ${burstOpen ? "rotate-180" : ""}`}
|
||||
/>
|
||||
<ChevronDownIcon
|
||||
|
||||
className={`text-xs text-base-fg/40 transition-transform ${burstOpen ? "rotate-180" : ""}`} />
|
||||
</button>
|
||||
<AnimatePresence initial={false}>
|
||||
{burstOpen && !disabled && burstAvailable && (
|
||||
@@ -159,7 +151,7 @@ export const ExtractionPanel = ({
|
||||
className="flex h-6 w-6 items-center justify-center rounded-md text-base-fg/50 transition-colors hover:bg-white/10 hover:text-base-fg"
|
||||
aria-label="Cancel extraction"
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} className="text-xs" />
|
||||
<XIcon className="text-xs" />
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||
import { faArrowRotateRight } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { RotateCwIcon } from "lucide-react";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { GalleryItem, GalleryModal } from "@storyteller/ui-gallery-modal";
|
||||
import { MediaFilesApi } from "@storyteller/api";
|
||||
@@ -450,7 +450,7 @@ export default function FrameExtractor() {
|
||||
{source && (
|
||||
<Button
|
||||
variant="action"
|
||||
icon={faArrowRotateRight}
|
||||
icon={RotateCwIcon}
|
||||
onClick={clearSource}
|
||||
className="px-3 py-1.5 text-sm"
|
||||
>
|
||||
|
||||
@@ -1,15 +1,7 @@
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faArrowDownToLine,
|
||||
faCheck,
|
||||
faImage,
|
||||
faSave,
|
||||
faSpinnerThird,
|
||||
faVideo,
|
||||
faXmark,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import type { IconDefinition } from "@fortawesome/fontawesome-svg-core";
|
||||
import { ArrowDownToLineIcon, CheckIcon, ImageIcon, LoaderCircleIcon, SaveIcon, VideoIcon, XIcon } from "lucide-react";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import { DynamicIcon } from "@storyteller/icons";
|
||||
import { Tooltip } from "@storyteller/ui-tooltip";
|
||||
import { EASE_EMPHASIS } from "../../lib/motion";
|
||||
import type { ExtractedFrame } from "./lib/extract-frames";
|
||||
@@ -66,8 +58,8 @@ export const FramesGrid = ({
|
||||
disabled={isSavingAll}
|
||||
className="flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-medium text-base-fg/70 transition-colors hover:bg-white/5 hover:text-base-fg disabled:opacity-50"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={isSavingAll ? faSpinnerThird : faSave}
|
||||
<DynamicIcon
|
||||
icon={isSavingAll ? LoaderCircleIcon : SaveIcon}
|
||||
className={isSavingAll ? "animate-spin" : ""}
|
||||
/>
|
||||
{isSavingAll ? "Saving…" : "Save all"}
|
||||
@@ -107,7 +99,7 @@ export const FramesGrid = ({
|
||||
className="absolute right-1.5 top-1.5 flex h-6 w-6 items-center justify-center rounded-md bg-black/60 text-white/60 opacity-0 backdrop-blur-sm transition-all hover:text-white group-hover/frame:opacity-100"
|
||||
aria-label="Remove frame"
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} className="text-xs" />
|
||||
<XIcon className="text-xs" />
|
||||
</button>
|
||||
<span className="absolute bottom-1.5 left-1.5 rounded bg-black/60 px-1.5 py-0.5 font-mono text-[10px] text-white/85 backdrop-blur-sm">
|
||||
{formatTimePrecise(frame.timestamp)}
|
||||
@@ -115,19 +107,19 @@ export const FramesGrid = ({
|
||||
</div>
|
||||
<div className="flex items-center justify-around border-t border-ui-panel-border/60 p-1">
|
||||
<FrameAction
|
||||
icon={faImage}
|
||||
icon={ImageIcon}
|
||||
tooltip="Use as image reference"
|
||||
onClick={() => onUseAsImageRef(frame)}
|
||||
loading={state.sending}
|
||||
/>
|
||||
<FrameAction
|
||||
icon={faVideo}
|
||||
icon={VideoIcon}
|
||||
tooltip="Animate into a video"
|
||||
onClick={() => onUseForVideo(frame)}
|
||||
loading={state.sending}
|
||||
/>
|
||||
<FrameAction
|
||||
icon={state.saved ? faCheck : faSave}
|
||||
icon={state.saved ? CheckIcon : SaveIcon}
|
||||
tooltip={state.saved ? "Saved to library" : "Save to library"}
|
||||
onClick={() => onSave(frame)}
|
||||
loading={state.saving}
|
||||
@@ -135,7 +127,7 @@ export const FramesGrid = ({
|
||||
success={state.saved}
|
||||
/>
|
||||
<FrameAction
|
||||
icon={faArrowDownToLine}
|
||||
icon={ArrowDownToLineIcon}
|
||||
tooltip="Download PNG"
|
||||
onClick={() => onDownload(frame)}
|
||||
/>
|
||||
@@ -150,7 +142,7 @@ export const FramesGrid = ({
|
||||
};
|
||||
|
||||
interface FrameActionProps {
|
||||
icon: IconDefinition;
|
||||
icon: LucideIcon;
|
||||
tooltip: string;
|
||||
onClick: () => void;
|
||||
loading?: boolean;
|
||||
@@ -179,8 +171,8 @@ const FrameAction = ({
|
||||
: "text-base-fg/60 hover:bg-white/5 hover:text-base-fg"
|
||||
}`}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={loading ? faSpinnerThird : icon}
|
||||
<DynamicIcon
|
||||
icon={loading ? LoaderCircleIcon : icon}
|
||||
className={loading ? "animate-spin" : ""}
|
||||
/>
|
||||
</button>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useRef, useState } from "react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faFilm, faImages, faUpload } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FilmIcon, ImagesIcon, UploadIcon } from "lucide-react";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
@@ -57,14 +56,14 @@ export const VideoDropZone = ({
|
||||
accept="video/*"
|
||||
onChange={(e) => handleFiles(e.target.files)}
|
||||
/>
|
||||
<FontAwesomeIcon icon={faFilm} className="text-2xl text-base-fg/25" />
|
||||
<FilmIcon className="text-2xl text-base-fg/25" />
|
||||
<div className="text-sm text-base-fg/55">
|
||||
Drag & drop a video, or
|
||||
</div>
|
||||
<div className="flex flex-wrap justify-center gap-2">
|
||||
<Button
|
||||
variant="primary"
|
||||
icon={faUpload}
|
||||
icon={UploadIcon}
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
className="px-4 py-1.5 text-sm font-medium"
|
||||
>
|
||||
@@ -72,7 +71,7 @@ export const VideoDropZone = ({
|
||||
</Button>
|
||||
<Button
|
||||
variant="action"
|
||||
icon={faImages}
|
||||
icon={ImagesIcon}
|
||||
onClick={onPickFromLibrary}
|
||||
className="px-4 py-1.5 text-sm font-medium"
|
||||
>
|
||||
|
||||
@@ -1,16 +1,7 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import type { RefObject } from "react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faPlay,
|
||||
faPause,
|
||||
faVolumeHigh,
|
||||
faVolumeMute,
|
||||
faBackwardStep,
|
||||
faForwardStep,
|
||||
faChevronLeft,
|
||||
faChevronRight,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { ChevronLeftIcon, ChevronRightIcon, PauseIcon, PlayIcon, SkipBackIcon, SkipForwardIcon, Volume2Icon, VolumeXIcon } from "lucide-react";
|
||||
import { DynamicIcon } from "@storyteller/icons";
|
||||
import { Tooltip } from "@storyteller/ui-tooltip";
|
||||
import { formatTime, formatTimePrecise } from "./lib/frame-actions";
|
||||
|
||||
@@ -227,13 +218,13 @@ export const VideoScrubber = ({
|
||||
const stepButtons: {
|
||||
label: string;
|
||||
tooltip: string;
|
||||
icon: typeof faChevronLeft;
|
||||
icon: typeof ChevronLeftIcon;
|
||||
delta: number;
|
||||
}[] = [
|
||||
{ label: "-1s", tooltip: "Back 1s (Shift+←)", icon: faBackwardStep, delta: -1 },
|
||||
{ label: "-1f", tooltip: "Back 1 frame (←)", icon: faChevronLeft, delta: -FRAME_STEP_SEC },
|
||||
{ label: "+1f", tooltip: "Forward 1 frame (→)", icon: faChevronRight, delta: FRAME_STEP_SEC },
|
||||
{ label: "+1s", tooltip: "Forward 1s (Shift+→)", icon: faForwardStep, delta: 1 },
|
||||
{ label: "-1s", tooltip: "Back 1s (Shift+←)", icon: SkipBackIcon, delta: -1 },
|
||||
{ label: "-1f", tooltip: "Back 1 frame (←)", icon: ChevronLeftIcon, delta: -FRAME_STEP_SEC },
|
||||
{ label: "+1f", tooltip: "Forward 1 frame (→)", icon: ChevronRightIcon, delta: FRAME_STEP_SEC },
|
||||
{ label: "+1s", tooltip: "Forward 1s (Shift+→)", icon: SkipForwardIcon, delta: 1 },
|
||||
];
|
||||
|
||||
return (
|
||||
@@ -255,7 +246,7 @@ export const VideoScrubber = ({
|
||||
className="absolute inset-0 m-auto flex h-12 w-12 items-center justify-center rounded-full bg-black/55 text-white opacity-0 backdrop-blur-sm transition-opacity group-hover/scrubber:opacity-100"
|
||||
aria-label="Play"
|
||||
>
|
||||
<FontAwesomeIcon icon={faPlay} className="ml-0.5" />
|
||||
<PlayIcon className="ml-0.5" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -301,7 +292,7 @@ export const VideoScrubber = ({
|
||||
className="flex h-8 w-8 items-center justify-center rounded-md text-white/70 transition-colors hover:bg-white/10 hover:text-white"
|
||||
aria-label={step.tooltip}
|
||||
>
|
||||
<FontAwesomeIcon icon={step.icon} className="text-xs" />
|
||||
<DynamicIcon icon={step.icon} className="text-xs" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
))}
|
||||
@@ -311,7 +302,7 @@ export const VideoScrubber = ({
|
||||
className="mx-0.5 flex h-9 w-9 items-center justify-center rounded-full bg-white/10 text-white transition-colors hover:bg-white/20"
|
||||
aria-label="Play or pause"
|
||||
>
|
||||
<FontAwesomeIcon icon={isPlaying ? faPause : faPlay} />
|
||||
<DynamicIcon icon={isPlaying ? PauseIcon : PlayIcon} />
|
||||
</button>
|
||||
</Tooltip>
|
||||
{stepButtons.slice(2).map((step) => (
|
||||
@@ -321,7 +312,7 @@ export const VideoScrubber = ({
|
||||
className="flex h-8 w-8 items-center justify-center rounded-md text-white/70 transition-colors hover:bg-white/10 hover:text-white"
|
||||
aria-label={step.tooltip}
|
||||
>
|
||||
<FontAwesomeIcon icon={step.icon} className="text-xs" />
|
||||
<DynamicIcon icon={step.icon} className="text-xs" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
))}
|
||||
@@ -334,8 +325,8 @@ export const VideoScrubber = ({
|
||||
className="flex h-7 w-7 items-center justify-center rounded-md text-white/60 transition-colors hover:bg-white/10 hover:text-white"
|
||||
aria-label={isMuted ? "Unmute" : "Mute"}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={isMuted ? faVolumeMute : faVolumeHigh}
|
||||
<DynamicIcon
|
||||
icon={isMuted ? VolumeXIcon : Volume2Icon}
|
||||
className="text-xs"
|
||||
/>
|
||||
</button>
|
||||
|
||||
@@ -1,19 +1,8 @@
|
||||
import { Link } from "react-router-dom";
|
||||
import { motion, useReducedMotion } from "framer-motion";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faImage,
|
||||
faVideo,
|
||||
faMusic,
|
||||
faWandMagicSparkles,
|
||||
faCube,
|
||||
faGlobe,
|
||||
faFilm,
|
||||
faObjectGroup,
|
||||
faPhotoFilm,
|
||||
faArrowRight,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import type { IconDefinition } from "@fortawesome/fontawesome-svg-core";
|
||||
import { ArrowRightIcon, BoxIcon, FilmIcon, GlobeIcon, GroupIcon, ImageIcon, ImagesIcon, MusicIcon, VideoIcon, WandSparklesIcon } from "lucide-react";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import { DynamicIcon } from "@storyteller/icons";
|
||||
import Seo from "../../components/seo";
|
||||
import { Reveal, RevealGroup } from "../../components/motion/reveal";
|
||||
import { TRANSITION_SPRING } from "../../lib/motion";
|
||||
@@ -24,7 +13,7 @@ type AppCard = {
|
||||
label: string;
|
||||
description: string;
|
||||
href: string;
|
||||
icon: IconDefinition;
|
||||
icon: LucideIcon;
|
||||
accent: string;
|
||||
iconBg: string;
|
||||
iconColor: string;
|
||||
@@ -36,7 +25,7 @@ const APPS: AppCard[] = [
|
||||
label: "Image",
|
||||
description: "Generate images from prompts and references.",
|
||||
href: "/create-image",
|
||||
icon: faImage,
|
||||
icon: ImageIcon,
|
||||
accent: "from-blue-500/20 to-blue-500/0",
|
||||
iconBg: "bg-blue-500/25 border-blue-400/30",
|
||||
iconColor: "text-blue-300",
|
||||
@@ -45,7 +34,7 @@ const APPS: AppCard[] = [
|
||||
label: "Video",
|
||||
description: "Generate cinematic clips from text or images.",
|
||||
href: "/create-video",
|
||||
icon: faVideo,
|
||||
icon: VideoIcon,
|
||||
accent: "from-purple-500/20 to-purple-500/0",
|
||||
iconBg: "bg-purple-500/25 border-purple-400/30",
|
||||
iconColor: "text-purple-300",
|
||||
@@ -54,7 +43,7 @@ const APPS: AppCard[] = [
|
||||
label: "Audio",
|
||||
description: "Generate songs and sound effects from prompts.",
|
||||
href: "/create-audio",
|
||||
icon: faMusic,
|
||||
icon: MusicIcon,
|
||||
accent: "from-pink-500/20 to-pink-500/0",
|
||||
iconBg: "bg-pink-500/25 border-pink-400/30",
|
||||
iconColor: "text-pink-300",
|
||||
@@ -63,7 +52,7 @@ const APPS: AppCard[] = [
|
||||
label: "3D Object",
|
||||
description: "Turn a prompt or image into a 3D model.",
|
||||
href: "/create-object",
|
||||
icon: faCube,
|
||||
icon: BoxIcon,
|
||||
accent: "from-cyan-500/20 to-cyan-500/0",
|
||||
iconBg: "bg-cyan-500/25 border-cyan-400/30",
|
||||
iconColor: "text-cyan-300",
|
||||
@@ -72,7 +61,7 @@ const APPS: AppCard[] = [
|
||||
label: "3D World",
|
||||
description: "Turn a prompt or image into an explorable world.",
|
||||
href: "/create-world",
|
||||
icon: faGlobe,
|
||||
icon: GlobeIcon,
|
||||
accent: "from-teal-500/20 to-teal-500/0",
|
||||
iconBg: "bg-teal-500/25 border-teal-400/30",
|
||||
iconColor: "text-teal-300",
|
||||
@@ -81,7 +70,7 @@ const APPS: AppCard[] = [
|
||||
label: "Edit 3D",
|
||||
description: "Compose 3D scenes and render with AI cameras.",
|
||||
href: "/edit-3d",
|
||||
icon: faCube,
|
||||
icon: BoxIcon,
|
||||
accent: "from-amber-500/20 to-amber-500/0",
|
||||
iconBg: "bg-amber-500/25 border-amber-400/30",
|
||||
iconColor: "text-amber-300",
|
||||
@@ -90,7 +79,7 @@ const APPS: AppCard[] = [
|
||||
label: "Background Change",
|
||||
description: "Swap or remove backgrounds with AI VFX.",
|
||||
href: "/background-change",
|
||||
icon: faWandMagicSparkles,
|
||||
icon: WandSparklesIcon,
|
||||
accent: "from-emerald-500/20 to-emerald-500/0",
|
||||
iconBg: "bg-emerald-500/25 border-emerald-400/30",
|
||||
iconColor: "text-emerald-300",
|
||||
@@ -99,7 +88,7 @@ const APPS: AppCard[] = [
|
||||
label: "Edit Video",
|
||||
description: "Trim, arrange, and edit clips on a timeline.",
|
||||
href: "/video-editor",
|
||||
icon: faFilm,
|
||||
icon: FilmIcon,
|
||||
accent: "from-rose-500/20 to-rose-500/0",
|
||||
iconBg: "bg-rose-500/25 border-rose-400/30",
|
||||
iconColor: "text-rose-300",
|
||||
@@ -109,7 +98,7 @@ const APPS: AppCard[] = [
|
||||
label: "Moodboard",
|
||||
description: "Collect references and ideas to steer a generation.",
|
||||
href: "/moodboard",
|
||||
icon: faObjectGroup,
|
||||
icon: GroupIcon,
|
||||
accent: "from-indigo-500/20 to-indigo-500/0",
|
||||
iconBg: "bg-indigo-500/25 border-indigo-400/30",
|
||||
iconColor: "text-indigo-300",
|
||||
@@ -119,7 +108,7 @@ const APPS: AppCard[] = [
|
||||
label: "Frame Extractor",
|
||||
description: "Grab a still frame from any video.",
|
||||
href: "/frame-extractor",
|
||||
icon: faPhotoFilm,
|
||||
icon: ImagesIcon,
|
||||
accent: "from-orange-500/20 to-orange-500/0",
|
||||
iconBg: "bg-orange-500/25 border-orange-400/30",
|
||||
iconColor: "text-orange-300",
|
||||
@@ -166,7 +155,7 @@ export function Home() {
|
||||
<div
|
||||
className={`flex h-11 w-11 shrink-0 items-center justify-center rounded-xl border ${app.iconBg} ${app.iconColor} shadow-[inset_0_1px_1px_rgba(255,255,255,0.12)] transition-transform duration-300 ease-[cubic-bezier(0.22,1,0.36,1)] group-hover:scale-105`}
|
||||
>
|
||||
<FontAwesomeIcon icon={app.icon} className="text-base" />
|
||||
<DynamicIcon icon={app.icon} className="text-base" />
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
@@ -181,7 +170,7 @@ export function Home() {
|
||||
)}
|
||||
</div>
|
||||
<span className="flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-white/[0.04] text-white/40 transition-all duration-300 ease-[cubic-bezier(0.22,1,0.36,1)] group-hover:bg-white/10 group-hover:text-white/80 group-hover:translate-x-0.5">
|
||||
<FontAwesomeIcon icon={faArrowRight} className="text-xs" />
|
||||
<ArrowRightIcon className="text-xs" />
|
||||
</span>
|
||||
</div>
|
||||
<p className="mt-1 text-sm text-white/55 leading-snug">
|
||||
|
||||
@@ -35,32 +35,8 @@ import {
|
||||
showActionReminder,
|
||||
isActionReminderOpen,
|
||||
} from "@storyteller/ui-action-reminder-modal";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faArrowDownToLine,
|
||||
faArrowsRotate,
|
||||
faBorderAll,
|
||||
faCube,
|
||||
faImage,
|
||||
faMusic,
|
||||
faVideo,
|
||||
faPencil,
|
||||
faTrashCan,
|
||||
faFolderPlus,
|
||||
faFolder,
|
||||
faFolderOpen,
|
||||
faGlobe,
|
||||
faPlus,
|
||||
faXmark,
|
||||
faStar,
|
||||
faTag,
|
||||
faTags,
|
||||
faEllipsis,
|
||||
faCloud,
|
||||
faList,
|
||||
faSpinnerThird,
|
||||
faPhotoFilm,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { ArrowDownToLineIcon, BoxIcon, CloudIcon, EllipsisIcon, FolderIcon, FolderOpenIcon, FolderPlusIcon, GlobeIcon, Grid3x3Icon, ImageIcon, ImagesIcon, ListIcon, LoaderCircleIcon, MusicIcon, PencilIcon, PlusIcon, RefreshCwIcon, StarIcon, TagIcon, TagsIcon, Trash2Icon, VideoIcon, XIcon } from "lucide-react";
|
||||
import { DynamicIcon } from "@storyteller/icons";
|
||||
import { Lightbox } from "../../components/lightbox/lightbox";
|
||||
import { toast } from "../../components/toast/toast";
|
||||
import { downloadItemsAsZip } from "../../lib/download-media-zip";
|
||||
@@ -86,23 +62,23 @@ import {
|
||||
const PAGE_SIZE = 60;
|
||||
|
||||
const FILTERS = [
|
||||
{ id: "all", label: "All", icon: faBorderAll, route: "/library" },
|
||||
{ id: "image", label: "Images", icon: faImage, route: "/library/images" },
|
||||
{ id: "video", label: "Videos", icon: faVideo, route: "/library/videos" },
|
||||
{ id: "audio", label: "Audio", icon: faMusic, route: "/library/audio" },
|
||||
{ id: "meshes", label: "Meshes", icon: faCube, route: "/library/meshes" },
|
||||
{ id: "splats", label: "Splats", icon: faGlobe, route: "/library/splats" },
|
||||
{ id: "all", label: "All", icon: Grid3x3Icon, route: "/library" },
|
||||
{ id: "image", label: "Images", icon: ImageIcon, route: "/library/images" },
|
||||
{ id: "video", label: "Videos", icon: VideoIcon, route: "/library/videos" },
|
||||
{ id: "audio", label: "Audio", icon: MusicIcon, route: "/library/audio" },
|
||||
{ id: "meshes", label: "Meshes", icon: BoxIcon, route: "/library/meshes" },
|
||||
{ id: "splats", label: "Splats", icon: GlobeIcon, route: "/library/splats" },
|
||||
];
|
||||
|
||||
// Class scopes for the Unfoldered tab. Local state (not routes): the tab is
|
||||
// one page and the filter maps to the endpoint's `filter_media_class` param.
|
||||
const FOLDERLESS_CLASS_FILTERS = [
|
||||
{ id: "all", label: "All", icon: faBorderAll },
|
||||
{ id: "image", label: "Images", icon: faImage },
|
||||
{ id: "video", label: "Videos", icon: faVideo },
|
||||
{ id: "audio", label: "Audio", icon: faMusic },
|
||||
{ id: "mesh", label: "Meshes", icon: faCube },
|
||||
{ id: "splat", label: "Splats", icon: faGlobe },
|
||||
{ id: "all", label: "All", icon: Grid3x3Icon },
|
||||
{ id: "image", label: "Images", icon: ImageIcon },
|
||||
{ id: "video", label: "Videos", icon: VideoIcon },
|
||||
{ id: "audio", label: "Audio", icon: MusicIcon },
|
||||
{ id: "mesh", label: "Meshes", icon: BoxIcon },
|
||||
{ id: "splat", label: "Splats", icon: GlobeIcon },
|
||||
];
|
||||
|
||||
const ROUTE_TO_FILTER: Record<string, string> = {
|
||||
@@ -1140,10 +1116,9 @@ export default function Library() {
|
||||
transition={{ duration: 0.32, ease: EASE_EMPHASIS }}
|
||||
/>
|
||||
)}
|
||||
<FontAwesomeIcon
|
||||
icon={faBorderAll}
|
||||
className="relative z-10 text-xs"
|
||||
/>
|
||||
<Grid3x3Icon
|
||||
|
||||
className="relative z-10 text-xs" />
|
||||
<span className="relative z-10">All Assets</span>
|
||||
</Link>
|
||||
<Link
|
||||
@@ -1161,10 +1136,9 @@ export default function Library() {
|
||||
transition={{ duration: 0.32, ease: EASE_EMPHASIS }}
|
||||
/>
|
||||
)}
|
||||
<FontAwesomeIcon
|
||||
icon={faFolder}
|
||||
className="relative z-10 text-xs"
|
||||
/>
|
||||
<FolderIcon
|
||||
|
||||
className="relative z-10 text-xs" />
|
||||
<span className="relative z-10">Folders</span>
|
||||
</Link>
|
||||
<Link
|
||||
@@ -1182,10 +1156,9 @@ export default function Library() {
|
||||
transition={{ duration: 0.32, ease: EASE_EMPHASIS }}
|
||||
/>
|
||||
)}
|
||||
<FontAwesomeIcon
|
||||
icon={faFolderOpen}
|
||||
className="relative z-10 text-xs"
|
||||
/>
|
||||
<FolderOpenIcon
|
||||
|
||||
className="relative z-10 text-xs" />
|
||||
<span className="relative z-10">Unfoldered</span>
|
||||
</Link>
|
||||
<Link
|
||||
@@ -1203,10 +1176,9 @@ export default function Library() {
|
||||
transition={{ duration: 0.32, ease: EASE_EMPHASIS }}
|
||||
/>
|
||||
)}
|
||||
<FontAwesomeIcon
|
||||
icon={faTag}
|
||||
className="relative z-10 text-xs"
|
||||
/>
|
||||
<TagIcon
|
||||
|
||||
className="relative z-10 text-xs" />
|
||||
<span className="relative z-10">Tags</span>
|
||||
</Link>
|
||||
</div>
|
||||
@@ -1216,10 +1188,9 @@ export default function Library() {
|
||||
className="h-8 w-8 flex items-center justify-center rounded-lg text-white/50 hover:text-white hover:bg-ui-controls/40 transition-colors"
|
||||
title="Refresh library"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowsRotate}
|
||||
className={`text-sm ${initialLoading ? "animate-spin" : ""}`}
|
||||
/>
|
||||
<RefreshCwIcon
|
||||
|
||||
className={`text-sm ${initialLoading ? "animate-spin" : ""}`} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -1244,7 +1215,7 @@ export default function Library() {
|
||||
transition={{ duration: 0.32, ease: EASE_EMPHASIS }}
|
||||
/>
|
||||
)}
|
||||
<FontAwesomeIcon
|
||||
<DynamicIcon
|
||||
icon={filter.icon}
|
||||
className="relative z-10 text-xs"
|
||||
/>
|
||||
@@ -1274,7 +1245,7 @@ export default function Library() {
|
||||
transition={{ duration: 0.32, ease: EASE_EMPHASIS }}
|
||||
/>
|
||||
)}
|
||||
<FontAwesomeIcon
|
||||
<DynamicIcon
|
||||
icon={filter.icon}
|
||||
className="relative z-10 text-xs"
|
||||
/>
|
||||
@@ -1288,7 +1259,7 @@ export default function Library() {
|
||||
{tab === "folders" && !inFolder && (
|
||||
<Button
|
||||
variant="primary"
|
||||
icon={faFolderPlus}
|
||||
icon={FolderPlusIcon}
|
||||
onClick={() => openNewFolderModal(null)}
|
||||
className="rounded-full text-xs sm:text-sm px-3 sm:px-4 py-2"
|
||||
>
|
||||
@@ -1332,8 +1303,8 @@ export default function Library() {
|
||||
<div className="flex items-center gap-1 bg-ui-controls/40 rounded-xl p-1">
|
||||
{(
|
||||
[
|
||||
["cloud", faCloud, "Cloud"],
|
||||
["list", faList, "List"],
|
||||
["cloud", CloudIcon, "Cloud"],
|
||||
["list", ListIcon, "List"],
|
||||
] as const
|
||||
).map(([id, icon, label]) => (
|
||||
<button
|
||||
@@ -1356,7 +1327,7 @@ export default function Library() {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<FontAwesomeIcon
|
||||
<DynamicIcon
|
||||
icon={icon}
|
||||
className="relative z-10 text-xs"
|
||||
/>
|
||||
@@ -1400,14 +1371,14 @@ export default function Library() {
|
||||
className="h-7 w-7 flex items-center justify-center rounded-lg text-white/50 hover:text-white hover:bg-ui-controls/40 transition-colors"
|
||||
title="Rename folder"
|
||||
>
|
||||
<FontAwesomeIcon icon={faPencil} className="text-xs" />
|
||||
<PencilIcon className="text-xs" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => openNewFolderModal(activeFolderId)}
|
||||
className="h-7 w-7 flex items-center justify-center rounded-lg text-white/50 hover:text-white hover:bg-ui-controls/40 transition-colors"
|
||||
title="New subfolder"
|
||||
>
|
||||
<FontAwesomeIcon icon={faFolderPlus} className="text-xs" />
|
||||
<FolderPlusIcon className="text-xs" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
@@ -1436,14 +1407,14 @@ export default function Library() {
|
||||
className="h-7 w-7 flex items-center justify-center rounded-lg text-white/50 hover:text-white hover:bg-ui-controls/40 transition-colors"
|
||||
title="Rename tag"
|
||||
>
|
||||
<FontAwesomeIcon icon={faPencil} className="text-xs" />
|
||||
<PencilIcon className="text-xs" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => confirmDeleteTag(activeTagToken)}
|
||||
className="h-7 w-7 flex items-center justify-center rounded-lg text-white/50 hover:text-red hover:bg-ui-controls/40 transition-colors"
|
||||
title="Delete tag"
|
||||
>
|
||||
<FontAwesomeIcon icon={faTrashCan} className="text-xs" />
|
||||
<Trash2Icon className="text-xs" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
@@ -1481,15 +1452,14 @@ export default function Library() {
|
||||
currentSubfolders.length === 0 && (
|
||||
<div className="flex flex-col items-center justify-center py-20 gap-3">
|
||||
<div className="flex h-14 w-14 items-center justify-center rounded-2xl bg-ui-controls/30">
|
||||
<FontAwesomeIcon
|
||||
icon={faFolderOpen}
|
||||
className="text-2xl text-white/40"
|
||||
/>
|
||||
<FolderOpenIcon
|
||||
|
||||
className="text-2xl text-white/40" />
|
||||
</div>
|
||||
<p className="text-white/40 text-sm">No folders yet.</p>
|
||||
<Button
|
||||
variant="primary"
|
||||
icon={faFolderPlus}
|
||||
icon={FolderPlusIcon}
|
||||
onClick={() => openNewFolderModal(null)}
|
||||
className="rounded-full text-sm px-4 py-2"
|
||||
>
|
||||
@@ -1548,10 +1518,9 @@ export default function Library() {
|
||||
) : tags.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center py-20 gap-3">
|
||||
<div className="flex h-14 w-14 items-center justify-center rounded-2xl bg-ui-controls/30">
|
||||
<FontAwesomeIcon
|
||||
icon={faTags}
|
||||
className="text-2xl text-white/40"
|
||||
/>
|
||||
<TagsIcon
|
||||
|
||||
className="text-2xl text-white/40" />
|
||||
</div>
|
||||
<p className="text-white/40 text-sm">No tags yet.</p>
|
||||
<p className="text-white/30 text-xs max-w-xs text-center">
|
||||
@@ -1587,10 +1556,9 @@ export default function Library() {
|
||||
}}
|
||||
className="flex items-center gap-2 pl-4 pr-1 py-2 text-sm font-medium text-white"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faTag}
|
||||
className="text-xs text-violet-400"
|
||||
/>
|
||||
<TagIcon
|
||||
|
||||
className="text-xs text-violet-400" />
|
||||
<span className="max-w-[14rem] truncate">{t.value}</span>
|
||||
<span className="text-xs text-white/40">
|
||||
{t.useCount}
|
||||
@@ -1610,7 +1578,7 @@ export default function Library() {
|
||||
title="Rename or delete"
|
||||
className="mr-1.5 flex h-6 w-6 items-center justify-center rounded-full text-white/40 hover:bg-white/10 hover:text-white transition-colors"
|
||||
>
|
||||
<FontAwesomeIcon icon={faEllipsis} className="text-xs" />
|
||||
<EllipsisIcon className="text-xs" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
@@ -1824,10 +1792,9 @@ export default function Library() {
|
||||
setContextMenu(null);
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faStar}
|
||||
className={`w-4 ${menuFolder?.hasStar ? "text-amber-400" : "text-base-fg/40"}`}
|
||||
/>
|
||||
<StarIcon
|
||||
|
||||
className={`w-4 ${menuFolder?.hasStar ? "text-amber-400" : "text-base-fg/40"}`} />
|
||||
<span>{menuFolder?.hasStar ? "Unstar" : "Star"}</span>
|
||||
</button>
|
||||
<FolderColorRow
|
||||
@@ -1847,7 +1814,7 @@ export default function Library() {
|
||||
openNewFolderModal(contextMenu.folderId);
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={faFolderPlus} className="w-4" />
|
||||
<FolderPlusIcon className="w-4" />
|
||||
<span>New subfolder</span>
|
||||
</button>
|
||||
<button
|
||||
@@ -1855,7 +1822,7 @@ export default function Library() {
|
||||
className="flex w-full items-center gap-2 px-2 py-2 rounded-md hover:bg-ui-controls/60 text-sm text-base-fg"
|
||||
onClick={() => startRename(contextMenu.folderId)}
|
||||
>
|
||||
<FontAwesomeIcon icon={faPencil} className="w-4" />
|
||||
<PencilIcon className="w-4" />
|
||||
<span>Rename</span>
|
||||
</button>
|
||||
<button
|
||||
@@ -1867,7 +1834,7 @@ export default function Library() {
|
||||
confirmDeleteFolder(folderId);
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={faTrashCan} className="w-4" />
|
||||
<Trash2Icon className="w-4" />
|
||||
<span>Delete folder</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -1896,7 +1863,7 @@ export default function Library() {
|
||||
className="flex w-full items-center gap-2 px-2 py-2 rounded-md hover:bg-ui-controls/60 text-sm text-base-fg"
|
||||
onClick={() => setTagRenameTarget(tagContextMenu.tagToken)}
|
||||
>
|
||||
<FontAwesomeIcon icon={faPencil} className="w-4" />
|
||||
<PencilIcon className="w-4" />
|
||||
<span>Rename</span>
|
||||
</button>
|
||||
<button
|
||||
@@ -1908,7 +1875,7 @@ export default function Library() {
|
||||
confirmDeleteTag(token);
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={faTrashCan} className="w-4" />
|
||||
<Trash2Icon className="w-4" />
|
||||
<span>Delete tag</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -2075,10 +2042,10 @@ const LibraryTile = memo(function LibraryTile({
|
||||
const SEND_TO_TARGETS: {
|
||||
destination: PromptDestination;
|
||||
label: string;
|
||||
icon: typeof faImage;
|
||||
icon: typeof ImageIcon;
|
||||
}[] = [
|
||||
{ destination: "image", label: "Create image", icon: faImage },
|
||||
{ destination: "video", label: "Create video", icon: faVideo },
|
||||
{ destination: "image", label: "Create image", icon: ImageIcon },
|
||||
{ destination: "video", label: "Create video", icon: VideoIcon },
|
||||
];
|
||||
|
||||
// Shared styling for the bar's action pills. `whitespace-nowrap` keeps labels
|
||||
@@ -2245,8 +2212,8 @@ function BulkSelectionBar({
|
||||
onClick={() => setSendToOpen((v) => !v)}
|
||||
className={BAR_BUTTON_CLASS}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={hasPromptImages ? faImage : faVideo}
|
||||
<DynamicIcon
|
||||
icon={hasPromptImages ? ImageIcon : VideoIcon}
|
||||
className="text-xs"
|
||||
/>
|
||||
{/* Icon-only on phones — a fourth labeled button overflows the bar. */}
|
||||
@@ -2275,7 +2242,7 @@ function BulkSelectionBar({
|
||||
}}
|
||||
className="flex w-full items-center gap-2.5 rounded-md px-2 py-1.5 text-sm text-white hover:bg-ui-controls/50 transition-colors"
|
||||
>
|
||||
<FontAwesomeIcon icon={target.icon} className="w-4 text-xs" />
|
||||
<DynamicIcon icon={target.icon} className="w-4 text-xs" />
|
||||
<span>{target.label}</span>
|
||||
</button>
|
||||
))}
|
||||
@@ -2298,7 +2265,7 @@ function BulkSelectionBar({
|
||||
}}
|
||||
className={BAR_BUTTON_CLASS}
|
||||
>
|
||||
<FontAwesomeIcon icon={faPhotoFilm} className="text-xs" />
|
||||
<ImagesIcon className="text-xs" />
|
||||
<span className="hidden sm:inline">Extract frames</span>
|
||||
</button>
|
||||
)}
|
||||
@@ -2311,7 +2278,7 @@ function BulkSelectionBar({
|
||||
onClick={() => setPopoverOpen((v) => !v)}
|
||||
className={BAR_BUTTON_CLASS}
|
||||
>
|
||||
<FontAwesomeIcon icon={faFolderPlus} className="text-xs" />
|
||||
<FolderPlusIcon className="text-xs" />
|
||||
{/* Icon-only on phones — five labeled buttons overflow the bar. */}
|
||||
<span className="hidden sm:inline">Add to folder</span>
|
||||
</button>
|
||||
@@ -2341,8 +2308,8 @@ function BulkSelectionBar({
|
||||
}}
|
||||
className="flex w-full items-center gap-2.5 rounded-md px-2 py-1.5 text-sm text-white hover:bg-ui-controls/50 transition-colors"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faFolder}
|
||||
<FolderIcon
|
||||
|
||||
className={
|
||||
folder.colorCode ? "text-xs" : "text-xs text-primary"
|
||||
}
|
||||
@@ -2350,8 +2317,7 @@ function BulkSelectionBar({
|
||||
folder.colorCode
|
||||
? { color: folder.colorCode }
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
} />
|
||||
<span className="truncate">{folder.name}</span>
|
||||
</button>
|
||||
))
|
||||
@@ -2365,7 +2331,7 @@ function BulkSelectionBar({
|
||||
}}
|
||||
className="flex w-full items-center gap-2.5 rounded-md px-2 py-1.5 text-sm text-white/70 hover:bg-ui-controls/50 transition-colors"
|
||||
>
|
||||
<FontAwesomeIcon icon={faPlus} className="w-4 text-xs" />
|
||||
<PlusIcon className="w-4 text-xs" />
|
||||
<span>Create new folder</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -2387,7 +2353,7 @@ function BulkSelectionBar({
|
||||
}}
|
||||
className={BAR_BUTTON_CLASS}
|
||||
>
|
||||
<FontAwesomeIcon icon={faTags} className="text-xs" />
|
||||
<TagsIcon className="text-xs" />
|
||||
<span className="hidden sm:inline">Add tags</span>
|
||||
</button>
|
||||
{tagsOpen && (
|
||||
@@ -2429,8 +2395,8 @@ function BulkSelectionBar({
|
||||
disabled={isDownloading}
|
||||
className={`${BAR_BUTTON_CLASS} disabled:opacity-60`}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={isDownloading ? faSpinnerThird : faArrowDownToLine}
|
||||
<DynamicIcon
|
||||
icon={isDownloading ? LoaderCircleIcon : ArrowDownToLineIcon}
|
||||
className={`text-xs ${isDownloading ? "animate-spin" : ""}`}
|
||||
/>
|
||||
{isDownloading && downloadProgress
|
||||
@@ -2443,7 +2409,7 @@ function BulkSelectionBar({
|
||||
onClick={onDeleteSelected}
|
||||
className="flex items-center gap-2 whitespace-nowrap rounded-full bg-red/90 px-3 py-1.5 text-sm font-medium text-white hover:bg-red transition-colors"
|
||||
>
|
||||
<FontAwesomeIcon icon={faTrashCan} className="text-xs" />
|
||||
<Trash2Icon className="text-xs" />
|
||||
Delete
|
||||
</button>
|
||||
<button
|
||||
@@ -2452,7 +2418,7 @@ function BulkSelectionBar({
|
||||
aria-label="Clear selection"
|
||||
className="flex h-8 w-8 items-center justify-center rounded-full bg-ui-controls/60 text-white hover:bg-ui-controls/90 transition-colors"
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} />
|
||||
<XIcon />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
@@ -2464,12 +2430,12 @@ function BulkThumb({ item }: { item: GalleryItem }) {
|
||||
const [failed, setFailed] = useState(false);
|
||||
const placeholderIcon =
|
||||
item.mediaClass === "video"
|
||||
? faVideo
|
||||
? VideoIcon
|
||||
: is3DMediaClass(item.mediaClass)
|
||||
? faCube
|
||||
? BoxIcon
|
||||
: item.mediaClass === "audio"
|
||||
? faMusic
|
||||
: faImage;
|
||||
? MusicIcon
|
||||
: ImageIcon;
|
||||
const showImage = !!item.thumbnail && !failed;
|
||||
return (
|
||||
<div className="-ml-2 h-8 w-8 flex-shrink-0 overflow-hidden rounded border-2 border-ui-panel bg-black/30 first:ml-0">
|
||||
@@ -2482,7 +2448,7 @@ function BulkThumb({ item }: { item: GalleryItem }) {
|
||||
/>
|
||||
) : (
|
||||
<div className="flex h-full w-full items-center justify-center bg-black/50">
|
||||
<FontAwesomeIcon
|
||||
<DynamicIcon
|
||||
icon={placeholderIcon}
|
||||
className="text-xs text-white/50"
|
||||
/>
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import {
|
||||
faEye,
|
||||
faEyeSlash,
|
||||
faSpinnerThird,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { EyeIcon, EyeOffIcon, LoaderCircleIcon } from "lucide-react";
|
||||
import { DynamicIcon } from "@storyteller/icons";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { Input } from "@storyteller/ui-input";
|
||||
import { useState } from "react";
|
||||
@@ -134,7 +130,7 @@ const Login = () => {
|
||||
className="absolute right-4 top-1/2 -translate-y-1/2 text-white/30 hover:text-white/60 transition-colors"
|
||||
tabIndex={-1}
|
||||
>
|
||||
<FontAwesomeIcon icon={showPassword ? faEyeSlash : faEye} />
|
||||
<DynamicIcon icon={showPassword ? EyeOffIcon : EyeIcon} />
|
||||
</button>
|
||||
</div>
|
||||
</Reveal>
|
||||
@@ -146,7 +142,7 @@ const Login = () => {
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isLoading ? (
|
||||
<FontAwesomeIcon icon={faSpinnerThird} className="animate-spin" />
|
||||
<LoaderCircleIcon className="animate-spin" />
|
||||
) : (
|
||||
"Log in"
|
||||
)}
|
||||
|
||||
@@ -2,12 +2,7 @@ import { useState, useEffect } from "react";
|
||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { Input } from "@storyteller/ui-input";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faCheckCircle,
|
||||
faInfoCircle,
|
||||
faSpinnerThird,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { CircleCheckIcon, InfoIcon, LoaderCircleIcon } from "lucide-react";
|
||||
import Seo from "../../components/seo";
|
||||
import { UsersApi, BillingApi } from "@storyteller/api";
|
||||
|
||||
@@ -311,10 +306,9 @@ const Onboarding = () => {
|
||||
<div className="max-w-md w-full text-center">
|
||||
<div className="mb-6">
|
||||
<div className="w-20 h-20 mx-auto rounded-full bg-green-500/20 flex items-center justify-center">
|
||||
<FontAwesomeIcon
|
||||
icon={faCheckCircle}
|
||||
className="text-5xl text-green-400"
|
||||
/>
|
||||
<CircleCheckIcon
|
||||
|
||||
className="text-5xl text-green-400" />
|
||||
</div>
|
||||
</div>
|
||||
<h1 className="text-3xl md:text-4xl font-medium mb-4 text-white">
|
||||
@@ -342,10 +336,9 @@ const Onboarding = () => {
|
||||
<div className="max-w-md w-full">
|
||||
{isNewAccount && (
|
||||
<div className="bg-blue-500/10 border border-blue-500/30 rounded-xl p-4 mb-6 flex items-start gap-3 animate-fade-in shadow-lg shadow-blue-900/10">
|
||||
<FontAwesomeIcon
|
||||
icon={faInfoCircle}
|
||||
className="text-blue-400 mt-1 flex-shrink-0 text-lg"
|
||||
/>
|
||||
<InfoIcon
|
||||
|
||||
className="text-blue-400 mt-1 flex-shrink-0 text-lg" />
|
||||
<div>
|
||||
<h3 className="text-blue-200 font-medium mb-1">
|
||||
Account Created
|
||||
@@ -463,10 +456,9 @@ const Onboarding = () => {
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
{isSubmitting ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinnerThird}
|
||||
className="animate-spin"
|
||||
/>
|
||||
<LoaderCircleIcon
|
||||
|
||||
className="animate-spin" />
|
||||
) : (
|
||||
"Continue"
|
||||
)}
|
||||
|
||||
@@ -8,13 +8,8 @@
|
||||
// - start from a blank canvas → BlankCanvasModal (from the lib)
|
||||
|
||||
import { useState } from "react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faExpand,
|
||||
faImages,
|
||||
faPencil,
|
||||
faSpinnerThird,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { ImagesIcon, LoaderCircleIcon, MaximizeIcon, PencilIcon } from "lucide-react";
|
||||
import { DynamicIcon } from "@storyteller/icons";
|
||||
import { UploaderStates } from "@storyteller/common";
|
||||
import { MediaFilesApi } from "@storyteller/api";
|
||||
import {
|
||||
@@ -160,8 +155,8 @@ export function BaseImageSelector({
|
||||
disabled={busy}
|
||||
/>
|
||||
<div className="flex h-14 w-14 items-center justify-center rounded-2xl border border-primary-400/30 bg-primary/30 text-primary-300">
|
||||
<FontAwesomeIcon
|
||||
icon={busy ? faSpinnerThird : faPencil}
|
||||
<DynamicIcon
|
||||
icon={busy ? LoaderCircleIcon : PencilIcon}
|
||||
className={`text-xl ${busy ? "animate-spin" : ""}`}
|
||||
/>
|
||||
</div>
|
||||
@@ -181,7 +176,7 @@ export function BaseImageSelector({
|
||||
disabled={busy}
|
||||
className="flex items-center gap-2 rounded-lg border border-ui-controls-border bg-ui-controls px-3 py-2 text-sm font-medium text-base-fg transition-colors hover:bg-ui-controls/80"
|
||||
>
|
||||
<FontAwesomeIcon icon={faImages} />
|
||||
<ImagesIcon />
|
||||
Pick from Library
|
||||
</button>
|
||||
<button
|
||||
@@ -193,7 +188,7 @@ export function BaseImageSelector({
|
||||
disabled={busy}
|
||||
className="flex items-center gap-2 rounded-lg border border-ui-controls-border bg-ui-controls px-3 py-2 text-sm font-medium text-base-fg transition-colors hover:bg-ui-controls/80"
|
||||
>
|
||||
<FontAwesomeIcon icon={faExpand} />
|
||||
<MaximizeIcon />
|
||||
Blank Canvas
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faCompress,
|
||||
faExpand,
|
||||
faImage,
|
||||
faVideo,
|
||||
faWandMagicSparkles,
|
||||
faXmark,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { ImageIcon, MaximizeIcon, MinimizeIcon, VideoIcon, WandSparklesIcon, XIcon } from "lucide-react";
|
||||
import { DynamicIcon } from "@storyteller/icons";
|
||||
import { MediaFilesApi, PromptsApi } from "@storyteller/api";
|
||||
import { addCorsParam, PLACEHOLDER_IMAGES } from "@storyteller/common";
|
||||
import { LoadingSpinner } from "@storyteller/ui-loading-spinner";
|
||||
@@ -218,7 +211,7 @@ function ShowOutputPill({ onClick }: { onClick: () => void }) {
|
||||
aria-label="Show rendered output"
|
||||
className="pointer-events-auto absolute right-2 top-16 z-30 flex items-center gap-2 rounded-xl border border-ui-controls-border bg-ui-controls px-3 py-1.5 text-sm font-medium text-base-fg shadow-xl transition-colors duration-150 hover:bg-ui-controls/80 animate-in fade-in slide-in-from-right-4"
|
||||
>
|
||||
<FontAwesomeIcon icon={faImage} className="h-3 w-3 text-primary" />
|
||||
<ImageIcon className="h-3 w-3 text-primary" />
|
||||
Show output
|
||||
</button>
|
||||
);
|
||||
@@ -249,14 +242,14 @@ function ViewToggle({ view, onChange }: ViewToggleProps) {
|
||||
transform: view === "image" ? "translateX(100%)" : "translateX(0%)",
|
||||
}}
|
||||
/>
|
||||
<ToggleIcon icon={faVideo} active={view === "video"} />
|
||||
<ToggleIcon icon={faImage} active={view === "image"} />
|
||||
<ToggleIcon icon={VideoIcon} active={view === "video"} />
|
||||
<ToggleIcon icon={ImageIcon} active={view === "image"} />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
interface ToggleIconProps {
|
||||
icon: typeof faVideo;
|
||||
icon: typeof VideoIcon;
|
||||
active: boolean;
|
||||
}
|
||||
|
||||
@@ -268,7 +261,7 @@ function ToggleIcon({ icon, active }: ToggleIconProps) {
|
||||
(active ? "text-white" : "text-base-fg/60")
|
||||
}
|
||||
>
|
||||
<FontAwesomeIcon icon={icon} className="h-3 w-3" />
|
||||
<DynamicIcon icon={icon} className="h-3 w-3" />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -307,10 +300,9 @@ function Card({
|
||||
<div className="glass pointer-events-auto overflow-hidden rounded-xl shadow-xl border-2 border-primary">
|
||||
<div className="flex items-center justify-between gap-3 border-b border-ui-controls-border/60 px-3 py-2">
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
<FontAwesomeIcon
|
||||
icon={faWandMagicSparkles}
|
||||
className="h-3 w-3 shrink-0 text-primary"
|
||||
/>
|
||||
<WandSparklesIcon
|
||||
|
||||
className="h-3 w-3 shrink-0 text-primary" />
|
||||
<div className="min-w-0 leading-tight">
|
||||
<div className="text-xs font-semibold uppercase tracking-wider text-base-fg">
|
||||
Rendered Output
|
||||
@@ -332,7 +324,7 @@ function Card({
|
||||
}
|
||||
className="flex h-6 w-6 shrink-0 items-center justify-center rounded-full text-base-fg/60 transition-colors hover:bg-ui-controls hover:text-base-fg"
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} className="h-4 w-4" />
|
||||
<XIcon className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -360,8 +352,8 @@ function Card({
|
||||
aria-label={isExpanded ? "Collapse output view" : "Expand output view"}
|
||||
className="absolute bottom-2 right-2 flex h-7 w-7 items-center justify-center rounded-md border border-white/10 bg-black/60 text-white/80 shadow-lg backdrop-blur-md transition-colors duration-150 hover:bg-black/80 hover:text-white"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={isExpanded ? faCompress : faExpand}
|
||||
<DynamicIcon
|
||||
icon={isExpanded ? MinimizeIcon : MaximizeIcon}
|
||||
className="h-3 w-3"
|
||||
/>
|
||||
</button>
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
import { useMemo, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faCubes,
|
||||
faLayerGroup,
|
||||
faXmark,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { BoxesIcon, LayersIcon, XIcon } from "lucide-react";
|
||||
import { SceneSplashCard } from "./splash/SceneSplashCard";
|
||||
import { EXAMPLE_SCENES } from "./splash/example-scenes";
|
||||
|
||||
@@ -70,7 +65,7 @@ function CollapsedPill({ onClick }: { onClick: () => void }) {
|
||||
onClick={onClick}
|
||||
className="flex items-center gap-2.5 rounded-full border border-primary/40 bg-gradient-to-r from-primary/90 to-primary px-5 py-2.5 text-sm font-semibold text-white transition-colors hover:from-primary hover:to-primary animate-in fade-in slide-in-from-bottom-2 duration-300"
|
||||
>
|
||||
<FontAwesomeIcon icon={faCubes} className="h-3.5 w-3.5" />
|
||||
<BoxesIcon className="h-3.5 w-3.5" />
|
||||
<span>See other demo scenes</span>
|
||||
</button>
|
||||
);
|
||||
@@ -87,10 +82,9 @@ function ExpandedPanel({ scenes, onClose, onPick }: ExpandedPanelProps) {
|
||||
<div className="glass w-[860px] max-w-[90vw] overflow-hidden rounded-xl border border-ui-controls-border shadow-2xl animate-in fade-in slide-in-from-bottom-2 duration-200">
|
||||
<div className="flex items-center justify-between border-b border-ui-controls-border/60 px-4 py-2.5">
|
||||
<div className="flex items-center gap-2">
|
||||
<FontAwesomeIcon
|
||||
icon={faLayerGroup}
|
||||
className="h-3 w-3 text-primary"
|
||||
/>
|
||||
<LayersIcon
|
||||
|
||||
className="h-3 w-3 text-primary" />
|
||||
<div className="text-xs font-semibold uppercase tracking-wider text-base-fg">
|
||||
Other demo scenes
|
||||
</div>
|
||||
@@ -101,7 +95,7 @@ function ExpandedPanel({ scenes, onClose, onPick }: ExpandedPanelProps) {
|
||||
aria-label="Close other scenes"
|
||||
className="flex h-6 w-6 items-center justify-center rounded-full text-base-fg/60 transition-colors hover:bg-ui-controls hover:text-base-fg"
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} className="h-4 w-4" />
|
||||
<XIcon className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-3 p-3 sm:grid-cols-3">
|
||||
|
||||
@@ -5,8 +5,7 @@ import {
|
||||
useParams,
|
||||
useSearchParams,
|
||||
} from "react-router-dom";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faDesktop, faHouse } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { HouseIcon, MonitorIcon } from "lucide-react";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { MediaFilesApi } from "@storyteller/api";
|
||||
import { Stage3D, usePageSceneStore } from "@storyteller/ui-pagescene";
|
||||
@@ -388,7 +387,7 @@ function MobileGate() {
|
||||
<div className="flex h-full w-full items-center justify-center p-6">
|
||||
<div className="flex max-w-sm flex-col items-center gap-4 text-center">
|
||||
<div className="flex h-14 w-14 items-center justify-center rounded-2xl border border-amber-400/30 bg-amber-500/25 text-amber-300">
|
||||
<FontAwesomeIcon icon={faDesktop} className="text-xl" />
|
||||
<MonitorIcon className="text-xl" />
|
||||
</div>
|
||||
<h1 className="text-xl font-semibold text-white">
|
||||
Edit 3D is desktop-only
|
||||
@@ -399,7 +398,7 @@ function MobileGate() {
|
||||
laptop to start editing.
|
||||
</p>
|
||||
<Link to="/" className="mt-2">
|
||||
<Button variant="primary" icon={faHouse}>
|
||||
<Button variant="primary" icon={HouseIcon}>
|
||||
Back to home
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faPlus } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import { MediaFilesApi, PromptsApi } from "@storyteller/api";
|
||||
|
||||
interface BlankCardProps {
|
||||
@@ -55,7 +54,7 @@ function CardThumbnail(props: SceneSplashCardProps) {
|
||||
return (
|
||||
<div className="flex aspect-video items-center justify-center bg-white/[0.02]">
|
||||
<div className="flex h-9 w-9 items-center justify-center rounded-full bg-white/5 text-white/55 transition-colors group-hover:bg-primary/15 group-hover:text-primary">
|
||||
<FontAwesomeIcon icon={faPlus} className="text-sm" />
|
||||
<PlusIcon className="text-sm" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import { faCoins } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { CoinsIcon } from "lucide-react";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { BillingApi, UsersApi } from "@storyteller/api";
|
||||
import Seo from "../../components/seo";
|
||||
@@ -77,7 +76,7 @@ const BuyMoreCreditsCta = ({ onClick }: { onClick: () => void }) => (
|
||||
className="mt-3 gap-2 rounded-full border border-white/[0.1] bg-white/[0.06] hover:bg-white/[0.1] px-5 py-2 h-11 text-[14px] font-semibold text-white"
|
||||
onClick={onClick}
|
||||
>
|
||||
<FontAwesomeIcon icon={faCoins} className="text-primary text-[13px]" />
|
||||
<CoinsIcon className="text-primary text-[13px]" />
|
||||
Buy more credits
|
||||
</Button>
|
||||
</div>
|
||||
@@ -191,10 +190,9 @@ const Pricing = () => {
|
||||
className="mt-4 gap-2 rounded-full border border-white/[0.1] bg-white/[0.06] hover:bg-white/[0.1] px-5 py-2 h-11 text-[14px] font-semibold text-white"
|
||||
onClick={() => setCreditsModalOpen(true)}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faCoins}
|
||||
className="text-primary text-[13px]"
|
||||
/>
|
||||
<CoinsIcon
|
||||
|
||||
className="text-primary text-[13px]" />
|
||||
Buy Credits
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -6,13 +6,8 @@ import {
|
||||
type ReferralCodeEntry,
|
||||
} from "@storyteller/api";
|
||||
import { LoadingSpinner } from "@storyteller/ui-loading-spinner";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faCheck,
|
||||
faCopy,
|
||||
faPlus,
|
||||
faTrashAlt,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { CheckIcon, CopyIcon, PlusIcon, Trash2Icon } from "lucide-react";
|
||||
import { DynamicIcon } from "@storyteller/icons";
|
||||
import { useSession } from "../../lib/session";
|
||||
import { toast } from "../../components/toast/toast";
|
||||
import Seo from "../../components/seo";
|
||||
@@ -252,7 +247,7 @@ export default function Referrals() {
|
||||
disabled={submitDisabled}
|
||||
className="inline-flex h-11 items-center justify-center gap-2 rounded-full bg-primary hover:bg-primary-600 px-6 text-[14px] font-semibold text-white transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
>
|
||||
<FontAwesomeIcon icon={faPlus} className="text-[12px]" />
|
||||
<PlusIcon className="text-[12px]" />
|
||||
{creating ? "Creating…" : "Create"}
|
||||
</button>
|
||||
</div>
|
||||
@@ -357,7 +352,7 @@ function ReferralCodeRow({
|
||||
className="h-9 w-9 inline-flex items-center justify-center rounded-full text-white/45 hover:text-white hover:bg-white/[0.06] transition-colors"
|
||||
title="Delete referral code"
|
||||
>
|
||||
<FontAwesomeIcon icon={faTrashAlt} className="text-[13px]" />
|
||||
<Trash2Icon className="text-[13px]" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -393,8 +388,8 @@ function CopyableLink({ value }: { value: string }) {
|
||||
onClick={handleCopy}
|
||||
className="inline-flex h-11 items-center gap-2 rounded-full bg-white/[0.06] hover:bg-white/[0.1] border border-white/[0.1] px-4 text-xs font-semibold text-white/85 transition-all"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={copied ? faCheck : faCopy}
|
||||
<DynamicIcon
|
||||
icon={copied ? CheckIcon : CopyIcon}
|
||||
className="text-[12px]"
|
||||
/>
|
||||
{copied ? "Copied" : "Copy"}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import {
|
||||
faEye,
|
||||
faEyeSlash,
|
||||
faSpinnerThird,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { EyeIcon, EyeOffIcon, LoaderCircleIcon } from "lucide-react";
|
||||
import { DynamicIcon } from "@storyteller/icons";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { Input } from "@storyteller/ui-input";
|
||||
import { useState } from "react";
|
||||
@@ -30,10 +26,9 @@ const SetPassword = () => {
|
||||
if (!authChecked) {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-[#101014]">
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinnerThird}
|
||||
className="animate-spin text-4xl text-primary/80"
|
||||
/>
|
||||
<LoaderCircleIcon
|
||||
|
||||
className="animate-spin text-4xl text-primary/80" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -134,7 +129,7 @@ const SetPassword = () => {
|
||||
className="absolute right-4 top-1/2 -translate-y-1/2 text-white/30 hover:text-white/60 transition-colors"
|
||||
tabIndex={-1}
|
||||
>
|
||||
<FontAwesomeIcon icon={showPassword ? faEyeSlash : faEye} />
|
||||
<DynamicIcon icon={showPassword ? EyeOffIcon : EyeIcon} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -157,8 +152,8 @@ const SetPassword = () => {
|
||||
className="absolute right-4 top-1/2 -translate-y-1/2 text-white/30 hover:text-white/60 transition-colors"
|
||||
tabIndex={-1}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={showConfirmPassword ? faEyeSlash : faEye}
|
||||
<DynamicIcon
|
||||
icon={showConfirmPassword ? EyeOffIcon : EyeIcon}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
@@ -171,10 +166,9 @@ const SetPassword = () => {
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isLoading ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinnerThird}
|
||||
className="animate-spin"
|
||||
/>
|
||||
<LoaderCircleIcon
|
||||
|
||||
className="animate-spin" />
|
||||
) : (
|
||||
"Set password"
|
||||
)}
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
import { Link } from "react-router-dom";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faCircleQuestion,
|
||||
faEnvelope,
|
||||
faChevronRight,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { faDiscord } from "@fortawesome/free-brands-svg-icons";
|
||||
import type { IconDefinition } from "@fortawesome/fontawesome-svg-core";
|
||||
import { ChevronRightIcon, CircleHelpIcon, MailIcon } from "lucide-react";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import { DynamicIcon, DiscordIcon } from "@storyteller/icons";
|
||||
import Seo from "../../components/seo";
|
||||
import { SOCIAL_LINKS, SUPPORT_EMAIL } from "../../config/links";
|
||||
|
||||
type SupportRow = {
|
||||
icon: IconDefinition;
|
||||
icon: LucideIcon;
|
||||
title: string;
|
||||
desc: string;
|
||||
to?: string;
|
||||
@@ -22,7 +17,7 @@ type SupportRow = {
|
||||
|
||||
const ROWS: SupportRow[] = [
|
||||
{
|
||||
icon: faDiscord,
|
||||
icon: DiscordIcon,
|
||||
title: "Join our Discord",
|
||||
desc: "The fastest way to reach us. Quick replies from the team and credit refunds if something goes wrong.",
|
||||
href: SOCIAL_LINKS.DISCORD,
|
||||
@@ -30,13 +25,13 @@ const ROWS: SupportRow[] = [
|
||||
iconColorClass: "text-white",
|
||||
},
|
||||
{
|
||||
icon: faEnvelope,
|
||||
icon: MailIcon,
|
||||
title: "Email us",
|
||||
desc: SUPPORT_EMAIL,
|
||||
href: `mailto:${SUPPORT_EMAIL}`,
|
||||
},
|
||||
{
|
||||
icon: faCircleQuestion,
|
||||
icon: CircleHelpIcon,
|
||||
title: "Browse the FAQ",
|
||||
desc: "Answers to common questions about ArtCraft.",
|
||||
to: "/faq",
|
||||
@@ -86,16 +81,15 @@ const SupportLink = ({ row }: { row: SupportRow }) => {
|
||||
row.iconBgClass ?? "bg-white/[0.04] border-white/[0.06]"
|
||||
} ${row.iconColorClass ?? "text-white/70 group-hover:text-white"}`}
|
||||
>
|
||||
<FontAwesomeIcon icon={row.icon} />
|
||||
<DynamicIcon icon={row.icon} />
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="text-[15px] font-medium text-white">{row.title}</div>
|
||||
<div className="text-sm text-white/60">{row.desc}</div>
|
||||
</div>
|
||||
<FontAwesomeIcon
|
||||
icon={faChevronRight}
|
||||
className="text-xs text-white/30 group-hover:text-white/60 transition-colors"
|
||||
/>
|
||||
<ChevronRightIcon
|
||||
|
||||
className="text-xs text-white/30 group-hover:text-white/60 transition-colors" />
|
||||
</>
|
||||
);
|
||||
|
||||
|
||||
+5
-10
@@ -1,9 +1,5 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faSparkles,
|
||||
faTriangleExclamation,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { SparklesIcon, TriangleAlertIcon } from "lucide-react";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { FolderNameDialog } from "@storyteller/ui-gallery-modal";
|
||||
import {
|
||||
@@ -195,7 +191,7 @@ export function VideoEditorProjectsLanding({
|
||||
</p>
|
||||
<Button
|
||||
variant="primary"
|
||||
icon={faSparkles}
|
||||
icon={SparklesIcon}
|
||||
onClick={onNewProject}
|
||||
className="mt-8 h-12 rounded-full px-6 text-base font-semibold"
|
||||
>
|
||||
@@ -206,10 +202,9 @@ export function VideoEditorProjectsLanding({
|
||||
|
||||
{status === "error" && (
|
||||
<div className="flex flex-col items-center justify-center gap-4 py-28 text-center">
|
||||
<FontAwesomeIcon
|
||||
icon={faTriangleExclamation}
|
||||
className="text-3xl text-white/30"
|
||||
/>
|
||||
<TriangleAlertIcon
|
||||
|
||||
className="text-3xl text-white/30" />
|
||||
<div>
|
||||
<div className="text-lg font-medium">
|
||||
Couldn't load your projects
|
||||
|
||||
+8
-16
@@ -1,12 +1,5 @@
|
||||
import { useState } from "react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faClapperboardPlay,
|
||||
faEllipsis,
|
||||
faPen,
|
||||
faPlus,
|
||||
faTrashCan,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { ClapperboardIcon, EllipsisIcon, PenIcon, PlusIcon, Trash2Icon } from "lucide-react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { PopoverMenu } from "@storyteller/ui-popover";
|
||||
import type { ProjectMeta } from "@storyteller/ui-video-editor";
|
||||
@@ -84,10 +77,9 @@ export function VideoProjectCard({
|
||||
gradientForProject(project.id),
|
||||
)}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faClapperboardPlay}
|
||||
className="text-3xl text-white/25 transition-colors group-hover:text-white/40"
|
||||
/>
|
||||
<ClapperboardIcon
|
||||
|
||||
className="text-3xl text-white/25 transition-colors group-hover:text-white/40" />
|
||||
</div>
|
||||
)}
|
||||
<div className="absolute inset-0 flex items-end bg-gradient-to-t from-black/70 to-transparent p-3 opacity-0 transition-opacity duration-200 group-hover:opacity-100">
|
||||
@@ -115,7 +107,7 @@ export function VideoProjectCard({
|
||||
align="end"
|
||||
mode="default"
|
||||
triggerIcon={
|
||||
<FontAwesomeIcon icon={faEllipsis} className="text-base-fg" />
|
||||
<EllipsisIcon className="text-base-fg" />
|
||||
}
|
||||
buttonClassName="h-7 w-7 p-0 rounded-full bg-ui-controls/60 hover:bg-ui-controls/90 text-base-fg border border-ui-controls-border"
|
||||
panelClassName="w-max min-w-44 p-1"
|
||||
@@ -132,7 +124,7 @@ export function VideoProjectCard({
|
||||
onRename();
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={faPen} className="w-3.5" />
|
||||
<PenIcon className="w-3.5" />
|
||||
Rename
|
||||
</button>
|
||||
<button
|
||||
@@ -144,7 +136,7 @@ export function VideoProjectCard({
|
||||
onDelete();
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={faTrashCan} className="w-3.5" />
|
||||
<Trash2Icon className="w-3.5" />
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
@@ -175,7 +167,7 @@ export function NewProjectTile({ onClick }: { onClick: () => void }) {
|
||||
className="group flex h-full w-full cursor-pointer flex-col items-center justify-center gap-2.5 rounded-xl border border-dashed border-white/20 bg-white/[0.02] py-10 transition-colors hover:border-white/40 hover:bg-white/[0.05] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/60"
|
||||
>
|
||||
<span className="flex h-9 w-9 items-center justify-center rounded-full bg-white/5 text-white/55 transition-colors group-hover:bg-primary/15 group-hover:text-primary">
|
||||
<FontAwesomeIcon icon={faPlus} />
|
||||
<PlusIcon />
|
||||
</span>
|
||||
<span className="text-sm font-medium text-white/70">New project</span>
|
||||
</button>
|
||||
|
||||
@@ -1,14 +1,5 @@
|
||||
import {
|
||||
faCheckCircle,
|
||||
faDownload,
|
||||
faDesktop,
|
||||
faImage,
|
||||
faVideo,
|
||||
faWandMagicSparkles,
|
||||
faCube,
|
||||
faArrowRight,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { ArrowRightIcon, BoxIcon, CircleCheckIcon, DownloadIcon, ImageIcon, MonitorIcon, VideoIcon, WandSparklesIcon } from "lucide-react";
|
||||
import { DynamicIcon } from "@storyteller/icons";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { Link } from "react-router-dom";
|
||||
import { isMobile, isMacOs } from "react-device-detect";
|
||||
@@ -36,10 +27,9 @@ const Welcome = () => {
|
||||
{/* Success Header */}
|
||||
<div className="text-center mb-12">
|
||||
<div className="inline-flex items-center gap-3 bg-primary/10 border border-primary/30 rounded-full px-6 py-3 mb-6">
|
||||
<FontAwesomeIcon
|
||||
icon={faCheckCircle}
|
||||
className="text-xl text-primary"
|
||||
/>
|
||||
<CircleCheckIcon
|
||||
|
||||
className="text-xl text-primary" />
|
||||
<span className="text-white font-medium">
|
||||
Account created successfully!
|
||||
</span>
|
||||
@@ -69,10 +59,9 @@ const Welcome = () => {
|
||||
<div className="bg-[#1A1A1E] border border-white/10 rounded-3xl p-8 md:p-10">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<div className="w-10 h-10 rounded-full bg-primary/20 flex items-center justify-center">
|
||||
<FontAwesomeIcon
|
||||
icon={faWandMagicSparkles}
|
||||
className="text-primary text-lg"
|
||||
/>
|
||||
<WandSparklesIcon
|
||||
|
||||
className="text-primary text-lg" />
|
||||
</div>
|
||||
<h2 className="text-2xl font-medium text-white">
|
||||
Start creating right here
|
||||
@@ -83,31 +72,30 @@ const Welcome = () => {
|
||||
</p>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||||
{[
|
||||
{ to: "/create-image", icon: faImage, label: "Image" },
|
||||
{ to: "/create-video", icon: faVideo, label: "Video" },
|
||||
{ to: "/create-image", icon: ImageIcon, label: "Image" },
|
||||
{ to: "/create-video", icon: VideoIcon, label: "Video" },
|
||||
{
|
||||
to: "/background-change",
|
||||
icon: faWandMagicSparkles,
|
||||
icon: WandSparklesIcon,
|
||||
label: "BG Change",
|
||||
},
|
||||
{ to: "/edit-3d", icon: faCube, label: "Edit 3D" },
|
||||
{ to: "/edit-3d", icon: BoxIcon, label: "Edit 3D" },
|
||||
].map((item) => (
|
||||
<Link
|
||||
key={item.to}
|
||||
to={item.to}
|
||||
className="group flex items-center gap-3 bg-[#252529] hover:bg-[#2D2D32] border border-white/[0.06] hover:border-white/[0.12] rounded-xl px-4 py-3 transition-all"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
<DynamicIcon
|
||||
icon={item.icon}
|
||||
className="text-primary text-base shrink-0"
|
||||
/>
|
||||
<span className="text-white/90 font-medium flex-1">
|
||||
{item.label}
|
||||
</span>
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowRight}
|
||||
className="text-white/30 text-[11px] transition-transform group-hover:translate-x-0.5"
|
||||
/>
|
||||
<ArrowRightIcon
|
||||
|
||||
className="text-white/30 text-[11px] transition-transform group-hover:translate-x-0.5" />
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
@@ -119,10 +107,9 @@ const Welcome = () => {
|
||||
<div className="bg-[#1A1A1E] border border-white/10 rounded-3xl p-8 md:p-10">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<div className="w-10 h-10 rounded-full bg-primary/20 flex items-center justify-center">
|
||||
<FontAwesomeIcon
|
||||
icon={faDesktop}
|
||||
className="text-primary text-lg"
|
||||
/>
|
||||
<MonitorIcon
|
||||
|
||||
className="text-primary text-lg" />
|
||||
</div>
|
||||
<h2 className="text-2xl font-medium text-white">
|
||||
Want the desktop app too?
|
||||
@@ -166,7 +153,7 @@ const Welcome = () => {
|
||||
href={downloadUrl}
|
||||
className="rounded-full bg-white text-black hover:bg-gray-100 dark:bg-white dark:text-black dark:hover:bg-gray-200 px-6 py-2.5 text-sm font-bold"
|
||||
>
|
||||
<FontAwesomeIcon icon={faDownload} className="mr-2" />
|
||||
<DownloadIcon className="mr-2" />
|
||||
Download for {isMacOs ? "Mac" : "Windows"}
|
||||
</Button>
|
||||
<div className="flex gap-4 text-sm font-medium text-white/30">
|
||||
@@ -193,7 +180,7 @@ const Welcome = () => {
|
||||
{isMobile && (
|
||||
<div className="bg-[#431407] border border-orange-900/50 rounded-2xl p-6 text-orange-200 text-sm leading-relaxed">
|
||||
<div className="flex items-center justify-center mb-3 text-orange-400">
|
||||
<FontAwesomeIcon icon={faDesktop} className="text-2xl" />
|
||||
<MonitorIcon className="text-2xl" />
|
||||
</div>
|
||||
ArtCraft is a powerful desktop experience. <br />
|
||||
Please head to your computer to download and install.
|
||||
|
||||
@@ -511,3 +511,18 @@
|
||||
.ac-sheet-overlay[data-state="closed"] {
|
||||
animation: ac-sheet-fade 0.2s ease-in reverse forwards;
|
||||
}
|
||||
|
||||
/* Lucide icons ship with a fixed 24px default; size them relative to the
|
||||
surrounding font so they scale with text like the previous icons did.
|
||||
1.2em (not 1em) because Lucide draws glyphs on a 20px grid inside a 24px
|
||||
viewBox — the extra 20% makes the visible mark fill a full 1em, matching
|
||||
the old solid-glyph sizing. Explicit w-/h-/size- utilities still win
|
||||
(the utilities layer is emitted later). */
|
||||
@layer base {
|
||||
.lucide {
|
||||
display: inline-block;
|
||||
width: 1.2em;
|
||||
height: 1.2em;
|
||||
vertical-align: -0.2em;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,6 +132,9 @@
|
||||
},
|
||||
{
|
||||
"path": "../../libs/api/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../../libs/icons/tsconfig.lib.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -98,6 +98,9 @@
|
||||
{
|
||||
"path": "../../libs/api"
|
||||
},
|
||||
{
|
||||
"path": "../../libs/icons"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
},
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Link } from "react-router-dom";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faDesktop, faXmark } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { MonitorIcon, XIcon } from "lucide-react";
|
||||
import { useDismissibleBanner } from "../../lib/useDismissibleBanner";
|
||||
|
||||
const DISMISS_KEY = "artcraft-app-download-cta-dismissed-v1";
|
||||
@@ -13,7 +12,7 @@ export function AppDownloadCta() {
|
||||
return (
|
||||
<div className="glass animate-fade-in-up mb-2 hidden sm:flex w-fit mx-auto items-center gap-3 rounded-xl px-4 py-2.5 text-sm text-white">
|
||||
<div className="flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-full bg-primary/20 text-primary">
|
||||
<FontAwesomeIcon icon={faDesktop} className="h-3.5 w-3.5" />
|
||||
<MonitorIcon className="h-3.5 w-3.5" />
|
||||
</div>
|
||||
<span className="text-white/90">
|
||||
Get more out of ArtCraft on desktop - 3D Stage, 2D Canvas & more.
|
||||
@@ -29,7 +28,7 @@ export function AppDownloadCta() {
|
||||
className="ml-1 text-white/40 transition-colors hover:text-white/80"
|
||||
aria-label="Dismiss"
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} className="h-3 w-3" />
|
||||
<XIcon className="h-3 w-3" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -6,8 +6,7 @@ import {
|
||||
getReferralUsername,
|
||||
getReferrer,
|
||||
} from "@storyteller/common";
|
||||
import { faSpinnerThird } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { LoaderCircleIcon } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
interface GoogleLoginButtonProps {
|
||||
@@ -61,10 +60,9 @@ export function GoogleLoginButton({
|
||||
{/* Custom visual button - shown behind */}
|
||||
<div className="absolute inset-0 w-full h-full flex items-center justify-center gap-3 bg-white text-black rounded-lg font-medium border border-gray-200 hover:bg-gray-50 transition-colors">
|
||||
{isLoading ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinnerThird}
|
||||
className="animate-spin text-lg text-gray-600"
|
||||
/>
|
||||
<LoaderCircleIcon
|
||||
|
||||
className="animate-spin text-lg text-gray-600" />
|
||||
) : (
|
||||
<>
|
||||
<GoogleIcon />
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faCheck,
|
||||
faEye,
|
||||
faEyeSlash,
|
||||
faSpinnerThird,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { CheckIcon, EyeIcon, EyeOffIcon, LoaderCircleIcon } from "lucide-react";
|
||||
import { DynamicIcon } from "@storyteller/icons";
|
||||
import { Modal } from "@storyteller/ui-modal";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { Input } from "@storyteller/ui-input";
|
||||
@@ -79,10 +74,9 @@ export function AuthGateModal({
|
||||
className="flex items-start gap-3 text-[14px] text-white/75"
|
||||
>
|
||||
<span className="mt-[2px] flex h-4 w-4 shrink-0 items-center justify-center rounded-full bg-primary/15">
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className="text-[9px] text-primary"
|
||||
/>
|
||||
<CheckIcon
|
||||
|
||||
className="text-[9px] text-primary" />
|
||||
</span>
|
||||
<span>{perk}</span>
|
||||
</li>
|
||||
@@ -223,7 +217,7 @@ function InlineLoginForm({ onSuccess }: { onSuccess: () => void }) {
|
||||
className="absolute right-4 top-1/2 -translate-y-1/2 text-white/30 transition-colors hover:text-white/60"
|
||||
tabIndex={-1}
|
||||
>
|
||||
<FontAwesomeIcon icon={showPassword ? faEyeSlash : faEye} />
|
||||
<DynamicIcon icon={showPassword ? EyeOffIcon : EyeIcon} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -235,7 +229,7 @@ function InlineLoginForm({ onSuccess }: { onSuccess: () => void }) {
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isLoading ? (
|
||||
<FontAwesomeIcon icon={faSpinnerThird} className="animate-spin" />
|
||||
<LoaderCircleIcon className="animate-spin" />
|
||||
) : (
|
||||
"Log in"
|
||||
)}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import {
|
||||
faEye,
|
||||
faEyeSlash,
|
||||
faSpinnerThird,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { EyeIcon, EyeOffIcon, LoaderCircleIcon } from "lucide-react";
|
||||
import { DynamicIcon } from "@storyteller/icons";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { Input } from "@storyteller/ui-input";
|
||||
import { useState } from "react";
|
||||
@@ -156,7 +152,7 @@ export const SignupForm = ({
|
||||
className="absolute right-4 top-1/2 -translate-y-1/2 text-white/30 hover:text-white/60 transition-colors"
|
||||
tabIndex={-1}
|
||||
>
|
||||
<FontAwesomeIcon icon={showPassword ? faEyeSlash : faEye} />
|
||||
<DynamicIcon icon={showPassword ? EyeOffIcon : EyeIcon} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -168,7 +164,7 @@ export const SignupForm = ({
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isLoading ? (
|
||||
<FontAwesomeIcon icon={faSpinnerThird} className="animate-spin" />
|
||||
<LoaderCircleIcon className="animate-spin" />
|
||||
) : (
|
||||
"Create Account"
|
||||
)}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { useState } from "react";
|
||||
import { Modal } from "@storyteller/ui-modal";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { faCoins } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { CoinsIcon } from "lucide-react";
|
||||
import { BillingApi } from "@storyteller/api";
|
||||
import { toast } from "./toast/toast";
|
||||
|
||||
@@ -86,10 +85,9 @@ export function CreditsModal({ isOpen, onClose }: CreditsModalProps) {
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-2.5">
|
||||
<FontAwesomeIcon
|
||||
icon={faCoins}
|
||||
className="text-2xl text-primary"
|
||||
/>
|
||||
<CoinsIcon
|
||||
|
||||
className="text-2xl text-primary" />
|
||||
<span className="text-3xl font-bold tracking-tight text-white">
|
||||
{pack.total.toLocaleString()}
|
||||
</span>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { faDiscord } from "@fortawesome/free-brands-svg-icons";
|
||||
import { DiscordIcon } from "@storyteller/icons";
|
||||
import { SOCIAL_LINKS } from "../../config/links";
|
||||
|
||||
interface DiscordButtonProps {
|
||||
@@ -18,7 +18,7 @@ export const DiscordButton = ({
|
||||
return (
|
||||
<Button
|
||||
className={`rounded-full relative z-10 ${sizeClasses} font-semibold transition-all duration-300 shadow-lg bg-white text-black hover:bg-gray-200 ${className}`}
|
||||
icon={faDiscord}
|
||||
icon={DiscordIcon}
|
||||
as="link"
|
||||
href={SOCIAL_LINKS.DISCORD}
|
||||
target="_blank"
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import {
|
||||
faApple,
|
||||
faWindows,
|
||||
faLinux,
|
||||
} from "@fortawesome/free-brands-svg-icons";
|
||||
import { AppleIcon, LinuxIcon, WindowsIcon } from "@storyteller/icons";
|
||||
import { isMobile, isWindows, isMacOs } from "react-device-detect";
|
||||
import {
|
||||
DOWNLOAD_LINKS,
|
||||
@@ -32,9 +28,9 @@ export const DownloadButton = ({ className = "" }: DownloadButtonProps) => {
|
||||
|
||||
const getIcon = () => {
|
||||
if (isMobile) return undefined;
|
||||
if (isWindows) return faWindows;
|
||||
if (isMacOs) return faApple;
|
||||
if (isLinux) return faLinux;
|
||||
if (isWindows) return WindowsIcon;
|
||||
if (isMacOs) return AppleIcon;
|
||||
if (isLinux) return LinuxIcon;
|
||||
return undefined;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
import {
|
||||
faTimes,
|
||||
faEnvelope,
|
||||
faArrowLeft,
|
||||
faArrowRight,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { ArrowLeftIcon, ArrowRightIcon, MailIcon, XIcon } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
@@ -71,7 +65,7 @@ export const DownloadModal = ({ isOpen, onClose }: DownloadModalProps) => {
|
||||
onClick={onClose}
|
||||
className="absolute top-4 right-4 w-8 h-8 flex items-center justify-center rounded-full bg-white/5 hover:bg-white/10 text-white/40 hover:text-white/80 transition-colors z-20"
|
||||
>
|
||||
<FontAwesomeIcon icon={faTimes} />
|
||||
<XIcon />
|
||||
</button>
|
||||
|
||||
{!USE_WEBAPP_FOR_APP_FEATURES && view === "signup" && (
|
||||
@@ -79,7 +73,7 @@ export const DownloadModal = ({ isOpen, onClose }: DownloadModalProps) => {
|
||||
onClick={() => setView("menu")}
|
||||
className="absolute top-4 left-4 w-8 h-8 flex items-center justify-center rounded-full bg-white/5 hover:bg-white/10 text-white/40 hover:text-white/80 transition-colors z-20"
|
||||
>
|
||||
<FontAwesomeIcon icon={faArrowLeft} />
|
||||
<ArrowLeftIcon />
|
||||
</button>
|
||||
)}
|
||||
|
||||
@@ -117,9 +111,9 @@ export const DownloadModal = ({ isOpen, onClose }: DownloadModalProps) => {
|
||||
href={webappUrl("/signup")}
|
||||
className="rounded-full w-full bg-white/5 hover:bg-white/10 text-white border-white/10 justify-center gap-3 font-medium h-12"
|
||||
>
|
||||
<FontAwesomeIcon icon={faEnvelope} className="text-lg" />
|
||||
<MailIcon className="text-lg" />
|
||||
Sign up
|
||||
<FontAwesomeIcon icon={faArrowRight} className="text-xs" />
|
||||
<ArrowRightIcon className="text-xs" />
|
||||
</Button>
|
||||
</div>
|
||||
) : view === "menu" ? (
|
||||
@@ -128,7 +122,7 @@ export const DownloadModal = ({ isOpen, onClose }: DownloadModalProps) => {
|
||||
className="rounded-full w-full bg-white/5 hover:bg-white/10 text-white border-white/10 justify-center gap-3 font-medium h-12"
|
||||
onClick={() => setView("signup")}
|
||||
>
|
||||
<FontAwesomeIcon icon={faEnvelope} className="text-lg" />
|
||||
<MailIcon className="text-lg" />
|
||||
Sign up with Email
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,4 @@
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faTiktok,
|
||||
faDiscord,
|
||||
faYoutube,
|
||||
faGithubAlt,
|
||||
faLinkedinIn,
|
||||
faInstagram,
|
||||
faRedditAlien,
|
||||
} from "@fortawesome/free-brands-svg-icons";
|
||||
import { DiscordIcon, GithubAltIcon, InstagramIcon, LinkedinInIcon, RedditAlienIcon, TiktokIcon, YoutubeIcon } from "@storyteller/icons";
|
||||
import {
|
||||
SOCIAL_LINKS,
|
||||
USE_WEBAPP_FOR_APP_FEATURES,
|
||||
@@ -28,37 +19,37 @@ const navigation = {
|
||||
{
|
||||
name: "Discord",
|
||||
href: SOCIAL_LINKS.DISCORD,
|
||||
icon: (props: any) => <FontAwesomeIcon icon={faDiscord} {...props} />,
|
||||
icon: (props: any) => <DiscordIcon {...props} />,
|
||||
},
|
||||
{
|
||||
name: "YouTube",
|
||||
href: SOCIAL_LINKS.YOUTUBE,
|
||||
icon: (props: any) => <FontAwesomeIcon icon={faYoutube} {...props} />,
|
||||
icon: (props: any) => <YoutubeIcon {...props} />,
|
||||
},
|
||||
{
|
||||
name: "TikTok",
|
||||
href: SOCIAL_LINKS.TIKTOK,
|
||||
icon: (props: any) => <FontAwesomeIcon icon={faTiktok} {...props} />,
|
||||
icon: (props: any) => <TiktokIcon {...props} />,
|
||||
},
|
||||
{
|
||||
name: "GitHub",
|
||||
href: SOCIAL_LINKS.GITHUB,
|
||||
icon: (props: any) => <FontAwesomeIcon icon={faGithubAlt} {...props} />,
|
||||
icon: (props: any) => <GithubAltIcon {...props} />,
|
||||
},
|
||||
{
|
||||
name: "Instagram",
|
||||
href: SOCIAL_LINKS.INSTAGRAM,
|
||||
icon: (props: any) => <FontAwesomeIcon icon={faInstagram} {...props} />,
|
||||
icon: (props: any) => <InstagramIcon {...props} />,
|
||||
},
|
||||
{
|
||||
name: "LinkedIn",
|
||||
href: SOCIAL_LINKS.LINKEDIN,
|
||||
icon: (props: any) => <FontAwesomeIcon icon={faLinkedinIn} {...props} />,
|
||||
icon: (props: any) => <LinkedinInIcon {...props} />,
|
||||
},
|
||||
{
|
||||
name: "Reddit",
|
||||
href: SOCIAL_LINKS.REDDIT,
|
||||
icon: (props: any) => <FontAwesomeIcon icon={faRedditAlien} {...props} />,
|
||||
icon: (props: any) => <RedditAlienIcon {...props} />,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -68,7 +59,7 @@ export default function Example() {
|
||||
<footer className="bg-transparent z-20">
|
||||
<div className="mx-auto max-w-7xl overflow-hidden px-4 sm:px-6 py-16 sm:py-16 lg:px-8 flex flex-col gap-12 items-center">
|
||||
{/* <Button
|
||||
icon={faArrowDownToLine}
|
||||
icon={ArrowDownToLineIcon}
|
||||
className="w-fit"
|
||||
onClick={() => window.open("/download", "_self")}
|
||||
>
|
||||
|
||||
+8
-11
@@ -1,10 +1,8 @@
|
||||
import { type ReactNode } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faSpinnerThird,
|
||||
type IconDefinition,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { LoaderCircleIcon } from "lucide-react";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import { DynamicIcon } from "@storyteller/icons";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { type PopoverItem } from "@storyteller/ui-popover";
|
||||
import Seo from "../../components/seo";
|
||||
@@ -19,7 +17,7 @@ interface CreateMediaPageShellProps {
|
||||
authChecked: boolean;
|
||||
isLoggedIn: boolean;
|
||||
// Unauthenticated state
|
||||
heroIcon: IconDefinition;
|
||||
heroIcon: LucideIcon;
|
||||
heroTitle: string;
|
||||
heroSubtitle: string;
|
||||
// Content
|
||||
@@ -58,10 +56,9 @@ export function CreateMediaPageShell({
|
||||
if (!authChecked) {
|
||||
return (
|
||||
<div className="flex h-screen items-center justify-center bg-[#101014]">
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinnerThird}
|
||||
className="animate-spin text-4xl text-primary/80"
|
||||
/>
|
||||
<LoaderCircleIcon
|
||||
|
||||
className="animate-spin text-4xl text-primary/80" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -90,7 +87,7 @@ export function CreateMediaPageShell({
|
||||
<div className="h-[600px] w-[600px] rounded-full bg-gradient-to-br from-primary/30 via-blue-500/20 to-teal-400/10 opacity-40 blur-[120px]" />
|
||||
</div>
|
||||
<div className="relative z-10 flex min-h-screen flex-col items-center justify-center px-4">
|
||||
<FontAwesomeIcon
|
||||
<DynamicIcon
|
||||
icon={heroIcon}
|
||||
className="mb-6 text-5xl text-white/20"
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { memo } from "react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faCircleExclamation, faXmark } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { CircleAlertIcon, XIcon } from "lucide-react";
|
||||
import { getCreatorIconPathForModelId } from "@storyteller/model-list";
|
||||
|
||||
export interface FailedCardProps {
|
||||
@@ -26,10 +25,9 @@ export const FailedCard = memo(function FailedCard({
|
||||
return (
|
||||
<div className="relative aspect-square w-full overflow-hidden rounded-lg bg-red-500/10">
|
||||
<div className="flex h-full flex-col items-center justify-center gap-2 px-4 sm:px-6">
|
||||
<FontAwesomeIcon
|
||||
icon={faCircleExclamation}
|
||||
className="text-2xl text-red-400"
|
||||
/>
|
||||
<CircleAlertIcon
|
||||
|
||||
className="text-2xl text-red-400" />
|
||||
<span className="text-center text-xs font-medium text-red-400">
|
||||
{failureReason || "Generation failed"}
|
||||
</span>
|
||||
@@ -42,7 +40,7 @@ export const FailedCard = memo(function FailedCard({
|
||||
onClick={() => onDismiss(id)}
|
||||
className="mt-1 flex items-center gap-1.5 rounded-md bg-white/5 px-3 py-1.5 text-xs text-white/50 transition-colors hover:bg-white/10 hover:text-white/70"
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} />
|
||||
<XIcon />
|
||||
Dismiss
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,16 +1,7 @@
|
||||
import { memo, useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faArrowDownToLine,
|
||||
faArrowRotateRight,
|
||||
faCheck,
|
||||
faCube,
|
||||
faImage,
|
||||
faLink,
|
||||
faSpinnerThird,
|
||||
faVideo,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { ArrowDownToLineIcon, BoxIcon, CheckIcon, ImageIcon, LinkIcon, LoaderCircleIcon, RotateCwIcon, VideoIcon } from "lucide-react";
|
||||
import { DynamicIcon } from "@storyteller/icons";
|
||||
import { PLACEHOLDER_IMAGES } from "@storyteller/common";
|
||||
import { Tooltip } from "@storyteller/ui-tooltip";
|
||||
import { toast } from "../toast/toast";
|
||||
@@ -197,7 +188,7 @@ export const GalleryCard = memo(function GalleryCard({
|
||||
? getCreatorIconPathForModelId(item.modelId)
|
||||
: null;
|
||||
|
||||
const mediaIcon = item.mediaClass === "video" ? faVideo : is3D ? faCube : faImage;
|
||||
const mediaIcon = item.mediaClass === "video" ? VideoIcon : is3D ? BoxIcon : ImageIcon;
|
||||
const mediaLabel = item.mediaClass === "video" ? "Video" : is3D ? "3D" : "Image";
|
||||
|
||||
const handleLoad = useCallback(
|
||||
@@ -326,10 +317,9 @@ export const GalleryCard = memo(function GalleryCard({
|
||||
{retrying ? (
|
||||
<>
|
||||
<div className="flex h-full w-full flex-col items-center justify-center gap-2">
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinnerThird}
|
||||
className="animate-spin text-lg text-white/30"
|
||||
/>
|
||||
<LoaderCircleIcon
|
||||
|
||||
className="animate-spin text-lg text-white/30" />
|
||||
<span className="text-[10px] text-white/30">
|
||||
Loading thumbnail…
|
||||
</span>
|
||||
@@ -357,7 +347,7 @@ export const GalleryCard = memo(function GalleryCard({
|
||||
/>
|
||||
) : (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<FontAwesomeIcon icon={mediaIcon} className="text-xl text-white/20" />
|
||||
<DynamicIcon icon={mediaIcon} className="text-xl text-white/20" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -366,7 +356,7 @@ export const GalleryCard = memo(function GalleryCard({
|
||||
<div className="pointer-events-none absolute inset-x-0 bottom-0 flex items-end justify-between gap-2 bg-gradient-to-t from-black/70 to-transparent px-2 pb-2 pt-6 opacity-0 transition-opacity group-hover:opacity-100">
|
||||
<div className="pointer-events-auto flex min-w-0 flex-wrap items-center gap-1.5">
|
||||
<div className="flex items-center gap-1.5 rounded-lg bg-black/60 px-2.5 py-1 text-xs font-medium text-white/90">
|
||||
<FontAwesomeIcon icon={mediaIcon} className="text-[10px]" />
|
||||
<DynamicIcon icon={mediaIcon} className="text-[10px]" />
|
||||
{mediaLabel}
|
||||
</div>
|
||||
{modelDisplayName && modelIconPath && (
|
||||
@@ -391,8 +381,8 @@ export const GalleryCard = memo(function GalleryCard({
|
||||
disabled={isRecreating}
|
||||
aria-label="Recreate"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={isRecreating ? faSpinnerThird : faArrowRotateRight}
|
||||
<DynamicIcon
|
||||
icon={isRecreating ? LoaderCircleIcon : RotateCwIcon}
|
||||
className={`text-sm ${isRecreating ? "animate-spin" : ""}`}
|
||||
/>
|
||||
</button>
|
||||
@@ -406,7 +396,7 @@ export const GalleryCard = memo(function GalleryCard({
|
||||
onClick={handleMakeVideo}
|
||||
aria-label="Make Video"
|
||||
>
|
||||
<FontAwesomeIcon icon={faVideo} className="text-sm" />
|
||||
<VideoIcon className="text-sm" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
)}
|
||||
@@ -417,8 +407,8 @@ export const GalleryCard = memo(function GalleryCard({
|
||||
onClick={handleShare}
|
||||
aria-label="Share"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={shareCopied ? faCheck : faLink}
|
||||
<DynamicIcon
|
||||
icon={shareCopied ? CheckIcon : LinkIcon}
|
||||
className="text-sm"
|
||||
/>
|
||||
</button>
|
||||
@@ -432,8 +422,8 @@ export const GalleryCard = memo(function GalleryCard({
|
||||
className="flex h-7 w-7 items-center justify-center rounded-md text-white/85 transition-colors hover:bg-white/15 hover:text-white disabled:opacity-60"
|
||||
aria-label="Download"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={isDownloading ? faSpinnerThird : faArrowDownToLine}
|
||||
<DynamicIcon
|
||||
icon={isDownloading ? LoaderCircleIcon : ArrowDownToLineIcon}
|
||||
className={`text-sm ${isDownloading ? "animate-spin" : ""}`}
|
||||
/>
|
||||
</button>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { memo } from "react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faSpinnerThird } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { LoaderCircleIcon } from "lucide-react";
|
||||
import { getCreatorIconPathForModelId } from "@storyteller/model-list";
|
||||
|
||||
export interface PendingCardProps {
|
||||
@@ -52,10 +51,9 @@ export const PendingCard = memo(function PendingCard({
|
||||
</div>
|
||||
)}
|
||||
<div className="absolute inset-0 flex flex-col items-center justify-center gap-2">
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinnerThird}
|
||||
className="animate-spin text-2xl text-white/20"
|
||||
/>
|
||||
<LoaderCircleIcon
|
||||
|
||||
className="animate-spin text-2xl text-white/20" />
|
||||
{progressPercent != null && (
|
||||
<span className="text-xs tabular-nums text-white/40">
|
||||
{progressPercent}%
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { isMobile, isMacOs } from "react-device-detect";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faWindows, faApple } from "@fortawesome/free-brands-svg-icons";
|
||||
import { faRocket } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { RocketIcon } from "lucide-react";
|
||||
import { DynamicIcon, AppleIcon, WindowsIcon } from "@storyteller/icons";
|
||||
import {
|
||||
DOWNLOAD_LINKS,
|
||||
DOWNLOADS_ENABLED,
|
||||
@@ -40,7 +39,7 @@ export const LandingActionButtons = ({
|
||||
as="link"
|
||||
href="/pricing"
|
||||
>
|
||||
<FontAwesomeIcon icon={faRocket} />
|
||||
<RocketIcon />
|
||||
{creditsButtonText}
|
||||
</Button>
|
||||
{DOWNLOADS_ENABLED && (
|
||||
@@ -51,7 +50,7 @@ export const LandingActionButtons = ({
|
||||
href={downloadUrl}
|
||||
onClick={onDownloadClick}
|
||||
>
|
||||
<FontAwesomeIcon icon={isMacOs ? faApple : faWindows} />
|
||||
<DynamicIcon icon={isMacOs ? AppleIcon : WindowsIcon} />
|
||||
Download for {isMacOs ? "Mac" : "Windows"}
|
||||
</Button>
|
||||
<a
|
||||
|
||||
@@ -4,22 +4,8 @@ import { Button } from "@storyteller/ui-button";
|
||||
import { toast } from "../toast/toast";
|
||||
import { Gravatar } from "@storyteller/ui-gravatar";
|
||||
import type { UserInfo } from "@storyteller/api";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faArrowDownToLine,
|
||||
faArrowRotateRight,
|
||||
faCheck,
|
||||
faCircleInfo,
|
||||
faCopy,
|
||||
faImage,
|
||||
faLink,
|
||||
faPencil,
|
||||
faSpinnerThird,
|
||||
faTrashCan,
|
||||
faUser,
|
||||
faVideo,
|
||||
faXmark,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { ArrowDownToLineIcon, CheckIcon, CopyIcon, ImageIcon, InfoIcon, LinkIcon, LoaderCircleIcon, PencilIcon, RotateCwIcon, Trash2Icon, UserIcon, VideoIcon, XIcon } from "lucide-react";
|
||||
import { DynamicIcon } from "@storyteller/icons";
|
||||
import {
|
||||
getContextImageThumbnail,
|
||||
THUMBNAIL_SIZES,
|
||||
@@ -159,7 +145,7 @@ export function LightboxDetails({
|
||||
className="absolute top-3 right-3 z-20 flex h-8 w-8 items-center justify-center rounded-full bg-black/40 text-white/70 transition-colors hover:bg-black/60 hover:text-white"
|
||||
aria-label="Close"
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} className="h-4 w-4" />
|
||||
<XIcon className="h-4 w-4" />
|
||||
</button>
|
||||
)}
|
||||
|
||||
@@ -195,7 +181,7 @@ export function LightboxDetails({
|
||||
/>
|
||||
) : (
|
||||
<div className="h-9 w-9 shrink-0 flex items-center justify-center rounded-xl bg-white/10 text-white/50 border border-white/5">
|
||||
<FontAwesomeIcon icon={faUser} />
|
||||
<UserIcon />
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col gap-1">
|
||||
@@ -213,7 +199,7 @@ export function LightboxDetails({
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2 text-xs font-medium text-white/60">
|
||||
<FontAwesomeIcon icon={faPencil} />
|
||||
<PencilIcon />
|
||||
<span>Prompt</span>
|
||||
</div>
|
||||
{promptData.text && (
|
||||
@@ -221,8 +207,8 @@ export function LightboxDetails({
|
||||
onClick={handleCopyPrompt}
|
||||
className="flex items-center gap-1.5 text-xs text-white/60 hover:text-white transition-colors"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={promptCopy.copied ? faCheck : faCopy}
|
||||
<DynamicIcon
|
||||
icon={promptCopy.copied ? CheckIcon : CopyIcon}
|
||||
className="h-3 w-3"
|
||||
/>
|
||||
<span>{promptCopy.copied ? "Copied" : "Copy"}</span>
|
||||
@@ -255,7 +241,7 @@ export function LightboxDetails({
|
||||
{promptData.contextImages && promptData.contextImages.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2 text-xs font-medium text-white/60">
|
||||
<FontAwesomeIcon icon={faImage} />
|
||||
<ImageIcon />
|
||||
<span>Reference Images</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-5 gap-2">
|
||||
@@ -287,7 +273,7 @@ export function LightboxDetails({
|
||||
{hasInfoSection && (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2 text-xs font-medium text-white/60">
|
||||
<FontAwesomeIcon icon={faCircleInfo} />
|
||||
<InfoIcon />
|
||||
<span>Information</span>
|
||||
</div>
|
||||
|
||||
@@ -374,7 +360,7 @@ export function LightboxDetails({
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<Button
|
||||
className="w-full border border-ui-panel-border bg-ui-controls/40 hover:bg-ui-controls/60 text-white"
|
||||
icon={shareCopy.copied ? faCheck : faLink}
|
||||
icon={shareCopy.copied ? CheckIcon : LinkIcon}
|
||||
variant="secondary"
|
||||
onClick={handleCopyShareLink}
|
||||
>
|
||||
@@ -401,8 +387,8 @@ export function LightboxDetails({
|
||||
: "bg-ui-controls/20 text-white/60 cursor-not-allowed"
|
||||
}`}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={isDownloading ? faSpinnerThird : faArrowDownToLine}
|
||||
<DynamicIcon
|
||||
icon={isDownloading ? LoaderCircleIcon : ArrowDownToLineIcon}
|
||||
className={isDownloading ? "animate-spin" : ""}
|
||||
/>
|
||||
{isDownloading ? "Downloading…" : "Download"}
|
||||
@@ -410,7 +396,7 @@ export function LightboxDetails({
|
||||
</div>
|
||||
{onMakeVideo && (
|
||||
<Button
|
||||
icon={faVideo}
|
||||
icon={VideoIcon}
|
||||
className="w-full shadow-lg shadow-brand-primary/20"
|
||||
variant="primary"
|
||||
onClick={onMakeVideo}
|
||||
@@ -420,7 +406,7 @@ export function LightboxDetails({
|
||||
)}
|
||||
{onRecreate && promptData.hasToken && (
|
||||
<Button
|
||||
icon={faArrowRotateRight}
|
||||
icon={RotateCwIcon}
|
||||
className="w-full border border-ui-panel-border bg-ui-controls/40 hover:bg-ui-controls/60 text-white"
|
||||
variant="secondary"
|
||||
onClick={onRecreate}
|
||||
@@ -430,7 +416,7 @@ export function LightboxDetails({
|
||||
)}
|
||||
{onDelete && mediaToken && (
|
||||
<Button
|
||||
icon={faTrashCan}
|
||||
icon={Trash2Icon}
|
||||
className="w-full bg-red-500/10 hover:bg-red-500/20 text-red-500 border border-red-500/20"
|
||||
variant="destructive"
|
||||
onClick={onDelete}
|
||||
@@ -440,7 +426,7 @@ export function LightboxDetails({
|
||||
)}
|
||||
{showDownloadAppCta && (
|
||||
<Button
|
||||
icon={faArrowDownToLine}
|
||||
icon={ArrowDownToLineIcon}
|
||||
className="w-full shadow-lg shadow-brand-primary/20"
|
||||
variant="primary"
|
||||
onClick={() => {
|
||||
|
||||
@@ -4,12 +4,7 @@ import { toast } from "../toast/toast";
|
||||
import { useEffect, useState, useMemo, useCallback } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { MediaFilesApi, PromptsApi, type UserInfo } from "@storyteller/api";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faChevronLeft,
|
||||
faChevronRight,
|
||||
faTrashCan,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { ChevronLeftIcon, ChevronRightIcon, Trash2Icon } from "lucide-react";
|
||||
import { addCorsParam, PLACEHOLDER_IMAGES } from "@storyteller/common";
|
||||
import { ActionReminderModal } from "@storyteller/ui-action-reminder-modal";
|
||||
import { Viewer3D } from "@storyteller/ui-viewer-3d";
|
||||
@@ -475,7 +470,7 @@ export function Lightbox({
|
||||
className="absolute left-2 sm:left-3 top-1/2 -translate-y-1/2 z-30 flex h-9 w-9 sm:h-10 sm:w-10 items-center justify-center rounded-full bg-black/50 text-white/70 sm:opacity-0 transition-opacity duration-200 hover:bg-black/70 hover:text-white sm:group-hover/nav:opacity-100"
|
||||
aria-label="Previous item"
|
||||
>
|
||||
<FontAwesomeIcon icon={faChevronLeft} className="text-lg" />
|
||||
<ChevronLeftIcon className="text-lg" />
|
||||
</button>
|
||||
)}
|
||||
{onNavigateNext && (
|
||||
@@ -487,7 +482,7 @@ export function Lightbox({
|
||||
className="absolute right-2 sm:right-3 top-1/2 -translate-y-1/2 z-30 flex h-9 w-9 sm:h-10 sm:w-10 items-center justify-center rounded-full bg-black/50 text-white/70 sm:opacity-0 transition-opacity duration-200 hover:bg-black/70 hover:text-white sm:group-hover/nav:opacity-100"
|
||||
aria-label="Next item"
|
||||
>
|
||||
<FontAwesomeIcon icon={faChevronRight} className="text-lg" />
|
||||
<ChevronRightIcon className="text-lg" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -529,7 +524,7 @@ export function Lightbox({
|
||||
onPrimaryAction={handleDelete}
|
||||
primaryActionText="Delete"
|
||||
secondaryActionText="Cancel"
|
||||
primaryActionIcon={faTrashCan}
|
||||
primaryActionIcon={Trash2Icon}
|
||||
primaryActionBtnClassName="bg-red-500/10 hover:bg-red-500/20 text-red-500"
|
||||
/>
|
||||
</>
|
||||
|
||||
@@ -14,18 +14,8 @@ import { Fragment } from "react";
|
||||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||
import { USER_FEATURE_FLAGS, UsersApi } from "@storyteller/api";
|
||||
import { useSession, invalidateSession } from "../../lib/session";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faBars,
|
||||
faXmark,
|
||||
faGem,
|
||||
faGift,
|
||||
faArrowRight,
|
||||
faChevronDown,
|
||||
faLifeRing,
|
||||
faRocket,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { faDiscord } from "@fortawesome/free-brands-svg-icons";
|
||||
import { ArrowRightIcon, ChevronDownIcon, GemIcon, GiftIcon, LifeBuoyIcon, MenuIcon, RocketIcon, XIcon } from "lucide-react";
|
||||
import { DynamicIcon, DiscordIcon } from "@storyteller/icons";
|
||||
import {
|
||||
appLink,
|
||||
SOCIAL_LINKS,
|
||||
@@ -161,10 +151,9 @@ export default function Navbar() {
|
||||
)}
|
||||
>
|
||||
{entry.name}
|
||||
<FontAwesomeIcon
|
||||
icon={faChevronDown}
|
||||
className="text-[9px] transition-transform duration-200 group-data-[state=open]:rotate-180"
|
||||
/>
|
||||
<ChevronDownIcon
|
||||
|
||||
className="text-[9px] transition-transform duration-200 group-data-[state=open]:rotate-180" />
|
||||
</Link>
|
||||
</NavigationMenu.Trigger>
|
||||
) : (
|
||||
@@ -176,10 +165,9 @@ export default function Navbar() {
|
||||
)}
|
||||
>
|
||||
{entry.name}
|
||||
<FontAwesomeIcon
|
||||
icon={faChevronDown}
|
||||
className="text-[9px] transition-transform duration-200 group-data-[state=open]:rotate-180"
|
||||
/>
|
||||
<ChevronDownIcon
|
||||
|
||||
className="text-[9px] transition-transform duration-200 group-data-[state=open]:rotate-180" />
|
||||
</NavigationMenu.Trigger>
|
||||
)}
|
||||
<NavigationMenu.Content className="absolute top-full left-0 mt-2 rounded-xl border border-white/[0.08] bg-[#1a1a1a] shadow-xl overflow-hidden">
|
||||
@@ -229,7 +217,7 @@ export default function Navbar() {
|
||||
to="/pricing"
|
||||
className="hidden xl:flex h-8 items-center gap-1.5 px-3 rounded-lg text-sm font-medium text-white/90 hover:text-white hover:bg-white/[0.04] transition-all"
|
||||
>
|
||||
<FontAwesomeIcon icon={faGem} className="text-[11px]" />
|
||||
<GemIcon className="text-[11px]" />
|
||||
Pricing
|
||||
</Link>
|
||||
|
||||
@@ -238,10 +226,9 @@ export default function Navbar() {
|
||||
href={WEBAPP_URL}
|
||||
className="group h-8 flex items-center gap-1.5 px-3.5 rounded-full text-sm font-semibold text-black bg-white hover:bg-white/90 transition-all shadow-sm"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faRocket}
|
||||
className="text-[10px]"
|
||||
/>
|
||||
<RocketIcon
|
||||
|
||||
className="text-[10px]" />
|
||||
Launch App
|
||||
</a>
|
||||
)}
|
||||
@@ -282,10 +269,9 @@ export default function Navbar() {
|
||||
"flex w-full items-center gap-2 px-4 py-2 text-sm text-white/70 transition-colors",
|
||||
)}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faLifeRing}
|
||||
className="text-[11px] text-white/50"
|
||||
/>
|
||||
<LifeBuoyIcon
|
||||
|
||||
className="text-[11px] text-white/50" />
|
||||
Support
|
||||
</button>
|
||||
)}
|
||||
@@ -300,10 +286,9 @@ export default function Navbar() {
|
||||
"flex w-full items-center gap-2 px-4 py-2 text-sm text-white/70 transition-colors",
|
||||
)}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faGift}
|
||||
className="text-[11px] text-white/50"
|
||||
/>
|
||||
<GiftIcon
|
||||
|
||||
className="text-[11px] text-white/50" />
|
||||
Referrals
|
||||
</a>
|
||||
)}
|
||||
@@ -320,10 +305,9 @@ export default function Navbar() {
|
||||
"flex w-full items-center gap-2 px-4 py-2 text-sm text-white/70 transition-colors",
|
||||
)}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faDiscord}
|
||||
className="text-[11px] text-white/50"
|
||||
/>
|
||||
<DiscordIcon
|
||||
|
||||
className="text-[11px] text-white/50" />
|
||||
Join Discord
|
||||
</a>
|
||||
)}
|
||||
@@ -351,7 +335,7 @@ export default function Navbar() {
|
||||
to="/pricing"
|
||||
className="h-8 flex items-center gap-1.5 px-3 rounded-lg text-sm font-medium text-white/90 hover:text-white hover:bg-white/[0.04] transition-all"
|
||||
>
|
||||
<FontAwesomeIcon icon={faGem} className="text-[11px]" />
|
||||
<GemIcon className="text-[11px]" />
|
||||
Pricing
|
||||
</Link>
|
||||
{USE_WEBAPP_FOR_APP_FEATURES ? (
|
||||
@@ -359,10 +343,9 @@ export default function Navbar() {
|
||||
href={WEBAPP_URL}
|
||||
className="group h-8 flex items-center gap-1.5 px-3.5 rounded-full text-sm font-semibold text-black bg-white hover:bg-white/90 transition-all shadow-sm"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faRocket}
|
||||
className="text-[10px]"
|
||||
/>
|
||||
<RocketIcon
|
||||
|
||||
className="text-[10px]" />
|
||||
Launch App
|
||||
</a>
|
||||
) : (
|
||||
@@ -378,10 +361,9 @@ export default function Navbar() {
|
||||
className="group h-8 flex items-center gap-1.5 px-3.5 rounded-full text-sm font-semibold text-black bg-white hover:bg-white/90 transition-all shadow-sm"
|
||||
>
|
||||
Sign up
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowRight}
|
||||
className="text-[10px] transition-transform group-hover:translate-x-0.5"
|
||||
/>
|
||||
<ArrowRightIcon
|
||||
|
||||
className="text-[10px] transition-transform group-hover:translate-x-0.5" />
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
@@ -392,8 +374,8 @@ export default function Navbar() {
|
||||
<div className="flex items-center gap-1.5 lg:hidden">
|
||||
<DisclosureButton className="flex h-8 w-8 items-center justify-center rounded-lg text-white/70 hover:text-white hover:bg-white/[0.06] transition-colors">
|
||||
<span className="sr-only">Open main menu</span>
|
||||
<FontAwesomeIcon
|
||||
icon={open ? faXmark : faBars}
|
||||
<DynamicIcon
|
||||
icon={open ? XIcon : MenuIcon}
|
||||
className="text-base"
|
||||
/>
|
||||
</DisclosureButton>
|
||||
@@ -508,10 +490,9 @@ export default function Navbar() {
|
||||
href={WEBAPP_URL}
|
||||
className="group h-10 flex items-center justify-center gap-1.5 px-3.5 rounded-full text-sm font-semibold text-black bg-white active:bg-white/90 transition-all shadow-sm"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faRocket}
|
||||
className="text-[10px]"
|
||||
/>
|
||||
<RocketIcon
|
||||
|
||||
className="text-[10px]" />
|
||||
Launch App
|
||||
</DisclosureButton>
|
||||
)}
|
||||
@@ -521,10 +502,9 @@ export default function Navbar() {
|
||||
to="/pricing"
|
||||
className="flex h-8 items-center gap-1.5 rounded-lg px-2.5 text-[12px] font-medium text-white/70 bg-white/[0.06] active:bg-white/10 transition-colors"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faGem}
|
||||
className="text-[10px]"
|
||||
/>
|
||||
<GemIcon
|
||||
|
||||
className="text-[10px]" />
|
||||
Pricing
|
||||
</DisclosureButton>
|
||||
<DisclosureButton
|
||||
@@ -534,10 +514,9 @@ export default function Navbar() {
|
||||
rel="noopener noreferrer"
|
||||
className="flex h-8 items-center gap-1.5 rounded-lg px-2.5 text-[12px] font-medium text-white/70 bg-white/[0.06] active:bg-white/10 transition-colors"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faDiscord}
|
||||
className="text-[10px]"
|
||||
/>
|
||||
<DiscordIcon
|
||||
|
||||
className="text-[10px]" />
|
||||
Discord
|
||||
</DisclosureButton>
|
||||
{hasReferralsFlag && (
|
||||
@@ -546,10 +525,9 @@ export default function Navbar() {
|
||||
href={webappUrl("/referrals")}
|
||||
className="flex h-8 items-center gap-1.5 rounded-lg px-2.5 text-[12px] font-medium text-white/70 bg-white/[0.06] active:bg-white/10 transition-colors"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faGift}
|
||||
className="text-[10px]"
|
||||
/>
|
||||
<GiftIcon
|
||||
|
||||
className="text-[10px]" />
|
||||
Referrals
|
||||
</DisclosureButton>
|
||||
)}
|
||||
@@ -579,7 +557,7 @@ export default function Navbar() {
|
||||
href={WEBAPP_URL}
|
||||
className="group h-10 flex items-center justify-center gap-1.5 px-3.5 rounded-full text-sm font-semibold text-black bg-white active:bg-white/90 transition-all shadow-sm"
|
||||
>
|
||||
<FontAwesomeIcon icon={faRocket} className="text-[10px]" />
|
||||
<RocketIcon className="text-[10px]" />
|
||||
Launch App
|
||||
</DisclosureButton>
|
||||
) : (
|
||||
|
||||
@@ -1,19 +1,7 @@
|
||||
import { Tooltip } from "@storyteller/ui-tooltip";
|
||||
import { PopoverMenu } from "@storyteller/ui-popover";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faListCheck,
|
||||
faSpinnerThird,
|
||||
faXmark,
|
||||
faTrashAlt,
|
||||
faTasks,
|
||||
faBroom,
|
||||
faBomb,
|
||||
faCircleExclamation,
|
||||
faTriangleExclamation,
|
||||
faCopy,
|
||||
faCheck,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { BombIcon, BrushIcon, CheckIcon, CircleAlertIcon, CopyIcon, ListChecksIcon, LoaderCircleIcon, Trash2Icon, TriangleAlertIcon, XIcon } from "lucide-react";
|
||||
import { DynamicIcon } from "@storyteller/icons";
|
||||
import { Modal } from "@storyteller/ui-modal";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { JobsApi, JobStatus } from "@storyteller/api";
|
||||
@@ -192,8 +180,8 @@ const CopyPromptButton = ({ prompt }: { prompt: string }) => {
|
||||
setTimeout(() => setCopied(false), 3000);
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={copied ? faCheck : faCopy}
|
||||
<DynamicIcon
|
||||
icon={copied ? CheckIcon : CopyIcon}
|
||||
className={copied ? "text-green-400" : ""}
|
||||
/>
|
||||
</button>
|
||||
@@ -226,11 +214,10 @@ const InProgressCard = ({
|
||||
<div className="rounded-md p-2 transition-colors hover:bg-ui-controls/40">
|
||||
<div className="flex items-center gap-2.5">
|
||||
<div className="flex h-[72px] w-[72px] shrink-0 items-center justify-center overflow-hidden rounded bg-ui-controls">
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinnerThird}
|
||||
<LoaderCircleIcon
|
||||
|
||||
className="animate-spin text-base-fg/60"
|
||||
size="lg"
|
||||
/>
|
||||
size="lg" />
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
@@ -252,10 +239,9 @@ const InProgressCard = ({
|
||||
zIndex={50}
|
||||
delay={100}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faTriangleExclamation}
|
||||
className="h-3 w-3 shrink-0 text-yellow-400/60 transition-all hover:text-yellow-400"
|
||||
/>
|
||||
<TriangleAlertIcon
|
||||
|
||||
className="h-3 w-3 shrink-0 text-yellow-400/60 transition-all hover:text-yellow-400" />
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
@@ -294,7 +280,7 @@ const InProgressCard = ({
|
||||
onDismiss();
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} />
|
||||
<XIcon />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -362,7 +348,7 @@ const CompletedCard = ({
|
||||
onDismiss();
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} />
|
||||
<XIcon />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -382,11 +368,10 @@ const FailedCard = ({
|
||||
<div className="rounded-md p-2 transition-colors hover:bg-ui-controls/40">
|
||||
<div className="flex items-center gap-2.5">
|
||||
<div className="flex h-[72px] w-[72px] shrink-0 items-center justify-center overflow-hidden rounded bg-red-500/10">
|
||||
<FontAwesomeIcon
|
||||
icon={faCircleExclamation}
|
||||
<CircleAlertIcon
|
||||
|
||||
className="text-red-400"
|
||||
size="lg"
|
||||
/>
|
||||
size="lg" />
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
@@ -451,7 +436,7 @@ const FailedCard = ({
|
||||
onDismiss();
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} />
|
||||
<XIcon />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -712,7 +697,7 @@ export const TaskQueue = () => {
|
||||
title: "",
|
||||
message: null,
|
||||
primaryActionText: "",
|
||||
primaryActionIcon: faTrashAlt,
|
||||
primaryActionIcon: Trash2Icon,
|
||||
primaryActionBtnClassName: "",
|
||||
onConfirm: async () => {},
|
||||
});
|
||||
@@ -729,7 +714,7 @@ export const TaskQueue = () => {
|
||||
</span>
|
||||
),
|
||||
primaryActionText: "Clear completed",
|
||||
primaryActionIcon: faBroom,
|
||||
primaryActionIcon: BrushIcon,
|
||||
primaryActionBtnClassName:
|
||||
"bg-green-500/10 hover:bg-green-500/20 text-green-500",
|
||||
onConfirm: async () => {
|
||||
@@ -748,7 +733,7 @@ export const TaskQueue = () => {
|
||||
</span>
|
||||
),
|
||||
primaryActionText: "Clear stale",
|
||||
primaryActionIcon: faTrashAlt,
|
||||
primaryActionIcon: Trash2Icon,
|
||||
primaryActionBtnClassName:
|
||||
"bg-orange-500/10 hover:bg-orange-500/20 text-orange-500",
|
||||
onConfirm: async () => {
|
||||
@@ -767,7 +752,7 @@ export const TaskQueue = () => {
|
||||
</span>
|
||||
),
|
||||
primaryActionText: "Clear failed",
|
||||
primaryActionIcon: faTrashAlt,
|
||||
primaryActionIcon: Trash2Icon,
|
||||
primaryActionBtnClassName:
|
||||
"bg-red-500/10 hover:bg-red-500/20 text-red-500",
|
||||
onConfirm: async () => {
|
||||
@@ -787,7 +772,7 @@ export const TaskQueue = () => {
|
||||
</span>
|
||||
),
|
||||
primaryActionText: "Nuke all",
|
||||
primaryActionIcon: faBomb,
|
||||
primaryActionIcon: BombIcon,
|
||||
primaryActionBtnClassName:
|
||||
"bg-red-500/10 hover:bg-red-500/20 text-red-500",
|
||||
onConfirm: async () => {
|
||||
@@ -991,7 +976,7 @@ export const TaskQueue = () => {
|
||||
{hasNothing ? (
|
||||
<div className="flex w-full flex-col items-center justify-center p-5 text-base-fg/60">
|
||||
<div className="flex items-center gap-2.5 text-sm opacity-60">
|
||||
<FontAwesomeIcon icon={faTasks} /> No tasks yet
|
||||
<ListChecksIcon /> No tasks yet
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
@@ -1020,7 +1005,7 @@ export const TaskQueue = () => {
|
||||
className="text-xs tracking-wide text-red-400/70 transition-colors hover:text-red-300"
|
||||
onClick={() => handleClearFailed()}
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} className="mr-1" />
|
||||
<XIcon className="mr-1" />
|
||||
Clear failed
|
||||
</button>
|
||||
</div>
|
||||
@@ -1043,7 +1028,7 @@ export const TaskQueue = () => {
|
||||
className="text-xs tracking-wide text-base-fg/50 transition-colors hover:text-base-fg/100"
|
||||
onClick={() => handleClearCompleted()}
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} className="mr-1" />
|
||||
<XIcon className="mr-1" />
|
||||
Clear completed
|
||||
</button>
|
||||
</div>
|
||||
@@ -1090,12 +1075,11 @@ export const TaskQueue = () => {
|
||||
align="end"
|
||||
triggerIcon={
|
||||
inProgressCount > 0 ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinnerThird}
|
||||
className="animate-spin text-[11px]"
|
||||
/>
|
||||
<LoaderCircleIcon
|
||||
|
||||
className="animate-spin text-[11px]" />
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faListCheck} className="text-[11px]" />
|
||||
<ListChecksIcon className="text-[11px]" />
|
||||
)
|
||||
}
|
||||
onOpenChange={handleOpenChange}
|
||||
@@ -1140,28 +1124,28 @@ export const TaskQueue = () => {
|
||||
className="rounded-full flex h-9 items-center justify-center bg-green-500/10 px-3 text-green-500 hover:bg-green-500/20"
|
||||
onClick={() => handleClearCompleted()}
|
||||
>
|
||||
<FontAwesomeIcon icon={faBroom} className="mr-1.5" />
|
||||
<BrushIcon className="mr-1.5" />
|
||||
Clear completed
|
||||
</Button>
|
||||
<Button
|
||||
className="rounded-full flex h-9 items-center justify-center bg-orange-500/10 px-3 text-orange-500 hover:bg-orange-500/20"
|
||||
onClick={() => handleClearStale()}
|
||||
>
|
||||
<FontAwesomeIcon icon={faTrashAlt} className="mr-1.5" />
|
||||
<Trash2Icon className="mr-1.5" />
|
||||
Clear stale
|
||||
</Button>
|
||||
<Button
|
||||
className="rounded-full flex h-9 items-center justify-center bg-red-500/10 px-3 text-red-400 hover:bg-red-500/20"
|
||||
onClick={() => handleClearFailed()}
|
||||
>
|
||||
<FontAwesomeIcon icon={faTrashAlt} className="mr-1.5" />
|
||||
<Trash2Icon className="mr-1.5" />
|
||||
Clear failed
|
||||
</Button>
|
||||
<Button
|
||||
className="rounded-full flex h-9 items-center justify-center bg-red-500/10 px-3 text-red-500 hover:bg-red-500/20"
|
||||
onClick={() => handleRemoveAll()}
|
||||
>
|
||||
<FontAwesomeIcon icon={faBomb} className="mr-1.5" />
|
||||
<BombIcon className="mr-1.5" />
|
||||
Remove all
|
||||
</Button>
|
||||
<div className="mr-2 h-4 w-[1px] bg-base-fg/10" />
|
||||
|
||||
+10
-11
@@ -1,9 +1,8 @@
|
||||
import { faServer, faBan, faUnlock, faKey, faGlobe, faCloud, faImage, faCreditCard, faTimes } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { faGithub } from "@fortawesome/free-brands-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { BanIcon, CloudIcon, CreditCardIcon, GlobeIcon, ImageIcon, KeyIcon, LockOpenIcon, ServerIcon, XIcon } from "lucide-react";
|
||||
import { DynamicIcon, GithubIcon } from "@storyteller/icons";
|
||||
|
||||
export const OwnershipComparison = () => {
|
||||
const websiteIcons = [faCloud, faGlobe, faImage, faServer];
|
||||
const websiteIcons = [CloudIcon, GlobeIcon, ImageIcon, ServerIcon];
|
||||
|
||||
return (
|
||||
<div className="relative z-10 w-full max-w-[1400px] mx-auto px-4 sm:px-8 lg:px-12 py-12 md:py-32 overflow-visible">
|
||||
@@ -47,10 +46,10 @@ export const OwnershipComparison = () => {
|
||||
zIndex: 10 + i,
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={icon} className="text-xl" />
|
||||
<DynamicIcon icon={icon} className="text-xl" />
|
||||
|
||||
<div className="absolute -top-3 -right-3 w-7 h-7 bg-[#ef4444] rounded-full flex items-center justify-center border-4 border-[#121214] text-white text-xs shadow-md">
|
||||
<FontAwesomeIcon icon={faTimes} className="font-bold" />
|
||||
<XIcon className="font-bold" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -67,10 +66,10 @@ export const OwnershipComparison = () => {
|
||||
|
||||
<div className="flex flex-wrap justify-center gap-3 text-xs sm:text-sm font-bold tracking-wide uppercase">
|
||||
<div className="flex items-center gap-2 text-[#ffc9c9] bg-[#ef4444]/10 px-4 py-2.5 rounded-xl border border-[#ef4444]/20 shadow-lg shadow-red-900/10">
|
||||
<FontAwesomeIcon icon={faBan} className="text-[#ef4444]" /> NO OWNERSHIP
|
||||
<BanIcon className="text-[#ef4444]" /> NO OWNERSHIP
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-[#ffc9c9] bg-[#ef4444]/10 px-4 py-2.5 rounded-xl border border-[#ef4444]/20 shadow-lg shadow-red-900/10">
|
||||
<FontAwesomeIcon icon={faCreditCard} className="text-[#ef4444]" /> MONTHLY FEES
|
||||
<CreditCardIcon className="text-[#ef4444]" /> MONTHLY FEES
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -89,7 +88,7 @@ export const OwnershipComparison = () => {
|
||||
<img src="/images/services/artcraft.svg" alt="ArtCraft" className="w-[55%] h-[55%] object-contain drop-shadow-md brightness-0 invert" />
|
||||
</div>
|
||||
<div className="absolute -right-6 -bottom-4 w-16 h-16 bg-[#24292e] rounded-2xl flex items-center justify-center text-white text-3xl border-4 border-[#1C1C20] z-20 shadow-xl translate-x-2 translate-y-2">
|
||||
<FontAwesomeIcon icon={faGithub} />
|
||||
<GithubIcon />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -105,10 +104,10 @@ export const OwnershipComparison = () => {
|
||||
|
||||
<div className="flex flex-wrap justify-center gap-3 text-xs sm:text-sm font-bold tracking-wide uppercase">
|
||||
<div className="flex items-center gap-2 text-white bg-primary/20 px-4 py-2.5 rounded-xl border border-primary/30 shadow-lg shadow-primary/10">
|
||||
<FontAwesomeIcon icon={faUnlock} className="text-primary" /> YOURS FOREVER
|
||||
<LockOpenIcon className="text-primary" /> YOURS FOREVER
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-white bg-primary/20 px-4 py-2.5 rounded-xl border border-primary/30 shadow-lg shadow-primary/10">
|
||||
<FontAwesomeIcon icon={faKey}className="text-primary" /> BYO KEYS
|
||||
<KeyIcon className="text-primary" /> BYO KEYS
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,16 +1,6 @@
|
||||
import { useState, useRef, useCallback, useEffect } from "react";
|
||||
import { Modal } from "@storyteller/ui-modal";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faPlus,
|
||||
faArrowLeft,
|
||||
faUpload,
|
||||
faUserGroup,
|
||||
faSpinnerThird,
|
||||
faXmark,
|
||||
faPen,
|
||||
faTrashAlt,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { ArrowLeftIcon, LoaderCircleIcon, PenIcon, PlusIcon, Trash2Icon, UploadIcon, UsersIcon, XIcon } from "lucide-react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import {
|
||||
CharactersApi,
|
||||
@@ -334,7 +324,7 @@ const CharacterListView = ({
|
||||
className="flex flex-col items-center justify-center gap-2 overflow-hidden rounded-lg border-2 border-dashed border-white/10 bg-white/[0.05] text-white/60 transition-colors hover:border-white/25 hover:text-white/80"
|
||||
>
|
||||
<div className="flex aspect-square w-full flex-col items-center justify-center gap-2">
|
||||
<FontAwesomeIcon icon={faPlus} className="text-lg" />
|
||||
<PlusIcon className="text-lg" />
|
||||
<span className="text-sm font-medium">Create New</span>
|
||||
</div>
|
||||
</button>
|
||||
@@ -354,14 +344,13 @@ const CharacterListView = ({
|
||||
/>
|
||||
) : (
|
||||
<div className="flex h-full w-full items-center justify-center text-white/20">
|
||||
<FontAwesomeIcon icon={faUserGroup} className="text-2xl" />
|
||||
<UsersIcon className="text-2xl" />
|
||||
</div>
|
||||
)}
|
||||
<div className="absolute inset-0 flex flex-col items-center justify-center gap-2 bg-black/40">
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinnerThird}
|
||||
className="text-lg text-white/80 animate-spin"
|
||||
/>
|
||||
<LoaderCircleIcon
|
||||
|
||||
className="text-lg text-white/80 animate-spin" />
|
||||
<span className="text-xs font-medium text-white/80">
|
||||
Creating...
|
||||
</span>
|
||||
@@ -397,10 +386,9 @@ const CharacterListView = ({
|
||||
/>
|
||||
) : (
|
||||
<div className="flex h-full w-full items-center justify-center text-white/20">
|
||||
<FontAwesomeIcon
|
||||
icon={faUserGroup}
|
||||
className="text-2xl"
|
||||
/>
|
||||
<UsersIcon
|
||||
|
||||
className="text-2xl" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -421,7 +409,7 @@ const CharacterListView = ({
|
||||
}}
|
||||
className="flex h-7 w-7 items-center justify-center rounded-full bg-black/60 text-white/80 transition-colors hover:bg-black/80"
|
||||
>
|
||||
<FontAwesomeIcon icon={faPen} className="text-[10px]" />
|
||||
<PenIcon className="text-[10px]" />
|
||||
</button>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
@@ -430,10 +418,9 @@ const CharacterListView = ({
|
||||
}}
|
||||
className="flex h-7 w-7 items-center justify-center rounded-full bg-black/60 text-white/80 transition-colors hover:bg-red-500"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faTrashAlt}
|
||||
className="text-[10px]"
|
||||
/>
|
||||
<Trash2Icon
|
||||
|
||||
className="text-[10px]" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
@@ -546,7 +533,7 @@ const EditCharacterView = ({
|
||||
onClick={onBack}
|
||||
className="flex items-center justify-center text-white/60 transition-colors hover:text-white"
|
||||
>
|
||||
<FontAwesomeIcon icon={faArrowLeft} />
|
||||
<ArrowLeftIcon />
|
||||
</button>
|
||||
<h2 className="text-xl font-bold text-white">Edit Character</h2>
|
||||
</div>
|
||||
@@ -795,7 +782,7 @@ const NewCharacterView = ({
|
||||
onClick={onBack}
|
||||
className="flex items-center justify-center text-white/60 transition-colors hover:text-white"
|
||||
>
|
||||
<FontAwesomeIcon icon={faArrowLeft} />
|
||||
<ArrowLeftIcon />
|
||||
</button>
|
||||
<h2 className="text-xl font-bold text-white">New Character</h2>
|
||||
</div>
|
||||
@@ -825,10 +812,9 @@ const NewCharacterView = ({
|
||||
/>
|
||||
{!images[0]!.mediaToken && (
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-black/50">
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinnerThird}
|
||||
className="text-white animate-spin"
|
||||
/>
|
||||
<LoaderCircleIcon
|
||||
|
||||
className="text-white animate-spin" />
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
@@ -838,15 +824,14 @@ const NewCharacterView = ({
|
||||
}}
|
||||
className="absolute right-2 top-2 flex h-7 w-7 items-center justify-center rounded-full bg-black/60 text-white/80 opacity-0 transition-all group-hover:opacity-100 hover:bg-red-500"
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} className="text-sm" />
|
||||
<XIcon className="text-sm" />
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex h-full w-full flex-col items-center justify-center text-white/60">
|
||||
<FontAwesomeIcon
|
||||
icon={faUpload}
|
||||
className="mb-2 text-xl text-white/40"
|
||||
/>
|
||||
<UploadIcon
|
||||
|
||||
className="mb-2 text-xl text-white/40" />
|
||||
<p className="text-sm">Upload reference image</p>
|
||||
<p className="mb-3 text-xs text-white/40">
|
||||
Click or drag an image here
|
||||
|
||||
@@ -8,14 +8,8 @@ import {
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faChevronDown,
|
||||
faChevronUp,
|
||||
faMusic,
|
||||
faUserGroup,
|
||||
faVideo,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { ChevronDownIcon, ChevronUpIcon, MusicIcon, UsersIcon, VideoIcon } from "lucide-react";
|
||||
import { DynamicIcon } from "@storyteller/icons";
|
||||
import { GenerateIconButton } from "@storyteller/ui-button";
|
||||
import { Tooltip } from "@storyteller/ui-tooltip";
|
||||
import {
|
||||
@@ -890,14 +884,13 @@ export const PromptBox = forwardRef<HTMLDivElement, PromptBoxProps>(
|
||||
className="h-full w-full object-cover"
|
||||
/>
|
||||
) : item.type === "character" ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faUserGroup}
|
||||
className="h-3.5 w-3.5 text-white/60"
|
||||
/>
|
||||
<UsersIcon
|
||||
|
||||
className="h-3.5 w-3.5 text-white/60" />
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
<DynamicIcon
|
||||
icon={
|
||||
item.type === "video" ? faVideo : faMusic
|
||||
item.type === "video" ? VideoIcon : MusicIcon
|
||||
}
|
||||
className="h-3.5 w-3.5 text-white/60"
|
||||
/>
|
||||
@@ -977,8 +970,8 @@ export const PromptBox = forwardRef<HTMLDivElement, PromptBoxProps>(
|
||||
onClick={toggleExpand}
|
||||
className="px-3 py-0.5 text-white/30 transition-colors hover:text-white/90"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={isExpanded ? faChevronUp : faChevronDown}
|
||||
<DynamicIcon
|
||||
icon={isExpanded ? ChevronUpIcon : ChevronDownIcon}
|
||||
className="text-xs"
|
||||
/>
|
||||
</button>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faImages, faMusic, faVideo } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { ImagesIcon, MusicIcon, VideoIcon } from "lucide-react";
|
||||
import { DynamicIcon } from "@storyteller/icons";
|
||||
import { toast } from "../toast/toast";
|
||||
|
||||
// ── Types ───────────────────────────────────────────────────────────────
|
||||
@@ -235,10 +235,10 @@ export function PromptBoxDropOverlay({
|
||||
|
||||
const kinds = [
|
||||
...(acceptsImages
|
||||
? [{ icon: faImages, label: keyframeMode ? "Frames" : "Images" }]
|
||||
? [{ icon: ImagesIcon, label: keyframeMode ? "Frames" : "Images" }]
|
||||
: []),
|
||||
...(acceptsVideos ? [{ icon: faVideo, label: "Video" }] : []),
|
||||
...(acceptsAudio ? [{ icon: faMusic, label: "Audio" }] : []),
|
||||
...(acceptsVideos ? [{ icon: VideoIcon, label: "Video" }] : []),
|
||||
...(acceptsAudio ? [{ icon: MusicIcon, label: "Audio" }] : []),
|
||||
];
|
||||
const rejected = dragState === "reject";
|
||||
|
||||
@@ -256,7 +256,7 @@ export function PromptBoxDropOverlay({
|
||||
className="promptbox-drop-chip flex h-9 w-9 items-center justify-center rounded-lg bg-white/10 text-base-fg/90"
|
||||
style={{ animationDelay: `${i * 50}ms` }}
|
||||
>
|
||||
<FontAwesomeIcon icon={kind.icon} className="text-sm" />
|
||||
<DynamicIcon icon={kind.icon} className="text-sm" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -2,8 +2,7 @@ import { ReactNode, useCallback, useEffect, useRef, useState } from "react";
|
||||
import { Modal } from "@storyteller/ui-modal";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { Tooltip } from "@storyteller/ui-tooltip";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faUpRightAndDownLeftFromCenter } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { Maximize2Icon } from "lucide-react";
|
||||
|
||||
// Webapp "focus mode" for the prompt editor. Mirrors the desktop lib's
|
||||
// (@storyteller/ui-promptbox) fullscreen feature but lives here because the
|
||||
@@ -35,10 +34,9 @@ export const PromptFullscreenButton = ({
|
||||
onClick={onClick}
|
||||
className="flex h-6 w-6 items-center justify-center rounded-full bg-base-fg/5 text-base-fg/50 transition-colors hover:bg-base-fg/10 hover:text-base-fg/90 focus:outline-none"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faUpRightAndDownLeftFromCenter}
|
||||
className="h-3 w-3"
|
||||
/>
|
||||
<Maximize2Icon
|
||||
|
||||
className="h-3 w-3" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { useState } from "react";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { Input } from "@storyteller/ui-input";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faSpinnerThird } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { LoaderCircleIcon } from "lucide-react";
|
||||
import { UsersApi, type UserInfo } from "@storyteller/api";
|
||||
import { refreshSession, updateSessionUser } from "../../lib/session";
|
||||
import { toast } from "../toast/toast";
|
||||
@@ -490,7 +489,7 @@ function FormActions({
|
||||
disabled={submitting || disabled}
|
||||
>
|
||||
{submitting ? (
|
||||
<FontAwesomeIcon icon={faSpinnerThird} className="animate-spin" />
|
||||
<LoaderCircleIcon className="animate-spin" />
|
||||
) : (
|
||||
"Save"
|
||||
)}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user