summaryrefslogtreecommitdiffstats
path: root/src/renderer/editor/library.tsx
blob: 840bdc9e5f2ced9d09f4b856f1117dc9a0d807f7 (plain)
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
119
120
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>
	);
}