mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-22 01:29:31 -04:00
Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a0c58db0d2 | ||
|
|
fd479135f3 | ||
|
|
7ea660a530 | ||
|
|
641c83ea6e | ||
|
|
4f397171f0 | ||
|
|
4c666fb2ff | ||
|
|
c6e93508e4 | ||
|
|
7b1d0d278f |
@@ -6,7 +6,7 @@ import "./App.css";
|
||||
import PromptEditor from "./PromptEditor/PromptEditor";
|
||||
import SideToolbar from "./components/ui/SideToolbar";
|
||||
// Import the Zustand store
|
||||
import { useSceneStore } from "./stores/SceneState";
|
||||
import { AspectRatioType, useSceneStore } from "./stores/SceneState";
|
||||
import { useUndoRedoHotkeys } from "./hooks/useUndoRedoHotkeys";
|
||||
import { useDeleteHotkeys } from "./hooks/useDeleteHotkeys";
|
||||
import { useCopyPasteHotkeys } from "./hooks/useCopyPasteHotkeys"; // Import the hook
|
||||
@@ -196,6 +196,26 @@ const PageDraw = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const onFitPressed = async () => {
|
||||
// Get the stage and its container dimensions
|
||||
const stage = stageRef.current;
|
||||
|
||||
// Get container dimensions
|
||||
const containerWidth = stage.container().offsetWidth;
|
||||
const containerHeight = stage.container().offsetHeight;
|
||||
|
||||
// Get canvas dimensions from store aspect ratio
|
||||
const canvasW = store.getAspectRatioDimensions().width;
|
||||
const canvasH = store.getAspectRatioDimensions().height;
|
||||
|
||||
// Calculate position to center canvas in container
|
||||
stage.position({
|
||||
x: (containerWidth - canvasW) / 2,
|
||||
y: (containerHeight - canvasH) / 2
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
@@ -219,7 +239,23 @@ const PageDraw = () => {
|
||||
}}
|
||||
onAspectRatioChange={(ratio: string) => {
|
||||
console.log("Aspect ratio:", ratio);
|
||||
// Handle aspect ratio changes here
|
||||
// Convert ratio string to AspectRatioType enum
|
||||
const ratioToType = (ratio: string): AspectRatioType => {
|
||||
switch (ratio) {
|
||||
case "2:3":
|
||||
return AspectRatioType.PORTRAIT;
|
||||
case "3:2":
|
||||
return AspectRatioType.LANDSCAPE;
|
||||
case "1:1":
|
||||
return AspectRatioType.SQUARE;
|
||||
default:
|
||||
return AspectRatioType.NONE;
|
||||
}
|
||||
};
|
||||
|
||||
const aspectRatioType = ratioToType(ratio);
|
||||
store.setAspectRatioType(aspectRatioType);
|
||||
onFitPressed()
|
||||
}}
|
||||
onEnqueuePressed={onEnqueuedPressed}
|
||||
/>
|
||||
@@ -413,6 +449,12 @@ const PageDraw = () => {
|
||||
showIconsInList
|
||||
triggerLabel="Model"
|
||||
/>
|
||||
<button
|
||||
className="bg-transparent p-2 text-lg text-white hover:text-white hover:bg-white/20 rounded transition"
|
||||
onClick={onFitPressed}
|
||||
>
|
||||
Fit
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -49,6 +49,9 @@ export const PaintSurface = ({
|
||||
stageRef,
|
||||
transformerRefs,
|
||||
}: MiraiProps) => {
|
||||
// switch off to be preview panel mode.
|
||||
const singlePaneMode = true
|
||||
|
||||
const store = useSceneStore(); // Use store directly
|
||||
const imageRef = React.useRef<HTMLImageElement>(null);
|
||||
const [snapshotImage, setSnapshotImage] = useState<HTMLImageElement | null>(
|
||||
@@ -66,7 +69,7 @@ export const PaintSurface = ({
|
||||
const [rightPanelHeight, setRightPanelHeight] = useState(1024);
|
||||
|
||||
/* 1️⃣ Track SplitPane percent so we can re-measure */
|
||||
const [leftPct, setLeftPct] = useState(50);
|
||||
const [leftPct, setLeftPct] = useState(singlePaneMode ? 100:50);
|
||||
const [isDrawing, setIsDrawing] = useState(false);
|
||||
const [lastPoint, setLastPoint] = useState<{ x: number; y: number } | null>(
|
||||
null,
|
||||
@@ -131,17 +134,17 @@ export const PaintSurface = ({
|
||||
y: number;
|
||||
}): { x: number; y: number } => {
|
||||
return {
|
||||
x: Math.max(0, Math.min(point.x, leftPanelWidth)),
|
||||
y: Math.max(0, Math.min(point.y, leftPanelHeight)),
|
||||
x: Math.max(0, Math.min(point.x, store.getAspectRatioDimensions().width)), //leftPanelWidth)),
|
||||
y: Math.max(0, Math.min(point.y, store.getAspectRatioDimensions().height))//leftPanelHeight)),
|
||||
};
|
||||
};
|
||||
|
||||
const isWithinLeftPanel = (point: { x: number; y: number }): boolean => {
|
||||
return (
|
||||
point.x >= 0 &&
|
||||
point.x <= leftPanelWidth &&
|
||||
point.x <= store.getAspectRatioDimensions().width && //leftPanelWidth &&
|
||||
point.y >= 0 &&
|
||||
point.y <= leftPanelHeight
|
||||
point.y <= store.getAspectRatioDimensions().height // leftPanelHeight
|
||||
);
|
||||
};
|
||||
|
||||
@@ -194,7 +197,8 @@ export const PaintSurface = ({
|
||||
stroke: activeTool === "draw" ? brushColor : fillColor || "#ffffff",
|
||||
strokeWidth: brushSize / stage.scaleX(),
|
||||
draggable: true,
|
||||
opacity: opacity
|
||||
opacity: opacity,
|
||||
locked: false,
|
||||
};
|
||||
store.selectNode(null);
|
||||
store.addLineNode(newLineNode, false); // Don't save state when starting line
|
||||
@@ -976,10 +980,11 @@ export const PaintSurface = ({
|
||||
}
|
||||
}, [selectedNodeIds]);
|
||||
|
||||
|
||||
return (
|
||||
<SplitPane
|
||||
singlePaneMode={true}
|
||||
initialPercent={50}
|
||||
singlePaneMode={singlePaneMode}
|
||||
initialPercent={singlePaneMode?100 : 50}
|
||||
onChange={setLeftPct}
|
||||
left={
|
||||
<div className="flex h-full w-full items-center justify-center overflow-hidden">
|
||||
@@ -990,7 +995,7 @@ export const PaintSurface = ({
|
||||
scaleX={1} // Initial scale, controlled by wheel/zoom
|
||||
scaleY={1} // Initial scale, controlled by wheel/zoom
|
||||
style={{
|
||||
display: "block",
|
||||
// display: "block",
|
||||
background: "transparent", // Or use fillColor if stage background is desired directly
|
||||
}}
|
||||
x={stagePosition.x} // Set the x position
|
||||
@@ -1007,14 +1012,14 @@ export const PaintSurface = ({
|
||||
<Layer
|
||||
ref={leftPanelRef}
|
||||
clipFunc={(ctx) => {
|
||||
ctx.rect(0, 0, leftPanelWidth, leftPanelHeight);
|
||||
ctx.rect(0, 0, store.getAspectRatioDimensions().width, store.getAspectRatioDimensions().height); // leftPanelWidth, leftPanelHeight);
|
||||
}}
|
||||
>
|
||||
<Rect
|
||||
x={0}
|
||||
y={0}
|
||||
width={leftPanelWidth}
|
||||
height={leftPanelHeight}
|
||||
width={store.getAspectRatioDimensions().width}
|
||||
height={store.getAspectRatioDimensions().height}
|
||||
fill={fillColor}
|
||||
listening={false}
|
||||
zIndex={-1}
|
||||
|
||||
@@ -12,6 +12,7 @@ import { getCanvasRenderBitmap } from "../../../signals/canvasRenderBitmap"
|
||||
const PromptEditor: React.FC<PromptEditorProps> = ({
|
||||
onImageStyleChange,
|
||||
onEnqueuePressed,
|
||||
onAspectRatioChange,
|
||||
}) => {
|
||||
const [images, setImages] = useState<ImageStyle[]>([]);
|
||||
|
||||
@@ -40,6 +41,7 @@ const PromptEditor: React.FC<PromptEditorProps> = ({
|
||||
EncodeImageBitmapToBase64={EncodeImageBitmapToBase64}
|
||||
useJobContext={useJobContext}
|
||||
onEnqueuePressed={onEnqueuePressed}
|
||||
onAspectRatioChange={onAspectRatioChange}
|
||||
/>
|
||||
</JobProvider>
|
||||
|
||||
|
||||
@@ -21,6 +21,13 @@ export type LineNode = {
|
||||
locked?: boolean; // Add locked property
|
||||
};
|
||||
|
||||
// Add this enum at the top of the file with other types
|
||||
export enum AspectRatioType {
|
||||
PORTRAIT = '2:3', // 683 x 1024
|
||||
LANDSCAPE = '3:2', // 1024 x 683
|
||||
SQUARE = '1:1', // 1024 x 1024
|
||||
NONE = 'none' // No aspect ratio constraint
|
||||
}
|
||||
|
||||
// Logic to remove background from image nodes would go here
|
||||
const convertFileToBase64 = (file: File): Promise<string> => {
|
||||
@@ -137,6 +144,13 @@ interface SceneState {
|
||||
|
||||
// Add new lock action
|
||||
toggleLock: (nodeIds: string[]) => void;
|
||||
|
||||
// Add minimal aspect ratio property
|
||||
aspectRatioType: AspectRatioType;
|
||||
|
||||
// Add aspect ratio action
|
||||
setAspectRatioType: (type: AspectRatioType) => void;
|
||||
getAspectRatioDimensions: () => { width: number; height: number };
|
||||
}
|
||||
|
||||
const generateId = (): string => {
|
||||
@@ -182,6 +196,9 @@ export const useSceneStore = create<SceneState>((set, get) => ({
|
||||
cursorPosition: null,
|
||||
cursorVisible: false,
|
||||
|
||||
// Add initial aspect ratio state
|
||||
aspectRatioType: AspectRatioType.NONE,
|
||||
|
||||
// Actions
|
||||
addNode: (node: Node) => {
|
||||
set((state) => {
|
||||
@@ -853,6 +870,7 @@ export const useSceneStore = create<SceneState>((set, get) => ({
|
||||
brushColor: state.brushColor,
|
||||
brushSize: state.brushSize,
|
||||
fillColor: state.fillColor,
|
||||
aspectRatioType: state.aspectRatioType,
|
||||
version: "1.0"
|
||||
};
|
||||
|
||||
@@ -911,7 +929,8 @@ export const useSceneStore = create<SceneState>((set, get) => ({
|
||||
selectedNodeIds: [],
|
||||
brushColor: sceneData.brushColor || '#000000',
|
||||
brushSize: sceneData.brushSize || 5,
|
||||
fillColor: sceneData.fillColor || 'white'
|
||||
fillColor: sceneData.fillColor || 'white',
|
||||
aspectRatioType: sceneData.aspectRatioType || AspectRatioType.NONE
|
||||
});
|
||||
|
||||
isRestoring = false;
|
||||
@@ -1222,5 +1241,25 @@ export const useSceneStore = create<SceneState>((set, get) => ({
|
||||
};
|
||||
});
|
||||
get().saveState();
|
||||
}
|
||||
},
|
||||
|
||||
// Add aspect ratio actions
|
||||
setAspectRatioType: (type: AspectRatioType) => {
|
||||
set({ aspectRatioType: type });
|
||||
get().saveState();
|
||||
},
|
||||
|
||||
getAspectRatioDimensions: () => {
|
||||
const { aspectRatioType } = get();
|
||||
switch (aspectRatioType) {
|
||||
case AspectRatioType.PORTRAIT:
|
||||
return { width: 683, height: 1024 };
|
||||
case AspectRatioType.LANDSCAPE:
|
||||
return { width: 1024, height: 683 };
|
||||
case AspectRatioType.SQUARE:
|
||||
return { width: 1024, height: 1024 };
|
||||
default:
|
||||
return { width: 1024, height: 683 }; // Default to landscape
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -63,6 +63,7 @@ interface PromptBox2DProps {
|
||||
EncodeImageBitmapToBase64: (imageBitmap: ImageBitmap) => Promise<string>;
|
||||
useJobContext: () => JobContextType;
|
||||
onEnqueuePressed?: () => void | Promise<void>;
|
||||
onAspectRatioChange?: (ratio: AspectRatio) => void;
|
||||
}
|
||||
|
||||
export const PromptBox2D = ({
|
||||
@@ -71,6 +72,7 @@ export const PromptBox2D = ({
|
||||
EncodeImageBitmapToBase64,
|
||||
useJobContext,
|
||||
onEnqueuePressed,
|
||||
onAspectRatioChange,
|
||||
}: PromptBox2DProps) => {
|
||||
useSignals();
|
||||
|
||||
@@ -120,6 +122,7 @@ export const PromptBox2D = ({
|
||||
});
|
||||
|
||||
const handleAspectRatioSelect = (selectedItem: PopoverItem) => {
|
||||
onAspectRatioChange?.(selectedItem.label);
|
||||
setAspectRatioList((prev) =>
|
||||
prev.map((item) => ({
|
||||
...item,
|
||||
@@ -448,7 +451,7 @@ export const PromptBox2D = ({
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
// Stop propagation of keyboard events to prevent them from reaching the canvas
|
||||
e.stopPropagation();
|
||||
|
||||
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
handleEnqueue();
|
||||
|
||||
Reference in New Issue
Block a user