Compare commits

...
5 changed files with 108 additions and 17 deletions
@@ -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();