mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-29 13:02:51 -04:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
15a6c5f8ac | ||
|
|
18ac503f54 |
+34
-40
@@ -3,11 +3,16 @@ import { invoke } from "@tauri-apps/api/core";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
|
||||
import { FileUtilities } from "../../utilities/FileUtilities";
|
||||
import { ImageNode, TextNode, ShapeNode, PreviewCopyNode, PaintNode } from "../Nodes";
|
||||
import {
|
||||
ImageNode,
|
||||
TextNode,
|
||||
ShapeNode,
|
||||
PreviewCopyNode,
|
||||
PaintNode,
|
||||
} from "../Nodes";
|
||||
import { MediaNode } from "../types";
|
||||
import { RenderTask } from "./RenderTask";
|
||||
|
||||
|
||||
import {
|
||||
isLoadingVisible,
|
||||
loadingProgress,
|
||||
@@ -22,7 +27,6 @@ interface ServerSettings {
|
||||
}
|
||||
|
||||
export class RealTimeDrawEngine {
|
||||
|
||||
private imageNodes: (ImageNode | TextNode | ShapeNode | PaintNode)[];
|
||||
|
||||
public offScreenCanvas: OffscreenCanvas;
|
||||
@@ -45,7 +49,6 @@ export class RealTimeDrawEngine {
|
||||
public captureCanvas: Konva.Rect;
|
||||
public backgroundRasterRect: Konva.Image;
|
||||
|
||||
|
||||
public fps: number = 24;
|
||||
|
||||
public currentPrompt: string;
|
||||
@@ -97,10 +100,9 @@ export class RealTimeDrawEngine {
|
||||
) => void;
|
||||
onPreviewCopyCallback?: (previewCopy: Konva.Image) => void; // New Parameter
|
||||
}) {
|
||||
|
||||
this.imageNodes = [];
|
||||
this.onDrawCallback = onDraw;
|
||||
|
||||
|
||||
// TODO: Make this dynamic and update this on change of canvas.
|
||||
this.width = width * 1.5;
|
||||
this.height = height * 1;
|
||||
@@ -108,7 +110,6 @@ export class RealTimeDrawEngine {
|
||||
this.positionX = window.innerWidth - this.width;
|
||||
this.positionY = window.innerHeight - this.height;
|
||||
|
||||
|
||||
this.offScreenCanvas = offScreenCanvas;
|
||||
this.offScreenCanvas.width = this.width;
|
||||
this.offScreenCanvas.height = this.height;
|
||||
@@ -150,7 +151,6 @@ export class RealTimeDrawEngine {
|
||||
this.mediaLayerRef.add(this.captureCanvas);
|
||||
|
||||
this.captureCanvas.setZIndex(0);
|
||||
|
||||
|
||||
this.listenToServerEvents();
|
||||
}
|
||||
@@ -520,7 +520,8 @@ export class RealTimeDrawEngine {
|
||||
|
||||
// Calculate new position to center the canvas in the window
|
||||
this.positionX = (window.innerWidth - this.width) / 2;
|
||||
this.positionY = (window.innerHeight - this.height) / 2;
|
||||
const headerPadding = 56;
|
||||
this.positionY = (window.innerHeight - headerPadding - this.height) / 2;
|
||||
|
||||
console.log("New centered position:", this.positionX, this.positionY);
|
||||
|
||||
@@ -636,7 +637,6 @@ export class RealTimeDrawEngine {
|
||||
public isProcessing = false;
|
||||
private handleNodeDragEnd = async () => {
|
||||
// Clean up any existing state
|
||||
|
||||
//await this.render();
|
||||
};
|
||||
|
||||
@@ -662,7 +662,7 @@ export class RealTimeDrawEngine {
|
||||
}
|
||||
|
||||
public removeNodes(node: MediaNode) {
|
||||
if (
|
||||
if (
|
||||
node instanceof ImageNode ||
|
||||
node instanceof TextNode ||
|
||||
node instanceof ShapeNode ||
|
||||
@@ -819,7 +819,6 @@ export class RealTimeDrawEngine {
|
||||
await this.startServer();
|
||||
}
|
||||
|
||||
|
||||
private async renderFrame(config: {
|
||||
layerOfInterest: Konva.Layer; // layer where the element that you want to clip lives.
|
||||
// XY and height of a captureCanvas ( region of interest )
|
||||
@@ -832,38 +831,33 @@ export class RealTimeDrawEngine {
|
||||
quality?: number; // 1.0 is the best.
|
||||
test: boolean; // true == blob else Image Bitmap
|
||||
}): Promise<ImageBitmap | Blob> {
|
||||
|
||||
const box = config.layerOfInterest.getClientRect();
|
||||
const stage = config.layerOfInterest.getStage();
|
||||
const box = config.layerOfInterest.getClientRect();
|
||||
const stage = config.layerOfInterest.getStage();
|
||||
|
||||
const x = config.x !== undefined ? config.x : Math.floor(box.x);
|
||||
const y = config.y !== undefined ? config.y : Math.floor(box.y);
|
||||
const pixelRatio = config.pixelRatio || 1;
|
||||
const x = config.x !== undefined ? config.x : Math.floor(box.x);
|
||||
const y = config.y !== undefined ? config.y : Math.floor(box.y);
|
||||
const pixelRatio = config.pixelRatio || 1;
|
||||
|
||||
// Clone the required layer from the stage
|
||||
// Set the right details (like removing highlight stroke)
|
||||
// Then render the cloned stage to a bitmap
|
||||
const stageClone = this.cloneStageForRender(
|
||||
stage,
|
||||
config.layerOfInterest,
|
||||
);
|
||||
const stageBlob = (await stageClone.toBlob({
|
||||
x: x,
|
||||
y: y,
|
||||
width: config.width || Math.ceil(box.width),
|
||||
height: config.height || Math.ceil(box.height),
|
||||
pixelRatio: pixelRatio,
|
||||
})) as Blob;
|
||||
// Clone the required layer from the stage
|
||||
// Set the right details (like removing highlight stroke)
|
||||
// Then render the cloned stage to a bitmap
|
||||
const stageClone = this.cloneStageForRender(stage, config.layerOfInterest);
|
||||
const stageBlob = (await stageClone.toBlob({
|
||||
x: x,
|
||||
y: y,
|
||||
width: config.width || Math.ceil(box.width),
|
||||
height: config.height || Math.ceil(box.height),
|
||||
pixelRatio: pixelRatio,
|
||||
})) as Blob;
|
||||
|
||||
// if config.test is true, the result is downloaded to the local files
|
||||
// config.test = true;
|
||||
if (config.test) {
|
||||
await FileUtilities.blobToFileJpeg(stageBlob, "1");
|
||||
}
|
||||
|
||||
const result = await createImageBitmap(stageBlob);
|
||||
return result;
|
||||
// if config.test is true, the result is downloaded to the local files
|
||||
// config.test = true;
|
||||
if (config.test) {
|
||||
await FileUtilities.blobToFileJpeg(stageBlob, "1");
|
||||
}
|
||||
|
||||
const result = await createImageBitmap(stageBlob);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Add method to create or update background
|
||||
|
||||
@@ -9,6 +9,8 @@ export const KonvaCanvasContainer = forwardRef<
|
||||
>(({ className, ...props }, ref) => {
|
||||
// useRenderCounter("KonvaCanvasContainer");
|
||||
|
||||
const classes = twMerge("pegboard -z-10", className);
|
||||
const classes = twMerge("pegboard -z-10", "h-[calc(100vh-56px)]", className);
|
||||
return <div ref={ref} className={classes} {...props} />;
|
||||
});
|
||||
|
||||
KonvaCanvasContainer.displayName = "KonvaCanvasContainer";
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useCallback, useRef, useState } from "react";
|
||||
import { KonvaCanvasContainer } from "./KonvaCanvasContainer";
|
||||
import { ContextualToolbarNode } from "./ContextualToolbarNode";
|
||||
// import { SignaledCanvasDragDropFiles } from "./SignaledCanvasDragDropFiles";
|
||||
import { SignaledDialogs } from "./SignaledDialogs";
|
||||
// import { SignaledDialogs } from "./SignaledDialogs";
|
||||
import { SignaledToolbarMain } from "./SignaledToolbarMain";
|
||||
|
||||
// The KonvaApp is the root of the Konva stage
|
||||
@@ -33,7 +33,17 @@ import { uploadImage } from "../../components/reusable/UploadModalMedia/uploadIm
|
||||
// import { EncodeImageBitmapToBase64 } from "./utilities/EncodeImageBitmapToBase64";
|
||||
import { JobProvider, useJobContext } from "./JobContext";
|
||||
import { RealTimeDrawEngine } from "./KonvaApp/RenderingPrimitives/RealTimeDrawEngine";
|
||||
import { VideoResolutions } from "./KonvaApp/constants";
|
||||
// import { VideoResolutions } from "./KonvaApp/constants";
|
||||
|
||||
import {
|
||||
DialogAddImage,
|
||||
DialogEditText,
|
||||
DialogError,
|
||||
} from "./components/features";
|
||||
import { dispatchUiEvents } from "./signals";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import { dialogError } from "./signals/uiAccess/dialogError";
|
||||
|
||||
export const KonvaRootComponent = ({
|
||||
className,
|
||||
sceneToken,
|
||||
@@ -41,6 +51,7 @@ export const KonvaRootComponent = ({
|
||||
className: string;
|
||||
sceneToken?: string;
|
||||
}) => {
|
||||
useSignals();
|
||||
// This is a hook that will log the number of times the component has rerendered
|
||||
// Let's make sure we only log once
|
||||
useRenderCounter("KonvaRootComponent");
|
||||
@@ -53,29 +64,38 @@ export const KonvaRootComponent = ({
|
||||
const engineRef = useRef<EngineType | null>(null);
|
||||
const renderEngineRef = useRef<RealTimeDrawEngine | null>(null);
|
||||
|
||||
const konvaContainerCallbackRef = useCallback((node: HTMLDivElement) => {
|
||||
// Only initialize if we have a node and haven't initialized yet
|
||||
if (node !== null && !isEngineReady && engineRef.current === null) {
|
||||
try {
|
||||
const options = {
|
||||
navigate: navigate,
|
||||
sceneToken: sceneToken,
|
||||
};
|
||||
|
||||
const konvaContainerCallbackRef = useCallback(
|
||||
(node: HTMLDivElement) => {
|
||||
// Only initialize if we have a node and haven't initialized yet
|
||||
if (node !== null && !isEngineReady && engineRef.current === null) {
|
||||
try {
|
||||
const options = {
|
||||
navigate: navigate,
|
||||
sceneToken: sceneToken,
|
||||
};
|
||||
|
||||
engineRef.current = KonvaApp(node, options);
|
||||
renderEngineRef.current = engineRef.current.realTimeDrawEngine;
|
||||
// Only set ready if initialization succeeded
|
||||
if (engineRef.current) {
|
||||
setIsEngineReady(true);
|
||||
engineRef.current = KonvaApp(node, options);
|
||||
renderEngineRef.current = engineRef.current.realTimeDrawEngine;
|
||||
// Only set ready if initialization succeeded
|
||||
if (engineRef.current) {
|
||||
setIsEngineReady(true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to initialize KonvaApp:", error);
|
||||
engineRef.current = null;
|
||||
setIsEngineReady(false);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to initialize KonvaApp:", error);
|
||||
engineRef.current = null;
|
||||
setIsEngineReady(false);
|
||||
}
|
||||
}
|
||||
}, [navigate, sceneToken, isEngineReady]);
|
||||
},
|
||||
[navigate, sceneToken, isEngineReady],
|
||||
);
|
||||
|
||||
// Modal stuff
|
||||
const props = dialogError.signal.value;
|
||||
const { isShowing, title, message } = props;
|
||||
const onClose = useCallback(() => {
|
||||
dialogError.hide();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -95,7 +115,6 @@ export const KonvaRootComponent = ({
|
||||
{/* Conditionally render the UI components that need the engine */}
|
||||
{isEngineReady && engineRef.current && (
|
||||
<UndoRedo engine={engineRef.current}>
|
||||
|
||||
{/* <SignaledCanvasDragDropFiles
|
||||
openAddImage={appUiContext.openAddImage}
|
||||
openAddVideo={appUiContext.openAddVideo}
|
||||
@@ -104,25 +123,52 @@ export const KonvaRootComponent = ({
|
||||
<PromptBox2D
|
||||
uploadImage={uploadImage}
|
||||
getCanvasRenderBitmap={getCanvasRenderBitmap}
|
||||
EncodeImageBitmapToBase64={EncodeImageBitmapToBase64}
|
||||
useJobContext={useJobContext}
|
||||
onEnqueuePressed={async ()=> {
|
||||
// will set the snapshot of the canvas internally.
|
||||
await renderEngineRef.current?.render();
|
||||
}}
|
||||
/> </JobProvider>
|
||||
|
||||
EncodeImageBitmapToBase64={EncodeImageBitmapToBase64}
|
||||
useJobContext={useJobContext}
|
||||
onEnqueuePressed={async () => {
|
||||
// will set the snapshot of the canvas internally.
|
||||
await renderEngineRef.current?.render();
|
||||
}}
|
||||
/>{" "}
|
||||
</JobProvider>
|
||||
|
||||
<SignaledToolbarMain
|
||||
layoutSignal={layoutContext.signal}
|
||||
appUiContext={appUiContext}
|
||||
/>
|
||||
<SignaledDialogs
|
||||
{/* <SignaledDialogs
|
||||
appUiSignal={appUiContext.signal}
|
||||
resetAll={appUiContext.resetAll}
|
||||
/>
|
||||
/> */}
|
||||
<ContextualToolbarNode />
|
||||
</UndoRedo>
|
||||
)}
|
||||
|
||||
{/* Modals */}
|
||||
|
||||
<DialogEditText
|
||||
isOpen={appUiContext.signal.value.isEditTextOpen}
|
||||
onDoneEditText={(data) => {
|
||||
dispatchUiEvents.addTextToEngine(data);
|
||||
}}
|
||||
closeCallback={appUiContext.resetAll}
|
||||
/>
|
||||
|
||||
<DialogAddImage
|
||||
isOpen={appUiContext.signal.value.isAddImageOpen}
|
||||
stagedImage={appUiContext.signal.value.stagedImage}
|
||||
closeCallback={appUiContext.resetAll}
|
||||
onAddImage={(file) => {
|
||||
dispatchUiEvents.addImageToEngine(file);
|
||||
}}
|
||||
/>
|
||||
|
||||
<DialogError
|
||||
isShowing={isShowing}
|
||||
title={title}
|
||||
message={message}
|
||||
onClose={onClose}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,11 +6,9 @@ import {
|
||||
DialogEditText,
|
||||
DialogError,
|
||||
} from "./components/features";
|
||||
|
||||
import { AppUiSignalType } from "./contextSignals/appUi";
|
||||
import { dialogError } from "./signals/uiAccess/dialogError";
|
||||
|
||||
|
||||
export const SignaledDialogs = ({
|
||||
appUiSignal,
|
||||
resetAll,
|
||||
|
||||
@@ -13,6 +13,7 @@ import { AppUiContextInterface } from "./contextSignals/appUi";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { paperWrapperStyles } from "./components/styles";
|
||||
import { dispatchUiEvents } from "./signals/uiEvents";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
|
||||
export const SignaledToolbarMain = ({
|
||||
// layoutSignal,
|
||||
@@ -21,7 +22,7 @@ export const SignaledToolbarMain = ({
|
||||
layoutSignal: LayoutSignalType;
|
||||
appUiContext: AppUiContextInterface;
|
||||
}) => {
|
||||
|
||||
useSignals();
|
||||
const loadingBar = toolbarMain.loadingBar.signal.value;
|
||||
const getButtonDispatcher = (buttonName: ToolbarMainButtonNames) => {
|
||||
switch (buttonName) {
|
||||
|
||||
+35
-34
@@ -6,8 +6,7 @@ import { FileUploader } from "../../../components/reusable/FileUploader";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
|
||||
import { IMAGE_FILE_TYPE } from "../../../constants/fileTypeEnums";
|
||||
|
||||
import { BaseDialog } from "../../../components/ui/BaseDialog";
|
||||
import { Modal } from "@storyteller/ui-modal";
|
||||
|
||||
export const DialogAddImage = ({
|
||||
stagedImage = null,
|
||||
@@ -39,38 +38,40 @@ export const DialogAddImage = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<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"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<Modal isOpen={isOpen} onClose={closeCallback}>
|
||||
<div className="flex flex-col gap-3">
|
||||
<DialogTitle className="text-2xl font-bold">Upload Image</DialogTitle>
|
||||
<div className="flex flex-col rounded-lg border-2 border-dashed border-white/20">
|
||||
<FileUploader
|
||||
fileTypes={Object.values(IMAGE_FILE_TYPE)}
|
||||
file={currFile}
|
||||
setFile={(file: File | null) => {
|
||||
setAssetFile(file);
|
||||
if (file) {
|
||||
onAddImage(file);
|
||||
handleClose();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{currFile && (
|
||||
<div
|
||||
className="bg-ui-border relative flex items-center justify-center"
|
||||
style={{ height: "calc(100vh - 500px)" }}
|
||||
>
|
||||
<img
|
||||
src={URL.createObjectURL(currFile)}
|
||||
className="max-h-full object-contain"
|
||||
alt="upload img"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex w-full justify-end gap-2">
|
||||
<Button onClick={handleClose} variant="secondary">
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex w-full justify-end gap-2">
|
||||
<Button onClick={handleClose} variant="secondary">
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</BaseDialog>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
+23
-23
@@ -31,14 +31,12 @@ export type TextNodeData = {
|
||||
text: string;
|
||||
} & TextFormatData;
|
||||
|
||||
|
||||
import { useState } from "react";
|
||||
|
||||
import { DialogTitle } from "@headlessui/react";
|
||||
import { Button, TextEditor } from "../../../components/ui";
|
||||
|
||||
import { BaseDialog } from "../../../components/ui/BaseDialog";
|
||||
|
||||
import { Modal } from "@storyteller/ui-modal";
|
||||
|
||||
export const DialogEditText = ({
|
||||
isOpen,
|
||||
@@ -92,26 +90,28 @@ export const DialogEditText = ({
|
||||
closeCallback();
|
||||
};
|
||||
return (
|
||||
<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>
|
||||
<Modal isOpen={isOpen} onClose={closeCallback} className="w-fit max-w-2xl">
|
||||
<div className="flex flex-col gap-3">
|
||||
<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>
|
||||
</div>
|
||||
</BaseDialog>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
+11
-13
@@ -2,10 +2,8 @@ import {
|
||||
faArrowRotateLeft,
|
||||
faArrowRotateRight,
|
||||
faCircle,
|
||||
faEraser,
|
||||
faFilePlus,
|
||||
faImage,
|
||||
faImages,
|
||||
faLocationArrow,
|
||||
faPaintbrush,
|
||||
faShapes,
|
||||
@@ -19,18 +17,18 @@ import { Fragment, MouseEventHandler } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { ToolbarButton } from "../../../components/features/ToolbarButton";
|
||||
import { Tooltip } from "~/components/ui/Tooltip";
|
||||
// import { Tooltip } from "~/components/ui/Tooltip";
|
||||
|
||||
// style and constants
|
||||
import { paperWrapperStyles, toolTipStyles } from "../../../components/styles";
|
||||
import { ToolbarMainButtonNames } from "./enum";
|
||||
import { dispatchUiEvents, uiEvents } from "../../../signals/uiEvents";
|
||||
import { dispatchUiEvents } from "../../../signals/uiEvents";
|
||||
import { paintColor } from "../../../signals/uiEvents/toolbarMain/paintMode";
|
||||
import { ColorPicker } from "../../../components/ui/TextEditor/ColorPicker";
|
||||
// import { ColorPicker } from "../../../components/ui/TextEditor/ColorPicker";
|
||||
import { PaintModeMenu } from "../../../components/ui/ToolbarMain/PaintModeMenu";
|
||||
import { EraseModeMenu } from "../../../components/ui/ToolbarMain/EraseModeMenu";
|
||||
import { BackgroundMenu } from "../../../components/ui/ToolbarMain/BackgroundMenu";
|
||||
import { bgColor } from "../../../signals/uiEvents/toolbarMain/backgroundMenu";
|
||||
// import { EraseModeMenu } from "../../../components/ui/ToolbarMain/EraseModeMenu";
|
||||
// import { BackgroundMenu } from "../../../components/ui/ToolbarMain/BackgroundMenu";
|
||||
// import { bgColor } from "../../../signals/uiEvents/toolbarMain/backgroundMenu";
|
||||
|
||||
export const ToolbarMain = ({
|
||||
disabled = false,
|
||||
@@ -53,7 +51,7 @@ export const ToolbarMain = ({
|
||||
paperWrapperStyles,
|
||||
"glass",
|
||||
disabled &&
|
||||
"pointer-events-none cursor-default bg-ui-border shadow-md",
|
||||
"bg-ui-border pointer-events-none cursor-default shadow-md",
|
||||
)}
|
||||
>
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
@@ -234,11 +232,11 @@ export const ToolbarMain = ({
|
||||
/> */}
|
||||
</div>
|
||||
|
||||
<hr className="w-full border-t border-white/15" />
|
||||
{/* <hr className="w-full border-t border-white/15" />
|
||||
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<div className="relative">
|
||||
{/* <Tooltip tip="Background Color" position="right">
|
||||
<Tooltip tip="Background Color" position="right">
|
||||
<div>
|
||||
<BackgroundMenu
|
||||
color={bgColor.value}
|
||||
@@ -258,9 +256,9 @@ export const ToolbarMain = ({
|
||||
style={{ backgroundColor: bgColor.value }}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip> */}
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export const paperWrapperStyles =
|
||||
"rounded-xl border border-ui-border p-1.5 shadow-lg bg-ui-panel";
|
||||
"rounded-xl border border-ui-panel-border p-1.5 shadow-lg bg-ui-background";
|
||||
|
||||
export const transitionTimingStyles = "transition duration-150 ease-in-out";
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ export const Combobox = ({ options, value, onChange }: ComboBoxInterface) => {
|
||||
onChange={onChange}
|
||||
onClose={() => setQuery("")}
|
||||
>
|
||||
<div className="relative flex h-10 rounded-md border border-ui-border">
|
||||
<div className="relative flex h-10 rounded-lg border border-ui-panel-border">
|
||||
<ComboboxInput
|
||||
className="rounded-lg px-3"
|
||||
displayValue={(font: string) => font}
|
||||
@@ -41,7 +41,7 @@ export const Combobox = ({ options, value, onChange }: ComboBoxInterface) => {
|
||||
</div>
|
||||
<ComboboxOptions
|
||||
anchor="bottom"
|
||||
className="mt-1 w-[var(--input-width)] rounded-lg border border-ui-border empty:invisible"
|
||||
className="mt-1 w-[var(--input-width)] rounded-lg border border-ui-panel-border empty:invisible"
|
||||
>
|
||||
{filteredOptions.map((option, idx) => (
|
||||
<ComboboxOption
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export const buttonGroupButton =
|
||||
"size-10 rounded-none bg-ui-controls text-white/80 hover:bg-white/30 data-[selected=true]:text-white data-[selected=true]:bg-primary/30 disabled:hover:bg-transparent disabled:text-gray-400 border border-ui-border";
|
||||
"size-10 rounded-none bg-ui-controls text-white/80 hover:bg-white/30 data-[selected=true]:text-white data-[selected=true]:bg-primary/30 disabled:hover:bg-transparent disabled:text-gray-400";
|
||||
export const buttonGroupGrouper =
|
||||
"flex divide-x overflow-hidden rounded-md border border-ui-border";
|
||||
"flex divide-x divide-ui-panel-border overflow-hidden rounded-md border border-ui-panel-border";
|
||||
|
||||
+3
-1
@@ -53,7 +53,9 @@ export const toolbarMain = {
|
||||
this.changeButtonState(ToolbarMainButtonNames.ERASER, { active: false });
|
||||
this.changeButtonState(ToolbarMainButtonNames.PAINT, { active: false });
|
||||
this.changeButtonState(mode, { active: true });
|
||||
}
|
||||
|
||||
console.log("setMode", mode);
|
||||
},
|
||||
};
|
||||
|
||||
function initButtonStates() {
|
||||
|
||||
@@ -345,24 +345,6 @@ export const PageEditor = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<LoginModal
|
||||
onClose={() => {}}
|
||||
videoSrc2D="/resources/videos/artcraft-canvas-demo.mp4"
|
||||
videoSrc3D="/resources/videos/artcraft-3d-demo.mp4"
|
||||
openAiLogo="/resources/images/openai-logo.png"
|
||||
onOpenChange={(isOpen: boolean) => {
|
||||
if (isOpen) {
|
||||
disableHotkeyInput(DomLevels.DIALOGUE);
|
||||
} else {
|
||||
enableHotkeyInput(DomLevels.DIALOGUE);
|
||||
}
|
||||
}}
|
||||
onArtCraftAuthSuccess={(userInfo: any) => {
|
||||
authentication.status.value = AUTH_STATUS.LOGGED_IN;
|
||||
authentication.userInfo.value = userInfo;
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -382,7 +364,26 @@ export const PageEditor = () => {
|
||||
appTabIdSignal={appTabId}
|
||||
setAppTabId={setAppTabId}
|
||||
/>
|
||||
|
||||
{appTabId.value === "3D" ? <ContentFor3D /> : <ContentFor2D />}
|
||||
|
||||
<LoginModal
|
||||
onClose={() => {}}
|
||||
videoSrc2D="/resources/videos/artcraft-canvas-demo.mp4"
|
||||
videoSrc3D="/resources/videos/artcraft-3d-demo.mp4"
|
||||
openAiLogo="/resources/images/openai-logo.png"
|
||||
onOpenChange={(isOpen: boolean) => {
|
||||
if (isOpen) {
|
||||
disableHotkeyInput(DomLevels.DIALOGUE);
|
||||
} else {
|
||||
enableHotkeyInput(DomLevels.DIALOGUE);
|
||||
}
|
||||
}}
|
||||
onArtCraftAuthSuccess={(userInfo: any) => {
|
||||
authentication.status.value = AUTH_STATUS.LOGGED_IN;
|
||||
authentication.userInfo.value = userInfo;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,7 +2,12 @@ import { useState, useRef, useEffect } from "react";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import { toast } from "sonner";
|
||||
import { JobContextType, MaybeCanvasRenderBitmapType, UploaderState, UploaderStates } from "@storyteller/common";
|
||||
import {
|
||||
JobContextType,
|
||||
MaybeCanvasRenderBitmapType,
|
||||
UploaderState,
|
||||
UploaderStates,
|
||||
} from "@storyteller/common";
|
||||
import { downloadFileFromUrl } from "@storyteller/api";
|
||||
import { PopoverMenu, PopoverItem } from "@storyteller/ui-popover";
|
||||
import { Tooltip } from "@storyteller/ui-tooltip";
|
||||
@@ -36,14 +41,18 @@ interface ReferenceImage {
|
||||
}
|
||||
|
||||
interface PromptBox2DProps {
|
||||
uploadImage: ({ title, assetFile, progressCallback, }: {
|
||||
uploadImage: ({
|
||||
title,
|
||||
assetFile,
|
||||
progressCallback,
|
||||
}: {
|
||||
title: string;
|
||||
assetFile: File;
|
||||
progressCallback: (newState: UploaderState) => void;
|
||||
}) => Promise<void>;
|
||||
getCanvasRenderBitmap: () => MaybeCanvasRenderBitmapType
|
||||
EncodeImageBitmapToBase64: (imageBitmap: ImageBitmap) => Promise<string>
|
||||
useJobContext: () => JobContextType
|
||||
getCanvasRenderBitmap: () => MaybeCanvasRenderBitmapType;
|
||||
EncodeImageBitmapToBase64: (imageBitmap: ImageBitmap) => Promise<string>;
|
||||
useJobContext: () => JobContextType;
|
||||
onEnqueuePressed?: () => void | Promise<void>;
|
||||
}
|
||||
|
||||
@@ -55,7 +64,7 @@ export const PromptBox2D = ({
|
||||
onEnqueuePressed,
|
||||
}: PromptBox2DProps) => {
|
||||
useSignals();
|
||||
|
||||
|
||||
console.log("Is this a desktop app?", IsDesktopApp());
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [content, setContent] = useState("");
|
||||
@@ -68,7 +77,7 @@ export const PromptBox2D = ({
|
||||
const [useSystemPrompt, setUseSystemPrompt] = useState(true);
|
||||
const [isGalleryModalOpen, setIsGalleryModalOpen] = useState(false);
|
||||
const [selectedGalleryImages, setSelectedGalleryImages] = useState<string[]>(
|
||||
[],
|
||||
[]
|
||||
);
|
||||
const [activeGalleryTab, setActiveGalleryTab] = useState("my-media");
|
||||
const [referenceImages, setReferenceImages] = useState<ReferenceImage[]>([]);
|
||||
@@ -108,7 +117,7 @@ export const PromptBox2D = ({
|
||||
prev.map((item) => ({
|
||||
...item,
|
||||
selected: item.label === selectedItem.label,
|
||||
})),
|
||||
}))
|
||||
);
|
||||
};
|
||||
|
||||
@@ -122,7 +131,9 @@ export const PromptBox2D = ({
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => {
|
||||
uploadImage({
|
||||
title: `reference-image-${Math.random().toString(36).substring(2, 15)}`,
|
||||
title: `reference-image-${Math.random()
|
||||
.toString(36)
|
||||
.substring(2, 15)}`,
|
||||
assetFile: file,
|
||||
progressCallback: (newState) => {
|
||||
console.debug("Upload progress:", newState.data);
|
||||
@@ -135,7 +146,7 @@ export const PromptBox2D = ({
|
||||
};
|
||||
setReferenceImages((prev) => [...prev, referenceImage]);
|
||||
setUploadingImages((prev) =>
|
||||
prev.filter((img) => img.id !== uploadId),
|
||||
prev.filter((img) => img.id !== uploadId)
|
||||
);
|
||||
toast.success("Reference image added.");
|
||||
} else if (
|
||||
@@ -143,7 +154,7 @@ export const PromptBox2D = ({
|
||||
newState.status === UploaderStates.imageCreateError
|
||||
) {
|
||||
setUploadingImages((prev) =>
|
||||
prev.filter((img) => img.id !== uploadId),
|
||||
prev.filter((img) => img.id !== uploadId)
|
||||
);
|
||||
|
||||
toast.error("Upload failed. Please try again.");
|
||||
@@ -266,7 +277,7 @@ export const PromptBox2D = ({
|
||||
disable_system_prompt: !useSystemPrompt,
|
||||
prompt: prompt,
|
||||
maybe_additional_images: referenceImages.map(
|
||||
(image) => image.mediaToken,
|
||||
(image) => image.mediaToken
|
||||
),
|
||||
maybe_number_of_samples: 1,
|
||||
},
|
||||
@@ -279,12 +290,11 @@ export const PromptBox2D = ({
|
||||
const api = new PromptsApi();
|
||||
// to take the snapshot of the canvas
|
||||
// Call onEnqueuePressed if it exists
|
||||
|
||||
|
||||
|
||||
let image = getCanvasRenderBitmap();
|
||||
if (image === undefined) {
|
||||
toast.error(
|
||||
"Error: Unable to generate image. Please check the input and try again.",
|
||||
"Error: Unable to generate image. Please check the input and try again."
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -350,7 +360,7 @@ export const PromptBox2D = ({
|
||||
"Enqueuing with prompt:",
|
||||
prompt,
|
||||
"and reference images:",
|
||||
referenceImages,
|
||||
referenceImages
|
||||
);
|
||||
|
||||
const isDesktop = IsDesktopApp();
|
||||
@@ -364,7 +374,7 @@ export const PromptBox2D = ({
|
||||
} catch (error) {
|
||||
console.error("Error during image generation:", error);
|
||||
toast.error(
|
||||
"An error occurred while generating the image. Please try again.",
|
||||
"An error occurred while generating the image. Please try again."
|
||||
);
|
||||
} finally {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
@@ -382,7 +392,7 @@ export const PromptBox2D = ({
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
|
||||
|
||||
handleEnqueue();
|
||||
}
|
||||
};
|
||||
@@ -473,7 +483,7 @@ export const PromptBox2D = ({
|
||||
]}
|
||||
onPanelAction={handleAction}
|
||||
showIconsInList
|
||||
buttonClassName="backdrop-blur-none bg-transparent hover:bg-transparent py-1.5 px-0 pr-1 m-0 hover:opacity-50 transition-opacity duration-100 ring-0 border-none focus:ring-0 outline-none"
|
||||
buttonClassName="bg-transparent hover:bg-transparent py-1.5 px-0 pr-1 m-0 hover:opacity-50 transition-opacity duration-100 ring-0 border-none focus:ring-0 outline-none"
|
||||
triggerIcon={
|
||||
<FontAwesomeIcon icon={faPlus} className="text-xl" />
|
||||
}
|
||||
@@ -488,8 +498,8 @@ export const PromptBox2D = ({
|
||||
onChange={handleChange}
|
||||
onPaste={handlePaste}
|
||||
onKeyDown={handleKeyDown}
|
||||
onFocus={() => { }}
|
||||
onBlur={() => { }}
|
||||
onFocus={() => {}}
|
||||
onBlur={() => {}}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-2 flex items-center justify-between gap-2">
|
||||
|
||||
Reference in New Issue
Block a user