@griddo/ax 10.2.25 → 10.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/package.json +2 -2
  2. package/public/img/icons/excel.png +0 -0
  3. package/public/img/icons/pdf.png +0 -0
  4. package/public/img/icons/word.png +0 -0
  5. package/public/img/icons/zip.png +0 -0
  6. package/src/GlobalStore.tsx +3 -0
  7. package/src/__mocks__/store/GenericStore.ts +3 -0
  8. package/src/__tests__/components/Fields/FileField/FileField.test.tsx +34 -8
  9. package/src/__tests__/components/Gallery/GalleryPanel/GalleryDragAndDrop/GalleryDragAndDrop.test.tsx +1 -1
  10. package/src/api/files.tsx +171 -1
  11. package/src/api/users.tsx +5 -2
  12. package/src/components/ActionMenu/index.tsx +5 -13
  13. package/src/components/BackFolder/index.tsx +28 -0
  14. package/src/components/BackFolder/style.tsx +33 -0
  15. package/src/components/BulkSelectionOptions/index.tsx +4 -8
  16. package/src/components/Button/index.tsx +8 -3
  17. package/src/components/Button/style.tsx +5 -3
  18. package/src/components/ElementsTooltip/index.tsx +22 -7
  19. package/src/components/ElementsTooltip/style.tsx +2 -2
  20. package/src/components/Fields/FileField/index.tsx +7 -7
  21. package/src/components/Fields/TextField/index.tsx +3 -0
  22. package/src/components/FileGallery/FolderItem/index.tsx +39 -0
  23. package/src/components/FileGallery/FolderItem/style.tsx +31 -0
  24. package/src/components/FileGallery/GalleryPanel/DetailPanel/index.tsx +164 -0
  25. package/src/components/FileGallery/GalleryPanel/DetailPanel/style.tsx +113 -0
  26. package/src/components/FileGallery/GalleryPanel/index.tsx +42 -0
  27. package/src/components/FileGallery/GalleryPanel/style.tsx +7 -0
  28. package/src/components/FileGallery/GridItem/index.tsx +47 -0
  29. package/src/components/FileGallery/GridItem/style.tsx +51 -0
  30. package/src/components/FileGallery/index.tsx +304 -0
  31. package/src/components/FileGallery/style.tsx +173 -0
  32. package/src/components/FileGallery/utils.tsx +19 -0
  33. package/src/components/Gallery/GalleryPanel/GalleryDragAndDrop/index.tsx +4 -6
  34. package/src/components/Icon/components/Back.js +10 -0
  35. package/src/components/Icon/components/ClosePanel.js +12 -0
  36. package/src/components/Icon/components/NewFolder.js +10 -0
  37. package/src/components/Icon/components/OpenPanel.js +12 -0
  38. package/src/components/Icon/svgs/Back.svg +3 -0
  39. package/src/components/Icon/svgs/Close_panel.svg +3 -0
  40. package/src/components/Icon/svgs/New-folder.svg +3 -0
  41. package/src/components/Icon/svgs/Open_panel.svg +3 -0
  42. package/src/components/Modal/index.tsx +7 -5
  43. package/src/components/Modal/style.tsx +6 -6
  44. package/src/components/ProgressBar/index.tsx +3 -2
  45. package/src/components/ProgressBar/style.tsx +5 -3
  46. package/src/components/TableList/index.tsx +3 -2
  47. package/src/components/TableList/style.tsx +4 -0
  48. package/src/components/Toast/style.tsx +2 -2
  49. package/src/components/index.tsx +4 -0
  50. package/src/containers/FileDrive/actions.tsx +386 -0
  51. package/src/containers/FileDrive/constants.tsx +24 -0
  52. package/src/containers/FileDrive/index.tsx +7 -0
  53. package/src/containers/FileDrive/interfaces.tsx +59 -0
  54. package/src/containers/FileDrive/reducer.tsx +57 -0
  55. package/src/containers/FileDrive/utils.tsx +37 -0
  56. package/src/containers/Gallery/actions.tsx +1 -1
  57. package/src/containers/Gallery/interfaces.tsx +1 -1
  58. package/src/helpers/index.tsx +2 -0
  59. package/src/helpers/objects.tsx +6 -0
  60. package/src/modules/FileDrive/Breadcrumb/index.tsx +42 -0
  61. package/src/modules/FileDrive/Breadcrumb/style.tsx +18 -0
  62. package/src/modules/FileDrive/BulkGridHeader/GridHeader/index.tsx +37 -0
  63. package/src/modules/FileDrive/BulkGridHeader/GridHeader/style.tsx +19 -0
  64. package/src/modules/FileDrive/BulkGridHeader/index.tsx +35 -0
  65. package/src/modules/FileDrive/BulkGridHeader/style.tsx +17 -0
  66. package/src/modules/FileDrive/BulkListHeader/TableHeader/index.tsx +42 -0
  67. package/src/modules/FileDrive/BulkListHeader/TableHeader/style.tsx +53 -0
  68. package/src/modules/FileDrive/BulkListHeader/index.tsx +35 -0
  69. package/src/modules/FileDrive/BulkListHeader/style.tsx +17 -0
  70. package/src/modules/FileDrive/FileDragAndDrop/index.tsx +249 -0
  71. package/src/{components/Fields/FileField → modules/FileDrive}/FileDragAndDrop/style.tsx +50 -9
  72. package/src/modules/FileDrive/FileModal/DetailPanel/index.tsx +170 -0
  73. package/src/modules/FileDrive/FileModal/DetailPanel/style.tsx +81 -0
  74. package/src/modules/FileDrive/FileModal/index.tsx +129 -0
  75. package/src/modules/FileDrive/FileModal/style.tsx +112 -0
  76. package/src/modules/FileDrive/FolderItem/index.tsx +180 -0
  77. package/src/modules/FileDrive/FolderItem/style.tsx +39 -0
  78. package/src/modules/FileDrive/FolderTree/index.tsx +108 -0
  79. package/src/modules/FileDrive/FolderTree/style.tsx +69 -0
  80. package/src/modules/FileDrive/FolderTree/utils.tsx +91 -0
  81. package/src/modules/FileDrive/GridItem/index.tsx +167 -0
  82. package/src/modules/FileDrive/GridItem/style.tsx +76 -0
  83. package/src/modules/FileDrive/ListItem/index.tsx +180 -0
  84. package/src/modules/FileDrive/ListItem/style.tsx +88 -0
  85. package/src/modules/FileDrive/atoms.tsx +173 -0
  86. package/src/modules/FileDrive/helpers.tsx +19 -0
  87. package/src/modules/FileDrive/index.tsx +670 -0
  88. package/src/modules/FileDrive/style.tsx +145 -0
  89. package/src/modules/Sites/SitesList/index.tsx +0 -3
  90. package/src/routes/multisite.tsx +9 -0
  91. package/src/routes/site.tsx +9 -0
  92. package/src/types/index.tsx +63 -0
  93. package/src/components/Fields/FileField/FileDragAndDrop/index.tsx +0 -188
  94. package/src/components/Fields/FileField/store.tsx +0 -61
