refactor: devtool tabs
This commit is contained in:
parent
1992c0ae68
commit
6ee9176756
|
@ -1,9 +1,10 @@
|
|||
import {useRef, useState} from 'react';
|
||||
|
||||
import {useEcs} from '@/context/ecs.js';
|
||||
import {Tab, Tabs, TabList, TabPanel} from 'react-tabs';
|
||||
import 'react-tabs/style/react-tabs.css';
|
||||
|
||||
import styles from './devtools.module.css';
|
||||
|
||||
import Tiles from './devtools/tiles.jsx';
|
||||
|
||||
export default function Devtools({
|
||||
applyFilters,
|
||||
brush,
|
||||
|
@ -13,142 +14,43 @@ export default function Devtools({
|
|||
setLayer,
|
||||
setStamp,
|
||||
}) {
|
||||
const offsetRef = useRef();
|
||||
const [selection, setSelection] = useState({x: 0, y: 0, w: 2, h: 2});
|
||||
const [moveStart, setMoveStart] = useState();
|
||||
const [ecs] = useEcs();
|
||||
if (!ecs) {
|
||||
return false;
|
||||
}
|
||||
const master = ecs.get(1);
|
||||
if (!master) {
|
||||
return false;
|
||||
}
|
||||
const {TileLayers} = master;
|
||||
const {sourceJson, tileSize} = TileLayers.layer(0);
|
||||
const {w, h} = sourceJson.meta.size;
|
||||
return (
|
||||
<div className={styles.devtools}>
|
||||
<form>
|
||||
<div className={styles.engineBar}>
|
||||
<label>
|
||||
Apply filters
|
||||
<input
|
||||
checked={applyFilters}
|
||||
onChange={() => {
|
||||
setApplyFilters(!applyFilters);
|
||||
}}
|
||||
type="checkbox"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div className={styles.paintBar}>
|
||||
<div className={styles.layer}>
|
||||
<label>
|
||||
Layer:
|
||||
<select
|
||||
onChange={(event) => {
|
||||
setLayer(event.target.value)
|
||||
}}
|
||||
value={layer}
|
||||
>
|
||||
{
|
||||
Object.keys(TileLayers.layers)
|
||||
.map((layer, i) => (
|
||||
<option
|
||||
key={i}
|
||||
value={i}
|
||||
>
|
||||
{i}
|
||||
</option>
|
||||
))
|
||||
}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div className={styles.brush}>
|
||||
<label>
|
||||
Brush:
|
||||
<select
|
||||
onChange={(event) => {
|
||||
setLayer(event.target.value)
|
||||
}}
|
||||
value={brush}
|
||||
>
|
||||
<option>Paint</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
|
||||
<div
|
||||
onMouseDown={(event) => {
|
||||
if (!offsetRef.current) {
|
||||
return;
|
||||
}
|
||||
const {left, top} = offsetRef.current.getBoundingClientRect();
|
||||
if (
|
||||
event.clientX - left >= w
|
||||
|| event.clientY - top >= h
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const x = Math.floor((event.clientX - left) / tileSize.x);
|
||||
const y = Math.floor((event.clientY - top) / tileSize.y);
|
||||
setMoveStart({x, y});
|
||||
setSelection({x, y, w: 1, h: 1});
|
||||
}}
|
||||
onMouseMove={(event) => {
|
||||
if (!offsetRef.current) {
|
||||
return;
|
||||
}
|
||||
if (!moveStart) {
|
||||
return;
|
||||
}
|
||||
const {x: sx, y: sy} = moveStart;
|
||||
const {left, top} = offsetRef.current.getBoundingClientRect();
|
||||
const x = Math.floor(
|
||||
Math.max(0, Math.min(w - 1, (event.clientX - left)) / tileSize.x),
|
||||
);
|
||||
const y = Math.floor(
|
||||
Math.max(0, Math.min(h - 1, (event.clientY - top)) / tileSize.y),
|
||||
);
|
||||
const mx = Math.min(sx, x);
|
||||
const my = Math.min(sy, y);
|
||||
setSelection({x: mx, y: my, w: Math.abs(sx - x) + 1, h: Math.abs(sy - y) + 1});
|
||||
}}
|
||||
onMouseUp={() => {
|
||||
setMoveStart();
|
||||
const stamp = [];
|
||||
const {x, y, w: sw, h: sh} = selection;
|
||||
const tw = w / tileSize.x;
|
||||
for (let iy = 0; iy < sh; ++iy) {
|
||||
const row = [];
|
||||
for (let ix = 0; ix < sw; ++ix) {
|
||||
row.push((y + iy) * tw + x + ix);
|
||||
}
|
||||
stamp.push(row);
|
||||
}
|
||||
setStamp(stamp);
|
||||
}}
|
||||
className={styles.selectionWrapper}
|
||||
ref={offsetRef}
|
||||
>
|
||||
<div
|
||||
className={styles.selection}
|
||||
style={{
|
||||
top: selection.y * tileSize.y,
|
||||
left: selection.x * tileSize.x,
|
||||
height: selection.h * tileSize.x,
|
||||
width: selection.w * tileSize.y,
|
||||
}}
|
||||
/>
|
||||
<img
|
||||
alt="tileset"
|
||||
src={TileLayers.layer(0).source.replace('.json', '.png')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Tabs>
|
||||
<TabList>
|
||||
<Tab>Dashboard</Tab>
|
||||
<Tab>Tiles</Tab>
|
||||
</TabList>
|
||||
|
||||
<TabPanel>
|
||||
<form>
|
||||
<div className={styles.engineBar}>
|
||||
<label>
|
||||
Apply filters
|
||||
<input
|
||||
checked={applyFilters}
|
||||
onChange={() => {
|
||||
setApplyFilters(!applyFilters);
|
||||
}}
|
||||
type="checkbox"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel>
|
||||
<Tiles
|
||||
brush={brush}
|
||||
layer={layer}
|
||||
setBrush={setBrush}
|
||||
setLayer={setLayer}
|
||||
setStamp={setStamp}
|
||||
/>
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -1,13 +1,7 @@
|
|||
.engineBar {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.paintBar {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
margin-bottom: 16px 0;
|
||||
}
|
||||
|
||||
.devtools {
|
||||
|
@ -17,20 +11,3 @@
|
|||
overflow-y: auto;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.devtools p {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.selectionWrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.selectionWrapper img {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.selection {
|
||||
background-color: #ffffff44;
|
||||
position: absolute;
|
||||
}
|
||||
|
|
140
app/react-components/devtools/tiles.jsx
Normal file
140
app/react-components/devtools/tiles.jsx
Normal file
|
@ -0,0 +1,140 @@
|
|||
import {useRef, useState} from 'react';
|
||||
|
||||
import {useEcs} from '@/context/ecs.js';
|
||||
|
||||
import styles from './tiles.module.css';
|
||||
|
||||
export default function Tiles({
|
||||
brush,
|
||||
layer,
|
||||
setBrush,
|
||||
setLayer,
|
||||
setStamp,
|
||||
}) {
|
||||
const offsetRef = useRef();
|
||||
const [selection, setSelection] = useState({x: 0, y: 0, w: 2, h: 2});
|
||||
const [moveStart, setMoveStart] = useState();
|
||||
const [ecs] = useEcs();
|
||||
if (!ecs) {
|
||||
return false;
|
||||
}
|
||||
const master = ecs.get(1);
|
||||
if (!master) {
|
||||
return false;
|
||||
}
|
||||
const {TileLayers} = master;
|
||||
const {sourceJson, tileSize} = TileLayers.layer(0);
|
||||
const {w, h} = sourceJson.meta.size;
|
||||
return (
|
||||
<div className={styles.tiles}>
|
||||
<form>
|
||||
<div className={styles.paintBar}>
|
||||
<div className={styles.layer}>
|
||||
<label>
|
||||
Layer:
|
||||
<select
|
||||
onChange={(event) => {
|
||||
setLayer(event.target.value)
|
||||
}}
|
||||
value={layer}
|
||||
>
|
||||
{
|
||||
Object.keys(TileLayers.layers)
|
||||
.map((layer, i) => (
|
||||
<option
|
||||
key={i}
|
||||
value={i}
|
||||
>
|
||||
{i}
|
||||
</option>
|
||||
))
|
||||
}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div className={styles.brush}>
|
||||
<label>
|
||||
Brush:
|
||||
<select
|
||||
onChange={(event) => {
|
||||
setLayer(event.target.value)
|
||||
}}
|
||||
value={brush}
|
||||
>
|
||||
<option>Paint</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
|
||||
<div
|
||||
onMouseDown={(event) => {
|
||||
if (!offsetRef.current) {
|
||||
return;
|
||||
}
|
||||
const {left, top} = offsetRef.current.getBoundingClientRect();
|
||||
if (
|
||||
event.clientX - left >= w
|
||||
|| event.clientY - top >= h
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const x = Math.floor((event.clientX - left) / tileSize.x);
|
||||
const y = Math.floor((event.clientY - top) / tileSize.y);
|
||||
setMoveStart({x, y});
|
||||
setSelection({x, y, w: 1, h: 1});
|
||||
}}
|
||||
onMouseMove={(event) => {
|
||||
if (!offsetRef.current) {
|
||||
return;
|
||||
}
|
||||
if (!moveStart) {
|
||||
return;
|
||||
}
|
||||
const {x: sx, y: sy} = moveStart;
|
||||
const {left, top} = offsetRef.current.getBoundingClientRect();
|
||||
const x = Math.floor(
|
||||
Math.max(0, Math.min(w - 1, (event.clientX - left)) / tileSize.x),
|
||||
);
|
||||
const y = Math.floor(
|
||||
Math.max(0, Math.min(h - 1, (event.clientY - top)) / tileSize.y),
|
||||
);
|
||||
const mx = Math.min(sx, x);
|
||||
const my = Math.min(sy, y);
|
||||
setSelection({x: mx, y: my, w: Math.abs(sx - x) + 1, h: Math.abs(sy - y) + 1});
|
||||
}}
|
||||
onMouseUp={() => {
|
||||
setMoveStart();
|
||||
const stamp = [];
|
||||
const {x, y, w: sw, h: sh} = selection;
|
||||
const tw = w / tileSize.x;
|
||||
for (let iy = 0; iy < sh; ++iy) {
|
||||
const row = [];
|
||||
for (let ix = 0; ix < sw; ++ix) {
|
||||
row.push((y + iy) * tw + x + ix);
|
||||
}
|
||||
stamp.push(row);
|
||||
}
|
||||
setStamp(stamp);
|
||||
}}
|
||||
className={styles.selectionWrapper}
|
||||
ref={offsetRef}
|
||||
>
|
||||
<div
|
||||
className={styles.selection}
|
||||
style={{
|
||||
top: selection.y * tileSize.y,
|
||||
left: selection.x * tileSize.x,
|
||||
height: selection.h * tileSize.x,
|
||||
width: selection.w * tileSize.y,
|
||||
}}
|
||||
/>
|
||||
<img
|
||||
alt="tileset"
|
||||
src={TileLayers.layer(0).source.replace('.json', '.png')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
18
app/react-components/devtools/tiles.module.css
Normal file
18
app/react-components/devtools/tiles.module.css
Normal file
|
@ -0,0 +1,18 @@
|
|||
.paintBar {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
.selectionWrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.selectionWrapper img {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.selection {
|
||||
background-color: #ffffff44;
|
||||
position: absolute;
|
||||
}
|
21
package-lock.json
generated
21
package-lock.json
generated
|
@ -28,6 +28,7 @@
|
|||
"pixi.js": "^7.4.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-tabs": "^6.0.2",
|
||||
"ws": "^8.17.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -8419,6 +8420,14 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/clsx": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
|
@ -15449,6 +15458,18 @@
|
|||
"integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/react-tabs": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-tabs/-/react-tabs-6.0.2.tgz",
|
||||
"integrity": "sha512-aQXTKolnM28k3KguGDBSAbJvcowOQr23A+CUJdzJtOSDOtTwzEaJA+1U4KwhNL9+Obe+jFS7geuvA7ICQPXOnQ==",
|
||||
"dependencies": {
|
||||
"clsx": "^2.0.0",
|
||||
"prop-types": "^15.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/read-pkg": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz",
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
"pixi.js": "^7.4.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-tabs": "^6.0.2",
|
||||
"ws": "^8.17.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
Loading…
Reference in New Issue
Block a user