Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions frontend/packages/erd-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
"clsx": "2.1.1",
"cmdk": "1.1.1",
"elkjs": "0.10.0",
"html-to-image": "1.11.13",
"jspdf": "4.0.0",
"neverthrow": "8.2.0",
"nuqs": "2.4.3",
"pako": "2.1.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ import {
TooltipTrigger,
} from '@dlh/erd-viewer-ui'
import type { FC } from 'react'
import { useMemo } from 'react'
import { CommandPaletteTriggerButton } from '../CommandPalette'
import styles from './AppBar.module.css'
import { CopyLinkButton } from './CopyLinkButton'
import { ExportDropdown } from './ExportDropdown'
import { ExportConfigProvider, ExportDropdown } from './ExportDropdown'
import { GithubButton } from './GithubButton'
import { HelpButton } from './HelpButton'
import { MenuButton } from './MenuButton'
Expand All @@ -21,28 +22,41 @@ type Props = {
config?: AppBarConfig
}

const getAppBarSettings = (config?: AppBarConfig) => ({
logoUrl: config?.logo?.url ?? 'https://liambx.com',
logoImgUrl: config?.logo?.imgUrl ?? '',
logoImgHeight: config?.logo?.imgHeight ?? '',
logoText: config?.logo?.text ?? 'Liam ERD',
showLogoText: config?.logo?.showText !== false,
showSearch: config?.search?.show !== false,
showGithub: config?.github?.show !== false,
githubUrl: config?.github?.url ?? 'https://github.com/liam-hq/liam',
showAnnouncements: config?.announcements?.show !== false,
announcementsUrl:
config?.announcements?.url ?? 'https://github.com/liam-hq/liam/releases',
showHelp: config?.help?.show !== false,
helpItems: config?.help?.items,
showExport: config?.export?.show !== false,
showCopyLink: config?.copyLink?.show !== false,
copyLinkValue: config?.copyLink?.value,
hasCustomLogo: Boolean(config?.logo?.imgUrl),
})