@@ -0,0 +1,249 @@
1
+ import React, { memo, useRef, useState } from "react";
2
+ import { connect } from "react-redux";
3
+
4
+ import { Icon, DragAndDrop, ProgressBar } from "@ax/components";
5
+ import { IFile, IRootState } from "@ax/types";
6
+ import { fileDriveActions } from "@ax/containers/FileDrive";
7
+
8
+ import * as S from "./style";
9
+
10
+ const FileDragAndDrop = (props: IProps) => {
11
+ const {
12
+ validFormats,
13
+ isUploading,
14
+ isSuccess,
15
+ isError,
16
+ errorMsg,
17
+ folderID = null,
18
+ inverse = false,
19
+ uploadError,
20
+ uploadFile,
21
+ handleUpload,
22
+ replaceData,
23
+ replaceFile,
24
+ siteID,
25
+ isAllowedToUpload = true,
26
+ } = props;
27
+
28
+ const validExtensions = validFormats.map((format) => `.${format}`).join(",");
29
+
30
+ const filesInputRef = useRef<any>(null);
31
+ const filesButtonRef = useRef<any>(null);
32
+ const [inDropZone, setInDropZone] = useState(false);
33
+ const [dropDepth, setDropDepth] = useState(0);
34
+ const [uploadingState, setUploadingState] = useState({ total: 0, ready: 0 });
35
+
36
+ const uploading = isUploading || uploadingState.total > uploadingState.ready;
37
+ const success = isSuccess && uploadingState.total === uploadingState.ready;
38
+ const uploadPercentage = (uploadingState.ready * 100) / uploadingState.total;
39
+
40
+ const handleDragEnter = () => {
41
+ setDropDepth((depth) => depth + 1);
42
+ };
43
+
44
+ const handleDragLeave = () => {
45
+ setDropDepth((depth) => depth - 1);
46
+ if (dropDepth > 1) return;
47
+ setInDropZone(false);
48
+ };
49
+
50
+ const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
51
+ e.dataTransfer.dropEffect = "copy";
52
+ setInDropZone(true);
53
+ };
54
+
55
+ const checkType = (type: string) => {
56
+ for (const i in validFormats) {
57
+ if (type.includes(validFormats[i])) return true;
58
+ }
59
+ return false;
60
+ };
61
+
62
+ const handleDrop = async (e: React.DragEvent<HTMLDivElement>) => {
63
+ const files = Array.from(e.dataTransfer.files);
64
+ e.dataTransfer.clearData();
65
+ await uploadFiles(files);
66
+ setDropDepth(0);
67
+ };
68
+
69
+ const handleFilesUpload = (e: any) => {
70
+ const files: File[] = Array.from(e.currentTarget.files);
71
+ uploadFiles(files);
72
+ };
73
+
74
+ const uploadFiles = async (files: File[]) => {
75
+ try {
76
+ if (!files.every((file) => checkType(file.type))) {
77
+ uploadError(true, "Invalid format");
78
+ return;
79
+ }
80
+
81
+ setUploadingState({ total: files.length, ready: 0 });
82
+
83
+ let result = [];
84
+ if (replaceData) {
85
+ const fileUploaded = files[0] && (await replaceFile(files[0], replaceData.fileID, replaceData.keepURL, siteID));
86
+ if (fileUploaded) {
87
+ result.push(fileUploaded);
88
+ }
89
+ } else {
90
+ while (files.length) {
91
+ const file = files.shift();
92
+ const fileUploaded = file && (await uploadFile(file, folderID, siteID || "global"));
93
+ if (fileUploaded) {
94
+ result.push(fileUploaded);
95
+ }
96
+ setUploadingState((state) => ({ total: state.total, ready: state.ready + 1 }));
97
+ }
98
+ }
99
+
100
+ if (result) {
101
+ setInDropZone(false);
102
+ setUploadingState({ total: 0, ready: 0 });
103
+ handleUpload(result);
104
+ }
105
+ } catch (error) {
106
+ console.log(error);
107
+ }
108
+ };
109
+
110
+ const handleTryAgain = () => {
111
+ setInDropZone(false);
112
+ };
113
+
114
+ const handleFileClick = () => {
115
+ if (filesInputRef) {
116
+ filesInputRef.current.click();
117
+ }
118
+ };
119
+
120
+ const errorWrapper = errorMsg ? <S.ErrorMsg>{errorMsg}</S.ErrorMsg> : null;
121
+
122
+ const renderDragAndDrop = () => (
123
+ <DragAndDrop
124
+ onDrop={handleDrop}
125
+ onDragOver={handleDragOver}
126
+ onDragEnter={handleDragEnter}
127
+ onDragLeave={handleDragLeave}
128
+ validFormats={validFormats}
129
+ >
130
+ <S.StatusWrapper onDragEnter={handleDragEnter} onDragLeave={handleDragLeave}>
131
+ <S.DragStatus onDragEnter={handleDragEnter} onDragLeave={handleDragLeave}>
132
+ <S.DragIcon>
133
+ <Icon name="page" size="48" />
134
+ </S.DragIcon>
135
+ <S.DragTitle>Drag your file here</S.DragTitle>
136
+ <S.DragSubtitle>or</S.DragSubtitle>
137
+ <S.FilesInput type="file" ref={filesInputRef} multiple accept={validExtensions} onInput={handleFilesUpload} />
138
+ <S.FilesButton
139
+ ref={filesButtonRef}
140
+ type="button"
141
+ buttonStyle={inverse ? "lineInverse" : "line"}
142
+ onClick={handleFileClick}
143
+ className={inverse ? "inverse" : ""}
144
+ >
145
+ Select files
146
+ </S.FilesButton>
147
+ <S.DragSubtitle>Valid formats: {validFormats.join(", ")}. Max. size: 50MB</S.DragSubtitle>
148
+ </S.DragStatus>
149
+ <S.DragOverStatus onDragEnter={handleDragEnter} onDragLeave={handleDragLeave}>
150
+ <S.DragIcon>
151
+ <Icon name="success" size="48" />
152
+ </S.DragIcon>
153
+ <S.DragTitle>Drop your file</S.DragTitle>
154
+ <S.DragSubtitle>Valid formats: {validFormats.join(", ")}. Max. size: 50MB</S.DragSubtitle>
155
+ </S.DragOverStatus>
156
+ </S.StatusWrapper>
157
+ </DragAndDrop>
158
+ );
159
+
160
+ const renderPlaceholder = () => (
161
+ <S.StatusWrapper>
162
+ <S.DragIcon>
163
+ <Icon name="page" size="36" />
164
+ </S.DragIcon>
165
+ <S.DragTitle>Select a file to see details</S.DragTitle>
166
+ </S.StatusWrapper>
167
+ );
168
+
169
+ return (
170
+ <S.Wrapper data-testid="file-drag-and-drop-wrapper" inverse={inverse}>
171
+ <S.DragAndDropWrapper
172
+ inDropZone={inDropZone}
173
+ uploading={uploading}
174
+ success={success}
175
+ error={isError}
176
+ inverse={inverse}
177
+ >
178
+ {isAllowedToUpload ? renderDragAndDrop() : renderPlaceholder()}
179
+ </S.DragAndDropWrapper>
180
+ <S.UploadingWrapper
181
+ inDropZone={inDropZone}
182
+ uploading={uploading}
183
+ success={success}
184
+ error={isError}
185
+ inverse={inverse}
186
+ >
187
+ <S.StatusWrapper>
188
+ <S.UploadingStatus>
189
+ <S.DragIcon>
190
+ <Icon name="uploadFile" size="48" />
191
+ </S.DragIcon>
192
+ <S.ProgressBar>
193
+ <ProgressBar percentage={uploadPercentage} inverse={inverse} />
194
+ </S.ProgressBar>
195
+ <S.DragTitle>Uploading...</S.DragTitle>
196
+ </S.UploadingStatus>
197
+ <S.SuccessStatus>
198
+ <S.DragIcon>
199
+ <Icon name="success" size="48" />
200
+ </S.DragIcon>
201
+ <S.DragTitle>File loaded!</S.DragTitle>
202
+ </S.SuccessStatus>
203
+ <S.ErrorStatus>
204
+ <S.DragIcon>
205
+ <Icon name="alert" size="48" />
206
+ </S.DragIcon>
207
+ <S.DragTitle>Error uploading file</S.DragTitle>
208
+ {errorWrapper}
209
+ <S.StyledButton type="button" buttonStyle="text" onClick={handleTryAgain}>
210
+ TRY AGAIN
211
+ </S.StyledButton>
212
+ </S.ErrorStatus>
213
+ </S.StatusWrapper>
214
+ </S.UploadingWrapper>
215
+ </S.Wrapper>
216
+ );
217
+ };
218
+
219
+ interface IProps {
220
+ validFormats: string[];
221
+ isUploading: boolean;
222
+ isSuccess: boolean;
223
+ isError: boolean;
224
+ errorMsg: string;
225
+ folderID?: number | null;
226
+ inverse?: boolean;
227
+ siteID: number | "global";
228
+ isAllowedToUpload?: boolean;
229
+ uploadError: (error: boolean, msg?: string) => Promise<void>;
230
+ uploadFile: (docFiles: File | File[], folderID: number | null, siteID: number | "global") => Promise<IFile | null>;
231
+ handleUpload: (result: IFile[]) => void;
232
+ replaceData?: { fileID: number; keepURL: boolean };
233
+ replaceFile: (docFile: File, fileID: number, keepUrl: boolean, siteID: number | "global") => Promise<IFile | null>;
234
+ }
235
+
236
+ const mapStateToProps = (state: IRootState) => ({
237
+ isUploading: state.fileDrive.isUploading,
238
+ isSuccess: state.fileDrive.isSuccess,
239
+ isError: state.fileDrive.isError,
240
+ errorMsg: state.fileDrive.errorMsg,
241
+ });
242
+
243
+ const mapDispatchToProps = {
244
+ uploadError: fileDriveActions.uploadError,
245
+ uploadFile: fileDriveActions.uploadFile,
246
+ replaceFile: fileDriveActions.replaceFile,
247
+ };
248
+
249
+ export default connect(mapStateToProps, mapDispatchToProps)(memo(FileDragAndDrop));
@@ -2,8 +2,8 @@ import React from "react";
2
2
  import styled from "styled-components";
