diff --git a/app/src/data/fields/Field.ts b/app/src/data/fields/Field.ts index 5260d61..ffa7f08 100644 --- a/app/src/data/fields/Field.ts +++ b/app/src/data/fields/Field.ts @@ -157,8 +157,12 @@ export abstract class Field< return this.config.virtual ?? false; } - getLabel(): string { - return this.config.label ?? snakeToPascalWithSpaces(this.name); + getLabel(options?: { fallback?: boolean }): string | undefined { + return this.config.label + ? this.config.label + : options?.fallback !== false + ? snakeToPascalWithSpaces(this.name) + : undefined; } getDescription(): string | undefined { diff --git a/app/src/ui/client/BkndProvider.tsx b/app/src/ui/client/BkndProvider.tsx index 4f5293f..cc32221 100644 --- a/app/src/ui/client/BkndProvider.tsx +++ b/app/src/ui/client/BkndProvider.tsx @@ -33,6 +33,7 @@ export function BkndProvider({ useState>(); const [fetched, setFetched] = useState(false); const errorShown = useRef(); + const [local_version, set_local_version] = useState(0); const api = useApi(); async function reloadSchema() { @@ -80,6 +81,7 @@ export function BkndProvider({ setSchema(schema); setWithSecrets(_includeSecrets); setFetched(true); + set_local_version((v) => v + 1); }); } @@ -98,7 +100,10 @@ export function BkndProvider({ const actions = getSchemaActions({ api, setSchema, reloadSchema }); return ( - + {children} ); diff --git a/app/src/ui/client/schema/data/use-bknd-data.ts b/app/src/ui/client/schema/data/use-bknd-data.ts index 21030a9..e5342c6 100644 --- a/app/src/ui/client/schema/data/use-bknd-data.ts +++ b/app/src/ui/client/schema/data/use-bknd-data.ts @@ -83,6 +83,26 @@ const modals = { bkndModals.open(bkndModals.ids.dataCreate, { initialPath: ["entities", "entity"], initialState: { action: "entity" } + }), + createRelation: (rel: { source?: string; target?: string; type?: string }) => + bkndModals.open(bkndModals.ids.dataCreate, { + initialPath: ["entities", "relation"], + initialState: { + action: "relation", + relations: { + create: [rel as any] + } + } + }), + createMedia: (entity?: string) => + bkndModals.open(bkndModals.ids.dataCreate, { + initialPath: ["entities", "template-media"], + initialState: { + action: "template-media", + initial: { + entity + } + } }) }; diff --git a/app/src/ui/components/buttons/Button.tsx b/app/src/ui/components/buttons/Button.tsx index c9df2b6..a9a55e2 100644 --- a/app/src/ui/components/buttons/Button.tsx +++ b/app/src/ui/components/buttons/Button.tsx @@ -4,15 +4,15 @@ import { twMerge } from "tailwind-merge"; import { Link } from "ui/components/wouter/Link"; const sizes = { - small: "px-2 py-1.5 rounded-md gap-1.5 text-sm", - default: "px-3 py-2.5 rounded-md gap-2.5", - large: "px-4 py-3 rounded-md gap-3 text-lg" + small: "px-2 py-1.5 rounded-md gap-1 text-sm", + default: "px-3 py-2.5 rounded-md gap-1.5", + large: "px-4 py-3 rounded-md gap-2.5 text-lg" }; const iconSizes = { - small: 15, - default: 18, - large: 22 + small: 12, + default: 16, + large: 20 }; const styles = { diff --git a/app/src/ui/modules/data/components/fields/EntityRelationalFormField.tsx b/app/src/ui/modules/data/components/fields/EntityRelationalFormField.tsx index 861bc24..1d3788c 100644 --- a/app/src/ui/modules/data/components/fields/EntityRelationalFormField.tsx +++ b/app/src/ui/modules/data/components/fields/EntityRelationalFormField.tsx @@ -9,6 +9,7 @@ import { useBknd } from "ui/client/bknd"; import { Button } from "ui/components/buttons/Button"; import * as Formy from "ui/components/form/Formy"; import { Popover } from "ui/components/overlay/Popover"; +import { Link } from "ui/components/wouter/Link"; import { routes } from "ui/lib/routes"; import { useLocation } from "wouter"; import { EntityTable } from "../EntityTable"; @@ -82,7 +83,9 @@ export function EntityRelationalFormField({ return ( - {field.getLabel()} + + {field.getLabel({ fallback: false }) ?? entity.label} +
- + + + ) : (
- Select -
diff --git a/app/src/ui/modules/data/components/schema/create-modal/CreateModal.tsx b/app/src/ui/modules/data/components/schema/create-modal/CreateModal.tsx index 791edec..e44d04e 100644 --- a/app/src/ui/modules/data/components/schema/create-modal/CreateModal.tsx +++ b/app/src/ui/modules/data/components/schema/create-modal/CreateModal.tsx @@ -39,6 +39,7 @@ export type TFieldCreate = Static; const createModalSchema = Type.Object( { action: schemaAction, + initial: Type.Optional(Type.Any()), entities: Type.Optional( Type.Object({ create: Type.Optional(Type.Array(entitySchema)) diff --git a/app/src/ui/modules/data/components/schema/create-modal/step.create.tsx b/app/src/ui/modules/data/components/schema/create-modal/step.create.tsx index 16cad49..371646e 100644 --- a/app/src/ui/modules/data/components/schema/create-modal/step.create.tsx +++ b/app/src/ui/modules/data/components/schema/create-modal/step.create.tsx @@ -10,6 +10,7 @@ import { ucFirst } from "core/utils"; import { useEffect, useState } from "react"; import { TbCirclesRelation, TbSettings } from "react-icons/tb"; import { twMerge } from "tailwind-merge"; +import { useBknd } from "ui/client/bknd"; import { useBkndData } from "ui/client/schema/data/use-bknd-data"; import { IconButton, type IconType } from "ui/components/buttons/IconButton"; import { JsonViewer } from "ui/components/code/JsonViewer"; @@ -26,6 +27,7 @@ export function StepCreate() { const [states, setStates] = useState<(boolean | string)[]>([]); const [submitting, setSubmitting] = useState(false); const $data = useBkndData(); + const b = useBknd(); const items: ActionItem[] = []; if (state.entities?.create) { @@ -90,7 +92,8 @@ export function StepCreate() { states.every((s) => s === true) ); if (items.length === states.length && states.every((s) => s === true)) { - close(); + b.actions.reload().then(close); + //close(); } else { setSubmitting(false); } diff --git a/app/src/ui/modules/data/components/schema/create-modal/step.relation.tsx b/app/src/ui/modules/data/components/schema/create-modal/step.relation.tsx index 05066a1..f32e4a1 100644 --- a/app/src/ui/modules/data/components/schema/create-modal/step.relation.tsx +++ b/app/src/ui/modules/data/components/schema/create-modal/step.relation.tsx @@ -9,12 +9,15 @@ import { registerCustomTypeboxKinds } from "core/utils"; import { ManyToOneRelation, type RelationType, RelationTypes } from "data"; -import { type ReactNode, useEffect } from "react"; +import { type ReactNode, startTransition, useEffect } from "react"; import { type Control, type FieldValues, type UseFormRegister, useForm } from "react-hook-form"; +import { TbRefresh } from "react-icons/tb"; import { useBknd } from "ui/client/bknd"; +import { Button } from "ui/components/buttons/Button"; import { MantineNumberInput } from "ui/components/form/hook-form-mantine/MantineNumberInput"; import { MantineSelect } from "ui/components/form/hook-form-mantine/MantineSelect"; import { useStepContext } from "ui/components/steps/Steps"; +import { useEvent } from "ui/hooks/use-event"; import { ModalBody, ModalFooter, type TCreateModalSchema } from "./CreateModal"; // @todo: check if this could become an issue @@ -63,7 +66,7 @@ type ComponentCtx = { export function StepRelation() { const { config } = useBknd(); const entities = config.data.entities; - const { nextStep, stepBack, state, setState } = useStepContext(); + const { nextStep, stepBack, state, path, setState } = useStepContext(); const { register, handleSubmit, @@ -93,6 +96,22 @@ export function StepRelation() { } } + const flip = useEvent(() => { + const { source, target } = data; + if (source && target) { + setValue("source", target); + setValue("target", source); + } else { + if (source) { + setValue("target", source); + setValue("source", null as any); + } else { + setValue("source", target); + setValue("target", null as any); + } + } + }); + return ( <>
@@ -109,14 +128,23 @@ export function StepRelation() { disabled: data.target === name }))} /> - setValue("config", {})} - label="Relation Type" - data={Relations.map((r) => ({ value: r.type, label: r.label }))} - allowDeselect={false} - /> +
+ setValue("config", {})} + label="Relation Type" + data={Relations.map((r) => ({ value: r.type, label: r.label }))} + allowDeselect={false} + /> + {data.type && ( +
+ +
+ )} +
diff --git a/app/src/ui/modules/data/components/schema/create-modal/templates/media/template.media.component.tsx b/app/src/ui/modules/data/components/schema/create-modal/templates/media/template.media.component.tsx index 3f5474b..ea4d149 100644 --- a/app/src/ui/modules/data/components/schema/create-modal/templates/media/template.media.component.tsx +++ b/app/src/ui/modules/data/components/schema/create-modal/templates/media/template.media.component.tsx @@ -31,7 +31,7 @@ const schema = Type.Object({ type TCreateModalMediaSchema = Static; export function TemplateMediaComponent() { - const { stepBack, setState, state, nextStep } = useStepContext(); + const { stepBack, setState, state, path, nextStep } = useStepContext(); const { register, handleSubmit, @@ -41,7 +41,7 @@ export function TemplateMediaComponent() { control } = useForm({ resolver: typeboxResolver(schema), - defaultValues: Default(schema, {}) as TCreateModalMediaSchema + defaultValues: Default(schema, state.initial ?? {}) as TCreateModalMediaSchema }); const { config } = useBknd(); @@ -134,7 +134,7 @@ export function TemplateMediaComponent() { prev={{ onClick: stepBack }} - debug={{ state, data }} + debug={{ state, path, data }} /> diff --git a/app/src/ui/routes/data/data.$entity.$id.tsx b/app/src/ui/routes/data/data.$entity.$id.tsx index ec3d569..a914c3d 100644 --- a/app/src/ui/routes/data/data.$entity.$id.tsx +++ b/app/src/ui/routes/data/data.$entity.$id.tsx @@ -41,6 +41,7 @@ export function DataEntityUpdate({ params }) { with: local_relation_refs }, { + keepPreviousData: false, revalidateOnFocus: false, shouldRetryOnError: false } @@ -95,8 +96,7 @@ export function DataEntityUpdate({ params }) { ); } - const makeKey = (key: string | number = "") => - `${params.entity.name}_${entityId}_${String(key)}`; + const makeKey = (key: string | number = "") => `${entity.name}_${entityId}_${String(key)}`; const fieldsDisabled = $q.isLoading || $q.isValidating || Form.state.isSubmitting; diff --git a/app/src/ui/routes/data/data.$entity.index.tsx b/app/src/ui/routes/data/data.$entity.index.tsx index e92a21f..b6862e0 100644 --- a/app/src/ui/routes/data/data.$entity.index.tsx +++ b/app/src/ui/routes/data/data.$entity.index.tsx @@ -94,6 +94,14 @@ export function DataEntityList({ params }) { items={[ { label: "Settings", + onClick: () => navigate(routes.data.schema.entity(entity.name)) + }, + { + label: "Data Schema", + onClick: () => navigate(routes.data.schema.root()) + }, + { + label: "Advanced Settings", onClick: () => navigate(routes.settings.path(["data", "entities", entity.name]), { absolute: true diff --git a/app/src/ui/routes/data/data.schema.$entity.tsx b/app/src/ui/routes/data/data.schema.$entity.tsx index 516ca9a..b477b15 100644 --- a/app/src/ui/routes/data/data.schema.$entity.tsx +++ b/app/src/ui/routes/data/data.schema.$entity.tsx @@ -8,11 +8,12 @@ import { isDebug } from "core"; import type { Entity } from "data"; import { cloneDeep } from "lodash-es"; import { useRef, useState } from "react"; -import { TbDots } from "react-icons/tb"; +import { TbCirclesRelation, TbDots, TbPhoto, TbPlus } from "react-icons/tb"; import { useBkndData } from "ui/client/schema/data/use-bknd-data"; import { Button } from "ui/components/buttons/Button"; import { IconButton } from "ui/components/buttons/IconButton"; import { Empty } from "ui/components/display/Empty"; +import { Message } from "ui/components/display/Message"; import { JsonSchemaForm, type JsonSchemaFormRef } from "ui/components/form/json-schema"; import { Dropdown } from "ui/components/overlay/Dropdown"; import * as AppShell from "ui/layouts/AppShell/AppShell"; @@ -24,7 +25,6 @@ import { EntityFieldsForm, type EntityFieldsFormRef } from "./forms/entity.field export function DataSchemaEntity({ params }) { const { $data } = useBkndData(); const [value, setValue] = useState("fields"); - const fieldsRef = useRef(null); function toggle(value) { return () => setValue(value); @@ -32,25 +32,58 @@ export function DataSchemaEntity({ params }) { const [navigate] = useNavigate(); const entity = $data.entity(params.entity as string)!; + if (!entity) { + return ; + } return ( <> - navigate(routes.settings.path(["data", "entities", entity.name]), { - absolute: true - }) - } - ]} - position="bottom-end" - > - - + <> + + navigate(routes.data.root() + routes.data.entity.list(entity.name), { + absolute: true + }) + }, + { + label: "Advanced Settings", + onClick: () => + navigate(routes.settings.path(["data", "entities", entity.name]), { + absolute: true + }) + } + ]} + position="bottom-end" + > + + + + $data.modals.createRelation({ + target: entity.name, + type: "n:1" + }) + }, + { + icon: TbPhoto, + label: "Add media", + onClick: () => $data.modals.createMedia(entity.name) + } + ]} + position="bottom-end" + > + + + } className="pl-3" > diff --git a/app/src/ui/routes/flows_old/_flows.root.tsx b/app/src/ui/routes/flows_old/_flows.root.tsx index e7bb1c6..15d1e09 100644 --- a/app/src/ui/routes/flows_old/_flows.root.tsx +++ b/app/src/ui/routes/flows_old/_flows.root.tsx @@ -55,8 +55,10 @@ export function FlowsEmpty() { title="No flow selected" description="Please select a flow from the left sidebar or create a new one to continue." - buttonText="Create Flow" - buttonOnClick={() => navigate(app.getSettingsPath(["flows"]))} + primary={{ + children: "Create Flow", + onClick: () => navigate(app.getSettingsPath(["flows"])) + }} />