380 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			380 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import React, { useState, useEffect, useRef, ReactNode } from 'react'
 | 
						|
import {
 | 
						|
  Share,
 | 
						|
  DownloadSimple,
 | 
						|
  Gear,
 | 
						|
  X,
 | 
						|
  GithubLogo,
 | 
						|
  SignOut,
 | 
						|
  ArrowSquareOut,
 | 
						|
  CloudArrowUp,
 | 
						|
  CaretDown,
 | 
						|
  User,
 | 
						|
  FilePlus
 | 
						|
} from 'phosphor-react'
 | 
						|
import Image from 'next/image'
 | 
						|
import {
 | 
						|
  DropdownMenu,
 | 
						|
  DropdownMenuTrigger,
 | 
						|
  DropdownMenuContent,
 | 
						|
  DropdownMenuItem,
 | 
						|
  DropdownMenuArrow,
 | 
						|
  DropdownMenuSeparator
 | 
						|
} from './DropdownMenu'
 | 
						|
import NewWindow from 'react-new-window'
 | 
						|
import { signOut, useSession } from 'next-auth/react'
 | 
						|
import { useSnapshot } from 'valtio'
 | 
						|
import toast from 'react-hot-toast'
 | 
						|
 | 
						|
import { syncToGist, updateEditorSettings, downloadAsZip } from '../state/actions'
 | 
						|
import state from '../state'
 | 
						|
import Box from './Box'
 | 
						|
import Button from './Button'
 | 
						|
import Container from './Container'
 | 
						|
import {
 | 
						|
  Dialog,
 | 
						|
  DialogTrigger,
 | 
						|
  DialogContent,
 | 
						|
  DialogTitle,
 | 
						|
  DialogDescription,
 | 
						|
  DialogClose
 | 
						|
} from './Dialog'
 | 
						|
import Flex from './Flex'
 | 
						|
import Stack from './Stack'
 | 
						|
import { Input, Label } from './Input'
 | 
						|
import Tooltip from './Tooltip'
 | 
						|
import { showAlert } from '../state/actions/showAlert'
 | 
						|
 | 
						|
