@griddo/ax 11.14.1 → 11.14.2-rc.1
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/config/jest/reactEasyCropMock.js +15 -0
- package/config/jest/reactTimezoneMock.js +13 -0
- package/package.json +221 -219
- package/public/img/welcome.svg +127 -0
- package/src/__tests__/components/Browser/Browser.test.tsx +27 -51
- package/src/__tests__/components/CategoryCell/CategoryCell.test.tsx +10 -5
- package/src/__tests__/components/ElementsTooltip/ElementsTooltip.test.tsx +27 -14
- package/src/__tests__/components/HeadingsPreviewModal/ErrorsBanner/ErrorItem/ErrorItem.test.tsx +2 -0
- package/src/__tests__/components/HeadingsPreviewModal/HeadingsPreviewModal.utils.test.tsx +138 -1
- package/src/__tests__/components/ImageDragAndDrop/CropStep/CropStep.test.tsx +84 -0
- package/src/__tests__/components/ImageDragAndDrop/ImageDragAndDrop.test.tsx +169 -0
- package/src/__tests__/components/ProfileImage/ProfileImage.test.tsx +120 -0
- package/src/__tests__/components/ResizePanel/ResizePanel.test.tsx +8 -0
- package/src/__tests__/components/UserRolesAndSites/RoleItem/RoleItem.test.tsx +190 -0
- package/src/__tests__/components/UserRolesAndSites/UserRolesAndSites.test.tsx +471 -0
- package/src/__tests__/modules/FramePreview/HeadingsOverlay/HeadingsOverlay.test.tsx +15 -2
- package/src/__tests__/modules/Sites/Sites.test.tsx +68 -224
- package/src/__tests__/modules/Sites/SitesList/ListView/BulkHeader/BulkHeader.test.tsx +21 -17
- package/src/__tests__/modules/Sites/SitesList/SitesList.test.tsx +65 -565
- package/src/__tests__/modules/Sites/SitesList/WelcomeModal/DataStep/DataStep.test.tsx +109 -0
- package/src/__tests__/modules/Sites/SitesList/WelcomeModal/FinalStep/FinalStep.test.tsx +157 -0
- package/src/__tests__/modules/Sites/SitesList/WelcomeModal/ImageStep/CropView/CropView.test.tsx +51 -0
- package/src/__tests__/modules/Sites/SitesList/WelcomeModal/ImageStep/ImageStep.test.tsx +70 -0
- package/src/__tests__/modules/Sites/SitesList/WelcomeModal/ImageStep/UploadView/UploadView.test.tsx +92 -0
- package/src/__tests__/modules/Sites/SitesList/WelcomeModal/TimezoneStep/TimezoneStep.test.tsx +94 -0
- package/src/__tests__/modules/Sites/SitesList/WelcomeModal/WelcomeModal.test.tsx +78 -0
- package/src/__tests__/modules/Sites/SitesList/WelcomeModal/WelcomeStep/WelcomeStep.test.tsx +39 -0
- package/src/__tests__/modules/Sites/SitesList/WelcomeModal/utils.test.ts +55 -0
- package/src/api/sites.tsx +4 -4
- package/src/components/Avatar/index.tsx +26 -5
- package/src/components/Avatar/style.tsx +20 -10
- package/src/components/Browser/index.tsx +7 -1
- package/src/components/ConfigPanel/index.tsx +5 -4
- package/src/components/ElementsTooltip/index.tsx +96 -34
- package/src/components/ElementsTooltip/style.tsx +12 -1
- package/src/components/Fields/FileField/index.tsx +16 -17
- package/src/components/Fields/HeadingField/index.tsx +1 -1
- package/src/components/Fields/ImageField/index.tsx +9 -38
- package/src/components/Fields/ImageField/style.tsx +12 -1
- package/src/components/Fields/ToggleField/index.tsx +1 -1
- package/src/components/Fields/Wysiwyg/index.tsx +25 -20
- package/src/components/FileGallery/GalleryPanel/index.tsx +15 -7
- package/src/components/FileGallery/index.tsx +33 -28
- package/src/components/Gallery/GalleryPanel/index.tsx +5 -16
- package/src/components/Gallery/index.tsx +0 -2
- package/src/components/HeadingsPreviewModal/ErrorsBanner/ErrorItem/index.tsx +11 -2
- package/src/components/HeadingsPreviewModal/ErrorsBanner/index.tsx +21 -3
- package/src/components/HeadingsPreviewModal/ErrorsBanner/style.tsx +2 -2
- package/src/components/HeadingsPreviewModal/index.tsx +13 -3
- package/src/components/HeadingsPreviewModal/style.tsx +18 -0
- package/src/components/HeadingsPreviewModal/utils.tsx +31 -3
- package/src/components/Image/index.tsx +2 -2
- package/src/components/ImageDragAndDrop/CropStep/index.tsx +95 -0
- package/src/components/ImageDragAndDrop/CropStep/style.tsx +101 -0
- package/src/{modules/MediaGallery → components}/ImageDragAndDrop/index.tsx +103 -40
- package/src/{modules/MediaGallery → components}/ImageDragAndDrop/style.tsx +14 -2
- package/src/components/ProfileImage/index.tsx +55 -0
- package/src/components/ProfileImage/style.tsx +58 -0
- package/src/components/ResizePanel/ResizeHandle/index.tsx +44 -6
- package/src/components/ResizePanel/ResizeHandle/style.tsx +7 -0
- package/src/components/ResizePanel/index.tsx +25 -4
- package/src/components/Tabs/style.tsx +1 -1
- package/src/components/Tag/index.tsx +0 -1
- package/src/components/UserRolesAndSites/RoleItem/index.tsx +42 -0
- package/src/components/UserRolesAndSites/RoleItem/style.tsx +29 -0
- package/src/components/UserRolesAndSites/index.tsx +102 -0
- package/src/components/UserRolesAndSites/style.tsx +67 -0
- package/src/components/index.tsx +6 -0
- package/src/constants/index.ts +13 -1
- package/src/containers/App/actions.tsx +8 -1
- package/src/containers/Sites/actions.tsx +26 -0
- package/src/containers/Sites/constants.tsx +1 -0
- package/src/containers/Sites/interfaces.tsx +6 -0
- package/src/containers/Sites/reducer.tsx +5 -1
- package/src/containers/Users/reducer.tsx +6 -5
- package/src/guards/routeLeaving/index.tsx +9 -11
- package/src/helpers/images.tsx +50 -3
- package/src/helpers/index.tsx +2 -1
- package/src/hooks/forms.tsx +45 -48
- package/src/hooks/modals.tsx +4 -3
- package/src/modules/ActivityLog/ItemLogUser/UserItem/index.tsx +1 -1
- package/src/modules/App/Routing/Logout/index.tsx +3 -5
- package/src/modules/App/Routing/NavMenu/NavItem/index.tsx +73 -52
- package/src/modules/App/Routing/NavMenu/NavItem/style.tsx +21 -7
- package/src/modules/App/Routing/NavMenu/index.tsx +59 -54
- package/src/modules/App/Routing/NavMenu/style.tsx +13 -11
- package/src/modules/CreatePass/index.tsx +1 -1
- package/src/modules/FileDrive/FileDragAndDrop/index.tsx +11 -8
- package/src/modules/FileDrive/FileModal/index.tsx +8 -9
- package/src/modules/FileDrive/index.tsx +1 -18
- package/src/modules/Forms/FormEditor/index.tsx +1 -1
- package/src/modules/FramePreview/HeadingsOverlay/index.tsx +22 -11
- package/src/modules/FramePreview/HeadingsOverlay/style.tsx +1 -1
- package/src/modules/MediaGallery/ImageModal/index.tsx +1 -5
- package/src/modules/MediaGallery/index.tsx +1 -3
- package/src/modules/Settings/Globals/constants.tsx +942 -106
- package/src/modules/Sites/SitesList/AllSitesHeader/index.tsx +33 -0
- package/src/modules/Sites/SitesList/AllSitesHeader/style.tsx +35 -0
- package/src/modules/Sites/SitesList/GridView/GridHeaderFilter/index.tsx +5 -5
- package/src/modules/Sites/SitesList/GridView/GridSiteItem/index.tsx +23 -119
- package/src/modules/Sites/SitesList/ListView/BulkHeader/TableHeader/index.tsx +4 -4
- package/src/modules/Sites/SitesList/ListView/BulkHeader/index.tsx +4 -3
- package/src/modules/Sites/SitesList/ListView/ListSiteItem/index.tsx +23 -120
- package/src/modules/Sites/SitesList/{RecentSiteItem → RecentSites/RecentSiteItem}/index.tsx +4 -5
- package/src/modules/Sites/SitesList/RecentSites/index.tsx +49 -0
- package/src/modules/Sites/SitesList/RecentSites/style.tsx +92 -0
- package/src/modules/Sites/SitesList/SiteModal/index.tsx +8 -7
- package/src/modules/Sites/SitesList/WelcomeModal/DataStep/index.tsx +72 -0
- package/src/modules/Sites/SitesList/WelcomeModal/DataStep/style.tsx +59 -0
- package/src/modules/Sites/SitesList/WelcomeModal/FinalStep/constants.tsx +78 -0
- package/src/modules/Sites/SitesList/WelcomeModal/FinalStep/index.tsx +78 -0
- package/src/modules/Sites/SitesList/WelcomeModal/FinalStep/style.tsx +141 -0
- package/src/modules/Sites/SitesList/WelcomeModal/ImageStep/CropView/index.tsx +93 -0
- package/src/modules/Sites/SitesList/WelcomeModal/ImageStep/CropView/style.tsx +77 -0
- package/src/modules/Sites/SitesList/WelcomeModal/ImageStep/UploadView/index.tsx +100 -0
- package/src/modules/Sites/SitesList/WelcomeModal/ImageStep/UploadView/style.tsx +94 -0
- package/src/modules/Sites/SitesList/WelcomeModal/ImageStep/index.tsx +44 -0
- package/src/modules/Sites/SitesList/WelcomeModal/ImageStep/style.tsx +31 -0
- package/src/modules/Sites/SitesList/WelcomeModal/TimezoneStep/index.tsx +51 -0
- package/src/modules/Sites/SitesList/WelcomeModal/TimezoneStep/style.tsx +52 -0
- package/src/modules/Sites/SitesList/WelcomeModal/WelcomeStep/index.tsx +40 -0
- package/src/modules/Sites/SitesList/WelcomeModal/WelcomeStep/style.tsx +53 -0
- package/src/modules/Sites/SitesList/WelcomeModal/index.tsx +215 -0
- package/src/modules/Sites/SitesList/WelcomeModal/style.tsx +12 -0
- package/src/modules/Sites/SitesList/WelcomeModal/utils.ts +26 -0
- package/src/modules/Sites/SitesList/atoms.tsx +4 -4
- package/src/modules/Sites/SitesList/hooks.tsx +149 -16
- package/src/modules/Sites/SitesList/index.tsx +127 -125
- package/src/modules/Sites/SitesList/style.tsx +1 -117
- package/src/modules/Sites/SitesList/utils.tsx +9 -2
- package/src/modules/Sites/index.tsx +19 -8
- package/src/modules/Users/Profile/index.tsx +169 -31
- package/src/modules/Users/Profile/style.tsx +81 -1
- package/src/modules/Users/Roles/RoleItem/index.tsx +2 -2
- package/src/modules/Users/UserCreate/SiteItem/index.tsx +11 -14
- package/src/modules/Users/UserForm/atoms.tsx +3 -3
- package/src/modules/Users/UserForm/index.tsx +25 -29
- package/src/modules/Users/UserForm/style.tsx +15 -2
- package/src/modules/Users/UserList/UserItem/index.tsx +4 -4
- package/src/routes/index.tsx +1 -0
- package/src/types/index.tsx +2 -0
- /package/src/modules/Sites/SitesList/{RecentSiteItem → RecentSites/RecentSiteItem}/style.tsx +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
1
2
|
import FroalaEditorComponent from "react-froala-wysiwyg";
|
|
2
3
|
import { connect } from "react-redux";
|
|
3
4
|
|
|
@@ -12,7 +13,7 @@ import "./vendors";
|
|
|
12
13
|
|
|
13
14
|
import * as S from "./style";
|
|
14
15
|
|
|
15
|
-
const Wysiwyg = (props: IWysiwygProps)
|
|
16
|
+
const Wysiwyg = (props: IWysiwygProps) => {
|
|
16
17
|
const {
|
|
17
18
|
value,
|
|
18
19
|
error,
|
|
@@ -28,12 +29,20 @@ const Wysiwyg = (props: IWysiwygProps): JSX.Element => {
|
|
|
28
29
|
} = props;
|
|
29
30
|
|
|
30
31
|
const imageSite = site ? site.id : "global";
|
|
32
|
+
const editorRef = useRef<any>(null);
|
|
33
|
+
const errorRef = useRef(error);
|
|
34
|
+
errorRef.current = error;
|
|
35
|
+
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (!editorRef.current) return;
|
|
38
|
+
disabled ? editorRef.current.edit.off() : editorRef.current.edit.on();
|
|
39
|
+
}, [disabled]);
|
|
31
40
|
|
|
32
41
|
const handleChange = (model: string) => {
|
|
33
42
|
onChange(model);
|
|
34
43
|
const stripedHtml = decodeEntities(model);
|
|
35
44
|
setTimeout(() => {
|
|
36
|
-
|
|
45
|
+
errorRef.current && handleValidation?.(stripedHtml);
|
|
37
46
|
}, 300);
|
|
38
47
|
};
|
|
39
48
|
|
|
@@ -49,28 +58,26 @@ const Wysiwyg = (props: IWysiwygProps): JSX.Element => {
|
|
|
49
58
|
Authorization: `bearer ${token}`,
|
|
50
59
|
},
|
|
51
60
|
events: {
|
|
52
|
-
initialized() {
|
|
53
|
-
|
|
54
|
-
|
|
61
|
+
initialized(this: any) {
|
|
62
|
+
editorRef.current = this;
|
|
63
|
+
|
|
55
64
|
if (disabled) {
|
|
56
|
-
|
|
57
|
-
editor.edit.off();
|
|
58
|
-
}, 1000);
|
|
65
|
+
this.edit.off();
|
|
59
66
|
}
|
|
60
67
|
},
|
|
61
|
-
"image.beforeUpload": async function (images: FileList) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
68
|
+
"image.beforeUpload": async function (this: any, images: FileList) {
|
|
69
|
+
try {
|
|
70
|
+
const result = await uploadImage(images[0], imageSite);
|
|
71
|
+
if (result) {
|
|
72
|
+
this.image.insert(result.url, true, null, this.image.get(), null);
|
|
73
|
+
}
|
|
74
|
+
} catch {
|
|
75
|
+
this.popups.hideAll();
|
|
67
76
|
}
|
|
68
77
|
return false;
|
|
69
78
|
},
|
|
70
|
-
blur: function () {
|
|
71
|
-
|
|
72
|
-
const editor: any = this;
|
|
73
|
-
const html = editor.html.get();
|
|
79
|
+
blur: function (this: any) {
|
|
80
|
+
const html = this.html.get();
|
|
74
81
|
const stripedHtml = decodeEntities(html);
|
|
75
82
|
handleValidation?.(stripedHtml);
|
|
76
83
|
},
|
|
@@ -88,9 +95,7 @@ const Wysiwyg = (props: IWysiwygProps): JSX.Element => {
|
|
|
88
95
|
|
|
89
96
|
interface IProps {
|
|
90
97
|
value: string;
|
|
91
|
-
title: string;
|
|
92
98
|
full?: boolean;
|
|
93
|
-
helptext?: string;
|
|
94
99
|
error?: boolean;
|
|
95
100
|
placeholder?: string;
|
|
96
101
|
token: string;
|
|
@@ -1,25 +1,33 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { memo } from "react";
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import FileDragAndDrop from "@ax/modules/FileDrive/FileDragAndDrop";
|
|
4
|
+
import type { IFile } from "@ax/types";
|
|
4
5
|
|
|
5
6
|
import DetailPanel from "./DetailPanel";
|
|
6
|
-
import FileDragAndDrop from "@ax/modules/FileDrive/FileDragAndDrop";
|
|
7
7
|
|
|
8
8
|
import * as S from "./style";
|
|
9
9
|
|
|
10
10
|
const GalleryPanel = (props: IGalleryPanelProps) => {
|
|
11
|
-
const {
|
|
12
|
-
|
|
11
|
+
const {
|
|
12
|
+
selectedFile,
|
|
13
|
+
setFile,
|
|
14
|
+
scope,
|
|
15
|
+
currentFolderID,
|
|
16
|
+
isAllowedToAdd,
|
|
17
|
+
isAllowedToEdit,
|
|
18
|
+
customFormats,
|
|
19
|
+
handleUpload,
|
|
20
|
+
} = props;
|
|
13
21
|
|
|
14
22
|
return (
|
|
15
23
|
<S.GalleryPanel>
|
|
16
24
|
{!selectedFile ? (
|
|
17
25
|
<FileDragAndDrop
|
|
18
|
-
validFormats={validFormats}
|
|
19
26
|
folderID={currentFolderID}
|
|
20
27
|
handleUpload={handleUpload}
|
|
21
28
|
siteID={scope}
|
|
22
29
|
isAllowedToUpload={isAllowedToAdd}
|
|
30
|
+
customFormats={customFormats}
|
|
23
31
|
/>
|
|
24
32
|
) : (
|
|
25
33
|
<DetailPanel selectedFile={selectedFile} setFile={setFile} isAllowedToEdit={isAllowedToEdit} />
|
|
@@ -30,7 +38,7 @@ const GalleryPanel = (props: IGalleryPanelProps) => {
|
|
|
30
38
|
|
|
31
39
|
export interface IGalleryPanelProps {
|
|
32
40
|
selectedFile: IFile | null;
|
|
33
|
-
|
|
41
|
+
customFormats?: string[];
|
|
34
42
|
setFile: (fileData: any) => void;
|
|
35
43
|
scope: number | "global";
|
|
36
44
|
currentFolderID: number | null;
|
|
@@ -1,43 +1,51 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { memo, useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
2
2
|
import { connect } from "react-redux";
|
|
3
3
|
|
|
4
|
-
import { IFile, IFilesFolder, IFolder, IFolderTree, IGetFolderParams, IQueryValue, IRootState, ISite } from "@ax/types";
|
|
5
4
|
import {
|
|
6
|
-
|
|
7
|
-
Tabs,
|
|
8
|
-
SearchField,
|
|
5
|
+
BackFolder,
|
|
9
6
|
EmptyState,
|
|
10
7
|
ErrorToast,
|
|
11
|
-
|
|
12
|
-
Tooltip,
|
|
8
|
+
FilterTagsBar,
|
|
13
9
|
Icon,
|
|
14
|
-
|
|
10
|
+
Loader,
|
|
11
|
+
Notification,
|
|
12
|
+
SearchField,
|
|
15
13
|
SearchTagsBar,
|
|
16
|
-
|
|
14
|
+
Tabs,
|
|
15
|
+
Tooltip,
|
|
17
16
|
} from "@ax/components";
|
|
18
17
|
import { fileDriveActions } from "@ax/containers/FileDrive";
|
|
19
18
|
import { usePermission, useResizable } from "@ax/hooks";
|
|
20
|
-
|
|
21
|
-
import GalleryPanel from "./GalleryPanel";
|
|
22
|
-
import GridItem from "./GridItem";
|
|
23
|
-
import FolderItem from "./FolderItem";
|
|
24
|
-
|
|
25
|
-
import * as S from "./style";
|
|
26
|
-
|
|
27
19
|
// refactor
|
|
28
20
|
import Breadcrumb from "@ax/modules/FileDrive/Breadcrumb";
|
|
29
|
-
import FolderTree from "@ax/modules/FileDrive/FolderTree";
|
|
30
|
-
import Type from "@ax/modules/FileDrive/FileFilters/Type";
|
|
31
21
|
import SortBy from "@ax/modules/FileDrive/FileFilters/SortBy";
|
|
22
|
+
import Type from "@ax/modules/FileDrive/FileFilters/Type";
|
|
23
|
+
import FolderTree from "@ax/modules/FileDrive/FolderTree";
|
|
32
24
|
import { useFilterQuery, useSortedListStatus } from "@ax/modules/FileDrive/hooks";
|
|
33
25
|
import { getSortedListStatus } from "@ax/modules/FileDrive/utils";
|
|
26
|
+
import type {
|
|
27
|
+
IFile,
|
|
28
|
+
IFilesFolder,
|
|
29
|
+
IFolder,
|
|
30
|
+
IFolderTree,
|
|
31
|
+
IGetFolderParams,
|
|
32
|
+
IQueryValue,
|
|
33
|
+
IRootState,
|
|
34
|
+
ISite,
|
|
35
|
+
} from "@ax/types";
|
|
36
|
+
|
|
37
|
+
import FolderItem from "./FolderItem";
|
|
38
|
+
import GalleryPanel from "./GalleryPanel";
|
|
39
|
+
import GridItem from "./GridItem";
|
|
40
|
+
|
|
41
|
+
import * as S from "./style";
|
|
34
42
|
|
|
35
43
|
const FileGallery = (props: IProps): JSX.Element => {
|
|
36
44
|
const {
|
|
37
45
|
currentFolderContent,
|
|
38
46
|
site,
|
|
39
47
|
currentFolderID,
|
|
40
|
-
|
|
48
|
+
customFormats,
|
|
41
49
|
breadcrumb,
|
|
42
50
|
toggleModal,
|
|
43
51
|
getFolderContent,
|
|
@@ -288,13 +296,10 @@ const FileGallery = (props: IProps): JSX.Element => {
|
|
|
288
296
|
</S.EmptyWrapper>
|
|
289
297
|
) : (
|
|
290
298
|
<S.Grid>
|
|
291
|
-
{items
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
<GridItem file={item} onClick={handleClick} key={item.fileName} isSelected={isSelected} />
|
|
296
|
-
);
|
|
297
|
-
})}
|
|
299
|
+
{items?.map((item: IFile) => {
|
|
300
|
+
const isSelected = item.id === selectedFile?.id;
|
|
301
|
+
return <GridItem file={item} onClick={handleClick} key={item.fileName} isSelected={isSelected} />;
|
|
302
|
+
})}
|
|
298
303
|
</S.Grid>
|
|
299
304
|
)}
|
|
300
305
|
</S.SectionWrapper>
|
|
@@ -304,7 +309,7 @@ const FileGallery = (props: IProps): JSX.Element => {
|
|
|
304
309
|
</S.ContentWrapper>
|
|
305
310
|
<GalleryPanel
|
|
306
311
|
selectedFile={selectedFile}
|
|
307
|
-
|
|
312
|
+
customFormats={customFormats}
|
|
308
313
|
setFile={setFile}
|
|
309
314
|
scope={galleryScope}
|
|
310
315
|
currentFolderID={currentFolderID}
|
|
@@ -321,7 +326,7 @@ export interface IGalleryProps {
|
|
|
321
326
|
currentFolderContent: IFilesFolder | null;
|
|
322
327
|
currentFolderID: number | null;
|
|
323
328
|
isLoading: boolean;
|
|
324
|
-
|
|
329
|
+
customFormats?: string[];
|
|
325
330
|
breadcrumb: IFolderTree[];
|
|
326
331
|
toggleModal: () => void;
|
|
327
332
|
addFile: (file: IFile) => void;
|
|
@@ -1,30 +1,20 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { memo } from "react";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { ImageDragAndDrop } from "@ax/components";
|
|
4
|
+
import type { IGetFolderParams, IImage } from "@ax/types";
|
|
4
5
|
|
|
5
6
|
import DetailPanel from "./DetailPanel";
|
|
6
|
-
import ImageDragAndDrop from "@ax/modules/MediaGallery/ImageDragAndDrop";
|
|
7
7
|
|
|
8
8
|
import * as S from "./style";
|
|
9
9
|
|
|
10
10
|
const GalleryPanel = (props: IGalleryPanelProps) => {
|
|
11
|
-
const {
|
|
12
|
-
|
|
13
|
-
validFormats,
|
|
14
|
-
setImage,
|
|
15
|
-
scope,
|
|
16
|
-
currentFolderID,
|
|
17
|
-
isAllowedToAdd,
|
|
18
|
-
isAllowedToEdit,
|
|
19
|
-
handleUpload,
|
|
20
|
-
getParams,
|
|
21
|
-
} = props;
|
|
11
|
+
const { selectedImage, setImage, scope, currentFolderID, isAllowedToAdd, isAllowedToEdit, handleUpload, getParams } =
|
|
12
|
+
props;
|
|
22
13
|
|
|
23
14
|
return (
|
|
24
15
|
<S.GalleryPanel>
|
|
25
16
|
{!selectedImage ? (
|
|
26
17
|
<ImageDragAndDrop
|
|
27
|
-
validFormats={validFormats}
|
|
28
18
|
folderID={currentFolderID}
|
|
29
19
|
handleUpload={handleUpload}
|
|
30
20
|
siteID={scope}
|
|
@@ -44,7 +34,6 @@ const GalleryPanel = (props: IGalleryPanelProps) => {
|
|
|
44
34
|
|
|
45
35
|
export interface IGalleryPanelProps {
|
|
46
36
|
selectedImage: IImage | null;
|
|
47
|
-
validFormats: string[];
|
|
48
37
|
setImage: (fileData: any) => void;
|
|
49
38
|
scope: number | "global";
|
|
50
39
|
currentFolderID: number | null;
|
|
@@ -60,7 +60,6 @@ const Gallery = (props: IProps): JSX.Element => {
|
|
|
60
60
|
const galleryScope = isLocalTab && site ? site.id : "global";
|
|
61
61
|
const hasFolders = !!folders?.length;
|
|
62
62
|
const isRoot = !breadcrumb?.length;
|
|
63
|
-
const validFormats = ["jpeg", "jpg", "png", "svg", "gif"];
|
|
64
63
|
|
|
65
64
|
const galleryRef = useRef<HTMLDivElement>(null);
|
|
66
65
|
|
|
@@ -345,7 +344,6 @@ const Gallery = (props: IProps): JSX.Element => {
|
|
|
345
344
|
</S.ContentWrapper>
|
|
346
345
|
<GalleryPanel
|
|
347
346
|
selectedImage={selectedImage}
|
|
348
|
-
validFormats={validFormats}
|
|
349
347
|
setImage={setImage}
|
|
350
348
|
scope={galleryScope}
|
|
351
349
|
currentFolderID={currentFolderID}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState } from "react";
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
2
|
|
|
3
3
|
import { Icon, Tooltip } from "@ax/components";
|
|
4
4
|
|
|
@@ -7,13 +7,19 @@ import type { IHeadingError } from "../../utils";
|
|
|
7
7
|
import * as S from "./style";
|
|
8
8
|
|
|
9
9
|
const ErrorsItem = (props: IErrorsItemProps) => {
|
|
10
|
-
const { error, onSelectHeading, onDelete } = props;
|
|
10
|
+
const { error, onSelectHeading, onDelete, onNavigateHeading, parentIsOpen } = props;
|
|
11
11
|
const { message, description, headingIds } = error;
|
|
12
12
|
|
|
13
13
|
const [isOpen, setIsOpen] = useState(false);
|
|
14
14
|
const [isDeleted, setIsDeleted] = useState(false);
|
|
15
15
|
const [currentIndex, setCurrentIndex] = useState<number | null>(null);
|
|
16
16
|
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (!parentIsOpen) {
|
|
19
|
+
setIsOpen(false);
|
|
20
|
+
}
|
|
21
|
+
}, [parentIsOpen]);
|
|
22
|
+
|
|
17
23
|
const handlePrevious = () => {
|
|
18
24
|
if (!currentIndex) return;
|
|
19
25
|
const newIndex = currentIndex - 1;
|
|
@@ -32,6 +38,7 @@ const ErrorsItem = (props: IErrorsItemProps) => {
|
|
|
32
38
|
setIsOpen(!isOpen);
|
|
33
39
|
setCurrentIndex(0);
|
|
34
40
|
onSelectHeading(headingIds[0])();
|
|
41
|
+
!isOpen && onNavigateHeading();
|
|
35
42
|
};
|
|
36
43
|
|
|
37
44
|
const errorText =
|
|
@@ -86,6 +93,8 @@ interface IErrorsItemProps {
|
|
|
86
93
|
error: IHeadingError;
|
|
87
94
|
onSelectHeading: (id: number) => () => void;
|
|
88
95
|
onDelete: () => void;
|
|
96
|
+
onNavigateHeading: () => void;
|
|
97
|
+
parentIsOpen: boolean;
|
|
89
98
|
}
|
|
90
99
|
|
|
91
100
|
export default ErrorsItem;
|
|
@@ -12,6 +12,7 @@ const ErrorsBanner = (props: IErrorsBannerProps) => {
|
|
|
12
12
|
|
|
13
13
|
const [isDeleted, setIsDeleted] = useState(false);
|
|
14
14
|
const [deletedErrorIndices, setDeletedErrorIndices] = useState<Set<number>>(new Set());
|
|
15
|
+
const [isSticky, setIsSticky] = useState(false);
|
|
15
16
|
|
|
16
17
|
const allErrorsDeleted = useMemo(
|
|
17
18
|
() => deletedErrorIndices.size === errors.length && errors.length > 0,
|
|
@@ -22,22 +23,37 @@ const ErrorsBanner = (props: IErrorsBannerProps) => {
|
|
|
22
23
|
setDeletedErrorIndices((prev) => new Set([...prev, index]));
|
|
23
24
|
};
|
|
24
25
|
|
|
26
|
+
const handleNavigateHeading = () => {
|
|
27
|
+
setIsSticky(true);
|
|
28
|
+
};
|
|
29
|
+
|
|
25
30
|
if (isDeleted || allErrorsDeleted) {
|
|
26
31
|
return <></>;
|
|
27
32
|
}
|
|
28
33
|
|
|
29
34
|
return (
|
|
30
|
-
<S.ErrorsWrapper>
|
|
35
|
+
<S.ErrorsWrapper isSticky={isSticky}>
|
|
31
36
|
<S.ErrorsHeader>
|
|
32
37
|
<S.WarningWrapper>
|
|
33
38
|
<Icon name="warning" size="16" />
|
|
34
39
|
</S.WarningWrapper>
|
|
35
40
|
<S.HeaderText>SEO Alerts</S.HeaderText>
|
|
36
41
|
<S.HeaderActions>
|
|
37
|
-
<S.ToggleWrapper
|
|
42
|
+
<S.ToggleWrapper
|
|
43
|
+
onClick={() => {
|
|
44
|
+
setIsOpen(!isOpen);
|
|
45
|
+
setIsSticky(false);
|
|
46
|
+
}}
|
|
47
|
+
>
|
|
38
48
|
<Icon name={isOpen ? "UpArrow" : "DownArrow"} size="24" />
|
|
39
49
|
</S.ToggleWrapper>
|
|
40
|
-
<S.IconWrapper
|
|
50
|
+
<S.IconWrapper
|
|
51
|
+
onClick={() => {
|
|
52
|
+
setIsDeleted(true);
|
|
53
|
+
setIsSticky(false);
|
|
54
|
+
setIsOpen(false);
|
|
55
|
+
}}
|
|
56
|
+
>
|
|
41
57
|
<Icon name="close" size="16" />
|
|
42
58
|
</S.IconWrapper>
|
|
43
59
|
</S.HeaderActions>
|
|
@@ -53,6 +69,8 @@ const ErrorsBanner = (props: IErrorsBannerProps) => {
|
|
|
53
69
|
error={error}
|
|
54
70
|
onSelectHeading={onSelectHeading}
|
|
55
71
|
onDelete={() => handleErrorDelete(index)}
|
|
72
|
+
onNavigateHeading={handleNavigateHeading}
|
|
73
|
+
parentIsOpen={isOpen}
|
|
56
74
|
/>
|
|
57
75
|
))}
|
|
58
76
|
</S.ErrorListWrapper>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import styled from "styled-components";
|
|
2
2
|
|
|
3
|
-
const ErrorsWrapper = styled.div
|
|
4
|
-
position: sticky;
|
|
3
|
+
const ErrorsWrapper = styled.div<{ isSticky: boolean }>`
|
|
4
|
+
position: ${(p) => (p.isSticky ? "sticky" : "static")};
|
|
5
5
|
top: 0;
|
|
6
6
|
background-color: ${(p) => p.theme.colors.uiBackground03};
|
|
7
7
|
width: 100%;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
|
|
3
|
-
import { EmptyState, FloatingPanel } from "@ax/components";
|
|
3
|
+
import { EmptyState, FloatingPanel, ToggleField } from "@ax/components";
|
|
4
4
|
import type { HeadingFilter, HeadingNode } from "@ax/types";
|
|
5
5
|
|
|
6
6
|
import ErrorsBanner from "./ErrorsBanner";
|
|
7
7
|
import HeadingItem from "./HeadingItem";
|
|
8
|
-
import { analyzeHeadings, extractUniqueHeadingTypes, filterHeadings, parseHeadingsTree } from "./utils";
|
|
8
|
+
import { analyzeHeadings, extractUniqueHeadingTypes, filterHeadings, hasHiddenHeadings, parseHeadingsTree } from "./utils";
|
|
9
9
|
|
|
10
10
|
import * as S from "./style";
|
|
11
11
|
|
|
@@ -17,12 +17,14 @@ const HeadingsPreviewModal = (props: IHeadingsPreviewProps) => {
|
|
|
17
17
|
|
|
18
18
|
const [headings, setHeadings] = useState<HeadingNode[]>([]);
|
|
19
19
|
const [selected, setSelected] = useState<number | null>(null);
|
|
20
|
+
const [toggleStatus, setToggleStatus] = useState<boolean>(true);
|
|
20
21
|
const [isErrorsBannerOpen, setIsErrorsBannerOpen] = useState(false);
|
|
21
22
|
const [errorsResetKey, setErrorsResetKey] = useState(0);
|
|
22
23
|
const listRef = useRef<HTMLDivElement>(null);
|
|
23
24
|
|
|
24
25
|
const filters = useMemo<HeadingFilter[]>(() => ["all", ...extractUniqueHeadingTypes(headings)], [headings]);
|
|
25
|
-
const filteredHeadings = useMemo(() => filterHeadings(headings, headingsFilter), [headings, headingsFilter]);
|
|
26
|
+
const filteredHeadings = useMemo(() => filterHeadings(headings, headingsFilter, toggleStatus), [headings, headingsFilter, toggleStatus]);
|
|
27
|
+
const showToggleWrapper = useMemo(() => hasHiddenHeadings(headings, headingsFilter), [headings, headingsFilter]);
|
|
26
28
|
const errors = useMemo(() => analyzeHeadings(filteredHeadings, isFiltering), [filteredHeadings, isFiltering]);
|
|
27
29
|
|
|
28
30
|
const getSEOHeadings = useCallback(() => {
|
|
@@ -117,6 +119,14 @@ const HeadingsPreviewModal = (props: IHeadingsPreviewProps) => {
|
|
|
117
119
|
</S.FilterItem>
|
|
118
120
|
))}
|
|
119
121
|
</S.FiltersWrapper>
|
|
122
|
+
{showToggleWrapper && (
|
|
123
|
+
<S.ToggleWrapper>
|
|
124
|
+
<S.ToggleText>
|
|
125
|
+
Show <strong>hidden headings</strong> (not visible on page)
|
|
126
|
+
</S.ToggleText>
|
|
127
|
+
<ToggleField name="hideHidden" value={toggleStatus} onChange={setToggleStatus} size="s" />
|
|
128
|
+
</S.ToggleWrapper>
|
|
129
|
+
)}
|
|
120
130
|
<S.HeadingsListWrapper ref={listRef}>
|
|
121
131
|
{filteredHeadings.map((head, index) => (
|
|
122
132
|
<HeadingItem
|
|
@@ -45,6 +45,22 @@ const FiltersWrapper = styled.div`
|
|
|
45
45
|
}
|
|
46
46
|
`;
|
|
47
47
|
|
|
48
|
+
const ToggleWrapper = styled.div`
|
|
49
|
+
display: flex;
|
|
50
|
+
background-color: ${(p) => p.theme.colors.uiBackground03};
|
|
51
|
+
border-radius: ${(p) => p.theme.radii.s};
|
|
52
|
+
padding: ${(p) => p.theme.spacing.xs};
|
|
53
|
+
margin-bottom: ${(p) => p.theme.spacing.xs};
|
|
54
|
+
gap: ${(p) => p.theme.spacing.xs};
|
|
55
|
+
width: 100%;
|
|
56
|
+
justify-content: space-between;
|
|
57
|
+
align-items: center;
|
|
58
|
+
`;
|
|
59
|
+
|
|
60
|
+
const ToggleText = styled.div`
|
|
61
|
+
${(p) => p.theme.textStyle.uiXS};
|
|
62
|
+
`;
|
|
63
|
+
|
|
48
64
|
const FilterText = styled.div`
|
|
49
65
|
${(p) => p.theme.textStyle.uiS};
|
|
50
66
|
color: ${(p) => p.theme.colors.textHighEmphasis};
|
|
@@ -79,4 +95,6 @@ export {
|
|
|
79
95
|
FilterItem,
|
|
80
96
|
HeadingsListWrapper,
|
|
81
97
|
EmptyWrapper,
|
|
98
|
+
ToggleWrapper,
|
|
99
|
+
ToggleText,
|
|
82
100
|
};
|
|
@@ -275,11 +275,29 @@ const extractUniqueHeadingTypes = (headings: HeadingNode[]): HeadingFilter[] =>
|
|
|
275
275
|
return Array.from(types).sort();
|
|
276
276
|
};
|
|
277
277
|
|
|
278
|
-
const filterHeadings = (headings: HeadingNode[], selectedType: string): HeadingNode[] => {
|
|
279
|
-
if (selectedType === "all") return headings;
|
|
278
|
+
const filterHeadings = (headings: HeadingNode[], selectedType: string, includeHidden = true): HeadingNode[] => {
|
|
279
|
+
if (selectedType === "all" && includeHidden) return headings;
|
|
280
280
|
|
|
281
|
+
if (selectedType === "all") {
|
|
282
|
+
// Filter by visibility but keep hierarchy
|
|
283
|
+
const filterNode = (node: HeadingNode): HeadingNode | null => {
|
|
284
|
+
const isVisible = !node.isHidden;
|
|
285
|
+
const filteredChildren = node.children.map(filterNode).filter((n): n is HeadingNode => n !== null);
|
|
286
|
+
|
|
287
|
+
if (isVisible) {
|
|
288
|
+
return { ...node, children: filteredChildren };
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return filteredChildren.length > 0 ? { ...node, children: filteredChildren } : null;
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
return headings.map(filterNode).filter((n): n is HeadingNode => n !== null);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// For specific types, flatten the result
|
|
281
298
|
return flattenHeadings(headings)
|
|
282
299
|
.filter((h) => h.tag === selectedType)
|
|
300
|
+
.filter((h) => includeHidden || !h.isHidden)
|
|
283
301
|
.map((h) => ({
|
|
284
302
|
level: h.level,
|
|
285
303
|
tag: h.tag,
|
|
@@ -289,6 +307,16 @@ const filterHeadings = (headings: HeadingNode[], selectedType: string): HeadingN
|
|
|
289
307
|
}));
|
|
290
308
|
};
|
|
291
309
|
|
|
310
|
+
const hasHiddenHeadings = (headings: HeadingNode[], filterType = "all"): boolean => {
|
|
311
|
+
const check = (node: HeadingNode): boolean => {
|
|
312
|
+
const matchesType = filterType === "all" || node.tag === filterType;
|
|
313
|
+
if (matchesType && node.isHidden) return true;
|
|
314
|
+
return node.children.length > 0 && node.children.some(check);
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
return headings.some(check);
|
|
318
|
+
};
|
|
319
|
+
|
|
292
320
|
interface IHeadingError {
|
|
293
321
|
message: string;
|
|
294
322
|
description: React.ReactNode;
|
|
@@ -303,5 +331,5 @@ interface IFlatHeading {
|
|
|
303
331
|
isHidden: boolean;
|
|
304
332
|
}
|
|
305
333
|
|
|
306
|
-
export { parseHeadingsTree, extractUniqueHeadingTypes, filterHeadings, analyzeHeadings, getHeadColor };
|
|
334
|
+
export { parseHeadingsTree, extractUniqueHeadingTypes, filterHeadings, analyzeHeadings, getHeadColor, hasHiddenHeadings };
|
|
307
335
|
export type { IHeadingError };
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { memo, useCallback, useState } from "react";
|
|
2
|
+
import type { Area } from "react-easy-crop";
|
|
3
|
+
import Cropper from "react-easy-crop";
|
|
4
|
+
|
|
5
|
+
import { Button } from "@ax/components";
|
|
6
|
+
|
|
7
|
+
import * as S from "./style";
|
|
8
|
+
|
|
9
|
+
const MIN_ZOOM = 1;
|
|
10
|
+
const MAX_ZOOM = 3;
|
|
11
|
+
const ZOOM_STEP = 0.1;
|
|
12
|
+
|
|
13
|
+
const CropStep = (props: IProps) => {
|
|
14
|
+
const { imageSrc, onConfirm, onCancel } = props;
|
|
15
|
+
|
|
16
|
+
const [crop, setCrop] = useState({ x: 0, y: 0 });
|
|
17
|
+
const [zoom, setZoom] = useState(1);
|
|
18
|
+
const [croppedAreaPixels, setCroppedAreaPixels] = useState<Area | null>(null);
|
|
19
|
+
const [objectFit, setObjectFit] = useState<"vertical-cover" | "horizontal-cover">("vertical-cover");
|
|
20
|
+
|
|
21
|
+
const onCropComplete = useCallback((_: Area, pixels: Area) => {
|
|
22
|
+
setCroppedAreaPixels(pixels);
|
|
23
|
+
}, []);
|
|
24
|
+
|
|
25
|
+
const onMediaLoaded = useCallback((mediaSize: { width: number; height: number }) => {
|
|
26
|
+
const isPortrait = mediaSize.height > mediaSize.width;
|
|
27
|
+
setObjectFit(isPortrait ? "vertical-cover" : "horizontal-cover");
|
|
28
|
+
}, []);
|
|
29
|
+
|
|
30
|
+
const handleConfirm = () => {
|
|
31
|
+
if (croppedAreaPixels) onConfirm(croppedAreaPixels);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const handleZoomIn = () => setZoom((z) => Math.min(z + ZOOM_STEP, MAX_ZOOM));
|
|
35
|
+
const handleZoomOut = () => setZoom((z) => Math.max(z - ZOOM_STEP, MIN_ZOOM));
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<S.Wrapper>
|
|
39
|
+
<S.CropArea>
|
|
40
|
+
<S.CropContent>
|
|
41
|
+
<S.CropContainer>
|
|
42
|
+
<Cropper
|
|
43
|
+
image={imageSrc}
|
|
44
|
+
crop={crop}
|
|
45
|
+
zoom={zoom}
|
|
46
|
+
aspect={1}
|
|
47
|
+
cropShape="round"
|
|
48
|
+
showGrid={false}
|
|
49
|
+
onCropChange={setCrop}
|
|
50
|
+
onZoomChange={setZoom}
|
|
51
|
+
onCropComplete={onCropComplete}
|
|
52
|
+
onMediaLoaded={onMediaLoaded}
|
|
53
|
+
objectFit={objectFit}
|
|
54
|
+
/>
|
|
55
|
+
</S.CropContainer>
|
|
56
|
+
<S.ZoomControls>
|
|
57
|
+
<S.ZoomButton type="button" onClick={handleZoomIn} aria-label="Zoom in">
|
|
58
|
+
+
|
|
59
|
+
</S.ZoomButton>
|
|
60
|
+
<S.ZoomSlider
|
|
61
|
+
type="range"
|
|
62
|
+
min={MIN_ZOOM}
|
|
63
|
+
max={MAX_ZOOM}
|
|
64
|
+
step={ZOOM_STEP}
|
|
65
|
+
value={zoom}
|
|
66
|
+
onChange={(e) => setZoom(Number(e.target.value))}
|
|
67
|
+
aria-label="Zoom"
|
|
68
|
+
/>
|
|
69
|
+
<S.ZoomButton type="button" onClick={handleZoomOut} aria-label="Zoom out">
|
|
70
|
+
{"\u2212"}
|
|
71
|
+
</S.ZoomButton>
|
|
72
|
+
</S.ZoomControls>
|
|
73
|
+
</S.CropContent>
|
|
74
|
+
</S.CropArea>
|
|
75
|
+
<S.CancelButtonContainer>
|
|
76
|
+
<Button type="button" buttonStyle="minimal" icon="refresh" onClick={onCancel}>
|
|
77
|
+
Upload new image
|
|
78
|
+
</Button>
|
|
79
|
+
</S.CancelButtonContainer>
|
|
80
|
+
<S.Actions>
|
|
81
|
+
<Button type="button" buttonStyle="solid" onClick={handleConfirm}>
|
|
82
|
+
Done
|
|
83
|
+
</Button>
|
|
84
|
+
</S.Actions>
|
|
85
|
+
</S.Wrapper>
|
|
86
|
+
);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
interface IProps {
|
|
90
|
+
imageSrc: string;
|
|
91
|
+
onConfirm: (croppedAreaPixels: Area) => void;
|
|
92
|
+
onCancel: () => void;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export default memo(CropStep);
|