Skip to content

Commit 529e3f0

Browse files
authored
Add new Scene Layout (#945)
* initial commit for new Scene Layout * add grid setup * some refactor + fns doc * more fixes * some refactor + make it work with negative values * add enable/disable parcels interaction * add manual (advanced) mode * lint * more fixes * advanced mode should not apply new layout, only update grid * lint * add tests * fix styling issues * CR fixes * add new texts
1 parent 9fa5dc0 commit 529e3f0

File tree

22 files changed

+1195
-158
lines changed

22 files changed

+1195
-158
lines changed

packages/@dcl/inspector/src/components/Box/Box.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from 'react'
22
import cx from 'classnames'
3+
34
import './Box.css'
45

56
interface Props {

packages/@dcl/inspector/src/components/Button/Button.css

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,27 @@
3333
.Button.danger.active {
3434
background-color: var(--primary-dark);
3535
}
36+
37+
.Button.dark {
38+
background-color: var(--base-20);
39+
border: 1px solid var(--base-15);
40+
}
41+
42+
.Button.dark:hover,
43+
.Button.dark.active {
44+
background-color: var(--base-15);
45+
}
46+
47+
.Button.blue {
48+
background-color: var(--accent-blue-05);
49+
border: 0;
50+
}
51+
52+
.Button.blue:hover,
53+
.Button.blue.active {
54+
background-color: var(--accent-blue-07);
55+
}
56+
57+
.Button.blue.big {
58+
font-weight: 700;
59+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
type Button = React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>
22

33
export type PropTypes = Omit<Button, 'type' | 'size'> & {
4-
type?: 'danger' | 'etc'
4+
type?: 'danger' | 'dark' | 'blue' | 'etc'
55
size?: 'big' | 'etc'
66
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
.Grid .row {
2+
display: flex;
3+
align-items: center;
4+
justify-content: center;
5+
}
6+
7+
.Grid .tile {
8+
background-color: var(--base-16);
9+
display: flex;
10+
align-items: flex-end;
11+
font-size: 8px;
12+
}
13+
14+
.Grid .tile:hover {
15+
background-color: var(--base-10);
16+
cursor: pointer;
17+
}
18+
19+
.Grid .tile .info {
20+
opacity: 0;
21+
position: absolute;
22+
background-color: var(--base-20);
23+
color: var(--base-5);
24+
padding: 0 6px;
25+
border: 1px solid var(--base-16);
26+
border-radius: 2px;
27+
display: flex;
28+
align-items: center;
29+
font-size: 12px;
30+
}
31+
32+
.Grid .tile .info svg {
33+
font-size: 15px;
34+
}
35+
36+
.Grid .tile:hover .info {
37+
opacity: 1;
38+
}
39+
40+
.Grid .tile svg.base {
41+
font-size: 13px;
42+
margin: 0 0 -8px -8px;
43+
z-index: 999;
44+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { useCallback } from 'react'
2+
import { BsPlusLg } from 'react-icons/bs'
3+
import { IoIosPin } from 'react-icons/io'
4+
5+
import { chunkCoords, getAxisLengths, getLargestAxis } from './utils'
6+
import { Props } from './types'
7+
8+
import './Grid.css'
9+
10+
function Grid({
11+
coords,
12+
maxTileSize = 50,
13+
minTileSize = 3,
14+
visualThreshold = 10,
15+
isBaseTile,
16+
isTileDisabled,
17+
handleTileClick
18+
}: Props) {
19+
const largestAxis = getLargestAxis(coords)
20+
const tileSize = Math.max(maxTileSize / largestAxis, minTileSize) // % of parent's size to use for max/min tile size
21+
const disableEnhancements = largestAxis >= visualThreshold // disable visual enhancements when grid is large
22+
23+
const gridStyles = {
24+
width: `${tileSize}%`,
25+
aspectRatio: '1/1',
26+
border: `${disableEnhancements ? 1 : 2}px solid var(--base-10)`,
27+
margin: `${disableEnhancements ? 1 : 2}px`
28+
}
29+
30+
const numberOfRows = coords.length / getAxisLengths(coords).y
31+
// actually chunking the array is not necessary
32+
// we could just use the `numberOfRows` to render the grid
33+
// but it's easier to read/use `[].map` than a for-loop in React...
34+
const chunks = chunkCoords(coords, numberOfRows)
35+
36+
return (
37+
<div className="Grid">
38+
{chunks.map((row) => (
39+
<Row
40+
key={`row-${row[0].y}`}
41+
row={row}
42+
tileStyles={gridStyles}
43+
isTileDisabled={isTileDisabled}
44+
onTileClick={handleTileClick}
45+
isBaseTile={isBaseTile}
46+
/>
47+
))}
48+
</div>
49+
)
50+
}
51+
52+
type Row = {
53+
row: Props['coords']
54+
tileStyles: React.CSSProperties
55+
isTileDisabled: Props['isTileDisabled']
56+
onTileClick: Props['handleTileClick']
57+
isBaseTile: Props['isBaseTile']
58+
}
59+
function Row({ row, tileStyles, isTileDisabled, onTileClick, isBaseTile }: Row) {
60+
return (
61+
<div className={`row y-${row[0].y}`}>
62+
{row.map((col) => (
63+
<Tile
64+
key={`${col.x}-${col.y}`}
65+
x={col.x}
66+
y={col.y}
67+
style={tileStyles}
68+
isTileDisabled={isTileDisabled}
69+
onTileClick={onTileClick}
70+
isBaseTile={isBaseTile}
71+
/>
72+
))}
73+
</div>
74+
)
75+
}
76+
77+
type Tile = {
78+
x: number
79+
y: number
80+
style: React.CSSProperties
81+
isTileDisabled: Props['isTileDisabled']
82+
onTileClick: Props['handleTileClick']
83+
isBaseTile: Props['isBaseTile']
84+
}
85+
function Tile({ x, y, style, isTileDisabled, onTileClick, isBaseTile }: Tile) {
86+
const isDisabled = isTileDisabled && isTileDisabled({ x, y })
87+
const isBase = isBaseTile && isBaseTile({ x, y })
88+
const handleClick = useCallback(() => {
89+
onTileClick && onTileClick({ x, y })
90+
}, [x, y, isTileDisabled])
91+
92+
const styles = { ...style, border: isDisabled ? 0 : style.border }
93+
94+
return (
95+
<div className={`tile x-${x}`} style={styles} onClick={handleClick}>
96+
<div className="info">
97+
<IoIosPin /> {`${x},${y}`}
98+
</div>
99+
{isBase && <BsPlusLg className="base" />}
100+
</div>
101+
)
102+
}
103+
104+
export default Grid
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import Grid from './Grid'
2+
import { Props } from './types'
3+
export { Grid, Props }
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Coords } from '@dcl/ecs'
2+
3+
export type Props = {
4+
coords: Coords[]
5+
isBaseTile?: (coord: Coords) => boolean
6+
isTileDisabled?: (coord: Coords) => boolean
7+
handleTileClick?: (coord: Coords) => void
8+
maxTileSize?: number // in %
9+
minTileSize?: number // in %
10+
visualThreshold?: number // in %
11+
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import { getAxisLengths, getLargestAxis, chunkCoords } from './utils'
2+
3+
describe('getAxisLengths', () => {
4+
it('returns correct lengths for coordinates', () => {
5+
const coords = [
6+
{ x: 0, y: 0 },
7+
{ x: 2, y: 2 }
8+
]
9+
const expected = { x: 3, y: 3 }
10+
expect(getAxisLengths(coords)).toEqual(expected)
11+
})
12+
13+
it('handles coordinates with negative values', () => {
14+
const coords = [
15+
{ x: -1, y: -1 },
16+
{ x: 1, y: 1 }
17+
]
18+
const expected = { x: 3, y: 3 }
19+
expect(getAxisLengths(coords)).toEqual(expected)
20+
})
21+
22+
it('handles single coordinate', () => {
23+
const coords = [{ x: 0, y: 0 }]
24+
const expected = { x: 1, y: 1 }
25+
expect(getAxisLengths(coords)).toEqual(expected)
26+
})
27+
})
28+
29+
describe('getLargestAxis', () => {
30+
it('returns correct length of the largest axis', () => {
31+
const coords = [
32+
{ x: 0, y: 0 },
33+
{ x: 2, y: 2 }
34+
]
35+
const expected = 3
36+
expect(getLargestAxis(coords)).toEqual(expected)
37+
})
38+
39+
it('handles coordinates with negative values', () => {
40+
const coords = [
41+
{ x: -1, y: -1 },
42+
{ x: 1, y: 1 }
43+
]
44+
const expected = 3
45+
expect(getLargestAxis(coords)).toEqual(expected)
46+
})
47+
48+
it('handles single coordinate', () => {
49+
const coords = [{ x: 0, y: 0 }]
50+
const expected = 1
51+
expect(getLargestAxis(coords)).toEqual(expected)
52+
})
53+
})
54+
55+
describe('chunkCoords', () => {
56+
it('returns correct chunks for coordinates', () => {
57+
const coords = [
58+
{ x: 0, y: 0 },
59+
{ x: 1, y: 1 },
60+
{ x: 2, y: 2 }
61+
]
62+
const chunkSize = 2
63+
const expected = [
64+
[
65+
{ x: 0, y: 0 },
66+
{ x: 1, y: 1 }
67+
],
68+
[{ x: 2, y: 2 }]
69+
]
70+
expect(chunkCoords(coords, chunkSize)).toEqual(expected)
71+
})
72+
73+
it('handles chunk size larger than coordinates length', () => {
74+
const coords = [
75+
{ x: 0, y: 0 },
76+
{ x: 1, y: 1 },
77+
{ x: 2, y: 2 }
78+
]
79+
const chunkSize = 5
80+
const expected = [
81+
[
82+
{ x: 0, y: 0 },
83+
{ x: 1, y: 1 },
84+
{ x: 2, y: 2 }
85+
]
86+
]
87+
expect(chunkCoords(coords, chunkSize)).toEqual(expected)
88+
})
89+
90+
it('handles chunk size of 1', () => {
91+
const coords = [
92+
{ x: 0, y: 0 },
93+
{ x: 1, y: 1 },
94+
{ x: 2, y: 2 }
95+
]
96+
const chunkSize = 1
97+
const expected = [[{ x: 0, y: 0 }], [{ x: 1, y: 1 }], [{ x: 2, y: 2 }]]
98+
expect(chunkCoords(coords, chunkSize)).toEqual(expected)
99+
})
100+
101+
it('handles empty coordinates array', () => {
102+
const coords = []
103+
const chunkSize = 2
104+
const expected = [[]]
105+
expect(chunkCoords(coords, chunkSize)).toEqual(expected)
106+
})
107+
108+
it('handles chunk size of 0', () => {
109+
const coords = [
110+
{ x: 0, y: 0 },
111+
{ x: 1, y: 1 },
112+
{ x: 2, y: 2 }
113+
]
114+
const chunkSize = 0
115+
const expected = [
116+
[
117+
{ x: 0, y: 0 },
118+
{ x: 1, y: 1 },
119+
{ x: 2, y: 2 }
120+
]
121+
]
122+
expect(chunkCoords(coords, chunkSize)).toEqual(expected)
123+
})
124+
})
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { Coords } from '@dcl/ecs'
2+
3+
import { Props } from './types'
4+
5+
/*
6+
** Get the length of the axis (x,y) on the grid
7+
*/
8+
export function getAxisLengths(coords: Props['coords']): Coords {
9+
const [first, last] = [coords[0], coords[coords.length - 1]]
10+
return {
11+
x: Math.abs(last.x - first.x) + 1, // zero-based
12+
y: Math.abs(first.y - last.y) + 1 // zero-based
13+
}
14+
}
15+
16+
/*
17+
** Get's the axis with the bigger length
18+
*/
19+
export function getLargestAxis(coords: Props['coords']): number {
20+
const axisLength = getAxisLengths(coords)
21+
return Math.max(axisLength.x, axisLength.y)
22+
}
23+
24+
/*
25+
** Splits coords into chunks of specific size
26+
*/
27+
export function chunkCoords(coords: Props['coords'], chunkSize: number): Props['coords'][] {
28+
if (chunkSize <= 0 || chunkSize >= coords.length) return [coords]
29+
30+
const chunks = []
31+
for (let i = 0; i < coords.length; i += chunkSize) {
32+
const tmp = []
33+
for (let j = i; j < i + chunkSize; j++) {
34+
tmp.push(coords[j])
35+
}
36+
chunks.push(tmp)
37+
}
38+
39+
return chunks
40+
}

0 commit comments

Comments
 (0)