mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 04:27:21 +00:00
Merge pull request #100 from bknd-io/fix/minor-admin-ui-fixes
fix active sidebar active item, added error boundaries for relational form fields, fixed schema diagram for m:n relations
This commit is contained in:
@@ -77,7 +77,7 @@ export class JsonSchemaField<
|
|||||||
return value;
|
return value;
|
||||||
case "table":
|
case "table":
|
||||||
if (value === null) return null;
|
if (value === null) return null;
|
||||||
return value;
|
return JSON.stringify(value);
|
||||||
case "submit":
|
case "submit":
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React, { Component, type ErrorInfo, type ReactNode } from "react";
|
|||||||
|
|
||||||
interface ErrorBoundaryProps {
|
interface ErrorBoundaryProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
|
suppressError?: boolean;
|
||||||
fallback?:
|
fallback?:
|
||||||
| (({ error, resetError }: { error: Error; resetError: () => void }) => ReactNode)
|
| (({ error, resetError }: { error: Error; resetError: () => void }) => ReactNode)
|
||||||
| ReactNode;
|
| ReactNode;
|
||||||
@@ -23,31 +24,44 @@ class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
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 = () => {
|
resetError = () => {
|
||||||
this.setState({ hasError: false, error: undefined });
|
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() {
|
override render() {
|
||||||
if (this.state.hasError) {
|
if (this.state.hasError) {
|
||||||
return this.props.fallback ? (
|
return this.renderFallback();
|
||||||
typeof this.props.fallback === "function" ? (
|
}
|
||||||
this.props.fallback({ error: this.state.error!, resetError: this.resetError })
|
|
||||||
) : (
|
if (this.props.suppressError) {
|
||||||
this.props.fallback
|
try {
|
||||||
)
|
return this.props.children;
|
||||||
) : (
|
} catch (e) {
|
||||||
<div>
|
return this.renderFallback();
|
||||||
<h2>Something went wrong.</h2>
|
}
|
||||||
<button onClick={this.resetError}>Try Again</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.props.children;
|
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;
|
export default ErrorBoundary;
|
||||||
|
|||||||
@@ -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;
|
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({
|
export function Link({
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ import { Media } from "ui/elements";
|
|||||||
import { useEvent } from "ui/hooks/use-event";
|
import { useEvent } from "ui/hooks/use-event";
|
||||||
import { EntityJsonSchemaFormField } from "./fields/EntityJsonSchemaFormField";
|
import { EntityJsonSchemaFormField } from "./fields/EntityJsonSchemaFormField";
|
||||||
import { EntityRelationalFormField } from "./fields/EntityRelationalFormField";
|
import { EntityRelationalFormField } from "./fields/EntityRelationalFormField";
|
||||||
|
import ErrorBoundary from "ui/components/display/ErrorBoundary";
|
||||||
|
import { Alert } from "ui/components/display/Alert";
|
||||||
|
|
||||||
type EntityFormProps = {
|
type EntityFormProps = {
|
||||||
entity: Entity;
|
entity: Entity;
|
||||||
@@ -94,8 +96,15 @@ export function EntityForm({
|
|||||||
const _key = `${entity.name}-${field.name}-${key}`;
|
const _key = `${entity.name}-${field.name}-${key}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form.Field
|
<ErrorBoundary
|
||||||
key={_key}
|
key={_key}
|
||||||
|
fallback={
|
||||||
|
<Alert.Exception className="font-mono">
|
||||||
|
Field error: {field.name}
|
||||||
|
</Alert.Exception>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Form.Field
|
||||||
name={field.name}
|
name={field.name}
|
||||||
children={(props) => (
|
children={(props) => (
|
||||||
<EntityFormField
|
<EntityFormField
|
||||||
@@ -108,6 +117,7 @@ export function EntityForm({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
</ErrorBoundary>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { Entity, EntityData } from "data";
|
import type { Entity, EntityData } from "data";
|
||||||
import { CellValue, DataTable, type DataTableProps } from "ui/components/table/DataTable";
|
import { CellValue, DataTable, type DataTableProps } from "ui/components/table/DataTable";
|
||||||
|
import ErrorBoundary from "ui/components/display/ErrorBoundary";
|
||||||
|
|
||||||
type EntityTableProps<Data extends EntityData = EntityData> = Omit<
|
type EntityTableProps<Data extends EntityData = EntityData> = Omit<
|
||||||
DataTableProps<Data>,
|
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 (
|
return (
|
||||||
|
|||||||
@@ -22,7 +22,27 @@ function entitiesToNodes(entities: AppDataConfig["entities"]): Node<TAppDataEnti
|
|||||||
}
|
}
|
||||||
|
|
||||||
function relationsToEdges(relations: AppDataConfig["relations"]) {
|
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}`;
|
let sourceHandle = relation.source + `:${relation.target}`;
|
||||||
if (relation.config?.mappedBy) {
|
if (relation.config?.mappedBy) {
|
||||||
sourceHandle = `${relation.source}:${relation.config?.mappedBy}`;
|
sourceHandle = `${relation.source}:${relation.config?.mappedBy}`;
|
||||||
@@ -65,6 +85,8 @@ export function DataSchemaCanvas() {
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
console.log("-", data, { nodes, edges });
|
||||||
|
|
||||||
const nodeLayout = layoutWithDagre({
|
const nodeLayout = layoutWithDagre({
|
||||||
nodes: nodes.map((n) => ({
|
nodes: nodes.map((n) => ({
|
||||||
id: n.id,
|
id: n.id,
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import { routes } from "ui/lib/routes";
|
|||||||
import { useLocation } from "wouter";
|
import { useLocation } from "wouter";
|
||||||
import { EntityTable, type EntityTableProps } from "../EntityTable";
|
import { EntityTable, type EntityTableProps } from "../EntityTable";
|
||||||
import type { ResponseObject } from "modules/ModuleApi";
|
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
|
// @todo: allow clear if not required
|
||||||
export function EntityRelationalFormField({
|
export function EntityRelationalFormField({
|
||||||
@@ -151,8 +153,13 @@ export function EntityRelationalFormField({
|
|||||||
<span className="opacity-60 text-nowrap">
|
<span className="opacity-60 text-nowrap">
|
||||||
{field.getLabel()}:
|
{field.getLabel()}:
|
||||||
</span>{" "}
|
</span>{" "}
|
||||||
{_value !== null && typeof value !== "undefined" ? (
|
{_value !== null && typeof _value !== "undefined" ? (
|
||||||
|
<ErrorBoundary
|
||||||
|
fallback={JSON.stringify(_value)}
|
||||||
|
suppressError
|
||||||
|
>
|
||||||
<span className="text-nowrap truncate">{_value}</span>
|
<span className="text-nowrap truncate">{_value}</span>
|
||||||
|
</ErrorBoundary>
|
||||||
) : (
|
) : (
|
||||||
<span className="opacity-30 text-nowrap font-mono mt-0.5">
|
<span className="opacity-30 text-nowrap font-mono mt-0.5">
|
||||||
null
|
null
|
||||||
@@ -219,7 +226,7 @@ const PopoverTable = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<EntityTable
|
<EntityTable2
|
||||||
classNames={{ value: "line-clamp-1 truncate max-w-52 text-nowrap" }}
|
classNames={{ value: "line-clamp-1 truncate max-w-52 text-nowrap" }}
|
||||||
data={container ?? []}
|
data={container ?? []}
|
||||||
entity={entity}
|
entity={entity}
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ const EntityLinkList = ({
|
|||||||
>
|
>
|
||||||
{entity.label}
|
{entity.label}
|
||||||
|
|
||||||
{isLinkActive(href) && (
|
{isLinkActive(href, 1) && (
|
||||||
<Button
|
<Button
|
||||||
IconLeft={IconSwitchHorizontal}
|
IconLeft={IconSwitchHorizontal}
|
||||||
size="small"
|
size="small"
|
||||||
|
|||||||
Reference in New Issue
Block a user