mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-21 09:22:41 -04:00
Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99acebae91 | ||
|
|
3bfd9315f9 | ||
|
|
d558917e09 | ||
|
|
7f6f633d0c | ||
|
|
bba4db0f91 | ||
|
|
d2f59414c8 | ||
|
|
91d35f5343 |
@@ -154,6 +154,9 @@ export const TopBar = ({ pageName, loginSignUpPressed }: Props) => {
|
||||
url,
|
||||
mediaToken: mediaId || "",
|
||||
};
|
||||
|
||||
// Add it to state history
|
||||
useEditStore.getState().addHistoryImageBundle({ images: [baseImage] });
|
||||
useEditStore.getState().setBaseImageInfo(baseImage);
|
||||
|
||||
// Close gallery modal and lightbox if open
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
faTrashXmark,
|
||||
faXmark,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { useRef, useState } from "react";
|
||||
import { Fragment, useRef, useState } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { BaseSelectorImage } from "./BaseImageSelector";
|
||||
import { Tooltip } from "@storyteller/ui-tooltip";
|
||||
@@ -23,21 +23,18 @@ export interface ImageBundle {
|
||||
interface HistoryStackProps {
|
||||
onClear: () => void;
|
||||
onImageSelect?: (image: BaseSelectorImage) => void;
|
||||
startingBundles: ImageBundle[];
|
||||
imageBundles: ImageBundle[];
|
||||
onNewImageBundle?: (newBundle: ImageBundle) => void;
|
||||
selectedImageToken?: string;
|
||||
}
|
||||
|
||||
export const HistoryStack = ({
|
||||
onClear,
|
||||
onImageSelect = () => {},
|
||||
startingBundles = [],
|
||||
onImageSelect = () => { },
|
||||
imageBundles,
|
||||
onNewImageBundle = () => { },
|
||||
selectedImageToken
|
||||
}: HistoryStackProps) => {
|
||||
const [imageBundles, setImageBundles] =
|
||||
useState<ImageBundle[]>(startingBundles);
|
||||
const [selectedImageToken, setSelectedImageToken] = useState<string | null>(
|
||||
startingBundles.length > 0 && startingBundles[0].images.length > 0
|
||||
? startingBundles[0].images[0].mediaToken
|
||||
: null,
|
||||
);
|
||||
useImageEditCompleteEvent(async (event) => {
|
||||
const newBundle: ImageBundle = {
|
||||
images: event.edited_images.map(
|
||||
@@ -49,12 +46,7 @@ export const HistoryStack = ({
|
||||
),
|
||||
};
|
||||
|
||||
setImageBundles([...imageBundles, newBundle]);
|
||||
const newlySelected = newBundle.images[0];
|
||||
if (newlySelected) {
|
||||
setSelectedImageToken(newlySelected.mediaToken);
|
||||
onImageSelect(newlySelected);
|
||||
}
|
||||
onNewImageBundle(newBundle);
|
||||
});
|
||||
|
||||
// This is used to force image reloads in different sessions
|
||||
@@ -62,26 +54,23 @@ export const HistoryStack = ({
|
||||
const sessionRandBuster = useRef(Math.random());
|
||||
|
||||
const handleClear = () => {
|
||||
setImageBundles(startingBundles);
|
||||
setSelectedImageToken(null);
|
||||
onClear();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="h-auto w-20 rounded-lg">
|
||||
<div className="glass max-h-1/2 flex flex-col-reverse items-center justify-center gap-2 overflow-y-auto rounded-lg p-1.5">
|
||||
<div className={"glass max-h-[50vh] flex flex-col-reverse items-center justify-center gap-2 overflow-y-auto rounded-lg p-1.5"}>
|
||||
{imageBundles.map((bundle, index) => (
|
||||
<>
|
||||
{bundle.images.map((image, imgIndex) => (
|
||||
<Fragment key={index}>
|
||||
{bundle.images.map((image) => (
|
||||
<Button
|
||||
key={imgIndex}
|
||||
key={image.mediaToken}
|
||||
className={twMerge(
|
||||
"relative aspect-square h-full w-full border-2 bg-transparent p-0 hover:bg-transparent hover:opacity-80",
|
||||
selectedImageToken === image.mediaToken &&
|
||||
"border-primary hover:opacity-100",
|
||||
"border-primary hover:opacity-100",
|
||||
)}
|
||||
onClick={() => {
|
||||
setSelectedImageToken(image.mediaToken);
|
||||
onImageSelect(image);
|
||||
}}
|
||||
>
|
||||
@@ -95,9 +84,9 @@ export const HistoryStack = ({
|
||||
</Button>
|
||||
))}
|
||||
{index < imageBundles.length - 1 && (
|
||||
<hr className="h-0.5 w-3/4 rounded-md border-none bg-white/10" />
|
||||
<hr className="min-h-0.5 h-0.5 w-3/4 rounded-md border-none bg-white/10" key={"hr" + index} />
|
||||
)}
|
||||
</>
|
||||
</Fragment>
|
||||
))}
|
||||
<FontAwesomeIcon
|
||||
icon={faClockRotateLeft}
|
||||
|
||||
@@ -27,7 +27,7 @@ const PAGE_ID: ModelPage = ModelPage.ImageEditor;
|
||||
const PageEdit = () => {
|
||||
//useStateSceneLoader();
|
||||
|
||||
const selectedImageModel : ImageModel | undefined = getSelectedImageModel(PAGE_ID);
|
||||
const selectedImageModel: ImageModel | undefined = getSelectedImageModel(PAGE_ID);
|
||||
|
||||
// State for canvas dimensions
|
||||
const canvasWidth = useRef<number>(1024);
|
||||
@@ -46,6 +46,8 @@ const PageEdit = () => {
|
||||
|
||||
// Use the Zustand store
|
||||
const store = useEditStore();
|
||||
const addHistoryImageBundle = useEditStore((state) => state.addHistoryImageBundle);
|
||||
const historyImageBundles = useEditStore((state) => state.historyImageBundles)
|
||||
|
||||
// Pass store actions directly as callbacks
|
||||
useDeleteHotkeys({ onDelete: store.deleteSelectedItems });
|
||||
@@ -154,18 +156,6 @@ const PageEdit = () => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
// Auto-fit the canvas when base image is loaded
|
||||
useEffect(() => {
|
||||
if (!store.baseImageBitmap) {
|
||||
console.log("No base image bitmap available, skipping auto-fit");
|
||||
return;
|
||||
}
|
||||
|
||||
// When baseImageBitmap changes from null to an actual image, call onFitPressed
|
||||
// Use a slight delay to ensure the image dimensions are properly loaded
|
||||
onFitPressed();
|
||||
}, [onFitPressed, store.baseImageBitmap]);
|
||||
|
||||
// Create a function to use the left layer ref and download the bitmap from it
|
||||
const getMaskArrayBuffer = async (): Promise<Uint8Array> => {
|
||||
if (
|
||||
@@ -247,11 +237,14 @@ const PageEdit = () => {
|
||||
//const supportsMaskedInpainting = modelConfig?.tags?.includes(ModelTag.MaskedInpainting) ?? false;
|
||||
const supportsMaskedInpainting = selectedImageModel?.usesInpaintingMask ?? false;
|
||||
|
||||
if (!supportsMaskedInpainting && (store.activeTool !== "select" || store.lineNodes.length > 0)) {
|
||||
// TODO: Implement a new mode for unsupported masking and hide the nodes layer instead of clearing
|
||||
store.setActiveTool("select");
|
||||
store.clearLineNodes();
|
||||
}
|
||||
useEffect(() => {
|
||||
if (!supportsMaskedInpainting && (store.activeTool !== "select" || store.lineNodes.length > 0)) {
|
||||
// TODO: Implement a new mode for unsupported masking and hide the nodes layer instead of clearing
|
||||
store.setActiveTool("select");
|
||||
store.clearLineNodes();
|
||||
}
|
||||
}, [store, supportsMaskedInpainting]);
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
@@ -273,6 +266,7 @@ const PageEdit = () => {
|
||||
>
|
||||
<BaseImageSelector
|
||||
onImageSelect={(image: BaseSelectorImage) => {
|
||||
addHistoryImageBundle({ images: [image] });
|
||||
store.setBaseImageInfo(image);
|
||||
}}
|
||||
showLoading={
|
||||
@@ -305,11 +299,13 @@ const PageEdit = () => {
|
||||
onClear={() => {
|
||||
store.RESET();
|
||||
}}
|
||||
startingBundles={[{ images: [store.baseImageInfo] } as ImageBundle]}
|
||||
imageBundles={historyImageBundles}
|
||||
onImageSelect={(baseImage) => {
|
||||
store.clearLineNodes();
|
||||
store.setBaseImageInfo(baseImage);
|
||||
}}
|
||||
onNewImageBundle={addHistoryImageBundle}
|
||||
selectedImageToken={store.baseImageInfo?.mediaToken}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
|
||||
@@ -3,6 +3,7 @@ import { LineNode, generateId } from "~/pages/PageDraw/stores/SceneState";
|
||||
import { Node } from "~/pages/PageDraw/Node";
|
||||
import { BaseSelectorImage } from "../BaseImageSelector";
|
||||
import { FetchProxy } from "libs/tauri-utils/src/lib/FetchProxy";
|
||||
import { ImageBundle } from "../HistoryStack";
|
||||
|
||||
export type ActiveEditTool = "select" | "edit" | "expand";
|
||||
export type EditOperation = "add" | "minus";
|
||||
@@ -126,6 +127,11 @@ interface EditState {
|
||||
// pushHistory: () => void;
|
||||
// clearHistory: () => void;
|
||||
RESET: () => void;
|
||||
|
||||
// For the history stack
|
||||
historyImageBundles: ImageBundle[];
|
||||
clearHistoryImages: () => void;
|
||||
addHistoryImageBundle: (bundle: ImageBundle) => void;
|
||||
}
|
||||
|
||||
// Add a flag to prevent saving state during restoration
|
||||
@@ -172,6 +178,9 @@ export const useEditStore = create<EditState>((set, get, store) => ({
|
||||
baseImageInfo: null,
|
||||
baseImageBitmap: null,
|
||||
|
||||
// History stack state
|
||||
historyImageBundles: [],
|
||||
|
||||
// Actions
|
||||
addNode: (node: Node) => {
|
||||
set((state) => {
|
||||
@@ -1009,5 +1018,13 @@ export const useEditStore = create<EditState>((set, get, store) => ({
|
||||
|
||||
RESET: () => {
|
||||
set(store.getInitialState());
|
||||
}
|
||||
},
|
||||
|
||||
clearHistoryImages: () => {
|
||||
set({ historyImageBundles: [] });
|
||||
},
|
||||
|
||||
addHistoryImageBundle(bundle) {
|
||||
set((state) => ({ historyImageBundles: [...state.historyImageBundles, bundle] }));
|
||||
},
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user