@griddo/ax 10.3.3 → 10.3.5
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.
- package/package.json +2 -2
- package/src/api/files.tsx +20 -4
- package/src/api/utils.tsx +4 -2
- package/src/components/Fields/FileField/index.tsx +1 -1
- package/src/components/FileGallery/index.tsx +101 -70
- package/src/components/FileGallery/style.tsx +19 -3
- package/src/components/Icon/components/Download.js +7 -0
- package/src/components/Icon/svgs/Download.svg +3 -0
- package/src/components/SearchField/index.tsx +17 -1
- package/src/components/SearchField/style.tsx +4 -1
- package/src/components/Tag/index.tsx +12 -2
- package/src/components/Tag/style.tsx +19 -9
- package/src/containers/FileDrive/actions.tsx +26 -0
- package/src/containers/Sites/actions.tsx +26 -13
- package/src/hooks/index.tsx +2 -0
- package/src/hooks/resize.ts +57 -0
- package/src/modules/Content/PageItem/index.tsx +1 -0
- package/src/modules/Content/index.tsx +23 -6
- package/src/modules/FileDrive/BulkListHeader/TableHeader/index.tsx +3 -1
- package/src/modules/FileDrive/BulkListHeader/TableHeader/style.tsx +16 -2
- package/src/modules/FileDrive/BulkListHeader/index.tsx +9 -2
- package/src/modules/FileDrive/FileDragAndDrop/index.tsx +4 -2
- package/src/modules/FileDrive/FileFilters/SortBy/index.tsx +60 -0
- package/src/modules/FileDrive/FileFilters/SortBy/style.tsx +31 -0
- package/src/modules/FileDrive/FileFilters/Type/index.tsx +77 -0
- package/src/modules/FileDrive/FileFilters/Type/style.tsx +39 -0
- package/src/modules/FileDrive/FileFilters/Usage/index.tsx +66 -0
- package/src/modules/FileDrive/FileFilters/Usage/style.tsx +30 -0
- package/src/modules/FileDrive/FileModal/DetailPanel/index.tsx +26 -7
- package/src/modules/FileDrive/FileModal/index.tsx +81 -15
- package/src/modules/FileDrive/GridItem/index.tsx +24 -5
- package/src/modules/FileDrive/GridItem/style.tsx +9 -4
- package/src/modules/FileDrive/ListItem/index.tsx +22 -4
- package/src/modules/FileDrive/ListItem/style.tsx +7 -1
- package/src/modules/FileDrive/atoms.tsx +27 -4
- package/src/modules/FileDrive/helpers.tsx +17 -17
- package/src/modules/FileDrive/hooks.tsx +77 -0
- package/src/modules/FileDrive/index.tsx +145 -23
- package/src/modules/FileDrive/style.tsx +40 -6
- package/src/modules/FileDrive/utils.tsx +19 -0
- package/src/modules/StructuredData/StructuredDataList/GlobalPageItem/index.tsx +1 -1
- package/src/types/index.tsx +2 -3
package/src/hooks/index.tsx
CHANGED
|
@@ -6,6 +6,7 @@ import { useCategoryColors, useEmptyState, useAdaptiveText } from "./content";
|
|
|
6
6
|
import { useWindowSize } from "./window";
|
|
7
7
|
import { useOnMessageReceivedFromIframe, useOnMessageReceivedFromOutside } from "./iframe";
|
|
8
8
|
import { usePermission, useGlobalPermission } from "./users";
|
|
9
|
+
import { useResizable } from "./resize";
|
|
9
10
|
|
|
10
11
|
export {
|
|
11
12
|
useModal,
|
|
@@ -20,6 +21,7 @@ export {
|
|
|
20
21
|
useCategoryColors,
|
|
21
22
|
useEmptyState,
|
|
22
23
|
useWindowSize,
|
|
24
|
+
useResizable,
|
|
23
25
|
useAdaptiveText,
|
|
24
26
|
usePermission,
|
|
25
27
|
useGlobalPermission,
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from "react";
|
|
2
|
+
|
|
3
|
+
const useResizable = () => {
|
|
4
|
+
const [node, setNode] = useState<HTMLElement>();
|
|
5
|
+
|
|
6
|
+
const ref = useCallback((nodeEle: any) => {
|
|
7
|
+
setNode(nodeEle);
|
|
8
|
+
}, []);
|
|
9
|
+
|
|
10
|
+
const handleMouseDown = useCallback(
|
|
11
|
+
(e: React.MouseEvent) => {
|
|
12
|
+
if (!node) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const startPos = e.clientX;
|
|
17
|
+
const styles = window.getComputedStyle(node);
|
|
18
|
+
const w = parseInt(styles.width, 10);
|
|
19
|
+
|
|
20
|
+
const handleMouseMove = (e: MouseEvent) => {
|
|
21
|
+
e.preventDefault();
|
|
22
|
+
const dx = e.clientX - startPos;
|
|
23
|
+
node.style.width = `${w + dx}px`;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const handleMouseUp = () => {
|
|
27
|
+
document.removeEventListener("mousemove", handleMouseMove);
|
|
28
|
+
document.removeEventListener("mouseup", handleMouseUp);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
document.addEventListener("mousemove", handleMouseMove);
|
|
32
|
+
document.addEventListener("mouseup", handleMouseUp);
|
|
33
|
+
},
|
|
34
|
+
[node]
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
if (!node) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const resizerElements = [...node.querySelectorAll(".resizer")];
|
|
43
|
+
resizerElements.forEach((resizerEle: any) => {
|
|
44
|
+
resizerEle.addEventListener("mousedown", handleMouseDown);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
return () => {
|
|
48
|
+
resizerElements.forEach((resizerEle: any) => {
|
|
49
|
+
resizerEle.removeEventListener("mousedown", handleMouseDown);
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
}, [node]);
|
|
53
|
+
|
|
54
|
+
return [ref];
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export { useResizable };
|
|
@@ -49,6 +49,7 @@ import PageImporter from "./PageImporter";
|
|
|
49
49
|
import { DeleteModal } from "./atoms";
|
|
50
50
|
|
|
51
51
|
import * as S from "./style";
|
|
52
|
+
import { IError } from "@ax/containers/App/reducer";
|
|
52
53
|
|
|
53
54
|
// TODO: Make this monster manageable
|
|
54
55
|
const Content = (props: IProps): JSX.Element => {
|
|
@@ -107,6 +108,7 @@ const Content = (props: IProps): JSX.Element => {
|
|
|
107
108
|
deleteAndRemoveFromSiteBulk,
|
|
108
109
|
checkUserSession,
|
|
109
110
|
getDefaults,
|
|
111
|
+
error,
|
|
110
112
|
} = props;
|
|
111
113
|
|
|
112
114
|
if (!currentSiteInfo) {
|
|
@@ -117,6 +119,7 @@ const Content = (props: IProps): JSX.Element => {
|
|
|
117
119
|
const firstPage = 1;
|
|
118
120
|
|
|
119
121
|
const tableRef = useRef<HTMLDivElement>(null);
|
|
122
|
+
const errorRef = useRef<HTMLDivElement>(null);
|
|
120
123
|
const { isOpen: isNewOpen, toggleModal: toggleNewModal } = useModal();
|
|
121
124
|
const { isOpen: isImporterOpen, toggleModal: toggleImporterModal } = useModal();
|
|
122
125
|
const { isOpen: isDeleteOpen, toggleModal: toggleDeleteModal } = useModal();
|
|
@@ -326,6 +329,16 @@ const Content = (props: IProps): JSX.Element => {
|
|
|
326
329
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
327
330
|
}, [filterValues]);
|
|
328
331
|
|
|
332
|
+
useEffect(() => {
|
|
333
|
+
const errorCode = (error && error.code) || undefined;
|
|
334
|
+
if (errorCode === 400 && errorRef.current) {
|
|
335
|
+
errorRef.current.scrollIntoView({
|
|
336
|
+
behavior: "smooth",
|
|
337
|
+
block: "start",
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
}, [error]);
|
|
341
|
+
|
|
329
342
|
const bulkFilter = (bulkSelection: number[]) => filterByStatus(bulkSelection, currentSitePages);
|
|
330
343
|
|
|
331
344
|
const handleAddToBulk = (item: ICheck) => {
|
|
@@ -738,11 +751,13 @@ const Content = (props: IProps): JSX.Element => {
|
|
|
738
751
|
|
|
739
752
|
const addNewAction = filter === "unique-pages" || isGlobalPages ? toggleNewModal : addNewData;
|
|
740
753
|
|
|
741
|
-
const rightButtonProps = isAllowedToCreatePages
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
754
|
+
const rightButtonProps = isAllowedToCreatePages
|
|
755
|
+
? {
|
|
756
|
+
label: "New",
|
|
757
|
+
action: addNewAction,
|
|
758
|
+
disabled: !isDataEditable,
|
|
759
|
+
}
|
|
760
|
+
: undefined;
|
|
746
761
|
|
|
747
762
|
const errorPagesText =
|
|
748
763
|
currentSiteErrorPages.length > 1
|
|
@@ -765,7 +780,7 @@ const Content = (props: IProps): JSX.Element => {
|
|
|
765
780
|
<S.ContentListWrapper>
|
|
766
781
|
<ContentFilters current={filter} dynamicValues={structuredData} resetFilter={resetFilter} />
|
|
767
782
|
<S.TableWrapper>
|
|
768
|
-
<ErrorToast />
|
|
783
|
+
<ErrorToast ref={errorRef} />
|
|
769
784
|
{!isDataEditable && (
|
|
770
785
|
<S.NotificationWrapper>
|
|
771
786
|
<Notification type="info" text={notEditableText} closeButton={false} />
|
|
@@ -863,6 +878,7 @@ const mapStateToProps = (state: IRootState) => ({
|
|
|
863
878
|
user: state.users.currentUser,
|
|
864
879
|
skipReviewOnPublish: state.app.globalSettings.skipReviewOnPublish,
|
|
865
880
|
contentFilters: state.sites.contentFilters,
|
|
881
|
+
error: state.app.error,
|
|
866
882
|
});
|
|
867
883
|
|
|
868
884
|
interface IDispatchProps {
|
|
@@ -966,6 +982,7 @@ interface IPagesProps {
|
|
|
966
982
|
user: IUser | null;
|
|
967
983
|
skipReviewOnPublish?: boolean;
|
|
968
984
|
contentFilters: Record<string, string> | null;
|
|
985
|
+
error: IError;
|
|
969
986
|
}
|
|
970
987
|
|
|
971
988
|
type IProps = IPagesProps & IDispatchProps;
|
|
@@ -5,7 +5,7 @@ import { CheckField, TableCounter } from "@ax/components";
|
|
|
5
5
|
import * as S from "./style";
|
|
6
6
|
|
|
7
7
|
const TableHeader = (props: IProps): JSX.Element => {
|
|
8
|
-
const { isScrolling, selectAllItems, checkState, totalItems } = props;
|
|
8
|
+
const { isScrolling, selectAllItems, checkState, totalItems, isSearching } = props;
|
|
9
9
|
|
|
10
10
|
return (
|
|
11
11
|
<S.TableHeader isScrolling={isScrolling}>
|
|
@@ -25,6 +25,7 @@ const TableHeader = (props: IProps): JSX.Element => {
|
|
|
25
25
|
<S.SizeHeader>Size</S.SizeHeader>
|
|
26
26
|
<S.UpdatedHeader>Updated</S.UpdatedHeader>
|
|
27
27
|
<S.TagsHeader>Tags</S.TagsHeader>
|
|
28
|
+
{isSearching && <S.FolderHeader>Folder</S.FolderHeader>}
|
|
28
29
|
<S.CounterHeader>
|
|
29
30
|
<TableCounter totalItems={totalItems} />
|
|
30
31
|
</S.CounterHeader>
|
|
@@ -37,6 +38,7 @@ interface IProps {
|
|
|
37
38
|
totalItems: number;
|
|
38
39
|
selectAllItems: () => void;
|
|
39
40
|
checkState: Record<string, boolean>;
|
|
41
|
+
isSearching: boolean;
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
export default TableHeader;
|
|
@@ -31,7 +31,7 @@ const TypeHeader = styled(Header)`
|
|
|
31
31
|
`;
|
|
32
32
|
|
|
33
33
|
const SizeHeader = styled(Header)`
|
|
34
|
-
flex: 0 0
|
|
34
|
+
flex: 0 0 85px;
|
|
35
35
|
justify-content: center;
|
|
36
36
|
`;
|
|
37
37
|
|
|
@@ -44,10 +44,24 @@ const TagsHeader = styled(Header)`
|
|
|
44
44
|
flex: 0 0 300px;
|
|
45
45
|
`;
|
|
46
46
|
|
|
47
|
+
const FolderHeader = styled(Header)`
|
|
48
|
+
flex: 0 0 200px;
|
|
49
|
+
`;
|
|
50
|
+
|
|
47
51
|
const CounterHeader = styled(Header)`
|
|
48
52
|
flex: 0 0 70px;
|
|
49
53
|
justify-content: flex-end;
|
|
50
54
|
padding 0;
|
|
51
55
|
`;
|
|
52
56
|
|
|
53
|
-
export {
|
|
57
|
+
export {
|
|
58
|
+
TableHeader,
|
|
59
|
+
CheckHeader,
|
|
60
|
+
NameHeader,
|
|
61
|
+
TypeHeader,
|
|
62
|
+
SizeHeader,
|
|
63
|
+
UpdatedHeader,
|
|
64
|
+
TagsHeader,
|
|
65
|
+
CounterHeader,
|
|
66
|
+
FolderHeader,
|
|
67
|
+
};
|
|
@@ -6,7 +6,7 @@ import TableHeader from "./TableHeader";
|
|
|
6
6
|
import * as S from "./style";
|
|
7
7
|
|
|
8
8
|
const BulkListHeader = (props: IBulkHeaderProps): JSX.Element => {
|
|
9
|
-
const { showBulk, checkState, selectItems, selectAllItems, totalItems, bulkActions } = props;
|
|
9
|
+
const { showBulk, checkState, selectItems, selectAllItems, totalItems, bulkActions, isSearching } = props;
|
|
10
10
|
|
|
11
11
|
return showBulk ? (
|
|
12
12
|
<S.BulkWrapper>
|
|
@@ -19,7 +19,13 @@ const BulkListHeader = (props: IBulkHeaderProps): JSX.Element => {
|
|
|
19
19
|
/>
|
|
20
20
|
</S.BulkWrapper>
|
|
21
21
|
) : (
|
|
22
|
-
<TableHeader
|
|
22
|
+
<TableHeader
|
|
23
|
+
isScrolling={false}
|
|
24
|
+
selectAllItems={selectAllItems}
|
|
25
|
+
checkState={checkState}
|
|
26
|
+
totalItems={totalItems}
|
|
27
|
+
isSearching={isSearching}
|
|
28
|
+
/>
|
|
23
29
|
);
|
|
24
30
|
};
|
|
25
31
|
|
|
@@ -30,6 +36,7 @@ export interface IBulkHeaderProps {
|
|
|
30
36
|
selectAllItems: () => void;
|
|
31
37
|
totalItems: number;
|
|
32
38
|
bulkActions: IBulkAction[];
|
|
39
|
+
isSearching: boolean;
|
|
33
40
|
}
|
|
34
41
|
|
|
35
42
|
export default BulkListHeader;
|
|
@@ -80,7 +80,7 @@ const FileDragAndDrop = (props: IProps) => {
|
|
|
80
80
|
|
|
81
81
|
setUploadingState({ total: files.length, ready: 0 });
|
|
82
82
|
|
|
83
|
-
let result = [];
|
|
83
|
+
let result: IFile[] = [];
|
|
84
84
|
if (replaceData) {
|
|
85
85
|
const fileUploaded = files[0] && (await replaceFile(files[0], replaceData.fileID, replaceData.keepURL, siteID));
|
|
86
86
|
if (fileUploaded) {
|
|
@@ -100,7 +100,9 @@ const FileDragAndDrop = (props: IProps) => {
|
|
|
100
100
|
if (result) {
|
|
101
101
|
setInDropZone(false);
|
|
102
102
|
setUploadingState({ total: 0, ready: 0 });
|
|
103
|
-
|
|
103
|
+
setTimeout(() => {
|
|
104
|
+
handleUpload(result);
|
|
105
|
+
}, 3000)
|
|
104
106
|
}
|
|
105
107
|
} catch (error) {
|
|
106
108
|
console.log(error);
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
import { Icon, FloatingMenu, ListTitle, ListItem } from "@ax/components";
|
|
4
|
+
|
|
5
|
+
import * as S from "./style";
|
|
6
|
+
|
|
7
|
+
const SortBy = ({ sortItems, sortedState }: ISortByProps): JSX.Element => {
|
|
8
|
+
const { isAscending, sortedByName, sortedByDate, sortedBySize } = sortedState;
|
|
9
|
+
const sortBy = (pointer: string, isAscending: boolean) => sortItems(pointer, isAscending);
|
|
10
|
+
const sortAscendingName = () => sortBy("name", true);
|
|
11
|
+
const sortDescendingName = () => sortBy("name", false);
|
|
12
|
+
const sortAscendingDate = () => sortBy("date", true);
|
|
13
|
+
const sortDescendingDate = () => sortBy("date", false);
|
|
14
|
+
const sortAscendingSize = () => sortBy("size", true);
|
|
15
|
+
const sortDescendingSize = () => sortBy("size", false);
|
|
16
|
+
|
|
17
|
+
const SortedStateArrow = () =>
|
|
18
|
+
isAscending ? <Icon name="FullArrowUp" size="16" /> : <Icon name="FullArrowDown" size="16" />;
|
|
19
|
+
const isActive = sortedByName || sortedByDate || sortedBySize;
|
|
20
|
+
|
|
21
|
+
const Header = () => (
|
|
22
|
+
<S.SortBy data-testid="sortby-wrapper" isActive={isActive}>
|
|
23
|
+
Sort by
|
|
24
|
+
<S.IconsWrapper>
|
|
25
|
+
{isActive && <SortedStateArrow />}
|
|
26
|
+
<S.InteractiveArrow>
|
|
27
|
+
<Icon name="DownArrow" size="16" />
|
|
28
|
+
</S.InteractiveArrow>
|
|
29
|
+
</S.IconsWrapper>
|
|
30
|
+
</S.SortBy>
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<FloatingMenu Button={Header} position="center">
|
|
35
|
+
<ListTitle>Name Sorting</ListTitle>
|
|
36
|
+
<ListItem isSelected={sortedByName && isAscending} onClick={sortAscendingName}>Ascendent</ListItem>
|
|
37
|
+
<ListItem isSelected={sortedByName && !isAscending} onClick={sortDescendingName}>Descendent</ListItem>
|
|
38
|
+
<ListTitle>Date sorting</ListTitle>
|
|
39
|
+
<ListItem isSelected={sortedByDate && !isAscending} onClick={sortDescendingDate}>Newest first</ListItem>
|
|
40
|
+
<ListItem isSelected={sortedByDate && isAscending} onClick={sortAscendingDate}>Oldest first</ListItem>
|
|
41
|
+
<ListTitle>Size sorting</ListTitle>
|
|
42
|
+
<ListItem isSelected={sortedBySize && isAscending} onClick={sortAscendingSize}>Ascendent</ListItem>
|
|
43
|
+
<ListItem isSelected={sortedBySize && !isAscending} onClick={sortDescendingSize}>Descendent</ListItem>
|
|
44
|
+
</FloatingMenu>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export interface ISortByProps {
|
|
49
|
+
sortedState: ISortedListStatus;
|
|
50
|
+
sortItems(orderPointer: string, isAscendent: boolean): void;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface ISortedListStatus {
|
|
54
|
+
isAscending: boolean;
|
|
55
|
+
sortedByName: boolean;
|
|
56
|
+
sortedByDate: boolean;
|
|
57
|
+
sortedBySize: boolean;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export default SortBy;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import styled from "styled-components";
|
|
3
|
+
import { Header } from "@ax/components/TableList/style";
|
|
4
|
+
|
|
5
|
+
const SortBy = styled((props) => <Header {...props} />)<{ isActive: boolean }>`
|
|
6
|
+
width: 100%;
|
|
7
|
+
cursor: pointer;
|
|
8
|
+
&:hover {
|
|
9
|
+
color: ${(p) => p.theme.color.interactive01};
|
|
10
|
+
}
|
|
11
|
+
`;
|
|
12
|
+
|
|
13
|
+
const IconsWrapper = styled.div`
|
|
14
|
+
display: flex;
|
|
15
|
+
align-items: center;
|
|
16
|
+
flex-direction: row;
|
|
17
|
+
svg {
|
|
18
|
+
margin-left: 4px;
|
|
19
|
+
}
|
|
20
|
+
`;
|
|
21
|
+
|
|
22
|
+
const InteractiveArrow = styled.div`
|
|
23
|
+
display: flex;
|
|
24
|
+
svg {
|
|
25
|
+
path {
|
|
26
|
+
fill: ${(p) => p.theme.color.interactive01};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
export { SortBy, IconsWrapper, InteractiveArrow };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { FloatingMenu, Icon, ListTitle, CheckGroup } from "@ax/components";
|
|
3
|
+
import { areEquals } from "@ax/helpers";
|
|
4
|
+
|
|
5
|
+
import * as S from "./style";
|
|
6
|
+
|
|
7
|
+
const Type = (props: ITypeProps): JSX.Element => {
|
|
8
|
+
const { filterItems, value } = props;
|
|
9
|
+
|
|
10
|
+
const initialState = ["all"];
|
|
11
|
+
const selectAllOption = "all";
|
|
12
|
+
const pointer = "filterType";
|
|
13
|
+
const cleanValue = value.replace(/docx|xlsx|csv/gi, "");
|
|
14
|
+
const arrayValues = cleanValue.split(",");
|
|
15
|
+
const [selectedValue, setSelectedValue] = useState(arrayValues);
|
|
16
|
+
|
|
17
|
+
const setQuery = (selection: any) => {
|
|
18
|
+
if (!selection.length) {
|
|
19
|
+
selection = initialState;
|
|
20
|
+
}
|
|
21
|
+
setSelectedValue(selection);
|
|
22
|
+
const queryFilters = selection.join(",");
|
|
23
|
+
|
|
24
|
+
var mapObj: Record<string, string> = {
|
|
25
|
+
doc: "doc,docx",
|
|
26
|
+
xls: "xls,xlsx,csv",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const fullFilters = queryFilters.replace(/doc|xls/gi, (matched: string) => mapObj[matched]);
|
|
30
|
+
|
|
31
|
+
filterItems(pointer, fullFilters);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const isActive = !areEquals(selectedValue, initialState);
|
|
35
|
+
|
|
36
|
+
const Header = () => (
|
|
37
|
+
<S.Type data-testid="type-wrapper" isActive={isActive}>
|
|
38
|
+
File type
|
|
39
|
+
<S.IconsWrapper>
|
|
40
|
+
{isActive && <Icon name="Filter" size="16" />}
|
|
41
|
+
<S.InteractiveArrow>
|
|
42
|
+
<Icon name="DownArrow" size="16" />
|
|
43
|
+
</S.InteractiveArrow>
|
|
44
|
+
</S.IconsWrapper>
|
|
45
|
+
</S.Type>
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const filters = [
|
|
49
|
+
{ name: "all", value: "all", title: "ALL" },
|
|
50
|
+
{ name: "pdf", value: "pdf", title: "PDF" },
|
|
51
|
+
{ name: "doc", value: "doc", title: "DOC, DOCX" },
|
|
52
|
+
{ name: "xls", value: "xls", title: "XLS, XLSX, CSV" },
|
|
53
|
+
{ name: "zip", value: "zip", title: "ZIP" },
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<FloatingMenu Button={Header} position="center" closeOnSelect={true} isCheckGroup={true}>
|
|
58
|
+
<ListTitle>Filter by type</ListTitle>
|
|
59
|
+
<S.ChecksWrapper>
|
|
60
|
+
<CheckGroup
|
|
61
|
+
options={filters}
|
|
62
|
+
value={selectedValue}
|
|
63
|
+
onChange={setQuery}
|
|
64
|
+
selectAllOption={selectAllOption}
|
|
65
|
+
multipleSelection={true}
|
|
66
|
+
/>
|
|
67
|
+
</S.ChecksWrapper>
|
|
68
|
+
</FloatingMenu>
|
|
69
|
+
);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export interface ITypeProps {
|
|
73
|
+
filterItems(pointer: string, filter: string): void;
|
|
74
|
+
value: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export default Type;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import styled from "styled-components";
|
|
3
|
+
import { Header } from "@ax/components/TableList/style";
|
|
4
|
+
|
|
5
|
+
const Type = styled((props) => <Header {...props} />)<{ isActive: boolean }>`
|
|
6
|
+
width: 90px;
|
|
7
|
+
white-space: nowrap;
|
|
8
|
+
justify-content: center;
|
|
9
|
+
flex-wrap: nowrap;
|
|
10
|
+
align-items: center;
|
|
11
|
+
cursor: pointer;
|
|
12
|
+
&:hover {
|
|
13
|
+
color: ${(p) => p.theme.color.interactive01};
|
|
14
|
+
}
|
|
15
|
+
`;
|
|
16
|
+
|
|
17
|
+
const IconsWrapper = styled.div`
|
|
18
|
+
display: flex;
|
|
19
|
+
align-items: center;
|
|
20
|
+
flex-direction: row;
|
|
21
|
+
svg {
|
|
22
|
+
margin-left: 4px;
|
|
23
|
+
}
|
|
24
|
+
`;
|
|
25
|
+
|
|
26
|
+
const InteractiveArrow = styled.div`
|
|
27
|
+
display: flex;
|
|
28
|
+
svg {
|
|
29
|
+
path {
|
|
30
|
+
fill: ${(p) => p.theme.color.interactive01};
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
`;
|
|
34
|
+
|
|
35
|
+
const ChecksWrapper = styled.div`
|
|
36
|
+
padding: ${(p) => p.theme.spacing.xs} ${(p) => p.theme.spacing.s};
|
|
37
|
+
`;
|
|
38
|
+
|
|
39
|
+
export { Type, IconsWrapper, InteractiveArrow, ChecksWrapper };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import React, { useEffect, useState } from "react";
|
|
2
|
+
|
|
3
|
+
import { Icon, FloatingMenu, ListTitle, ListItem } from "@ax/components";
|
|
4
|
+
|
|
5
|
+
import * as S from "./style";
|
|
6
|
+
|
|
7
|
+
const UsageFilter = (props: IProps): JSX.Element => {
|
|
8
|
+
const { value, filterItems } = props;
|
|
9
|
+
|
|
10
|
+
const [selectedValue, setSelectedValue] = useState<string>("");
|
|
11
|
+
|
|
12
|
+
const isFilterActived = selectedValue !== "";
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
value && setSelectedValue(value);
|
|
16
|
+
}, [value]);
|
|
17
|
+
|
|
18
|
+
const setFilterQuery = (selection: any) => {
|
|
19
|
+
if (!selection.length) {
|
|
20
|
+
selection = "";
|
|
21
|
+
}
|
|
22
|
+
setSelectedValue(selection);
|
|
23
|
+
filterItems("filterUsage", selection);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const filterAllStates = () => setFilterQuery("");
|
|
27
|
+
const filterUsedState = () => setFilterQuery("used");
|
|
28
|
+
const filterUnusedState = () => setFilterQuery("unused");
|
|
29
|
+
|
|
30
|
+
const Header = () => (
|
|
31
|
+
<S.State isActive={isFilterActived}>
|
|
32
|
+
Usage
|
|
33
|
+
<S.IconsWrapper>
|
|
34
|
+
{isFilterActived ? (
|
|
35
|
+
<Icon name="Filter" size="16" />
|
|
36
|
+
) : (
|
|
37
|
+
<S.InteractiveArrow>
|
|
38
|
+
<Icon name="DownArrow" size="16" />
|
|
39
|
+
</S.InteractiveArrow>
|
|
40
|
+
)}
|
|
41
|
+
</S.IconsWrapper>
|
|
42
|
+
</S.State>
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<FloatingMenu Button={Header} position="center">
|
|
47
|
+
<ListTitle>Filter by usage</ListTitle>
|
|
48
|
+
<ListItem isSelected={!isFilterActived} onClick={filterAllStates}>
|
|
49
|
+
All
|
|
50
|
+
</ListItem>
|
|
51
|
+
<ListItem isSelected={selectedValue === "used"} onClick={filterUsedState}>
|
|
52
|
+
Show files in use
|
|
53
|
+
</ListItem>
|
|
54
|
+
<ListItem isSelected={selectedValue === "unused"} onClick={filterUnusedState}>
|
|
55
|
+
Show unused files
|
|
56
|
+
</ListItem>
|
|
57
|
+
</FloatingMenu>
|
|
58
|
+
);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
interface IProps {
|
|
62
|
+
filterItems(pointer: string, filter: string): void;
|
|
63
|
+
value: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export default UsageFilter;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import styled from "styled-components";
|
|
3
|
+
import { Header } from "@ax/components/TableList/style";
|
|
4
|
+
|
|
5
|
+
const State = styled((props) => <Header {...props} />)<{ isActive: boolean }>`
|
|
6
|
+
width: 170px;
|
|
7
|
+
&:hover {
|
|
8
|
+
color: ${(p) => p.theme.color.interactive01};
|
|
9
|
+
}
|
|
10
|
+
`;
|
|
11
|
+
|
|
12
|
+
const IconsWrapper = styled.div`
|
|
13
|
+
display: flex;
|
|
14
|
+
align-items: center;
|
|
15
|
+
flex-direction: row;
|
|
16
|
+
svg {
|
|
17
|
+
margin-left: 4px;
|
|
18
|
+
}
|
|
19
|
+
`;
|
|
20
|
+
|
|
21
|
+
const InteractiveArrow = styled.div`
|
|
22
|
+
display: flex;
|
|
23
|
+
svg {
|
|
24
|
+
path {
|
|
25
|
+
fill: ${(p) => p.theme.color.interactive01};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
`;
|
|
29
|
+
|
|
30
|
+
export { State, IconsWrapper, InteractiveArrow };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useEffect
|
|
1
|
+
import React, { useEffect } from "react";
|
|
2
2
|
import { connect } from "react-redux";
|
|
3
3
|
|
|
4
4
|
import { formatBytes, getFormattedDateWithTimezone } from "@ax/helpers";
|
|
@@ -11,15 +11,25 @@ import { DeleteFileModal } from "../../atoms";
|
|
|
11
11
|
import * as S from "./style";
|
|
12
12
|
|
|
13
13
|
const DetailPanel = (props: IProps) => {
|
|
14
|
-
const {
|
|
14
|
+
const {
|
|
15
|
+
file,
|
|
16
|
+
isSaving,
|
|
17
|
+
activeDelete,
|
|
18
|
+
isAllowedToEdit,
|
|
19
|
+
toggleModal,
|
|
20
|
+
onDelete,
|
|
21
|
+
updateFile,
|
|
22
|
+
form,
|
|
23
|
+
setForm,
|
|
24
|
+
isDirty,
|
|
25
|
+
resetDirty,
|
|
26
|
+
} = props;
|
|
15
27
|
const { title, alt, tags, site } = file;
|
|
16
28
|
|
|
17
29
|
const { isOpen: isDeleteOpen, toggleModal: toggleDeleteModal } = useModal();
|
|
18
30
|
|
|
19
|
-
const initState: IFormState = { title: title || "", alt: alt || "", tags: tags || [] };
|
|
20
|
-
const [form, setForm] = useState(initState);
|
|
21
|
-
|
|
22
31
|
useEffect(() => {
|
|
32
|
+
const initState: IFormState = { title: title || "", alt: alt || "", tags: tags || [] };
|
|
23
33
|
setForm(initState);
|
|
24
34
|
}, [file]);
|
|
25
35
|
|
|
@@ -27,7 +37,12 @@ const DetailPanel = (props: IProps) => {
|
|
|
27
37
|
const handleAlt = (value: string) => setForm({ ...form, alt: value });
|
|
28
38
|
const handleTags = (value: string[]) => setForm({ ...form, tags: value });
|
|
29
39
|
|
|
30
|
-
const handleSave = async () =>
|
|
40
|
+
const handleSave = async () => {
|
|
41
|
+
const updated = await updateFile(file.id, form, site || "global");
|
|
42
|
+
if (updated) {
|
|
43
|
+
resetDirty();
|
|
44
|
+
}
|
|
45
|
+
};
|
|
31
46
|
|
|
32
47
|
const handleOpenUrl = () => {
|
|
33
48
|
const win = window.open(file.url, "_blank");
|
|
@@ -118,7 +133,7 @@ const DetailPanel = (props: IProps) => {
|
|
|
118
133
|
</Button>
|
|
119
134
|
)}
|
|
120
135
|
{isAllowedToEdit && (
|
|
121
|
-
<Button type="button" onClick={handleSave} disabled={isSaving}>
|
|
136
|
+
<Button type="button" onClick={handleSave} disabled={isSaving || !isDirty}>
|
|
122
137
|
{isSaving ? "Saving" : "Save"}
|
|
123
138
|
</Button>
|
|
124
139
|
)}
|
|
@@ -144,6 +159,10 @@ interface IProps {
|
|
|
144
159
|
isSaving: boolean;
|
|
145
160
|
activeDelete: boolean;
|
|
146
161
|
isAllowedToEdit: boolean;
|
|
162
|
+
form: IFormState;
|
|
163
|
+
setForm(form: IFormState): void;
|
|
164
|
+
isDirty: boolean;
|
|
165
|
+
resetDirty(): void;
|
|
147
166
|
toggleModal(): void;
|
|
148
167
|
onDelete(fileID: number): void;
|
|
149
168
|
updateFile(
|