Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(masonry): create tree for the stack of bricks #410

Open
wants to merge 2 commits into
base: gsoc-2024/week-3-4
Choose a base branch
from
Open
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
4 changes: 4 additions & 0 deletions modules/masonry/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};
5 changes: 5 additions & 0 deletions modules/masonry/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,10 @@
"test": "vitest",
"coverage": "vitest run --coverage",
"lint": "eslint src"
},
"devDependencies": {
"@types/jest": "^29.5.12",
"jest": "^29.7.0",
"ts-jest": "^29.1.5"
}
}
2 changes: 1 addition & 1 deletion modules/masonry/playground/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<link rel="icon" type="image/svg+xml" href="" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Masonry-Test</title>
</head>
Expand Down
1 change: 0 additions & 1 deletion modules/masonry/playground/public/vite.svg

This file was deleted.

141 changes: 13 additions & 128 deletions modules/masonry/playground/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,134 +1,19 @@
import React, { useState, useRef, useEffect } from 'react';

interface Brick {
id: number;
type: string;
x: number;
y: number;
color: string;
}
import React from 'react';
import DivStack from './components/DivStack';
import NormalSVG from './components/NormalSVG';
import SVGWithValues from './components/SVGWithValues';

const App: React.FC = () => {
const [bricks, setBricks] = useState<Brick[]>([
{ id: 1, type: 'top', x: 50, y: 50, color: '#ff6b6b' },
{ id: 2, type: 'middle', x: 50, y: 110, color: '#4ecdc4' },
{ id: 3, type: 'middle', x: 50, y: 170, color: '#45b7d1' },
{ id: 4, type: 'middle', x: 50, y: 230, color: '#f9d56e' },
{ id: 5, type: 'middle', x: 50, y: 290, color: '#ff9ff3' },
{ id: 6, type: 'bottom', x: 50, y: 350, color: '#54a0ff' },
]);
const [isDragging, setIsDragging] = useState(false);
const [draggedBrickId, setDraggedBrickId] = useState<number | null>(null);
const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 });
const svgRef = useRef<SVGSVGElement>(null);

const isConnected = (brick1: Brick, brick2: Brick) =>
Math.abs(brick1.y + 60 - brick2.y) < 5 && Math.abs(brick1.x - brick2.x) < 5;

const findConnectedBricks = (startId: number): number[] => {
const result: number[] = [startId];
let currentId = startId;

// Find bricks below
while (true) {
const currentBrick = bricks.find(b => b.id === currentId);
const nextBrick = bricks.find(b => b.id !== currentId && isConnected(currentBrick!, b));
if (nextBrick && nextBrick.y > currentBrick!.y) {
result.push(nextBrick.id);
currentId = nextBrick.id;
} else {
break;
}
}

return result;
};

const handleMouseDown = (e: React.MouseEvent, id: number) => {
const brick = bricks.find(b => b.id === id);
if (brick) {
const svgRect = svgRef.current?.getBoundingClientRect();
if (svgRect) {
setIsDragging(true);
setDraggedBrickId(id);
setDragOffset({
x: e.clientX - svgRect.left - brick.x,
y: e.clientY - svgRect.top - brick.y
});
}
}
};

const handleMouseMove = (e: MouseEvent) => {
if (isDragging && draggedBrickId !== null) {
const svgRect = svgRef.current?.getBoundingClientRect();
if (svgRect) {
const newX = e.clientX - svgRect.left - dragOffset.x;
const newY = e.clientY - svgRect.top - dragOffset.y;

const connectedBricks = findConnectedBricks(draggedBrickId);

setBricks(prevBricks => {
return prevBricks.map(brick => {
if (connectedBricks.includes(brick.id)) {
const index = connectedBricks.indexOf(brick.id);
return {
...brick,
x: newX,
y: newY + index * 60
};
}
return brick;
});
});
}
}
};

const handleMouseUp = () => {
setIsDragging(false);
setDraggedBrickId(null);
};

useEffect(() => {
window.addEventListener('mousemove', handleMouseMove);
window.addEventListener('mouseup', handleMouseUp);
return () => {
window.removeEventListener('mousemove', handleMouseMove);
window.removeEventListener('mouseup', handleMouseUp);
};
}, [isDragging, draggedBrickId]);

const renderBrickPath = (type: string) => {
switch (type) {
case 'top':
return "M0,0 h100 v50 h-80 l-20,10 v-60 z";
case 'middle':
return "M0,0 h80 l20,-10 v60 h-80 l-20,10 v-60 z";
case 'bottom':
return "M0,0 h80 l20,-10 v60 h-100 v-50 z";
default:
return "";
}
};

