@griddo/ax 10.3.3 → 10.3.4

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 (42) hide show
  1. package/package.json +2 -2
  2. package/src/api/files.tsx +20 -4
  3. package/src/api/utils.tsx +4 -2
  4. package/src/components/Fields/FileField/index.tsx +1 -1
  5. package/src/components/FileGallery/index.tsx +101 -70
  6. package/src/components/FileGallery/style.tsx +19 -3
  7. package/src/components/Icon/components/Download.js +7 -0
  8. package/src/components/Icon/svgs/Download.svg +3 -0
  9. package/src/components/SearchField/index.tsx +17 -1
  10. package/src/components/SearchField/style.tsx +4 -1
  11. package/src/components/Tag/index.tsx +12 -2
  12. package/src/components/Tag/style.tsx +19 -9
  13. package/src/containers/FileDrive/actions.tsx +26 -0
  14. package/src/containers/Sites/actions.tsx +26 -13
  15. package/src/hooks/index.tsx +2 -0
  16. package/src/hooks/resize.ts +57 -0
  17. package/src/modules/Content/PageItem/index.tsx +1 -0
  18. package/src/modules/Content/index.tsx +23 -6
  19. package/src/modules/FileDrive/BulkListHeader/TableHeader/index.tsx +3 -1
  20. package/src/modules/FileDrive/BulkListHeader/TableHeader/style.tsx +16 -2
  21. package/src/modules/FileDrive/BulkListHeader/index.tsx +9 -2
  22. package/src/modules/FileDrive/FileDragAndDrop/index.tsx +4 -2
  23. package/src/modules/FileDrive/FileFilters/SortBy/index.tsx +60 -0
  24. package/src/modules/FileDrive/FileFilters/SortBy/style.tsx +31 -0
  25. package/src/modules/FileDrive/FileFilters/Type/index.tsx +77 -0
  26. package/src/modules/FileDrive/FileFilters/Type/style.tsx +39 -0
  27. package/src/modules/FileDrive/FileFilters/Usage/index.tsx +66 -0
  28. package/src/modules/FileDrive/FileFilters/Usage/style.tsx +30 -0
  29. package/src/modules/FileDrive/FileModal/DetailPanel/index.tsx +26 -7
  30. package/src/modules/FileDrive/FileModal/index.tsx +81 -15
  31. package/src/modules/FileDrive/GridItem/index.tsx +24 -5
  32. package/src/modules/FileDrive/GridItem/style.tsx +9 -4
  33. package/src/modules/FileDrive/ListItem/index.tsx +22 -4
  34. package/src/modules/FileDrive/ListItem/style.tsx +7 -1
  35. package/src/modules/FileDrive/atoms.tsx +27 -4
  36. package/src/modules/FileDrive/helpers.tsx +17 -17
  37. package/src/modules/FileDrive/hooks.tsx +77 -0
  38. package/src/modules/FileDrive/index.tsx +145 -23
  39. package/src/modules/FileDrive/style.tsx +40 -6
  40. package/src/modules/FileDrive/utils.tsx +19 -0
  41. package/src/modules/StructuredData/StructuredDataList/GlobalPageItem/index.tsx +1 -1
  42. package/src/types/index.tsx +2 -3
@@ -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 };
@@ -198,6 +198,7 @@ const PageItem = (props: IPageItemProps): JSX.Element => {
198
198
  setRemovedPage(page.originalGlobalPage);
199
199
  toggleToast();
200
200
  }
201
+ toggleRemoveModal();
201
202
  });
202
203
  };
203
204
 
@@ -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
- label: "New",
743
- action: addNewAction,
744
- disabled: !isDataEditable,
745
- } : undefined;
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 80px;
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 { TableHeader, CheckHeader, NameHeader, TypeHeader, SizeHeader, UpdatedHeader, TagsHeader, CounterHeader };
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 isScrolling={false} selectAllItems={selectAllItems} checkState={checkState} totalItems={totalItems} />
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
- handleUpload(result);
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, useState } from "react";
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 { file, isSaving, activeDelete, isAllowedToEdit, toggleModal, onDelete, updateFile } = props;
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 () => await updateFile(file.id, form, site || "global");
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(