index.tsx 5.3 KB

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