mirror of
https://github.com/chartdb/chartdb.git
synced 2025-10-23 07:11:56 +00:00
add helmet instead of updating document.title (#301)
This commit is contained in:
35
index.html
35
index.html
@@ -4,41 +4,8 @@
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Free and Open-source database diagrams editor, visualize and design your database with a single query. Tool to help you draw your DB relationship diagrams and export DDL scripts."
|
||||
/>
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta
|
||||
property="og:title"
|
||||
content="ChartDB - Database schema diagrams visualizer"
|
||||
/>
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Free and Open-source database diagrams editor, visualize and design your database with a single query. Tool to help you draw your DB relationship diagrams and export DDL scripts."
|
||||
/>
|
||||
<meta
|
||||
property="og:image"
|
||||
content="https://app.chartdb.io/ChartDB.png"
|
||||
/>
|
||||
<meta property="og:url" content="https://app.chartdb.io" />
|
||||
<!-- Twitter -->
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta
|
||||
name="twitter:title"
|
||||
content="ChartDB - Database schema diagrams visualizer"
|
||||
/>
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content="Free and Open-source database diagrams editor, visualize and design your database with a single query. Tool to help you draw your DB relationship diagrams and export DDL scripts."
|
||||
/>
|
||||
<meta
|
||||
name="twitter:image"
|
||||
content="https://github.com/chartdb/chartdb/raw/main/public/ChartDB.png"
|
||||
/>
|
||||
<meta name="robots" content="max-image-preview:large" />
|
||||
<title>ChartDB - Database schema diagrams visualizer</title>
|
||||
<title>ChartDB - Create & Visualize Database Schema Diagrams</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
|
21
package-lock.json
generated
21
package-lock.json
generated
@@ -50,6 +50,7 @@
|
||||
"node-sql-parser": "^5.3.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-helmet-async": "^2.0.5",
|
||||
"react-hotkeys-hook": "^4.5.0",
|
||||
"react-i18next": "^15.0.1",
|
||||
"react-resizable-panels": "^2.0.22",
|
||||
@@ -8463,6 +8464,20 @@
|
||||
"integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-helmet-async": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/react-helmet-async/-/react-helmet-async-2.0.5.tgz",
|
||||
"integrity": "sha512-rYUYHeus+i27MvFE+Jaa4WsyBKGkL6qVgbJvSBoX8mbsWoABJXdEO0bZyi0F6i+4f0NuIb8AvqPMj3iXFHkMwg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"invariant": "^2.2.4",
|
||||
"react-fast-compare": "^3.2.2",
|
||||
"shallowequal": "^1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.6.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-hotkeys-hook": {
|
||||
"version": "4.5.1",
|
||||
"resolved": "https://registry.npmjs.org/react-hotkeys-hook/-/react-hotkeys-hook-4.5.1.tgz",
|
||||
@@ -9052,6 +9067,12 @@
|
||||
"integrity": "sha512-pfVOw8QZIXpMbhBWvzBISicvToTiM5WBF1EeAUZDDSb5Dt29yl4AYbyywbJFSEsRUMr7gJaxqCdr4L3tQf9wVg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/shallowequal": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
|
||||
"integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
|
@@ -54,6 +54,7 @@
|
||||
"node-sql-parser": "^5.3.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-helmet-async": "^2.0.5",
|
||||
"react-hotkeys-hook": "^4.5.0",
|
||||
"react-i18next": "^15.0.1",
|
||||
"react-resizable-panels": "^2.0.22",
|
||||
|
43
src/app.tsx
43
src/app.tsx
@@ -2,11 +2,48 @@ import React from 'react';
|
||||
import { RouterProvider } from 'react-router-dom';
|
||||
import { router } from './router';
|
||||
import { TooltipProvider } from './components/tooltip/tooltip';
|
||||
import { Helmet, HelmetProvider } from 'react-helmet-async';
|
||||
|
||||
export const App = () => {
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<RouterProvider router={router} />
|
||||
</TooltipProvider>
|
||||
<HelmetProvider>
|
||||
<Helmet>
|
||||
<meta
|
||||
name="description"
|
||||
content="Free and Open-source database diagrams editor, visualize and design your database with a single query. Tool to help you draw your DB relationship diagrams and export DDL scripts."
|
||||
/>
|
||||
<meta property="og:type" content="website" />
|
||||
<meta
|
||||
property="og:title"
|
||||
content="ChartDB - Database schema diagrams visualizer"
|
||||
/>
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Free and Open-source database diagrams editor, visualize and design your database with a single query. Tool to help you draw your DB relationship diagrams and export DDL scripts."
|
||||
/>
|
||||
<meta
|
||||
property="og:image"
|
||||
content="https://app.chartdb.io/ChartDB.png"
|
||||
/>
|
||||
<meta property="og:url" content="https://app.chartdb.io" />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta
|
||||
name="twitter:title"
|
||||
content="ChartDB - Database schema diagrams visualizer"
|
||||
/>
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content="Free and Open-source database diagrams editor, visualize and design your database with a single query. Tool to help you draw your DB relationship diagrams and export DDL scripts."
|
||||
/>
|
||||
<meta
|
||||
name="twitter:image"
|
||||
content="https://github.com/chartdb/chartdb/raw/main/public/ChartDB.png"
|
||||
/>
|
||||
<title>ChartDB - Database schema diagrams visualizer</title>
|
||||
</Helmet>
|
||||
<TooltipProvider>
|
||||
<RouterProvider router={router} />
|
||||
</TooltipProvider>
|
||||
</HelmetProvider>
|
||||
);
|
||||
};
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import type { DBTable } from '@/lib/domain/db-table';
|
||||
import { deepCopy, generateId } from '@/lib/utils';
|
||||
import { randomColor } from '@/lib/colors';
|
||||
@@ -28,11 +28,10 @@ import { storageInitialValue } from '../storage-context/storage-context';
|
||||
export interface ChartDBProviderProps {
|
||||
diagram?: Diagram;
|
||||
readonly?: boolean;
|
||||
skipTitleUpdate?: boolean;
|
||||
}
|
||||
export const ChartDBProvider: React.FC<
|
||||
React.PropsWithChildren<ChartDBProviderProps>
|
||||
> = ({ children, diagram, readonly, skipTitleUpdate }) => {
|
||||
> = ({ children, diagram, readonly }) => {
|
||||
let db = useStorage();
|
||||
const events = useEventEmitter<ChartDBEvent>();
|
||||
const navigate = useNavigate();
|
||||
@@ -64,19 +63,6 @@ export const ChartDBProvider: React.FC<
|
||||
db = storageInitialValue;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (skipTitleUpdate) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (diagramName) {
|
||||
document.title = `ChartDB - ${diagramName} Diagram | Visualize Database Schemas`;
|
||||
} else {
|
||||
document.title =
|
||||
'ChartDB - Create & Visualize Database Schema Diagrams';
|
||||
}
|
||||
}, [diagramName, skipTitleUpdate]);
|
||||
|
||||
const schemas = useMemo(
|
||||
() =>
|
||||
databasesWithSchemas.includes(databaseType)
|
||||
|
@@ -34,6 +34,7 @@ import { ExportImageProvider } from '@/context/export-image-context/export-image
|
||||
import { DialogProvider } from '@/context/dialog-context/dialog-provider';
|
||||
import { KeyboardShortcutsProvider } from '@/context/keyboard-shortcuts-context/keyboard-shortcuts-provider';
|
||||
import { Spinner } from '@/components/spinner/spinner';
|
||||
import { Helmet } from 'react-helmet-async';
|
||||
|
||||
const OPEN_STAR_US_AFTER_SECONDS = 30;
|
||||
const SHOW_STAR_US_AGAIN_AFTER_DAYS = 1;
|
||||
@@ -47,8 +48,13 @@ export const EditorMobileLayoutLazy = React.lazy(
|
||||
);
|
||||
|
||||
const EditorPageComponent: React.FC = () => {
|
||||
const { loadDiagram, currentDiagram, schemas, filteredSchemas } =
|
||||
useChartDB();
|
||||
const {
|
||||
loadDiagram,
|
||||
diagramName,
|
||||
currentDiagram,
|
||||
schemas,
|
||||
filteredSchemas,
|
||||
} = useChartDB();
|
||||
const { openSelectSchema, showSidePanel } = useLayout();
|
||||
const { resetRedoStack, resetUndoStack } = useRedoUndoStack();
|
||||
const { showLoader, hideLoader } = useFullScreenLoader();
|
||||
@@ -210,6 +216,13 @@ const EditorPageComponent: React.FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>
|
||||
{diagramName
|
||||
? `ChartDB - ${diagramName} Diagram | Visualize Database Schemas`
|
||||
: 'ChartDB - Create & Visualize Database Schema Diagrams'}
|
||||
</title>
|
||||
</Helmet>
|
||||
<section
|
||||
className={`bg-background ${isDesktop ? 'h-screen w-screen' : 'h-dvh w-dvw'} flex select-none flex-col overflow-x-hidden`}
|
||||
>
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import React from 'react';
|
||||
import ChartDBLogo from '@/assets/logo-light.png';
|
||||
import ChartDBDarkLogo from '@/assets/logo-dark.png';
|
||||
import { examples } from './examples-data/examples-data';
|
||||
@@ -7,51 +7,56 @@ import { useTheme } from '@/hooks/use-theme';
|
||||
import { LocalConfigProvider } from '@/context/local-config-context/local-config-provider';
|
||||
import { StorageProvider } from '@/context/storage-context/storage-provider';
|
||||
import { ThemeProvider } from '@/context/theme-context/theme-provider';
|
||||
import { Helmet } from 'react-helmet-async';
|
||||
|
||||
const ExamplesPageComponent: React.FC = () => {
|
||||
const { effectiveTheme } = useTheme();
|
||||
useEffect(() => {
|
||||
document.title = 'ChartDB - Example Database Diagrams & Schemas';
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<section className="flex w-screen flex-col bg-background">
|
||||
<nav className="flex h-12 flex-row items-center justify-between border-b px-4">
|
||||
<div className="flex flex-1 justify-start gap-x-3">
|
||||
<div className="flex items-center font-primary">
|
||||
<a
|
||||
href="https://chartdb.io"
|
||||
className="cursor-pointer"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<img
|
||||
src={
|
||||
effectiveTheme === 'light'
|
||||
? ChartDBLogo
|
||||
: ChartDBDarkLogo
|
||||
}
|
||||
alt="chartDB"
|
||||
className="h-4 max-w-fit"
|
||||
/>
|
||||
</a>
|
||||
<>
|
||||
<Helmet>
|
||||
<title>ChartDB - Example Database Diagrams & Schemas</title>
|
||||
</Helmet>
|
||||
<section className="flex w-screen flex-col bg-background">
|
||||
<nav className="flex h-12 flex-row items-center justify-between border-b px-4">
|
||||
<div className="flex flex-1 justify-start gap-x-3">
|
||||
<div className="flex items-center font-primary">
|
||||
<a
|
||||
href="https://chartdb.io"
|
||||
className="cursor-pointer"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<img
|
||||
src={
|
||||
effectiveTheme === 'light'
|
||||
? ChartDBLogo
|
||||
: ChartDBDarkLogo
|
||||
}
|
||||
alt="chartDB"
|
||||
className="h-4 max-w-fit"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="group flex flex-1 flex-row items-center justify-center"></div>
|
||||
<div className="hidden flex-1 justify-end sm:flex"></div>
|
||||
</nav>
|
||||
<div className="flex flex-col px-3 pt-3 text-center md:px-28 md:text-left">
|
||||
<h1 className="font-primary text-2xl font-bold">
|
||||
Examples
|
||||
</h1>
|
||||
<h2 className="mt-1 font-primary text-base text-muted-foreground">
|
||||
A collection of examples to help you get started with
|
||||
ChartDB.
|
||||
</h2>
|
||||
<div className="mt-6 grid grid-flow-row grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||
{examples.map((example) => (
|
||||
<ExampleCard key={example.id} example={example} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="group flex flex-1 flex-row items-center justify-center"></div>
|
||||
<div className="hidden flex-1 justify-end sm:flex"></div>
|
||||
</nav>
|
||||
<div className="flex flex-col px-3 pt-3 text-center md:px-28 md:text-left">
|
||||
<h1 className="font-primary text-2xl font-bold">Examples</h1>
|
||||
<h2 className="mt-1 font-primary text-base text-muted-foreground">
|
||||
A collection of examples to help you get started with
|
||||
ChartDB.
|
||||
</h2>
|
||||
<div className="mt-6 grid grid-flow-row grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||
{examples.map((example) => (
|
||||
<ExampleCard key={example.id} example={example} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
@@ -7,7 +7,7 @@ import { StorageProvider } from '@/context/storage-context/storage-provider';
|
||||
import { ThemeProvider } from '@/context/theme-context/theme-provider';
|
||||
import { Button } from '@/components/button/button';
|
||||
import { CloudDownload } from 'lucide-react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { useLoaderData, useNavigate, useParams } from 'react-router-dom';
|
||||
import type { Template } from '../../templates-data/templates-data';
|
||||
import {
|
||||
Breadcrumb,
|
||||
@@ -34,37 +34,27 @@ import { ChartDBProvider } from '@/context/chartdb-context/chartdb-provider';
|
||||
import { convertTemplateToNewDiagram } from '@/templates-data/template-utils';
|
||||
import { useStorage } from '@/hooks/use-storage';
|
||||
import type { Diagram } from '@/lib/domain/diagram';
|
||||
import { Helmet } from 'react-helmet-async';
|
||||
|
||||
export interface TemplatePageLoaderData {
|
||||
template: Template | undefined;
|
||||
}
|
||||
|
||||
const TemplatePageComponent: React.FC = () => {
|
||||
const { addDiagram } = useStorage();
|
||||
const { templateSlug } = useParams<{ templateSlug: string }>();
|
||||
const [template, setTemplate] = React.useState<Template>();
|
||||
const navigate = useNavigate();
|
||||
const data = useLoaderData() as TemplatePageLoaderData;
|
||||
|
||||
const template = data.template;
|
||||
|
||||
useEffect(() => {
|
||||
const loadTemplate = async () => {
|
||||
const { templates } = await import(
|
||||
'@/templates-data/templates-data'
|
||||
);
|
||||
const template = templates.find((t) => t.slug === templateSlug);
|
||||
|
||||
if (!template) {
|
||||
navigate('/templates');
|
||||
return;
|
||||
}
|
||||
|
||||
setTemplate(template);
|
||||
};
|
||||
|
||||
loadTemplate();
|
||||
}, [templateSlug, navigate]);
|
||||
const { effectiveTheme } = useTheme();
|
||||
useEffect(() => {
|
||||
if (template) {
|
||||
document.title = `ChartDB - ${template.name} - ${template.shortDescription}`;
|
||||
} else {
|
||||
document.title = 'ChartDB - Database Schema Template';
|
||||
if (!template) {
|
||||
navigate('/templates');
|
||||
}
|
||||
}, [template]);
|
||||
}, [template, navigate]);
|
||||
|
||||
const { effectiveTheme } = useTheme();
|
||||
|
||||
const cloneTemplate = useCallback(async () => {
|
||||
if (!template) {
|
||||
@@ -85,156 +75,168 @@ const TemplatePageComponent: React.FC = () => {
|
||||
}, [addDiagram, navigate, template]);
|
||||
|
||||
return (
|
||||
<section className="flex h-screen w-screen flex-col bg-background">
|
||||
<nav className="flex h-12 shrink-0 flex-row items-center justify-between border-b px-4">
|
||||
<div className="flex flex-1 justify-start gap-x-3">
|
||||
<div className="flex items-center font-primary">
|
||||
<a
|
||||
href="https://chartdb.io"
|
||||
className="cursor-pointer"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<img
|
||||
src={
|
||||
effectiveTheme === 'light'
|
||||
? ChartDBLogo
|
||||
: ChartDBDarkLogo
|
||||
}
|
||||
alt="chartDB"
|
||||
className="h-4 max-w-fit"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="group flex flex-1 flex-row items-center justify-center"></div>
|
||||
<div className="hidden flex-1 justify-end sm:flex"></div>
|
||||
</nav>
|
||||
{!template ? (
|
||||
<Spinner size={'large'} className="mt-20 text-pink-600" />
|
||||
) : (
|
||||
<div className="flex flex-1 flex-col p-3 pb-5 text-center md:px-28 md:text-left">
|
||||
<Breadcrumb className="mb-2">
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink href={`/templates`}>
|
||||
Templates
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink
|
||||
href={`/templates/${templateSlug}`}
|
||||
>
|
||||
{templateSlug}
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
<div className="flex flex-col items-center gap-4 md:flex-row md:items-start md:justify-between md:gap-0">
|
||||
<div className="flex flex-col pr-0 md:pr-20">
|
||||
<h1 className="font-primary text-2xl font-bold">
|
||||
{template?.name}
|
||||
</h1>
|
||||
<h2 className="mt-3">
|
||||
<span className="font-semibold">
|
||||
{template?.shortDescription}
|
||||
{': '}
|
||||
</span>
|
||||
{template?.description}
|
||||
</h2>
|
||||
</div>
|
||||
<div className="flex justify-end">
|
||||
<Button onClick={cloneTemplate}>
|
||||
<CloudDownload className="mr-2" size="16" />
|
||||
Clone Template
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Separator className="my-5" />
|
||||
<div className="flex w-full flex-1 flex-col gap-4 md:flex-row">
|
||||
<div className="relative top-0 flex h-fit w-full shrink-0 flex-col gap-4 md:sticky md:top-1 md:w-60">
|
||||
<div>
|
||||
<h4 className="mb-1 text-base font-semibold md:text-left">
|
||||
Metadata
|
||||
</h4>
|
||||
<>
|
||||
<Helmet>
|
||||
<title>
|
||||
{template
|
||||
? `ChartDB - ${template.name} - ${template.shortDescription}`
|
||||
: 'ChartDB - Database Schema Template'}
|
||||
</title>
|
||||
</Helmet>
|
||||
|
||||
<div className="text-sm text-muted-foreground">
|
||||
<div className="inline-flex">
|
||||
<span className="mr-2">Database:</span>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<img
|
||||
src={
|
||||
databaseSecondaryLogoMap[
|
||||
<section className="flex h-screen w-screen flex-col bg-background">
|
||||
<nav className="flex h-12 shrink-0 flex-row items-center justify-between border-b px-4">
|
||||
<div className="flex flex-1 justify-start gap-x-3">
|
||||
<div className="flex items-center font-primary">
|
||||
<a
|
||||
href="https://chartdb.io"
|
||||
className="cursor-pointer"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<img
|
||||
src={
|
||||
effectiveTheme === 'light'
|
||||
? ChartDBLogo
|
||||
: ChartDBDarkLogo
|
||||
}
|
||||
alt="chartDB"
|
||||
className="h-4 max-w-fit"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="group flex flex-1 flex-row items-center justify-center"></div>
|
||||
<div className="hidden flex-1 justify-end sm:flex"></div>
|
||||
</nav>
|
||||
{!template ? (
|
||||
<Spinner size={'large'} className="mt-20 text-pink-600" />
|
||||
) : (
|
||||
<div className="flex flex-1 flex-col p-3 pb-5 text-center md:px-28 md:text-left">
|
||||
<Breadcrumb className="mb-2">
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink href={`/templates`}>
|
||||
Templates
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink
|
||||
href={`/templates/${templateSlug}`}
|
||||
>
|
||||
{templateSlug}
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
<div className="flex flex-col items-center gap-4 md:flex-row md:items-start md:justify-between md:gap-0">
|
||||
<div className="flex flex-col pr-0 md:pr-20">
|
||||
<h1 className="font-primary text-2xl font-bold">
|
||||
{template?.name}
|
||||
</h1>
|
||||
<h2 className="mt-3">
|
||||
<span className="font-semibold">
|
||||
{template?.shortDescription}
|
||||
{': '}
|
||||
</span>
|
||||
{template?.description}
|
||||
</h2>
|
||||
</div>
|
||||
<div className="flex justify-end">
|
||||
<Button onClick={cloneTemplate}>
|
||||
<CloudDownload className="mr-2" size="16" />
|
||||
Clone Template
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Separator className="my-5" />
|
||||
<div className="flex w-full flex-1 flex-col gap-4 md:flex-row">
|
||||
<div className="relative top-0 flex h-fit w-full shrink-0 flex-col gap-4 md:sticky md:top-1 md:w-60">
|
||||
<div>
|
||||
<h4 className="mb-1 text-base font-semibold md:text-left">
|
||||
Metadata
|
||||
</h4>
|
||||
|
||||
<div className="text-sm text-muted-foreground">
|
||||
<div className="inline-flex">
|
||||
<span className="mr-2">
|
||||
Database:
|
||||
</span>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<img
|
||||
src={
|
||||
databaseSecondaryLogoMap[
|
||||
template.diagram
|
||||
.databaseType
|
||||
]
|
||||
}
|
||||
className="h-5 max-w-fit"
|
||||
alt="database"
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{
|
||||
databaseTypeToLabelMap[
|
||||
template.diagram
|
||||
.databaseType
|
||||
]
|
||||
}
|
||||
className="h-5 max-w-fit"
|
||||
alt="database"
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{
|
||||
databaseTypeToLabelMap[
|
||||
template.diagram
|
||||
.databaseType
|
||||
]
|
||||
}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
<span>Tables:</span>
|
||||
<span className="ml-2 font-semibold">
|
||||
{template?.diagram?.tables
|
||||
?.length ?? 0}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
<span>Relationships:</span>
|
||||
<span className="ml-2 font-semibold">
|
||||
{template?.diagram?.relationships
|
||||
?.length ?? 0}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
<span>Tables:</span>
|
||||
<span className="ml-2 font-semibold">
|
||||
{template?.diagram?.tables?.length ?? 0}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
<span>Relationships:</span>
|
||||
<span className="ml-2 font-semibold">
|
||||
{template?.diagram?.relationships
|
||||
?.length ?? 0}
|
||||
</span>
|
||||
<div>
|
||||
<h4 className="mb-1 text-base font-semibold md:text-left">
|
||||
Tags
|
||||
</h4>
|
||||
<div className="flex flex-wrap justify-center gap-1 md:justify-start">
|
||||
{template.tags.map((tag) => (
|
||||
<Badge
|
||||
variant="outline"
|
||||
key={`${template.id}_${tag}`}
|
||||
>
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="mb-1 text-base font-semibold md:text-left">
|
||||
Tags
|
||||
</h4>
|
||||
<div className="flex flex-wrap justify-center gap-1 md:justify-start">
|
||||
{template.tags.map((tag) => (
|
||||
<Badge
|
||||
variant="outline"
|
||||
key={`${template.id}_${tag}`}
|
||||
>
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex min-h-96 overflow-hidden rounded border md:flex-1 md:rounded-lg">
|
||||
<div className="size-full">
|
||||
<ChartDBProvider
|
||||
diagram={template.diagram}
|
||||
readonly
|
||||
skipTitleUpdate
|
||||
>
|
||||
<Canvas
|
||||
<div className="flex min-h-96 overflow-hidden rounded border md:flex-1 md:rounded-lg">
|
||||
<div className="size-full">
|
||||
<ChartDBProvider
|
||||
diagram={template.diagram}
|
||||
readonly
|
||||
initialTables={
|
||||
template.diagram.tables ?? []
|
||||
}
|
||||
/>
|
||||
</ChartDBProvider>
|
||||
>
|
||||
<Canvas
|
||||
readonly
|
||||
initialTables={
|
||||
template.diagram.tables ?? []
|
||||
}
|
||||
/>
|
||||
</ChartDBProvider>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
)}
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
@@ -11,6 +11,7 @@ import { useMatches, useParams } from 'react-router-dom';
|
||||
import type { Template } from '@/templates-data/templates-data';
|
||||
import { Spinner } from '@/components/spinner/spinner';
|
||||
import { removeDups } from '@/lib/utils';
|
||||
import { Helmet } from 'react-helmet-async';
|
||||
|
||||
const TemplatesPageComponent: React.FC = () => {
|
||||
const { effectiveTheme } = useTheme();
|
||||
@@ -25,10 +26,6 @@ const TemplatesPageComponent: React.FC = () => {
|
||||
const isAllTemplates = matches.some((match) => match.id === 'templates');
|
||||
const isTags = matches.some((match) => match.id === 'templates_tags');
|
||||
|
||||
useEffect(() => {
|
||||
document.title = 'ChartDB - Database Schema Templates';
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const loadTemplates = async () => {
|
||||
const { templates: loadedTemplates } = await import(
|
||||
@@ -55,88 +52,97 @@ const TemplatesPageComponent: React.FC = () => {
|
||||
}, [isFeatured, isTags, tag]);
|
||||
|
||||
return (
|
||||
<section className="flex w-screen flex-col bg-background">
|
||||
<nav className="flex h-12 shrink-0 flex-row items-center justify-between border-b px-4">
|
||||
<div className="flex flex-1 justify-start gap-x-3">
|
||||
<div className="flex items-center font-primary">
|
||||
<a
|
||||
href="https://chartdb.io"
|
||||
className="cursor-pointer"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<img
|
||||
src={
|
||||
effectiveTheme === 'light'
|
||||
? ChartDBLogo
|
||||
: ChartDBDarkLogo
|
||||
}
|
||||
alt="chartDB"
|
||||
className="h-4 max-w-fit"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="group flex flex-1 flex-row items-center justify-center"></div>
|
||||
<div className="hidden flex-1 justify-end sm:flex"></div>
|
||||
</nav>
|
||||
<div className="flex flex-col p-3 text-center md:px-28 md:text-left">
|
||||
<h1 className="font-primary text-2xl font-bold">
|
||||
Database Schema Templates
|
||||
</h1>
|
||||
<h2 className="mt-1 font-primary text-base text-muted-foreground">
|
||||
Explore a collection of real-world database schemas drawn
|
||||
from real-world live applications and open-source projects.
|
||||
Use these as a foundation or source of inspiration when
|
||||
designing your app’s architecture.
|
||||
</h2>
|
||||
{!templates ? (
|
||||
<Spinner size={'large'} className="mt-20 text-pink-600" />
|
||||
) : (
|
||||
<div className="mt-6 flex w-full flex-col-reverse gap-4 md:flex-row">
|
||||
<div className="relative top-0 flex h-fit w-full shrink-0 flex-col md:sticky md:top-1 md:w-44">
|
||||
<ListMenu
|
||||
items={[
|
||||
{
|
||||
title: 'Featured',
|
||||
href: '/templates/featured',
|
||||
icon: Star,
|
||||
selected: isFeatured,
|
||||
},
|
||||
{
|
||||
title: 'All Templates',
|
||||
href: '/templates',
|
||||
icon: Component,
|
||||
selected: isAllTemplates,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<>
|
||||
<Helmet>
|
||||
<title>ChartDB - Database Schema Templates</title>
|
||||
</Helmet>
|
||||
|
||||
<h4 className="mt-4 text-left text-sm font-semibold">
|
||||
Tags
|
||||
</h4>
|
||||
{tags ? (
|
||||
<ListMenu
|
||||
className="mt-1 w-44 shrink-0"
|
||||
items={tags.map((currentTag) => ({
|
||||
title: currentTag,
|
||||
href: `/templates/tags/${currentTag}`,
|
||||
selected: tag === currentTag,
|
||||
}))}
|
||||
<section className="flex w-screen flex-col bg-background">
|
||||
<nav className="flex h-12 shrink-0 flex-row items-center justify-between border-b px-4">
|
||||
<div className="flex flex-1 justify-start gap-x-3">
|
||||
<div className="flex items-center font-primary">
|
||||
<a
|
||||
href="https://chartdb.io"
|
||||
className="cursor-pointer"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<img
|
||||
src={
|
||||
effectiveTheme === 'light'
|
||||
? ChartDBLogo
|
||||
: ChartDBDarkLogo
|
||||
}
|
||||
alt="chartDB"
|
||||
className="h-4 max-w-fit"
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="grid flex-1 grid-flow-row grid-cols-1 gap-6 md:grid-cols-1 lg:grid-cols-2 xl:grid-cols-4">
|
||||
{templates.map((template) => (
|
||||
<TemplateCard
|
||||
key={`${template.id}`}
|
||||
template={template}
|
||||
/>
|
||||
))}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
<div className="group flex flex-1 flex-row items-center justify-center"></div>
|
||||
<div className="hidden flex-1 justify-end sm:flex"></div>
|
||||
</nav>
|
||||
<div className="flex flex-col p-3 text-center md:px-28 md:text-left">
|
||||
<h1 className="font-primary text-2xl font-bold">
|
||||
Database Schema Templates
|
||||
</h1>
|
||||
<h2 className="mt-1 font-primary text-base text-muted-foreground">
|
||||
Explore a collection of real-world database schemas
|
||||
drawn from real-world live applications and open-source
|
||||
projects. Use these as a foundation or source of
|
||||
inspiration when designing your app’s architecture.
|
||||
</h2>
|
||||
{!templates ? (
|
||||
<Spinner
|
||||
size={'large'}
|
||||
className="mt-20 text-pink-600"
|
||||
/>
|
||||
) : (
|
||||
<div className="mt-6 flex w-full flex-col-reverse gap-4 md:flex-row">
|
||||
<div className="relative top-0 flex h-fit w-full shrink-0 flex-col md:sticky md:top-1 md:w-44">
|
||||
<ListMenu
|
||||
items={[
|
||||
{
|
||||
title: 'Featured',
|
||||
href: '/templates/featured',
|
||||
icon: Star,
|
||||
selected: isFeatured,
|
||||
},
|
||||
{
|
||||
title: 'All Templates',
|
||||
href: '/templates',
|
||||
icon: Component,
|
||||
selected: isAllTemplates,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
<h4 className="mt-4 text-left text-sm font-semibold">
|
||||
Tags
|
||||
</h4>
|
||||
{tags ? (
|
||||
<ListMenu
|
||||
className="mt-1 w-44 shrink-0"
|
||||
items={tags.map((currentTag) => ({
|
||||
title: currentTag,
|
||||
href: `/templates/tags/${currentTag}`,
|
||||
selected: tag === currentTag,
|
||||
}))}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="grid flex-1 grid-flow-row grid-cols-1 gap-6 md:grid-cols-1 lg:grid-cols-2 xl:grid-cols-4">
|
||||
{templates.map((template) => (
|
||||
<TemplateCard
|
||||
key={`${template.id}`}
|
||||
template={template}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import type { RouteObject } from 'react-router-dom';
|
||||
import { createBrowserRouter } from 'react-router-dom';
|
||||
import type { TemplatePageLoaderData } from './pages/template-page/template-page';
|
||||
|
||||
const routes: RouteObject[] = [
|
||||
...['', 'diagrams/:diagramId'].map((path) => ({
|
||||
@@ -63,6 +64,7 @@ const routes: RouteObject[] = [
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'templates_templateSlug',
|
||||
path: 'templates/:templateSlug',
|
||||
async lazy() {
|
||||
const { TemplatePage } = await import(
|
||||
@@ -72,6 +74,16 @@ const routes: RouteObject[] = [
|
||||
element: <TemplatePage />,
|
||||
};
|
||||
},
|
||||
loader: async ({ params }): Promise<TemplatePageLoaderData> => {
|
||||
const { templates } = await import(
|
||||
'./templates-data/templates-data'
|
||||
);
|
||||
return {
|
||||
template: templates.find(
|
||||
(template) => template.slug === params.templateSlug
|
||||
),
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '*',
|
||||
|
Reference in New Issue
Block a user