const EditorNavigation = ({ renderNav }: { renderNav?: () => ReactNode }) => {
 | 
						|
  const snap = useSnapshot(state)
 | 
						|
  const [editorSettingsOpen, setEditorSettingsOpen] = useState(false)
 | 
						|
  const { data: session, status } = useSession()
 | 
						|
  const [popup, setPopUp] = useState(false)
 | 
						|
  const [editorSettings, setEditorSettings] = useState(snap.editorSettings)
 | 
						|
 | 
						|
  useEffect(() => {
 | 
						|
    if (session && session.user && popup) {
 | 
						|
      setPopUp(false)
 | 
						|
    }
 | 
						|
  }, [session, popup])
 | 
						|
 | 
						|
  const showNewGistAlert = () => {
 | 
						|
    showAlert('Are you sure?', {
 | 
						|
      body: (
 | 
						|
        <>
 | 
						|
          This action will create new <strong>public</strong> Github Gist from your current saved
 | 
						|
          files. You can delete gist anytime from your GitHub Gists page.
 | 
						|
        </>
 | 
						|
      ),
 | 
						|
      cancelText: 'Cancel',
 | 
						|
      confirmText: 'Create new Gist',
 | 
						|
      confirmPrefix: <FilePlus size="15px" />,
 | 
						|
      onConfirm: () => syncToGist(session, true)
 | 
						|
    })
 | 
						|
  }
 | 
						|
 | 
						|
  const scrollRef = useRef<HTMLDivElement>(null)
 | 
						|
  const containerRef = useRef<HTMLDivElement>(null)
 | 
						|
  return (
 | 
						|
    <Flex css={{ flexShrink: 0, gap: '$0' }}>
 | 
						|
      <Flex
 | 
						|
        id="kissa"
 | 
						|
        ref={scrollRef}
 | 
						|
        css={{
 | 
						|
          overflowX: 'scroll',
 | 
						|
          overflowY: 'hidden',
 | 
						|
          py: '$3',
 | 
						|
          pb: '$0',
 | 
						|
          flex: 1,
 | 
						|
          '&::-webkit-scrollbar': {
 | 
						|
            height: '0.3em',
 | 
						|
            background: 'rgba(0,0,0,.0)'
 | 
						|
          },
 | 
						|
          '&::-webkit-scrollbar-gutter': 'stable',
 | 
						|
          '&::-webkit-scrollbar-thumb': {
 | 
						|
            backgroundColor: 'rgba(0,0,0,.2)',
 | 
						|
            outline: '0px',
 | 
						|
            borderRadius: '9999px'
 | 
						|
          },
 | 
						|
          scrollbarColor: 'rgba(0,0,0,.2) rgba(0,0,0,0)',
 | 
						|
          scrollbarGutter: 'stable',
 | 
						|
          scrollbarWidth: 'thin',
 | 
						|
          '.dark &': {
 | 
						|
            '&::-webkit-scrollbar': {
 | 
						|
              background: 'rgba(0,0,0,.0)'
 | 
						|
            },
 | 
						|
            '&::-webkit-scrollbar-gutter': 'stable',
 | 
						|
            '&::-webkit-scrollbar-thumb': {
 | 
						|
              backgroundColor: 'rgba(255,255,255,.2)',
 | 
						|
              outline: '0px',
 | 
						|
              borderRadius: '9999px'
 | 
						|
            },
 | 
						|
            scrollbarColor: 'rgba(255,255,255,.2) rgba(0,0,0,0)',
 | 
						|
            scrollbarGutter: 'stable',
 | 
						|
            scrollbarWidth: 'thin'
 | 
						|
          }
 | 
						|
        }}
 | 
						|
        onWheelCapture={e => {
 | 
						|
          if (scrollRef.current) {
 | 
						|
            scrollRef.current.scrollLeft += e.deltaY
 | 
						|
          }
 | 
						|
        }}
 | 
						|
      >
 | 
						|
        <Container css={{ flex: 1 }} ref={containerRef}>
 | 
						|
          {renderNav?.()}
 | 
						|
        </Container>
 | 
						|
      </Flex>
 | 
						|
      <Flex
 | 
						|
        css={{
 | 
						|
          py: '$3',
 | 
						|
          backgroundColor: '$mauve2',
 | 
						|
          zIndex: 1
 | 
						|
        }}
 | 
						|
      >
 | 
						|
        <Container css={{ width: 'unset', display: 'flex', alignItems: 'center' }}>
 | 
						|
          {status === 'authenticated' ? (
 | 
						|
            <DropdownMenu>
 | 
						|
              <DropdownMenuTrigger asChild>
 | 
						|
                <Box
 | 
						|
                  css={{
 | 
						|
                    display: 'flex',
 | 
						|
                    borderRadius: '$full',
 | 
						|
                    overflow: 'hidden',
 | 
						|
                    width: '$6',
 | 
						|
                    height: '$6',
 | 
						|
                    boxShadow: '0px 0px 0px 1px $colors$mauve11',
 | 
						|
                    position: 'relative',
 | 
						|
                    mr: '$3',
 | 
						|
                    '@hover': {
 | 
						|
                      '&:hover': {
 | 
						|
                        cursor: 'pointer',
 | 
						|
                        boxShadow: '0px 0px 0px 1px $colors$mauve12'
 | 
						|
                      }
 | 
						|
                    }
 | 
						|
                  }}
 | 
						|
                >
 | 
						|
                  <Image
 | 
						|
                    src={session?.user?.image || ''}
 | 
						|
                    width="30px"
 | 
						|
                    height="30px"
 | 
						|
                    objectFit="cover"
 | 
						|
                    alt="User avatar"
 | 
						|
                  />
 | 
						|
                </Box>
 | 
						|
              </DropdownMenuTrigger>
 | 
						|
              <DropdownMenuContent>
 | 
						|
                <DropdownMenuItem disabled onClick={() => signOut()}>
 | 
						|
                  <User size="16px" /> {session?.user?.username} ({session?.user.name})
 | 
						|
                </DropdownMenuItem>
 | 
						|
                <DropdownMenuItem
 | 
						|
                  onClick={() => window.open(`http://gist.github.com/${session?.user.username}`)}
 | 
						|
                >
 | 
						|
                  <ArrowSquareOut size="16px" />
 | 
						|
                  Go to your Gist
 | 
						|
                </DropdownMenuItem>
 | 
						|
                <DropdownMenuSeparator />
 | 
						|
                <DropdownMenuItem onClick={() => signOut({ callbackUrl: '/' })}>
 | 
						|
                  <SignOut size="16px" /> Log out
 | 
						|
                </DropdownMenuItem>
 | 
						|
 | 
						|
                <DropdownMenuArrow offset={10} />
 | 
						|
              </DropdownMenuContent>
 | 
						|
            </DropdownMenu>
 | 
						|
          ) : (
 | 
						|
            <Button outline size="sm" css={{ mr: '$3' }} onClick={() => setPopUp(true)}>
 | 
						|
              <GithubLogo size="16px" /> Login
 | 
						|
            </Button>
 | 
						|
          )}
 | 
						|
 | 
						|
          <Stack
 | 
						|
            css={{
 | 
						|
              display: 'inline-flex',
 | 
						|
              marginLeft: 'auto',
 | 
						|
              flexShrink: 0,
 | 
						|
              gap: '$0',
 | 
						|
              borderRadius: '$sm',
 | 
						|
              boxShadow: 'inset 0px 0px 0px 1px $colors$mauve10',
 | 
						|
              zIndex: 9,
 | 
						|
              position: 'relative',
 | 
						|
              button: {
 | 
						|
                borderRadius: 0,
 | 
						|
                px: '$2',
 | 
						|
                alignSelf: 'flex-start',
 | 
						|
                boxShadow: 'none'
 | 
						|
              },
 | 
						|
              'button:not(:first-child):not(:last-child)': {
 | 
						|
                borderRight: 0,
 | 
						|
                borderLeft: 0
 | 
						|
              },
 | 
						|
              'button:first-child': {
 | 
						|
                borderTopLeftRadius: '$sm',
 | 
						|
                borderBottomLeftRadius: '$sm'
 | 
						|
              },
 | 
						|
              'button:last-child': {
 | 
						|
                borderTopRightRadius: '$sm',
 | 
						|
                borderBottomRightRadius: '$sm',
 | 
						|
                boxShadow: 'inset 0px 0px 0px 1px $colors$mauve10',
 | 
						|
                '&:hover': {
 | 
						|
                  boxShadow: 'inset 0px 0px 0px 1px $colors$mauve12'
 | 
						|
                }
 | 
						|
              }
 | 
						|
            }}
 | 
						|
          >
 | 
						|
            <Tooltip content="Download as ZIP">
 | 
						|
              <Button
 | 
						|
                isLoading={snap.zipLoading}
 | 
						|
                onClick={downloadAsZip}
 | 
						|
                outline
 | 
						|
                size="sm"
 | 
						|
                css={{ alignItems: 'center' }}
 | 
						|
              >
 | 
						|
                <DownloadSimple size="16px" />
 | 
						|
              </Button>
 | 
						|
            </Tooltip>
 | 
						|
            <Tooltip content="Copy share link to clipboard">
 | 
						|
              <Button
 | 
						|
                outline
 | 
						|
                size="sm"
 | 
						|
                css={{ alignItems: 'center' }}
 | 
						|
                onClick={() => {
 | 
						|
                  navigator.clipboard.writeText(`${window.location.origin}/develop/${snap.gistId}`)
 | 
						|
                  toast.success('Copied share link to clipboard!')
 | 
						|
                }}
 | 
						|
              >
 | 
						|
                <Share size="16px" />
 | 
						|
              </Button>
 | 
						|
            </Tooltip>
 | 
						|
            <Tooltip
 | 
						|
              content={
 | 
						|
                session && session.user
 | 
						|
                  ? snap.gistOwner === session?.user.username
 | 
						|
                    ? 'Sync to Gist'
 | 
						|
                    : 'Create as a new Gist'
 | 
						|
                  : 'You need to be logged in to sync with Gist'
 | 
						|
              }
 | 
						|
            >
 | 
						|
              <Button
 | 
						|
                outline
 | 
						|
                size="sm"
 | 
						|
                isDisabled={!session || !session.user}
 | 
						|
                isLoading={snap.gistLoading}
 | 
						|
                css={{ alignItems: 'center' }}
 | 
						|
                onClick={() => {
 | 
						|
                  if (!session || !session.user) {
 | 
						|
                    return
 | 
						|
                  }
 | 
						|
                  if (snap.gistOwner === session?.user.username) {
 | 
						|
                    syncToGist(session)
 | 
						|
                  } else {
 | 
						|
                    showNewGistAlert()
 | 
						|
                  }
 | 
						|
                }}
 | 
						|
              >
 | 
						|
                {snap.gistOwner === session?.user.username ? (
 | 
						|
                  <CloudArrowUp size="16px" />
 | 
						|
                ) : (
 | 
						|
                  <FilePlus size="16px" />
 | 
						|
                )}
 | 
						|
              </Button>
 | 
						|
            </Tooltip>
 | 
						|
 | 
						|
            <DropdownMenu>
 | 
						|
              <DropdownMenuTrigger asChild>
 | 
						|
                <Button outline size="sm">
 | 
						|
                  <CaretDown size="16px" />
 | 
						|
                </Button>
 | 
						|
              </DropdownMenuTrigger>
 | 
						|
              <DropdownMenuContent>
 | 
						|
                <DropdownMenuItem disabled={snap.zipLoading} onClick={downloadAsZip}>
 | 
						|
                  <DownloadSimple size="16px" /> Download as ZIP
 | 
						|
                </DropdownMenuItem>
 | 
						|
                <DropdownMenuItem
 | 
						|
                  onClick={() => {
 | 
						|
                    navigator.clipboard.writeText(
 | 
						|
                      `${window.location.origin}/develop/${snap.gistId}`
 | 
						|
                    )
 | 
						|
                    toast.success('Copied share link to clipboard!')
 | 
						|
                  }}
 | 
						|
                >
 | 
						|
                  <Share size="16px" />
 | 
						|
                  Copy share link to clipboard
 | 
						|
                </DropdownMenuItem>
 | 
						|
                <DropdownMenuItem
 | 
						|
                  disabled={session?.user.username !== snap.gistOwner || !snap.gistId}
 | 
						|
                  onClick={() => {
 | 
						|
                    syncToGist(session)
 | 
						|
                  }}
 | 
						|
                >
 | 
						|
                  <CloudArrowUp size="16px" /> Push to Gist
 | 
						|
                </DropdownMenuItem>
 | 
						|
                <DropdownMenuSeparator />
 | 
						|
                <DropdownMenuItem
 | 
						|
                  disabled={status !== 'authenticated'}
 | 
						|
                  onClick={() => {
 | 
						|
                    showNewGistAlert()
 | 
						|
                  }}
 | 
						|
                >
 | 
						|
                  <FilePlus size="16px" /> Create as a new Gist
 | 
						|
                </DropdownMenuItem>
 | 
						|
 | 
						|
                <DropdownMenuItem onClick={() => setEditorSettingsOpen(true)}>
 | 
						|
                  <Gear size="16px" /> Editor Settings
 | 
						|
                </DropdownMenuItem>
 | 
						|
 | 
						|
                <DropdownMenuArrow offset={10} />
 | 
						|
              </DropdownMenuContent>
 | 
						|
            </DropdownMenu>
 | 
						|
          </Stack>
 | 
						|
 | 
						|
          {popup && !session ? <NewWindow center="parent" url="/sign-in" /> : null}
 | 
						|
        </Container>
 | 
						|
      </Flex>
 | 
						|
 | 
						|
      <Dialog open={editorSettingsOpen} onOpenChange={setEditorSettingsOpen}>
 | 
						|
        <DialogTrigger asChild>
 | 
						|
          {/* <Button outline size="sm" css={{ alignItems: "center" }}>
 | 
						|
                  <Gear size="16px" />
 | 
						|
                </Button> */}
 | 
						|
        </DialogTrigger>
 | 
						|
        <DialogContent>
 | 
						|
          <DialogTitle>Editor settings</DialogTitle>
 | 
						|
          <DialogDescription>
 | 
						|
            <Label>Tab size</Label>
 | 
						|
            <Input
 | 
						|
              type="number"
 | 
						|
              min="1"
 | 
						|
              value={editorSettings.tabSize}
 | 
						|
              onChange={e =>
 | 
						|
                setEditorSettings(curr => ({
 | 
						|
                  ...curr,
 | 
						|
                  tabSize: Number(e.target.value)
 | 
						|
                }))
 | 
						|
              }
 | 
						|
            />
 | 
						|
          </DialogDescription>
 | 
						|
 | 
						|
          <Flex css={{ marginTop: 25, justifyContent: 'flex-end', gap: '$3' }}>
 | 
						|
            <DialogClose asChild>
 | 
						|
              <Button outline onClick={() => updateEditorSettings(editorSettings)}>
 | 
						|
                Cancel
 | 
						|
              </Button>
 | 
						|
            </DialogClose>
 | 
						|
            <DialogClose asChild>
 | 
						|
              <Button variant="primary" onClick={() => updateEditorSettings(editorSettings)}>
 | 
						|
                Save changes
 | 
						|
              </Button>
 | 
						|
            </DialogClose>
 | 
						|
          </Flex>
 | 
						|
          <DialogClose asChild>
 | 
						|
            <Box css={{ position: 'absolute', top: '$3', right: '$3' }}>
 | 
						|
              <X size="20px" />
 | 
						|
            </Box>
 | 
						|
          </DialogClose>
 | 
						|
        </DialogContent>
 | 
						|
      </Dialog>
 | 
						|
    </Flex>
 | 
						|
  )
 | 
						|
}
 | 
						|
 | 
						|
export default EditorNavigation
 |