return (
<svg ref={svgRef} width="800" height="600" style={{ border: '1px solid black' }}>
{bricks.map(brick => (
<g
key={brick.id}
transform={`translate(${brick.x},${brick.y})`}
onMouseDown={(e) => handleMouseDown(e, brick.id)}
>
<path
d={renderBrickPath(brick.type)}
fill={brick.color}
stroke="black"
/>
</g>
))}
</svg>
<div>
<h1>Div Stack</h1>
<DivStack />
<h1>Normal SVG</h1>
<NormalSVG />
<h1>SVG With Values</h1>
<SVGWithValues />
</div>
);
};

export default App;
export default App;
1 change: 0 additions & 1 deletion modules/masonry/playground/src/assets/react.svg

This file was deleted.

135 changes: 135 additions & 0 deletions modules/masonry/playground/src/components/DivStack.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable no-constant-condition */
import React, { useState, useEffect } from 'react';

interface Brick {
id: number;
type: string;
x: number;
y: number;
color: string;
}

const DivStack: React.FC = () => {
const [bricks, setBricks] = useState<Brick[]>([
{ id: 1, type: 'top', x: 50, y: 50, color: '#ff6b6b' },
{ id: 2, type: 'middle', x: 50, y: 110, color: '#4ecdc4' },
{ id: 3, type: 'middle', x: 50, y: 170, color: '#45b7d1' },
{ id: 4, type: 'middle', x: 50, y: 230, color: '#f9d56e' },
{ id: 5, type: 'middle', x: 50, y: 290, color: '#ff9ff3' },
{ id: 6, type: 'bottom', x: 50, y: 350, color: '#54a0ff' },
]);

const [isDragging, setIsDragging] = useState(false);
const [draggedBrickId, setDraggedBrickId] = useState<number | null>(null);
const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 });

const isConnected = (brick1: Brick, brick2: Brick) =>
Math.abs(brick1.y + 60 - brick2.y) < 5 && Math.abs(brick1.x - brick2.x) < 5;

const findConnectedBricks = (startId: number): number[] => {
const result: number[] = [startId];
let currentId = startId;

while (true) {
const currentBrick = bricks.find((b) => b.id === currentId);
const nextBrick = bricks.find((b) => b.id !== currentId && isConnected(currentBrick!, b));
if (nextBrick && nextBrick.y > currentBrick!.y) {
result.push(nextBrick.id);
currentId = nextBrick.id;
} else {
break;
}
}
return result;
};

const handleMouseDown = (e: React.MouseEvent, id: number) => {
const brick = bricks.find((b) => b.id === id);
if (brick) {
setIsDragging(true);
setDraggedBrickId(id);
setDragOffset({
x: e.clientX - brick.x,
y: e.clientY - brick.y,
});
}
};

const handleMouseMove = (e: MouseEvent) => {
if (isDragging && draggedBrickId !== null) {
const newX = e.clientX - dragOffset.x;
const newY = e.clientY - dragOffset.y;
const connectedBricks = findConnectedBricks(draggedBrickId);
setBricks((prevBricks) => {
return prevBricks.map((brick) => {
if (connectedBricks.includes(brick.id)) {
const index = connectedBricks.indexOf(brick.id);
return {
...brick,
x: newX,
y: newY + index * 60,
};
}
return brick;
});
});
}
};

const handleMouseUp = () => {
setIsDragging(false);
setDraggedBrickId(null);
};

useEffect(() => {
window.addEventListener('mousemove', handleMouseMove);
window.addEventListener('mouseup', handleMouseUp);
return () => {
window.removeEventListener('mousemove', handleMouseMove);
window.removeEventListener('mouseup', handleMouseUp);
};
}, [isDragging, draggedBrickId]);

return (
<div className="workspace">
<div
id="workspace-div"
className="workspace-div"
style={{
width: '800px',
height: '600px',
border: '1px solid black',
position: 'relative',
}}
>
{bricks.map((brick) => (
<div
key={brick.id}
className="brick"
style={{
position: 'absolute',
left: brick.x,
top: brick.y,
width: '100px',
height: '50px',
backgroundColor: brick.color,
cursor: 'grab',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
border: '1px solid black',
}}
onMouseDown={(e) => handleMouseDown(e, brick.id)}
>
{brick.type === 'top' && <button>Top Button</button>}
{brick.type === 'middle' && <input type="text" placeholder="Middle Input" />}
{brick.type === 'bottom' && <label>Bottom Label</label>}
</div>
))}
</div>
</div>
);
};

export default DivStack;
Loading
Loading