MediaLibraryModal.jsx 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. import React from "react";
  2. import { useTranslation } from "react-i18next";
  3. import { useQuery, useMutation, useQueryClient } from "react-query";
  4. import styled from "styled-components";
  5. import { API_BASE } from "../../utils/settings";
  6. import backgroundGrid from "../../images/background-grid.png";
  7. import { confirmAlert } from "react-confirm-alert";
  8. import { toast } from "react-toastify";
  9. import Modal from "../ui/Modal";
  10. import { useMediaLibrary } from ".";
  11. import { useDropzone } from "react-dropzone";
  12. const ImageGrid = styled.div`
  13. display: flex;
  14. flex-wrap: wrap;
  15. & > div {
  16. position: relative;
  17. }
  18. & img {
  19. height: 100px;
  20. margin: 0.5em;
  21. cursor: pointer;
  22. border: 1px solid transparent;
  23. background-image: url(${backgroundGrid});
  24. &:hover {
  25. filter: brightness(1.2);
  26. border: 1px dotted var(--color-primary);
  27. }
  28. }
  29. & .remove {
  30. position: absolute;
  31. top: 15px;
  32. right: 5px;
  33. }
  34. `;
  35. const MediaLibraryModal = ({ show, setShow, onSelect }) => {
  36. const { t } = useTranslation();
  37. const {
  38. getLibraryMedia,
  39. addMedia,
  40. removeMedia,
  41. libraries,
  42. } = useMediaLibrary();
  43. const queryClient = useQueryClient();
  44. const [tab, setTab] = React.useState(libraries[0].id);
  45. const currentLibrary = libraries.find(({ id }) => id === tab);
  46. const { isLoading, data = [] } = useQuery(
  47. `media__${tab}`,
  48. () => getLibraryMedia(currentLibrary),
  49. {
  50. enabled: show,
  51. }
  52. );
  53. const handleSelect = React.useCallback(
  54. (media) => {
  55. onSelect(media);
  56. setShow(false);
  57. },
  58. [onSelect, setShow]
  59. );
  60. const uploadMediaMutation = useMutation(
  61. async (files) => {
  62. if (files.length === 1) {
  63. return [await addMedia(currentLibrary, files[0])];
  64. } else {
  65. return Promise.all(files.map((file) => addMedia(currentLibrary, file)));
  66. }
  67. },
  68. {
  69. onSuccess: (result) => {
  70. if (result.length === 1) {
  71. // If only one file is processed
  72. handleSelect(result[0].content);
  73. }
  74. queryClient.invalidateQueries(`media__${tab}`);
  75. },
  76. }
  77. );
  78. const onRemove = React.useCallback((key) => {
  79. confirmAlert({
  80. title: t("Confirmation"),
  81. message: t("Do you really want to remove this media?"),
  82. buttons: [
  83. {
  84. label: t("Yes"),
  85. onClick: async () => {
  86. try {
  87. await removeMedia(key);
  88. toast.success(t("Media deleted"), { autoClose: 1500 });
  89. } catch (e) {
  90. if (e.message === "Forbidden") {
  91. toast.error(t("Action forbidden. Try logging in again."));
  92. } else {
  93. console.log(e);
  94. toast.error(
  95. t("Error while deleting media. Try again later...")
  96. );
  97. }
  98. }
  99. },
  100. },
  101. {
  102. label: t("No"),
  103. onClick: () => {},
  104. },
  105. ],
  106. });
  107. }, []);
  108. const { getRootProps, getInputProps } = useDropzone({
  109. onDrop: uploadMediaMutation.mutate,
  110. });
  111. return (
  112. <Modal
  113. title={t("Media library")}
  114. show={show}
  115. setShow={setShow}
  116. position="left"
  117. >
  118. <nav className="tabs">
  119. {libraries.map(({ id, name }) => (
  120. <a
  121. onClick={() => setTab(id)}
  122. className={tab === id ? "active" : ""}
  123. style={{ cursor: "pointer" }}
  124. key={id}
  125. >
  126. {name}
  127. </a>
  128. ))}
  129. </nav>
  130. <section>
  131. {libraries.map(({ id, name }, index) => {
  132. if (tab === id) {
  133. return (
  134. <div key={id}>
  135. {index === 0 && (
  136. <>
  137. <h3>{t("Add file")}</h3>
  138. <div
  139. {...getRootProps()}
  140. style={{
  141. border: "3px dashed white",
  142. margin: "0.5em",
  143. padding: "0.5em",
  144. textAlign: "center",
  145. }}
  146. >
  147. <input {...getInputProps()} />
  148. <p>{t("Click or drag'n'drop file here")}</p>
  149. </div>
  150. </>
  151. )}
  152. <h3>{name}</h3>
  153. {!isLoading && (
  154. <ImageGrid>
  155. {data.map((key) => (
  156. <div key={key}>
  157. <img
  158. src={`${API_BASE}/${key}`}
  159. onClick={() => handleSelect(key)}
  160. />
  161. <button
  162. onClick={() => onRemove(key)}
  163. className="button icon-only remove"
  164. title={t("Remove")}
  165. >
  166. X
  167. </button>
  168. </div>
  169. ))}
  170. </ImageGrid>
  171. )}
  172. </div>
  173. );
  174. } else {
  175. return null;
  176. }
  177. })}
  178. </section>
  179. </Modal>
  180. );
  181. };
  182. export default MediaLibraryModal;