Basic game system editor

This commit is contained in:
Emma
2023-06-13 21:21:47 -06:00
parent a7337bd33a
commit e78e4304a4
12 changed files with 180 additions and 13 deletions

View File

@@ -12,12 +12,14 @@
},
"dependencies": {
"@types/react-router-dom": "^5.3.3",
"@types/recoilize": "^0.8.0",
"autoprefixer": "^10.4.14",
"postcss": "^8.4.24",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.12.0",
"recoil": "^0.7.7",
"recoilize": "^3.2.0",
"tailwindcss": "^3.3.2",
"vite-plugin-svgr": "^3.2.0"
},

View File

@@ -1,10 +1,14 @@
import { RecoilRoot } from 'recoil'
import { SchemaBuilder } from './components/SchemaBuilder'
import { GameSystemEditor } from './components/GameSystemEditor'
import RecoilizeDebugger from 'recoilize';
function App() {
return (
<RecoilRoot>
<SchemaBuilder />
<RecoilizeDebugger />
{/* <SchemaBuilder /> */}
<GameSystemEditor />
</RecoilRoot>
)
}

View File

@@ -0,0 +1,87 @@
import { FC, useCallback, useEffect, useState } from 'react'
import { GameSystem } from '../../types/gameSystem';
import { useObjectState } from '../../hooks/useObjectState';
import { GameSystemsService } from '../../services/game-systems';
export const GameSystemEditor: FC = () => {
const { state: gameSystem, update: updateGameSystem, bindProperty: bindGameSystemProperty, setState: setGameSystem } = useObjectState<GameSystem>({
id: '',
schema: {
id: '',
name: '',
templates: {},
types: {}
},
schemaId: '286f4c18-d280-444b-8d7e-9a3dd09f64ef',
name: 'Asshammer 40x a day',
accolades: [],
});
const [schemas, setSchemas] = useState<{ name: string, id: string }[]>([]);
const [lastSaved, setLastSaved] = useState(gameSystem);
const fetchSchema = useCallback(async (id: string) => {
const res = await GameSystemsService.getSchema(id);
if (res.status !== 200) return;
const schema = await res.json()
updateGameSystem({
schema
});
}, [updateGameSystem])
useEffect(() => {
if (gameSystem.schemaId === gameSystem.schema.id) return;
fetchSchema(gameSystem.schemaId);
}, [fetchSchema, gameSystem.schema.id, gameSystem.schemaId]);
useEffect(() => {
GameSystemsService.getSchemaList()
.then(res => res.json())
.then(schemas => setSchemas(schemas));
}, []);
const saveGameSystem = useCallback(() => {
GameSystemsService.saveGameSystem(gameSystem);
setLastSaved(gameSystem);
}, [gameSystem])
const fetchGameSystem = useCallback(async () => {
const res = await GameSystemsService.getGameSystem('');
const gs = await res.json();
setGameSystem(gs);
setLastSaved(gs);
}, [setGameSystem]);
useEffect(() => {
fetchGameSystem()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<div className="container p-8">
<div className="flex gap-1">
<input className="no-default text-white bg-transparent header border-b-2 border-falcon w-1/2" type="text" {...bindGameSystemProperty('name')} placeholder="Game Name" />
<select className="no-default text-white bg-transparent header border-b-2 border-falcon" {...bindGameSystemProperty('schemaId')}>
<option value=""></option>
{schemas.map(s => (
<option value={s.id}>{s.name}</option>
))}
</select>
<button onClick={saveGameSystem} disabled={lastSaved === gameSystem}>Save Game System</button>
</div>
<div>
<p>Accolades</p>
<div className="grid grid-cols-3">
{gameSystem.accolades.map(a => (
<div>
<p>{a.name}</p>
<p>{a.description}</p>
</div>
))}
</div>
</div>
</div>
)
}

View File

@@ -9,7 +9,7 @@ interface IProps {
}
export const FieldTypeInput: FCC<IProps> = ({ bind }) => {
const Schema = useRecoilValue(SchemaEditAtom);
const schema = useRecoilValue(SchemaEditAtom);
return (
<label className="w-min">
@@ -19,7 +19,7 @@ export const FieldTypeInput: FCC<IProps> = ({ bind }) => {
{Object.keys(TEMPLATE_TYPES).map(k => (
<option className="capitalize" value={k}>{k}</option>
))}
{Object.keys(Schema.types).map(k => (
{Object.keys(schema.types).map(k => (
<option className="capitalize" value={k}>{k}</option>
))}
</datalist>

View File

@@ -22,7 +22,7 @@ export const SchemaBuilder: FC = () => {
const [lastSaved, setLastSaved] = useState(schema);
const fetchSchema = useCallback(async () => {
const result = await GameSystemsService.getSchema('')
const result = await GameSystemsService.getSchema('286f4c18-d280-444b-8d7e-9a3dd09f64ef')
if (result.status !== 200) return;
const fetchedSchema = await result.json();
// if (fetchedSchema.name === schema.name) return;

View File

@@ -1,6 +1,9 @@
import { FC, useCallback } from 'react'
import { Template } from '../../types/schema';
import { useObjectStateWrapper } from '../../hooks/useObjectState';
import { TEMPLATE_TYPES } from '../../constants/TemplateTypes';
import { SchemaEditAtom } from '../../recoil/atoms/schema';
import { useRecoilValue } from 'recoil';
interface IProps {
templateKey: string;
@@ -9,7 +12,7 @@ interface IProps {
}
export const TemplateEditor: FC<IProps> = ({ templateKey, update, template }) => {
const schema = useRecoilValue(SchemaEditAtom);
const updateTemplate = useCallback((t: Template | ((arg: Template) => Template)) => {
update(templateKey, typeof t === 'function' ? t(template) : t)
}, [templateKey, update, template])
@@ -22,7 +25,15 @@ export const TemplateEditor: FC<IProps> = ({ templateKey, update, template }) =>
<div className="flex gap-4">
<label className="w-min">
Type
<input type="text" {...bindProperty('type')} />
<input type="text" {...bindProperty('type')} list="type-editor-type-list" />
<datalist id="type-editor-type-list">
{Object.keys(TEMPLATE_TYPES).map(k => (
<option className="capitalize" value={k}>{k}</option>
))}
{Object.keys(schema.types).map(k => (
<option className="capitalize" value={k}>{k}</option>
))}
</datalist>
</label>
<label>
<input type="checkbox" {...bindPropertyCheck('publishable')} />

View File

@@ -11,7 +11,16 @@
input,
select {
@apply p-1 rounded-lg text-cinder-500 interactive
@apply p-1
}
option {
@apply text-cinder-500
}
input:not(.no-default),
select:not(.no-default) {
@apply rounded-lg text-cinder-500 interactive
}
* {
@@ -52,6 +61,7 @@
animation: fade 300ms forwards ease-in;
animation-delay: 300ms;
}
.fade-out {
animation: fade 300ms forwards ease-in reverse;
}
@@ -63,7 +73,8 @@
from {
opacity: 0;
}
to {
opacity: 1;
}
}
}

View File

@@ -3,5 +3,5 @@ import { Schema } from '../../types/schema';
export const SchemaEditAtom = atom<Schema>({
key: 'schema-edit',
default: {name: '', types: {}, templates: {}}
default: {name: '', types: {}, templates: {}, id: ''}
});

View File

@@ -1,15 +1,18 @@
import { GameSystem } from '../types/gameSystem';
import { Schema } from '../types/schema';
const emptySchema = { name: '', types: {}, templates: {}, id: crypto.randomUUID() };
export const GameSystemsService = {
// todo - connect to service to save schema for game
saveSchema: async (schema: Schema) => {
localStorage.setItem('schema', JSON.stringify(schema));
localStorage.setItem('schema ' + schema.id, JSON.stringify(schema));
return { status: 200 }
},
// todo - connect to service to fetch schema for game
getSchema: async (id: string): Promise<{status: 200 | 404, json: () => Promise<Schema>}> => {
const schema = localStorage.getItem('schema');
getSchema: async (id: string): Promise<{ status: 200 | 404, json: () => Promise<Schema> }> => {
const schema = localStorage.getItem('schema ' + id);
if (schema)
return {
@@ -19,7 +22,30 @@ export const GameSystemsService = {
return {
status: 404,
json: async () => ({name:'', types: {}, templates: {}})
json: async () => (emptySchema)
}
},
getSchemaList: async () => {
return {
status: 200, json: async () => [{
name: 'Test Schema',
id: '286f4c18-d280-444b-8d7e-9a3dd09f64ef'
}]
}
},
saveGameSystem: async (gs: GameSystem) => {
localStorage.setItem('game-system ' + gs.id, JSON.stringify(gs));
return { status: 200 }
},
getGameSystem: async (id: string): Promise<{ status: 200 | 404, json: () => Promise<GameSystem> }> => {
const gs = localStorage.getItem('game-system ' + id);
if (!gs) return {status: 404, json:async () => ({
accolades: [],
name: '',
id: crypto.randomUUID(),
schema: emptySchema,
schemaId: ''
})}
return { status: 200, json:async () => (JSON.parse(gs))}
}
}

View File

@@ -0,0 +1,15 @@
import { Schema } from './schema';
export type Accolade = {
id: string;
name: string;
description: string;
}
export type GameSystem = {
id: string;
schemaId: string;
schema: Schema;
name: string;
accolades: Accolade[];
}

View File

@@ -17,6 +17,7 @@ export type Template = {
}
export type Schema = {
id: string;
name: string;
templates: Record<string, Template>;
types: Record<string, TypeType>;

View File

@@ -630,6 +630,11 @@
"@types/scheduler" "*"
csstype "^3.0.2"
"@types/recoilize@^0.8.0":
version "0.8.0"
resolved "https://registry.yarnpkg.com/@types/recoilize/-/recoilize-0.8.0.tgz#7351f7a048bcef9c712f39d05696ce7fd2a414fe"
integrity sha512-h4Rm8J/8XH1wZIZC5gOeN9EDJWBhK2sLEs5voSHyZKB5cynIDbohuYS6rEdv2kNPrwoYQyt7D3yeXI9ODvZDlw==
"@types/scheduler@*":
version "0.16.3"
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5"
@@ -1825,6 +1830,11 @@ recoil@^0.7.7:
dependencies:
hamt_plus "1.0.2"
recoilize@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/recoilize/-/recoilize-3.2.0.tgz#aaf4c76390293a105a73b74dced7a49dfbeb1c84"
integrity sha512-2J0SEzefl8rKe5iaJ2vVg6AgWy5aQ8NFPfZbZfXagycF5ohUzCQA2c2SZDz5jtZfYmendR8hsILstPyofxfg7g==
resolve-from@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"