add helmet instead of updating document.title (#301)

This commit is contained in:
Guy Ben-Aharon
2024-11-03 11:53:53 +02:00
committed by GitHub
parent bbced225b1
commit 3714ca58ea
10 changed files with 388 additions and 338 deletions

View File

@@ -4,41 +4,8 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.ico" /> <link rel="icon" type="image/svg+xml" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <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" /> <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.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link <link

21
package-lock.json generated
View File

@@ -50,6 +50,7 @@
"node-sql-parser": "^5.3.2", "node-sql-parser": "^5.3.2",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-helmet-async": "^2.0.5",
"react-hotkeys-hook": "^4.5.0", "react-hotkeys-hook": "^4.5.0",
"react-i18next": "^15.0.1", "react-i18next": "^15.0.1",
"react-resizable-panels": "^2.0.22", "react-resizable-panels": "^2.0.22",
@@ -8463,6 +8464,20 @@
"integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==", "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==",
"license": "MIT" "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": { "node_modules/react-hotkeys-hook": {
"version": "4.5.1", "version": "4.5.1",
"resolved": "https://registry.npmjs.org/react-hotkeys-hook/-/react-hotkeys-hook-4.5.1.tgz", "resolved": "https://registry.npmjs.org/react-hotkeys-hook/-/react-hotkeys-hook-4.5.1.tgz",
@@ -9052,6 +9067,12 @@
"integrity": "sha512-pfVOw8QZIXpMbhBWvzBISicvToTiM5WBF1EeAUZDDSb5Dt29yl4AYbyywbJFSEsRUMr7gJaxqCdr4L3tQf9wVg==", "integrity": "sha512-pfVOw8QZIXpMbhBWvzBISicvToTiM5WBF1EeAUZDDSb5Dt29yl4AYbyywbJFSEsRUMr7gJaxqCdr4L3tQf9wVg==",
"license": "MIT" "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": { "node_modules/shebang-command": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",

View File

@@ -54,6 +54,7 @@
"node-sql-parser": "^5.3.2", "node-sql-parser": "^5.3.2",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-helmet-async": "^2.0.5",
"react-hotkeys-hook": "^4.5.0", "react-hotkeys-hook": "^4.5.0",
"react-i18next": "^15.0.1", "react-i18next": "^15.0.1",
"react-resizable-panels": "^2.0.22", "react-resizable-panels": "^2.0.22",

View File

@@ -2,11 +2,48 @@ import React from 'react';
import { RouterProvider } from 'react-router-dom'; import { RouterProvider } from 'react-router-dom';
import { router } from './router'; import { router } from './router';
import { TooltipProvider } from './components/tooltip/tooltip'; import { TooltipProvider } from './components/tooltip/tooltip';
import { Helmet, HelmetProvider } from 'react-helmet-async';
export const App = () => { export const App = () => {
return ( return (
<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> <TooltipProvider>
<RouterProvider router={router} /> <RouterProvider router={router} />
</TooltipProvider> </TooltipProvider>
</HelmetProvider>
); );
}; };

View File

@@ -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 type { DBTable } from '@/lib/domain/db-table';
import { deepCopy, generateId } from '@/lib/utils'; import { deepCopy, generateId } from '@/lib/utils';
import { randomColor } from '@/lib/colors'; import { randomColor } from '@/lib/colors';
@@ -28,11 +28,10 @@ import { storageInitialValue } from '../storage-context/storage-context';
export interface ChartDBProviderProps { export interface ChartDBProviderProps {
diagram?: Diagram; diagram?: Diagram;
readonly?: boolean; readonly?: boolean;
skipTitleUpdate?: boolean;
} }
export const ChartDBProvider: React.FC< export const ChartDBProvider: React.FC<
React.PropsWithChildren<ChartDBProviderProps> React.PropsWithChildren<ChartDBProviderProps>
> = ({ children, diagram, readonly, skipTitleUpdate }) => { > = ({ children, diagram, readonly }) => {
let db = useStorage(); let db = useStorage();
const events = useEventEmitter<ChartDBEvent>(); const events = useEventEmitter<ChartDBEvent>();
const navigate = useNavigate(); const navigate = useNavigate();
@@ -64,19 +63,6 @@ export const ChartDBProvider: React.FC<
db = storageInitialValue; 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( const schemas = useMemo(
() => () =>
databasesWithSchemas.includes(databaseType) databasesWithSchemas.includes(databaseType)

View File

@@ -34,6 +34,7 @@ import { ExportImageProvider } from '@/context/export-image-context/export-image
import { DialogProvider } from '@/context/dialog-context/dialog-provider'; import { DialogProvider } from '@/context/dialog-context/dialog-provider';
import { KeyboardShortcutsProvider } from '@/context/keyboard-shortcuts-context/keyboard-shortcuts-provider'; import { KeyboardShortcutsProvider } from '@/context/keyboard-shortcuts-context/keyboard-shortcuts-provider';
import { Spinner } from '@/components/spinner/spinner'; import { Spinner } from '@/components/spinner/spinner';
import { Helmet } from 'react-helmet-async';
const OPEN_STAR_US_AFTER_SECONDS = 30; const OPEN_STAR_US_AFTER_SECONDS = 30;
const SHOW_STAR_US_AGAIN_AFTER_DAYS = 1; const SHOW_STAR_US_AGAIN_AFTER_DAYS = 1;
@@ -47,8 +48,13 @@ export const EditorMobileLayoutLazy = React.lazy(
); );
const EditorPageComponent: React.FC = () => { const EditorPageComponent: React.FC = () => {
const { loadDiagram, currentDiagram, schemas, filteredSchemas } = const {
useChartDB(); loadDiagram,
diagramName,
currentDiagram,
schemas,
filteredSchemas,
} = useChartDB();
const { openSelectSchema, showSidePanel } = useLayout(); const { openSelectSchema, showSidePanel } = useLayout();
const { resetRedoStack, resetUndoStack } = useRedoUndoStack(); const { resetRedoStack, resetUndoStack } = useRedoUndoStack();
const { showLoader, hideLoader } = useFullScreenLoader(); const { showLoader, hideLoader } = useFullScreenLoader();
@@ -210,6 +216,13 @@ const EditorPageComponent: React.FC = () => {
return ( return (
<> <>
<Helmet>
<title>
{diagramName
? `ChartDB - ${diagramName} Diagram | Visualize Database Schemas`
: 'ChartDB - Create & Visualize Database Schema Diagrams'}
</title>
</Helmet>
<section <section
className={`bg-background ${isDesktop ? 'h-screen w-screen' : 'h-dvh w-dvw'} flex select-none flex-col overflow-x-hidden`} className={`bg-background ${isDesktop ? 'h-screen w-screen' : 'h-dvh w-dvw'} flex select-none flex-col overflow-x-hidden`}
> >

View File

@@ -1,4 +1,4 @@
import React, { useEffect } from 'react'; import React from 'react';
import ChartDBLogo from '@/assets/logo-light.png'; import ChartDBLogo from '@/assets/logo-light.png';
import ChartDBDarkLogo from '@/assets/logo-dark.png'; import ChartDBDarkLogo from '@/assets/logo-dark.png';
import { examples } from './examples-data/examples-data'; import { examples } from './examples-data/examples-data';
@@ -7,14 +7,16 @@ import { useTheme } from '@/hooks/use-theme';
import { LocalConfigProvider } from '@/context/local-config-context/local-config-provider'; import { LocalConfigProvider } from '@/context/local-config-context/local-config-provider';
import { StorageProvider } from '@/context/storage-context/storage-provider'; import { StorageProvider } from '@/context/storage-context/storage-provider';
import { ThemeProvider } from '@/context/theme-context/theme-provider'; import { ThemeProvider } from '@/context/theme-context/theme-provider';
import { Helmet } from 'react-helmet-async';
const ExamplesPageComponent: React.FC = () => { const ExamplesPageComponent: React.FC = () => {
const { effectiveTheme } = useTheme(); const { effectiveTheme } = useTheme();
useEffect(() => {
document.title = 'ChartDB - Example Database Diagrams & Schemas';
}, []);
return ( return (
<>
<Helmet>
<title>ChartDB - Example Database Diagrams & Schemas</title>
</Helmet>
<section className="flex w-screen flex-col bg-background"> <section className="flex w-screen flex-col bg-background">
<nav className="flex h-12 flex-row items-center justify-between border-b px-4"> <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 flex-1 justify-start gap-x-3">
@@ -40,7 +42,9 @@ const ExamplesPageComponent: React.FC = () => {
<div className="hidden flex-1 justify-end sm:flex"></div> <div className="hidden flex-1 justify-end sm:flex"></div>
</nav> </nav>
<div className="flex flex-col px-3 pt-3 text-center md:px-28 md:text-left"> <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> <h1 className="font-primary text-2xl font-bold">
Examples
</h1>
<h2 className="mt-1 font-primary text-base text-muted-foreground"> <h2 className="mt-1 font-primary text-base text-muted-foreground">
A collection of examples to help you get started with A collection of examples to help you get started with
ChartDB. ChartDB.
@@ -52,6 +56,7 @@ const ExamplesPageComponent: React.FC = () => {
</div> </div>
</div> </div>
</section> </section>
</>
); );
}; };

View File

@@ -7,7 +7,7 @@ import { StorageProvider } from '@/context/storage-context/storage-provider';
import { ThemeProvider } from '@/context/theme-context/theme-provider'; import { ThemeProvider } from '@/context/theme-context/theme-provider';
import { Button } from '@/components/button/button'; import { Button } from '@/components/button/button';
import { CloudDownload } from 'lucide-react'; 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 type { Template } from '../../templates-data/templates-data';
import { import {
Breadcrumb, Breadcrumb,
@@ -34,37 +34,27 @@ import { ChartDBProvider } from '@/context/chartdb-context/chartdb-provider';
import { convertTemplateToNewDiagram } from '@/templates-data/template-utils'; import { convertTemplateToNewDiagram } from '@/templates-data/template-utils';
import { useStorage } from '@/hooks/use-storage'; import { useStorage } from '@/hooks/use-storage';
import type { Diagram } from '@/lib/domain/diagram'; import type { Diagram } from '@/lib/domain/diagram';
import { Helmet } from 'react-helmet-async';
export interface TemplatePageLoaderData {
template: Template | undefined;
}
const TemplatePageComponent: React.FC = () => { const TemplatePageComponent: React.FC = () => {
const { addDiagram } = useStorage(); const { addDiagram } = useStorage();
const { templateSlug } = useParams<{ templateSlug: string }>(); const { templateSlug } = useParams<{ templateSlug: string }>();
const [template, setTemplate] = React.useState<Template>();
const navigate = useNavigate(); const navigate = useNavigate();
useEffect(() => { const data = useLoaderData() as TemplatePageLoaderData;
const loadTemplate = async () => {
const { templates } = await import(
'@/templates-data/templates-data'
);
const template = templates.find((t) => t.slug === templateSlug);
const template = data.template;
useEffect(() => {
if (!template) { if (!template) {
navigate('/templates'); navigate('/templates');
return;
} }
}, [template, navigate]);
setTemplate(template);
};
loadTemplate();
}, [templateSlug, navigate]);
const { effectiveTheme } = useTheme(); const { effectiveTheme } = useTheme();
useEffect(() => {
if (template) {
document.title = `ChartDB - ${template.name} - ${template.shortDescription}`;
} else {
document.title = 'ChartDB - Database Schema Template';
}
}, [template]);
const cloneTemplate = useCallback(async () => { const cloneTemplate = useCallback(async () => {
if (!template) { if (!template) {
@@ -85,6 +75,15 @@ const TemplatePageComponent: React.FC = () => {
}, [addDiagram, navigate, template]); }, [addDiagram, navigate, template]);
return ( return (
<>
<Helmet>
<title>
{template
? `ChartDB - ${template.name} - ${template.shortDescription}`
: 'ChartDB - Database Schema Template'}
</title>
</Helmet>
<section className="flex h-screen w-screen flex-col bg-background"> <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"> <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 flex-1 justify-start gap-x-3">
@@ -160,7 +159,9 @@ const TemplatePageComponent: React.FC = () => {
<div className="text-sm text-muted-foreground"> <div className="text-sm text-muted-foreground">
<div className="inline-flex"> <div className="inline-flex">
<span className="mr-2">Database:</span> <span className="mr-2">
Database:
</span>
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<img <img
@@ -188,7 +189,8 @@ const TemplatePageComponent: React.FC = () => {
<div className="text-sm text-muted-foreground"> <div className="text-sm text-muted-foreground">
<span>Tables:</span> <span>Tables:</span>
<span className="ml-2 font-semibold"> <span className="ml-2 font-semibold">
{template?.diagram?.tables?.length ?? 0} {template?.diagram?.tables
?.length ?? 0}
</span> </span>
</div> </div>
<div className="text-sm text-muted-foreground"> <div className="text-sm text-muted-foreground">
@@ -220,7 +222,6 @@ const TemplatePageComponent: React.FC = () => {
<ChartDBProvider <ChartDBProvider
diagram={template.diagram} diagram={template.diagram}
readonly readonly
skipTitleUpdate
> >
<Canvas <Canvas
readonly readonly
@@ -235,6 +236,7 @@ const TemplatePageComponent: React.FC = () => {
</div> </div>
)} )}
</section> </section>
</>
); );
}; };

View File

@@ -11,6 +11,7 @@ import { useMatches, useParams } from 'react-router-dom';
import type { Template } from '@/templates-data/templates-data'; import type { Template } from '@/templates-data/templates-data';
import { Spinner } from '@/components/spinner/spinner'; import { Spinner } from '@/components/spinner/spinner';
import { removeDups } from '@/lib/utils'; import { removeDups } from '@/lib/utils';
import { Helmet } from 'react-helmet-async';
const TemplatesPageComponent: React.FC = () => { const TemplatesPageComponent: React.FC = () => {
const { effectiveTheme } = useTheme(); const { effectiveTheme } = useTheme();
@@ -25,10 +26,6 @@ const TemplatesPageComponent: React.FC = () => {
const isAllTemplates = matches.some((match) => match.id === 'templates'); const isAllTemplates = matches.some((match) => match.id === 'templates');
const isTags = matches.some((match) => match.id === 'templates_tags'); const isTags = matches.some((match) => match.id === 'templates_tags');
useEffect(() => {
document.title = 'ChartDB - Database Schema Templates';
}, []);
useEffect(() => { useEffect(() => {
const loadTemplates = async () => { const loadTemplates = async () => {
const { templates: loadedTemplates } = await import( const { templates: loadedTemplates } = await import(
@@ -55,6 +52,11 @@ const TemplatesPageComponent: React.FC = () => {
}, [isFeatured, isTags, tag]); }, [isFeatured, isTags, tag]);
return ( return (
<>
<Helmet>
<title>ChartDB - Database Schema Templates</title>
</Helmet>
<section className="flex w-screen flex-col bg-background"> <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"> <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 flex-1 justify-start gap-x-3">
@@ -84,13 +86,16 @@ const TemplatesPageComponent: React.FC = () => {
Database Schema Templates Database Schema Templates
</h1> </h1>
<h2 className="mt-1 font-primary text-base text-muted-foreground"> <h2 className="mt-1 font-primary text-base text-muted-foreground">
Explore a collection of real-world database schemas drawn Explore a collection of real-world database schemas
from real-world live applications and open-source projects. drawn from real-world live applications and open-source
Use these as a foundation or source of inspiration when projects. Use these as a foundation or source of
designing your apps architecture. inspiration when designing your apps architecture.
</h2> </h2>
{!templates ? ( {!templates ? (
<Spinner size={'large'} className="mt-20 text-pink-600" /> <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="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"> <div className="relative top-0 flex h-fit w-full shrink-0 flex-col md:sticky md:top-1 md:w-44">
@@ -137,6 +142,7 @@ const TemplatesPageComponent: React.FC = () => {
)} )}
</div> </div>
</section> </section>
</>
); );
}; };

View File

@@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import type { RouteObject } from 'react-router-dom'; import type { RouteObject } from 'react-router-dom';
import { createBrowserRouter } from 'react-router-dom'; import { createBrowserRouter } from 'react-router-dom';
import type { TemplatePageLoaderData } from './pages/template-page/template-page';
const routes: RouteObject[] = [ const routes: RouteObject[] = [
...['', 'diagrams/:diagramId'].map((path) => ({ ...['', 'diagrams/:diagramId'].map((path) => ({
@@ -63,6 +64,7 @@ const routes: RouteObject[] = [
}, },
}, },
{ {
id: 'templates_templateSlug',
path: 'templates/:templateSlug', path: 'templates/:templateSlug',
async lazy() { async lazy() {
const { TemplatePage } = await import( const { TemplatePage } = await import(
@@ -72,6 +74,16 @@ const routes: RouteObject[] = [
element: <TemplatePage />, 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: '*', path: '*',