3
3
  import Button from "@ax/components/Button";
4
4
 
5
- export const Wrapper = styled.div`
6
- background-color: ${(p) => p.theme.color.uiBarBackground};
5
+ export const Wrapper = styled.div<{ inverse: boolean }>`
6
+ background-color: ${(p) => (p.inverse ? p.theme.color.overlay : p.theme.color.uiBarBackground)};
7
7
  position: relative;
8
8
  transition: opacity 0.1s;
9
9
  height: 100%;
@@ -25,13 +25,11 @@ export const DragStatus = styled.div`
25
25
 
26
26
  export const DragTitle = styled.div`
27
27
  ${(p) => p.theme.textStyle.fieldLabel};
28
- color: ${(p) => p.theme.color.interactive01};
29
28
  margin-bottom: ${(p) => p.theme.spacing.xxs};
30
29
  `;
31
30
 
32
31
  export const DragSubtitle = styled.div`
33
32
  ${(p) => p.theme.textStyle.uiXS};
34
- color: ${(p) => p.theme.color.textMediumEmphasis};
35
33
  margin-bottom: ${(p) => p.theme.spacing.xxs};
36
34
  `;
37
35
 
@@ -88,10 +86,13 @@ export const DragAndDropWrapper = styled.div<{
88
86
  uploading: boolean;
89
87
  success: boolean;
90
88
  error: boolean;
89
+ inverse: boolean;
91
90
  }>`
