Fixes broken accordion ref
Fixes broken poppable ref adds Schema page Fixes schema creation not including game system id
This commit is contained in:
parent
a2fde9cc79
commit
c8f20fbda8
@ -22,6 +22,7 @@ export const saveSchemaDb = async (s: Schema, version: number) => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
authorId: sesh.user.id,
|
authorId: sesh.user.id,
|
||||||
|
gameSystemId: s.gameSystemId,
|
||||||
id: undefined,
|
id: undefined,
|
||||||
},
|
},
|
||||||
update: {
|
update: {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { FC, PropsWithChildren } from "react";
|
import { FC, PropsWithChildren } from "react";
|
||||||
import { Poppable } from "@/lib/poppables/components/poppable";
|
import { Poppable } from "@/lib/poppables/components/poppable";
|
||||||
import { Icon } from "@/components/Icon";
|
import { QuestionMarkCircleIcon } from "@heroicons/react/24/solid";
|
||||||
|
|
||||||
export const HelpPopper: FC<PropsWithChildren> = ({ children }) => {
|
export const HelpPopper: FC<PropsWithChildren> = ({ children }) => {
|
||||||
return (
|
return (
|
||||||
@ -9,7 +9,7 @@ export const HelpPopper: FC<PropsWithChildren> = ({ children }) => {
|
|||||||
preferredAlign="centered"
|
preferredAlign="centered"
|
||||||
preferredEdge="bottom"
|
preferredEdge="bottom"
|
||||||
>
|
>
|
||||||
<Icon icon="Help" className="svg-white w-4 h-4" />
|
<QuestionMarkCircleIcon className="w-4 h-4 fill-white" />
|
||||||
</Poppable>
|
</Poppable>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -2,13 +2,14 @@ import { FC, useCallback, useEffect, useState } from "react";
|
|||||||
import { useObjectStateWrapper } from "../../hooks/useObjectState";
|
import { useObjectStateWrapper } from "../../hooks/useObjectState";
|
||||||
import { ValueField } from "./value-field";
|
import { ValueField } from "./value-field";
|
||||||
import { HelpPopper } from "../Poppables/help";
|
import { HelpPopper } from "../Poppables/help";
|
||||||
import { Icon } from "../Icon";
|
|
||||||
import { RESERVED_FIELDS } from "../../constants/ReservedFields";
|
import { RESERVED_FIELDS } from "../../constants/ReservedFields";
|
||||||
import {
|
import {
|
||||||
fieldTypeOptions,
|
fieldTypeOptions,
|
||||||
FieldTypes,
|
FieldTypes,
|
||||||
fieldTypesWithValues,
|
fieldTypesWithValues,
|
||||||
} from "./fieldtypes";
|
} from "./fieldtypes";
|
||||||
|
import { TrashIcon } from "@heroicons/react/24/solid";
|
||||||
|
import { FieldType } from "@/types";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
update: (arg: FieldType) => void;
|
update: (arg: FieldType) => void;
|
||||||
@ -17,9 +18,12 @@ interface IProps {
|
|||||||
deleteField: (arg: string) => void;
|
deleteField: (arg: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FieldEditor: FC<IProps> = (
|
export const FieldEditor: FC<IProps> = ({
|
||||||
{ update, field, fieldName, deleteField },
|
update,
|
||||||
) => {
|
field,
|
||||||
|
fieldName,
|
||||||
|
deleteField,
|
||||||
|
}) => {
|
||||||
const { bindProperty, bindPropertyCheck } = useObjectStateWrapper(
|
const { bindProperty, bindPropertyCheck } = useObjectStateWrapper(
|
||||||
field,
|
field,
|
||||||
(e) => update(typeof e === "function" ? e(field) : e),
|
(e) => update(typeof e === "function" ? e(field) : e),
|
||||||
@ -83,9 +87,8 @@ export const FieldEditor: FC<IProps> = (
|
|||||||
)}
|
)}
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2">
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" {...bindPropertyCheck("isConstant")} />
|
<input type="checkbox" {...bindPropertyCheck("isConstant")} /> Is
|
||||||
{" "}
|
constant
|
||||||
Is constant
|
|
||||||
</label>
|
</label>
|
||||||
<HelpPopper>
|
<HelpPopper>
|
||||||
<p className="text-sm">
|
<p className="text-sm">
|
||||||
@ -108,7 +111,11 @@ export const FieldEditor: FC<IProps> = (
|
|||||||
</label>
|
</label>
|
||||||
<label className="w-min">
|
<label className="w-min">
|
||||||
Limit:
|
Limit:
|
||||||
<input className="w-12 min-w-min" type="number" {...bindProperty("limit")} />
|
<input
|
||||||
|
className="w-12 min-w-min"
|
||||||
|
type="number"
|
||||||
|
{...bindProperty("limit")}
|
||||||
|
/>
|
||||||
</label>
|
</label>
|
||||||
<HelpPopper>
|
<HelpPopper>
|
||||||
<p className="text-sm">
|
<p className="text-sm">
|
||||||
@ -122,11 +129,7 @@ export const FieldEditor: FC<IProps> = (
|
|||||||
className="no-default self-end ml-auto"
|
className="no-default self-end ml-auto"
|
||||||
onClick={() => deleteField(fieldName)}
|
onClick={() => deleteField(fieldName)}
|
||||||
>
|
>
|
||||||
<Icon
|
<TrashIcon className="w-6 h-6 fill-white" />
|
||||||
className="svg-red-700 hover:svg-red-500 trash-can w-6 h-6"
|
|
||||||
icon="Trash"
|
|
||||||
>
|
|
||||||
</Icon>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -15,6 +15,8 @@ import { findSchema, saveSchemaDb } from "@/actions/Schemas/index";
|
|||||||
import { useToast } from "../toast";
|
import { useToast } from "../toast";
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import { Schema, TypeType } from "@/types";
|
import { Schema, TypeType } from "@/types";
|
||||||
|
import { TrashIcon } from "@heroicons/react/24/solid";
|
||||||
|
import { PencilSquareIcon } from "@heroicons/react/24/solid";
|
||||||
|
|
||||||
export const SchemaBuilder: FC = () => {
|
export const SchemaBuilder: FC = () => {
|
||||||
const [schema, setSchema] = useAtom<Schema>(SchemaEditAtom);
|
const [schema, setSchema] = useAtom<Schema>(SchemaEditAtom);
|
||||||
@ -184,20 +186,14 @@ export const SchemaBuilder: FC = () => {
|
|||||||
className="no-default"
|
className="no-default"
|
||||||
onClick={() => selectTypeForEdit(t)}
|
onClick={() => selectTypeForEdit(t)}
|
||||||
>
|
>
|
||||||
<Icon
|
<PencilSquareIcon className="w-6 h-6 fill-white" />
|
||||||
icon="Anvil"
|
|
||||||
className="anvil svg-olive-drab hover:svg-olive-drab-100 w-6 h-6"
|
|
||||||
/>
|
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
title="Delete"
|
title="Delete"
|
||||||
className="no-default"
|
className="no-default"
|
||||||
onClick={() => deleteType(t)}
|
onClick={() => deleteType(t)}
|
||||||
>
|
>
|
||||||
<Icon
|
<TrashIcon className="w-6 h-6 fill-white" />
|
||||||
icon="Trash"
|
|
||||||
className="trash-can svg-red-700 hover:svg-red-500 w-6 h-6"
|
|
||||||
/>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
@ -34,10 +34,10 @@ export const SchemaViewer: FC<IProps> = ({ schema, onTypeClick }) => {
|
|||||||
<hr />
|
<hr />
|
||||||
<p className="font-bold italic">Templates</p>
|
<p className="font-bold italic">Templates</p>
|
||||||
<ul>
|
<ul>
|
||||||
{Object.entries(schema.schema).map(([templateKey, template]) => (
|
{Object.entries(schema.fields).map(([templateKey, template]) => (
|
||||||
<li key={templateKey}>
|
<li key={templateKey}>
|
||||||
<p className="font-bold">{templateKey}</p>
|
<p className="font-bold">{templateKey}</p>
|
||||||
<p className="font-thin text-xs">{template.type}</p>
|
<p className="text-mixed-600 ml-2">Type: {template}</p>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -6,6 +6,7 @@ import { FieldTypes } from "./fieldtypes";
|
|||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import { useInput } from "@/hooks/useInput";
|
import { useInput } from "@/hooks/useInput";
|
||||||
import { Schema } from "@/types";
|
import { Schema } from "@/types";
|
||||||
|
import { TrashIcon } from "@heroicons/react/24/solid";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
templateKey: string;
|
templateKey: string;
|
||||||
@ -19,19 +20,11 @@ export const TemplateEditor: FC<IProps> = ({
|
|||||||
fieldType,
|
fieldType,
|
||||||
}) => {
|
}) => {
|
||||||
const [schema, setSchema] = useAtom(SchemaEditAtom);
|
const [schema, setSchema] = useAtom(SchemaEditAtom);
|
||||||
// const updateTemplate = useCallback(
|
|
||||||
// (t: FieldTypes | ((arg: FieldTypes) => FieldTypes)) => {
|
|
||||||
// update(templateKey, typeof t === "function" ? t(template) : t);
|
|
||||||
// },
|
|
||||||
// [templateKey, update, template],
|
|
||||||
// );
|
|
||||||
|
|
||||||
// const { bindProperty } = useObjectStateWrapper(template, updateTemplate);
|
|
||||||
const { bind: bindFieldType, value } = useInput(fieldType);
|
const { bind: bindFieldType, value } = useInput(fieldType);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
update(templateKey, value);
|
update(templateKey, value);
|
||||||
});
|
}, []);
|
||||||
|
|
||||||
const deleteField = useCallback(() => {
|
const deleteField = useCallback(() => {
|
||||||
setSchema((s: Schema) => {
|
setSchema((s: Schema) => {
|
||||||
@ -70,10 +63,7 @@ export const TemplateEditor: FC<IProps> = ({
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<button className="no-default" onClick={deleteField}>
|
<button className="no-default" onClick={deleteField}>
|
||||||
<Icon
|
<TrashIcon className="w-6 h-6 fill-white" />
|
||||||
icon="Trash"
|
|
||||||
className="svg-red-700 hover:svg-red-500 trash-can w-6 h-6"
|
|
||||||
/>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
@ -9,6 +9,7 @@ import { useObjectState } from "../../hooks/useObjectState";
|
|||||||
import { useInput } from "../../hooks/useInput";
|
import { useInput } from "../../hooks/useInput";
|
||||||
import { FieldEditor } from "./field-editor";
|
import { FieldEditor } from "./field-editor";
|
||||||
import { FieldTypes } from "./fieldtypes";
|
import { FieldTypes } from "./fieldtypes";
|
||||||
|
import { FieldType, TypeType } from "@/types";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
name: string;
|
name: string;
|
||||||
@ -18,9 +19,11 @@ interface IProps {
|
|||||||
|
|
||||||
const constantProperties = ["metadata"];
|
const constantProperties = ["metadata"];
|
||||||
|
|
||||||
export const TypeEditor: FC<PropsWithChildren<IProps>> = (
|
export const TypeEditor: FC<PropsWithChildren<IProps>> = ({
|
||||||
{ saveType, name, type: passedType },
|
saveType,
|
||||||
) => {
|
name,
|
||||||
|
type: passedType,
|
||||||
|
}) => {
|
||||||
const {
|
const {
|
||||||
update: updateType,
|
update: updateType,
|
||||||
reset: resetType,
|
reset: resetType,
|
||||||
@ -39,7 +42,8 @@ export const TypeEditor: FC<PropsWithChildren<IProps>> = (
|
|||||||
resetType();
|
resetType();
|
||||||
};
|
};
|
||||||
|
|
||||||
const addField = useCallback((e: FormEvent) => {
|
const addField = useCallback(
|
||||||
|
(e: FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
updateType({
|
updateType({
|
||||||
[propertyName]: {
|
[propertyName]: {
|
||||||
@ -51,7 +55,9 @@ export const TypeEditor: FC<PropsWithChildren<IProps>> = (
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
resetPropertyName();
|
resetPropertyName();
|
||||||
}, [propertyName, updateType, resetPropertyName]);
|
},
|
||||||
|
[propertyName, updateType, resetPropertyName],
|
||||||
|
);
|
||||||
|
|
||||||
const updateField = useCallback(
|
const updateField = useCallback(
|
||||||
(k: keyof typeof type) => (field: FieldType) => {
|
(k: keyof typeof type) => (field: FieldType) => {
|
||||||
@ -64,13 +70,16 @@ export const TypeEditor: FC<PropsWithChildren<IProps>> = (
|
|||||||
passedType && setType(passedType);
|
passedType && setType(passedType);
|
||||||
}, [passedType, setType]);
|
}, [passedType, setType]);
|
||||||
|
|
||||||
const deleteField = useCallback((name: string) => {
|
const deleteField = useCallback(
|
||||||
|
(name: string) => {
|
||||||
setType((t) => {
|
setType((t) => {
|
||||||
const fields = { ...t };
|
const fields = { ...t };
|
||||||
delete fields[name];
|
delete fields[name];
|
||||||
return fields;
|
return fields;
|
||||||
});
|
});
|
||||||
}, [setType]);
|
},
|
||||||
|
[setType],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@ -82,9 +91,10 @@ export const TypeEditor: FC<PropsWithChildren<IProps>> = (
|
|||||||
<button disabled={!propertyName}>Add Field</button>
|
<button disabled={!propertyName}>Add Field</button>
|
||||||
</form>
|
</form>
|
||||||
<ul className="rounded-lg overflow-hidden">
|
<ul className="rounded-lg overflow-hidden">
|
||||||
{Object.entries(type).reverse().filter(([k]) =>
|
{Object.entries(type)
|
||||||
!constantProperties.includes(k)
|
.reverse()
|
||||||
).map(([key, value]) => (
|
.filter(([k]) => !constantProperties.includes(k))
|
||||||
|
.map(([key, value]) => (
|
||||||
<FieldEditor
|
<FieldEditor
|
||||||
key={"field-editor" + key}
|
key={"field-editor" + key}
|
||||||
field={value}
|
field={value}
|
||||||
|
@ -6,17 +6,21 @@ interface IProps {
|
|||||||
title?: ReactNode;
|
title?: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Accordion: FC<PropsWithChildren<IProps>> = (
|
export const Accordion: FC<PropsWithChildren<IProps>> = ({
|
||||||
{ children, expandOnHover, expanded, title },
|
children,
|
||||||
) => {
|
expandOnHover,
|
||||||
|
expanded,
|
||||||
|
title,
|
||||||
|
}) => {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-expanded={open || expanded}
|
data-expanded={open || expanded}
|
||||||
data-expandonhover={expandOnHover}
|
data-expandonhover={expandOnHover}
|
||||||
className={(expandOnHover ? "group/hover" : "group/controlled") +
|
className={
|
||||||
" group"}
|
(expandOnHover ? "group/hover" : "group/controlled") + " group"
|
||||||
|
}
|
||||||
onClick={() => !title && !expandOnHover && setOpen(!open)}
|
onClick={() => !title && !expandOnHover && setOpen(!open)}
|
||||||
>
|
>
|
||||||
{!!title && (
|
{!!title && (
|
||||||
@ -24,9 +28,7 @@ export const Accordion: FC<PropsWithChildren<IProps>> = (
|
|||||||
className="flex justify-between cursor-pointer"
|
className="flex justify-between cursor-pointer"
|
||||||
onClick={() => !expandOnHover && setOpen(!open)}
|
onClick={() => !expandOnHover && setOpen(!open)}
|
||||||
>
|
>
|
||||||
<div className="accordion-title">
|
<div className="accordion-title">{title}</div>
|
||||||
{title}
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
className={`
|
className={`
|
||||||
group-hover/hover:-rotate-180
|
group-hover/hover:-rotate-180
|
||||||
@ -41,10 +43,8 @@ export const Accordion: FC<PropsWithChildren<IProps>> = (
|
|||||||
scale-y-50
|
scale-y-50
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<span className="block w-2 h-2 rotate-45 border-r-2 border-b-2 place-self-center">
|
<span className="block w-2 h-2 rotate-45 border-r-2 border-b-2 place-self-center"></span>
|
||||||
</span>
|
<span className="block w-2 h-2 rotate-45 border-r-2 border-b-2 place-self-center"></span>
|
||||||
<span className="block w-2 h-2 rotate-45 border-r-2 border-b-2 place-self-center">
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -64,15 +64,15 @@ export const AccordionContent: FC<PropsWithChildren> = ({ children }) => {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const Child = () => (
|
|
||||||
<div className="absolute bottom-0 w-full" ref={updateRef}>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative overflow-hidden">
|
<div className="relative overflow-hidden">
|
||||||
{<Child />}
|
<div
|
||||||
|
key={"accordion-content"}
|
||||||
|
className="absolute bottom-0 w-full"
|
||||||
|
ref={updateRef}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
<span
|
<span
|
||||||
style={{ ["--v-height" as never]: height + "px" }}
|
style={{ ["--v-height" as never]: height + "px" }}
|
||||||
className="w-0 block h-0 group-hover/hover:h-variable group-data-[expanded]/controlled:h-variable transition-all duration-700"
|
className="w-0 block h-0 group-hover/hover:h-variable group-data-[expanded]/controlled:h-variable transition-all duration-700"
|
||||||
|
@ -1,6 +1,13 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { FC, PropsWithChildren, ReactNode, useCallback, useState } from "react";
|
import {
|
||||||
|
FC,
|
||||||
|
PropsWithChildren,
|
||||||
|
ReactNode,
|
||||||
|
useCallback,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
import { PoppableContent } from "./poppable-content";
|
import { PoppableContent } from "./poppable-content";
|
||||||
import { useDebounce } from "../../../hooks/useDebounce";
|
import { useDebounce } from "../../../hooks/useDebounce";
|
||||||
|
|
||||||
@ -12,38 +19,45 @@ interface IProps {
|
|||||||
spacing?: number;
|
spacing?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Poppable: FC<PropsWithChildren<IProps>> = (
|
export const Poppable: FC<PropsWithChildren<IProps>> = ({
|
||||||
{ className, content, children, preferredEdge, preferredAlign, spacing },
|
className,
|
||||||
) => {
|
content,
|
||||||
|
children,
|
||||||
|
preferredEdge,
|
||||||
|
preferredAlign,
|
||||||
|
spacing,
|
||||||
|
}) => {
|
||||||
const [isHovered, setIsHovered] = useState(false);
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
const closing = useDebounce(!isHovered, 1000);
|
const closing = useDebounce(!isHovered, 1000);
|
||||||
const closed = useDebounce(closing, 300);
|
const closed = useDebounce(closing, 300);
|
||||||
|
|
||||||
const [ref, setRef] = useState<HTMLElement>();
|
// const [ref, setRef] = useState<HTMLElement>();
|
||||||
|
|
||||||
const updateRef = useCallback((node: HTMLElement) => {
|
// const updateRef = useCallback((node: HTMLElement) => {
|
||||||
if (!node) return;
|
// if (!node) return;
|
||||||
setRef(node);
|
// setRef(node);
|
||||||
}, []);
|
// }, []);
|
||||||
|
|
||||||
|
const ref = useRef(null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<span
|
<span
|
||||||
ref={updateRef}
|
ref={ref}
|
||||||
className={className}
|
className={className}
|
||||||
onMouseEnter={() => setIsHovered(true)}
|
onMouseEnter={() => setIsHovered(true)}
|
||||||
onMouseLeave={() => setIsHovered(false)}
|
onMouseLeave={() => setIsHovered(false)}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</span>
|
</span>
|
||||||
{!!ref && (
|
{!!ref.current && (
|
||||||
<PoppableContent
|
<PoppableContent
|
||||||
preferredAlign={preferredAlign}
|
preferredAlign={preferredAlign}
|
||||||
preferredEdge={preferredEdge}
|
preferredEdge={preferredEdge}
|
||||||
spacing={spacing}
|
spacing={spacing}
|
||||||
isClosing={closing}
|
isClosing={closing}
|
||||||
isClosed={closed}
|
isClosed={closed}
|
||||||
relativeElement={ref}
|
relativeElement={ref.current}
|
||||||
setHover={setIsHovered}
|
setHover={setIsHovered}
|
||||||
>
|
>
|
||||||
{content}
|
{content}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user