index.tsx 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. import React from "react";
  2. import { toBlob, toPng, toSvg } from "html-to-image";
  3. import { TwitterPicker } from "react-color";
  4. import { TwitterPickerStylesProps } from "react-color/lib/components/twitter/Twitter";
  5. import toast from "react-hot-toast";
  6. import { FiCopy, FiDownload } from "react-icons/fi";
  7. import { Button } from "src/components/Button";
  8. import { FileInput } from "src/components/FileInput";
  9. import { Modal, ModalProps } from "src/components/Modal";
  10. import styled from "styled-components";
  11. const ColorPickerStyles: Partial<TwitterPickerStylesProps> = {
  12. card: {
  13. background: "transparent",
  14. boxShadow: "none",
  15. },
  16. body: {
  17. padding: 0,
  18. },
  19. input: {
  20. background: "rgba(0, 0, 0, 0.2)",
  21. boxShadow: "none",
  22. textTransform: "none",
  23. whiteSpace: "nowrap",
  24. textOverflow: "ellipsis",
  25. },
  26. hash: {
  27. background: "rgba(180, 180, 180, 0.3)",
  28. },
  29. };
  30. const defaultColors = [
  31. "#B80000",
  32. "#DB3E00",
  33. "#FCCB00",
  34. "#008B02",
  35. "#006B76",
  36. "#1273DE",
  37. "#004DCF",
  38. "#5300EB",
  39. "#EB9694",
  40. "#FAD0C3",
  41. "#FEF3BD",
  42. "#C1E1C5",
  43. "#BEDADC",
  44. "#C4DEF6",
  45. "#BED3F3",
  46. "#D4C4FB",
  47. "transparent",
  48. ];
  49. function downloadURI(uri: string, name: string) {
  50. var link = document.createElement("a");
  51. link.download = name;
  52. link.href = uri;
  53. document.body.appendChild(link);
  54. link.click();
  55. document.body.removeChild(link);
  56. }
  57. const StyledContainer = styled.div`
  58. display: flex;
  59. flex-direction: column;
  60. gap: 16px;
  61. padding: 12px 0;
  62. border-top: 1px solid ${({ theme }) => theme.BACKGROUND_MODIFIER_ACCENT};
  63. font-size: 12px;
  64. line-height: 16px;
  65. font-weight: 600;
  66. text-transform: uppercase;
  67. color: ${({ theme }) => theme.INTERACTIVE_NORMAL};
  68. &:first-of-type {
  69. padding-top: 0;
  70. border: none;
  71. }
  72. `;
  73. const StyledColorWrapper = styled.div`
  74. display: flex;
  75. justify-content: space-between;
  76. `;
  77. const StyledColorIndicator = styled.div<{ color: string }>`
  78. flex: 1;
  79. width: 100%;
  80. height: auto;
  81. border-radius: 6px;
  82. background: ${({ color }) => color};
  83. border: 1px solid;
  84. border-color: rgba(0, 0, 0, 0.1);
  85. `;
  86. enum Extensions {
  87. svg,
  88. png,
  89. }
  90. export const DownloadModal: React.FC<ModalProps> = ({ visible, setVisible }) => {
  91. const [extension, setExtension] = React.useState(Extensions.svg);
  92. const [fileDetails, setFileDetails] = React.useState({
  93. filename: "jsoncrack.com",
  94. backgroundColor: "transparent",
  95. quality: 1,
  96. });
  97. const clipboardImage = async () => {
  98. try {
  99. toast.loading("Copying to clipboard...", { id: "toastClipboard" });
  100. const imageElement = document.querySelector("svg[id*='ref']") as HTMLElement;
  101. const blob = await toBlob(imageElement, {
  102. quality: fileDetails.quality,
  103. backgroundColor: fileDetails.backgroundColor,
  104. });
  105. if (!blob) return;
  106. navigator.clipboard?.write([
  107. new ClipboardItem({
  108. [blob.type]: blob,
  109. }),
  110. ]);
  111. toast.success("Copied to clipboard");
  112. } catch (error) {
  113. toast.error("Failed to copy to clipboard");
  114. } finally {
  115. toast.dismiss("toastClipboard");
  116. setVisible(false);
  117. }
  118. };
  119. const exportAsImage = async () => {
  120. try {
  121. toast.loading("Downloading...", { id: "toastDownload" });
  122. const imageElement = document.querySelector("svg[id*='ref']") as HTMLElement;
  123. let exportImage = extension === Extensions.svg ? toSvg : toPng;
  124. const dataURI = await exportImage(imageElement, {
  125. quality: fileDetails.quality,
  126. backgroundColor: fileDetails.backgroundColor,
  127. });
  128. downloadURI(dataURI, `${fileDetails.filename}.${Extensions[extension]}`);
  129. } catch (error) {
  130. toast.error("Failed to download image!");
  131. } finally {
  132. toast.dismiss("toastDownload");
  133. setVisible(false);
  134. }
  135. };
  136. const updateDetails = (key: keyof typeof fileDetails, value: string | number) =>
  137. setFileDetails({ ...fileDetails, [key]: value });
  138. return (
  139. <Modal visible={visible} setVisible={setVisible}>
  140. <Modal.Header>Download Image</Modal.Header>
  141. <Modal.Content>
  142. <StyledContainer>
  143. File Name
  144. <StyledColorWrapper>
  145. <FileInput
  146. value={fileDetails.filename}
  147. onChange={e => updateDetails("filename", e.target.value)}
  148. setExtension={setExtension}
  149. activeExtension={extension}
  150. extensions={Object.keys(Extensions).filter(v => isNaN(Number(v)))}
  151. />
  152. </StyledColorWrapper>
  153. </StyledContainer>
  154. <StyledContainer>
  155. Background Color
  156. <StyledColorWrapper>
  157. <TwitterPicker
  158. triangle="hide"
  159. colors={defaultColors}
  160. color={fileDetails.backgroundColor}
  161. onChange={color => updateDetails("backgroundColor", color.hex)}
  162. styles={{
  163. default: ColorPickerStyles,
  164. }}
  165. />
  166. <StyledColorIndicator color={fileDetails.backgroundColor} />
  167. </StyledColorWrapper>
  168. </StyledContainer>
  169. </Modal.Content>
  170. <Modal.Controls setVisible={setVisible}>
  171. <Button status="SECONDARY" onClick={clipboardImage}>
  172. <FiCopy size={18} /> Clipboard
  173. </Button>
  174. <Button status="SUCCESS" onClick={exportAsImage}>
  175. <FiDownload size={18} />
  176. Download
  177. </Button>
  178. </Modal.Controls>
  179. </Modal>
  180. );
  181. };