index.tsx 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. import React from "react";
  2. import toast from "react-hot-toast";
  3. import Link from "next/link";
  4. import styled from "styled-components";
  5. import { CanvasDirection } from "reaflow";
  6. import { TiFlowMerge } from "react-icons/ti";
  7. import { CgArrowsMergeAltH, CgArrowsShrinkH } from "react-icons/cg";
  8. import {
  9. AiOutlineDelete,
  10. AiFillGithub,
  11. AiOutlineTwitter,
  12. AiOutlineSave,
  13. AiOutlineFileAdd,
  14. AiOutlineLink,
  15. } from "react-icons/ai";
  16. import { Tooltip } from "src/components/Tooltip";
  17. import { useRouter } from "next/router";
  18. import { ImportModal } from "src/containers/Modals/ImportModal";
  19. import { ClearModal } from "src/containers/Modals/ClearModal";
  20. import { ShareModal } from "src/containers/Modals/ShareModal";
  21. import useConfig from "src/hooks/store/useConfig";
  22. import { getNextLayout } from "src/containers/Editor/LiveEditor/helpers";
  23. import { HiHeart } from "react-icons/hi";
  24. import shallow from "zustand/shallow";
  25. import { IoAlertCircleSharp } from "react-icons/io5";
  26. const StyledSidebar = styled.div`
  27. display: flex;
  28. justify-content: space-between;
  29. flex-direction: column;
  30. align-items: center;
  31. width: fit-content;
  32. background: ${({ theme }) => theme.BACKGROUND_TERTIARY};
  33. padding: 4px;
  34. border-right: 1px solid ${({ theme }) => theme.BACKGROUND_MODIFIER_ACCENT};
  35. `;
  36. const StyledElement = styled.div<{ beta?: boolean }>`
  37. position: relative;
  38. display: flex;
  39. justify-content: center;
  40. text-align: center;
  41. font-size: 26px;
  42. font-weight: 600;
  43. width: 100%;
  44. color: ${({ theme }) => theme.INTERACTIVE_NORMAL};
  45. cursor: pointer;
  46. svg {
  47. padding: 8px;
  48. vertical-align: middle;
  49. }
  50. a {
  51. display: flex;
  52. }
  53. &:hover :is(a, svg) {
  54. color: ${({ theme }) => theme.INTERACTIVE_HOVER};
  55. }
  56. `;
  57. const StyledText = styled.span<{ secondary?: boolean }>`
  58. color: ${({ theme, secondary }) =>
  59. secondary ? theme.INTERACTIVE_NORMAL : theme.ORANGE};
  60. `;
  61. const StyledFlowIcon = styled(TiFlowMerge)<{ rotate: number }>`
  62. transform: rotate(${({ rotate }) => `${rotate}deg`});
  63. `;
  64. const StyledTopWrapper = styled.nav`
  65. display: flex;
  66. justify-content: space-between;
  67. flex-direction: column;
  68. align-items: center;
  69. width: 100%;
  70. & > div:nth-child(n + 1) {
  71. border-bottom: 1px solid ${({ theme }) => theme.BACKGROUND_MODIFIER_ACCENT};
  72. }
  73. `;
  74. const StyledBottomWrapper = styled.nav`
  75. display: flex;
  76. justify-content: space-between;
  77. flex-direction: column;
  78. align-items: center;
  79. width: 100%;
  80. & > div,
  81. a:nth-child(0) {
  82. border-top: 1px solid ${({ theme }) => theme.BACKGROUND_MODIFIER_ACCENT};
  83. }
  84. `;
  85. const StyledLogo = styled.div`
  86. color: ${({ theme }) => theme.FULL_WHITE};
  87. `;
  88. function rotateLayout(layout: CanvasDirection) {
  89. if (layout === "LEFT") return 90;
  90. if (layout === "UP") return 180;
  91. if (layout === "RIGHT") return 270;
  92. return 360;
  93. }
  94. export const Sidebar: React.FC = () => {
  95. const getJson = useConfig((state) => state.getJson);
  96. const setConfig = useConfig((state) => state.setConfig);
  97. const [uploadVisible, setUploadVisible] = React.useState(false);
  98. const [clearVisible, setClearVisible] = React.useState(false);
  99. const [shareVisible, setShareVisible] = React.useState(false);
  100. const { push } = useRouter();
  101. const [expand, layout] = useConfig(
  102. (state) => [state.expand, state.layout],
  103. shallow
  104. );
  105. const handleSave = () => {
  106. const a = document.createElement("a");
  107. const file = new Blob([getJson()], { type: "text/plain" });
  108. a.href = window.URL.createObjectURL(file);
  109. a.download = "jsonvisio.json";
  110. a.click();
  111. };
  112. const toggleExpandCollapse = () => {
  113. setConfig("expand", !expand);
  114. toast(`${expand ? "Collapsed" : "Expanded"} nodes.`);
  115. };
  116. const toggleLayout = () => {
  117. const nextLayout = getNextLayout(layout);
  118. setConfig("layout", nextLayout);
  119. };
  120. return (
  121. <StyledSidebar>
  122. <StyledTopWrapper>
  123. <Link passHref href="/">
  124. <StyledElement onClick={() => push("/")}>
  125. <StyledLogo>
  126. <StyledText>J</StyledText>
  127. <StyledText secondary>V</StyledText>
  128. </StyledLogo>
  129. </StyledElement>
  130. </Link>
  131. <Tooltip title="Import File">
  132. <StyledElement onClick={() => setUploadVisible(true)}>
  133. <AiOutlineFileAdd />
  134. </StyledElement>
  135. </Tooltip>
  136. <Tooltip title="Rotate Layout">
  137. <StyledElement onClick={toggleLayout}>
  138. <StyledFlowIcon rotate={rotateLayout(layout)} />
  139. </StyledElement>
  140. </Tooltip>
  141. <Tooltip title={expand ? "Shrink Nodes" : "Expand Nodes"}>
  142. <StyledElement
  143. title="Toggle Expand/Collapse"
  144. onClick={toggleExpandCollapse}
  145. >
  146. {expand ? <CgArrowsMergeAltH /> : <CgArrowsShrinkH />}
  147. </StyledElement>
  148. </Tooltip>
  149. <Tooltip title="Save JSON">
  150. <StyledElement onClick={handleSave}>
  151. <AiOutlineSave />
  152. </StyledElement>
  153. </Tooltip>
  154. <Tooltip title="Clear JSON">
  155. <StyledElement onClick={() => setClearVisible(true)}>
  156. <AiOutlineDelete />
  157. </StyledElement>
  158. </Tooltip>
  159. <Tooltip title="Share">
  160. <StyledElement onClick={() => setShareVisible(true)}>
  161. <AiOutlineLink />
  162. </StyledElement>
  163. </Tooltip>
  164. </StyledTopWrapper>
  165. <StyledBottomWrapper>
  166. <StyledElement>
  167. <Link href="https://twitter.com/aykutsarach">
  168. <a aria-label="Twitter" rel="me" target="_blank">
  169. <AiOutlineTwitter />
  170. </a>
  171. </Link>
  172. </StyledElement>
  173. <StyledElement>
  174. <Link href="https://github.com/AykutSarac/jsonvisio.com">
  175. <a aria-label="GitHub" rel="me" target="_blank">
  176. <AiFillGithub />
  177. </a>
  178. </Link>
  179. </StyledElement>
  180. <StyledElement>
  181. <Link href="https://github.com/sponsors/AykutSarac">
  182. <a aria-label="GitHub Sponsors" rel="me" target="_blank">
  183. <HiHeart />
  184. </a>
  185. </Link>
  186. </StyledElement>
  187. </StyledBottomWrapper>
  188. <ImportModal visible={uploadVisible} setVisible={setUploadVisible} />
  189. <ClearModal visible={clearVisible} setVisible={setClearVisible} />
  190. <ShareModal visible={shareVisible} setVisible={setShareVisible} />
  191. </StyledSidebar>
  192. );
  193. };