From f84ef2ee19c36e0157edb2c6c487e6ac3f54d25b Mon Sep 17 00:00:00 2001 From: Emma Date: Wed, 14 Jun 2023 03:09:27 -0600 Subject: [PATCH] New icons, better page transition, truncated popper, adds disallowSpaces option to binders, delete and edit buttons --- project-warstone/src/App.tsx | 4 +- .../src/assets/icons/Anvil Icon.svg | 6 ++ .../src/assets/icons/Trash Icon Open.svg | 8 +++ .../src/assets/icons/Trash Icon.svg | 12 ++++ .../AnimatedPageContainer/index.css | 6 ++ .../AnimatedPageContainer/index.tsx | 17 ++++- .../src/components/Icon/index.tsx | 8 ++- .../src/components/Poppables/truncation.tsx | 15 +++++ .../components/PublicationEditor/index.tsx | 19 ++++++ .../src/components/SchemaBuilder/index.tsx | 65 ++++++++++++++----- .../SchemaBuilder/schema-viewer.tsx | 4 +- .../SchemaBuilder/template-editor.tsx | 61 +++++++++++------ .../components/SchemaBuilder/type-editor.tsx | 4 +- .../components/SchemaBuilder/value-field.tsx | 33 ++++++++++ .../src/constants/TemplateTypes.ts | 2 +- project-warstone/src/hooks/useInput.ts | 15 +++-- project-warstone/src/hooks/useObjectState.ts | 19 ++++-- project-warstone/src/index.css | 22 ++++++- project-warstone/src/types/gemerics.ts | 1 + project-warstone/src/types/publication.ts | 9 +++ project-warstone/src/types/schema.ts | 7 +- 21 files changed, 277 insertions(+), 60 deletions(-) create mode 100644 project-warstone/src/assets/icons/Anvil Icon.svg create mode 100644 project-warstone/src/assets/icons/Trash Icon Open.svg create mode 100644 project-warstone/src/assets/icons/Trash Icon.svg create mode 100644 project-warstone/src/components/Poppables/truncation.tsx create mode 100644 project-warstone/src/components/PublicationEditor/index.tsx create mode 100644 project-warstone/src/types/gemerics.ts create mode 100644 project-warstone/src/types/publication.ts diff --git a/project-warstone/src/App.tsx b/project-warstone/src/App.tsx index 2bcdc80..3dad912 100644 --- a/project-warstone/src/App.tsx +++ b/project-warstone/src/App.tsx @@ -7,8 +7,8 @@ function App() { return ( - {/* */} - + + {/* */} ) } diff --git a/project-warstone/src/assets/icons/Anvil Icon.svg b/project-warstone/src/assets/icons/Anvil Icon.svg new file mode 100644 index 0000000..3f0fc47 --- /dev/null +++ b/project-warstone/src/assets/icons/Anvil Icon.svg @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/project-warstone/src/assets/icons/Trash Icon Open.svg b/project-warstone/src/assets/icons/Trash Icon Open.svg new file mode 100644 index 0000000..24e039b --- /dev/null +++ b/project-warstone/src/assets/icons/Trash Icon Open.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/project-warstone/src/assets/icons/Trash Icon.svg b/project-warstone/src/assets/icons/Trash Icon.svg new file mode 100644 index 0000000..8670a85 --- /dev/null +++ b/project-warstone/src/assets/icons/Trash Icon.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/project-warstone/src/components/AnimatedPageContainer/index.css b/project-warstone/src/components/AnimatedPageContainer/index.css index e69de29..4f4461a 100644 --- a/project-warstone/src/components/AnimatedPageContainer/index.css +++ b/project-warstone/src/components/AnimatedPageContainer/index.css @@ -0,0 +1,6 @@ +.page { + transition: + transform 500ms, + opacity 300ms, + z-index 0ms 500ms +} \ No newline at end of file diff --git a/project-warstone/src/components/AnimatedPageContainer/index.tsx b/project-warstone/src/components/AnimatedPageContainer/index.tsx index f381de5..860962c 100644 --- a/project-warstone/src/components/AnimatedPageContainer/index.tsx +++ b/project-warstone/src/components/AnimatedPageContainer/index.tsx @@ -1,5 +1,6 @@ import React, { useState, useEffect, ReactNode, useRef, useLayoutEffect, useCallback } from 'react'; import { FCC } from '../../types'; +import './index.css'; interface IProps { currentPage: number; @@ -10,12 +11,24 @@ const AnimatedPageContainer: FCC = ({ children, currentPage }) => { const renderChild = (child: ReactNode, index: number) => { const isActive = index === currentPage; + let position = 'active'; + switch ((index - currentPage) / Math.abs(index - currentPage)) { + case 1: + position = 'right'; + break; + case -1: + position = 'left'; + break; + default: + position = 'active'; + } return (
{child}
@@ -23,7 +36,7 @@ const AnimatedPageContainer: FCC = ({ children, currentPage }) => { }; return ( -
+
{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

+ + +
+ +
+
    + {Object.keys(schema.types).map(t => ( +
  • + {t} +
    + + +
    +
  • + ))} +
+
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];