fix active sidebar active item, added error boundaries for relational form fields, fixed schema diagram for m:n relations

This commit is contained in:
dswbx
2025-03-03 07:13:04 +01:00
parent 3bf3bb32a4
commit 5ca21b6c01
8 changed files with 100 additions and 35 deletions

View File

@@ -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;
}

View File

@@ -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<ErrorBoundaryProps, ErrorBoundaryState> {
}
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 <BaseError>Error</BaseError>;
}
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
)
) : (
<div>
<h2>Something went wrong.</h2>
<button onClick={this.resetError}>Try Again</button>
</div>
);
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 }) => (
<div className="bg-red-700 text-white py-1 px-2 rounded-md leading-none font-mono">
{children}
</div>
);
export default ErrorBoundary;

View File

@@ -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({

View File

@@ -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 (
<Form.Field
<ErrorBoundary
key={_key}
name={field.name}
children={(props) => (
<EntityFormField
field={field}
fieldApi={props}
disabled={fieldsDisabled}
tabIndex={key + 1}
action={action}
data={data}
/>
)}
/>
fallback={
<Alert.Exception className="font-mono">
Field error: {field.name}
</Alert.Exception>
}
>
<Form.Field
name={field.name}
children={(props) => (
<EntityFormField
field={field}
fieldApi={props}
disabled={fieldsDisabled}
tabIndex={key + 1}
action={action}
data={data}
/>
)}
/>
</ErrorBoundary>
);
})}
</div>

View File

@@ -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<Data extends EntityData = EntityData> = Omit<
DataTableProps<Data>,
@@ -41,7 +42,11 @@ export function EntityTable2({ entity, select, ...props }: EntityTableProps) {
);
}
return <CellValue value={_value} property={property} />;
return (
<ErrorBoundary fallback={String(_value)}>
<CellValue value={_value} property={property} />
</ErrorBoundary>
);
}
return (

View File

@@ -22,7 +22,27 @@ function entitiesToNodes(entities: AppDataConfig["entities"]): Node<TAppDataEnti
}
function relationsToEdges(relations: AppDataConfig["relations"]) {
return Object.entries(relations ?? {}).map(([name, relation]) => {
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,

View File

@@ -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({
<span className="opacity-60 text-nowrap">
{field.getLabel()}:
</span>{" "}
{_value !== null && typeof value !== "undefined" ? (
<span className="text-nowrap truncate">{_value}</span>
{_value !== null && typeof _value !== "undefined" ? (
<ErrorBoundary
fallback={JSON.stringify(_value)}
suppressError
>
<span className="text-nowrap truncate">{_value}</span>
</ErrorBoundary>
) : (
<span className="opacity-30 text-nowrap font-mono mt-0.5">
null
@@ -219,7 +226,7 @@ const PopoverTable = ({
return (
<div>
<EntityTable
<EntityTable2
classNames={{ value: "line-clamp-1 truncate max-w-52 text-nowrap" }}
data={container ?? []}
entity={entity}

View File

@@ -161,7 +161,7 @@ const EntityLinkList = ({
>
{entity.label}
{isLinkActive(href) && (
{isLinkActive(href, 1) && (
<Button
IconLeft={IconSwitchHorizontal}
size="small"