index.tsx 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. import React from "react";
  2. import Link from "next/link";
  3. import styled from "styled-components";
  4. import { useLocalStorage } from "usehooks-ts";
  5. import { FaFileImport } from "react-icons/fa";
  6. import { MdUnfoldMore, MdUnfoldLess } from "react-icons/md";
  7. import {
  8. AiFillHome,
  9. AiOutlineClear,
  10. AiFillGithub,
  11. AiOutlineTwitter,
  12. AiFillControl,
  13. AiOutlineControl,
  14. } from "react-icons/ai";
  15. import {
  16. CgArrowLongDownE,
  17. CgArrowLongLeftE,
  18. CgArrowLongRightE,
  19. CgArrowLongUpE,
  20. } from "react-icons/cg";
  21. import { getNextLayout } from "src/containers/LiveEditor/helpers";
  22. import { StorageConfig } from "src/typings/global";
  23. import { CanvasDirection } from "reaflow";
  24. import { useLoading } from "src/hooks/useLoading";
  25. const StyledSidebar = styled.div`
  26. display: flex;
  27. justify-content: space-between;
  28. flex-direction: column;
  29. align-items: center;
  30. width: 60px;
  31. background: #2f3136;
  32. padding: 8px;
  33. border-right: 1px solid ${({ theme }) => theme.SILVER_DARK};
  34. `;
  35. const StyledElement = styled.div<{ disabled?: boolean }>`
  36. text-align: center;
  37. font-size: 32px;
  38. font-weight: 700;
  39. width: 100%;
  40. color: ${({ theme, disabled }) =>
  41. disabled ? theme.SILVER_DARK : theme.SILVER};
  42. cursor: pointer;
  43. pointer-events: ${({ disabled }) => disabled && "none"};
  44. a {
  45. text-align: center;
  46. width: 100%;
  47. }
  48. svg {
  49. vertical-align: middle;
  50. }
  51. `;
  52. const StyledText = styled.span<{ secondary?: boolean }>`
  53. color: ${({ theme, secondary }) =>
  54. secondary ? theme.FULL_WHITE : theme.ORANGE};
  55. `;
  56. const StyledTopWrapper = styled.nav`
  57. display: flex;
  58. justify-content: space-between;
  59. flex-direction: column;
  60. align-items: center;
  61. width: 100%;
  62. & > div,
  63. a {
  64. border-bottom: 1px solid ${({ theme }) => theme.SILVER_DARK};
  65. }
  66. `;
  67. const StyledBottomWrapper = styled.nav`
  68. display: flex;
  69. justify-content: space-between;
  70. flex-direction: column;
  71. align-items: center;
  72. width: 100%;
  73. & > div,
  74. a {
  75. border-top: 1px solid ${({ theme }) => theme.SILVER_DARK};
  76. }
  77. `;
  78. const StyledLogo = styled.div`
  79. color: ${({ theme }) => theme.FULL_WHITE};
  80. `;
  81. const StyledImportFile = styled.label`
  82. cursor: pointer;
  83. input[type="file"] {
  84. display: none;
  85. }
  86. `;
  87. function getLayoutIcon(layout: CanvasDirection) {
  88. if (layout === "LEFT") return <CgArrowLongRightE />;
  89. if (layout === "UP") return <CgArrowLongDownE />;
  90. if (layout === "RIGHT") return <CgArrowLongLeftE />;
  91. return <CgArrowLongUpE />;
  92. }
  93. export const Sidebar: React.FC<{
  94. setJson: React.Dispatch<React.SetStateAction<string>>;
  95. }> = ({ setJson }) => {
  96. const pageLoaded = useLoading();
  97. const [jsonFile, setJsonFile] = React.useState<File | null>(null);
  98. const [config, setConfig] = useLocalStorage<StorageConfig>("config", {
  99. layout: "LEFT",
  100. expand: true,
  101. controls: true,
  102. });
  103. const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  104. if (e.target.files) setJsonFile(e.target.files?.item(0));
  105. };
  106. const toggle = (setting: "expand" | "controls") => {
  107. setConfig((c) => ({
  108. ...c,
  109. [setting]: !c[setting],
  110. }));
  111. };
  112. React.useEffect(() => {
  113. if (jsonFile) {
  114. const reader = new FileReader();
  115. reader.readAsText(jsonFile, "UTF-8");
  116. reader.onload = function (data) {
  117. setJson(data.target?.result as string);
  118. };
  119. }
  120. }, [jsonFile, setJson]);
  121. if (pageLoaded)
  122. return (
  123. <StyledSidebar>
  124. <StyledTopWrapper>
  125. <Link passHref href="/">
  126. <StyledElement as="a">
  127. <StyledLogo>
  128. <StyledText>J</StyledText>
  129. <StyledText secondary>V</StyledText>
  130. </StyledLogo>
  131. </StyledElement>
  132. </Link>
  133. <StyledElement title="Home">
  134. <Link href="/">
  135. <a>
  136. <AiFillHome />
  137. </a>
  138. </Link>
  139. </StyledElement>
  140. <StyledElement
  141. as="a"
  142. onClick={() => {
  143. setJson("[]");
  144. localStorage.removeItem("json");
  145. }}
  146. title="Clear JSON"
  147. >
  148. <AiOutlineClear />
  149. </StyledElement>
  150. <StyledElement
  151. as="a"
  152. onClick={() =>
  153. setConfig((c) => ({
  154. ...c,
  155. layout: getNextLayout(c.layout),
  156. }))
  157. }
  158. title="Change Layout"
  159. >
  160. {getLayoutIcon(config.layout)}
  161. </StyledElement>
  162. <StyledElement
  163. title="Toggle Controls"
  164. as="a"
  165. onClick={() => toggle("controls")}
  166. >
  167. {config.controls ? <AiFillControl /> : <AiOutlineControl />}
  168. </StyledElement>
  169. <StyledElement
  170. as="a"
  171. title="Toggle Expand/Collapse"
  172. onClick={() => toggle("expand")}
  173. >
  174. {config.expand ? <MdUnfoldMore /> : <MdUnfoldLess />}
  175. </StyledElement>
  176. <StyledElement as="a" title="Import JSON File">
  177. <StyledImportFile>
  178. <input
  179. key={jsonFile?.name}
  180. onChange={handleFileChange}
  181. type="file"
  182. accept="application/JSON"
  183. />
  184. <FaFileImport />
  185. </StyledImportFile>
  186. </StyledElement>
  187. </StyledTopWrapper>
  188. <StyledBottomWrapper>
  189. <StyledElement>
  190. <Link href="https://twitter.com/aykutsarach">
  191. <a rel="me" target="_blank">
  192. <AiOutlineTwitter />
  193. </a>
  194. </Link>
  195. </StyledElement>
  196. <StyledElement>
  197. <Link href="https://github.com/AykutSarac/jsonvisio.com">
  198. <a rel="me" target="_blank">
  199. <AiFillGithub />
  200. </a>
  201. </Link>
  202. </StyledElement>
  203. </StyledBottomWrapper>
  204. </StyledSidebar>
  205. );
  206. return null;
  207. };