Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/Drawd.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,9 @@ export default function Drawd({ initialRoomCode }) {
setToast(message);
toastTimerRef.current = setTimeout(() => setToast(null), duration);
}, []);
useEffect(() => {
return () => { if (toastTimerRef.current) clearTimeout(toastTimerRef.current); };
}, []);

// ── Drag-over state (drop zone overlay) ───────────────────────────────────────────
const [isDraggingOver, setIsDraggingOver] = useState(false);
Expand All @@ -307,13 +310,13 @@ export default function Drawd({ initialRoomCode }) {
const drawdFile = detectDrawdFile(e.dataTransfer.files);
if (!drawdFile) {
const imageFiles = Array.from(e.dataTransfer.files).filter(
(f) => f.type === "image/png" || f.type === "image/jpeg"
(f) => f.type.startsWith("image/")
);
if (imageFiles.length === 0) return;
const rect = canvasRef.current.getBoundingClientRect();
const worldX = (e.clientX - rect.left - pan.x) / zoom;
const worldY = (e.clientY - rect.top - pan.y) / zoom;
handleCanvasDrop(e, worldX, worldY);
handleCanvasDrop(imageFiles, worldX, worldY);
showToast(`Created ${imageFiles.length} screen${imageFiles.length > 1 ? "s" : ""} from dropped images`);
return;
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/CanvasArea.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export function CanvasArea({
style={{
position: "absolute",
inset: 0,
background: "rgba(97,175,239,0.08)",
background: COLORS.accent008,
border: `2px dashed ${COLORS.accent}`,
borderRadius: 12,
display: "flex",
Expand Down
2 changes: 1 addition & 1 deletion src/components/Toast.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export function Toast({ message }) {
color: COLORS.text,
fontFamily: FONTS.mono,
fontSize: 13,
zIndex: Z_INDEX.contextMenu + 1,
zIndex: Z_INDEX.toast,
boxShadow: "0 8px 32px rgba(0,0,0,0.5)",
pointerEvents: "none",
}}>
Expand Down
98 changes: 32 additions & 66 deletions src/hooks/useScreenManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,30 @@ import {
HEADER_HEIGHT,
} from "../constants";

function makeScreen(overrides = {}) {
return {
id: generateId(),
name: "",
x: 0,
y: 0,
width: DEFAULT_SCREEN_WIDTH,
imageData: null,
description: "",
notes: "",
codeRef: "",
status: "new",
acceptanceCriteria: [],
hotspots: [],
stateGroup: null,
stateName: "",
tbd: false,
tbdNote: "",
roles: [],
figmaSource: null,
...overrides,
};
}

export function useScreenManager(pan, zoom, canvasRef) {
const [screens, setScreens] = useState([]);
const [connections, setConnections] = useState([]);
Expand Down Expand Up @@ -109,26 +133,12 @@ export function useScreenManager(pan, zoom, canvasRef) {
const count = screenCounter.current++;
const offsetX = (screens.length % GRID_COLUMNS) * GRID_COL_WIDTH + GRID_MARGIN;
const offsetY = Math.floor(screens.length / GRID_COLUMNS) * GRID_ROW_HEIGHT + GRID_MARGIN;
const newScreen = {
id: generateId(),
const newScreen = makeScreen({
name: name || SCREEN_NAME_TEMPLATE(count),
x: (-pan.x + offsetX) / zoom,
y: (-pan.y + offsetY) / zoom,
width: DEFAULT_SCREEN_WIDTH,
imageData,
description: "",
notes: "",
codeRef: "",
status: "new",
acceptanceCriteria: [],
hotspots: [],
stateGroup: null,
stateName: "",
tbd: false,
tbdNote: "",
roles: [],
figmaSource: null,
};
});
setScreens((prev) => [...prev, newScreen]);
setSelectedScreen(newScreen.id);
}, [screens, connections, documents, pushHistory, pan, zoom]);
Expand All @@ -141,53 +151,25 @@ export function useScreenManager(pan, zoom, canvasRef) {
const vh = el ? el.clientHeight : VIEWPORT_FALLBACK_HEIGHT;
const cx = (-pan.x + vw / 2) / zoom - DEFAULT_SCREEN_WIDTH / 2 + offset * PASTE_STAGGER;
const cy = (-pan.y + vh / 2) / zoom - CENTER_HEIGHT_ESTIMATE / 2 + offset * PASTE_STAGGER;
const newScreen = {
id: generateId(),
const newScreen = makeScreen({
name: name || SCREEN_NAME_TEMPLATE(count),
x: cx,
y: cy,
width: DEFAULT_SCREEN_WIDTH,
imageData,
description: "",
notes: "",
codeRef: "",
status: "new",
acceptanceCriteria: [],
hotspots: [],
stateGroup: null,
stateName: "",
tbd: false,
tbdNote: "",
roles: [],
figmaSource: null,
...meta,
};
});
setScreens((prev) => [...prev, newScreen]);
setSelectedScreen(newScreen.id);
}, [screens, connections, documents, pushHistory, pan, zoom, canvasRef]);

const addScreensBatch = useCallback((screenDefs) => {
if (screenDefs.length === 0) return 0;
pushHistory(screens, connections, documents);
const newScreens = screenDefs.map((def) => ({
id: generateId(),
const newScreens = screenDefs.map((def) => makeScreen({
name: def.name,
x: def.x,
y: def.y,
width: DEFAULT_SCREEN_WIDTH,
imageData: def.imageData,
description: "",
notes: "",
codeRef: "",
status: "new",
acceptanceCriteria: [],
hotspots: [],
stateGroup: null,
stateName: "",
tbd: false,
tbdNote: "",
roles: [],
figmaSource: null,
}));
setScreens((prev) => [...prev, ...newScreens]);
setSelectedScreen(newScreens[0].id);
Expand Down Expand Up @@ -357,11 +339,7 @@ export function useScreenManager(pan, zoom, canvasRef) {
});
}, [addScreenAtCenter, selectedScreen, screens, assignScreenImage]);

const handleCanvasDrop = useCallback((e, worldX, worldY) => {
e.preventDefault();
const files = Array.from(e.dataTransfer.files).filter(
(f) => f.type === "image/png" || f.type === "image/jpeg"
);
const handleCanvasDrop = useCallback((files, worldX, worldY) => {
if (files.length === 0) return;

Promise.all(
Expand Down Expand Up @@ -840,26 +818,14 @@ export function useScreenManager(pan, zoom, canvasRef) {
}

screenCounter.current++;
const newScreen = {
id: generateId(),
const newScreen = makeScreen({
name: parent.name,
x: parent.x + STATE_VARIANT_OFFSET,
y: parent.y,
width: parent.width || DEFAULT_SCREEN_WIDTH,
imageData: null,
description: "",
notes: "",
codeRef: "",
status: "new",
acceptanceCriteria: [],
hotspots: [],
stateGroup: groupId,
stateName: `State ${stateNumber - 1}`,
tbd: false,
tbdNote: "",
roles: [],
figmaSource: null,
};
});
setScreens((prev) => [...prev, newScreen]);
setSelectedScreen(newScreen.id);
}, [screens, connections, documents, pushHistory]);
Expand Down
1 change: 1 addition & 0 deletions src/styles/theme.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export const Z_INDEX = {
batchBar: 900,
modal: 1000,
contextMenu: 9999,
toast: 10000,
};

export const STATUS_CONFIG = {
Expand Down
6 changes: 3 additions & 3 deletions src/utils/dropImport.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { GRID_COLUMNS, GRID_COL_WIDTH, GRID_ROW_HEIGHT, DROP_OVERLAP_MARGIN } from "../constants";
import { GRID_COLUMNS, GRID_COL_WIDTH, GRID_ROW_HEIGHT, GRID_MARGIN, DROP_OVERLAP_MARGIN } from "../constants";
import { rectsIntersect } from "./canvasMath";

/**
Expand All @@ -22,10 +22,10 @@ export function filenameToScreenName(filename) {
* @param {number[]} heights - height of each item
* @param {number} originX
* @param {number} originY
* @param {number} rowGap - vertical spacing between rows (default 60)
* @param {number} rowGap - vertical spacing between rows (default GRID_MARGIN)
* @returns {Array<{x, y}>}
*/
export function gridPositions(heights, originX, originY, rowGap = 60) {
export function gridPositions(heights, originX, originY, rowGap = GRID_MARGIN) {
const positions = [];
let rowY = originY;
let rowMaxHeight = 0;
Expand Down
Loading