Compare commits

...
Author SHA1 Message Date
Michael Chung 28b6034cfb Removed docs and playground, wizard.
Readded the strange login.
2025-05-27 11:21:10 -04:00
bflatastic e8d3b0bef9 Fixed errors (upload modal) 2025-05-27 20:56:20 +07:00
Tanish Manku c7b1a361b1 Move UploadModal 2025-05-27 01:37:34 -07:00
Tanish Manku 074e340cb5 Move SearchFilter 2025-05-27 01:18:48 -07:00
Tanish Manku f0dbeca36a Move Pagination 2025-05-27 01:14:44 -07:00
Tanish Manku a43b34c1d9 Move NumberInput to input 2025-05-27 01:10:50 -07:00
Tanish Manku b8b90476a1 Move InputVector to input package 2025-05-27 01:04:02 -07:00
Tanish Manku 04420f1dd3 Port FilterButtons 2025-05-27 00:51:14 -07:00
Tanish Manku 7577bd977a Move popover 2025-05-27 00:14:47 -07:00
Tanish Manku 1708c6094f Migrate ListDropdown 2025-05-27 00:03:29 -07:00
Tanish Manku 194229516e Delete WaveformPlayer 2025-05-26 23:59:30 -07:00
Tanish Manku 8e8da70c8d Move loadingdots, spinner, and bar 2025-05-26 23:58:37 -07:00
bflatastic 6c76e3e248 removed typography 2025-05-27 13:40:51 +07:00
bflatastic 49474c3c45 sync libs 2025-05-27 13:40:51 +07:00
bflatastic f5a09d69b6 shared Gravatar and FileUploader 2025-05-27 13:40:51 +07:00
bflatastic 281d7b2da2 shared Input 2025-05-27 13:40:51 +07:00
bflatastic 723f2cba87 shared ButtonLink 2025-05-27 13:40:50 +07:00
bflatastic 26986d6757 shared ButtonIcon/ButtonIconSelect 2025-05-27 13:40:50 +07:00
bflatastic 1b19909159 shared ButtonDropdown 2025-05-27 13:40:50 +07:00
bflatastic c58c465128 shared badge 2025-05-27 13:40:50 +07:00
bflatastic 4efa7d5a95 Refactor modal components to use new Modal implementation and remove TransitionDialogue references across various components. 2025-05-27 13:40:50 +07:00
CrossPr0duct f93c62e8e1 Ok tabs and tabs selector from comps removed. 2025-05-27 13:40:50 +07:00
Michael Chung caf1c05b9a Restore index.tsx 2025-05-27 13:40:50 +07:00
Michael Chung df27435564 Remove unused components and files 2025-05-27 13:40:50 +07:00
CrossPr0duct 654456ac40 Removals 2025-05-27 13:40:50 +07:00
316 changed files with 2869 additions and 4905 deletions
@@ -1 +0,0 @@
export { Badge } from "./Badge";
@@ -1,15 +0,0 @@
import { Canvas, Meta } from "@storybook/blocks";
import * as ButtonDropdownStories from "./ButtonDropdown.stories";
<Meta of={ButtonDropdownStories} />
# Button for dropdown actions
Dropdown options that open up a dialog
<Canvas of={ButtonDropdownStories.OpenDialog} />
Dropdown options with onClick event
<Canvas of={ButtonDropdownStories.ClickEvent} />
@@ -1,77 +0,0 @@
import type { Meta, StoryObj } from "@storybook/react";
import { ButtonDropdown } from "./ButtonDropdown";
import { withActions } from "@storybook/addon-actions/decorator";
import { faAngleLeft, faFile } from "@fortawesome/pro-solid-svg-icons";
const meta: Meta<typeof ButtonDropdown> = {
component: ButtonDropdown,
parameters: {
actions: {
handles: ["click"],
},
},
decorators: [withActions],
};
type Story = StoryObj<typeof ButtonDropdown>;
export const OpenDialog: Story = {
render: () => (
<ButtonDropdown
label="Dialog Options"
icon={faFile}
options={[
{
label: "Open Dialog",
description: "Desc 1",
onDialogOpen: () => {
//console.log("Opened Dialog");
},
dialogProps: {
title: "Dialog Title",
content: <div className="text-white">Dialog content here...</div>,
confirmButtonProps: {
label: "Create",
disabled: false,
onClick: () => {
//console.log("Action on Dialog");
},
},
closeButtonProps: {
label: "Cancel",
},
showClose: true,
onClose: () => {
//console.log("Closed Dialog");
},
},
},
{
label: "Disabled",
description: "Desc 2",
disabled: true,
},
]}
/>
),
};
export const ClickEvent: Story = {
render: () => (
<ButtonDropdown
label="Event Options"
icon={faFile}
options={[
{
label: "Click Event",
description: "Desc",
onClick: () => {
//console.log("Clicked");
},
},
]}
/>
),
};
export default meta;
@@ -1 +0,0 @@
export * from "./ButtonDropdown";
@@ -1,13 +0,0 @@
import { Canvas, Meta } from '@storybook/blocks';
import * as ButtonIconStories from './ButtonIcon.stories';
<Meta of={ButtonIconStories} />
# ButtonIcon
A button denotes a possible action to be taken on behalf of the user.
This button shows only a single icon.
<Canvas of={ButtonIconStories.Default} />
@@ -1,22 +0,0 @@
import type { Meta, StoryObj } from "@storybook/react";
import { ButtonIcon } from "./ButtonIcon";
import { withActions } from "@storybook/addon-actions/decorator";
import { faAngleLeft } from "@fortawesome/pro-solid-svg-icons";
const meta: Meta<typeof ButtonIcon> = {
component: ButtonIcon,
parameters: {
actions: {
handles: ["click"],
},
},
decorators: [withActions],
};
type Story = StoryObj<typeof ButtonIcon>;
export const Default: Story = {
render: () => <ButtonIcon icon={faAngleLeft} onClick={() => {}} />,
};
export default meta;
@@ -1 +0,0 @@
export * from "./ButtonIcon";
@@ -1,13 +0,0 @@
import { Canvas, Meta } from '@storybook/blocks';
import * as ButtonIconSelectStories from './ButtonIconSelect.stories';
<Meta of={ButtonIconSelectStories} />
# ButtonIconSelectStories
A button denotes a possible action to be taken on behalf of the user.
This button shows only a single icon.
<Canvas of={ButtonIconSelectStories.Default} />
@@ -1,38 +0,0 @@
import type { Meta, StoryObj } from "@storybook/react";
import { ButtonIconSelect } from "./ButtonIconSelect";
import { withActions } from "@storybook/addon-actions/decorator";
import { faAngleLeft, faAngleRight } from "@fortawesome/pro-solid-svg-icons";
const meta: Meta<typeof ButtonIconSelect> = {
component: ButtonIconSelect,
parameters: {
actions: {
handles: ["click"],
},
},
decorators: [withActions],
};
type Story = StoryObj<typeof ButtonIconSelect>;
export const Default: Story = {
render: () => (
<ButtonIconSelect
onOptionChange={() => {}}
options={[
{
value: "V1",
icon: faAngleLeft,
text: "Select 1",
},
{
value: "V2",
icon: faAngleRight,
text: "Select 2",
},
]}
/>
),
};
export default meta;
@@ -1 +0,0 @@
export * from "./ButtonIconSelect";
@@ -1,13 +0,0 @@
import { Canvas, Meta } from '@storybook/blocks';
import * as ButtonIconStackStories from './ButtonIconStack.stories';
<Meta of={ButtonIconStackStories} />
# ButtonIconStackStories
A button denotes a possible action to be taken on behalf of the user.
This button shows only a single icon.
<Canvas of={ButtonIconStackStories.Default} />
@@ -1,28 +0,0 @@
import { faImage } from "@fortawesome/pro-solid-svg-icons";
import { withActions } from "@storybook/addon-actions/decorator";
import type { Meta, StoryObj } from "@storybook/react";
import { ButtonIconStack } from "./ButtonIconStack";
const meta: Meta<typeof ButtonIconStack> = {
component: ButtonIconStack,
parameters: {
actions: {
handles: ["click"],
},
},
decorators: [withActions],
};
type Story = StoryObj<typeof ButtonIconStack>;
export const Default: Story = {
render: () => (
<ButtonIconStack
onClick={() => { }}
icon={faImage}
text={"Start Frame"}
/>
),
};
export default meta;
@@ -1,39 +0,0 @@
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { twMerge } from "tailwind-merge";
interface ButtonIconStackProps {
icon: IconDefinition;
text: string;
onClick?: () => void;
style?: string;
additionalStyle?: string;
}
export function ButtonIconStack({
icon,
text = "",
onClick,
style,
additionalStyle,
}: ButtonIconStackProps) {
const defaultStyle = twMerge(
"flex flex-col items-center justify-center text-xs transition-all duration-150",
"h-full w-full gap-1 p-2",
additionalStyle
)
return (
<button
className={style ?? defaultStyle}
onClick={onClick}
>
<FontAwesomeIcon icon={icon} size="xl" />
{text && (
<span className="text-nowrap text-xs font-medium">{text}</span>
)}
</button>
);
}
@@ -1 +0,0 @@
export * from "./ButtonIconStack";
@@ -1,20 +0,0 @@
import { Canvas, Meta } from '@storybook/blocks';
import * as ButtonLinkStories from './ButtonLink.stories';
<Meta of={ButtonLinkStories} />
# ButtonLink
A button denotes a possible action to be taken on behalf of the user.
The button label comes via "children".
There are three types of Button: primary, secondary, action.
<Canvas of={ButtonLinkStories.Primary} />
<Canvas of={ButtonLinkStories.Secondary} />
It can be shown with an optional icon.
<Canvas of={ButtonLinkStories.WithIcon} />
@@ -1,42 +0,0 @@
import type { Meta, StoryObj } from "@storybook/react";
import { ButtonLink } from "./ButtonLink";
import { withActions } from "@storybook/addon-actions/decorator";
import { faAngleLeft } from "@fortawesome/pro-solid-svg-icons";
const meta: Meta<typeof ButtonLink> = {
component: ButtonLink,
parameters: {
actions: {
handles: ["click"],
},
},
decorators: [withActions],
};
type Story = StoryObj<typeof ButtonLink>;
export const Primary: Story = {
render: () => (
<ButtonLink to="google.com" variant="primary">
Primary
</ButtonLink>
),
};
export const Secondary: Story = {
render: () => (
<ButtonLink to="google.com" variant="secondary">
Secondary
</ButtonLink>
),
};
export const WithIcon: Story = {
render: () => (
<ButtonLink to="google.com" variant="secondary" icon={faAngleLeft}>
Icon
</ButtonLink>
),
};
export default meta;
@@ -1 +0,0 @@
export * from "./ButtonLink";
@@ -1,34 +0,0 @@
import { faXmark } from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { twMerge } from "tailwind-merge";
interface CloseButtonProps {
onClick: () => void;
className?: string;
size?: "sm" | "md" | "lg";
}
export const CloseButton = ({
onClick,
className = "",
size = "md",
}: CloseButtonProps) => {
const sizeClasses = {
sm: "h-5 w-5 text-sm",
md: "h-7 w-7 text-md",
lg: "h-9 w-9 text-xl",
};
return (
<button
onClick={onClick}
className={twMerge(
"flex items-center justify-center rounded-full bg-black/40 text-white/60 transition-all hover:bg-black/70 hover:text-white",
sizeClasses[size],
className,
)}
>
<FontAwesomeIcon icon={faXmark} />
</button>
);
};
@@ -108,4 +108,4 @@ export const ConfirmationModal = ({
</Dialog>
</Transition>
);
};
};
@@ -1 +0,0 @@
export * from "./FileUploader";
@@ -1,14 +0,0 @@
import { Canvas, Meta } from '@storybook/blocks';
import * as FilterButtonsStories from './FilterButtons.stories';
<Meta of={FilterButtonsStories} />
# Three buttons which appear at the top of some of the media tabs.
The value can be set to Featured
<Canvas of={FilterButtonsStories.Featured} />
Mine
<Canvas of={FilterButtonsStories.Mine} />
or Bookmarked
<Canvas of={FilterButtonsStories.Bookmarked} />
@@ -1,36 +0,0 @@
import type { Meta, StoryObj } from "@storybook/react";
import { FilterButtons } from "./FilterButtons";
import { withActions } from "@storybook/addon-actions/decorator";
import { AssetFilterOption } from "~/enums";
const meta: Meta<typeof FilterButtons> = {
component: FilterButtons,
parameters: {
actions: {
handles: ["click"],
},
},
decorators: [withActions],
};
type Story = StoryObj<typeof FilterButtons>;
export const Featured: Story = {
render: () => (
<FilterButtons onClick={() => null} value={AssetFilterOption.FEATURED} />
),
};
export const Mine: Story = {
render: () => (
<FilterButtons onClick={() => null} value={AssetFilterOption.MINE} />
),
};
export const Bookmarked: Story = {
render: () => (
<FilterButtons onClick={() => null} value={AssetFilterOption.BOOKMARKED} />
),
};
export default meta;
@@ -1 +0,0 @@
export * from "./FilterButtons";
@@ -1 +0,0 @@
export * from "./Gravatar";
@@ -1,19 +0,0 @@
import { Canvas, Meta } from '@storybook/blocks';
import * as InputStories from './Input.stories';
<Meta of={InputStories} />
# Input control
Here is the input with placeholder
<Canvas of={InputStories.Default} />
Here is the input with icon
<Canvas of={InputStories.WithIcon} />
Here is the input with label
<Canvas of={InputStories.WithLabel} />
Here is the input with error message
<Canvas of={InputStories.WithError} />
@@ -1,50 +0,0 @@
import type { Meta, StoryObj } from "@storybook/react";
import { Input } from "./Input";
import { withActions } from "@storybook/addon-actions/decorator";
import { faUser } from "@fortawesome/pro-solid-svg-icons";
const meta: Meta<typeof Input> = {
component: Input,
parameters: {
actions: {
handles: ["change", "blur", "focus"],
},
},
decorators: [withActions],
};
type Story = StoryObj<typeof Input>;
export const Default: Story = {
render: () => (
<div className="bg-action p-8">
<Input placeholder="field placeholder" />
</div>
),
};
export const WithIcon: Story = {
render: () => (
<div className="bg-action p-8">
<Input icon={faUser} />
</div>
),
};
export const WithLabel: Story = {
render: () => (
<div className="bg-action p-8">
<Input label="Input label" />
</div>
),
};
export const WithError: Story = {
render: () => (
<div className="bg-action p-8">
<Input errorMessage="Error message" />
</div>
),
};
export default meta;
@@ -1,83 +0,0 @@
import React from "react";
import { twMerge } from "tailwind-merge";
import { IconDefinition } from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Label, H6 } from "~/components";
import { kebabCase } from "~/utilities";
import {
disableHotkeyInput,
enableHotkeyInput,
DomLevels,
} from "~/pages/PageEnigma/signals";
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
inputClassName?: string;
iconClassName?: string;
label?: string;
icon?: IconDefinition;
isError?: boolean;
errorMessage?: string;
}
export const Input = React.forwardRef(
(
{
label,
icon,
inputClassName,
iconClassName,
className,
id,
isError,
onBlur,
onFocus,
errorMessage,
...rest
}: InputProps,
ref: React.ForwardedRef<HTMLInputElement>,
) => {
return (
<div className={twMerge("flex flex-col", className)}>
{label && <Label htmlFor={id ? id : kebabCase(label)}>{label}</Label>}
<div className="relative w-full">
{icon && (
<FontAwesomeIcon
icon={icon}
className={twMerge("text-md absolute pl-3 pt-3", iconClassName)}
/>
)}
<input
ref={ref}
id={id ? id : label ? kebabCase(label) : undefined}
className={twMerge(
"h-10 w-full rounded-lg bg-[#242424] px-3 py-2.5 text-white placeholder-white/50 outline-none",
"hover:border-primary/60 focus:border-primary border border-[#363636] transition-all duration-150 ease-in-out focus:!outline-none",
icon && "pl-10",
isError && "outline-red focus:outline-red",
inputClassName,
)}
onFocus={(e: React.FocusEvent<HTMLInputElement>) => {
if (onFocus) {
onFocus(e);
}
disableHotkeyInput(DomLevels.INPUT);
}}
onBlur={(e: React.FocusEvent<HTMLInputElement>) => {
if (onBlur) {
onBlur(e);
}
enableHotkeyInput(DomLevels.INPUT);
}}
{...rest}
/>
{errorMessage && (
<H6 className="absolute z-10 text-red">{errorMessage}</H6>
)}
</div>
</div>
);
},
);
Input.displayName = "Input";
@@ -1 +0,0 @@
export * from "./Input";
@@ -1,10 +0,0 @@
import { Canvas, Meta } from '@storybook/blocks';
import * as InputVectorStories from './InputVector.stories';
<Meta of={InputVectorStories} />
# 3 Input controls: one for x, y, and z
It allows entry os plus or minus numbers with decimals. It can also be adjusted via dragging the mouse left or right.
<Canvas of={InputVectorStories.Default} />
@@ -1,44 +0,0 @@
import { useState } from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { InputVector } from "./InputVector";
import { withActions } from "@storybook/addon-actions/decorator";
const meta: Meta<typeof InputVector> = {
component: InputVector,
parameters: {
actions: {
handles: ["change", "blur", "focus"],
},
},
decorators: [withActions],
};
type Story = StoryObj<typeof InputVector>;
const InputVectorRender = () => {
// Sets the hooks for both the label and primary props
const [xyz, setXyz] = useState<Record<string, string>>({
x: "0",
y: "0",
z: "0",
});
return (
<InputVector
x={xyz.x}
y={xyz.y}
z={xyz.z}
onChange={(value) => setXyz(value)}
/>
);
};
export const Default: Story = {
render: () => (
<div className="bg-action p-8">
<InputVectorRender />
</div>
),
};
export default meta;
@@ -1 +0,0 @@
export * from "./InputVector";
@@ -1,27 +0,0 @@
import { twMerge } from "tailwind-merge";
interface LoadingSpinnerProps {
className?: string;
}
export const LoadingSpinner = ({ className }: LoadingSpinnerProps) => {
return (
<svg
className={twMerge("h-6 w-6 animate-spin", className)}
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24">
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"></circle>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
);
};
@@ -1 +0,0 @@
export * from "./NumberInput";
@@ -1,16 +0,0 @@
import { Canvas, Meta } from '@storybook/blocks';
import * as PaginationStories from './Pagination.stories';
<Meta of={PaginationStories} />
# Controls for pagination.
Here is what it looks like when empty
<Canvas of={PaginationStories.Empty} />
First Page
<Canvas of={PaginationStories.FirstPage} />
Middle
<Canvas of={PaginationStories.Middle} />
Last Page
<Canvas of={PaginationStories.LastPage} />
@@ -1,69 +0,0 @@
import type { Meta, StoryObj } from "@storybook/react";
import { Pagination } from "./Pagination";
import { withActions } from "@storybook/addon-actions/decorator";
const meta: Meta<typeof Pagination> = {
component: Pagination,
parameters: {
actions: {
handles: ["pageChange"],
},
},
decorators: [withActions],
};
type Story = StoryObj<typeof Pagination>;
export const Empty: Story = {
render: () => (
<div className="w-[300px] bg-brand-secondary-200">
<Pagination
className=""
currentPage={0}
totalPages={0}
onPageChange={() => {}}
/>
</div>
),
};
export const FirstPage: Story = {
render: () => (
<div className="w-[300px] bg-brand-secondary-200">
<Pagination
className=""
currentPage={0}
totalPages={5}
onPageChange={() => {}}
/>
</div>
),
};
export const Middle: Story = {
render: () => (
<div className="w-[300px] bg-brand-secondary-200">
<Pagination
className=""
currentPage={2}
totalPages={5}
onPageChange={() => {}}
/>
</div>
),
};
export const LastPage: Story = {
render: () => (
<div className="w-[300px] bg-brand-secondary-200">
<Pagination
className=""
currentPage={4}
totalPages={5}
onPageChange={() => {}}
/>
</div>
),
};
export default meta;
@@ -1 +0,0 @@
export * from "./Pagination";
@@ -1,11 +0,0 @@
import { Canvas, Meta } from '@storybook/blocks';
import * as PillStories from './Pill.stories';
<Meta of={PillStories} />
# Pill
A pill is a container around the children.
<Canvas of={PillStories.Default} />
@@ -1,15 +0,0 @@
import type { Meta, StoryObj } from "@storybook/react";
import { Pill } from "./Pill";
const meta: Meta<typeof Pill> = {
component: Pill,
parameters: {},
};
type Story = StoryObj<typeof Pill>;
export const Default: Story = {
render: () => <Pill>A Pill</Pill>,
};
export default meta;
@@ -1,21 +0,0 @@
import { twMerge } from "tailwind-merge";
import { ReactNode } from "react";
export const Pill = ({
className,
children,
}: {
className?: string;
children: ReactNode;
}) => {
return (
<div
className={twMerge(
"w-fit rounded-md bg-brand-primary px-1.5 py-0.5 text-xs font-bold",
className,
)}
>
{children}
</div>
);
};
@@ -1 +0,0 @@
export * from "./Pill";
@@ -1,213 +0,0 @@
import { ReactNode } from "react";
import { Popover, Transition } from "@headlessui/react";
import { twMerge } from "tailwind-merge";
import { Button } from "@storyteller/ui-button"
export interface PopoverItem {
label: string;
selected: boolean;
icon?: ReactNode;
action?: string;
disabled?: boolean;
}
interface PopoverMenuProps {
items?: PopoverItem[];
onSelect?: (item: PopoverItem) => void;
onAdd?: () => void;
triggerIcon?: ReactNode;
showAddButton?: boolean;
disableAddButton?: boolean;
showIconsInList?: boolean;
mode?: "default" | "toggle" | "button";
triggerLabel?: string;
children?: ReactNode;
buttonClassName?: string;
panelClassName?: string;
onPanelAction?: (action: string) => void;
panelTitle?: string;
position?: "top" | "bottom";
align?: "start" | "center" | "end";
panelActionLabel?: string;
}
export const PopoverMenu = ({
items = [],
onSelect,
onAdd,
triggerIcon,
showAddButton = false,
disableAddButton = false,
showIconsInList = false,
mode = "default",
triggerLabel,
children,
buttonClassName,
panelClassName,
onPanelAction,
panelTitle,
position = "top",
align = "start",
panelActionLabel,
}: PopoverMenuProps) => {
const selectedItem = items.find((item) => item.selected);
const handleItemClick = (item: PopoverItem, close: () => void) => {
if (mode === "button" && item.action && onPanelAction) {
onPanelAction(item.action);
close();
} else {
onSelect?.(item);
close();
}
};
const className = twMerge(
"text-sm font-medium rounded-lg px-3 py-2 shadow-sm",
"flex gap-2 items-center justify-center outline-none",
"transition-all duration-150",
"bg-[#5F5F68]/60 px-3 text-white hover:bg-[#5F5F68]/90",
buttonClassName,
);
const positionClasses = {
top: "bottom-full",
bottom: "top-full",
};
const alignClasses = {
start: "left-0",
center: "left-1/2 -translate-x-1/2",
end: "right-0",
};
return (
<Popover className="relative">
{({ close }) => (
<>
<Popover.Button className={className}>
<>
{triggerIcon}
{mode === "toggle" && selectedItem ? (
<span className="truncate">{selectedItem.label}</span>
) : null}
{mode === "default" && triggerLabel ? (
<span className="truncate">{triggerLabel}</span>
) : null}
</>
</Popover.Button>
<div
className={twMerge(
"absolute transform-gpu",
positionClasses[position],
alignClasses[align],
)}
>
<Transition
enter="transition duration-100 ease-out"
enterFrom={
position === "bottom"
? "translate-y-2 opacity-0"
: "-translate-y-2 opacity-0"
}
enterTo="translate-y-0 opacity-100"
leave="transition duration-100 ease-in"
leaveFrom="translate-y-0 opacity-100"
leaveTo={
position === "bottom"
? "translate-y-2 opacity-0"
: "-translate-y-2 opacity-0"
}
>
<Popover.Panel
className={twMerge(
position === "bottom" ? "origin-top" : "origin-bottom",
)}
>
<div
className={twMerge(
"z-10 min-w-48 rounded-lg bg-[#46464B] p-1.5 shadow-lg",
position === "top" && "mb-2",
position === "bottom" && "mt-2",
panelClassName,
)}
>
{panelTitle && (
<div className="mb-2 mt-0.5 flex justify-between px-1.5 text-sm font-normal text-white opacity-70">
{panelTitle}
{panelActionLabel && (
<button
onClick={() => {
onPanelAction?.(panelActionLabel);
close();
}}
className="text-end text-sm text-white/85 hover:underline"
>
{panelActionLabel}
</button>
)}
</div>
)}
{mode === "default" && children ? (
<div className="text-sm text-white">{children}</div>
) : (
<div className="flex flex-col gap-0 text-sm text-white">
{items.map((item, index) => (
<Button
key={index}
className={twMerge(
"flex items-center justify-between border-transparent bg-transparent px-1.5",
"hover:bg-[#63636B]/60",
mode === "toggle" && item.selected
? "hover:bg-[#63636B]"
: "bg-transparent",
item.disabled
? "!cursor-not-allowed opacity-50"
: "",
)}
onClick={() =>
!item.disabled && handleItemClick(item, close)
}
variant="secondary"
disabled={item.disabled}
>
<div className="flex items-center gap-2 truncate">
{showIconsInList && item.icon}
<span className="truncate">{item.label}</span>
</div>
{mode === "toggle" && (
<input
type="radio"
checked={item.selected}
readOnly
/>
)}
</Button>
))}
{showAddButton && onAdd && (
<Button
variant="secondary"
className={twMerge(
"mb-0.5 mt-2 border-none py-1",
disableAddButton
? "cursor-not-allowed bg-[#7B7B84]/50 opacity-50"
: "bg-[#7B7B84] hover:bg-[#8c8c96]",
)}
onClick={onAdd}
disabled={disableAddButton}
>
+ Add
</Button>
)}
</div>
)}
</div>
</Popover.Panel>
</Transition>
</div>
</>
)}
</Popover>
);
};
@@ -1 +0,0 @@
export * from "./Popover";
@@ -1 +0,0 @@
export * from "./SearchFilter";
@@ -1,95 +0,0 @@
import { Listbox, Transition } from "@headlessui/react";
import { faCheck, faChevronDown } from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { twMerge } from "tailwind-merge";
export type SelectValue = string | number;
export type SelectOption = { label: string; value: SelectValue };
interface ListDropdownProps {
options: SelectOption[];
onChange: (val: SelectValue) => void;
placeholder?: string;
value?: SelectValue;
id?: string;
}
export const Select = ({
onChange,
options,
placeholder,
value,
id,
}: ListDropdownProps) => {
const selectedOption = options.find(
(option: SelectOption) => option.value === value,
) || { label: placeholder || "", value: "" };
return (
<div className="relative mt-1">
<Listbox value={value || ""} onChange={onChange}>
{({ open }) => (
<>
<Listbox.Button
id={id}
className="relative h-10 w-full cursor-pointer rounded-md bg-brand-secondary py-2 pl-3 pr-10 text-left outline-none outline-offset-0 transition-all duration-150 ease-in-out focus:outline-brand-primary sm:text-sm"
>
<span
className={twMerge("block truncate", !value && "opacity-50")}
>
{selectedOption.label}
</span>
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2.5">
<FontAwesomeIcon icon={faChevronDown} aria-hidden="true" />
</span>
</Listbox.Button>
<Transition
show={open}
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Listbox.Options className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-brand-secondary py-1 text-base shadow-xl focus:outline-none sm:text-sm">
{options.map((option, itemIdx) => (
<Listbox.Option
key={itemIdx}
className={({ active, selected }) =>
twMerge(
"relative cursor-pointer select-none py-2.5 pl-8 pr-2 text-white transition-all duration-150 ease-in-out",
active && "bg-brand-primary/20 text-white",
selected && "bg-brand-primary/40",
!selected && "text-white/90",
)
}
value={option.value}
>
{({ selected }) => (
<>
<span
className={`block truncate ${
selected ? "font-medium" : "font-normal"
}`}
>
{option.label}
</span>
{selected ? (
<span className="absolute inset-y-0 left-0 flex items-center pl-2">
<FontAwesomeIcon
icon={faCheck}
aria-hidden="true"
/>
</span>
) : null}
</>
)}
</Listbox.Option>
))}
</Listbox.Options>
</Transition>
</>
)}
</Listbox>
</div>
);
};
@@ -1,130 +0,0 @@
import { Transition } from "@headlessui/react";
import React, { useState, useRef, useEffect, ReactNode } from "react";
import { twMerge } from "tailwind-merge";
interface SliderProps {
min: number;
max: number;
value: number;
onChange: (value: number) => void;
step: number;
className?: string;
showTooltip?: boolean;
tooltipContent?: ReactNode;
suffix?: string;
}
export const Slider: React.FC<SliderProps> = ({
min,
max,
value,
onChange,
step,
className,
showTooltip,
tooltipContent,
suffix,
}) => {
const [localValue, setLocalValue] = useState(value);
const sliderRef = useRef<HTMLDivElement>(null);
const [isDragging, setIsDragging] = useState(false);
const [showHandleTooltip, setShowHandleTooltip] = useState(false);
useEffect(() => {
setLocalValue(value);
}, [value]);
const updatePosition = (x: number) => {
if (!sliderRef.current) return;
const rect = sliderRef.current.getBoundingClientRect();
const fraction = (x - rect.left) / rect.width;
const newValue = fraction * (max - min) + min;
const roundedValue = Math.round(newValue / step) * step;
const clampedValue = Math.min(Math.max(roundedValue, min), max);
setLocalValue(clampedValue);
onChange(clampedValue);
};
const handleMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
e.preventDefault();
setIsDragging(true);
setShowHandleTooltip(true);
document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp);
updatePosition(e.clientX);
};
const handleMouseMove = (e: MouseEvent) => {
e.preventDefault();
updatePosition(e.clientX);
};
const handleMouseUp = (e: MouseEvent) => {
e.preventDefault();
setIsDragging(false);
setShowHandleTooltip(false);
document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mouseup", handleMouseUp);
};
const handleMouseEnter = () => {
if (!isDragging) {
setShowHandleTooltip(true);
}
};
const handleMouseLeave = () => {
if (!isDragging) {
setShowHandleTooltip(false);
}
};
const percentage = ((localValue - min) / (max - min)) * 100;
const displayValue =
localValue.toFixed(step.toString().split(".")[1]?.length || 0) +
(suffix || "");
return (
<div
ref={sliderRef}
className={twMerge(
"relative h-2.5 w-full cursor-pointer rounded-full bg-brand-secondary-900",
className,
)}
onMouseDown={handleMouseDown}
>
<div
className="absolute h-2.5 rounded-full bg-brand-primary"
style={{ width: `${percentage}%` }}
></div>
<div
className={twMerge(
"absolute -top-1 h-[18px] w-[18px] -translate-x-1/2 transform rounded-full bg-gray-200 transition-colors",
isDragging ? "bg-white" : "bg-gray-300 hover:bg-white",
)}
style={{ left: `${percentage}%` }}
onMouseDown={handleMouseDown}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
/>
<Transition
show={showHandleTooltip}
enter="transition ease-out duration-200"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition ease-in duration-150"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0"
>
{showTooltip && showHandleTooltip && (
<div
className="absolute -top-8 z-10 rounded-md bg-ui-controls px-2 py-1 text-xs text-white shadow-lg"
style={{ left: `${percentage}%`, transform: "translateX(-50%)" }}
>
{tooltipContent ? tooltipContent : displayValue}
</div>
)}
</Transition>
</div>
);
};
@@ -1 +0,0 @@
export * from "./Slider";
@@ -1,184 +0,0 @@
import { Transition } from "@headlessui/react";
import React, { useState, useRef, useEffect, ReactNode } from "react";
import { twMerge } from "tailwind-merge";
import { Button } from "../Button";
import { faMinus, faPlus } from "@fortawesome/pro-solid-svg-icons";
interface SliderProps {
min: number;
max: number;
value: number;
onChange: (value: number) => void;
step: number;
className?: string;
showTooltip?: boolean;
tooltipContent?: ReactNode;
suffix?: string;
innerLabel?: string;
showDecrement?: boolean;
showIncrement?: boolean;
}
export const SliderV2 = ({
min,
max,
value,
onChange,
step,
className,
showTooltip,
tooltipContent,
suffix,
innerLabel,
showDecrement,
showIncrement,
}: SliderProps) => {
const [localValue, setLocalValue] = useState(value);
const sliderRef = useRef<HTMLDivElement>(null);
const [isDragging, setIsDragging] = useState(false);
const [showHandleTooltip, setShowHandleTooltip] = useState(false);
useEffect(() => {
setLocalValue(value);
}, [value]);
const setClampedValue = (value: number) => {
const roundedValue = Math.round(value / step) * step;
const clampedValue = Math.min(Math.max(roundedValue, min), max);
setLocalValue(clampedValue);
onChange(clampedValue);
};
const updatePosition = (x: number) => {
if (!sliderRef.current) return;
const rect = sliderRef.current.getBoundingClientRect();
const fraction = (x - rect.left) / rect.width;
const newValue = fraction * (max - min) + min;
setClampedValue(newValue);
};
const handleMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
e.preventDefault();
setIsDragging(true);
setShowHandleTooltip(true);
document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp);
updatePosition(e.clientX);
};
const handleMouseMove = (e: MouseEvent) => {
e.preventDefault();
updatePosition(e.clientX);
};
const handleMouseUp = (e: MouseEvent) => {
e.preventDefault();
setIsDragging(false);
setShowHandleTooltip(false);
document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mouseup", handleMouseUp);
};
const handleMouseEnter = () => {
if (!isDragging) {
setShowHandleTooltip(true);
}
};
const handleMouseLeave = () => {
if (!isDragging) {
setShowHandleTooltip(false);
}
};
const handleDecrement = () => {
setClampedValue(localValue - step);
};
const handleIncrement = () => {
setClampedValue(localValue + step);
};
const percentage = ((localValue - min) / (max - min)) * 100;
const displayValue =
localValue.toFixed(step.toString().split(".")[1]?.length || 0) +
(suffix || "");
return (
<div className="flex w-full">
{showDecrement && (
<Button
icon={faMinus}
className="focus-visible:outline-primary my-auto mr-1 size-6 rounded-full bg-transparent text-white/80 hover:bg-white/10 active:bg-brand-primary/30"
onClick={handleDecrement}
/>
)}
<div
ref={sliderRef}
className={twMerge(
"glass border-ui-border group relative h-7 w-full cursor-pointer overflow-hidden rounded-lg border",
isDragging && "!bg-ui-controls/90",
className,
)}
onMouseDown={handleMouseDown}
>
<div
className={twMerge(
"absolute h-full bg-brand-primary/30 transition-colors duration-300 group-hover:bg-brand-primary/50",
isDragging && "!bg-brand-primary/50",
)}
style={{ width: `${percentage}%` }}
>
{innerLabel && (
<span
className={twMerge(
"absolute top-1/2 ml-2.5 -translate-y-1/2 text-nowrap text-sm font-medium text-white/60 transition-colors duration-300 group-hover:text-white",
isDragging && "!text-white",
)}
>
{innerLabel}
</span>
)}
<div
className={twMerge(
"absolute right-0 top-1/2 mr-1 h-4 w-0.5 -translate-y-1/2 rounded-full",
isDragging ? "bg-white" : "bg-white/50",
)}
onMouseDown={handleMouseDown}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
/>
</div>
</div>
{showIncrement && (
<Button
icon={faPlus}
className="focus-visible:outline-primary my-auto ml-1 size-6 rounded-full bg-transparent text-white/80 hover:bg-white/10 active:bg-brand-primary/30"
onClick={handleIncrement}
/>
)}
{showTooltip && (
<Transition
as={"div"}
show={showHandleTooltip}
enter="transition ease-out duration-200"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition ease-in duration-150"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0"
>
<div
className="absolute -top-8 z-10 rounded-md bg-ui-panel px-2 py-1 text-xs text-black shadow-lg"
style={{ left: `${percentage}%`, transform: "translateX(-50%)" }}
>
{tooltipContent ? tooltipContent : displayValue}
</div>
</Transition>
)}
</div>
);
};
@@ -1 +0,0 @@
export * from "./SliderV2";
@@ -1,109 +0,0 @@
import React, { useRef, useEffect, useState } from "react";
import { Tab } from "@headlessui/react";
import { twMerge } from "tailwind-merge";
import { Tooltip } from "~/components";
export interface TabItem {
id: string;
label: string;
}
interface TabSelectorProps {
tabs: TabItem[];
activeTab: string;
onTabChange: (tabId: string) => void;
className?: string;
disabled?: boolean;
disabledMessage?: string;
}
export const TabSelector: React.FC<TabSelectorProps> = ({
tabs,
activeTab,
onTabChange,
className,
disabled,
disabledMessage,
}) => {
// Find the index of the active tab
const selectedIndex = tabs.findIndex((tab) => tab.id === activeTab);
const tabsRef = useRef<(HTMLElement | null)[]>([]);
const [indicatorStyle, setIndicatorStyle] = useState({
left: 0,
width: 0,
});
// Handle tab change
const handleTabChange = (index: number) => {
onTabChange(tabs[index].id);
};
// Update the indicator position when the selected tab changes
useEffect(() => {
if (selectedIndex >= 0 && tabsRef.current[selectedIndex]) {
const currentTab = tabsRef.current[selectedIndex];
if (currentTab) {
setIndicatorStyle({
left: currentTab.offsetLeft,
width: currentTab.offsetWidth,
});
}
}
}, [selectedIndex, tabs]);
const TabGroupElement = (
<div
className={twMerge(
"w-full",
className,
disabled ? "cursor-not-allowed opacity-60" : "",
)}
>
<Tab.Group selectedIndex={selectedIndex} onChange={handleTabChange}>
<Tab.List className="glass relative inline-flex min-w-fit overflow-x-auto rounded-lg p-0.5 py-1 !shadow-none">
{/* Animated indicator */}
<div
className="absolute top-1 z-10 h-[calc(100%-8px)] rounded-md bg-brand-primary/30 transition-all duration-200 ease-in-out"
style={{
left: indicatorStyle.left,
width: indicatorStyle.width,
}}
/>
{tabs.map((tab, index) => (
<Tab
key={tab.id}
ref={(el) => (tabsRef.current[index] = el)}
disabled={disabled}
className={({ selected }) =>
twMerge(
"relative z-20 mx-0.5 min-w-max rounded-md border-2 border-transparent px-4 py-0.5 text-center text-sm font-semibold transition-all duration-200 ease-in-out",
selected ? "text-white" : "text-gray-300 hover:text-white",
disabled ? "cursor-not-allowed opacity-60" : "",
)
}
>
{tab.label}
</Tab>
))}
</Tab.List>
</Tab.Group>
</div>
);
return (
<>
{disabled && (
<Tooltip
content={
disabledMessage ?? "Cannot change tab. Generation in progress."
}
position="bottom"
>
{TabGroupElement}
</Tooltip>
)}
{!disabled && TabGroupElement}
</>
);
};
@@ -1,42 +0,0 @@
import { useState } from "react";
import { Tab } from "@headlessui/react";
import { twMerge } from "tailwind-merge";
export type TabType = {
header: string;
children: JSX.Element;
};
export const Tabs = ({ tabs: tabProps }: { tabs: TabType[] }) => {
const [tabs] = useState(tabProps);
const tabHeaderClassName = (selected: boolean) =>
twMerge(
"px-4 py-4 text-md font-medium leading-5 focus:outline-none transition duration-150 ease-in-out border-b-[3px] border-white/[.1]",
selected
? "text-white border-brand-primary"
: "text-white/[0.5] hover:text-white hover:border-white/[.3]",
);
return (
<div className="flex h-full w-full flex-col px-2 sm:px-0">
<Tab.Group defaultIndex={3}>
<Tab.List className="flex">
{tabs.map((tab, idx) => (
<Tab
key={idx}
className={({ selected }) => tabHeaderClassName(selected)}
>
{tab.header}
</Tab>
))}
</Tab.List>
<Tab.Panels className="grow overflow-auto p-4">
{tabs.map((tab, idx) => (
<Tab.Panel key={idx}>{tab.children}</Tab.Panel>
))}
</Tab.Panels>
</Tab.Group>
</div>
);
};
@@ -1 +0,0 @@
export { Tabs } from './Tabs';
@@ -1,13 +0,0 @@
import { Canvas, Meta } from '@storybook/blocks';
import * as TextareaStories from './Textarea.stories';
<Meta of={TextareaStories} />
# Textarea control
Here is the Textarea with placeholder
<Canvas of={TextareaStories.Default} />
Here is the Textarea with label
<Canvas of={TextareaStories.WithLabel} />
@@ -1,33 +0,0 @@
import type { Meta, StoryObj } from "@storybook/react";
import { Textarea } from "./Textarea";
import { withActions } from "@storybook/addon-actions/decorator";
const meta: Meta<typeof Textarea> = {
component: Textarea,
parameters: {
actions: {
handles: ["change", "blur", "focus"],
},
},
decorators: [withActions],
};
type Story = StoryObj<typeof Textarea>;
export const Default: Story = {
render: () => (
<div className="bg-action p-8">
<Textarea placeholder="field placeholder" />
</div>
),
};
export const WithLabel: Story = {
render: () => (
<div className="bg-action p-8">
<Textarea label="Label" />
</div>
),
};
export default meta;
@@ -1,54 +0,0 @@
import { TextareaHTMLAttributes } from "react";
import { twMerge } from "tailwind-merge";
import { Label } from "~/components";
import { kebabCase } from "~/utilities";
import {
disableHotkeyInput,
enableHotkeyInput,
DomLevels,
} from "~/pages/PageEnigma/signals";
type ResizeType =
| "none"
| "both"
| "horizontal"
| "vertical"
| "block"
| "inline"
| undefined;
export const Textarea = ({
className,
label,
resize = "vertical",
id,
...rest
}: TextareaHTMLAttributes<HTMLTextAreaElement> & {
label?: string;
resize?: ResizeType;
}) => {
return (
<div className="flex flex-col">
{label && <Label htmlFor={id ? id : kebabCase(label)}>{label}</Label>}
<textarea
id={id ? id : label ? kebabCase(label) : undefined}
className={twMerge(
"rounded-lg border border-[#363636] bg-brand-secondary px-3 py-2 placeholder-white/50 outline-none transition-all duration-150 ease-in-out hover:border-brand-primary/60 focus:border-brand-primary focus:outline-none",
className,
)}
style={{
resize: resize,
}}
onFocus={() => {
disableHotkeyInput(DomLevels.INPUT);
}}
onBlur={() => {
enableHotkeyInput(DomLevels.INPUT);
}}
onKeyDown={(event) => event.stopPropagation()}
{...rest}
/>
</div>
);
};
@@ -1 +0,0 @@
export * from "./Textarea";
@@ -1,149 +0,0 @@
import React, { useState, useRef, useEffect } from "react";
import { Transition } from "@headlessui/react";
import { twMerge } from "tailwind-merge";
interface TooltipProps {
children: React.ReactElement;
content: React.ReactNode;
position: "top" | "bottom" | "left" | "right";
className?: string;
delay?: number;
closeOnClick?: boolean;
}
export const Tooltip = ({
children,
content,
position,
className,
delay = 300,
closeOnClick = false,
}: TooltipProps) => {
const [isShowing, setIsShowing] = useState(false);
const triggerRef = useRef<HTMLDivElement>(null);
const tooltipRef = useRef<HTMLDivElement>(null);
const checkForOpenPopovers = () => {
if (!triggerRef.current) return false;
return (
triggerRef.current.querySelectorAll('[data-headlessui-state="open"]')
.length > 0
);
};
useEffect(() => {
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (
mutation.type === "attributes" &&
mutation.attributeName === "data-headlessui-state"
) {
const target = mutation.target as HTMLElement;
if (target.getAttribute("data-headlessui-state") === "open") {
setIsShowing(false);
}
}
});
});
if (triggerRef.current) {
observer.observe(triggerRef.current, {
attributes: true,
subtree: true,
attributeFilter: ["data-headlessui-state"],
});
}
return () => {
observer.disconnect();
};
}, []);
const getStyleForPosition = () => {
if (triggerRef.current) {
const rect = triggerRef.current.getBoundingClientRect();
switch (position) {
case "top":
return {
bottom: rect.height + 10,
left: "50%",
transform: "translateX(-50%)",
};
case "bottom":
return {
top: rect.height + 10,
left: "50%",
transform: "translateX(-50%)",
};
case "left":
return {
right: rect.width + 10,
top: "50%",
transform: "translateY(-50%)",
};
case "right":
return {
left: rect.width + 10,
top: "50%",
transform: "translateY(-50%)",
};
default:
return {};
}
}
};
const handleClick = (e: React.MouseEvent) => {
if (closeOnClick) {
setIsShowing(false);
e.stopPropagation();
}
};
return (
<div
ref={triggerRef}
onMouseEnter={() => {
if (!checkForOpenPopovers()) {
setIsShowing(true);
}
}}
onMouseLeave={() => {
setIsShowing(false);
}}
onClick={handleClick}
className="relative"
>
{children}
<Transition
show={isShowing}
enter={twMerge(
"transition ease-out duration-200",
delay ? `delay-[${delay}ms]` : "delay-[300ms]",
)}
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition ease-in duration-150"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div
ref={tooltipRef}
style={{
...getStyleForPosition(),
transitionDelay: `${delay}ms`,
transitionProperty: "opacity",
transitionDuration: "200ms",
transitionTimingFunction: "ease-out",
}}
className={twMerge(
"pointer-events-none absolute z-10 w-max rounded-lg bg-[#5F5F68] px-2.5 py-1.5 text-[13px] font-medium text-white shadow-xl",
className ? className : "",
)}
>
{content}
</div>
</Transition>
</div>
);
};
@@ -1,157 +0,0 @@
import { useEffect, Fragment, ReactNode } from "react";
import { Dialog, Transition } from "@headlessui/react";
import { twMerge } from "tailwind-merge";
import {
disableHotkeyInput,
enableHotkeyInput,
DomLevels,
} from "~/pages/PageEnigma/signals";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
IconDefinition,
faDownLeftAndUpRightToCenter,
} from "@fortawesome/pro-solid-svg-icons";
import { Tooltip } from "~/components";
const DialogBackdrop = ({ className }: { className?: string }) => {
useEffect(() => {
disableHotkeyInput(DomLevels.DIALOGUE);
return () => {
enableHotkeyInput(DomLevels.DIALOGUE);
};
}, []);
return (
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div
className={twMerge("fixed inset-0 bg-black/60", className)}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
}}
/>
</Transition.Child>
);
};
export const TransitionDialogue = ({
isOpen,
title,
titleIcon,
onTitleIconClick,
onClose,
className,
backdropClassName,
width,
children,
childPadding = true,
titleIconClassName,
showClose = true,
}: {
isOpen: boolean;
title?: ReactNode;
titleIcon?: IconDefinition;
onTitleIconClick?: () => void;
titleIconClassName?: string;
onClose: () => void;
className?: string;
backdropClassName?: string;
width?: number;
children: ReactNode;
childPadding?: boolean;
showClose?: boolean;
}) => {
return (
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-[70]" onClose={onClose}>
<DialogBackdrop className={backdropClassName} />
<div className="fixed inset-0 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-200"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel
className={twMerge(
"w-full max-w-lg transform rounded-xl",
"border border-ui-panel-border bg-[#2C2C2C]",
"text-left align-middle shadow-xl transition-all",
className,
)}
style={{ minWidth: width }}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
}}
>
{title && (
<Dialog.Title
as="div"
className="mb-5 flex justify-between p-5 pb-0 text-xl font-bold text-white"
>
<>
{onTitleIconClick ? (
<button
className="flex items-center gap-3"
onClick={onTitleIconClick}
>
{titleIcon && (
<FontAwesomeIcon
icon={titleIcon}
className={titleIconClassName}
/>
)}
{title}
</button>
) : (
<div className="flex items-center gap-3">
{titleIcon && (
<FontAwesomeIcon
icon={titleIcon}
className={titleIconClassName}
/>
)}
{title}
</div>
)}
</>
{showClose && (
<Tooltip position="top" content="Close">
<button
onClick={onClose}
className="opacity-50 transition-opacity duration-150 hover:opacity-80 focus:outline-none"
>
<FontAwesomeIcon
icon={faDownLeftAndUpRightToCenter}
/>
</button>
</Tooltip>
)}
</Dialog.Title>
)}
<div
className={`h-full ${childPadding ? "p-5 pt-0" : ""}`.trim()}
>
{children}
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
);
};
@@ -1,74 +0,0 @@
import { NavLink, NavLinkProps } from "react-router-dom";
import { LabelHTMLAttributes, ReactNode } from "react";
import { twMerge } from "tailwind-merge";
interface TypoProps {
className?: string;
children: ReactNode;
}
const baseTypo = "text-white";
export const H1 = ({ className, children }: TypoProps) => (
<h1 className={twMerge(baseTypo, "text-2xl font-medium", className)}>
{children}
</h1>
);
export const H2 = ({ className, children }: TypoProps) => (
<h2 className={twMerge(baseTypo, "text-xl font-medium", className)}>
{children}
</h2>
);
export const H3 = ({ className, children }: TypoProps) => (
<h3 className={twMerge(baseTypo, "text-lg font-medium", className)}>
{children}
</h3>
);
export const H4 = ({ className, children }: TypoProps) => (
<h4 className={twMerge(baseTypo, "text-base font-medium", className)}>
{children}
</h4>
);
export const Label = ({
className,
children,
required,
...rest
}: LabelHTMLAttributes<HTMLLabelElement> & { required?: boolean }) => (
<label
className={twMerge(baseTypo, "mb-2 text-[15px] font-medium", className)}
{...rest}
>
{children}
{required && <span className="ml-0.5 text-[#ff6467]">*</span>}
</label>
);
export const H5 = ({ className, children }: TypoProps) => (
<h5 className={twMerge(baseTypo, "text-sm font-medium", className)}>
{children}
</h5>
);
export const H6 = ({ className, children }: TypoProps) => (
<h6 className={twMerge(baseTypo, "text-sm font-normal", className)}>
{children}
</h6>
);
export const Link = ({ className, ...rest }: NavLinkProps) => (
<NavLink
className={twMerge(
"text-brand-primary transition-all duration-150 hover:text-brand-primary-400",
className as string,
)}
{...rest}
/>
);
export const P = ({ className, children }: TypoProps) => (
<p className={twMerge(baseTypo, className)}> {children} </p>
);
@@ -1,53 +0,0 @@
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faFileArrowUp, faFileAudio } from "@fortawesome/pro-solid-svg-icons";
import { P } from "~/components";
import { getFileName } from "~/utilities";
interface Props {
file: File | null;
fileTypes: string[];
}
export const DragAndDropZone = ({ file, fileTypes }: Props) => {
const fileSize =
file && file.size >= 1024 * 1024
? (file.size / 1024 / 1024).toFixed(2) + " MB"
: file
? `${Math.floor(file.size / 1024)} KB`
: null;
const fileName = file && getFileName(file).toUpperCase();
if (!file) {
return (
<div className="flex cursor-pointer items-center gap-3.5 rounded-lg border border-dashed border-[#363636] bg-brand-secondary p-3">
<FontAwesomeIcon icon={faFileArrowUp} className="text-4xl" />
<div className="flex flex-col gap-0">
<P className="font-medium">
<u>Upload a file</u> or drop it here
</P>
<P className="flex items-center gap-2 text-sm font-normal opacity-50">
{fileTypes.join(", ").toString().toUpperCase()} supported
</P>
</div>
</div>
);
} else {
return (
<div className="flex cursor-pointer items-center justify-between gap-3.5 rounded-lg border border-dashed border-[#363636] bg-brand-secondary p-3">
<FontAwesomeIcon icon={faFileAudio} className="text-4xl" />
<div className="flex grow flex-col gap-0">
<P className="font-medium">
{file.name.slice(0, file.name.lastIndexOf("."))}
</P>
<P className="flex items-center gap-2 text-sm font-normal">
<span className="opacity-50">
{`${fileName} file size: ${fileSize} `}
</span>
<u className="transition-all hover:text-white/80">Change File</u>
</P>
</div>
</div>
);
}
};
@@ -1,28 +0,0 @@
import { FileUploader as DragDropFilesUploader } from "react-drag-drop-files";
import { DragAndDropZone } from "./DragAndDropZone";
import { Label } from "../Typography";
interface Props {
title: string;
fileTypes: string[];
file: File | null;
setFile: (file: File | null) => void;
}
export const FileUploader = ({ fileTypes, title, file, setFile }: Props) => {
return (
<div className="flex w-full flex-col">
<Label required>{title}</Label>
<DragDropFilesUploader
handleChange={(file: File | null) => {
setFile(file);
}}
name="file"
types={fileTypes}
maxSize={50}
>
<DragAndDropZone file={file} fileTypes={fileTypes} />
</DragDropFilesUploader>
</div>
);
};
@@ -1 +0,0 @@
export * from "./UploadModal";
@@ -1,9 +1,9 @@
import { useCallback, useEffect, useState, useRef } from "react";
import { H6, Input, Label, ListDropdown } from "~/components";
import {
Button
} from "@storyteller/ui-button"
import { FileUploader } from "../UploadModal/FileUploader";
import { ListDropdown } from "@storyteller/ui-list-dropdown";
import { Button } from "@storyteller/ui-button";
import { Input } from "@storyteller/ui-input";
import { Label } from "@storyteller/ui-label";
import { FileUploader } from "@storyteller/ui-file-uploader";
import { loadPreviewOnCanvas, snapshotCanvasAsThumbnail } from "./utilities";
import { upload3DObjects } from "./utilities/upload3DObjects";
import { UploaderState } from "~/models";
@@ -152,7 +152,7 @@ export const UploadFiles3D = ({
/>
)}
{assetFile.error && (
<H6 className="z-10 text-red">{assetFile.error}</H6>
<h6 className="z-10 text-red">{assetFile.error}</h6>
)}
<div className="relative m-auto w-full overflow-hidden rounded-lg bg-brand-secondary">
@@ -161,18 +161,18 @@ export const UploadFiles3D = ({
ref={canvasCallbackRef}
/>
{!assetFile.value && (
<H6 className="pointer-events-auto absolute left-0 top-1/2 -mt-5 flex w-full items-center justify-center gap-2.5 text-center opacity-50">
<h6 className="pointer-events-auto absolute left-0 top-1/2 -mt-5 flex w-full items-center justify-center gap-2.5 text-center opacity-50">
<FontAwesomeIcon icon={faCube} />
Your model preview will appear here
</H6>
</h6>
)}
{previewStatus.type.includes("Error") && (
<>
<H6 className="pointer-events-auto absolute left-0 top-1/2 -mt-5 w-full text-center">
<h6 className="pointer-events-auto absolute left-0 top-1/2 -mt-5 w-full text-center">
{previewStatus.type}
{previewStatus.message && <br />}
{previewStatus.message}
</H6>
</h6>
</>
)}
</div>
@@ -1,12 +1,11 @@
import { useEffect, useState } from "react";
import { LoadingDots } from "~/components";
import { LoadingDots } from "@storyteller/ui-loading";
import { Modal } from "@storyteller/ui-modal";
import { Label } from "@storyteller/ui-label";
import { UploadAssetError } from "../UploadModal/UploadAssetError";
import { UploadSuccess } from "../UploadModal/UploadSuccess";
import { UploadAssetError, UploadSuccess } from "@storyteller/ui-upload-modal";
import { UploadFiles3D } from "./UploadFiles3D";
import { initialUploaderState, UploaderState } from "~/models";
import { Select } from "../Select";
import { Select } from "@storyteller/ui-select";
import {
FilterEngineCategories,
UploaderStates,
@@ -1,7 +1,7 @@
import { useState } from "react";
import { H6, Input } from "~/components";
import { Button } from "@storyteller/ui-button"
import { FileUploader } from "../UploadModal/FileUploader";
import { Button } from "@storyteller/ui-button";
import { Input } from "@storyteller/ui-input";
import { FileUploader } from "@storyteller/ui-file-uploader";
import { uploadPlane } from "./uploadPlane";
import { UploaderState } from "~/models";
@@ -81,14 +81,14 @@ export const UploadFilesMedia = ({
}}
/>
{assetFile.error && (
<H6 className="z-10 text-red">{assetFile.error}</H6>
<h6 className="z-10 text-red">{assetFile.error}</h6>
)}
<div className="relative m-auto aspect-square w-full overflow-hidden rounded-lg bg-brand-secondary">
{!assetFile.value && (
<H6 className="absolute left-0 top-1/2 -mt-5 w-full text-center">
<h6 className="absolute left-0 top-1/2 -mt-5 w-full text-center">
File Preview
</H6>
</h6>
)}
{assetFile.value &&
(assetFile.value.type === "video/mp4" ? (
@@ -1,9 +1,8 @@
import { useState } from "react";
import { H6, Input } from "~/components";
import { Button } from "@storyteller/ui-button"
import { H6 } from "~/components";
import { Button } from "@storyteller/ui-button";
import { Input } from "@storyteller/ui-input";
import { FileUploader } from "../UploadModal/FileUploader";
import { UploaderState } from "~/models";
import { uploadImage } from "./uploadImage";
@@ -59,7 +58,7 @@ export const UploadImageMedia = ({
return (
<>
<div className="mb-4 flex flex-col gap-4" >
<div className="mb-4 flex flex-col gap-4">
<Input
placeholder="Enter the title here"
errorMessage={uploadTitle.error}
@@ -82,34 +81,30 @@ export const UploadImageMedia = ({
}
}}
/>
{
assetFile.error && (
<H6 className="z-10 text-red" > {assetFile.error} </H6>
)
}
{assetFile.error && (
<H6 className="z-10 text-red"> {assetFile.error} </H6>
)}
<div className="relative m-auto aspect-square w-full overflow-hidden rounded-lg bg-brand-secondary" >
<div className="relative m-auto aspect-square w-full overflow-hidden rounded-lg bg-brand-secondary">
{!assetFile.value && (
<H6 className="absolute left-0 top-1/2 -mt-5 w-full text-center" >
<H6 className="absolute left-0 top-1/2 -mt-5 w-full text-center">
File Preview
</H6>
)}
{
assetFile.value &&
{assetFile.value && (
<img
alt="file upload preview"
className="m-auto max-h-full max-w-full"
src={assetFile.src}
/>
}
)}
</div>
< div className="flex justify-end gap-4" >
<Button variant="primary" onClick={handleSubmit} >
<div className="flex justify-end gap-4">
<Button variant="primary" onClick={handleSubmit}>
Upload
</Button>
< Button variant="secondary" onClick={onClose} >
<Button variant="secondary" onClick={onClose}>
Cancel
</Button>
</div>
@@ -1,10 +1,9 @@
import { useEffect, useState } from "react";
import { TransitionDialogue, LoadingDots } from "~/components";
import { LoadingDots } from "@storyteller/ui-loading";
import { Modal } from "@storyteller/ui-modal";
import { UploadAssetError } from "../UploadModal/UploadAssetError";
import { UploadSuccess } from "../UploadModal/UploadSuccess";
import { UploadFilesMedia } from "./UploadFilesMedia";
import { FilterEngineCategories, UploaderStates } from "~/enums";
import { initialUploaderState, UploaderState } from "~/models";
import { UploadImageMedia } from "./UploadImageMedia";
@@ -113,8 +112,8 @@ export function UploadImageMediaModal({
};
return (
<TransitionDialogue isOpen={isOpen} onClose={onClose} title={title}>
<Modal isOpen={isOpen} onClose={onClose} title={title}>
<UploaderModalContent />
</TransitionDialogue>
</Modal>
);
}
@@ -1,9 +1,9 @@
import { useEffect, useState } from "react";
import { TransitionDialogue, LoadingDots } from "~/components";
import { LoadingDots } from "@storyteller/ui-loading";
import { Modal } from "@storyteller/ui-modal";
import { UploadAssetError } from "../UploadModal/UploadAssetError";
import { UploadSuccess } from "../UploadModal/UploadSuccess";
import { UploadAssetError, UploadSuccess } from "@storyteller/ui-upload-modal";
import { UploadFilesMedia } from "./UploadFilesMedia";
import { FilterEngineCategories, UploaderStates } from "~/enums";
import { initialUploaderState, UploaderState } from "~/models";
@@ -99,8 +99,8 @@ export function UploadModalMedia({
};
return (
<TransitionDialogue isOpen={isOpen} onClose={onClose} title={title}>
<Modal isOpen={isOpen} onClose={onClose} title={title}>
<UploaderModalContent />
</TransitionDialogue>
</Modal>
);
}
@@ -1,114 +0,0 @@
import { useCallback, useEffect, useState, useRef } from "react";
import { twMerge } from "tailwind-merge";
import WaveSurfer from "wavesurfer.js";
import {
faCirclePlay,
faCirclePause,
faSpinnerThird,
} from "@fortawesome/pro-solid-svg-icons";
import { ButtonIcon } from "~/components";
export const WaveformPlayer = ({
audio,
hasPlayButton,
onLoad,
}: {
audio: string;
hasPlayButton?: boolean;
onLoad?: ({ duration }: { duration: number }) => void;
}) => {
const waveSurferRef = useRef<WaveSurfer | undefined>(undefined);
const [{ isPlaying, isLoading }, setState] = useState<{
isLoading: boolean;
isPlaying: boolean;
}>({
isLoading: true,
isPlaying: false,
});
const containerRef = useCallback(
(node: HTMLDivElement) => {
const toggleIsPlaying = (newIsPlaying: boolean) => {
setState((curr) => ({ ...curr, isPlaying: newIsPlaying }));
};
const toggleIsLoading = (newIsLoading: boolean) => {
setState((curr) => ({ ...curr, isLoading: newIsLoading }));
};
if (node) {
if (waveSurferRef.current) {
//need to destroy previous wavesurfer to not have doubles
waveSurferRef.current.destroy();
toggleIsLoading(true);
}
const waveSurfer = WaveSurfer.create({
container: node,
barWidth: 2,
height: 24,
cursorWidth: 0,
waveColor: "#D7C8C8",
progressColor: "#FB8381",
});
waveSurfer.load(audio);
waveSurfer.on("ready", () => {
waveSurferRef.current = waveSurfer;
// console.log("here");
toggleIsLoading(false);
if (onLoad) onLoad({ duration: waveSurfer.getDuration() });
});
waveSurfer.on("play", () => {
toggleIsPlaying(true);
});
waveSurfer.on("pause", () => {
toggleIsPlaying(false);
});
}
},
[audio],
);
useEffect(() => {
return () => {
//destructor on unmount
waveSurferRef.current?.destroy();
};
}, []);
return (
<div className="flex items-center gap-2 py-1">
{hasPlayButton && (
<ButtonIcon
icon={
isLoading
? faSpinnerThird
: isPlaying
? faCirclePause
: faCirclePlay
}
className="w-auto bg-transparent p-0 text-2xl hover:bg-transparent hover:opacity-75"
spin={isLoading}
onClick={() => {
if (!isLoading) waveSurferRef.current?.playPause();
}}
/>
)}
<div className="relative h-10 grow overflow-hidden">
<span
className={twMerge(
"absolute top-[18px] w-full border-t border-dotted border-t-white transition-opacity",
isLoading ? "opacity-100" : "opacity-0",
)}
/>
<div
ref={containerRef}
className={twMerge(
"absolute top-[7px] w-full transition-opacity",
isLoading ? "opacity-0" : "opacity-100",
)}
/>
</div>
</div>
);
};
@@ -1,32 +1,2 @@
export * from "./Badge";
export * from "./ButtonDropdown";
export * from "./ButtonIcon";
export * from "./ButtonIconSelect";
export * from "./ButtonLink";
export * from "./CloseButton";
export * from "./ConfirmationModal";
export * from "./FileUploader";
export * from "./FilterButtons";
export * from "./Gravatar";
export * from "./Input";
export * from "./InputVector";
export * from "./ListDropdown";
export * from "./LoadingBar";
export * from "./LoadingDots";
export * from "./LoadingSpinner";
export * from "./NumberInput";
export * from "./Pagination";
export * from "./Pill";
export * from "./Popover";
export * from "./SearchFilter";
export * from "./Select";
export * from "./Slider";
export * from "./Tabs";
export * from "./Textarea";
export * from "./Tooltip";
export * from "./TransitionDialogue";
export * from "./Typography";
export * from "./UploadModal";
export * from "./UploadModal3D";
export * from "./UploadModalMedia";
export * from "./WaveformPlayer";
@@ -1,8 +1,8 @@
import { useSignals } from "@preact/signals-react/runtime";
import { faTriangleExclamation } from "@fortawesome/pro-solid-svg-icons";
import { TransitionDialogue } from "~/components";
import { Button } from "@storyteller/ui-button"
import { Modal } from "@storyteller/ui-modal";
import { Button } from "@storyteller/ui-button";
import {
showErrorDialog,
errorDialogMessage,
@@ -13,7 +13,7 @@ export function ErrorDialog() {
useSignals();
return (
<TransitionDialogue
<Modal
title={errorDialogTitle.value}
titleIcon={faTriangleExclamation}
titleIconClassName="text-brand-primary"
@@ -33,6 +33,6 @@ export function ErrorDialog() {
</Button>
</div>
</div>
</TransitionDialogue>
</Modal>
);
}
@@ -1,7 +1,7 @@
import { useSignals } from "@preact/signals-react/runtime";
import { AUTH_STATUS } from "~/enums";
import { authentication } from "~/signals";
import { ButtonLink } from "~/components";
import { ButtonLink } from "@storyteller/ui-button-link";
import ProfileDropdown from "./ProfileDropdown";
export const AuthButtons = () => {
@@ -7,9 +7,8 @@ import {
faUser,
} from "@fortawesome/pro-solid-svg-icons";
import { Menu, Transition } from "@headlessui/react";
import { Gravatar } from "~/components/reusable";
import { Gravatar } from "@storyteller/ui-gravatar";
import { twMerge } from "tailwind-merge";
import { authentication, logout } from "~/signals";
export default function ProfileDropdown() {
@@ -3,15 +3,14 @@ import { twMerge } from "tailwind-merge";
import { useSignals } from "@preact/signals-react/runtime";
import { faPencil, faSpinnerThird } from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { scene, signalScene, authentication, currentPage } from "~/signals";
import { scene, signalScene, authentication } from "~/signals";
import {
showErrorDialog,
errorDialogMessage,
errorDialogTitle,
} from "~/pages/PageEnigma/signals";
import { Input } from "~/components";
import { Input } from "@storyteller/ui-input";
import { MediaFilesApi } from "~/Classes/ApiManager/MediaFilesApi";
import { Pages } from "~/pages/PageEnigma/constants/page";
interface Props {
pageName: string;
@@ -9,9 +9,8 @@ import {
faImage,
} from "@fortawesome/pro-solid-svg-icons";
import { useLocation, useParams } from "react-router-dom";
import { Button } from "@storyteller/ui-button"
import { ButtonLink } from "~/components";
import { Button } from "@storyteller/ui-button";
import { ButtonLink } from "@storyteller/ui-button-link";
import { AuthButtons } from "./AuthButtons";
import { SceneTitleInput } from "./SceneTitleInput";
import { getCurrentLocationWithoutParams } from "~/utilities";
@@ -1,7 +1,7 @@
import { ComponentType, useEffect, useRef } from "react";
import { useNavigate, useLocation } from "react-router-dom";
import { useSignals, useSignalEffect } from "@preact/signals-react/runtime";
import { LoadingDots } from "~/components";
import { LoadingDots } from "@storyteller/ui-loading";
import { authentication, persistLogin } from "~/signals";
import { AUTH_STATUS } from "~/enums";
import { TWO_SECONDS } from "~/constants";
-2
View File
@@ -55,9 +55,7 @@ const GlobalSettingsManager = ({ env }: { env: Record<string, string> }) => {
pageWidth.value = window.innerWidth;
}
useEffect(() => {
setPage();
window.addEventListener("resize", setPage);
return () => {
+2 -1
View File
@@ -13,7 +13,8 @@ import { AUTH_STATUS } from "~/enums";
import { authentication, login, logout } from "~/signals";
import { createRoot } from "react-dom/client";
import { Input, ConfirmationModal } from "~/components";
import { ConfirmationModal } from "~/components/reusable/ConfirmationModal";
import { Input } from "@storyteller/ui-input";
import { Button } from "@storyteller/ui-button";
import "./styles/normalize.css";
@@ -30,12 +30,9 @@ export type TextFormatData = {
export type TextNodeData = {
text: string;
} & TextFormatData;
import { useState } from "react";
import { DialogTitle } from "@headlessui/react";
import { Button, TextEditor } from "../../../components/ui";
import { TextEditor } from "../../../components/ui";
import { Button } from "@storyteller/ui-button";
import { Modal } from "@storyteller/ui-modal";
export const DialogEditText = ({
@@ -1,13 +1,16 @@
import { twMerge } from "tailwind-merge";
import { dialogPanelStyles, paperWrapperStyles } from "../../../components/styles";
import {
dialogPanelStyles,
paperWrapperStyles,
} from "../../../components/styles";
import {
Description,
Dialog,
DialogPanel,
DialogTitle,
} from "@headlessui/react";
import { Button } from "../../../components/ui";
import { Button } from "@storyteller/ui-button";
export const DialogError = ({
isShowing,
@@ -1,5 +1,6 @@
import { faCameraMovie, faPlayCircle } from "@fortawesome/pro-solid-svg-icons";
import { Button, Tooltip } from "~/components/ui";
import { Button } from "@storyteller/ui-button";
import { Tooltip } from "@storyteller/ui-tooltip";
import { ToolbarButtonProps } from "../ToolbarButton";
export const ButtonPreviewAndRender = ({
@@ -9,10 +10,7 @@ export const ButtonPreviewAndRender = ({
buttonPreviewProps: ToolbarButtonProps;
buttonRenderProps: ToolbarButtonProps;
}) => {
return (
<div className="flex">
</div>
);
return <div className="flex"></div>;
};
const ButtonPreview = (buttonProps: ToolbarButtonProps) => {
const {
@@ -1,8 +1,6 @@
import { faFileArrowUp, faFileAudio } from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { P } from "~/components";
export const DragAndDropZone = ({
file,
fileTypes,
@@ -15,13 +13,13 @@ export const DragAndDropZone = ({
<div className="flex cursor-pointer items-center gap-3.5 rounded-lg border border-dashed border-[#363636] bg-brand-secondary p-3">
<FontAwesomeIcon icon={faFileArrowUp} className="text-4xl" />
<div className="flex flex-col gap-0">
<P className="font-medium">
<p className="font-medium">
<u>Upload a file</u> or drop it here
</P>
</p>
<P className="flex items-center gap-2 text-sm font-normal opacity-50">
<p className="flex items-center gap-2 text-sm font-normal opacity-50">
{fileTypes.join(", ").toString()} supported
</P>
</p>
</div>
</div>
);
@@ -37,15 +35,15 @@ export const DragAndDropZone = ({
<div className="flex cursor-pointer items-center justify-between gap-3.5 rounded-lg border border-dashed border-[#363636] bg-brand-secondary p-3">
<FontAwesomeIcon icon={faFileAudio} className="text-4xl" />
<div className="flex grow flex-col gap-0">
<P className="font-medium">
<p className="font-medium">
{file.name.slice(0, file.name.lastIndexOf("."))}
</P>
<P className="flex items-center gap-2 text-sm font-normal">
</p>
<p className="flex items-center gap-2 text-sm font-normal">
<span className="opacity-50">
{`${fileName} file size: ${fileSize} `}
</span>
<u className="transition-all hover:text-white/80">Change File</u>
</P>
</p>
</div>
</div>
);
@@ -1,12 +1,12 @@
import { useEffect, useState } from "react";
import { faBell, faSpinnerThird } from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { PopoverMenu } from "~/components/reusable/Popover";
import { PopoverMenu } from "@storyteller/ui-popover";
import { CompletedCard } from "./CompletedCard";
import { InProgressCard } from "./InProgressCard";
import { useJobContext } from "~/components/JobContext";
import { Api } from "~/KonvaApp/Api";
import { toast } from "sonner";
import { toast } from "@storyteller/ui-toaster";
import { Tooltip } from "@storyteller/ui-tooltip";
// Dummy data types
@@ -133,7 +133,7 @@ export function Activity() {
</>
</span>
{jobs.length > 0 && (
<div className="absolute -right-1 -top-1 h-3 w-3 animate-pulse rounded-full bg-blue-500" />
<div className="bg-blue-500 absolute -right-1 -top-1 h-3 w-3 animate-pulse rounded-full" />
)}
</div>
}
@@ -1,86 +0,0 @@
import React from "react";
import { twMerge } from "tailwind-merge";
import { IconDefinition } from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { kebabCase } from "~/utilities";
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
inputClassName?: string;
iconClassName?: string;
label?: string;
icon?: IconDefinition;
isError?: boolean;
errorMessage?: string;
}
export const Input = React.forwardRef(
(
{
label,
icon,
inputClassName,
iconClassName,
className,
id,
isError,
onBlur,
onFocus,
errorMessage,
...rest
}: InputProps,
ref: React.ForwardedRef<HTMLInputElement>,
) => {
return (
<div className={twMerge("flex flex-col", className)}>
{label && (
<label
htmlFor={id ? id : kebabCase(label)}
className="pb-2 font-medium"
>
{label}
</label>
)}
<div className="relative w-full">
{icon && (
<FontAwesomeIcon
icon={icon}
className={twMerge(
"absolute pl-3.5 pt-3 text-sm opacity-70",
iconClassName,
)}
/>
)}
<input
ref={ref}
id={id ? id : label ? kebabCase(label) : undefined}
className={twMerge(
"h-10 w-full rounded-md px-3 py-2.5 outline-1 outline-ui-border",
// on focus
"outline-offset-0 transition-all duration-150 ease-in-out focus:outline-primary",
icon && "pl-10",
isError && "outline-red focus:outline-red",
inputClassName,
)}
onFocus={(e: React.FocusEvent<HTMLInputElement>) => {
if (onFocus) {
onFocus(e);
}
}}
onBlur={(e: React.FocusEvent<HTMLInputElement>) => {
if (onBlur) {
onBlur(e);
}
}}
{...rest}
/>
{errorMessage && (
<h6 className="text-red absolute z-10">{errorMessage}</h6>
)}
</div>
</div>
);
},
);
Input.displayName = "Input";
@@ -2,8 +2,7 @@ import React, { useEffect, useState } from "react";
import { twMerge } from "tailwind-merge";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faChevronDown, faChevronUp } from "@fortawesome/pro-solid-svg-icons";
import { Input } from "./Input";
import { Input } from "@storyteller/ui-input";
interface NumberInputProps {
value: number;
@@ -11,7 +11,7 @@ import { twMerge } from "tailwind-merge";
import { paperWrapperStyles } from "../../../components/styles";
import { Button } from "../Button";
import { Input } from "../Input";
import { Input } from "@storyteller/ui-input";
import { HexEyedropPicker } from "../HexEyedropPicker";
export const ColorPicker = ({
@@ -8,12 +8,11 @@ import { Popover, PopoverButton, PopoverPanel } from "@headlessui/react";
import { ChangeEvent, useEffect, useState } from "react";
import { HexColorPicker } from "react-colorful";
import { twMerge } from "tailwind-merge";
import { useSignals } from "@preact/signals-react/runtime";
import { paperWrapperStyles } from "~/components/styles";
import { Button } from "../Button";
import { Button } from "@storyteller/ui-button";
import { HexEyedropPicker } from "../HexEyedropPicker";
import { Input } from "../Input";
import { Input } from "@storyteller/ui-input";
export const BackgroundMenu = ({
color: prevColor,
@@ -90,7 +89,7 @@ export const BackgroundMenu = ({
if (streamChanges) {
onChange(color);
}
}
};
// Rerender the component when these signals change:
useSignals();
@@ -11,11 +11,18 @@ import { twMerge } from "tailwind-merge";
import { useSignals } from "@preact/signals-react/runtime";
import { paperWrapperStyles } from "~/components/styles";
import { BRUSH_MAX_SIZE, BRUSH_MIN_SIZE, paintBrushSize, setPaintBrushSize } from "~/signals/uiEvents/toolbarMain/paintMode";
import { Button } from "../Button";
import { Input } from "../Input";
import { Slider } from "../Slider";
import { eraseBrushSize, setEraseBrushSize } from "~/signals/uiEvents/toolbarMain/eraseMode";
import {
BRUSH_MAX_SIZE,
BRUSH_MIN_SIZE,
paintBrushSize,
setPaintBrushSize,
} from "~/signals/uiEvents/toolbarMain/paintMode";
import { Slider } from "@storyteller/ui-slider";
import {
eraseBrushSize,
setEraseBrushSize,
} from "~/signals/uiEvents/toolbarMain/eraseMode";
export const EraseModeMenu = ({
faIcon = faFont,
@@ -60,9 +67,7 @@ export const EraseModeMenu = ({
)}
onMouseEnter={() => defaultOpen && setIsOpen(true)}
>
<FontAwesomeIcon
icon={faIcon}
/>
<FontAwesomeIcon icon={faIcon} />
</PopoverButton>
{(open || isOpen) && (
<div onMouseLeave={() => handleMouseLeave(close)}>
@@ -70,7 +75,7 @@ export const EraseModeMenu = ({
anchor={anchor}
className={twMerge(
paperWrapperStyles,
"flex flex-col items-center gap-2 overflow-hidden w-[198px]",
"flex w-[198px] flex-col items-center gap-2 overflow-hidden",
)}
style={
{
@@ -79,8 +84,10 @@ export const EraseModeMenu = ({
}
static
>
<div className="flex flex-col w-full gap-2">
<p className="text-white font-medium text-sm w-full justify-start">Eraser Size:</p>
<div className="flex w-full flex-col gap-2">
<p className="w-full justify-start text-sm font-medium text-white">
Eraser Size:
</p>
<Slider
min={BRUSH_MIN_SIZE}
max={BRUSH_MAX_SIZE}
@@ -8,7 +8,6 @@ import { Popover, PopoverButton, PopoverPanel } from "@headlessui/react";
import { ChangeEvent, useEffect, useState, useRef } from "react";
import { HexAlphaColorPicker } from "react-colorful";
import { twMerge } from "tailwind-merge";
import { useSignals } from "@preact/signals-react/runtime";
import { paperWrapperStyles } from "../../../components/styles";
import {
@@ -17,9 +16,9 @@ import {
paintBrushSize,
setPaintBrushSize,
} from "../../../signals/uiEvents/toolbarMain/paintMode";
import { Button } from "../Button";
import { Input } from "../Input";
import { Slider } from "../Slider";
import { Button } from "@storyteller/ui-button";
import { Input } from "@storyteller/ui-input";
import { SliderV2 } from "@storyteller/ui-sliderv2";
import { HexEyedropPicker } from "../HexEyedropPicker";
export const PaintModeMenu = ({
@@ -206,7 +205,7 @@ export const PaintModeMenu = ({
<p className="w-full justify-start text-sm font-medium text-white">
Brush Size:
</p>
<Slider
<SliderV2
min={BRUSH_MIN_SIZE}
max={BRUSH_MAX_SIZE}
value={brushSize}
@@ -1,5 +1,3 @@
export * from "./Button";
export * from "./Input";
export * from "./InputNumber";
export * from "./LoadingBar";
export * from "./LoadingSpinner";
@@ -1,24 +1,25 @@
import { faChevronLeft } from "@fortawesome/pro-solid-svg-icons";
import { P, H1, ButtonLink } from "~/components";
import { P, H1 } from "~/components";
import { json } from "react-router-dom";
import { ButtonLink } from "@storyteller/ui-button-link";
export const loader = () => {
return json(null, { status: 404 });
};
export const Page404 = () => {
return(
return (
<div
className="fixed w-full overflow-scroll"
style={{height: "calc(100% - 72px)"}}
className="fixed w-full overflow-scroll"
style={{ height: "calc(100% - 72px)" }}
>
<div
className='bg-ui-panel w-10/12 max-w-7xl h-1/2 mx-auto my-24 rounded-lg p-6 flex flex-col items-center justify-center gap-8 text-center'
>
<div className="mx-auto my-24 flex h-1/2 w-10/12 max-w-7xl flex-col items-center justify-center gap-8 rounded-lg bg-ui-panel p-6 text-center">
<P>Oops, seems like this is not a page that exists!</P>
<H1>Server Error: 404</H1>
<ButtonLink to="/" icon={faChevronLeft}>Back to Dashboard</ButtonLink>
<ButtonLink to="/" icon={faChevronLeft}>
Back to Dashboard
</ButtonLink>
</div>
</div>
);
@@ -3,7 +3,7 @@ import EnvironmentVariables from "~/Classes/EnvironmentVariables";
import { persistLogin } from "~/signals";
import { pageHeight, pageWidth } from "~/signals";
import { useEffect } from "react";
import { showWizard } from "~/pages/PageEnigma/Wizard/signals/wizard";
export const GlobalSettingsManager = ({ env }: { env: Record<string, string> }) => {
@@ -35,17 +35,7 @@ export const GlobalSettingsManager = ({ env }: { env: Record<string, string> })
pageWidth.value = window.innerWidth;
}
function initWizard() {
if (showWizard.value) {
return;
}
const wizard = localStorage.getItem("storyteller-wizard");
showWizard.value = wizard ? "" : "initial";
localStorage.setItem("storyteller-wizard", "shown");
}
useEffect(() => {
initWizard();
setPage();
window.addEventListener("resize", setPage);
return () => {
@@ -1,6 +1,6 @@
import React, { useContext, useEffect } from "react";
import { useSignals } from "@preact/signals-react/runtime";
import { LoadingDots, TopBar } from "~/components";
import { TopBar } from "~/components";
import { Controls3D } from "./comps/Controls3D";
import { ControlsTopButtons } from "./comps/ControlsTopButtons";
import { ControlPanelSceneObject } from "./comps/ControlPanelSceneObject";
@@ -28,6 +28,7 @@ import { Outliner } from "./comps/Outliner";
import { CameraAspectRatio } from "./enums";
import { PromptBox3D } from "@storyteller/ui-promptbox";
import { PopoverItem } from "@storyteller/ui-popover";
import { LoadingDots } from "@storyteller/ui-loading";
import { OnboardingHelper } from "./comps/OnboardingHelper";
import { FocalLengthDisplay } from "./comps/FocalLengthDisplay/FocalLengthDisplay";
import { appTabId, is3DEditorInitialized, setAppTabId } from "~/signals/appTab";
@@ -456,7 +457,7 @@ export const PageEditor = () => {
</div>
<LoginModal
onClose={() => {}}
onClose={() => { }}
videoSrc2D="/resources/videos/artcraft-canvas-demo.mp4"
videoSrc3D="/resources/videos/artcraft-3d-demo.mp4"
openAiLogo="/resources/images/openai-logo.png"
@@ -4,7 +4,7 @@ import { EngineProvider } from "./contexts/EngineContext";
import { useActiveJobs } from "~/hooks/useActiveJobs";
import { useBackgroundLoadingMedia } from "~/hooks/useBackgroundLoadingMedia";
import { useQueueHandler } from "./hooks/useQueueHandler";
import { ErrorDialog, LoadingDots } from "~/components";
import { ErrorDialog } from "~/components";
import { GenerateModals } from "~/pages/PageEnigma/comps/GenerateModals/GenerateModals";
import { Toaster } from "@storyteller/ui-toaster";
import { EditorLoadingBar } from "./comps/EditorLoadingBar";
@@ -25,6 +25,7 @@ import { useImageGenerationFailureEvent } from "./TauriEvents/useImageGeneration
import { useImageGenerationSuccessEvent } from "./TauriEvents/useImageGenerationSuccessEvent";
import { PageEditor } from "~/pages/PageEnigma/PageEditor";
import { GalleryDragComponent } from "@storyteller/ui-gallery-modal";
import { LoadingDots } from "@storyteller/ui-loading";
export const PageEnigma = ({ sceneToken }: { sceneToken?: string }) => {
useSignals();
@@ -106,23 +107,23 @@ export const PageEnigma = ({ sceneToken }: { sceneToken?: string }) => {
}
return (
<EngineProvider sceneToken={sceneToken}>
<PageEditor />
<DragComponent />
<GalleryDragComponent />
<PrecisionSelector
showSignal={showPrecisionSelector}
coordSignal={precisionSelectorMenuCoords}
valuesSignal={precisionSelectorValues}
selectedValueSignal={precisionSelectedValue}
/>
<GenerateModals />
<ErrorDialog />
<EditorLoadingBar/>
<EngineProvider sceneToken={sceneToken}>
<PageEditor />
<DragComponent />
<GalleryDragComponent />
<PrecisionSelector
showSignal={showPrecisionSelector}
coordSignal={precisionSelectorMenuCoords}
valuesSignal={precisionSelectorValues}
selectedValueSignal={precisionSelectedValue}
/>
<GenerateModals />
<ErrorDialog />
<EditorLoadingBar />
<Toaster offsetTop={70} offsetRight={12} />
</EngineProvider>
</EngineProvider>
);
};
@@ -20,7 +20,7 @@ import { StyleMoreOptions } from "~/pages/PageEnigma/comps/StyleMoreOptions/Styl
import Queue, { QueueNames } from "~/pages/PageEnigma/Queue";
import { toTimelineActions } from "~/pages/PageEnigma/Queue/toTimelineActions";
import { appTabId, is3DEditorInitialized, setAppTabId } from "~/signals/appTab";
// TODO: Delete Page Styling?
export const PageStyling = () => {
useSignals();
const editorEngine = useContext(EngineContext);
@@ -9,7 +9,7 @@ import { cameraAspectRatio } from "~/pages/PageEnigma/signals";
import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames";
import Queue from "~/pages/PageEnigma/Queue/Queue";
import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions";
import { ButtonDropdown } from "~/components";
import { ButtonDropdown } from "@storyteller/ui-button-dropdown";
export const AspectRatioMenu = () => {
useSignals();
@@ -1,13 +1,15 @@
import { TransitionDialogue } from "~/components/reusable/TransitionDialogue";
import { Modal } from "@storyteller/ui-modal";
import { faPlus, faTrashAlt } from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { PopoverItem } from "~/components/reusable/Popover";
import { Input, Label, Tooltip } from "~/components";
import { PopoverItem } from "@storyteller/ui-popover";
import { Input } from "@storyteller/ui-input";
import { Label } from "@storyteller/ui-label";
import { Button } from "@storyteller/ui-button";
import { SliderV2 } from "~/components/reusable/SliderV2/SliderV2";
import { SliderV2 } from "@storyteller/ui-sliderv2";
import { focalLengthDragging } from "~/pages/PageEnigma/signals/camera";
import { useState, useEffect } from "react";
import { twMerge } from "tailwind-merge";
import { Tooltip } from "@storyteller/ui-tooltip";
interface ExtendedPopoverItem extends PopoverItem {
id: string;
@@ -91,7 +93,7 @@ export const CameraSettingsModal = ({
};
return (
<TransitionDialogue
<Modal
isOpen={isOpen}
onClose={onClose}
className={twMerge(
@@ -223,6 +225,6 @@ export const CameraSettingsModal = ({
</div>
</div>
</div>
</TransitionDialogue>
</Modal>
);
};
@@ -10,13 +10,14 @@ import {
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
disableHotkeyInput,
enableHotkeyInput,
objectPanel as objectPanelSignals,
timelineHeight,
} from "../../signals";
import { EngineContext } from "~/pages/PageEnigma/contexts/EngineContext";
import { H5, InputVector } from "~/components";
import { Button } from "@storyteller/ui-button"
import { InputVector } from "@storyteller/ui-input";
import { Button } from "@storyteller/ui-button";
import { editorState } from "~/pages/PageEnigma/signals/engine";
import { twMerge } from "tailwind-merge";
import { EditorStates } from "~/pages/PageEnigma/enums";
@@ -222,7 +223,7 @@ export const ControlPanelSceneObject = () => {
className={"flex flex-col gap-2 overflow-y-auto"}
>
<div className="flex flex-col gap-1">
<H5>Color</H5>
<h5>Color</h5>
<input
className="h-0 w-0 cursor-pointer opacity-0"
id={colorInputId}
@@ -246,18 +247,20 @@ export const ControlPanelSceneObject = () => {
></Button>
</div>
<div className="flex flex-col gap-1">
<H5>Location</H5>
<h5>Location</h5>
<InputVector
x={localPosition.x.toString()}
y={localPosition.y.toString()}
z={localPosition.z.toString()}
onChange={handlePositionChange}
disabled={locked}
enableHotkeyInput={enableHotkeyInput}
disableHotkeyInput={disableHotkeyInput}
/>
</div>
<div className="flex flex-col gap-1">
<H5>Rotation</H5>
<h5>Rotation</h5>
<InputVector
x={localRotation.x.toString()}
y={localRotation.y.toString()}
@@ -265,6 +268,8 @@ export const ControlPanelSceneObject = () => {
onChange={handleRotationChange}
increment={1}
disabled={locked}
enableHotkeyInput={enableHotkeyInput}
disableHotkeyInput={disableHotkeyInput}
/>
</div>
@@ -278,6 +283,8 @@ export const ControlPanelSceneObject = () => {
z={localScale.z.toString()}
onChange={handleScaleChange}
disabled={locked}
enableHotkeyInput={enableHotkeyInput}
disableHotkeyInput={disableHotkeyInput}
/>
</div>
</Transition>
@@ -5,9 +5,9 @@ import {
faPlus,
faUpRightAndDownLeftFromCenter,
} from "@fortawesome/pro-solid-svg-icons";
import { ButtonIconSelect, Tooltip } from "~/components";
import { Button } from "@storyteller/ui-button"
import { ButtonIconSelect } from "@storyteller/ui-button-icon-select";
import { Button } from "@storyteller/ui-button";
import { Tooltip } from "@storyteller/ui-tooltip";
import { EngineContext } from "../../contexts/EngineContext";
import { useContext, useEffect, useState } from "react";
import { assetModalVisibleDuringDrag, assetModalVisible } from "../../signals";
@@ -4,9 +4,9 @@ import { useSignals, useSignalEffect } from "@preact/signals-react/runtime";
import {
faCheckSquare,
faFile,
faQuestion,
faSquare,
} from "@fortawesome/pro-solid-svg-icons";
import {
EditorExpandedI,
EngineContext,
@@ -14,15 +14,9 @@ import {
import { ToastTypes, getArtStyle } from "~/enums";
import { scene, signalScene, authentication, addToast } from "~/signals";
import { outlinerIsShowing } from "~/pages/PageEnigma/signals/outliner/outliner";
import {
ButtonDropdown,
Input,
H4,
} from "~/components";
import {Button} from "@storyteller/ui-button";
import { ButtonDropdown } from "@storyteller/ui-button-dropdown";
import { Input } from "@storyteller/ui-input";
import { Button } from "@storyteller/ui-button";
import { TestFeaturesButtons } from "./TestFeaturesButtons";
import { LoadUserScenes } from "./LoadUserScenes";
import { getCurrentLocationWithoutParams, isNumberString } from "~/utilities";
@@ -40,14 +34,15 @@ import {
} from "~/pages/PageEnigma/signals";
import { CameraAspectRatio } from "~/pages/PageEnigma/enums";
import { twMerge } from "tailwind-merge";
import { Help } from "./Help";
import { Modal } from "@storyteller/ui-modal";
export const ControlsTopButtons = () => {
useSignals();
const params = useParams();
const location = useLocation();
const navigate = useNavigate();
const [helpIsShowing, setHelpIsShowing] = useState(false);
const editorEngine = useContext(EngineContext);
const [sceneTitleInput, setSceneTitleInput] = useState<string>("");
@@ -191,8 +186,7 @@ export const ControlsTopButtons = () => {
label: "New scene",
description: "Ctrl+N",
onClick: () => {
textInput.value = "Untitled New Scene";
showWizard.value = "new_scene";
setSceneTitleInput("Untitled New Scene");
},
},
{
@@ -216,19 +210,21 @@ export const ControlsTopButtons = () => {
},
},
{
disabled:
!(scene.value.isModified &&
(scene.value.ownerToken === undefined || scene.value.ownerToken ===
authentication.userInfo.value?.user_token)),
disabled: !(
scene.value.isModified &&
(scene.value.ownerToken === undefined ||
scene.value.ownerToken ===
authentication.userInfo.value?.user_token)
),
// save scene should be disabled if there are no changes
label: "Save scene",
description: "Ctrl+S",
dialogProps: {
title: "Save Scene",
content: (
<H4>
<h4>
Save scene to <b>{scene.value.title}</b>?
</H4>
</h4>
),
confirmButtonProps: {
label: "Save",
@@ -295,29 +291,25 @@ export const ControlsTopButtons = () => {
Outliner
</Button>
{/* <ButtonDialogue
buttonProps={{
variant: "secondary",
label: "Help",
className: "shadow-xl",
icon: faQuestion,
}}
dialogProps={{
className: "max-w-6xl w-auto",
}}
title={
<>
Help
<span className="text-sm font-medium opacity-60">
@%CURRENT_STORYTELLER_GIT_VERSION%
</span>
</>
}
<Button
icon={faQuestion}
variant="secondary"
className="shadow-xl"
onClick={() => setHelpIsShowing(true)}
>
<Help />
</ButtonDialogue> */}
Help
</Button>
</div>
<TestFeaturesButtons debug={false} />
<Modal
isOpen={helpIsShowing}
onClose={() => setHelpIsShowing(false)}
title="Help"
className="h-[500px] max-w-4xl"
>
<Help />
</Modal>
</div>
);
};
@@ -1,4 +1,4 @@
import { Label } from "~/components";
import { Label } from "@storyteller/ui-label";
export const ShortcutsGroup = (props: {
label: string;

Some files were not shown because too many files have changed in this diff Show More