92
- border: ${(p) => `2px dashed ${p.inDropZone ? p.theme.color.interactiveInverse : p.theme.color.interactive01}`};
91
+ border: ${(p) =>
92
+ `2px dashed ${p.inDropZone || p.inverse ? p.theme.color.interactiveInverse : p.theme.color.interactive01}`};
93
93
  border-radius: 4px;
94
- background-color: ${(p) => (p.inDropZone ? p.theme.color.interactive01 : p.theme.color.uiBarBackground)};
94
+ background-color: ${(p) =>
95
+ p.inDropZone ? p.theme.color.interactive01 : p.inverse ? "transparent" : p.theme.color.uiBarBackground};
95
96
  width: 100%;
96
97
  height: 100%;
97
98
  opacity: ${(p) => (p.uploading || p.success || p.error ? "0" : "1")};
@@ -109,11 +110,20 @@ export const DragAndDropWrapper = styled.div<{
109
110
  }
110
111
 
111
112
  ${DragTitle} {
112
- color: ${(p) => (p.inDropZone ? p.theme.color.interactiveInverse : p.theme.color.interactive01)};
113
+ color: ${(p) => (p.inDropZone || p.inverse ? p.theme.color.interactiveInverse : p.theme.color.interactive01)};
113
114
  }
114
115
 