export const AppBar: FC<Props> = ({ config }) => {
const exportConfig = useMemo(
() => ({
printExportLogo: config?.export?.printExportLogo,
printExportLogoPosition: config?.export?.printExportLogoPosition,
}),
[config?.export?.printExportLogo, config?.export?.printExportLogoPosition],
)

const settings = useMemo(() => getAppBarSettings(config), [config])

if (config?.show === false) {
return null
}

const logoUrl = config?.logo?.url ?? 'https://liambx.com'
const logoImgUrl = config?.logo?.imgUrl ?? ''
const logoImgHeight = config?.logo?.imgHeight ?? ''
const logoText = config?.logo?.text ?? 'Liam ERD'
const showLogoText = config?.logo?.showText !== false
const showSearch = config?.search?.show !== false
const showGithub = config?.github?.show !== false
const githubUrl = config?.github?.url ?? 'https://github.com/liam-hq/liam'
const showAnnouncements = config?.announcements?.show !== false
const announcementsUrl =
config?.announcements?.url ?? 'https://github.com/liam-hq/liam/releases'
const showHelp = config?.help?.show !== false
const helpItems = config?.help?.items
const showExport = config?.export?.show !== false
const showCopyLink = config?.copyLink?.show !== false
const copyLinkValue = config?.copyLink?.value

return (
<header className={styles.wrapper}>
<div className={styles.menuButtonWrapper}>
Expand All @@ -53,13 +67,17 @@ export const AppBar: FC<Props> = ({ config }) => {
<TooltipRoot>
<TooltipTrigger asChild>
<a
href={logoUrl}
href={settings.logoUrl}
target="_blank"
rel="noreferrer"
className={styles.iconWrapper}
>
{config?.logo?.imgUrl ? (
<img src={logoImgUrl} alt="logo" height={logoImgHeight} />
{settings.hasCustomLogo ? (
<img
src={settings.logoImgUrl}
alt="logo"
height={settings.logoImgHeight}
/>
) : (
<LiamLogoMark className={styles.logo} />
)}
Expand All @@ -72,21 +90,33 @@ export const AppBar: FC<Props> = ({ config }) => {
</TooltipProvider>
</div>

{showLogoText && <h1 className={styles.title}>{logoText}</h1>}
{settings.showLogoText && (
<h1 className={styles.title}>{settings.logoText}</h1>
)}

<div className={styles.rightSide}>
<div className={styles.iconButtonGroup}>
{showSearch && <CommandPaletteTriggerButton />}
{showGithub && <GithubButton url={githubUrl} />}
{showAnnouncements && <ReleaseNoteButton url={announcementsUrl} />}
{showHelp && (
<HelpButton {...(helpItems ? { items: helpItems } : {})} />
{settings.showSearch && <CommandPaletteTriggerButton />}
{settings.showGithub && <GithubButton url={settings.githubUrl} />}
{settings.showAnnouncements && (
<ReleaseNoteButton url={settings.announcementsUrl} />
)}
{settings.showHelp && (
<HelpButton
{...(settings.helpItems ? { items: settings.helpItems } : {})}
/>
)}
</div>
{showExport && <ExportDropdown />}
{showCopyLink && (
{settings.showExport && (
<ExportConfigProvider config={exportConfig}>
<ExportDropdown />
</ExportConfigProvider>
)}
{settings.showCopyLink && (
<CopyLinkButton
{...(copyLinkValue ? { value: copyLinkValue } : {})}
{...(settings.copyLinkValue
? { value: settings.copyLinkValue }
: {})}
/>
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { createContext, type FC, type ReactNode, useContext } from 'react'
import type { WatermarkPosition } from './useExportDiagram'

type ExportConfig = {
printExportLogo?: string | undefined
printExportLogoPosition?: WatermarkPosition | undefined
}

const ExportConfigContext = createContext<ExportConfig | null>(null)

type ExportConfigProviderProps = {
config: ExportConfig
children: ReactNode
}

export const ExportConfigProvider: FC<ExportConfigProviderProps> = ({
config,
children,
}) => {
return (
<ExportConfigContext.Provider value={config}>
{children}
</ExportConfigContext.Provider>
)
}

export const useExportConfig = (): ExportConfig => {
const context = useContext(ExportConfigContext)
return context ?? {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,26 @@ import {
Button,
ChevronDown,
Copy,
Download,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuPortal,
DropdownMenuRoot,
DropdownMenuSeparator,
DropdownMenuTrigger,
useToast,
} from '@dlh/erd-viewer-ui'
import type { FC } from 'react'
import { useSchemaOrThrow } from '../../../../../../stores'
import { fromPromise } from '../../../../../../utils/neverthrow'
import { useExportConfig } from './ExportConfigContext'
import { useExportDiagram } from './useExportDiagram'

export const ExportDropdown: FC = () => {
const toast = useToast()
const schema = useSchemaOrThrow()
const { exportToPng, exportToPdf } = useExportDiagram()
const exportConfig = useExportConfig()

const handleCopyPostgreSQL = async () => {
// Feature detection for clipboard API
Expand Down Expand Up @@ -104,6 +110,56 @@ export const ExportDropdown: FC = () => {
)
}

const handleExportPng = async () => {
const result = await exportToPng({
watermarkLogo: exportConfig.printExportLogo,
watermarkPosition: exportConfig.printExportLogoPosition,
})

result.match(
() => {
toast({
title: 'PNG exported!',
description: 'Diagram has been exported as PNG',
status: 'success',
})
},
(error: Error) => {
console.error('Failed to export PNG:', error)
toast({
title: 'Export failed',
description: error.message,
status: 'error',
})
},
)
}

const handleExportPdf = async () => {
const result = await exportToPdf({
watermarkLogo: exportConfig.printExportLogo,
watermarkPosition: exportConfig.printExportLogoPosition,
})

result.match(
() => {
toast({
title: 'PDF exported!',
description: 'Diagram has been exported as PDF',
status: 'success',
})
},
(error: Error) => {
console.error('Failed to export PDF:', error)
toast({
title: 'Export failed',
description: error.message,
status: 'error',
})
},
)
}

return (
<DropdownMenuRoot>
<DropdownMenuTrigger asChild>
Expand All @@ -117,6 +173,19 @@ export const ExportDropdown: FC = () => {
</DropdownMenuTrigger>
<DropdownMenuPortal>
<DropdownMenuContent align="end" sideOffset={8}>
<DropdownMenuItem
leftIcon={<Download size={16} />}
onSelect={handleExportPng}
>
Download PNG
</DropdownMenuItem>
<DropdownMenuItem
leftIcon={<Download size={16} />}
onSelect={handleExportPdf}
>
Download PDF
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
leftIcon={<Copy size={16} />}
onSelect={handleCopyPostgreSQL}
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export { ExportConfigProvider } from './ExportConfigContext'
export * from './ExportDropdown'
export type { WatermarkPosition } from './useExportDiagram'
Loading
Loading