1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
|
import * as React from 'react';
const { useCallback, useMemo } = React;
import { makeStyles, Theme } from '@material-ui/core/styles';
import Box from '@material-ui/core/Box';
import Button from '@material-ui/core/Button';
import Container from '@material-ui/core/Container';
import * as Color from 'color';
import * as glob from 'fast-glob';
import { Tiling, TilingMeta } from './types';
import { usePromise, useReadFile, readJSON } from './util';
const useStyles = makeStyles((theme: Theme) => ({
grid: {
display: 'flex',
flexWrap: 'wrap',
margin: theme.spacing(-0.5),
},
tile: {
imageRendering: 'pixelated',
borderStyle: 'solid',
borderWidth: 1,
borderColor: theme.palette.divider,
background: theme.palette.background.paper,
position: 'relative',
width: 130,
height: 130,
margin: theme.spacing(0.5),
// cursor: 'pointer',
// '&:hover': {
// borderColor: theme.palette.text.secondary,
// boxShadow: `0 0 2px 1px ${theme.palette.text.secondary}`,
// },
},
img: {
position: 'absolute',
zIndex: 1,
width: 128,
height: 128,
},
label: {
position: 'absolute',
zIndex: 2,
left: 0,
right: 0,
bottom: 0,
height: '37.5%',
padding: theme.spacing(1),
background: Color(theme.palette.background.default).fade(0.3).string(),
},
}));
function tilingSprite(tiling: Tiling): string {
const x = (tiling.meta.width - 1) / 2;
const y = (tiling.meta.height - 1) / 2;
return `project/tiling/${tiling.id}/${x}_${y}.png`;
}
interface TilingDisplayProps {
tiling: Tiling;
}
function TilingDisplay({ tiling }: TilingDisplayProps): JSX.Element | null {
const classes = useStyles();
const path = tilingSprite(tiling);
const image = useReadFile(path);
const src = useMemo(() => (image ? `data:image/png;base64,${image.toString('base64')}` : undefined), [image]);
return (
<div className={classes.tile}>
<img className={classes.img} src={src} />
<div className={classes.label}>{tiling.meta.name}</div>
</div>
);
}
async function listTilings(): Promise<string[]> {
const matches = await glob('project/tiling/*/meta.json');
return matches.map((m) => m.split('/')[2]);
}
async function loadTilingMeta(id: string): Promise<TilingMeta> {
const path = `project/tiling/${id}/meta.json`;
const meta = await readJSON(path);
return meta as TilingMeta;
}
async function loadTilings(): Promise<Tiling[]> {
const tilings = await listTilings();
return Promise.all(
tilings.map((id) => [id, loadTilingMeta(id)] as const).map(async ([id, p]) => ({ id, meta: await p })),
);
}
export function Library(): JSX.Element {
const classes = useStyles();
const mkLoadTilings = useCallback(() => loadTilings(), []);
const tilings = usePromise(mkLoadTilings) ?? [];
return (
<Container>
<Box mb={2}>
<div className={classes.grid}>
{tilings.map((tiling, i) => (
<TilingDisplay key={i} tiling={tiling} />
))}
</div>
</Box>
<Button variant='contained' color='primary'>
Add
</Button>
</Container>
);
}
|