115
116
  ${DragSubtitle} {
116
- color: ${(p) => (p.inDropZone ? p.theme.color.textMediumEmphasisInverse : p.theme.color.textMediumEmphasis)};
117
+ color: ${(p) =>
118
+ p.inDropZone || p.inverse ? p.theme.color.textMediumEmphasisInverse : p.theme.color.textMediumEmphasis};
119
+ }
120
+
121
+ ${DragIcon} {
122
+ svg {
123
+ path {
124
+ fill: ${(p) => (p.inverse ? p.theme.color.interactiveInverse : p.theme.color.interactive01)};
125
+ }
126
+ }
117
127
  }
118
128
  `;
119
129
 
@@ -122,10 +132,11 @@ export const UploadingWrapper = styled.div<{
122
132
  uploading: boolean;
123
133
  success: boolean;
124
134
  error: boolean;
135
+ inverse: boolean;
125
136
  }>`
126
137
  border: ${(p) => (p.success || p.error ? `none` : `2px dashed ${p.theme.color.interactive01}`)};
127
138
  border-radius: 4px;
128
- background-color: ${(p) => p.theme.color.uiBackground01};
139
+ background-color: ${(p) => (p.inverse ? "transparent" : p.theme.color.uiBackground01)};
129
140
  padding: ${(p) => p.theme.spacing.m};
