mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 12:37:20 +00:00
248 lines
7.8 KiB
TypeScript
248 lines
7.8 KiB
TypeScript
import { SegmentedControl, Tooltip } from "@mantine/core";
|
|
import {
|
|
IconAlignJustified,
|
|
IconCirclesRelation,
|
|
IconDatabase,
|
|
IconExternalLink,
|
|
IconPhoto,
|
|
IconPlus,
|
|
IconSettings
|
|
} from "@tabler/icons-react";
|
|
import type { Entity, TEntityType } from "data";
|
|
import { TbDatabasePlus } from "react-icons/tb";
|
|
import { twMerge } from "tailwind-merge";
|
|
import { useBkndData } from "ui/client/schema/data/use-bknd-data";
|
|
import { IconButton } from "ui/components/buttons/IconButton";
|
|
import { Empty } from "ui/components/display/Empty";
|
|
import { Dropdown, type DropdownClickableChild } from "ui/components/overlay/Dropdown";
|
|
import { Link } from "ui/components/wouter/Link";
|
|
import { useBrowserTitle } from "ui/hooks/use-browser-title";
|
|
import * as AppShell from "ui/layouts/AppShell/AppShell";
|
|
import { routes, useNavigate } from "ui/lib/routes";
|
|
|
|
export function DataRoot({ children }) {
|
|
// @todo: settings routes should be centralized
|
|
const { entities, $data } = useBkndData();
|
|
const entityList: Record<TEntityType, Entity[]> = {
|
|
regular: [],
|
|
generated: [],
|
|
system: []
|
|
} as const;
|
|
const [navigate] = useNavigate();
|
|
const context = window.location.href.match(/\/schema/) ? "schema" : "data";
|
|
|
|
for (const entity of Object.values(entities)) {
|
|
entityList[entity.getType()].push(entity);
|
|
}
|
|
|
|
function handleSegmentChange(value?: string) {
|
|
if (!value) return;
|
|
const selected = window.location.href.match(/\/entity\/([^/]+)/)?.[1] || null;
|
|
switch (value) {
|
|
case "data":
|
|
if (selected) {
|
|
navigate(routes.data.entity.list(selected));
|
|
} else {
|
|
navigate(routes.data.root(), { absolute: true });
|
|
}
|
|
break;
|
|
case "schema":
|
|
if (selected) {
|
|
navigate(routes.data.schema.entity(selected));
|
|
} else {
|
|
navigate(routes.data.schema.root());
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<AppShell.Sidebar>
|
|
<AppShell.SectionHeader
|
|
right={
|
|
<>
|
|
<SegmentedControl
|
|
data={[
|
|
{ value: "data", label: "Data" },
|
|
{ value: "schema", label: "Schema" }
|
|
]}
|
|
value={context}
|
|
onChange={handleSegmentChange}
|
|
/>
|
|
<Tooltip label="New Entity">
|
|
<IconButton Icon={TbDatabasePlus} onClick={$data.modals.createEntity} />
|
|
</Tooltip>
|
|
</>
|
|
}
|
|
>
|
|
Entities
|
|
</AppShell.SectionHeader>
|
|
<AppShell.Scrollable initialOffset={96}>
|
|
<div className="flex flex-col flex-grow py-3 gap-3">
|
|
{/*<div className="pt-3 px-3">
|
|
<SearchInput placeholder="Search entities" />
|
|
</div>*/}
|
|
|
|
<EntityLinkList entities={entityList.regular} context={context} suggestCreate />
|
|
<EntityLinkList entities={entityList.system} context={context} title="System" />
|
|
<EntityLinkList
|
|
entities={entityList.generated}
|
|
context={context}
|
|
title="Generated"
|
|
/>
|
|
</div>
|
|
</AppShell.Scrollable>
|
|
</AppShell.Sidebar>
|
|
<AppShell.Main>{children}</AppShell.Main>
|
|
</>
|
|
);
|
|
}
|
|
|
|
const EntityLinkList = ({
|
|
entities,
|
|
title,
|
|
context,
|
|
suggestCreate = false
|
|
}: { entities: Entity[]; title?: string; context: "data" | "schema"; suggestCreate?: boolean }) => {
|
|
const { $data } = useBkndData();
|
|
if (entities.length === 0) {
|
|
return suggestCreate ? (
|
|
<Empty
|
|
className="py-10"
|
|
description="Create your first entity to get started."
|
|
secondary={{
|
|
children: "Create entity",
|
|
onClick: () => $data.modals.createEntity()
|
|
}}
|
|
/>
|
|
) : null;
|
|
}
|
|
|
|
return (
|
|
<nav
|
|
className={twMerge(
|
|
"flex flex-col flex-1 gap-1 px-3",
|
|
title && "border-t border-primary/10 pt-2"
|
|
)}
|
|
>
|
|
{title && <div className="text-sm text-primary/50 ml-3.5 mb-1">{title}</div>}
|
|
|
|
{entities.map((entity) => {
|
|
const href =
|
|
context === "data"
|
|
? routes.data.entity.list(entity.name)
|
|
: routes.data.schema.entity(entity.name);
|
|
return (
|
|
<EntityContextMenu key={entity.name} entity={entity}>
|
|
<AppShell.SidebarLink as={Link} href={href}>
|
|
{entity.label}
|
|
</AppShell.SidebarLink>
|
|
</EntityContextMenu>
|
|
);
|
|
})}
|
|
</nav>
|
|
);
|
|
};
|
|
|
|
const EntityContextMenu = ({
|
|
entity,
|
|
children,
|
|
enabled = true
|
|
}: { entity: Entity; children: DropdownClickableChild; enabled?: boolean }) => {
|
|
if (!enabled) return children;
|
|
const [navigate] = useNavigate();
|
|
const { $data } = useBkndData();
|
|
|
|
// get href from children (single item)
|
|
const href = (children as any).props.href;
|
|
const separator = () => <div className="h-px my-1 w-full bg-primary/5" />;
|
|
|
|
return (
|
|
<Dropdown
|
|
className="flex flex-col w-full"
|
|
dropdownWrapperProps={{
|
|
className: "min-w-fit"
|
|
}}
|
|
title={entity.label + " Actions"}
|
|
items={[
|
|
href && {
|
|
icon: IconExternalLink,
|
|
label: "Open in tab",
|
|
onClick: () => navigate(href, { target: "_blank" })
|
|
},
|
|
separator,
|
|
!$data.system(entity.name).any && {
|
|
icon: IconPlus,
|
|
label: "Create new",
|
|
onClick: () => navigate(routes.data.entity.create(entity.name))
|
|
},
|
|
{
|
|
icon: IconDatabase,
|
|
label: "List entries",
|
|
onClick: () => navigate(routes.data.entity.list(entity.name))
|
|
},
|
|
separator,
|
|
{
|
|
icon: IconAlignJustified,
|
|
label: "Manage fields",
|
|
onClick: () => navigate(routes.data.schema.entity(entity.name))
|
|
},
|
|
{
|
|
icon: IconCirclesRelation,
|
|
label: "Add relation",
|
|
onClick: () =>
|
|
$data.modals.createRelation({
|
|
target: entity.name,
|
|
type: "n:1"
|
|
})
|
|
},
|
|
!$data.system(entity.name).media && {
|
|
icon: IconPhoto,
|
|
label: "Add media",
|
|
onClick: () => $data.modals.createMedia(entity.name)
|
|
},
|
|
separator,
|
|
{
|
|
icon: IconSettings,
|
|
label: "Settings",
|
|
onClick: () =>
|
|
navigate(routes.settings.path(["data", "entities", entity.name]), {
|
|
absolute: true
|
|
})
|
|
}
|
|
]}
|
|
openEvent="onContextMenu"
|
|
position="bottom-start"
|
|
>
|
|
{children}
|
|
</Dropdown>
|
|
);
|
|
};
|
|
|
|
export function DataEmpty() {
|
|
useBrowserTitle(["Data"]);
|
|
const [navigate] = useNavigate();
|
|
const { $data } = useBkndData();
|
|
|
|
function handleButtonClick() {
|
|
navigate(routes.data.schema.root());
|
|
}
|
|
|
|
return (
|
|
<Empty
|
|
Icon={IconDatabase}
|
|
title="No entity selected"
|
|
description="Please select an entity from the left sidebar or create a new one to continue."
|
|
secondary={{
|
|
children: "Go to schema",
|
|
onClick: handleButtonClick
|
|
}}
|
|
primary={{
|
|
children: "Create entity",
|
|
onClick: $data.modals.createEntity
|
|
}}
|
|
/>
|
|
);
|
|
}
|