diff --git a/app/src/data/fields/JsonSchemaField.ts b/app/src/data/fields/JsonSchemaField.ts index 18627c1..c77870d 100644 --- a/app/src/data/fields/JsonSchemaField.ts +++ b/app/src/data/fields/JsonSchemaField.ts @@ -77,7 +77,7 @@ export class JsonSchemaField< return value; case "table": if (value === null) return null; - return value; + return JSON.stringify(value); case "submit": break; } diff --git a/app/src/ui/components/display/ErrorBoundary.tsx b/app/src/ui/components/display/ErrorBoundary.tsx index 1148d29..f829ce7 100644 --- a/app/src/ui/components/display/ErrorBoundary.tsx +++ b/app/src/ui/components/display/ErrorBoundary.tsx @@ -2,6 +2,7 @@ import React, { Component, type ErrorInfo, type ReactNode } from "react"; interface ErrorBoundaryProps { children: ReactNode; + suppressError?: boolean; fallback?: | (({ error, resetError }: { error: Error; resetError: () => void }) => ReactNode) | ReactNode; @@ -23,31 +24,44 @@ class ErrorBoundary extends Component { } override componentDidCatch(error: Error, errorInfo: ErrorInfo) { - console.error("ErrorBoundary caught an error:", error, errorInfo); + const type = this.props.suppressError ? "warn" : "error"; + console[type]("ErrorBoundary caught an error:", error, errorInfo); } resetError = () => { this.setState({ hasError: false, error: undefined }); }; + private renderFallback() { + if (this.props.fallback) { + return typeof this.props.fallback === "function" + ? this.props.fallback({ error: this.state.error!, resetError: this.resetError }) + : this.props.fallback; + } + return Error; + } + override render() { if (this.state.hasError) { - return this.props.fallback ? ( - typeof this.props.fallback === "function" ? ( - this.props.fallback({ error: this.state.error!, resetError: this.resetError }) - ) : ( - this.props.fallback - ) - ) : ( -
-

Something went wrong.

- -
- ); + return this.renderFallback(); + } + + if (this.props.suppressError) { + try { + return this.props.children; + } catch (e) { + return this.renderFallback(); + } } return this.props.children; } } +const BaseError = ({ children }: { children: ReactNode }) => ( +
+ {children} +
+); + export default ErrorBoundary; diff --git a/app/src/ui/components/wouter/Link.tsx b/app/src/ui/components/wouter/Link.tsx index 2d33f36..c1ca181 100644 --- a/app/src/ui/components/wouter/Link.tsx +++ b/app/src/ui/components/wouter/Link.tsx @@ -42,9 +42,16 @@ const useLocationFromRouter = (router) => { ]; }; -export function isLinkActive(href: string, strict?: boolean) { +export function isLinkActive(href: string, strictness?: number) { const path = window.location.pathname; - return strict ? path === href : path.includes(href); + + if (!strictness || strictness === 0) { + return path.includes(href); + } else if (strictness === 1) { + return path === href || path.endsWith(href) || path.includes(href + "/"); + } + + return path === href; } export function Link({ diff --git a/app/src/ui/modules/data/components/EntityForm.tsx b/app/src/ui/modules/data/components/EntityForm.tsx index 54fbcb7..7fb74dc 100644 --- a/app/src/ui/modules/data/components/EntityForm.tsx +++ b/app/src/ui/modules/data/components/EntityForm.tsx @@ -17,6 +17,8 @@ import { Media } from "ui/elements"; import { useEvent } from "ui/hooks/use-event"; import { EntityJsonSchemaFormField } from "./fields/EntityJsonSchemaFormField"; import { EntityRelationalFormField } from "./fields/EntityRelationalFormField"; +import ErrorBoundary from "ui/components/display/ErrorBoundary"; +import { Alert } from "ui/components/display/Alert"; type EntityFormProps = { entity: Entity; @@ -94,20 +96,28 @@ export function EntityForm({ const _key = `${entity.name}-${field.name}-${key}`; return ( - ( - - )} - /> + fallback={ + + Field error: {field.name} + + } + > + ( + + )} + /> + ); })} diff --git a/app/src/ui/modules/data/components/EntityTable2.tsx b/app/src/ui/modules/data/components/EntityTable2.tsx index 89bce73..c94f2e9 100644 --- a/app/src/ui/modules/data/components/EntityTable2.tsx +++ b/app/src/ui/modules/data/components/EntityTable2.tsx @@ -1,5 +1,6 @@ import type { Entity, EntityData } from "data"; import { CellValue, DataTable, type DataTableProps } from "ui/components/table/DataTable"; +import ErrorBoundary from "ui/components/display/ErrorBoundary"; type EntityTableProps = Omit< DataTableProps, @@ -41,7 +42,11 @@ export function EntityTable2({ entity, select, ...props }: EntityTableProps) { ); } - return ; + return ( + + + + ); } return ( diff --git a/app/src/ui/modules/data/components/canvas/DataSchemaCanvas.tsx b/app/src/ui/modules/data/components/canvas/DataSchemaCanvas.tsx index 0c5623b..83daba7 100644 --- a/app/src/ui/modules/data/components/canvas/DataSchemaCanvas.tsx +++ b/app/src/ui/modules/data/components/canvas/DataSchemaCanvas.tsx @@ -22,7 +22,27 @@ function entitiesToNodes(entities: AppDataConfig["entities"]): Node { + return Object.entries(relations ?? {}).flatMap(([name, relation]) => { + if (relation.type === "m:n") { + const conn_table = `${relation.source}_${relation.target}`; + return [ + { + id: name, + target: relation.source, + source: conn_table, + targetHandle: `${relation.source}:id`, + sourceHandle: `${conn_table}:${relation.source}_id`, + }, + { + id: `${name}-2`, + target: relation.target, + source: conn_table, + targetHandle: `${relation.target}:id`, + sourceHandle: `${conn_table}:${relation.target}_id`, + }, + ]; + } + let sourceHandle = relation.source + `:${relation.target}`; if (relation.config?.mappedBy) { sourceHandle = `${relation.source}:${relation.config?.mappedBy}`; @@ -65,6 +85,8 @@ export function DataSchemaCanvas() { }, })); + console.log("-", data, { nodes, edges }); + const nodeLayout = layoutWithDagre({ nodes: nodes.map((n) => ({ id: n.id, diff --git a/app/src/ui/modules/data/components/fields/EntityRelationalFormField.tsx b/app/src/ui/modules/data/components/fields/EntityRelationalFormField.tsx index 5955c1d..f7c3439 100644 --- a/app/src/ui/modules/data/components/fields/EntityRelationalFormField.tsx +++ b/app/src/ui/modules/data/components/fields/EntityRelationalFormField.tsx @@ -14,6 +14,8 @@ import { routes } from "ui/lib/routes"; import { useLocation } from "wouter"; import { EntityTable, type EntityTableProps } from "../EntityTable"; import type { ResponseObject } from "modules/ModuleApi"; +import ErrorBoundary from "ui/components/display/ErrorBoundary"; +import { EntityTable2 } from "ui/modules/data/components/EntityTable2"; // @todo: allow clear if not required export function EntityRelationalFormField({ @@ -151,8 +153,13 @@ export function EntityRelationalFormField({ {field.getLabel()}: {" "} - {_value !== null && typeof value !== "undefined" ? ( - {_value} + {_value !== null && typeof _value !== "undefined" ? ( + + {_value} + ) : ( null @@ -219,7 +226,7 @@ const PopoverTable = ({ return (
- {entity.label} - {isLinkActive(href) && ( + {isLinkActive(href, 1) && (