+
{React.Children.map(children, renderChild)}
);
diff --git a/project-warstone/src/components/Icon/index.tsx b/project-warstone/src/components/Icon/index.tsx
index 71867e4..97375e9 100644
--- a/project-warstone/src/components/Icon/index.tsx
+++ b/project-warstone/src/components/Icon/index.tsx
@@ -1,8 +1,14 @@
import { FC } from 'react'
import { ReactComponent as help } from '../../assets/icons/Help Icon.svg';
+import { ReactComponent as trash } from '../../assets/icons/Trash Icon.svg';
+import { ReactComponent as trash_hover } from '../../assets/icons/Trash Icon Open.svg';
+import { ReactComponent as anvil } from '../../assets/icons/Anvil Icon.svg';
const library = {
- help
+ help,
+ trash,
+ trash_hover,
+ anvil
}
interface IProps {
diff --git a/project-warstone/src/components/Poppables/truncation.tsx b/project-warstone/src/components/Poppables/truncation.tsx
new file mode 100644
index 0000000..5fa2965
--- /dev/null
+++ b/project-warstone/src/components/Poppables/truncation.tsx
@@ -0,0 +1,15 @@
+import { FC, PropsWithChildren } from 'react';
+import { Poppable } from '../../lib/poppables/components/poppable';
+
+export const Truncate: FC
= ({children}) => {
+
+ return (
+
+ {children}
+
+ );
+}
\ No newline at end of file
diff --git a/project-warstone/src/components/PublicationEditor/index.tsx b/project-warstone/src/components/PublicationEditor/index.tsx
new file mode 100644
index 0000000..f823f7c
--- /dev/null
+++ b/project-warstone/src/components/PublicationEditor/index.tsx
@@ -0,0 +1,19 @@
+import {FC} from 'react'
+import { Publication } from '../../types/publication';
+import { useObjectState } from '../../hooks/useObjectState';
+
+export const PublicationEditor: FC = () => {
+ const {value: publication, setValue: setPublication, bindProperty: bindPublication, update: updatePublication, reset: resetPublication} = useObjectState({
+ gameSystemId: '',
+ name: '',
+ status: 'private',
+ templates: {},
+ version: '1'
+ });
+
+ return (
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/project-warstone/src/components/SchemaBuilder/index.tsx b/project-warstone/src/components/SchemaBuilder/index.tsx
index 4f3a511..e910ec5 100644
--- a/project-warstone/src/components/SchemaBuilder/index.tsx
+++ b/project-warstone/src/components/SchemaBuilder/index.tsx
@@ -9,6 +9,7 @@ import { SchemaEditAtom } from '../../recoil/atoms/schema';
import { GameSystemsService } from '../../services/game-systems';
import { SchemaViewer } from './schema-viewer';
import { TemplateEditor } from './template-editor';
+import { Icon } from '../Icon';
export const SchemaBuilder: FC = () => {
@@ -60,7 +61,7 @@ export const SchemaBuilder: FC = () => {
setPageNumber(1);
}, [])
- const { value: templateName, bind: bindTemplateName, reset: resetTemplateName } = useInput('');
+ const { value: templateName, bind: bindTemplateName, reset: resetTemplateName } = useInput('', { disallowSpaces: true });
const addTemplate = useCallback(() => {
updateSchema(s => ({
templates: {
@@ -83,25 +84,55 @@ export const SchemaBuilder: FC = () => {
}))
}, [updateSchema])
+ const deleteType = useCallback((key: string) => {
+ updateSchema(s => {
+ const types = { ...s.types };
+ delete types[key];
+ return { types }
+ })
+ }, [updateSchema])
+
return (
-
-
Add a template
-
-
-
- {Object.entries(schema.templates).map(([templateKey, template]) => (
-
- ))}
-
-
-
-
Add a type
-
-
+
+
+
Add a template
+
+
+
-
-
+
+ {Object.entries(schema.templates).map(([templateKey, template]) => (
+
+ ))}
+
+
+
+
+
+
+
Add a type
+
+
+
+
+
+
+
diff --git a/project-warstone/src/components/SchemaBuilder/schema-viewer.tsx b/project-warstone/src/components/SchemaBuilder/schema-viewer.tsx
index 9d6a832..a40a9f3 100644
--- a/project-warstone/src/components/SchemaBuilder/schema-viewer.tsx
+++ b/project-warstone/src/components/SchemaBuilder/schema-viewer.tsx
@@ -1,5 +1,6 @@
import { FC, useCallback } from 'react'
import { FieldType, FieldTypes, Schema, TypeType, fieldTypesWithValues } from '../../types/schema'
+import { Truncate } from '../Poppables/truncation';
interface IProps {
schema: Schema;
@@ -16,6 +17,7 @@ export const SchemaViewer: FC
= ({ schema, onTypeClick }) => {
switch (field.type) {
case FieldTypes.type: return 'Type:'
case FieldTypes.dice: return 'Dice:'
+ case FieldTypes.select: return 'Options:'
default: return '';
}
}, [])
@@ -53,7 +55,7 @@ export const SchemaViewer: FC = ({ schema, onTypeClick }) => {
{fieldKey}
{field.type}
Maximum entries: {field.limit === 0 ? 'unlimited ' : field.limit}
- {(field.isConstant || fieldTypesWithValues.includes(field.type)) && {createValueLable(field)} {field.value}
}
+ {(field.isConstant || fieldTypesWithValues.includes(field.type)) && {createValueLable(field)} {field.value}
}
))}
diff --git a/project-warstone/src/components/SchemaBuilder/template-editor.tsx b/project-warstone/src/components/SchemaBuilder/template-editor.tsx
index 30082d5..f15f932 100644
--- a/project-warstone/src/components/SchemaBuilder/template-editor.tsx
+++ b/project-warstone/src/components/SchemaBuilder/template-editor.tsx
@@ -1,9 +1,10 @@
-import { FC, useCallback } from 'react'
+import { FC, useCallback, useState } 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';
+import { useRecoilState, useRecoilValue } from 'recoil';
+import { Icon } from '../Icon';
interface IProps {
templateKey: string;
@@ -12,33 +13,51 @@ interface IProps {
}
export const TemplateEditor: FC = ({ templateKey, update, template }) => {
- const schema = useRecoilValue(SchemaEditAtom);
+ const [schema, setSchema] = useRecoilState(SchemaEditAtom);
const updateTemplate = useCallback((t: Template | ((arg: Template) => Template)) => {
update(templateKey, typeof t === 'function' ? t(template) : t)
}, [templateKey, update, template])
const { bindProperty, bindPropertyCheck } = useObjectStateWrapper(template, updateTemplate)
+ const deleteTemplate = useCallback(() => {
+ setSchema(s => {
+ const templates = { ...s.templates };
+ delete templates[templateKey]
+ return {
+ ...s,
+ templates
+ }
+ })
+ }, [setSchema, templateKey])
return (
-
+
{templateKey}
-
-
-
+
)
diff --git a/project-warstone/src/components/SchemaBuilder/type-editor.tsx b/project-warstone/src/components/SchemaBuilder/type-editor.tsx
index 5e24f2f..c4348db 100644
--- a/project-warstone/src/components/SchemaBuilder/type-editor.tsx
+++ b/project-warstone/src/components/SchemaBuilder/type-editor.tsx
@@ -1,4 +1,4 @@
-import { FC, useCallback, useEffect } from 'react'
+import { useCallback, useEffect } from 'react'
import { FCC } from '../../types'
import { FieldType, FieldTypes, TypeType } from '../../types/schema'
import { useObjectState } from '../../hooks/useObjectState';
@@ -16,7 +16,7 @@ const constantProperties = ['metadata'];
export const TypeEditor: FCC
= ({ saveType, name, type: passedType }) => {
const { update: updateType, reset: resetType, state: type, setState: setType } = useObjectState({});
- const { value: propertyName, setValue: setPropertyName, bind: bindPropertyName, reset: resetPropertyName } = useInput('');
+ const { value: propertyName, bind: bindPropertyName, reset: resetPropertyName } = useInput('', { disallowSpaces: true });
const save = () => {
saveType(name, type);
diff --git a/project-warstone/src/components/SchemaBuilder/value-field.tsx b/project-warstone/src/components/SchemaBuilder/value-field.tsx
index 9f14a26..186fa96 100644
--- a/project-warstone/src/components/SchemaBuilder/value-field.tsx
+++ b/project-warstone/src/components/SchemaBuilder/value-field.tsx
@@ -3,6 +3,7 @@ import { FieldTypes } from '../../types/schema';
import { InputBinder } from '../../types/inputBinder';
import { FieldTypeInput } from './field-type-input';
import { useInput } from '../../hooks/useInput';
+import { HelpPopper } from '../Poppables/help';
interface IValueProps {
type: FieldTypes;
@@ -62,6 +63,38 @@ export const ValueField: FC = ({ type, bind }) => {
return (
)
+ case FieldTypes.select:
+ return (
+ <>
+
+ >
+ )
+ case FieldTypes.any:
+ return (
+ <>
+
+ >
+ )
default:
return <>>;
}
diff --git a/project-warstone/src/constants/TemplateTypes.ts b/project-warstone/src/constants/TemplateTypes.ts
index 1c6cc3f..1bb6eb5 100644
--- a/project-warstone/src/constants/TemplateTypes.ts
+++ b/project-warstone/src/constants/TemplateTypes.ts
@@ -45,7 +45,7 @@ export const TEMPLATE_TYPES: Record = {
value: ''
}
},
- tableRow: {
+ table_row: {
columns: {
isConstant: false,
limit: 0,
diff --git a/project-warstone/src/hooks/useInput.ts b/project-warstone/src/hooks/useInput.ts
index 907be3d..8df66e9 100644
--- a/project-warstone/src/hooks/useInput.ts
+++ b/project-warstone/src/hooks/useInput.ts
@@ -1,6 +1,11 @@
import { useState, ChangeEvent } from 'react';
-export const useInput = (initialValue: T) => {
+type InputHookConfig = {
+ disallowSpaces?: boolean;
+ spaceReplacer?: string;
+}
+
+export const useInput = (initialValue: T, config?: InputHookConfig) => {
const [value, setValue] = useState(initialValue);
return {
@@ -10,11 +15,11 @@ export const useInput = (initialValue: T) => {
bind: {
value: value,
onChange: (event: ChangeEvent) => {
- const changed: string | number = typeof initialValue === 'number' ? parseInt(event.target.value) : event.target.value;
- setValue(changed as T);
- }
+ const changed: string | number = typeof initialValue === 'number' ? parseInt(event.target.value) : config?.disallowSpaces ? event.target.value.replace(' ', config.spaceReplacer || '_') : event.target.value;
+ setValue(changed as T);
}
- };
+ }
+};
};
export const useCheckbox = (initial: boolean) => {
diff --git a/project-warstone/src/hooks/useObjectState.ts b/project-warstone/src/hooks/useObjectState.ts
index 6706a97..fcea972 100644
--- a/project-warstone/src/hooks/useObjectState.ts
+++ b/project-warstone/src/hooks/useObjectState.ts
@@ -1,10 +1,15 @@
import React, { ChangeEvent, useCallback, useState } from 'react';
import { InputBinder } from '../types/inputBinder';
+type ObjectStateHookConfig = {
+ disallowSpaces?: boolean;
+ spaceReplacer?: string;
+}
+
export const useObjectState = (initial: T) => {
const [state, setState] = useState(initial || {} as T);
- const bindProperty = useCallback((property: K) => ({
+ const bindProperty = useCallback((property: K, config: ObjectStateHookConfig) => ({
value: state[property] ?? '',
name: property,
onChange: (event: ChangeEvent) =>
@@ -14,7 +19,10 @@ export const useObjectState = (initial: T) => {
[event.target.name]: (
(typeof value[property] === 'number') ?
Number(event.target.value) || 0 :
- event.target.value)
+ config?.disallowSpaces ?
+ event.target.value.replace(' ', config.spaceReplacer || '_') :
+ event.target.value
+ )
}
))
}), [state])
@@ -46,7 +54,7 @@ export const useObjectState = (initial: T) => {
export const useObjectStateWrapper = (state: T, setState: React.Dispatch>) => {
- const bindProperty = useCallback((property: K): InputBinder => ({
+ const bindProperty = useCallback((property: K, config?: ObjectStateHookConfig): InputBinder => ({
value: state[property] ?? '',
name: property.toString(),
onChange: (event: ChangeEvent) =>
@@ -56,7 +64,10 @@ export const useObjectStateWrapper = (state: T, setState: Reac
[event.target.name]: (
(typeof value[property] === 'number') ?
Number(event.target.value) || 0 :
- event.target.value)
+ config?.disallowSpaces ?
+ event.target.value.replace(' ', config.spaceReplacer || '_') :
+ event.target.value
+ )
}
))
}), [setState, state])
diff --git a/project-warstone/src/index.css b/project-warstone/src/index.css
index aceae0a..f760d46 100644
--- a/project-warstone/src/index.css
+++ b/project-warstone/src/index.css
@@ -41,7 +41,7 @@
@apply text-xl font-bold
}
- button {
+ button:not(.no-default) {
@apply interactive bg-olive-drab p-1
}
@@ -65,6 +65,26 @@
.fade-out {
animation: fade 300ms forwards ease-in reverse;
}
+
+ .trash-can, .anvil {
+ overflow: visible;
+ }
+
+ .trash-can path.trash-lid, .anvil .anvil-base, .anvil .anvil-body {
+ transition: 300ms transform, 300ms rotate, 300ms fill, 300ms stroke;
+ }
+
+ .trash-can:hover path.trash-lid {
+ transform: translate(-5%, -10%);
+ rotate: -10deg;
+ }
+
+ .anvil:hover .anvil-base {
+ transform: translate(0px, 5%);
+ }
+ .anvil:hover .anvil-body {
+ transform: translate(0px, -5%);
+ }
}
@layer utilities {}
diff --git a/project-warstone/src/types/gemerics.ts b/project-warstone/src/types/gemerics.ts
new file mode 100644
index 0000000..25b3178
--- /dev/null
+++ b/project-warstone/src/types/gemerics.ts
@@ -0,0 +1 @@
+export type WithId = {id: string} & T;
\ No newline at end of file
diff --git a/project-warstone/src/types/publication.ts b/project-warstone/src/types/publication.ts
new file mode 100644
index 0000000..23666f8
--- /dev/null
+++ b/project-warstone/src/types/publication.ts
@@ -0,0 +1,9 @@
+import { WithId } from './gemerics';
+
+export type Publication = {
+ name: string;
+ version: string;
+ gameSystemId: string;
+ status: 'published' | 'private';
+ templates: Record[]>;
+}
diff --git a/project-warstone/src/types/schema.ts b/project-warstone/src/types/schema.ts
index 9410201..fafa795 100644
--- a/project-warstone/src/types/schema.ts
+++ b/project-warstone/src/types/schema.ts
@@ -30,8 +30,9 @@ export enum FieldTypes {
checkbox = 'checkbox',
type = '@type',
dice = 'dice',
- any = '@select'
+ any = '@select',
+ select = 'select'
}
-export const fieldTypeOptions: (keyof typeof FieldTypes)[] = ['number', 'text', 'long text', 'checkbox', 'type', 'dice']
-export const fieldTypesWithValues = [FieldTypes.dice, FieldTypes.type];
+export const fieldTypeOptions: (keyof typeof FieldTypes)[] = ['number', 'text', 'long text', 'checkbox', 'type', 'dice', 'select', 'any']
+export const fieldTypesWithValues = [FieldTypes.dice, FieldTypes.type, FieldTypes.select, FieldTypes.any];