130
141
  width: 100%;
131
142
  height: 100%;
@@ -147,6 +158,18 @@ export const UploadingWrapper = styled.div<{
147
158
  opacity: ${(p) => (p.error ? "1" : "0")};
148
159
  display: ${(p) => (p.error ? "block" : "none")};
149
160
  }
161
+
162
+ ${DragTitle} {
163
+ color: ${(p) => (p.inverse ? p.theme.color.interactiveInverse : p.theme.color.interactive01)};
164
+ }
165
+
166
+ ${DragIcon} {
167
+ svg {
168
+ path {
169
+ fill: ${(p) => (p.inverse ? p.theme.color.interactiveInverse : p.theme.color.interactive01)};
170
+ }
171
+ }
172
+ }
150
173
  `;
151
174
 
152
175
  export const ErrorMsg = styled.div`
@@ -167,4 +190,22 @@ const _Button: any = React.forwardRef((props: any, ref?: React.Ref<HTMLDivElemen
167
190
  export const FilesButton = styled(_Button)`
168
191
  margin-top: ${(p) => p.theme.spacing.xs};
169
192
  margin-bottom: ${(p) => p.theme.spacing.xs};
193
+
194
+ &.inverse {
195
+ background-color: transparent;
196
+
197
+ :hover:before {
198
+ background-color: ${(p) => p.theme.color.overlay};
199
+ }
200
+ :focus:before {
201
+ background-color: ${(p) => p.theme.color.overlay};
202
+ }
203
+ :active:before {
204
+ background-color: ${(p) => p.theme.color.overlay};
205
+ }
206
+ }
207
+ `;
208
+
209
+ export const ProgressBar = styled.div`
210
+ margin-bottom: ${(p) => p.theme.spacing.xs};
170
211
  `;
@@ -0,0 +1,170 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import { connect } from "react-redux";
3
+
4
+ import { formatBytes, getFormattedDateWithTimezone } from "@ax/helpers";
5
+ import { Button, FieldsBehavior, IconAction } from "@ax/components";
6
+ import { IFile, IRootState } from "@ax/types";
7
+ import { fileDriveActions } from "@ax/containers/FileDrive";
8
+ import { useModal } from "@ax/hooks";
9
+ import { DeleteFileModal } from "../../atoms";
10
+
11
+ import * as S from "./style";
12
+
13
+ const DetailPanel = (props: IProps) => {
14
+ const { file, isSaving, activeDelete, isAllowedToEdit, toggleModal, onDelete, updateFile } = props;
15
+ const { title, alt, tags, site } = file;
16
+
17
+ const { isOpen: isDeleteOpen, toggleModal: toggleDeleteModal } = useModal();
18
+
19
+ const initState: IFormState = { title: title || "", alt: alt || "", tags: tags || [] };
20
+ const [form, setForm] = useState(initState);
21
+
22
+ useEffect(() => {
23
+ setForm(initState);
24
+ }, [file]);
25
+
26
+ const handleTitle = (value: string) => setForm({ ...form, title: value });
27
+ const handleAlt = (value: string) => setForm({ ...form, alt: value });
28
+ const handleTags = (value: string[]) => setForm({ ...form, tags: value });
29
+
30
+ const handleSave = async () => await updateFile(file.id, form, site || "global");
31
+
32
+ const handleOpenUrl = () => {
33
+ const win = window.open(file.url, "_blank");
34
+ if (win) {
35
+ win.focus();
36
+ }
37
+ };
38
+
39
+ const handleCopyUrl = () => navigator.clipboard.writeText(file.url);
40
+
41
+ const handleDeleteFile = () => {
42
+ onDelete(file.id);
43
+ isDeleteOpen && toggleDeleteModal();
44
+ toggleModal();
45
+ };
46
+
47
+ const mainNewModalAction = {
48
+ title: "Delete document",
49
+ onClick: () => handleDeleteFile(),
50
+ };
51
+
52
+ const secondaryNewModalAction = { title: "Cancel", onClick: toggleDeleteModal };
53
+
54
+ return (
55
+ <>
56
+ <S.Wrapper>
57
+ <S.Header>DETAILS</S.Header>
58
+ <S.Content>
59
+ <S.FileInfo>
60
+ <S.FileName>{file.fileName}</S.FileName>
61
+ <S.InfoLine>
62
+ <strong>Uploaded:</strong> {getFormattedDateWithTimezone(file.uploadDate, "d MMM Y")}
63
+ </S.InfoLine>
64
+ <S.InfoLine>
65
+ <strong>Type:</strong> {file.fileType}
66
+ </S.InfoLine>
67
+ <S.InfoLine>
68
+ <strong>Size:</strong> {formatBytes(file.sizeBytes)}
69
+ </S.InfoLine>
70
+ <S.FileActions>
71
+ <Button type="button" onClick={handleCopyUrl} buttonStyle="line">
72
+ Copy URL
73
+ </Button>
74
+ <IconAction icon="OpenOutside" size="m" onClick={handleOpenUrl} />
75
+ </S.FileActions>
76
+ </S.FileInfo>
77
+ <S.FileForm>
78
+ {isAllowedToEdit ? (
79
+ <>
80
+ <FieldsBehavior
81
+ title="Title"
82
+ name="title"
83
+ value={form.title}
84
+ fieldType="TextField"
85
+ onChange={handleTitle}
86
+ />
87
+ <FieldsBehavior
88
+ title="Alternative text"
89
+ name="alt"
90
+ value={form.alt}
91
+ fieldType="TextField"
92
+ onChange={handleAlt}
93
+ />
94
+ </>
95
+ ) : (
96
+ <>
97
+ <S.Label>Title</S.Label>
98
+ <S.FieldText>{form.title}</S.FieldText>
99
+ <S.Label>Alternative text</S.Label>
100
+ <S.FieldText>{form.alt}</S.FieldText>
101
+ </>
102
+ )}
103
+ <FieldsBehavior
104
+ title="Tags"
105
+ value={form.tags}
106
+ fieldType="TagsField"
107
+ onChange={handleTags}
108
+ disabled={!isAllowedToEdit}
109
+ helptext="Type a tag and press enter to create it"
110
+ />
111
+ </S.FileForm>
112
+ </S.Content>
113
+ {(isAllowedToEdit || activeDelete) && (
114
+ <S.Footer>
115
+ {activeDelete && (
116
+ <Button type="button" buttonStyle="text" onClick={toggleDeleteModal}>
117
+ Delete
118
+ </Button>
119
+ )}
120
+ {isAllowedToEdit && (
121
+ <Button type="button" onClick={handleSave} disabled={isSaving}>
122
+ {isSaving ? "Saving" : "Save"}
123
+ </Button>
124
+ )}
125
+ </S.Footer>
126
+ )}
127
+ </S.Wrapper>
128
+ {isDeleteOpen && (
129
+ <DeleteFileModal
130
+ isOpen={isDeleteOpen}
131
+ toggleModal={toggleDeleteModal}
132
+ mainModalAction={mainNewModalAction}
133
+ secondaryModalAction={secondaryNewModalAction}
134
+ title={file.fileName}
135
+ isChild={true}
136
+ />
137
+ )}
138
+ </>
139
+ );
140
+ };
141
+
142
+ interface IProps {
143
+ file: IFile;
144
+ isSaving: boolean;
145
+ activeDelete: boolean;
146
+ isAllowedToEdit: boolean;
147
+ toggleModal(): void;
148
+ onDelete(fileID: number): void;
149
+ updateFile(
150
+ fileID: number,
151
+ data: { title: string; alt: string; tags: string[] },
152
+ siteID: number | "global"
153
+ ): Promise<boolean>;
154
+ }
155
+
156
+ interface IFormState {
157
+ title: string;
158
+ alt: string;
159
+ tags: string[];
160
+ }
161
+
162
+ const mapStateToProps = (state: IRootState) => ({
163
+ isSaving: state.app.isSaving,
164
+ });
165
+
166
+ const mapDispatchToProps = {
167
+ updateFile: fileDriveActions.updateFile,
168
+ };
169
+
170
+ export default connect(mapStateToProps, mapDispatchToProps)(DetailPanel);
@@ -0,0 +1,81 @@
1
+ import styled from "styled-components";
2
+
3
+ const Wrapper = styled.div`
4
+ position: relative;
5
+ display: flex;
6
+ width: 320px;
7
+ height: 100%;
8
+ flex-shrink: 0;
9
+ background-color: ${(p) => p.theme.color.uiBackground02};
10
+ border-left: ${(p) => `1px solid ${p.theme.color.uiLine}`};
11
+ overflow: hidden;
12
+ flex-flow: column nowrap;
13
+ `;
14
+
15
+ const Header = styled.div`
16
+ ${(p) => p.theme.textStyle.headingXS};
17
+ padding: ${(p) => p.theme.spacing.s};
18
+ text-align: center;
19
+ border-bottom: ${(p) => `1px solid ${p.theme.color.uiLine}`};
20
+ `;
21
+
22
+ const Content = styled.div`
23
+ overflow: auto;
24
+ height: 100%;
25
+ `;
26
+
27
+ const Footer = styled.div`
28
+ display: flex;
29
+ justify-content: flex-end;
30
+ padding: ${(p) => p.theme.spacing.s};
31
+ background-color: ${(p) => p.theme.color.uiBackground02};
32
+ border-top: ${(p) => `1px solid ${p.theme.color.uiLine}`};
33
+ button:not(:first-child) {
34
+ margin-left: ${(p) => p.theme.spacing.s};
35
+ }
36
+ `;
37
+
38
+ const FileInfo = styled.div`
39
+ ${(p) => p.theme.textStyle.uiM};
40
+ color: ${(p) => p.theme.colors.textMediumEmphasis};
41
+ padding: ${(p) => `${p.theme.spacing.s} ${p.theme.spacing.s} ${p.theme.spacing.m} ${p.theme.spacing.s}`};
42
+ border-bottom: ${(p) => `1px solid ${p.theme.color.uiLine}`};
43
+ `;
44
+
45
+ const FileName = styled.div`
46
+ ${(p) => p.theme.textStyle.uiL};
47
+ color: ${(p) => p.theme.colors.textHighEmphasis};
48
+ margin-bottom: ${(p) => p.theme.spacing.xxs};
49
+ word-break: break-word;
50
+ `;
51
+
52
+ const InfoLine = styled.div`
53
+ margin-bottom: ${(p) => p.theme.spacing.xxs};
54
+ `;
55
+
56
+ const FileActions = styled.div`
57
+ display: flex;
58
+ align-items: center;
59
+ padding-top: ${(p) => p.theme.spacing.s};
60
+ button:first-child {
61
+ margin-right: ${(p) => p.theme.spacing.xs};
62
+ }
63
+ `;
64
+
65
+ const FileForm = styled.div`
66
+ padding: ${(p) => p.theme.spacing.s};
67
+ `;
68
+
69
+ const Label = styled.div`
70
+ ${(p) => p.theme.textStyle.fieldLabel};
71
+ color: ${(p) => p.theme.colors.textMediumEmphasis};
72
+ margin-bottom: ${(p) => p.theme.spacing.xxs};
73
+ `;
74
+
75
+ const FieldText = styled.div`
76
+ ${(p) => p.theme.textStyle.uiM};
77
+ color: ${(p) => p.theme.colors.textHighEmphasis};
78
+ margin-bottom: ${(p) => p.theme.spacing.s};
79
+ `;
80
+
81
+ export { Wrapper, Header, Content, Footer, FileInfo, FileName, InfoLine, FileActions, FileForm, Label, FieldText };