| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477 | import * as _ContextMenu from '@radix-ui/react-context-menu'
import styled from 'styles'
import {
  IconWrapper,
  IconButton as _IconButton,
  RowButton,
} from 'components/shared'
import {
  commandKey,
  deepCompareArrays,
  getSelectedShapes,
  isMobile,
} from 'utils/utils'
import state, { useSelector } from 'state'
import {
  AlignType,
  DistributeType,
  MoveType,
  ShapeType,
  StretchType,
} from 'types'
import React, { useRef } from 'react'
import {
  ChevronRightIcon,
  AlignBottomIcon,
  AlignCenterHorizontallyIcon,
  AlignCenterVerticallyIcon,
  AlignLeftIcon,
  AlignRightIcon,
  AlignTopIcon,
  SpaceEvenlyHorizontallyIcon,
  SpaceEvenlyVerticallyIcon,
  StretchHorizontallyIcon,
  StretchVerticallyIcon,
} from '@radix-ui/react-icons'
function alignTop() {
  state.send('ALIGNED', { type: AlignType.Top })
}
function alignCenterVertical() {
  state.send('ALIGNED', { type: AlignType.CenterVertical })
}
function alignBottom() {
  state.send('ALIGNED', { type: AlignType.Bottom })
}
function stretchVertically() {
  state.send('STRETCHED', { type: StretchType.Vertical })
}
function distributeVertically() {
  state.send('DISTRIBUTED', { type: DistributeType.Vertical })
}
function alignLeft() {
  state.send('ALIGNED', { type: AlignType.Left })
}
function alignCenterHorizontal() {
  state.send('ALIGNED', { type: AlignType.CenterHorizontal })
}
function alignRight() {
  state.send('ALIGNED', { type: AlignType.Right })
}
function stretchHorizontally() {
  state.send('STRETCHED', { type: StretchType.Horizontal })
}
function distributeHorizontally() {
  state.send('DISTRIBUTED', { type: DistributeType.Horizontal })
}
export default function ContextMenu({
  children,
}: {
  children: React.ReactNode
}): JSX.Element {
  const selectedShapes = useSelector(
    (s) => getSelectedShapes(s.data),
    deepCompareArrays
  )
  const rContent = useRef<HTMLDivElement>(null)
  const hasGroupSelectd = selectedShapes.some((s) => s.type === ShapeType.Group)
  const hasTwoOrMore = selectedShapes.length > 1
  const hasThreeOrMore = selectedShapes.length > 2
  return (
    <_ContextMenu.Root>
      <_ContextMenu.Trigger>{children}</_ContextMenu.Trigger>
      <StyledContent ref={rContent} isMobile={isMobile()}>
        {selectedShapes.length ? (
          <>
            {/* <Button onSelect={() => state.send('COPIED')}>
              <span>Copy</span>
              <kbd>
                <span>{commandKey()}</span>
                <span>C</span>
              </kbd>
            </Button>
            <Button onSelect={() => state.send('CUT')}>
              <span>Cut</span>
              <kbd>
                <span>{commandKey()}</span>
                <span>X</span>
              </kbd>
            </Button>
             */}
            <Button onSelect={() => state.send('DUPLICATED')}>
              <span>Duplicate</span>
              <kbd>
                <span>{commandKey()}</span>
                <span>D</span>
              </kbd>
            </Button>
            <StyledDivider />
            {hasGroupSelectd ||
              (hasTwoOrMore && (
                <>
                  {hasGroupSelectd && (
                    <Button onSelect={() => state.send('UNGROUPED')}>
                      <span>Ungroup</span>
                      <kbd>
                        <span>{commandKey()}</span>
                        <span>⇧</span>
                        <span>G</span>
                      </kbd>
                    </Button>
                  )}
                  {hasTwoOrMore && (
                    <Button onSelect={() => state.send('GROUPED')}>
                      <span>Group</span>
                      <kbd>
                        <span>{commandKey()}</span>
                        <span>G</span>
                      </kbd>
                    </Button>
                  )}
                </>
              ))}
            <SubMenu label="Move">
              <Button
                onSelect={() =>
                  state.send('MOVED', {
                    type: MoveType.ToFront,
                  })
                }
              >
                <span>To Front</span>
                <kbd>
                  <span>{commandKey()}</span>
                  <span>⇧</span>
                  <span>]</span>
                </kbd>
              </Button>
              <Button
                onSelect={() =>
                  state.send('MOVED', {
                    type: MoveType.Forward,
                  })
                }
              >
                <span>Forward</span>
                <kbd>
                  <span>{commandKey()}</span>
                  <span>]</span>
                </kbd>
              </Button>
              <Button
                onSelect={() =>
                  state.send('MOVED', {
                    type: MoveType.Backward,
                  })
                }
              >
                <span>Backward</span>
                <kbd>
                  <span>{commandKey()}</span>
                  <span>[</span>
                </kbd>
              </Button>
              <Button
                onSelect={() =>
                  state.send('MOVED', {
                    type: MoveType.ToBack,
                  })
                }
              >
                <span>To Back</span>
                <kbd>
                  <span>{commandKey()}</span>
                  <span>⇧</span>
                  <span>[</span>
                </kbd>
              </Button>
            </SubMenu>
            {hasTwoOrMore && (
              <AlignDistributeSubMenu
                hasTwoOrMore={hasTwoOrMore}
                hasThreeOrMore={hasThreeOrMore}
              />
            )}
            <MoveToPageMenu />
            <Button onSelect={() => state.send('COPIED_TO_SVG')}>
              <span>Copy to SVG</span>
              <kbd>
                <span>{commandKey()}</span>
                <span>⇧</span>
                <span>C</span>
              </kbd>
            </Button>
            <StyledDivider />
            <Button onSelect={() => state.send('DELETED')}>
              <span>Delete</span>
              <kbd>
                <span>⌫</span>
              </kbd>
            </Button>
          </>
        ) : (
          <>
            <Button onSelect={() => state.send('UNDO')}>
              <span>Undo</span>
              <kbd>
                <span>{commandKey()}</span>
                <span>Z</span>
              </kbd>
            </Button>
            <Button onSelect={() => state.send('REDO')}>
              <span>Redo</span>
              <kbd>
                <span>{commandKey()}</span>
                <span>⇧</span>
                <span>Z</span>
              </kbd>
            </Button>
          </>
        )}
      </StyledContent>
    </_ContextMenu.Root>
  )
}
const StyledContent = styled(_ContextMenu.Content, {
  position: 'relative',
  backgroundColor: '$panel',
  borderRadius: '4px',
  overflow: 'hidden',
  pointerEvents: 'all',
  userSelect: 'none',
  zIndex: 200,
  padding: 3,
  boxShadow: '0px 2px 4px rgba(0,0,0,.2)',
  minWidth: 128,
  '& kbd': {
    marginLeft: '32px',
    fontSize: '$1',
    fontFamily: '$ui',
  },
  '& kbd > span': {
    display: 'inline-block',
    width: '12px',
  },
  variants: {
    isMobile: {
      true: {
        '& kbd': {
          display: 'none',
        },
      },
    },
  },
})
const StyledDivider = styled(_ContextMenu.Separator, {
  backgroundColor: '$hover',
  height: 1,
  margin: '3px -3px',
})
function Button({
  onSelect,
  children,
  disabled = false,
}: {
  onSelect: () => void
  disabled?: boolean
  children: React.ReactNode
}) {
  return (
    <_ContextMenu.Item
      as={RowButton}
      disabled={disabled}
      bp={{ '@initial': 'mobile', '@sm': 'small' }}
      onSelect={onSelect}
    >
      {children}
    </_ContextMenu.Item>
  )
}
function IconButton({
  onSelect,
  children,
  disabled = false,
}: {
  onSelect: () => void
  disabled?: boolean
  children: React.ReactNode
}) {
  return (
    <_ContextMenu.Item
      as={_IconButton}
      bp={{ '@initial': 'mobile', '@sm': 'small' }}
      disabled={disabled}
      onSelect={onSelect}
    >
      {children}
    </_ContextMenu.Item>
  )
}
function SubMenu({
  children,
  label,
}: {
  label: string
  children: React.ReactNode
}) {
  return (
    <_ContextMenu.Root>
      <_ContextMenu.TriggerItem
        as={RowButton}
        bp={{ '@initial': 'mobile', '@sm': 'small' }}
      >
        <span>{label}</span>
        <IconWrapper size="small">
          <ChevronRightIcon />
        </IconWrapper>
      </_ContextMenu.TriggerItem>
      <StyledContent sideOffset={2} alignOffset={-2} isMobile={isMobile()}>
        {children}
        <StyledArrow offset={13} />
      </StyledContent>
    </_ContextMenu.Root>
  )
}
function AlignDistributeSubMenu({
  hasThreeOrMore,
}: {
  hasTwoOrMore: boolean
  hasThreeOrMore: boolean
}) {
  return (
    <_ContextMenu.Root>
      <_ContextMenu.TriggerItem
        as={RowButton}
        bp={{ '@initial': 'mobile', '@sm': 'small' }}
      >
        <span>Align / Distribute</span>
        <IconWrapper size="small">
          <ChevronRightIcon />
        </IconWrapper>
      </_ContextMenu.TriggerItem>
      <StyledGrid
        sideOffset={2}
        alignOffset={-2}
        isMobile={isMobile()}
        selectedStyle={hasThreeOrMore ? 'threeOrMore' : 'twoOrMore'}
      >
        <IconButton onSelect={alignLeft}>
          <AlignLeftIcon />
        </IconButton>
        <IconButton onSelect={alignCenterHorizontal}>
          <AlignCenterHorizontallyIcon />
        </IconButton>
        <IconButton onSelect={alignRight}>
          <AlignRightIcon />
        </IconButton>
        <IconButton onSelect={stretchHorizontally}>
          <StretchHorizontallyIcon />
        </IconButton>
        {hasThreeOrMore && (
          <IconButton onSelect={distributeHorizontally}>
            <SpaceEvenlyHorizontallyIcon />
          </IconButton>
        )}
        <IconButton onSelect={alignTop}>
          <AlignTopIcon />
        </IconButton>
        <IconButton onSelect={alignCenterVertical}>
          <AlignCenterVerticallyIcon />
        </IconButton>
        <IconButton onSelect={alignBottom}>
          <AlignBottomIcon />
        </IconButton>
        <IconButton onSelect={stretchVertically}>
          <StretchVerticallyIcon />
        </IconButton>
        {hasThreeOrMore && (
          <IconButton onSelect={distributeVertically}>
            <SpaceEvenlyVerticallyIcon />
          </IconButton>
        )}
        <StyledArrow offset={13} />
      </StyledGrid>
    </_ContextMenu.Root>
  )
}
const StyledGrid = styled(StyledContent, {
  display: 'grid',
  variants: {
    selectedStyle: {
      threeOrMore: {
        gridTemplateColumns: 'repeat(5, auto)',
      },
      twoOrMore: {
        gridTemplateColumns: 'repeat(4, auto)',
      },
    },
  },
})
function MoveToPageMenu() {
  const documentPages = useSelector((s) => s.data.document.pages)
  const currentPageId = useSelector((s) => s.data.currentPageId)
  if (!documentPages[currentPageId]) return null
  const sorted = Object.values(documentPages)
    .sort((a, b) => a.childIndex - b.childIndex)
    .filter((a) => a.id !== currentPageId)
  if (sorted.length === 0) return null
  return (
    <_ContextMenu.Root>
      <_ContextMenu.TriggerItem
        as={RowButton}
        bp={{ '@initial': 'mobile', '@sm': 'small' }}
      >
        <span>Move To Page</span>
        <IconWrapper size="small">
          <ChevronRightIcon />
        </IconWrapper>
      </_ContextMenu.TriggerItem>
      <StyledContent sideOffset={2} alignOffset={-2} isMobile={isMobile()}>
        {sorted.map(({ id, name }) => (
          <Button
            key={id}
            disabled={id === currentPageId}
            onSelect={() => state.send('MOVED_TO_PAGE', { id })}
          >
            <span>{name}</span>
          </Button>
        ))}
        <StyledArrow offset={13} />
      </StyledContent>
    </_ContextMenu.Root>
  )
}
const StyledArrow = styled(_ContextMenu.Arrow, {
  fill: 'white',
})
 |