419 lines
13 KiB
TypeScript
419 lines
13 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;
|