json / src /containers /Editor /BottomBar.tsx
xinnni's picture
Upload 146 files
f909d7c verified
raw
history blame
8.16 kB
import React from "react";
import Head from "next/head";
import { useRouter } from "next/router";
import { Flex, Popover, Text } from "@mantine/core";
import styled from "styled-components";
import toast from "react-hot-toast";
import {
AiOutlineCloudSync,
AiOutlineCloudUpload,
AiOutlineLink,
AiOutlineLock,
AiOutlineUnlock,
} from "react-icons/ai";
import { BiSolidDockLeft } from "react-icons/bi";
import { MdOutlineCheckCircleOutline } from "react-icons/md";
import { TbTransform } from "react-icons/tb";
import { VscError, VscFeedback, VscSourceControl, VscSync, VscSyncIgnored } from "react-icons/vsc";
import { documentSvc } from "src/services/document.service";
import useConfig from "src/store/useConfig";
import useFile from "src/store/useFile";
import useGraph from "src/store/useGraph";
import useModal from "src/store/useModal";
import useUser from "src/store/useUser";
const StyledBottomBar = styled.div`
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
border-top: 1px solid ${({ theme }) => theme.BACKGROUND_MODIFIER_ACCENT};
background: ${({ theme }) => theme.TOOLBAR_BG};
max-height: 27px;
height: 27px;
z-index: 35;
padding-right: 6px;
@media screen and (max-width: 320px) {
display: none;
}
`;
const StyledLeft = styled.div`
display: flex;
align-items: center;
justify-content: left;
gap: 4px;
padding-left: 8px;
@media screen and (max-width: 480px) {
display: none;
}
`;
const StyledRight = styled.div`
display: flex;
align-items: center;
justify-content: right;
gap: 4px;
`;
const StyledBottomBarItem = styled.button<{ $bg?: string }>`
display: flex;
align-items: center;
gap: 4px;
width: fit-content;
margin: 0;
height: 28px;
padding: 4px;
font-size: 12px;
font-weight: 400;
color: ${({ theme }) => theme.INTERACTIVE_NORMAL};
background: ${({ $bg }) => $bg};
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
&:hover:not(&:disabled) {
background-image: linear-gradient(rgba(0, 0, 0, 0.1) 0 0);
color: ${({ theme }) => theme.INTERACTIVE_HOVER};
}
&:disabled {
opacity: 0.6;
cursor: default;
}
`;
export const BottomBar = () => {
const { query, replace } = useRouter();
const data = useFile(state => state.fileData);
const user = useUser(state => state.user);
const toggleLiveTransform = useConfig(state => state.toggleLiveTransform);
const liveTransformEnabled = useConfig(state => state.liveTransformEnabled);
const hasChanges = useFile(state => state.hasChanges);
const error = useFile(state => state.error);
const getContents = useFile(state => state.getContents);
const setContents = useFile(state => state.setContents);
const nodeCount = useGraph(state => state.nodes.length);
const fileName = useFile(state => state.fileData?.name);
const toggleFullscreen = useGraph(state => state.toggleFullscreen);
const fullscreen = useGraph(state => state.fullscreen);
const setVisible = useModal(state => state.setVisible);
const setHasChanges = useFile(state => state.setHasChanges);
const getFormat = useFile(state => state.getFormat);
const [isPrivate, setIsPrivate] = React.useState(false);
const [isUpdating, setIsUpdating] = React.useState(false);
const toggleEditor = () => toggleFullscreen(!fullscreen);
React.useEffect(() => {
setIsPrivate(data?.private ?? true);
}, [data]);
const handleSaveJson = React.useCallback(async () => {
if (!user) return setVisible("login")(true);
if (
hasChanges &&
!error &&
(typeof query.json === "string" || typeof query.json === "undefined")
) {
try {
setIsUpdating(true);
toast.loading("Saving document...", { id: "fileSave" });
const { data, error } = await documentSvc.upsert({
id: query?.json,
contents: getContents(),
format: getFormat(),
});
if (error) throw error;
if (data) replace({ query: { json: data } });
toast.success("Document saved to cloud", { id: "fileSave" });
setHasChanges(false);
} catch (error: any) {
toast.error(error.message, { id: "fileSave" });
} finally {
setIsUpdating(false);
}
}
}, [
error,
getContents,
getFormat,
hasChanges,
query.json,
replace,
setHasChanges,
setVisible,
user,
]);
const setPrivate = async () => {
try {
if (!query.json) return handleSaveJson();
setIsUpdating(true);
const { data: updatedJsonData, error } = await documentSvc.update(query.json as string, {
private: !isPrivate,
});
if (error) return toast.error(error.message);
if (updatedJsonData[0]) {
setIsPrivate(updatedJsonData[0].private);
toast.success(`Document set to ${isPrivate ? "public" : "private"}.`);
} else throw error;
} catch (error) {
console.error(error);
} finally {
setIsUpdating(false);
}
};
return (
<StyledBottomBar>
{data?.name && (
<Head>
<title>{data.name} | JSON Crack</title>
</Head>
)}
<StyledLeft>
<StyledBottomBarItem onClick={toggleEditor}>
<BiSolidDockLeft />
</StyledBottomBarItem>
{fileName && (
<StyledBottomBarItem onClick={() => setVisible("cloud")(true)}>
<VscSourceControl />
{fileName}
</StyledBottomBarItem>
)}
<StyledBottomBarItem>
{error ? (
<Popover width="auto" shadow="md" position="top" withArrow>
<Popover.Target>
<Flex align="center" gap={2}>
<VscError color="red" size={16} />
<Text c="red" fw={500} fz="xs">
Invalid
</Text>
</Flex>
</Popover.Target>
<Popover.Dropdown
style={{
pointerEvents: "none",
}}
>
<Text size="xs">{error}</Text>
</Popover.Dropdown>
</Popover>
) : (
<Flex align="center" gap={2}>
<MdOutlineCheckCircleOutline />
<Text size="xs">Valid</Text>
</Flex>
)}
</StyledBottomBarItem>
{(data?.owner_email === user?.email || (!data && user)) && (
<StyledBottomBarItem onClick={handleSaveJson} disabled={isUpdating || error}>
{hasChanges || !user ? <AiOutlineCloudUpload /> : <AiOutlineCloudSync />}
{hasChanges || !user ? (query?.json ? "Unsaved Changes" : "Save to Cloud") : "Saved"}
</StyledBottomBarItem>
)}
{data?.owner_email === user?.email && (
<StyledBottomBarItem onClick={setPrivate} disabled={isUpdating}>
{isPrivate ? <AiOutlineLock /> : <AiOutlineUnlock />}
{isPrivate ? "Private" : "Public"}
</StyledBottomBarItem>
)}
<StyledBottomBarItem
onClick={() => setVisible("share")(true)}
disabled={isPrivate || !data}
>
<AiOutlineLink />
Share
</StyledBottomBarItem>
{liveTransformEnabled ? (
<StyledBottomBarItem onClick={() => toggleLiveTransform(false)}>
<VscSync />
<Text fz="xs">Live Transform</Text>
</StyledBottomBarItem>
) : (
<StyledBottomBarItem onClick={() => toggleLiveTransform(true)}>
<VscSyncIgnored />
<Text fz="xs">Manual Transform</Text>
</StyledBottomBarItem>
)}
{!liveTransformEnabled && (
<StyledBottomBarItem onClick={() => setContents({})}>
<TbTransform />
Transform
</StyledBottomBarItem>
)}
</StyledLeft>
<StyledRight>
<StyledBottomBarItem>Nodes: {nodeCount}</StyledBottomBarItem>
<StyledBottomBarItem onClick={() => setVisible("review")(true)}>
<VscFeedback />
Feedback
</StyledBottomBarItem>
</StyledRight>
</StyledBottomBar>
);
};