@griddo/ax 11.14.2-rc.0 → 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/KeywordsPreviewModal/KeywordsPreviewModal.test.tsx +3 -4
- 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/KeywordsPreviewModal/atoms.tsx +2 -2
- package/src/components/KeywordsPreviewModal/index.tsx +6 -6
- package/src/components/KeywordsPreviewModal/utils.tsx +2 -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
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { getCompanyFromEmail, getMostFrequentRole } from "@ax/modules/Sites/SitesList/WelcomeModal/utils";
|
|
2
|
+
|
|
3
|
+
describe("getCompanyFromEmail", () => {
|
|
4
|
+
it("should extract company name from professional email", () => {
|
|
5
|
+
expect(getCompanyFromEmail("user@secuoyas.com")).toBe("Secuoyas");
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
it("should capitalize the first letter of the company name", () => {
|
|
9
|
+
expect(getCompanyFromEmail("user@acme.io")).toBe("Acme");
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("should return empty string for generic email providers", () => {
|
|
13
|
+
expect(getCompanyFromEmail("user@gmail.com")).toBe("");
|
|
14
|
+
expect(getCompanyFromEmail("user@hotmail.com")).toBe("");
|
|
15
|
+
expect(getCompanyFromEmail("user@outlook.com")).toBe("");
|
|
16
|
+
expect(getCompanyFromEmail("user@yahoo.com")).toBe("");
|
|
17
|
+
expect(getCompanyFromEmail("user@icloud.com")).toBe("");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("should return empty string when email has no domain", () => {
|
|
21
|
+
expect(getCompanyFromEmail("usernodomain")).toBe("");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("should return the full subdomain string before the TLD", () => {
|
|
25
|
+
expect(getCompanyFromEmail("user@mail.company.com")).toBe("Mail.company");
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe("getMostFrequentRole", () => {
|
|
30
|
+
it("should return null for empty userRoles", () => {
|
|
31
|
+
expect(getMostFrequentRole([])).toBeNull();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("should return the ID of the most frequent role", () => {
|
|
35
|
+
const userRoles = [
|
|
36
|
+
{ siteId: 1, roles: [1, 2] },
|
|
37
|
+
{ siteId: 2, roles: [1] },
|
|
38
|
+
];
|
|
39
|
+
expect(getMostFrequentRole(userRoles)).toBe(1);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("should handle single role occurrence", () => {
|
|
43
|
+
const userRoles = [{ siteId: 1, roles: [5] }];
|
|
44
|
+
expect(getMostFrequentRole(userRoles)).toBe(5);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("should return correct role when multiple roles have different frequencies", () => {
|
|
48
|
+
const userRoles = [
|
|
49
|
+
{ siteId: 1, roles: [1, 2, 3] },
|
|
50
|
+
{ siteId: 2, roles: [1, 2] },
|
|
51
|
+
{ siteId: 3, roles: [1] },
|
|
52
|
+
];
|
|
53
|
+
expect(getMostFrequentRole(userRoles)).toBe(1);
|
|
54
|
+
});
|
|
55
|
+
});
|
package/src/api/sites.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { AxiosResponse } from "axios";
|
|
2
|
-
import { IGetGlobalPagesParams, IGetSitePagesParams, IGetSitesParams } from "@ax/types";
|
|
1
|
+
import type { AxiosResponse } from "axios";
|
|
2
|
+
import type { IGetGlobalPagesParams, IGetSitePagesParams, IGetSitesParams } from "@ax/types";
|
|
3
3
|
import { template } from "./config";
|
|
4
|
-
import { sendRequest, IServiceConfig, sendInitialRequest } from "./utils";
|
|
4
|
+
import { sendRequest, type IServiceConfig, sendInitialRequest } from "./utils";
|
|
5
5
|
|
|
6
6
|
const SERVICES: { [key: string]: IServiceConfig } = {
|
|
7
7
|
GET_ALL_SITES: {
|
|
@@ -123,7 +123,7 @@ const SERVICES: { [key: string]: IServiceConfig } = {
|
|
|
123
123
|
|
|
124
124
|
const getAllSites = async (params: IGetSitesParams = { recentSitesNumber: 7 }) => {
|
|
125
125
|
const { host, endpoint } = SERVICES.GET_ALL_SITES;
|
|
126
|
-
const { token, language, recentSitesNumber, searchQuery, filterQuery, page, itemsPerPage, pagination } = params;
|
|
126
|
+
const { token, language, recentSitesNumber = 7, searchQuery, filterQuery, page, itemsPerPage, pagination } = params;
|
|
127
127
|
|
|
128
128
|
const queryParams =
|
|
129
129
|
language && recentSitesNumber
|
|
@@ -1,14 +1,32 @@
|
|
|
1
|
-
import React from "react";
|
|
2
1
|
import { getInitials } from "@ax/helpers";
|
|
3
2
|
|
|
4
3
|
import * as S from "./style";
|
|
5
4
|
|
|
6
|
-
const
|
|
7
|
-
|
|
5
|
+
const AVATAR_COLORS = ["#7C9CDE", "#E07C7C", "#7CC8A0", "#C97CCC", "#D4A76A", "#6AB8C8", "#C8A86A", "#8E7CC8"];
|
|
6
|
+
|
|
7
|
+
const getAvatarColor = (name?: string): string => {
|
|
8
|
+
if (!name) return AVATAR_COLORS[0];
|
|
9
|
+
let hash = 0;
|
|
10
|
+
const normalized = name.toLowerCase();
|
|
11
|
+
for (let i = 0; i < normalized.length; i++) {
|
|
12
|
+
hash = ((hash << 5) - hash) + normalized.charCodeAt(i);
|
|
13
|
+
hash = hash & hash; // Convert to 32bit integer
|
|
14
|
+
}
|
|
15
|
+
return AVATAR_COLORS[Math.abs(hash) % AVATAR_COLORS.length];
|
|
16
|
+
};
|
|
8
17
|
|
|
18
|
+
const Avatar = (props: IAvatarProps): JSX.Element => {
|
|
19
|
+
const { image, name, size, fontSize, bold, background } = props;
|
|
9
20
|
return (
|
|
10
21
|
<S.AvatarWrapper data-testid="avatar-wrapper" size={size}>
|
|
11
|
-
<S.Avatar
|
|
22
|
+
<S.Avatar
|
|
23
|
+
image={image}
|
|
24
|
+
initials={getInitials(name)}
|
|
25
|
+
color={background ? getAvatarColor(name) : undefined}
|
|
26
|
+
fontSize={fontSize}
|
|
27
|
+
bold={bold}
|
|
28
|
+
data-testid="avatar"
|
|
29
|
+
/>
|
|
12
30
|
</S.AvatarWrapper>
|
|
13
31
|
);
|
|
14
32
|
};
|
|
@@ -16,7 +34,10 @@ const Avatar = (props: IAvatarProps): JSX.Element => {
|
|
|
16
34
|
export interface IAvatarProps {
|
|
17
35
|
image?: string | null;
|
|
18
36
|
name?: string;
|
|
19
|
-
size?:
|
|
37
|
+
size?: number;
|
|
38
|
+
fontSize?: number;
|
|
39
|
+
background?: boolean;
|
|
40
|
+
bold?: boolean;
|
|
20
41
|
}
|
|
21
42
|
|
|
22
43
|
export default Avatar;
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import styled from "styled-components";
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
const Avatar = styled.span<{
|
|
4
|
+
image?: string | null;
|
|
5
|
+
initials: string;
|
|
6
|
+
color?: string;
|
|
7
|
+
fontSize?: number;
|
|
8
|
+
bold?: boolean;
|
|
9
|
+
}>`
|
|
9
10
|
border-radius: 50%;
|
|
10
|
-
|
|
11
|
-
height: ${(p) => (p.size === "s" ? " 24px" : "32px")};
|
|
12
|
-
background: ${(p) => `url(${p.image})`}, ${(p) => p.theme.colors.uiBackground03};
|
|
11
|
+
background: ${(p) => `url(${p.image})`}, ${(p) => (p.color ? p.color : p.theme.colors.uiBackground03)};
|
|
13
12
|
background-size: cover;
|
|
14
13
|
background-repeat: no-repeat;
|
|
15
14
|
background-position: center center;
|
|
@@ -19,8 +18,19 @@ const Avatar = styled.span<{ image?: string | null; initials: string; size?: str
|
|
|
19
18
|
|
|
20
19
|
&:after {
|
|
21
20
|
content: ${(p) => !p.image && JSON.stringify(p.initials)};
|
|
22
|
-
color: ${(p) => p.theme.colors.textMediumEmphasis};
|
|
21
|
+
color: ${(p) => (p.bold ? p.theme.colors.textHighEmphasis : p.theme.colors.textMediumEmphasis)};
|
|
23
22
|
${(p) => p.theme.textStyle.uiXS};
|
|
23
|
+
font-size: ${(p) => (p.fontSize ? `${p.fontSize}px` : "12px")};
|
|
24
|
+
}
|
|
25
|
+
`;
|
|
26
|
+
|
|
27
|
+
const AvatarWrapper = styled.div<{ size?: number }>`
|
|
28
|
+
width: ${(p) => (p.size ? `${p.size}px` : "32px")};
|
|
29
|
+
height: ${(p) => (p.size ? `${p.size}px` : "32px")};
|
|
30
|
+
|
|
31
|
+
${Avatar} {
|
|
32
|
+
width: ${(p) => (p.size ? `${p.size}px` : "32px")};
|
|
33
|
+
height: ${(p) => (p.size ? `${p.size}px` : "32px")};
|
|
24
34
|
}
|
|
25
35
|
`;
|
|
26
36
|
|
|
@@ -88,10 +88,16 @@ const Browser = (props: IBrowserProps): JSX.Element => {
|
|
|
88
88
|
const el = frameWrapperRef.current;
|
|
89
89
|
if (!el) return;
|
|
90
90
|
|
|
91
|
+
const getScrollbarWidth = () => {
|
|
92
|
+
return window.innerWidth - document.documentElement.clientWidth || 15;
|
|
93
|
+
};
|
|
94
|
+
|
|
91
95
|
let lastWidth = 0;
|
|
96
|
+
const scrollbarThreshold = getScrollbarWidth();
|
|
97
|
+
|
|
92
98
|
const observer = new ResizeObserver(([entry]) => {
|
|
93
99
|
const containerWidth = entry.contentRect.width;
|
|
94
|
-
if (containerWidth > 0 && Math.abs(containerWidth - lastWidth) >
|
|
100
|
+
if (containerWidth > 0 && Math.abs(containerWidth - lastWidth) > scrollbarThreshold) {
|
|
95
101
|
lastWidth = containerWidth;
|
|
96
102
|
const resolution = calcDefaultResolution(containerWidth, resolutionOptions);
|
|
97
103
|
const newZoom = calcAutoZoom(containerWidth, resolution);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
2
|
|
|
3
3
|
import { isEmptyObj } from "@ax/helpers";
|
|
4
4
|
import { Loading } from "@ax/components";
|
|
5
|
-
import { IBreadcrumbItem, IUserEditing } from "@ax/types";
|
|
5
|
+
import type { IBreadcrumbItem, IUserEditing } from "@ax/types";
|
|
6
6
|
|
|
7
7
|
import Form from "./Form";
|
|
8
8
|
import NavigationForm from "./NavigationForm";
|
|
@@ -11,9 +11,10 @@ import PreviewForm from "./PreviewForm";
|
|
|
11
11
|
import Header from "./Header";
|
|
12
12
|
|
|
13
13
|
import * as S from "./style";
|
|
14
|
+
|
|
14
15
|
const navigationModulesTypes = ["header", "footer"];
|
|
15
16
|
|
|
16
|
-
const ConfigPanel = (props: IStateProps)
|
|
17
|
+
const ConfigPanel = (props: IStateProps) => {
|
|
17
18
|
const {
|
|
18
19
|
isLoading,
|
|
19
20
|
schema,
|
|
@@ -48,7 +49,7 @@ const ConfigPanel = (props: IStateProps): JSX.Element => {
|
|
|
48
49
|
useEffect(() => {
|
|
49
50
|
if (lastElementAddedId && wrapperRef.current) {
|
|
50
51
|
const element = document.querySelector(`.editorId-${lastElementAddedId}`);
|
|
51
|
-
element
|
|
52
|
+
element?.scrollIntoView();
|
|
52
53
|
}
|
|
53
54
|
}, [lastElementAddedId]);
|
|
54
55
|
|
|
@@ -1,8 +1,51 @@
|
|
|
1
|
-
import React from "react";
|
|
1
|
+
import type React from "react";
|
|
2
|
+
import { useRef, useState } from "react";
|
|
3
|
+
import { createPortal } from "react-dom";
|
|
4
|
+
|
|
2
5
|
import { trimText } from "@ax/helpers";
|
|
3
6
|
|
|
4
7
|
import * as S from "./style";
|
|
5
8
|
|
|
9
|
+
const PortalTooltip = (props: {
|
|
10
|
+
elementsRows: string[][];
|
|
11
|
+
defaultColor?: string;
|
|
12
|
+
colors?: any;
|
|
13
|
+
prefix: string;
|
|
14
|
+
rounded: boolean;
|
|
15
|
+
size: "S" | "M";
|
|
16
|
+
tooltipRef: React.RefObject<HTMLDivElement>;
|
|
17
|
+
isVisible: boolean;
|
|
18
|
+
elementRect: DOMRect | null;
|
|
19
|
+
}) => {
|
|
20
|
+
const { elementsRows, defaultColor, colors, prefix, rounded, size, tooltipRef, isVisible, elementRect } = props;
|
|
21
|
+
|
|
22
|
+
if (!isVisible || !elementRect) return null;
|
|
23
|
+
|
|
24
|
+
const top = elementRect.top - 8;
|
|
25
|
+
const left = elementRect.left + elementRect.width / 2;
|
|
26
|
+
|
|
27
|
+
return createPortal(
|
|
28
|
+
<S.PortalTooltip data-testid="portal-tooltip" style={{ top, left }} ref={tooltipRef}>
|
|
29
|
+
{elementsRows.map((row, idx) => {
|
|
30
|
+
return (
|
|
31
|
+
<S.Row data-testid="row-element" key={idx}>
|
|
32
|
+
{row.map((element, idx) => {
|
|
33
|
+
const color = defaultColor || colors?.[element];
|
|
34
|
+
return (
|
|
35
|
+
<S.Element data-testid="div-element" key={idx} color={color} rounded={rounded} size={size}>
|
|
36
|
+
{prefix}
|
|
37
|
+
{element}
|
|
38
|
+
</S.Element>
|
|
39
|
+
);
|
|
40
|
+
})}
|
|
41
|
+
</S.Row>
|
|
42
|
+
);
|
|
43
|
+
})}
|
|
44
|
+
</S.PortalTooltip>,
|
|
45
|
+
document.body,
|
|
46
|
+
);
|
|
47
|
+
};
|
|
48
|
+
|
|
6
49
|
const ElementsTooltip = (props: IElementsTooltipProps): JSX.Element => {
|
|
7
50
|
const {
|
|
8
51
|
elements,
|
|
@@ -16,6 +59,11 @@ const ElementsTooltip = (props: IElementsTooltipProps): JSX.Element => {
|
|
|
16
59
|
size = "S",
|
|
17
60
|
} = props;
|
|
18
61
|
|
|
62
|
+
const tooltipRef = useRef<HTMLDivElement>(null);
|
|
63
|
+
const elementRef = useRef<HTMLDivElement>(null);
|
|
64
|
+
const [isTooltipVisible, setIsTooltipVisible] = useState(false);
|
|
65
|
+
const [elementRect, setElementRect] = useState<DOMRect | null>(null);
|
|
66
|
+
|
|
19
67
|
if (!elements) return <></>;
|
|
20
68
|
|
|
21
69
|
const visibleElements = elements.slice(0, defaultElements);
|
|
@@ -28,42 +76,56 @@ const ElementsTooltip = (props: IElementsTooltipProps): JSX.Element => {
|
|
|
28
76
|
elementsRows[row] = isNewRow ? [element] : [...elementsRows[row], element];
|
|
29
77
|
});
|
|
30
78
|
|
|
79
|
+
const handleMouseEnter = () => {
|
|
80
|
+
if (elementRef.current) {
|
|
81
|
+
setElementRect(elementRef.current.getBoundingClientRect());
|
|
82
|
+
}
|
|
83
|
+
setIsTooltipVisible(true);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const handleMouseLeave = () => {
|
|
87
|
+
setIsTooltipVisible(false);
|
|
88
|
+
};
|
|
89
|
+
|
|
31
90
|
return (
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
91
|
+
<>
|
|
92
|
+
<S.Wrapper data-testid="elements-wrapper">
|
|
93
|
+
{visibleElements.map((fullElement, idx) => {
|
|
94
|
+
const element = defaultElements === 1 && maxChar ? trimText(fullElement, maxChar) : fullElement;
|
|
95
|
+
const color = defaultColor || colors?.[element];
|
|
36
96
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
97
|
+
return (
|
|
98
|
+
<S.Element data-testid="element" key={idx} color={color} rounded={rounded} size={size}>
|
|
99
|
+
{prefix}
|
|
100
|
+
{element}
|
|
101
|
+
</S.Element>
|
|
102
|
+
);
|
|
103
|
+
})}
|
|
104
|
+
{remainingElements > 0 && (
|
|
105
|
+
<S.Element
|
|
106
|
+
data-testid="remaining-element"
|
|
107
|
+
rounded={rounded}
|
|
108
|
+
size={size}
|
|
109
|
+
ref={elementRef}
|
|
110
|
+
onMouseEnter={handleMouseEnter}
|
|
111
|
+
onMouseLeave={handleMouseLeave}
|
|
112
|
+
>
|
|
113
|
+
+{remainingElements}
|
|
41
114
|
</S.Element>
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
{element}
|
|
57
|
-
</S.Element>
|
|
58
|
-
);
|
|
59
|
-
})}
|
|
60
|
-
</S.Row>
|
|
61
|
-
);
|
|
62
|
-
})}
|
|
63
|
-
</S.Tooltip>
|
|
64
|
-
</S.Element>
|
|
65
|
-
)}
|
|
66
|
-
</S.Wrapper>
|
|
115
|
+
)}
|
|
116
|
+
</S.Wrapper>
|
|
117
|
+
<PortalTooltip
|
|
118
|
+
elementsRows={elementsRows}
|
|
119
|
+
defaultColor={defaultColor}
|
|
120
|
+
colors={colors}
|
|
121
|
+
prefix={prefix}
|
|
122
|
+
rounded={rounded}
|
|
123
|
+
size={size}
|
|
124
|
+
tooltipRef={tooltipRef}
|
|
125
|
+
isVisible={isTooltipVisible}
|
|
126
|
+
elementRect={elementRect}
|
|
127
|
+
/>
|
|
128
|
+
</>
|
|
67
129
|
);
|
|
68
130
|
};
|
|
69
131
|
|
|
@@ -26,6 +26,7 @@ const Element = styled.div<{ color?: string; rounded: boolean; size: "S" | "M" }
|
|
|
26
26
|
background-color: ${(p) => (p.color ? p.color : p.theme.colors.uiBackground01)};
|
|
27
27
|
border-radius: ${(p) => (p.rounded ? "34px" : p.theme.radii.xs)};
|
|
28
28
|
white-space: nowrap;
|
|
29
|
+
cursor: pointer;
|
|
29
30
|
|
|
30
31
|
&:hover > ${Tooltip} {
|
|
31
32
|
display: block;
|
|
@@ -36,4 +37,14 @@ const Row = styled.div`
|
|
|
36
37
|
display: flex;
|
|
37
38
|
`;
|
|
38
39
|
|
|
39
|
-
|
|
40
|
+
const PortalTooltip = styled.div`
|
|
41
|
+
position: fixed;
|
|
42
|
+
z-index: 9999;
|
|
43
|
+
padding: ${(p) => p.theme.spacing.s};
|
|
44
|
+
box-shadow: ${(p) => p.theme.shadow.shadowL};
|
|
45
|
+
background-color: ${(p) => p.theme.colors.uiBarBackground};
|
|
46
|
+
border-radius: ${(p) => p.theme.radii.s};
|
|
47
|
+
transform: translate(-50%, -100%);
|
|
48
|
+
`;
|
|
49
|
+
|
|
50
|
+
export { Wrapper, Element, Tooltip, Row, PortalTooltip };
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import { useModal } from "@ax/hooks";
|
|
4
|
-
import { ISite } from "@ax/types";
|
|
1
|
+
import { FileGallery, Icon, Modal, TextField } from "@ax/components";
|
|
2
|
+
import { VALID_DOCUMENT_FORMATS } from "@ax/constants";
|
|
5
3
|
import { formatBytes, getFormattedDateWithTimezone } from "@ax/helpers";
|
|
6
|
-
import {
|
|
4
|
+
import { useModal } from "@ax/hooks";
|
|
5
|
+
import type { ISite } from "@ax/types";
|
|
7
6
|
|
|
8
7
|
import * as S from "./style";
|
|
9
8
|
|
|
@@ -12,11 +11,8 @@ const FileField = (props: IFileFieldProps): JSX.Element => {
|
|
|
12
11
|
|
|
13
12
|
const { isOpen, toggleModal } = useModal(false);
|
|
14
13
|
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
? allowedFormats
|
|
18
|
-
: ["pdf", "doc", "docx", "xls", "xlsx", "zip", "csv", "txt", "mov", "mp4", "wmv", "avi", "webm", "mkv"];
|
|
19
|
-
const videoFormats = ["mov", "mp4", "wmv", "avi", "webm", "mkv"];
|
|
14
|
+
const hasCustomFormats = !!allowedFormats?.length;
|
|
15
|
+
|
|
20
16
|
const handleClick = () => {
|
|
21
17
|
if (!disabled) {
|
|
22
18
|
toggleModal();
|
|
@@ -29,7 +25,7 @@ const FileField = (props: IFileFieldProps): JSX.Element => {
|
|
|
29
25
|
};
|
|
30
26
|
|
|
31
27
|
const handleOnClickUrl = () => {
|
|
32
|
-
if (value
|
|
28
|
+
if (value?.url) {
|
|
33
29
|
const win = window.open(value.url, "_blank");
|
|
34
30
|
if (win) {
|
|
35
31
|
win.focus();
|
|
@@ -60,9 +56,9 @@ const FileField = (props: IFileFieldProps): JSX.Element => {
|
|
|
60
56
|
},
|
|
61
57
|
];
|
|
62
58
|
|
|
63
|
-
const fileName = value
|
|
64
|
-
const fileDate = value
|
|
65
|
-
const fileSize = value
|
|
59
|
+
const fileName = value?.url ? value.url.split("/").pop() : "File title";
|
|
60
|
+
const fileDate = value?.uploadDate ? getFormattedDateWithTimezone(value.uploadDate, "d MMM Y") : "--";
|
|
61
|
+
const fileSize = value?.sizeBytes ? formatBytes(value.sizeBytes) : "-- KB";
|
|
66
62
|
|
|
67
63
|
return (
|
|
68
64
|
<>
|
|
@@ -73,7 +69,7 @@ const FileField = (props: IFileFieldProps): JSX.Element => {
|
|
|
73
69
|
<div>{fileSize}</div>
|
|
74
70
|
</S.FileData>
|
|
75
71
|
</S.FileDataWrapper>
|
|
76
|
-
{value
|
|
72
|
+
{value?.url && (
|
|
77
73
|
<S.TextFieldWrapper data-testid="text-field-wrapper">
|
|
78
74
|
<TextField
|
|
79
75
|
name="url"
|
|
@@ -93,7 +89,8 @@ const FileField = (props: IFileFieldProps): JSX.Element => {
|
|
|
93
89
|
</S.IconWrapper>
|
|
94
90
|
</S.Field>
|
|
95
91
|
<S.HelpText>
|
|
96
|
-
Valid formats:
|
|
92
|
+
Valid formats:{" "}
|
|
93
|
+
{hasCustomFormats ? allowedFormats.join(", ") : `${VALID_DOCUMENT_FORMATS.join(", ")} and videos`}. Max.
|
|
97
94
|
size: 50MB
|
|
98
95
|
</S.HelpText>
|
|
99
96
|
</>
|
|
@@ -108,7 +105,9 @@ const FileField = (props: IFileFieldProps): JSX.Element => {
|
|
|
108
105
|
</S.Component>
|
|
109
106
|
)}
|
|
110
107
|
<Modal isOpen={isOpen} hide={toggleModal} size="XL" title="Select file">
|
|
111
|
-
{isOpen &&
|
|
108
|
+
{isOpen && (
|
|
109
|
+
<FileGallery customFormats={allowedFormats} addFile={addFile} toggleModal={toggleModal} site={site} />
|
|
110
|
+
)}
|
|
112
111
|
</Modal>
|
|
113
112
|
</>
|
|
114
113
|
);
|
|
@@ -28,7 +28,7 @@ const HeadingField = (props: IHeadingFieldProps): JSX.Element => {
|
|
|
28
28
|
return (
|
|
29
29
|
<>
|
|
30
30
|
{toolbar ? (
|
|
31
|
-
<Wysiwyg
|
|
31
|
+
<Wysiwyg site={site} inline={true} value={contentValue} onChange={handleChange} />
|
|
32
32
|
) : (
|
|
33
33
|
<TextField value={contentValue} onChange={handleChange} />
|
|
34
34
|
)}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { Icon, IconAction, Gallery, Modal, Image, FieldsBehavior } from "@ax/components";
|
|
1
|
+
import { memo, useEffect, useMemo, useRef, useState } from "react";
|
|
3
2
|
|
|
3
|
+
import { FieldsBehavior, Gallery, Icon, IconAction, Image, ImageDragAndDrop, Modal } from "@ax/components";
|
|
4
4
|
import { formatBytes, getFormattedDateWithTimezone } from "@ax/helpers";
|
|
5
|
-
import { IImage, ISite } from "@ax/types";
|
|
6
5
|
import { useModal } from "@ax/hooks";
|
|
7
|
-
import
|
|
6
|
+
import type { IImage, ISite } from "@ax/types";
|
|
8
7
|
|
|
9
8
|
import * as S from "./style";
|
|
10
9
|
|
|
@@ -30,42 +29,17 @@ const ImageField = (props: IImageFieldProps) => {
|
|
|
30
29
|
const { isOpen, toggleModal } = useModal(isModalOpen);
|
|
31
30
|
const { isOpen: isOpenDD, toggleModal: toggleModalDD } = useModal(isModalOpen);
|
|
32
31
|
const [previewSrc, setPreviewSrc] = useState<string>();
|
|
33
|
-
const [previewHeight, setPreviewHeight] = useState<{ height: string | number }>({ height: "auto" });
|
|
34
32
|
const previewRef = useRef<HTMLDivElement>(null);
|
|
35
|
-
const [imageLoaded, setImageLoaded] = useState(false);
|
|
36
|
-
|
|
37
|
-
const validFormats = ["jpeg", "jpg", "png", "svg", "gif"];
|
|
38
33
|
|
|
39
34
|
const imageUrl = value ? (typeof value === "string" ? value : value.url) : "";
|
|
40
35
|
const imagePosition = value && typeof value === "object" ? value.position : "center";
|
|
41
36
|
|
|
42
37
|
useEffect(() => {
|
|
43
38
|
setPreviewSrc(imageUrl);
|
|
44
|
-
setImageLoaded(false);
|
|
45
39
|
}, [imageUrl]);
|
|
46
40
|
|
|
47
|
-
const calculateImageHeight = () => {
|
|
48
|
-
if (previewRef.current) {
|
|
49
|
-
const height = previewRef.current.getBoundingClientRect().height;
|
|
50
|
-
|
|
51
|
-
if (!cropPreview && height < 180) {
|
|
52
|
-
setPreviewHeight({ height: "180px" });
|
|
53
|
-
} else {
|
|
54
|
-
setPreviewHeight({ height: "auto" });
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
const handleImageLoad = () => {
|
|
60
|
-
if (!imageLoaded) {
|
|
61
|
-
calculateImageHeight();
|
|
62
|
-
setImageLoaded(true);
|
|
63
|
-
}
|
|
64
|
-
};
|
|
65
|
-
|
|
66
41
|
const handleDelete = () => {
|
|
67
42
|
if (!disabled) {
|
|
68
|
-
setPreviewHeight({ height: "auto" });
|
|
69
43
|
setPreviewSrc("");
|
|
70
44
|
onChange({});
|
|
71
45
|
}
|
|
@@ -76,7 +50,7 @@ const ImageField = (props: IImageFieldProps) => {
|
|
|
76
50
|
const url = typeof img === "string" ? img : img.url;
|
|
77
51
|
setPreviewSrc(url);
|
|
78
52
|
onChange(img);
|
|
79
|
-
setIsGalleryOpened
|
|
53
|
+
setIsGalleryOpened?.();
|
|
80
54
|
error && handleValidation && handleValidation(url, validators);
|
|
81
55
|
isOpenDD && toggleModalDD();
|
|
82
56
|
}
|
|
@@ -84,14 +58,14 @@ const ImageField = (props: IImageFieldProps) => {
|
|
|
84
58
|
|
|
85
59
|
const handleClick = () => {
|
|
86
60
|
if (!disabled) {
|
|
87
|
-
setIsGalleryOpened
|
|
61
|
+
setIsGalleryOpened?.();
|
|
88
62
|
noGallery ? toggleModalDD() : toggleModal();
|
|
89
63
|
}
|
|
90
64
|
};
|
|
91
65
|
|
|
92
66
|
const handleChange = () => {
|
|
93
67
|
if (!disabled) {
|
|
94
|
-
setIsGalleryOpened
|
|
68
|
+
setIsGalleryOpened?.();
|
|
95
69
|
noGallery ? toggleModalDD() : toggleModal();
|
|
96
70
|
}
|
|
97
71
|
};
|
|
@@ -113,6 +87,8 @@ const ImageField = (props: IImageFieldProps) => {
|
|
|
113
87
|
|
|
114
88
|
const handleUpload = (file: IImage[]) => getImageSelected(file[0]);
|
|
115
89
|
|
|
90
|
+
const imageElement = useMemo(() => <Image url={previewSrc} width="320" />, [previewSrc]);
|
|
91
|
+
|
|
116
92
|
return (
|
|
117
93
|
<>
|
|
118
94
|
<S.FieldWrapper
|
|
@@ -139,11 +115,7 @@ const ImageField = (props: IImageFieldProps) => {
|
|
|
139
115
|
</S.ImageDataWrapper>
|
|
140
116
|
)}
|
|
141
117
|
<S.Preview preview={!!previewSrc} data-testid="previewDiv" ref={previewRef}>
|
|
142
|
-
{previewSrc &&
|
|
143
|
-
<S.ImageContainer style={{ height: previewHeight.height }}>
|
|
144
|
-
<Image url={previewSrc} width={320} onLoad={handleImageLoad} />
|
|
145
|
-
</S.ImageContainer>
|
|
146
|
-
)}
|
|
118
|
+
{previewSrc && <S.ImageContainer cropPreview={cropPreview}>{imageElement}</S.ImageContainer>}
|
|
147
119
|
<S.PositionWrapper>
|
|
148
120
|
<S.PositionTitle>
|
|
149
121
|
<Icon name="grid" size="16" />
|
|
@@ -184,7 +156,6 @@ const ImageField = (props: IImageFieldProps) => {
|
|
|
184
156
|
<ImageDragAndDrop
|
|
185
157
|
siteID={site ? site.id : "global"}
|
|
186
158
|
isAllowedToUpload={true}
|
|
187
|
-
validFormats={validFormats}
|
|
188
159
|
handleUpload={handleUpload}
|
|
189
160
|
visible={false}
|
|
190
161
|
/>
|
|
@@ -143,10 +143,21 @@ const ImageDataWrapper = styled.div`
|
|
|
143
143
|
max-width: ${(p) => `calc(${p.theme.spacing.xl} * 5)`};
|
|
144
144
|
`;
|
|
145
145
|
|
|
146
|
-
const ImageContainer = styled.div
|
|
146
|
+
const ImageContainer = styled.div<{ cropPreview?: boolean }>`
|
|
147
147
|
display: flex;
|
|
148
148
|
justify-content: center;
|
|
149
149
|
align-items: center;
|
|
150
|
+
${(p) => (p.cropPreview ? "height: 180px;" : "min-height: 180px;")}
|
|
151
|
+
width: 100%;
|
|
152
|
+
overflow: hidden;
|
|
153
|
+
|
|
154
|
+
img {
|
|
155
|
+
object-fit: ${(p) => (p.cropPreview ? "cover" : "contain")};
|
|
156
|
+
object-position: center;
|
|
157
|
+
max-width: 100%;
|
|
158
|
+
height: 100%;
|
|
159
|
+
width: 100%;
|
|
160
|
+
}
|
|
150
161
|
`;
|
|
151
162
|
|
|
152
163
|
const FileName = styled.div`
|