mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-21 17:28:24 -04:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6526cac55e |
@@ -4,7 +4,7 @@
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
@apply bg-ui-background font-sans text-white;
|
||||
@apply select-none bg-ui-background font-sans text-white;
|
||||
}
|
||||
.pegboard {
|
||||
background-image: radial-gradient(#ffffff25 1px, transparent 0);
|
||||
@@ -42,7 +42,7 @@
|
||||
}
|
||||
|
||||
input {
|
||||
@apply bg-ui-controls border-0 px-2 py-1 outline-none -outline-offset-2 focus:outline-primary-500 focus:ring-0;
|
||||
@apply border-0 bg-ui-controls px-2 py-1 outline-none -outline-offset-2 focus:outline-primary-500 focus:ring-0;
|
||||
}
|
||||
|
||||
textarea {
|
||||
|
||||
@@ -9,6 +9,7 @@ import { ToolbarUserProfile } from "~/components/features";
|
||||
import { KonvaRootComponent } from "~/KonvaRootComponent";
|
||||
import { ToolbarTopLeft } from "~/components/features/ToolbarTopLeft";
|
||||
import { ToolbarTopRight } from "~/components/features/ToolbarTopRight";
|
||||
import { ToolbarTopCenter } from "~/components/features/ToolbarTopCenter";
|
||||
|
||||
export const Main = withProtectionRoute(() => {
|
||||
// This is a hook that will log the number of times the component has rerendered
|
||||
@@ -24,10 +25,18 @@ export const Main = withProtectionRoute(() => {
|
||||
sceneToken={sceneToken}
|
||||
className="col-span-12 col-start-1 row-span-12 row-start-1"
|
||||
/>
|
||||
<div className="absolute top-0 flex w-full justify-between px-2">
|
||||
<ToolbarTopLeft />
|
||||
<ToolbarTopRight />
|
||||
{/* <ToolbarUserProfile /> */}
|
||||
<div className="absolute top-0 w-full p-3.5">
|
||||
<div className="relative flex w-full">
|
||||
<div className="absolute left-0">
|
||||
<ToolbarTopLeft />
|
||||
</div>
|
||||
<div className="flex w-full justify-center">
|
||||
<ToolbarTopCenter />
|
||||
</div>
|
||||
<div className="absolute right-0">
|
||||
<ToolbarTopRight />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
import { useRef, useState } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { Dialog, DialogPanel, DialogTitle } from "@headlessui/react";
|
||||
import { DialogTitle } from "@headlessui/react";
|
||||
|
||||
import { FileUploader } from "../FileUploader";
|
||||
import { Button } from "~/components/ui";
|
||||
|
||||
import { IMAGE_FILE_TYPE } from "~/constants/fileTypeEnums";
|
||||
|
||||
import {
|
||||
paperWrapperStyles,
|
||||
dialogBackgroundStyles,
|
||||
dialogPanelStyles,
|
||||
} from "~/components/styles";
|
||||
import { BaseDialog } from "~/components/ui/BaseDialog";
|
||||
|
||||
export const DialogAddImage = ({
|
||||
stagedImage = null,
|
||||
@@ -43,48 +38,38 @@ export const DialogAddImage = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onClose={closeCallback} className="relative z-50">
|
||||
<div className={dialogBackgroundStyles}>
|
||||
<DialogPanel
|
||||
className={twMerge(
|
||||
paperWrapperStyles,
|
||||
dialogPanelStyles,
|
||||
"w-full max-w-2xl",
|
||||
)}
|
||||
>
|
||||
<DialogTitle className="text-2xl font-bold">Upload Image</DialogTitle>
|
||||
<div className="flex flex-col rounded-lg border-2 border-dashed border-ui-border">
|
||||
<FileUploader
|
||||
title=""
|
||||
fileTypes={Object.values(IMAGE_FILE_TYPE)}
|
||||
file={currFile}
|
||||
setFile={(file: File | null) => {
|
||||
setAssetFile(file);
|
||||
if (file) {
|
||||
onAddImage(file);
|
||||
handleClose();
|
||||
}
|
||||
}}
|
||||
<BaseDialog isOpen={isOpen} onClose={closeCallback}>
|
||||
<DialogTitle className="text-3xl font-bold">Upload Image</DialogTitle>
|
||||
<div className="flex flex-col rounded-lg border-2 border-dashed border-ui-border">
|
||||
<FileUploader
|
||||
title=""
|
||||
fileTypes={Object.values(IMAGE_FILE_TYPE)}
|
||||
file={currFile}
|
||||
setFile={(file: File | null) => {
|
||||
setAssetFile(file);
|
||||
if (file) {
|
||||
onAddImage(file);
|
||||
handleClose();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{currFile && (
|
||||
<div
|
||||
className="relative flex items-center justify-center bg-ui-border"
|
||||
style={{ height: "calc(100vh - 500px)" }}
|
||||
>
|
||||
<img
|
||||
src={URL.createObjectURL(currFile)}
|
||||
className="max-h-full object-contain"
|
||||
/>
|
||||
{currFile && (
|
||||
<div
|
||||
className="relative flex items-center justify-center bg-ui-border"
|
||||
style={{ height: "calc(100vh - 500px)" }}
|
||||
>
|
||||
<img
|
||||
src={URL.createObjectURL(currFile)}
|
||||
className="max-h-full object-contain"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex w-full justify-end gap-2">
|
||||
<Button onClick={handleClose} variant="secondary">
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</DialogPanel>
|
||||
)}
|
||||
</div>
|
||||
</Dialog>
|
||||
<div className="flex w-full justify-end gap-2">
|
||||
<Button onClick={handleClose} variant="secondary">
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</BaseDialog>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
import { useCallback, useRef, useState, useEffect } from "react";
|
||||
import { signal } from "@preact/signals-react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { Dialog, DialogPanel, DialogTitle } from "@headlessui/react";
|
||||
|
||||
import {
|
||||
dialogBackgroundStyles,
|
||||
paperWrapperStyles,
|
||||
dialogPanelStyles,
|
||||
} from "~/components/styles";
|
||||
import { DialogTitle } from "@headlessui/react";
|
||||
import { Button } from "~/components/ui";
|
||||
import { BaseDialog } from "~/components/ui/BaseDialog";
|
||||
|
||||
import {
|
||||
QuickTrimVideoUploader,
|
||||
@@ -108,78 +103,72 @@ export const DialogAddVideo = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onClose={closeCallback} className="relative z-50">
|
||||
<div className={dialogBackgroundStyles}>
|
||||
<DialogPanel
|
||||
className={twMerge("w-full", file ? "max-w-5xl" : "max-w-2xl")}
|
||||
>
|
||||
<div className={twMerge(paperWrapperStyles, dialogPanelStyles)}>
|
||||
<DialogTitle className="text-3xl font-bold">
|
||||
Upload Video
|
||||
</DialogTitle>
|
||||
{dialogStatus === DialogAddMediaStatuses.STAGING_FILE && (
|
||||
<>
|
||||
<QuickTrimVideoUploader
|
||||
file={currFile}
|
||||
onFileStaged={(file) => {
|
||||
setStates((curr) => ({ ...curr, file }));
|
||||
}}
|
||||
videoPropsSignal={videoProps}
|
||||
trimDataSignal={trimData}
|
||||
onTrimChange={(newTrimData: TrimData) => {
|
||||
trimData.value = newTrimData;
|
||||
}}
|
||||
/>
|
||||
<span className="grow" />
|
||||
</>
|
||||
)}
|
||||
<LoadingScreens
|
||||
currStatus={dialogStatus}
|
||||
retryButton={
|
||||
<ButtonSubmitAdd
|
||||
file={file}
|
||||
trimData={trimData}
|
||||
onStatusChanged={changeDialogStatus}
|
||||
onUploadedVideo={handleOnUploadedVideo}
|
||||
retry
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<BaseDialog
|
||||
isOpen={isOpen}
|
||||
onClose={closeCallback}
|
||||
className={twMerge("w-full", file ? "max-w-5xl" : "max-w-2xl")}
|
||||
>
|
||||
<DialogTitle className="text-3xl font-bold">Upload Video</DialogTitle>
|
||||
{dialogStatus === DialogAddMediaStatuses.STAGING_FILE && (
|
||||
<>
|
||||
<QuickTrimVideoUploader
|
||||
file={currFile}
|
||||
onFileStaged={(file) => {
|
||||
setStates((curr) => ({ ...curr, file }));
|
||||
}}
|
||||
videoPropsSignal={videoProps}
|
||||
trimDataSignal={trimData}
|
||||
onTrimChange={(newTrimData: TrimData) => {
|
||||
trimData.value = newTrimData;
|
||||
}}
|
||||
/>
|
||||
<span className="grow" />
|
||||
</>
|
||||
)}
|
||||
<LoadingScreens
|
||||
currStatus={dialogStatus}
|
||||
retryButton={
|
||||
<ButtonSubmitAdd
|
||||
file={file}
|
||||
trimData={trimData}
|
||||
onStatusChanged={changeDialogStatus}
|
||||
onUploadedVideo={handleOnUploadedVideo}
|
||||
retry
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="-mt-2 flex w-full justify-end gap-2">
|
||||
<Button
|
||||
onClick={handleClose}
|
||||
variant="secondary"
|
||||
disabled={
|
||||
dialogStatus === DialogAddMediaStatuses.FILE_UPLOADING ||
|
||||
dialogStatus === DialogAddMediaStatuses.FILE_RECORD_REQUESTING
|
||||
}
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
{dialogStatus === DialogAddMediaStatuses.STAGING_FILE && (
|
||||
<ButtonSubmitAdd
|
||||
file={currFile}
|
||||
trimData={trimData}
|
||||
onStatusChanged={changeDialogStatus}
|
||||
onUploadedVideo={handleOnUploadedVideo}
|
||||
/>
|
||||
)}
|
||||
{(dialogStatus === DialogAddMediaStatuses.ERROR_FILE_UPLOAD ||
|
||||
dialogStatus ===
|
||||
DialogAddMediaStatuses.ERROR_FILE_RECORD_REQUEST) && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
setStates(initialState);
|
||||
}}
|
||||
>
|
||||
Add Another Video
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</DialogPanel>
|
||||
<div className="-mt-2 flex w-full justify-end gap-2">
|
||||
<Button
|
||||
onClick={handleClose}
|
||||
variant="secondary"
|
||||
disabled={
|
||||
dialogStatus === DialogAddMediaStatuses.FILE_UPLOADING ||
|
||||
dialogStatus === DialogAddMediaStatuses.FILE_RECORD_REQUESTING
|
||||
}
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
{dialogStatus === DialogAddMediaStatuses.STAGING_FILE && (
|
||||
<ButtonSubmitAdd
|
||||
file={currFile}
|
||||
trimData={trimData}
|
||||
onStatusChanged={changeDialogStatus}
|
||||
onUploadedVideo={handleOnUploadedVideo}
|
||||
/>
|
||||
)}
|
||||
{(dialogStatus === DialogAddMediaStatuses.ERROR_FILE_UPLOAD ||
|
||||
dialogStatus ===
|
||||
DialogAddMediaStatuses.ERROR_FILE_RECORD_REQUEST) && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
setStates(initialState);
|
||||
}}
|
||||
>
|
||||
Add Another Video
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</Dialog>
|
||||
</BaseDialog>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { Dialog, DialogPanel, DialogTitle } from "@headlessui/react";
|
||||
import { DialogTitle } from "@headlessui/react";
|
||||
import { faWandSparkles } from "@fortawesome/pro-solid-svg-icons";
|
||||
|
||||
import { Button } from "~/components/ui";
|
||||
import {
|
||||
dialogBackgroundStyles,
|
||||
paperWrapperStyles,
|
||||
dialogPanelStyles,
|
||||
} from "~/components/styles";
|
||||
import { BaseDialog } from "~/components/ui/BaseDialog";
|
||||
|
||||
import { ArtStyleNames, SubPanelNames } from "./enums";
|
||||
import {
|
||||
@@ -104,52 +99,46 @@ export const DialogAiStylize = ({
|
||||
}, [isOpen]);
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onClose={closeCallback} className="relative z-50">
|
||||
<div className={dialogBackgroundStyles}>
|
||||
<DialogPanel
|
||||
className={twMerge(
|
||||
paperWrapperStyles,
|
||||
dialogPanelStyles,
|
||||
"min-h-[calc(100vh-300px)] max-w-7xl",
|
||||
)}
|
||||
<BaseDialog
|
||||
isOpen={isOpen}
|
||||
onClose={closeCallback}
|
||||
className="min-h-[calc(100vh-300px)] max-w-7xl"
|
||||
>
|
||||
<DialogTitle className="text-3xl font-bold">
|
||||
Use AI to Stylize
|
||||
</DialogTitle>
|
||||
|
||||
{panelState === SubPanelNames.BASIC && (
|
||||
<SubPanelBasic
|
||||
selectedArtStyle={selectedArtStyle}
|
||||
positivePrompt={positivePrompt}
|
||||
negativePrompt={negativePrompt}
|
||||
onSelectedArtStyle={onSelectedArtStyle}
|
||||
onChangeNegativePrompt={onChangeNegativePrompt}
|
||||
onChangePositivePrompt={onChangePositivePrompt}
|
||||
onChangePanel={onChangePanel}
|
||||
/>
|
||||
)}
|
||||
{panelState === SubPanelNames.ADVANCED && (
|
||||
<SubPanelAdvance
|
||||
aiStylizeProps={aiStylizeProps}
|
||||
onStylizeOptionsChanged={setStylizeOptions}
|
||||
onChangePanel={onChangePanel}
|
||||
/>
|
||||
)}
|
||||
<div className="flex w-full justify-end gap-2">
|
||||
<Button onClick={closeCallback} variant="secondary">
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
className="hover:animate-pulse"
|
||||
icon={faWandSparkles}
|
||||
onClick={handleGenerate}
|
||||
>
|
||||
<DialogTitle className="text-3xl font-bold">
|
||||
Use AI to Stylize
|
||||
</DialogTitle>
|
||||
|
||||
{panelState === SubPanelNames.BASIC && (
|
||||
<SubPanelBasic
|
||||
selectedArtStyle={selectedArtStyle}
|
||||
positivePrompt={positivePrompt}
|
||||
negativePrompt={negativePrompt}
|
||||
onSelectedArtStyle={onSelectedArtStyle}
|
||||
onChangeNegativePrompt={onChangeNegativePrompt}
|
||||
onChangePositivePrompt={onChangePositivePrompt}
|
||||
onChangePanel={onChangePanel}
|
||||
/>
|
||||
)}
|
||||
{panelState === SubPanelNames.ADVANCED && (
|
||||
<SubPanelAdvance
|
||||
aiStylizeProps={aiStylizeProps}
|
||||
onStylizeOptionsChanged={setStylizeOptions}
|
||||
onChangePanel={onChangePanel}
|
||||
/>
|
||||
)}
|
||||
<div className="flex w-full justify-end gap-2">
|
||||
<Button onClick={closeCallback} variant="secondary">
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
className="hover:animate-pulse"
|
||||
icon={faWandSparkles}
|
||||
onClick={handleGenerate}
|
||||
>
|
||||
Generate
|
||||
</Button>
|
||||
</div>
|
||||
</DialogPanel>
|
||||
Generate
|
||||
</Button>
|
||||
</div>
|
||||
</Dialog>
|
||||
</BaseDialog>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Dialog } from "@headlessui/react";
|
||||
|
||||
import { DialogPanelChromakey } from "./DialogPanelChromakey";
|
||||
import { dialogBackgroundStyles } from "~/components/styles";
|
||||
import { DialogChromakeyProps } from "./type";
|
||||
|
||||
export const DialogChromakey = ({
|
||||
@@ -16,7 +15,7 @@ export const DialogChromakey = ({
|
||||
className="relative z-50"
|
||||
unmount={true}
|
||||
>
|
||||
<div className={dialogBackgroundStyles}>
|
||||
<div>
|
||||
<DialogPanelChromakey {...rest} onClose={onClose} />
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
import { useState } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { Dialog, DialogPanel, DialogTitle } from "@headlessui/react";
|
||||
|
||||
import { DialogTitle } from "@headlessui/react";
|
||||
import { Button, TextEditor } from "~/components/ui";
|
||||
import { TextNodeData } from "~/KonvaApp/types";
|
||||
|
||||
import {
|
||||
dialogBackgroundStyles,
|
||||
dialogPanelStyles,
|
||||
paperWrapperStyles,
|
||||
} from "~/components/styles";
|
||||
import { BaseDialog } from "~/components/ui/BaseDialog";
|
||||
|
||||
import {
|
||||
TextFormatData,
|
||||
@@ -70,31 +65,26 @@ export const DialogEditText = ({
|
||||
closeCallback();
|
||||
};
|
||||
return (
|
||||
<Dialog open={isOpen} onClose={closeCallback} className="relative z-50">
|
||||
<div className={dialogBackgroundStyles}>
|
||||
<DialogPanel className={twMerge(paperWrapperStyles, dialogPanelStyles)}>
|
||||
<DialogTitle className="text-2xl font-bold">Edit Text</DialogTitle>
|
||||
<TextEditor
|
||||
text={text}
|
||||
formatData={textFormatData}
|
||||
onChangeText={handleOnChangeText}
|
||||
onChangeFormatting={handleOnChangeFormatting}
|
||||
/>
|
||||
<div className="flex w-full justify-end gap-2">
|
||||
<Button onClick={closeCallback} variant="secondary">
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
className="hover:animate-pulse"
|
||||
onClick={handleOnDoneEditText}
|
||||
disabled={text === ""}
|
||||
>
|
||||
Enter
|
||||
</Button>
|
||||
</div>
|
||||
</DialogPanel>
|
||||
<BaseDialog isOpen={isOpen} onClose={closeCallback} className="w-fit">
|
||||
<DialogTitle className="text-2xl font-bold">Edit Text</DialogTitle>
|
||||
<TextEditor
|
||||
text={text}
|
||||
formatData={textFormatData}
|
||||
onChangeText={handleOnChangeText}
|
||||
onChangeFormatting={handleOnChangeFormatting}
|
||||
/>
|
||||
<div className="flex w-full justify-end gap-2">
|
||||
<Button onClick={closeCallback} variant="secondary">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
className="hover:animate-pulse"
|
||||
onClick={handleOnDoneEditText}
|
||||
disabled={text === ""}
|
||||
>
|
||||
Enter
|
||||
</Button>
|
||||
</div>
|
||||
</Dialog>
|
||||
</BaseDialog>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import {
|
||||
dialogBackgroundStyles,
|
||||
dialogPanelStyles,
|
||||
paperWrapperStyles,
|
||||
} from "~/components/styles";
|
||||
import { dialogPanelStyles, paperWrapperStyles } from "~/components/styles";
|
||||
import {
|
||||
Description,
|
||||
Dialog,
|
||||
@@ -26,7 +22,7 @@ export const DialogError = ({
|
||||
}) => {
|
||||
return (
|
||||
<Dialog open={isShowing} onClose={onClose} className="relative z-50">
|
||||
<div className={dialogBackgroundStyles}>
|
||||
<div>
|
||||
<DialogPanel className={twMerge(paperWrapperStyles, dialogPanelStyles)}>
|
||||
<DialogTitle className="font-bold">{title}</DialogTitle>
|
||||
<Description>{message}</Description>
|
||||
|
||||
@@ -19,6 +19,7 @@ import { Fragment, MouseEventHandler } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { ToolbarButton } from "../ToolbarButton";
|
||||
import { Tooltip } from "~/components/ui/Tooltip";
|
||||
|
||||
// style and constants
|
||||
import { paperWrapperStyles, toolTipStyles } from "~/components/styles";
|
||||
@@ -52,7 +53,7 @@ export const ToolbarMain = ({
|
||||
paperWrapperStyles,
|
||||
"glass",
|
||||
disabled &&
|
||||
"pointer-events-none cursor-default bg-ui-border shadow-md",
|
||||
"pointer-events-none cursor-default bg-ui-border shadow-md",
|
||||
)}
|
||||
>
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
@@ -160,7 +161,7 @@ export const ToolbarMain = ({
|
||||
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
{ /* Conditionally show paint menu on mode selection */}
|
||||
{/* Conditionally show paint menu on mode selection */}
|
||||
{buttonProps.PAINT.active ? (
|
||||
<div className="relative">
|
||||
<PaintModeMenu
|
||||
@@ -178,7 +179,12 @@ export const ToolbarMain = ({
|
||||
/>
|
||||
<div
|
||||
className="pointer-events-none absolute -bottom-1.5 -right-1.5 h-[18px] w-[18px] rounded-full border border-gray-400"
|
||||
style={{ backgroundColor: paintColor.value }}
|
||||
style={{
|
||||
background: `
|
||||
linear-gradient(${paintColor.value}, ${paintColor.value}),
|
||||
conic-gradient(rgb(204, 204, 204) 25%, rgb(255, 255, 255) 0deg, rgb(255, 255, 255) 50%, rgb(204, 204, 204) 0deg, rgb(204, 204, 204) 75%, rgb(255, 255, 255) 0deg) 0% 0% / 8px 8px
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
@@ -189,7 +195,7 @@ export const ToolbarMain = ({
|
||||
/>
|
||||
)}
|
||||
|
||||
{ /* Conditionally show eraser menu on mode selection */}
|
||||
{/* Conditionally show eraser menu on mode selection */}
|
||||
{buttonProps.ERASER.active ? (
|
||||
<EraseModeMenu
|
||||
faIcon={faEraser}
|
||||
@@ -199,14 +205,13 @@ export const ToolbarMain = ({
|
||||
anchorGap={12}
|
||||
closeOnMouseLeave={true}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<ToolbarButton
|
||||
icon={faEraser}
|
||||
buttonProps={buttonProps.ERASER}
|
||||
tooltip="Eraser"
|
||||
/>
|
||||
)}
|
||||
) : (
|
||||
<ToolbarButton
|
||||
icon={faEraser}
|
||||
buttonProps={buttonProps.ERASER}
|
||||
tooltip="Eraser"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -232,26 +237,29 @@ export const ToolbarMain = ({
|
||||
|
||||
<hr className="w-full border-t border-white/15" />
|
||||
|
||||
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<div className="relative">
|
||||
<BackgroundMenu
|
||||
color={bgColor.value}
|
||||
onChange={dispatchUiEvents.toolbarMain.setBgColor}
|
||||
faIcon={faImage}
|
||||
borderStyle="bg-transparent hover:bg-white/15 text-white"
|
||||
showBar={false}
|
||||
staticIconColor="white"
|
||||
streamChanges
|
||||
defaultOpen={false}
|
||||
anchor="right"
|
||||
anchorGap={12}
|
||||
closeOnMouseLeave={true}
|
||||
/>
|
||||
<div
|
||||
className="pointer-events-none absolute -bottom-1.5 -right-1.5 h-[18px] w-[18px] rounded-full border border-gray-400"
|
||||
style={{ backgroundColor: bgColor.value }}
|
||||
/>
|
||||
<Tooltip tip="Background Color" position="right">
|
||||
<div>
|
||||
<BackgroundMenu
|
||||
color={bgColor.value}
|
||||
onChange={dispatchUiEvents.toolbarMain.setBgColor}
|
||||
faIcon={faImage}
|
||||
borderStyle="bg-transparent hover:bg-white/15 text-white"
|
||||
showBar={false}
|
||||
staticIconColor="white"
|
||||
streamChanges
|
||||
defaultOpen={false}
|
||||
anchor="right"
|
||||
anchorGap={12}
|
||||
closeOnMouseLeave={true}
|
||||
/>
|
||||
<div
|
||||
className="pointer-events-none absolute -bottom-1.5 -right-1.5 h-[18px] w-[18px] rounded-full border border-gray-400"
|
||||
style={{ backgroundColor: bgColor.value }}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { Button } from "~/components/ui/Button";
|
||||
import { useState } from "react";
|
||||
import { BaseDialog } from "~/components/ui/BaseDialog";
|
||||
import { DialogTitle } from "@headlessui/react";
|
||||
|
||||
interface ModelButtonProps {
|
||||
iconSrc: string;
|
||||
iconAlt: string;
|
||||
label?: string;
|
||||
subtitle?: string;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
const ModelButton = ({
|
||||
iconSrc,
|
||||
iconAlt,
|
||||
label,
|
||||
subtitle,
|
||||
children,
|
||||
}: ModelButtonProps) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="glass flex w-40 items-center justify-start gap-2 bg-ui-controls/70 p-1"
|
||||
onClick={() => setIsOpen(true)}
|
||||
>
|
||||
<img
|
||||
src={iconSrc}
|
||||
alt={iconAlt}
|
||||
className="h-8 w-8 rounded-md bg-white/10"
|
||||
/>
|
||||
{subtitle ? (
|
||||
<div className="flex flex-col gap-0 truncate text-start">
|
||||
<span className="truncate text-[11px] opacity-60">{subtitle}</span>
|
||||
<span className="truncate text-[13px] opacity-80">{label}</span>
|
||||
</div>
|
||||
) : (
|
||||
<span className="truncate">{label}</span>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
<BaseDialog isOpen={isOpen} onClose={() => setIsOpen(false)}>
|
||||
{children}
|
||||
</BaseDialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const ToolbarTopCenter = () => {
|
||||
return (
|
||||
<>
|
||||
{import.meta.env.DEV && ( // only show in dev mode for now
|
||||
<div className={twMerge("z-20 flex h-fit w-fit items-center gap-2")}>
|
||||
<ModelButton
|
||||
iconSrc="/resources/icons/model.png"
|
||||
iconAlt="Model"
|
||||
label="Realistic"
|
||||
subtitle="Model"
|
||||
>
|
||||
<div className="flex flex-col gap-2 text-sm">
|
||||
<DialogTitle className="text-2xl font-bold">
|
||||
Select Model
|
||||
</DialogTitle>
|
||||
<p>Model configuration options will go here</p>
|
||||
</div>
|
||||
</ModelButton>
|
||||
|
||||
<ModelButton
|
||||
iconSrc="/resources/icons/lora.png"
|
||||
iconAlt="LoRA"
|
||||
label="Detailed Tweaker"
|
||||
subtitle="LoRA"
|
||||
>
|
||||
<div className="flex flex-col gap-2 text-sm">
|
||||
<DialogTitle className="text-2xl font-bold">
|
||||
Select LoRA
|
||||
</DialogTitle>
|
||||
<p>LoRA configuration options will go here</p>
|
||||
</div>
|
||||
</ModelButton>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -6,11 +6,7 @@ import { twMerge } from "tailwind-merge";
|
||||
|
||||
export const ToolbarTopLeft = () => {
|
||||
return (
|
||||
<div
|
||||
className={twMerge(
|
||||
"z-20 mt-2 flex h-fit w-fit items-center gap-1 py-2.5 pl-4",
|
||||
)}
|
||||
>
|
||||
<div className={twMerge("z-20 flex h-fit w-fit items-center gap-1")}>
|
||||
<img
|
||||
src="/brand/mira-logo.png"
|
||||
alt="logo"
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
import { faSave } from "@fortawesome/pro-solid-svg-icons";
|
||||
import {
|
||||
faRectangle,
|
||||
faRectanglePortrait,
|
||||
faSave,
|
||||
faSquare,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { Button } from "~/components/ui/Button";
|
||||
import { toolbarMain } from "~/signals/uiAccess/toolbarMain";
|
||||
import { dispatchers } from "~/signals/uiEvents/toolbarMain";
|
||||
import { ToolbarMainButtonNames } from "../ToolbarMain/enum";
|
||||
import { ToggleButton } from "~/components/ui/ToggleButton";
|
||||
import { useState } from "react";
|
||||
|
||||
// import { paperWrapperStyles } from "~/components/styles";
|
||||
// import { faPlus, faQuestion } from "@fortawesome/pro-solid-svg-icons";
|
||||
@@ -12,13 +19,23 @@ import { ToolbarMainButtonNames } from "../ToolbarMain/enum";
|
||||
export const ToolbarTopRight = () => {
|
||||
const saveButtonState =
|
||||
toolbarMain.signal.value.buttonStates[ToolbarMainButtonNames.SAVE];
|
||||
const [aspectRatio, setAspectRatio] = useState("1:1");
|
||||
|
||||
return (
|
||||
<div
|
||||
className={twMerge(
|
||||
"z-20 mt-2 flex h-fit w-fit items-center gap-1 p-4 py-2.5",
|
||||
<div className={twMerge("z-20 flex h-fit w-fit items-center gap-2")}>
|
||||
{import.meta.env.DEV && ( // only show in dev mode for now
|
||||
<ToggleButton
|
||||
label="Ratio"
|
||||
value={aspectRatio}
|
||||
onChange={setAspectRatio}
|
||||
options={["1:1", "3:2", "2:3"]}
|
||||
icons={{
|
||||
"1:1": faSquare,
|
||||
"3:2": faRectangle,
|
||||
"2:3": faRectanglePortrait,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
>
|
||||
<Button
|
||||
icon={faSave}
|
||||
onClick={dispatchers[ToolbarMainButtonNames.SAVE]}
|
||||
|
||||
@@ -5,9 +5,6 @@ export const paperWrapperStyles =
|
||||
|
||||
export const transitionTimingStyles = "transition duration-150 ease-in-out";
|
||||
|
||||
export const dialogBackgroundStyles =
|
||||
"fixed inset-0 flex w-screen items-center justify-center bg-black/50";
|
||||
|
||||
export const toolTipStyles = {
|
||||
base: twMerge(
|
||||
"relative",
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import { Fragment } from "react";
|
||||
import {
|
||||
Dialog,
|
||||
DialogPanel,
|
||||
Transition,
|
||||
TransitionChild,
|
||||
} from "@headlessui/react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { dialogPanelStyles, paperWrapperStyles } from "~/components/styles";
|
||||
|
||||
interface BaseDialogProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const BaseDialog = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
children,
|
||||
className = "max-w-2xl",
|
||||
}: BaseDialogProps) => {
|
||||
return (
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-50" onClose={onClose}>
|
||||
<TransitionChild
|
||||
as={Fragment}
|
||||
enter="ease-out duration-150"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-150"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-black/30" aria-hidden="true" />
|
||||
</TransitionChild>
|
||||
|
||||
<div className="fixed inset-0 flex items-center justify-center p-4">
|
||||
<TransitionChild
|
||||
as={Fragment}
|
||||
enter="ease-out duration-150"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-150"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<DialogPanel
|
||||
className={twMerge(
|
||||
paperWrapperStyles,
|
||||
dialogPanelStyles,
|
||||
"w-full",
|
||||
"max-w-2xl",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</DialogPanel>
|
||||
</TransitionChild>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,68 @@
|
||||
import { Popover, PopoverButton, PopoverPanel } from "@headlessui/react";
|
||||
import { Button } from "~/components/ui/Button";
|
||||
import { useState, useRef } from "react";
|
||||
|
||||
export interface LabeledSelectProps {
|
||||
label: string;
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
options?: string[];
|
||||
}
|
||||
|
||||
// TODO: finish up this hover drop down component (WIP) - BFlat
|
||||
|
||||
export const LabeledSelect = ({
|
||||
label,
|
||||
value,
|
||||
onChange,
|
||||
options = ["1", "2", "3", "4"],
|
||||
}: LabeledSelectProps) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const timeoutRef = useRef<number>();
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
clearTimeout(timeoutRef.current);
|
||||
setIsOpen(true);
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
timeoutRef.current = window.setTimeout(() => {
|
||||
setIsOpen(false);
|
||||
}, 100);
|
||||
};
|
||||
|
||||
return (
|
||||
<Popover className="relative">
|
||||
<div onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
|
||||
<PopoverButton as={Button} variant="secondary" className="px-3 py-1.5">
|
||||
{label} — {value}
|
||||
</PopoverButton>
|
||||
{isOpen && (
|
||||
<PopoverPanel
|
||||
static
|
||||
className="absolute z-10 mt-1 w-fit origin-top-left rounded-md border bg-white p-1 shadow-lg transition-all duration-200 ease-out"
|
||||
style={{
|
||||
opacity: isOpen ? 1 : 0,
|
||||
transform: `scale(${isOpen ? 1 : 0.95}) translateY(${isOpen ? 0 : -4}px)`,
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col gap-1">
|
||||
{options.map((option) => (
|
||||
<Button
|
||||
key={option}
|
||||
className="justify-start px-3 py-1.5"
|
||||
onClick={() => {
|
||||
onChange(option);
|
||||
setIsOpen(false);
|
||||
}}
|
||||
>
|
||||
{option}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</PopoverPanel>
|
||||
)}
|
||||
</div>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
import { faArrowsRotate } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { Button } from "~/components/ui/Button";
|
||||
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
|
||||
|
||||
export interface ToggleButtonProps {
|
||||
label?: string;
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
options?: string[];
|
||||
icons?: { [key: string]: IconDefinition };
|
||||
}
|
||||
|
||||
export const ToggleButton = ({
|
||||
label,
|
||||
value,
|
||||
onChange,
|
||||
options = ["1", "2", "3"],
|
||||
icons,
|
||||
}: ToggleButtonProps) => {
|
||||
const handleClick = () => {
|
||||
const currentIndex = options.indexOf(value);
|
||||
const nextIndex = (currentIndex + 1) % options.length;
|
||||
onChange(options[nextIndex]);
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
icon={icons?.[value] ?? faArrowsRotate}
|
||||
variant="secondary"
|
||||
className="glass px-3 py-1.5"
|
||||
onClick={handleClick}
|
||||
>
|
||||
{label && (
|
||||
<>
|
||||
{label} <span className="opacity-60">—</span>
|
||||
</>
|
||||
)}{" "}
|
||||
{value}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
@@ -11,7 +11,12 @@ 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 {
|
||||
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";
|
||||
@@ -92,7 +97,7 @@ export const BackgroundMenu = ({
|
||||
<>
|
||||
<PopoverButton
|
||||
className={twMerge(
|
||||
"flex size-10 items-center gap-1 rounded-lg bg-ui-controls p-2",
|
||||
"flex size-10 items-center gap-1 rounded-lg bg-ui-controls p-2 transition-all duration-100",
|
||||
borderStyle,
|
||||
showBar ? "flex-col" : "justify-center",
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user