kopia lustrzana https://github.com/Tldraw/Tldraw
223 wiersze
6.2 KiB
TypeScript
223 wiersze
6.2 KiB
TypeScript
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
|
import { CheckIcon, PlusIcon } from '@radix-ui/react-icons'
|
|
import * as React from 'react'
|
|
import { FormattedMessage, useIntl } from 'react-intl'
|
|
import { Divider } from '~components/Primitives/Divider'
|
|
import { DMContent } from '~components/Primitives/DropdownMenu'
|
|
import { RowButton } from '~components/Primitives/RowButton'
|
|
import { SmallIcon } from '~components/Primitives/SmallIcon'
|
|
import { ToolButton } from '~components/Primitives/ToolButton'
|
|
import { useTldrawApp } from '~hooks'
|
|
import { styled } from '~styles'
|
|
import type { TDSnapshot } from '~types'
|
|
import { PageOptionsDialog } from '../PageOptionsDialog'
|
|
|
|
const sortedSelector = (s: TDSnapshot) =>
|
|
Object.values(s.document.pages).sort((a, b) => (a.childIndex || 0) - (b.childIndex || 0))
|
|
|
|
const currentPageNameSelector = (s: TDSnapshot) => s.document.pages[s.appState.currentPageId].name
|
|
|
|
const currentPageIdSelector = (s: TDSnapshot) => s.document.pages[s.appState.currentPageId].id
|
|
|
|
export function PageMenu() {
|
|
const app = useTldrawApp()
|
|
|
|
const intl = useIntl()
|
|
|
|
const rIsOpen = React.useRef(false)
|
|
|
|
const [isOpen, setIsOpen] = React.useState(false)
|
|
|
|
React.useEffect(() => {
|
|
if (rIsOpen.current !== isOpen) {
|
|
rIsOpen.current = isOpen
|
|
}
|
|
}, [isOpen])
|
|
|
|
const handleClose = React.useCallback(() => {
|
|
setIsOpen(false)
|
|
}, [setIsOpen])
|
|
|
|
const handleOpenChange = React.useCallback(
|
|
(isOpen: boolean) => {
|
|
if (rIsOpen.current !== isOpen) {
|
|
setIsOpen(isOpen)
|
|
}
|
|
},
|
|
[setIsOpen]
|
|
)
|
|
const currentPageName = app.useStore(currentPageNameSelector)
|
|
|
|
return (
|
|
<DropdownMenu.Root dir="ltr" open={isOpen} onOpenChange={handleOpenChange}>
|
|
<DropdownMenu.Trigger dir="ltr" asChild id="TD-Page">
|
|
<ToolButton variant="text">{currentPageName || intl.formatMessage({ id: 'page' })}</ToolButton>
|
|
</DropdownMenu.Trigger>
|
|
<DMContent variant="menu" align="start" sideOffset={4}>
|
|
{isOpen && <PageMenuContent onClose={handleClose} />}
|
|
</DMContent>
|
|
</DropdownMenu.Root>
|
|
)
|
|
}
|
|
|
|
function PageMenuContent({ onClose }: { onClose: () => void }) {
|
|
const app = useTldrawApp()
|
|
const intl = useIntl()
|
|
|
|
const sortedPages = app.useStore(sortedSelector)
|
|
|
|
const currentPageId = app.useStore(currentPageIdSelector)
|
|
|
|
const defaultPageName = intl.formatMessage({ id: 'page' })
|
|
|
|
const handleCreatePage = React.useCallback(() => {
|
|
const pageName =
|
|
defaultPageName + ' ' + (Object.keys(app.document.pages).length + 1)
|
|
app.createPage(undefined, pageName)
|
|
}, [app])
|
|
|
|
const handleChangePage = React.useCallback(
|
|
(id: string) => {
|
|
onClose()
|
|
app.changePage(id)
|
|
},
|
|
[app]
|
|
)
|
|
|
|
const [dragId, setDragId] = React.useState<null | string>(null)
|
|
|
|
const [dropIndex, setDropIndex] = React.useState<null | number>(null)
|
|
|
|
const handleDragStart = React.useCallback((ev: React.DragEvent<HTMLDivElement>) => {
|
|
setDragId(ev.currentTarget.id)
|
|
setDropIndex(sortedPages.findIndex((p) => p.id === ev.currentTarget.id))
|
|
ev.dataTransfer.effectAllowed = 'move'
|
|
}, [])
|
|
|
|
const handleDrag = React.useCallback(
|
|
(ev: React.DragEvent<HTMLDivElement>) => {
|
|
ev.preventDefault()
|
|
|
|
let dropIndex = sortedPages.findIndex((p) => p.id === ev.currentTarget.id)
|
|
|
|
const rect = ev.currentTarget.getBoundingClientRect()
|
|
const ny = (ev.clientY - rect.top) / rect.height
|
|
|
|
dropIndex = ny < 0.5 ? dropIndex : dropIndex + 1
|
|
|
|
setDropIndex(dropIndex)
|
|
},
|
|
[dragId, sortedPages]
|
|
)
|
|
|
|
const handleDrop = React.useCallback(() => {
|
|
if (dragId !== null && dropIndex !== null) {
|
|
app.movePage(dragId, dropIndex)
|
|
}
|
|
|
|
setDragId(null)
|
|
setDropIndex(null)
|
|
}, [dragId, dropIndex])
|
|
|
|
return (
|
|
<>
|
|
<DropdownMenu.RadioGroup dir="ltr" value={currentPageId} onValueChange={handleChangePage}>
|
|
{sortedPages.map((page, i) => (
|
|
<ButtonWithOptions
|
|
key={page.id}
|
|
isDropAbove={i === dropIndex && i === 0}
|
|
isDropBelow={dropIndex !== null && i === dropIndex - 1}
|
|
>
|
|
<DropdownMenu.RadioItem
|
|
title={page.name || defaultPageName}
|
|
value={page.id}
|
|
key={page.id}
|
|
id={page.id}
|
|
asChild
|
|
onDragOver={handleDrag}
|
|
onDragStart={handleDragStart}
|
|
// onDrag={handleDrag}
|
|
onDrop={handleDrop}
|
|
draggable={true}
|
|
>
|
|
<PageButton>
|
|
<span id={page.id}>{page.name || defaultPageName}</span>
|
|
<DropdownMenu.ItemIndicator>
|
|
<SmallIcon>
|
|
<CheckIcon />
|
|
</SmallIcon>
|
|
</DropdownMenu.ItemIndicator>
|
|
</PageButton>
|
|
</DropdownMenu.RadioItem>
|
|
<PageOptionsDialog page={page} onClose={onClose} />
|
|
</ButtonWithOptions>
|
|
))}
|
|
</DropdownMenu.RadioGroup>
|
|
<Divider />
|
|
<DropdownMenu.Item onSelect={handleCreatePage} asChild>
|
|
<RowButton>
|
|
<span>
|
|
<FormattedMessage id="create.page" />
|
|
</span>
|
|
<SmallIcon>
|
|
<PlusIcon />
|
|
</SmallIcon>
|
|
</RowButton>
|
|
</DropdownMenu.Item>
|
|
</>
|
|
)
|
|
}
|
|
|
|
const ButtonWithOptions = styled('div', {
|
|
position: 'relative',
|
|
display: 'grid',
|
|
gridTemplateColumns: '1fr auto',
|
|
gridAutoFlow: 'column',
|
|
margin: 0,
|
|
|
|
'& > *[data-shy="true"]': {
|
|
opacity: 0,
|
|
},
|
|
|
|
'&:hover > *[data-shy="true"]': {
|
|
opacity: 1,
|
|
},
|
|
|
|
variants: {
|
|
isDropAbove: {
|
|
true: {
|
|
'&::after': {
|
|
content: '',
|
|
display: 'block',
|
|
position: 'absolute',
|
|
top: 0,
|
|
width: '100%',
|
|
height: '1px',
|
|
backgroundColor: '$selected',
|
|
zIndex: 999,
|
|
pointerEvents: 'none',
|
|
},
|
|
},
|
|
},
|
|
isDropBelow: {
|
|
true: {
|
|
'&::after': {
|
|
content: '',
|
|
display: 'block',
|
|
position: 'absolute',
|
|
width: '100%',
|
|
height: '1px',
|
|
top: '100%',
|
|
backgroundColor: '$selected',
|
|
zIndex: 999,
|
|
pointerEvents: 'none',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
export const PageButton = styled(RowButton, {
|
|
minWidth: 128,
|
|
})
|