mirror of
https://github.com/chartdb/chartdb.git
synced 2025-11-03 05:23:26 +00:00
some mobile & bundle optimizations (#262)
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -24,3 +24,4 @@ dist-ssr
|
||||
*.sw?
|
||||
|
||||
.env
|
||||
stats/
|
||||
@@ -22,6 +22,7 @@
|
||||
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
|
||||
|
||||
86
package-lock.json
generated
86
package-lock.json
generated
@@ -83,6 +83,7 @@
|
||||
"rollup-plugin-visualizer": "^5.12.0",
|
||||
"tailwindcss": "^3.4.7",
|
||||
"typescript": "^5.2.2",
|
||||
"unplugin-inject-preload": "^3.0.0",
|
||||
"vite": "^5.3.4"
|
||||
}
|
||||
},
|
||||
@@ -7844,6 +7845,29 @@
|
||||
"node": ">=8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||
@@ -10276,6 +10300,51 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/unplugin": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.14.1.tgz",
|
||||
"integrity": "sha512-lBlHbfSFPToDYp9pjXlUEFVxYLaue9f9T1HC+4OHlmj+HnMDdz9oZY+erXfoCe/5V/7gKUSY2jpXPb9S7f0f/w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"acorn": "^8.12.1",
|
||||
"webpack-virtual-modules": "^0.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"webpack-sources": "^3"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"webpack-sources": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/unplugin-inject-preload": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unplugin-inject-preload/-/unplugin-inject-preload-3.0.0.tgz",
|
||||
"integrity": "sha512-VwHhjdaGo/CISu5ZZhlN74n3ioUjYGgWBwVwzpQjiCybusZajbT+vsL88sxK/xkH5Ypn2QUc1FA01Ne8K6TJHQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-types": "^2.1.35",
|
||||
"unplugin": "^1.12.2",
|
||||
"webpack-sources": "^3.2.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"html-webpack-plugin": ">=5.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"html-webpack-plugin": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/update-browserslist-db": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz",
|
||||
@@ -10479,6 +10548,23 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/webpack-sources": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz",
|
||||
"integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/webpack-virtual-modules": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",
|
||||
"integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
|
||||
@@ -87,6 +87,7 @@
|
||||
"rollup-plugin-visualizer": "^5.12.0",
|
||||
"tailwindcss": "^3.4.7",
|
||||
"typescript": "^5.2.2",
|
||||
"unplugin-inject-preload": "^3.0.0",
|
||||
"vite": "^5.3.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,30 @@
|
||||
import { cn } from '@/lib/utils';
|
||||
import React from 'react';
|
||||
import { CopyBlock, atomOneDark } from 'react-code-blocks';
|
||||
import type { CodeBlockProps } from 'react-code-blocks/dist/components/CodeBlock';
|
||||
import React, { Suspense } from 'react';
|
||||
import type { CopyBlockProps } from 'react-code-blocks/dist/components/CopyBlock';
|
||||
import { Spinner } from '../spinner/spinner';
|
||||
|
||||
export interface CodeSnippetProps {
|
||||
className?: string;
|
||||
codeProps?: CodeBlockProps;
|
||||
codeProps?: CopyBlockProps;
|
||||
code: string;
|
||||
language?: 'sql' | 'bash';
|
||||
}
|
||||
|
||||
export const CodeSnippet: React.FC<CodeSnippetProps> = ({
|
||||
className,
|
||||
codeProps,
|
||||
code,
|
||||
language = 'sql',
|
||||
}) => {
|
||||
return (
|
||||
<div className={cn('flex flex-1', className)}>
|
||||
const CopyBlock = React.lazy(() =>
|
||||
import('react-code-blocks').then((module) => ({
|
||||
default: (props: CopyBlockProps) => (
|
||||
<module.CopyBlock {...props} theme={module.atomOneDark} />
|
||||
),
|
||||
}))
|
||||
);
|
||||
|
||||
export const CodeSnippet: React.FC<CodeSnippetProps> = React.memo(
|
||||
({ className, codeProps, code, language = 'sql' }) => (
|
||||
<div className={cn('flex flex-1 justify-center', className)}>
|
||||
<Suspense fallback={<Spinner />}>
|
||||
<CopyBlock
|
||||
language={language}
|
||||
text={code}
|
||||
theme={atomOneDark}
|
||||
customStyle={{
|
||||
display: 'flex',
|
||||
flex: '1',
|
||||
@@ -30,6 +33,9 @@ export const CodeSnippet: React.FC<CodeSnippetProps> = ({
|
||||
}}
|
||||
{...codeProps}
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
CodeSnippet.displayName = 'CodeSnippet';
|
||||
|
||||
@@ -12,7 +12,6 @@ import { DatabaseType } from '@/lib/domain/database-type';
|
||||
import { databaseSecondaryLogoMap } from '@/lib/databases';
|
||||
import { CodeSnippet } from '@/components/code-snippet/code-snippet';
|
||||
import { Textarea } from '@/components/textarea/textarea';
|
||||
import { importMetadataScripts } from '@/lib/data/import-metadata/scripts/scripts';
|
||||
import type { DatabaseEdition } from '@/lib/domain/database-edition';
|
||||
import {
|
||||
databaseEditionToImageMap,
|
||||
@@ -33,6 +32,7 @@ import {
|
||||
databaseTypeToClientsMap,
|
||||
} from '@/lib/domain/database-clients';
|
||||
import { isDatabaseMetadata } from '@/lib/data/import-metadata/metadata-types/database-metadata';
|
||||
import type { ImportMetadataScripts } from '@/lib/data/import-metadata/scripts/scripts';
|
||||
|
||||
const errorScriptOutputMessage =
|
||||
'Invalid JSON. Please correct it or contact us at chartdb.io@gmail.com for help.';
|
||||
@@ -70,6 +70,23 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
|
||||
DatabaseClient | undefined
|
||||
>();
|
||||
const { t } = useTranslation();
|
||||
const [importMetadataScripts, setImportMetadataScripts] =
|
||||
useState<ImportMetadataScripts>(
|
||||
Object.values(DatabaseType).reduce((acc, val) => {
|
||||
acc[val] = () => '';
|
||||
return acc;
|
||||
}, {} as ImportMetadataScripts)
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const loadScripts = async () => {
|
||||
const { importMetadataScripts } = await import(
|
||||
'@/lib/data/import-metadata/scripts/scripts'
|
||||
);
|
||||
setImportMetadataScripts(importMetadataScripts);
|
||||
};
|
||||
loadScripts();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (scriptResult.trim().length === 0) {
|
||||
@@ -280,6 +297,7 @@ export const ImportDatabase: React.FC<ImportDatabaseProps> = ({
|
||||
setDatabaseEdition,
|
||||
databaseClients,
|
||||
databaseClient,
|
||||
importMetadataScripts,
|
||||
t,
|
||||
]);
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { ToggleGroupItem } from '@/components/toggle/toggle-group';
|
||||
import type { DatabaseType } from '@/lib/domain/database-type';
|
||||
import { databaseTypeToLabelMap, getDatabaseLogo } from '@/lib/databases';
|
||||
import { useTheme } from '@/hooks/use-theme';
|
||||
|
||||
export interface DatabaseOptionProps {
|
||||
type: DatabaseType;
|
||||
}
|
||||
|
||||
export const DatabaseOption: React.FC<DatabaseOptionProps> = ({ type }) => {
|
||||
const { effectiveTheme } = useTheme();
|
||||
const logo = useMemo(
|
||||
() => getDatabaseLogo(type, effectiveTheme),
|
||||
[type, effectiveTheme]
|
||||
);
|
||||
|
||||
return (
|
||||
<ToggleGroupItem
|
||||
value={type}
|
||||
aria-label="Toggle bold"
|
||||
className="flex size-20 md:size-32"
|
||||
>
|
||||
<img src={logo} alt={databaseTypeToLabelMap[type]} />
|
||||
</ToggleGroupItem>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import { Link } from '@/components/link/link';
|
||||
import { LayoutGrid } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export interface ExampleOptionProps {}
|
||||
|
||||
export const ExampleOption: React.FC<ExampleOptionProps> = () => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Link href="/examples" className="text-primary hover:text-primary">
|
||||
<div className="flex size-20 cursor-pointer flex-col items-center rounded-md border py-3 text-center md:size-32">
|
||||
<div className="flex flex-1 items-center">
|
||||
<LayoutGrid size={34} />
|
||||
</div>
|
||||
<div className="flex flex-col-reverse">
|
||||
<div className="hidden text-sm text-primary md:flex">
|
||||
{t(
|
||||
'new_diagram_dialog.database_selection.check_examples_long'
|
||||
)}
|
||||
</div>
|
||||
<div className="flex text-xs text-primary md:hidden">
|
||||
{t(
|
||||
'new_diagram_dialog.database_selection.check_examples_short'
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
import React from 'react';
|
||||
import { ToggleGroup } from '@/components/toggle/toggle-group';
|
||||
import { DatabaseType } from '@/lib/domain/database-type';
|
||||
import { DatabaseOption } from './database-option';
|
||||
import { ExampleOption } from './example-option';
|
||||
|
||||
export interface SelectDatabaseContentProps {
|
||||
databaseType: DatabaseType;
|
||||
setDatabaseType: React.Dispatch<React.SetStateAction<DatabaseType>>;
|
||||
onContinue: () => void;
|
||||
}
|
||||
|
||||
export const SelectDatabaseContent: React.FC<SelectDatabaseContentProps> = ({
|
||||
databaseType,
|
||||
setDatabaseType,
|
||||
onContinue,
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex flex-1 items-center justify-center">
|
||||
<ToggleGroup
|
||||
value={databaseType}
|
||||
onValueChange={(value: DatabaseType) => {
|
||||
if (!value) {
|
||||
setDatabaseType(DatabaseType.GENERIC);
|
||||
} else {
|
||||
setDatabaseType(value);
|
||||
onContinue();
|
||||
}
|
||||
}}
|
||||
type="single"
|
||||
className="grid grid-flow-row grid-cols-3 gap-6"
|
||||
>
|
||||
<DatabaseOption type={DatabaseType.MYSQL} />
|
||||
<DatabaseOption type={DatabaseType.POSTGRESQL} />
|
||||
<DatabaseOption type={DatabaseType.MARIADB} />
|
||||
<DatabaseOption type={DatabaseType.SQLITE} />
|
||||
<DatabaseOption type={DatabaseType.SQL_SERVER} />
|
||||
<ExampleOption />
|
||||
</ToggleGroup>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import React from 'react';
|
||||
import { Button } from '@/components/button/button';
|
||||
import {
|
||||
DialogClose,
|
||||
@@ -7,14 +7,9 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/dialog/dialog';
|
||||
|
||||
import { ToggleGroup, ToggleGroupItem } from '@/components/toggle/toggle-group';
|
||||
import { DatabaseType } from '@/lib/domain/database-type';
|
||||
import { databaseTypeToLabelMap, getDatabaseLogo } from '@/lib/databases';
|
||||
import { Link } from '@/components/link/link';
|
||||
import { LayoutGrid } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useTheme } from '@/hooks/use-theme';
|
||||
import { SelectDatabaseContent } from './select-database-content';
|
||||
|
||||
export interface SelectDatabaseProps {
|
||||
onContinue: () => void;
|
||||
@@ -32,50 +27,9 @@ export const SelectDatabase: React.FC<SelectDatabaseProps> = ({
|
||||
createNewDiagram,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { effectiveTheme } = useTheme();
|
||||
const renderDatabaseOption = useCallback(
|
||||
(type: DatabaseType) => {
|
||||
const logo = getDatabaseLogo(type, effectiveTheme);
|
||||
return (
|
||||
<ToggleGroupItem
|
||||
value={type}
|
||||
aria-label="Toggle bold"
|
||||
className="flex size-20 md:size-32"
|
||||
>
|
||||
<img src={logo} alt={databaseTypeToLabelMap[type]} />
|
||||
</ToggleGroupItem>
|
||||
);
|
||||
},
|
||||
[effectiveTheme]
|
||||
);
|
||||
|
||||
const renderExamplesOption = useCallback(
|
||||
() => (
|
||||
<Link href="/examples" className="text-primary hover:text-primary">
|
||||
<div className="flex size-20 cursor-pointer flex-col items-center rounded-md border py-3 text-center md:size-32">
|
||||
<div className="flex flex-1 items-center">
|
||||
<LayoutGrid size={34} />
|
||||
</div>
|
||||
<div className="flex flex-col-reverse">
|
||||
<div className="hidden text-sm text-primary md:flex">
|
||||
{t(
|
||||
'new_diagram_dialog.database_selection.check_examples_long'
|
||||
)}
|
||||
</div>
|
||||
<div className="flex text-xs text-primary md:hidden">
|
||||
{t(
|
||||
'new_diagram_dialog.database_selection.check_examples_short'
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
),
|
||||
[t]
|
||||
);
|
||||
|
||||
const renderHeader = useCallback(() => {
|
||||
return (
|
||||
<>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{t('new_diagram_dialog.database_selection.title')}
|
||||
@@ -84,44 +38,11 @@ export const SelectDatabase: React.FC<SelectDatabaseProps> = ({
|
||||
{t('new_diagram_dialog.database_selection.description')}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
);
|
||||
}, [t]);
|
||||
|
||||
const renderContent = useCallback(() => {
|
||||
return (
|
||||
<div className="flex flex-1 items-center justify-center">
|
||||
<ToggleGroup
|
||||
value={databaseType}
|
||||
onValueChange={(value: DatabaseType) => {
|
||||
if (!value) {
|
||||
setDatabaseType(DatabaseType.GENERIC);
|
||||
} else {
|
||||
setDatabaseType(value);
|
||||
onContinue();
|
||||
}
|
||||
}}
|
||||
type="single"
|
||||
className="grid grid-flow-row grid-cols-3 gap-6"
|
||||
>
|
||||
{renderDatabaseOption(DatabaseType.MYSQL)}
|
||||
{renderDatabaseOption(DatabaseType.POSTGRESQL)}
|
||||
{renderDatabaseOption(DatabaseType.MARIADB)}
|
||||
{renderDatabaseOption(DatabaseType.SQLITE)}
|
||||
{renderDatabaseOption(DatabaseType.SQL_SERVER)}
|
||||
{renderExamplesOption()}
|
||||
</ToggleGroup>
|
||||
</div>
|
||||
);
|
||||
}, [
|
||||
databaseType,
|
||||
renderDatabaseOption,
|
||||
renderExamplesOption,
|
||||
setDatabaseType,
|
||||
onContinue,
|
||||
]);
|
||||
|
||||
const renderFooter = useCallback(() => {
|
||||
return (
|
||||
<SelectDatabaseContent
|
||||
databaseType={databaseType}
|
||||
onContinue={onContinue}
|
||||
setDatabaseType={setDatabaseType}
|
||||
/>
|
||||
<DialogFooter className="mt-4 flex !justify-between gap-2">
|
||||
{hasExistingDiagram ? (
|
||||
<DialogClose asChild>
|
||||
@@ -150,14 +71,6 @@ export const SelectDatabase: React.FC<SelectDatabaseProps> = ({
|
||||
</Button>
|
||||
</div>
|
||||
</DialogFooter>
|
||||
);
|
||||
}, [createNewDiagram, databaseType, hasExistingDiagram, onContinue, t]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{renderHeader()}
|
||||
{renderContent()}
|
||||
{renderFooter()}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -7,13 +7,15 @@ import { mariaDBQuery } from './maria-script';
|
||||
import type { DatabaseEdition } from '@/lib/domain/database-edition';
|
||||
import type { DatabaseClient } from '@/lib/domain/database-clients';
|
||||
|
||||
export const importMetadataScripts: Record<
|
||||
export type ImportMetadataScripts = Record<
|
||||
DatabaseType,
|
||||
(options?: {
|
||||
databaseEdition?: DatabaseEdition;
|
||||
databaseClient?: DatabaseClient;
|
||||
}) => string
|
||||
> = {
|
||||
>;
|
||||
|
||||
export const importMetadataScripts: ImportMetadataScripts = {
|
||||
[DatabaseType.GENERIC]: () => '',
|
||||
[DatabaseType.POSTGRESQL]: getPostgresQuery,
|
||||
[DatabaseType.MYSQL]: getMySQLQuery,
|
||||
|
||||
42
src/pages/editor-page/editor-desktop-layout.tsx
Normal file
42
src/pages/editor-page/editor-desktop-layout.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
ResizableHandle,
|
||||
ResizablePanel,
|
||||
ResizablePanelGroup,
|
||||
} from '@/components/resizable/resizable';
|
||||
import { SidePanel } from './side-panel/side-panel';
|
||||
import { Canvas } from './canvas/canvas';
|
||||
import { useBreakpoint } from '@/hooks/use-breakpoint';
|
||||
import { useLayout } from '@/hooks/use-layout';
|
||||
import type { Diagram } from '@/lib/domain/diagram';
|
||||
|
||||
export interface EditorDesktopLayoutProps {
|
||||
initialDiagram?: Diagram;
|
||||
}
|
||||
export const EditorDesktopLayout: React.FC<EditorDesktopLayoutProps> = ({
|
||||
initialDiagram,
|
||||
}) => {
|
||||
const { isSidePanelShowed } = useLayout();
|
||||
const { isLg } = useBreakpoint('lg');
|
||||
const { isXl } = useBreakpoint('xl');
|
||||
|
||||
return (
|
||||
<ResizablePanelGroup direction="horizontal">
|
||||
<ResizablePanel
|
||||
defaultSize={isXl ? 25 : isLg ? 35 : 50}
|
||||
minSize={isXl ? 25 : isLg ? 35 : 50}
|
||||
maxSize={isSidePanelShowed ? 99 : 0}
|
||||
// eslint-disable-next-line
|
||||
className="transition-[flex-grow] duration-200"
|
||||
>
|
||||
<SidePanel />
|
||||
</ResizablePanel>
|
||||
<ResizableHandle />
|
||||
<ResizablePanel defaultSize={isXl ? 75 : isLg ? 65 : 50}>
|
||||
<Canvas initialTables={initialDiagram?.tables ?? []} />
|
||||
</ResizablePanel>
|
||||
</ResizablePanelGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditorDesktopLayout;
|
||||
41
src/pages/editor-page/editor-mobile-layout.tsx
Normal file
41
src/pages/editor-page/editor-mobile-layout.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import React from 'react';
|
||||
import { SidePanel } from './side-panel/side-panel';
|
||||
import { Canvas } from './canvas/canvas';
|
||||
import { useLayout } from '@/hooks/use-layout';
|
||||
import {
|
||||
Drawer,
|
||||
DrawerContent,
|
||||
DrawerDescription,
|
||||
DrawerHeader,
|
||||
DrawerTitle,
|
||||
} from '@/components/drawer/drawer';
|
||||
import { Separator } from '@/components/separator/separator';
|
||||
import type { Diagram } from '@/lib/domain/diagram';
|
||||
|
||||
export interface EditorMobileLayoutProps {
|
||||
initialDiagram?: Diagram;
|
||||
}
|
||||
export const EditorMobileLayout: React.FC<EditorMobileLayoutProps> = ({
|
||||
initialDiagram,
|
||||
}) => {
|
||||
const { isSidePanelShowed, hideSidePanel } = useLayout();
|
||||
return (
|
||||
<>
|
||||
<Drawer open={isSidePanelShowed} onClose={() => hideSidePanel()}>
|
||||
<DrawerContent className="h-full" fullScreen>
|
||||
<DrawerHeader>
|
||||
<DrawerTitle>Manage Diagram</DrawerTitle>
|
||||
<DrawerDescription>
|
||||
Manage your diagram objects
|
||||
</DrawerDescription>
|
||||
</DrawerHeader>
|
||||
<Separator orientation="horizontal" />
|
||||
<SidePanel data-vaul-no-drag />
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
<Canvas initialTables={initialDiagram?.tables ?? []} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditorMobileLayout;
|
||||
@@ -1,12 +1,11 @@
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import React, {
|
||||
Suspense,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { TopNavbar } from './top-navbar/top-navbar';
|
||||
import {
|
||||
ResizableHandle,
|
||||
ResizablePanel,
|
||||
ResizablePanelGroup,
|
||||
} from '@/components/resizable/resizable';
|
||||
import { SidePanel } from './side-panel/side-panel';
|
||||
import { Canvas } from './canvas/canvas';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { useConfig } from '@/hooks/use-config';
|
||||
import { useChartDB } from '@/hooks/use-chartdb';
|
||||
@@ -17,14 +16,6 @@ import { useFullScreenLoader } from '@/hooks/use-full-screen-spinner';
|
||||
import { useBreakpoint } from '@/hooks/use-breakpoint';
|
||||
import { useLayout } from '@/hooks/use-layout';
|
||||
import { useToast } from '@/components/toast/use-toast';
|
||||
import {
|
||||
Drawer,
|
||||
DrawerContent,
|
||||
DrawerDescription,
|
||||
DrawerHeader,
|
||||
DrawerTitle,
|
||||
} from '@/components/drawer/drawer';
|
||||
import { Separator } from '@/components/separator/separator';
|
||||
import type { Diagram } from '@/lib/domain/diagram';
|
||||
import { ToastAction } from '@/components/toast/toast';
|
||||
import { useLocalConfig } from '@/hooks/use-local-config';
|
||||
@@ -42,27 +33,31 @@ import { ReactFlowProvider } from '@xyflow/react';
|
||||
import { ExportImageProvider } from '@/context/export-image-context/export-image-provider';
|
||||
import { DialogProvider } from '@/context/dialog-context/dialog-provider';
|
||||
import { KeyboardShortcutsProvider } from '@/context/keyboard-shortcuts-context/keyboard-shortcuts-provider';
|
||||
// import { EditorMobileLayout } from './editor-mobile-layout';
|
||||
import { Spinner } from '@/components/spinner/spinner';
|
||||
// import { EditorDesktopLayout } from './editor-desktop-layout';
|
||||
|
||||
const OPEN_STAR_US_AFTER_SECONDS = 30;
|
||||
const SHOW_STAR_US_AGAIN_AFTER_DAYS = 1;
|
||||
|
||||
export const EditorDesktopLayoutLazy = React.lazy(
|
||||
() => import('./editor-desktop-layout')
|
||||
);
|
||||
|
||||
export const EditorMobileLayoutLazy = React.lazy(
|
||||
() => import('./editor-mobile-layout')
|
||||
);
|
||||
|
||||
const EditorPageComponent: React.FC = () => {
|
||||
const { loadDiagram, currentDiagram, schemas, filteredSchemas } =
|
||||
useChartDB();
|
||||
const {
|
||||
isSidePanelShowed,
|
||||
hideSidePanel,
|
||||
openSelectSchema,
|
||||
showSidePanel,
|
||||
} = useLayout();
|
||||
const { openSelectSchema, showSidePanel } = useLayout();
|
||||
const { resetRedoStack, resetUndoStack } = useRedoUndoStack();
|
||||
const { showLoader, hideLoader } = useFullScreenLoader();
|
||||
const { openCreateDiagramDialog, openStarUsDialog } = useDialog();
|
||||
const { diagramId } = useParams<{ diagramId: string }>();
|
||||
const { config, updateConfig } = useConfig();
|
||||
const navigate = useNavigate();
|
||||
const { isLg } = useBreakpoint('lg');
|
||||
const { isXl } = useBreakpoint('xl');
|
||||
const { isMd: isDesktop } = useBreakpoint('md');
|
||||
const [initialDiagram, setInitialDiagram] = useState<Diagram | undefined>();
|
||||
const {
|
||||
@@ -221,46 +216,23 @@ const EditorPageComponent: React.FC = () => {
|
||||
className={`bg-background ${isDesktop ? 'h-screen w-screen' : 'h-dvh w-dvw'} flex select-none flex-col overflow-x-hidden`}
|
||||
>
|
||||
<TopNavbar />
|
||||
<Suspense
|
||||
fallback={
|
||||
<div className="flex flex-1 items-center justify-center">
|
||||
<Spinner size={isDesktop ? 'large' : 'medium'} />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{isDesktop ? (
|
||||
<ResizablePanelGroup direction="horizontal">
|
||||
<ResizablePanel
|
||||
defaultSize={isXl ? 25 : isLg ? 35 : 50}
|
||||
minSize={isXl ? 25 : isLg ? 35 : 50}
|
||||
maxSize={isSidePanelShowed ? 99 : 0}
|
||||
// eslint-disable-next-line
|
||||
className="transition-[flex-grow] duration-200"
|
||||
>
|
||||
<SidePanel />
|
||||
</ResizablePanel>
|
||||
<ResizableHandle />
|
||||
<ResizablePanel
|
||||
defaultSize={isXl ? 75 : isLg ? 65 : 50}
|
||||
>
|
||||
<Canvas
|
||||
initialTables={initialDiagram?.tables ?? []}
|
||||
<EditorDesktopLayoutLazy
|
||||
initialDiagram={initialDiagram}
|
||||
/>
|
||||
</ResizablePanel>
|
||||
</ResizablePanelGroup>
|
||||
) : (
|
||||
<>
|
||||
<Drawer
|
||||
open={isSidePanelShowed}
|
||||
onClose={() => hideSidePanel()}
|
||||
>
|
||||
<DrawerContent className="h-full" fullScreen>
|
||||
<DrawerHeader>
|
||||
<DrawerTitle>Manage Diagram</DrawerTitle>
|
||||
<DrawerDescription>
|
||||
Manage your diagram objects
|
||||
</DrawerDescription>
|
||||
</DrawerHeader>
|
||||
<Separator orientation="horizontal" />
|
||||
<SidePanel data-vaul-no-drag />
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
<Canvas initialTables={initialDiagram?.tables ?? []} />
|
||||
</>
|
||||
<EditorMobileLayoutLazy
|
||||
initialDiagram={initialDiagram}
|
||||
/>
|
||||
)}
|
||||
</Suspense>
|
||||
</section>
|
||||
<Toaster />
|
||||
</>
|
||||
@@ -268,15 +240,15 @@ const EditorPageComponent: React.FC = () => {
|
||||
};
|
||||
|
||||
export const EditorPage: React.FC = () => (
|
||||
<LocalConfigProvider>
|
||||
<ThemeProvider>
|
||||
<FullScreenLoaderProvider>
|
||||
<LayoutProvider>
|
||||
<LocalConfigProvider>
|
||||
<StorageProvider>
|
||||
<ConfigProvider>
|
||||
<RedoUndoStackProvider>
|
||||
<ChartDBProvider>
|
||||
<HistoryProvider>
|
||||
<ThemeProvider>
|
||||
<ReactFlowProvider>
|
||||
<ExportImageProvider>
|
||||
<DialogProvider>
|
||||
@@ -286,13 +258,13 @@ export const EditorPage: React.FC = () => (
|
||||
</DialogProvider>
|
||||
</ExportImageProvider>
|
||||
</ReactFlowProvider>
|
||||
</ThemeProvider>
|
||||
</HistoryProvider>
|
||||
</ChartDBProvider>
|
||||
</RedoUndoStackProvider>
|
||||
</ConfigProvider>
|
||||
</StorageProvider>
|
||||
</LocalConfigProvider>
|
||||
</LayoutProvider>
|
||||
</FullScreenLoaderProvider>
|
||||
</ThemeProvider>
|
||||
</LocalConfigProvider>
|
||||
);
|
||||
|
||||
92
src/pages/editor-page/top-navbar/diagram-name.tsx
Normal file
92
src/pages/editor-page/top-navbar/diagram-name.tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { Label } from '@/components/label/label';
|
||||
import { Button } from '@/components/button/button';
|
||||
import { Check, Pencil } from 'lucide-react';
|
||||
import { Input } from '@/components/input/input';
|
||||
import { useChartDB } from '@/hooks/use-chartdb';
|
||||
import { useClickAway, useKeyPressEvent } from 'react-use';
|
||||
import { useBreakpoint } from '@/hooks/use-breakpoint';
|
||||
import { DiagramIcon } from '@/components/diagram-icon/diagram-icon';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { labelVariants } from '@/components/label/label-variants';
|
||||
|
||||
export interface DiagramNameProps {}
|
||||
|
||||
export const DiagramName: React.FC<DiagramNameProps> = () => {
|
||||
const { diagramName, updateDiagramName, currentDiagram } = useChartDB();
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { isMd: isDesktop } = useBreakpoint('md');
|
||||
const [editMode, setEditMode] = useState(false);
|
||||
const [editedDiagramName, setEditedDiagramName] =
|
||||
React.useState(diagramName);
|
||||
const inputRef = React.useRef<HTMLInputElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setEditedDiagramName(diagramName);
|
||||
}, [diagramName]);
|
||||
|
||||
const editDiagramName = useCallback(() => {
|
||||
if (!editMode) return;
|
||||
if (editedDiagramName.trim()) {
|
||||
updateDiagramName(editedDiagramName.trim());
|
||||
}
|
||||
setEditMode(false);
|
||||
}, [editedDiagramName, updateDiagramName, editMode]);
|
||||
|
||||
useClickAway(inputRef, editDiagramName);
|
||||
useKeyPressEvent('Enter', editDiagramName);
|
||||
|
||||
const enterEditMode = (
|
||||
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
|
||||
) => {
|
||||
event.stopPropagation();
|
||||
setEditMode(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<DiagramIcon diagram={currentDiagram} />
|
||||
<div className="flex">
|
||||
{isDesktop ? <Label>{t('diagrams')}/</Label> : null}
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-1">
|
||||
{editMode ? (
|
||||
<>
|
||||
<Input
|
||||
ref={inputRef}
|
||||
autoFocus
|
||||
type="text"
|
||||
placeholder={diagramName}
|
||||
value={editedDiagramName}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onChange={(e) =>
|
||||
setEditedDiagramName(e.target.value)
|
||||
}
|
||||
className="ml-1 h-7 focus-visible:ring-0"
|
||||
/>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="hidden size-7 p-2 text-slate-500 hover:bg-primary-foreground hover:text-slate-700 group-hover:flex dark:text-slate-400 dark:hover:text-slate-300"
|
||||
onClick={editDiagramName}
|
||||
>
|
||||
<Check />
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<h1 className={cn(labelVariants())}>{diagramName}</h1>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="hidden size-7 p-2 text-slate-500 hover:bg-primary-foreground hover:text-slate-700 group-hover:flex dark:text-slate-400 dark:hover:text-slate-300"
|
||||
onClick={enterEditMode}
|
||||
>
|
||||
<Pencil />
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
31
src/pages/editor-page/top-navbar/last-saved.tsx
Normal file
31
src/pages/editor-page/top-navbar/last-saved.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import TimeAgo from 'timeago-react';
|
||||
import { useChartDB } from '@/hooks/use-chartdb';
|
||||
import { Badge } from '@/components/badge/badge';
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from '@/components/tooltip/tooltip';
|
||||
import { useBreakpoint } from '@/hooks/use-breakpoint';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
export interface LastSavedProps {}
|
||||
|
||||
export const LastSaved: React.FC<LastSavedProps> = () => {
|
||||
const { currentDiagram } = useChartDB();
|
||||
const { t } = useTranslation();
|
||||
const { isMd: isDesktop } = useBreakpoint('md');
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<Badge variant="secondary" className="flex gap-1">
|
||||
{isDesktop ? t('last_saved') : t('saved')}
|
||||
<TimeAgo datetime={currentDiagram.updatedAt} />
|
||||
</Badge>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{currentDiagram.updatedAt.toLocaleString()}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
@@ -1,5 +1,4 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import TimeAgo from 'timeago-react';
|
||||
import React, { useCallback } from 'react';
|
||||
import {
|
||||
Menubar,
|
||||
MenubarCheckboxItem,
|
||||
@@ -13,28 +12,16 @@ import {
|
||||
MenubarSubTrigger,
|
||||
MenubarTrigger,
|
||||
} from '@/components/menubar/menubar';
|
||||
import { Label } from '@/components/label/label';
|
||||
import { Button } from '@/components/button/button';
|
||||
import { Check, Pencil } from 'lucide-react';
|
||||
import { Input } from '@/components/input/input';
|
||||
import { useChartDB } from '@/hooks/use-chartdb';
|
||||
import { useClickAway, useKeyPressEvent } from 'react-use';
|
||||
import ChartDBLogo from '@/assets/logo.png';
|
||||
import ChartDBLogo from '@/assets/logo-light.png';
|
||||
import ChartDBDarkLogo from '@/assets/logo-dark.png';
|
||||
import { useDialog } from '@/hooks/use-dialog';
|
||||
import { Badge } from '@/components/badge/badge';
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from '@/components/tooltip/tooltip';
|
||||
import { useExportImage } from '@/hooks/use-export-image';
|
||||
import { databaseTypeToLabelMap } from '@/lib/databases';
|
||||
import { DatabaseType } from '@/lib/domain/database-type';
|
||||
import { useConfig } from '@/hooks/use-config';
|
||||
import { IS_CHARTDB_IO } from '@/lib/env';
|
||||
import { useBreakpoint } from '@/hooks/use-breakpoint';
|
||||
import { DiagramIcon } from '@/components/diagram-icon/diagram-icon';
|
||||
import {
|
||||
KeyboardShortcutAction,
|
||||
keyboardShortcutsForOS,
|
||||
@@ -49,20 +36,14 @@ import { deMetadata } from '@/i18n/locales/de';
|
||||
import { jaMetadata } from '@/i18n/locales/ja';
|
||||
import { useLocalConfig } from '@/hooks/use-local-config';
|
||||
import { frMetadata } from '@/i18n/locales/fr';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { labelVariants } from '@/components/label/label-variants';
|
||||
import { DiagramName } from './diagram-name';
|
||||
import { LastSaved } from './last-saved';
|
||||
|
||||
export interface TopNavbarProps {}
|
||||
|
||||
export const TopNavbar: React.FC<TopNavbarProps> = () => {
|
||||
const {
|
||||
diagramName,
|
||||
updateDiagramName,
|
||||
currentDiagram,
|
||||
clearDiagramData,
|
||||
deleteDiagram,
|
||||
updateDiagramUpdatedAt,
|
||||
} = useChartDB();
|
||||
const { clearDiagramData, deleteDiagram, updateDiagramUpdatedAt } =
|
||||
useChartDB();
|
||||
const {
|
||||
openCreateDiagramDialog,
|
||||
openOpenDiagramDialog,
|
||||
@@ -86,26 +67,7 @@ export const TopNavbar: React.FC<TopNavbarProps> = () => {
|
||||
const { redo, undo, hasRedo, hasUndo } = useHistory();
|
||||
const { isMd: isDesktop } = useBreakpoint('md');
|
||||
const { config, updateConfig } = useConfig();
|
||||
const [editMode, setEditMode] = useState(false);
|
||||
const { exportImage } = useExportImage();
|
||||
const [editedDiagramName, setEditedDiagramName] =
|
||||
React.useState(diagramName);
|
||||
const inputRef = React.useRef<HTMLInputElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setEditedDiagramName(diagramName);
|
||||
}, [diagramName]);
|
||||
|
||||
const editDiagramName = useCallback(() => {
|
||||
if (!editMode) return;
|
||||
if (editedDiagramName.trim()) {
|
||||
updateDiagramName(editedDiagramName.trim());
|
||||
}
|
||||
setEditMode(false);
|
||||
}, [editedDiagramName, updateDiagramName, editMode]);
|
||||
|
||||
useClickAway(inputRef, editDiagramName);
|
||||
useKeyPressEvent('Enter', editDiagramName);
|
||||
|
||||
const createNewDiagram = () => {
|
||||
openCreateDiagramDialog();
|
||||
@@ -115,13 +77,6 @@ export const TopNavbar: React.FC<TopNavbarProps> = () => {
|
||||
openOpenDiagramDialog();
|
||||
};
|
||||
|
||||
const enterEditMode = (
|
||||
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
|
||||
) => {
|
||||
event.stopPropagation();
|
||||
setEditMode(true);
|
||||
};
|
||||
|
||||
const exportSVG = useCallback(() => {
|
||||
exportImage('svg', 1);
|
||||
}, [exportImage]);
|
||||
@@ -220,79 +175,6 @@ export const TopNavbar: React.FC<TopNavbarProps> = () => {
|
||||
);
|
||||
}, [isDesktop]);
|
||||
|
||||
const renderLastSaved = useCallback(() => {
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<Badge variant="secondary" className="flex gap-1">
|
||||
{isDesktop ? t('last_saved') : t('saved')}
|
||||
<TimeAgo datetime={currentDiagram.updatedAt} />
|
||||
</Badge>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{currentDiagram.updatedAt.toLocaleString()}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
);
|
||||
}, [currentDiagram.updatedAt, isDesktop, t]);
|
||||
|
||||
const renderDiagramName = useCallback(() => {
|
||||
return (
|
||||
<>
|
||||
<DiagramIcon diagram={currentDiagram} />
|
||||
<div className="flex">
|
||||
{isDesktop ? <Label>{t('diagrams')}/</Label> : null}
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-1">
|
||||
{editMode ? (
|
||||
<>
|
||||
<Input
|
||||
ref={inputRef}
|
||||
autoFocus
|
||||
type="text"
|
||||
placeholder={diagramName}
|
||||
value={editedDiagramName}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onChange={(e) =>
|
||||
setEditedDiagramName(e.target.value)
|
||||
}
|
||||
className="ml-1 h-7 focus-visible:ring-0"
|
||||
/>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="hidden size-7 p-2 text-slate-500 hover:bg-primary-foreground hover:text-slate-700 group-hover:flex dark:text-slate-400 dark:hover:text-slate-300"
|
||||
onClick={editDiagramName}
|
||||
>
|
||||
<Check />
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<h1 className={cn(labelVariants())}>
|
||||
{diagramName}
|
||||
</h1>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="hidden size-7 p-2 text-slate-500 hover:bg-primary-foreground hover:text-slate-700 group-hover:flex dark:text-slate-400 dark:hover:text-slate-300"
|
||||
onClick={enterEditMode}
|
||||
>
|
||||
<Pencil />
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}, [
|
||||
currentDiagram,
|
||||
diagramName,
|
||||
editDiagramName,
|
||||
editMode,
|
||||
editedDiagramName,
|
||||
isDesktop,
|
||||
t,
|
||||
]);
|
||||
|
||||
const showOrHideSidePanel = useCallback(() => {
|
||||
if (isSidePanelShowed) {
|
||||
hideSidePanel();
|
||||
@@ -762,19 +644,21 @@ export const TopNavbar: React.FC<TopNavbarProps> = () => {
|
||||
{isDesktop ? (
|
||||
<>
|
||||
<div className="group flex flex-1 flex-row items-center justify-center">
|
||||
{renderDiagramName()}
|
||||
<DiagramName />
|
||||
</div>
|
||||
<div className="hidden flex-1 items-center justify-end gap-2 sm:flex">
|
||||
{renderLastSaved()}
|
||||
<LastSaved />
|
||||
{renderStars()}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="flex flex-1 flex-row justify-between gap-2">
|
||||
<div className="group flex flex-1 flex-row items-center">
|
||||
{renderDiagramName()}
|
||||
<DiagramName />
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<LastSaved />
|
||||
</div>
|
||||
<div className="flex items-center">{renderLastSaved()}</div>
|
||||
<div className="flex items-center">{renderStars()}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import ChartDBLogo from '@/assets/logo.png';
|
||||
import ChartDBLogo from '@/assets/logo-light.png';
|
||||
import ChartDBDarkLogo from '@/assets/logo-dark.png';
|
||||
import { examples } from './examples-data/examples-data';
|
||||
import { ExampleCard } from './example-card';
|
||||
|
||||
@@ -1,11 +1,30 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
// import { visualizer } from 'rollup-plugin-visualizer';
|
||||
import { visualizer } from 'rollup-plugin-visualizer';
|
||||
import path from 'path';
|
||||
import UnpluginInjectPreload from 'unplugin-inject-preload/vite';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react() /*, visualizer()*/],
|
||||
plugins: [
|
||||
react(),
|
||||
visualizer({
|
||||
filename: './stats/stats.html',
|
||||
open: false,
|
||||
}),
|
||||
UnpluginInjectPreload({
|
||||
files: [
|
||||
{
|
||||
entryMatch: /logo-light.png$/,
|
||||
outputMatch: /logo-light-.*.png$/,
|
||||
},
|
||||
{
|
||||
entryMatch: /logo-dark.png$/,
|
||||
outputMatch: /logo-dark-.*.png$/,
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src'),
|
||||
|
||||
Reference in New Issue
Block a user