MediaLibraryModal.jsx 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  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 "../media/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(
  79. (key) => {
  80. confirmAlert({
  81. title: t("Confirmation"),
  82. message: t("Do you really want to remove this media?"),
  83. buttons: [
  84. {
  85. label: t("Yes"),
  86. onClick: async () => {
  87. try {
  88. await removeMedia(key);
  89. toast.success(t("Media deleted"), { autoClose: 1500 });
  90. } catch (e) {
  91. if (e.message === "Forbidden") {
  92. toast.error(t("Action forbidden. Try logging in again."));
  93. } else {
  94. console.log(e);
  95. toast.error(
  96. t("Error while deleting media. Try again later...")
  97. );
  98. }
  99. }
  100. },
  101. },
  102. {
  103. label: t("No"),
  104. onClick: () => {},
  105. },
  106. ],
  107. });
  108. },
  109. [removeMedia, t]
  110. );
  111. const { getRootProps, getInputProps } = useDropzone({
  112. onDrop: uploadMediaMutation.mutate,
  113. });
  114. return (
  115. <Modal
  116. title={t("Media library")}
  117. show={show}
  118. setShow={setShow}
  119. position="left"
  120. >
  121. <nav className="tabs">
  122. {libraries.map(({ id, name }) => (
  123. <a
  124. onClick={() => setTab(id)}
  125. className={tab === id ? "active" : ""}
  126. style={{ cursor: "pointer" }}
  127. key={id}
  128. >
  129. {name}
  130. </a>
  131. ))}
  132. </nav>
  133. <section>
  134. {libraries.map(({ id, name }, index) => {
  135. if (tab === id) {
  136. return (
  137. <div key={id}>
  138. {index === 0 && (
  139. <>
  140. <h3>{t("Add file")}</h3>
  141. <div
  142. {...getRootProps()}
  143. style={{
  144. border: "3px dashed white",
  145. margin: "0.5em",
  146. padding: "0.5em",
  147. textAlign: "center",
  148. }}
  149. >
  150. <input {...getInputProps()} />
  151. <p>{t("Click or drag'n'drop file here")}</p>
  152. </div>
  153. </>
  154. )}
  155. <h3>{name}</h3>
  156. {!isLoading && (
  157. <ImageGrid>
  158. {data.map((key) => (
  159. <div key={key}>
  160. <img
  161. src={`${API_BASE}/${key}`}
  162. onClick={() => handleSelect(key)}
  163. />
  164. <button
  165. onClick={() => onRemove(key)}
  166. className="button icon-only remove"
  167. title={t("Remove")}
  168. >
  169. X
  170. </button>
  171. </div>
  172. ))}
  173. </ImageGrid>
  174. )}
  175. </div>
  176. );
  177. } else {
  178. return null;
  179. }
  180. })}
  181. </section>
  182. </Modal>
  183. );
  184. };
  185. export default MediaLibraryModal;