@griddo/ax 11.11.7 → 11.11.8-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/jest/componentsMock.js +7 -5
- package/package.json +2 -2
- package/src/__tests__/components/Browser/Browser.test.tsx +438 -87
- package/src/__tests__/components/Browser/Browser.utils.test.ts +55 -0
- package/src/__tests__/components/ConfigPanel/ConfigPanel.test.tsx +1 -3
- package/src/__tests__/components/Fields/Button/Button.test.tsx +29 -27
- package/src/__tests__/components/HeadingsPreviewModal/ErrorsBanner/ErrorItem/ErrorItem.test.tsx +158 -0
- package/src/__tests__/components/HeadingsPreviewModal/ErrorsBanner/ErrorsBanner.test.tsx +90 -0
- package/src/__tests__/components/HeadingsPreviewModal/HeadingsPreviewModal.test.tsx +178 -0
- package/src/__tests__/components/HeadingsPreviewModal/HeadingsPreviewModal.utils.test.tsx +150 -0
- package/src/__tests__/components/KeywordsPreviewModal/KeywordItem/KeywordItem.test.tsx +91 -0
- package/src/__tests__/components/KeywordsPreviewModal/KeywordsPreviewModal.test.tsx +122 -0
- package/src/__tests__/components/KeywordsPreviewModal/KeywordsPreviewModal.utils.test.ts +15 -0
- package/src/__tests__/components/KeywordsPreviewModal/atoms.test.tsx +101 -0
- package/src/__tests__/components/ResizePanel/ResizePanel.test.tsx +1 -1
- package/src/__tests__/modules/FramePreview/FramePreview.test.tsx +318 -0
- package/src/__tests__/modules/FramePreview/FramePreview.utils.test.ts +242 -0
- package/src/__tests__/modules/FramePreview/HeadingsOverlay/HeadingsOverlay.test.tsx +185 -0
- package/src/components/Browser/index.tsx +301 -134
- package/src/components/Browser/style.tsx +75 -6
- package/src/components/Browser/utils.tsx +13 -0
- package/src/components/Button/index.tsx +2 -1
- package/src/components/ConfigPanel/Form/ConnectedField/PageConnectedField/Field/index.tsx +2 -4
- package/src/components/Fields/AsyncSelect/style.tsx +13 -0
- package/src/components/Fields/FieldGroup/index.tsx +5 -2
- package/src/components/Fields/FieldGroup/style.tsx +32 -7
- package/src/components/Fields/HeadingField/index.tsx +2 -2
- package/src/components/Fields/HiddenField/style.tsx +1 -1
- package/src/components/Fields/NumberField/index.tsx +15 -16
- package/src/components/Fields/NumberField/style.tsx +2 -0
- package/src/components/Fields/ReferenceField/index.tsx +1 -1
- package/src/components/Fields/SEOPreview/index.tsx +36 -0
- package/src/components/Fields/SEOPreview/style.tsx +24 -0
- package/src/components/Fields/Select/index.tsx +5 -1
- package/src/components/Fields/Select/style.tsx +56 -0
- package/src/components/Fields/SummaryButton/index.tsx +18 -9
- package/src/components/Fields/SummaryButton/style.tsx +1 -2
- package/src/components/Fields/TagsField/index.tsx +8 -9
- package/src/components/Fields/UrlField/index.tsx +26 -27
- package/src/components/Fields/index.tsx +2 -0
- package/src/components/FloatingNote/index.tsx +35 -0
- package/src/components/FloatingNote/style.tsx +26 -0
- package/src/components/FloatingPanel/index.tsx +5 -2
- package/src/components/FloatingPanel/style.tsx +2 -1
- package/src/components/HeadingsPreviewModal/ErrorsBanner/ErrorItem/index.tsx +85 -0
- package/src/components/HeadingsPreviewModal/ErrorsBanner/ErrorItem/style.tsx +80 -0
- package/src/components/HeadingsPreviewModal/ErrorsBanner/index.tsx +57 -0
- package/src/components/HeadingsPreviewModal/ErrorsBanner/style.tsx +82 -0
- package/src/components/HeadingsPreviewModal/HeadingItem/index.tsx +71 -0
- package/src/components/HeadingsPreviewModal/HeadingItem/style.tsx +77 -0
- package/src/components/HeadingsPreviewModal/index.tsx +146 -0
- package/src/components/HeadingsPreviewModal/style.tsx +82 -0
- package/src/components/HeadingsPreviewModal/utils.tsx +257 -0
- package/src/components/IconAction/index.tsx +1 -1
- package/src/components/KeywordsPreviewModal/KeywordItem/index.tsx +46 -0
- package/src/components/KeywordsPreviewModal/KeywordItem/style.tsx +64 -0
- package/src/components/KeywordsPreviewModal/atoms.tsx +96 -0
- package/src/components/KeywordsPreviewModal/index.tsx +99 -0
- package/src/components/KeywordsPreviewModal/style.tsx +87 -0
- package/src/components/KeywordsPreviewModal/utils.tsx +22 -0
- package/src/components/MainWrapper/AppBar/index.tsx +8 -1
- package/src/components/MainWrapper/index.tsx +7 -1
- package/src/components/Notification/index.tsx +2 -2
- package/src/components/PageFinder/index.tsx +1 -1
- package/src/components/ResizePanel/index.tsx +4 -3
- package/src/components/ResizePanel/style.tsx +1 -1
- package/src/components/SearchField/style.tsx +2 -2
- package/src/components/SideModal/index.tsx +2 -1
- package/src/components/Tabs/index.tsx +13 -4
- package/src/components/Tabs/style.tsx +7 -8
- package/src/components/Toast/index.tsx +4 -2
- package/src/components/Tooltip/index.tsx +4 -3
- package/src/components/index.tsx +8 -0
- package/src/forms/fields.tsx +70 -68
- package/src/hooks/forms.tsx +22 -1
- package/src/hooks/index.tsx +13 -3
- package/src/hooks/modals.tsx +103 -15
- package/src/hooks/users.tsx +25 -8
- package/src/modules/Forms/atoms.tsx +2 -2
- package/src/modules/FramePreview/HeadingsOverlay/index.tsx +113 -0
- package/src/modules/FramePreview/HeadingsOverlay/style.tsx +24 -0
- package/src/modules/FramePreview/index.tsx +55 -16
- package/src/modules/FramePreview/style.tsx +34 -2
- package/src/modules/FramePreview/utils.tsx +140 -0
- package/src/modules/GlobalEditor/Editor/index.tsx +37 -3
- package/src/modules/GlobalEditor/PageBrowser/index.tsx +19 -2
- package/src/modules/GlobalEditor/Preview/index.tsx +0 -2
- package/src/modules/GlobalEditor/Preview/style.tsx +1 -1
- package/src/modules/GlobalEditor/index.tsx +119 -57
- package/src/modules/PageEditor/Editor/index.tsx +33 -2
- package/src/modules/PageEditor/PageBrowser/index.tsx +20 -2
- package/src/modules/PageEditor/Preview/index.tsx +0 -2
- package/src/modules/PageEditor/Preview/style.tsx +1 -1
- package/src/modules/PageEditor/atoms.tsx +1 -1
- package/src/modules/PageEditor/index.tsx +130 -66
- package/src/modules/PublicPreview/index.tsx +5 -2
- package/src/schemas/pages/GlobalPage.ts +87 -70
- package/src/schemas/pages/Page.ts +87 -70
- package/src/types/index.tsx +12 -0
package/src/forms/fields.tsx
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import { IErrorItem, IPage, ISite } from "@ax/types";
|
|
1
|
+
import { FieldContainer, FieldGroup, FieldsBehavior } from "@ax/components";
|
|
2
|
+
import type { IErrorItem, IPage, ISite } from "@ax/types";
|
|
4
3
|
|
|
5
4
|
const getInnerFields = (
|
|
6
5
|
innerFields: any[],
|
|
@@ -16,48 +15,54 @@ const getInnerFields = (
|
|
|
16
15
|
) => {
|
|
17
16
|
let fieldArr: any[] = [];
|
|
18
17
|
|
|
19
|
-
return (
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const { key } = singleFieldProps;
|
|
23
|
-
const error = errors && errors.find((err: any) => err.editorID === selectedContent.editorID && err.key === key);
|
|
18
|
+
return innerFields?.map((singleFieldProps: any) => {
|
|
19
|
+
const { key } = singleFieldProps;
|
|
20
|
+
const error = errors?.find((err: any) => err.editorID === selectedContent.editorID && err.key === key);
|
|
24
21
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
selectedContent,
|
|
30
|
-
isTemplateActivated,
|
|
31
|
-
theme,
|
|
32
|
-
moduleCopy,
|
|
33
|
-
parentDisabled,
|
|
34
|
-
site,
|
|
35
|
-
errors,
|
|
36
|
-
deleteError,
|
|
37
|
-
);
|
|
38
|
-
}
|
|
22
|
+
const isGroup = singleFieldProps.type === "FieldGroup";
|
|
23
|
+
const isCollapsed = isGroup && singleFieldProps.collapsed;
|
|
24
|
+
const isConditional = singleFieldProps.type === "ConditionalField";
|
|
25
|
+
const isArrayGroup = singleFieldProps.type === "ArrayFieldGroup";
|
|
39
26
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
disabled={!isTemplateActivated || singleFieldProps.disabled || parentDisabled}
|
|
53
|
-
error={error}
|
|
54
|
-
deleteError={deleteError}
|
|
55
|
-
theme={theme}
|
|
56
|
-
moduleCopy={moduleCopy}
|
|
57
|
-
/>
|
|
27
|
+
if (isGroup || isConditional || isArrayGroup) {
|
|
28
|
+
fieldArr = getInnerFields(
|
|
29
|
+
singleFieldProps.fields,
|
|
30
|
+
innerActions,
|
|
31
|
+
selectedContent,
|
|
32
|
+
isTemplateActivated,
|
|
33
|
+
theme,
|
|
34
|
+
moduleCopy,
|
|
35
|
+
parentDisabled,
|
|
36
|
+
site,
|
|
37
|
+
errors,
|
|
38
|
+
deleteError,
|
|
58
39
|
);
|
|
59
|
-
}
|
|
60
|
-
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return isGroup ? (
|
|
43
|
+
<FieldGroup key={key} title={singleFieldProps.title} collapsed={isCollapsed} solid={singleFieldProps.solid}>
|
|
44
|
+
{fieldArr}
|
|
45
|
+
</FieldGroup>
|
|
46
|
+
) : (
|
|
47
|
+
<FieldContainer
|
|
48
|
+
key={key}
|
|
49
|
+
objKey={key}
|
|
50
|
+
innerFields={fieldArr}
|
|
51
|
+
field={singleFieldProps}
|
|
52
|
+
actions={innerActions}
|
|
53
|
+
selectedContent={selectedContent}
|
|
54
|
+
updateValue={innerActions.updateValue}
|
|
55
|
+
goTo={innerActions.goTo}
|
|
56
|
+
site={site}
|
|
57
|
+
{...singleFieldProps}
|
|
58
|
+
disabled={!isTemplateActivated || singleFieldProps.disabled || parentDisabled}
|
|
59
|
+
error={error}
|
|
60
|
+
deleteError={deleteError}
|
|
61
|
+
theme={theme}
|
|
62
|
+
moduleCopy={moduleCopy}
|
|
63
|
+
/>
|
|
64
|
+
);
|
|
65
|
+
});
|
|
61
66
|
};
|
|
62
67
|
|
|
63
68
|
const getStructuredDataInnerFields = (
|
|
@@ -69,38 +74,35 @@ const getStructuredDataInnerFields = (
|
|
|
69
74
|
) => {
|
|
70
75
|
let fieldArr: any[] = [];
|
|
71
76
|
|
|
72
|
-
return (
|
|
73
|
-
|
|
74
|
-
innerFields.map((singleFieldProps: any) => {
|
|
75
|
-
const { key, type, fields } = singleFieldProps;
|
|
77
|
+
return innerFields?.map((singleFieldProps: any) => {
|
|
78
|
+
const { key, type, fields } = singleFieldProps;
|
|
76
79
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
+
const handleChange = (newValue: any) => {
|
|
81
|
+
updateValue({ [key]: newValue });
|
|
82
|
+
};
|
|
80
83
|
|
|
81
|
-
|
|
84
|
+
const value = content?.[key];
|
|
82
85
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
+
if (type === "ConditionalField" || type === "ArrayFieldGroup") {
|
|
87
|
+
fieldArr = getStructuredDataInnerFields(fields, content, updateValue, theme, errors);
|
|
88
|
+
}
|
|
86
89
|
|
|
87
|
-
|
|
90
|
+
const error = errors.find((err: any) => err.key === key);
|
|
88
91
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
92
|
+
const fieldProps = {
|
|
93
|
+
value,
|
|
94
|
+
objKey: key,
|
|
95
|
+
fieldType: type,
|
|
96
|
+
innerFields: fieldArr,
|
|
97
|
+
field: singleFieldProps,
|
|
98
|
+
onChange: handleChange,
|
|
99
|
+
...singleFieldProps,
|
|
100
|
+
theme,
|
|
101
|
+
error,
|
|
102
|
+
};
|
|
100
103
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
);
|
|
104
|
+
return <FieldsBehavior key={key} {...fieldProps} />;
|
|
105
|
+
});
|
|
104
106
|
};
|
|
105
107
|
|
|
106
108
|
export { getInnerFields, getStructuredDataInnerFields };
|
package/src/hooks/forms.tsx
CHANGED
|
@@ -26,6 +26,27 @@ const useDebounce = (value: any) => {
|
|
|
26
26
|
return debouncedValue;
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
+
const useDebouncedCallback = <T extends unknown[]>(callback: (...args: T) => void, delay: number) => {
|
|
30
|
+
const timeoutRef = useRef<ReturnType<typeof setTimeout>>();
|
|
31
|
+
const callbackRef = useRef(callback);
|
|
32
|
+
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
callbackRef.current = callback;
|
|
35
|
+
}, [callback]);
|
|
36
|
+
|
|
37
|
+
return useCallback(
|
|
38
|
+
(...args: T) => {
|
|
39
|
+
if (delay === 0) {
|
|
40
|
+
callbackRef.current(...args);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
clearTimeout(timeoutRef.current);
|
|
44
|
+
timeoutRef.current = setTimeout(() => callbackRef.current(...args), delay);
|
|
45
|
+
},
|
|
46
|
+
[delay],
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
|
|
29
50
|
const useEqualStructured = (component: any) => {
|
|
30
51
|
return memo(component, (prevProps: any, newProps: any) => {
|
|
31
52
|
const { fieldKey } = prevProps;
|
|
@@ -234,4 +255,4 @@ const useShouldBeSaved = (form: Record<string, unknown> | IUser | FormContent, d
|
|
|
234
255
|
return { isDirty, setIsDirty };
|
|
235
256
|
};
|
|
236
257
|
|
|
237
|
-
export { useDebounce,
|
|
258
|
+
export { useDebounce, useDebouncedCallback, useEqualStructured, usePrevious, useIsDirty, useShouldBeSaved };
|
package/src/hooks/index.tsx
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { type IBulkSelectedItems, useBulkSelection } from "./bulk";
|
|
2
2
|
import { useAdaptiveText, useCategoryColors, useEmptyState } from "./content";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
useDebounce,
|
|
5
|
+
useDebouncedCallback,
|
|
6
|
+
useEqualStructured,
|
|
7
|
+
useIsDirty,
|
|
8
|
+
usePrevious,
|
|
9
|
+
useShouldBeSaved,
|
|
10
|
+
} from "./forms";
|
|
4
11
|
import { useOnMessageReceivedFromIframe, useOnMessageReceivedFromOutside } from "./iframe";
|
|
5
12
|
import { useURLSearchParam } from "./location";
|
|
6
|
-
import { useHandleClickOutside, useModal, useToast } from "./modals";
|
|
13
|
+
import { useHandleClickOutside, useModal, useModals, useToast } from "./modals";
|
|
7
14
|
import { useNetworkStatus } from "./network";
|
|
8
15
|
import { useResizable } from "./resize";
|
|
9
|
-
import { useGlobalPermission, usePermission } from "./users";
|
|
16
|
+
import { useGlobalPermission, usePermission, usePermissions } from "./users";
|
|
10
17
|
import { useWindowSize } from "./window";
|
|
11
18
|
|
|
12
19
|
export {
|
|
@@ -14,16 +21,19 @@ export {
|
|
|
14
21
|
useBulkSelection,
|
|
15
22
|
useCategoryColors,
|
|
16
23
|
useDebounce,
|
|
24
|
+
useDebouncedCallback,
|
|
17
25
|
useEmptyState,
|
|
18
26
|
useEqualStructured,
|
|
19
27
|
useGlobalPermission,
|
|
20
28
|
useHandleClickOutside,
|
|
21
29
|
useIsDirty,
|
|
22
30
|
useModal,
|
|
31
|
+
useModals,
|
|
23
32
|
useNetworkStatus,
|
|
24
33
|
useOnMessageReceivedFromIframe,
|
|
25
34
|
useOnMessageReceivedFromOutside,
|
|
26
35
|
usePermission,
|
|
36
|
+
usePermissions,
|
|
27
37
|
usePrevious,
|
|
28
38
|
useResizable,
|
|
29
39
|
useShouldBeSaved,
|
package/src/hooks/modals.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useEffect } from "react";
|
|
1
|
+
import { useState, useEffect, useRef, useCallback } from "react";
|
|
2
2
|
|
|
3
3
|
const useModal = (initialState?: boolean, bodyBlock = true) => {
|
|
4
4
|
const [isOpen, setIsOpen] = useState(initialState || false);
|
|
@@ -6,11 +6,26 @@ const useModal = (initialState?: boolean, bodyBlock = true) => {
|
|
|
6
6
|
setIsOpen(!isOpen);
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
if (isOpen && bodyBlock) {
|
|
11
|
+
if (!document.body.classList.contains("modal-open")) {
|
|
12
|
+
document.body.classList.add("modal-open");
|
|
13
|
+
}
|
|
14
|
+
return () => {
|
|
15
|
+
// Solo eliminar si no hay otros modales abiertos
|
|
16
|
+
const modals = document.querySelectorAll('[data-testid="modal-wrapper"]');
|
|
17
|
+
if (modals.length <= 1) {
|
|
18
|
+
document.body.classList.remove("modal-open");
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
} else if (!bodyBlock || !isOpen) {
|
|
22
|
+
// Solo eliminar si no hay modales abiertos
|
|
23
|
+
const modals = document.querySelectorAll('[data-testid="modal-wrapper"]');
|
|
24
|
+
if (modals.length === 0) {
|
|
25
|
+
document.body.classList.remove("modal-open");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}, [isOpen, bodyBlock]);
|
|
14
29
|
|
|
15
30
|
return {
|
|
16
31
|
isOpen,
|
|
@@ -18,6 +33,66 @@ const useModal = (initialState?: boolean, bodyBlock = true) => {
|
|
|
18
33
|
};
|
|
19
34
|
};
|
|
20
35
|
|
|
36
|
+
const useModals = <T extends string>(modalKeys: readonly T[], bodyBlock = true) => {
|
|
37
|
+
const [openModals, setOpenModals] = useState<Record<T, boolean>>(() =>
|
|
38
|
+
modalKeys.reduce(
|
|
39
|
+
(acc, key) => {
|
|
40
|
+
acc[key] = false;
|
|
41
|
+
return acc;
|
|
42
|
+
},
|
|
43
|
+
{} as Record<T, boolean>,
|
|
44
|
+
),
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
const toggleModal = useCallback((modalKey: T) => {
|
|
48
|
+
setOpenModals((prev) => ({ ...prev, [modalKey]: !prev[modalKey] }));
|
|
49
|
+
}, []);
|
|
50
|
+
|
|
51
|
+
const openModal = useCallback((modalKey: T) => {
|
|
52
|
+
setOpenModals((prev) => ({ ...prev, [modalKey]: true }));
|
|
53
|
+
}, []);
|
|
54
|
+
|
|
55
|
+
const closeModal = useCallback((modalKey: T) => {
|
|
56
|
+
setOpenModals((prev) => ({ ...prev, [modalKey]: false }));
|
|
57
|
+
}, []);
|
|
58
|
+
|
|
59
|
+
const isOpen = useCallback(
|
|
60
|
+
(modalKey: T) => {
|
|
61
|
+
return openModals[modalKey] || false;
|
|
62
|
+
},
|
|
63
|
+
[openModals],
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
const hasOpenModals = Object.values(openModals).some(Boolean);
|
|
68
|
+
|
|
69
|
+
if (hasOpenModals && bodyBlock) {
|
|
70
|
+
if (!document.body.classList.contains("modal-open")) {
|
|
71
|
+
document.body.classList.add("modal-open");
|
|
72
|
+
}
|
|
73
|
+
return () => {
|
|
74
|
+
// Solo eliminar si no hay otros modales abiertos
|
|
75
|
+
const modals = document.querySelectorAll('[data-testid="modal-wrapper"]');
|
|
76
|
+
if (modals.length <= 1) {
|
|
77
|
+
document.body.classList.remove("modal-open");
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
// Solo eliminar si no hay modales abiertos
|
|
82
|
+
const modals = document.querySelectorAll('[data-testid="modal-wrapper"]');
|
|
83
|
+
if (modals.length === 0) {
|
|
84
|
+
document.body.classList.remove("modal-open");
|
|
85
|
+
}
|
|
86
|
+
}, [openModals, bodyBlock]);
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
toggleModal,
|
|
90
|
+
openModal,
|
|
91
|
+
closeModal,
|
|
92
|
+
isOpen,
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
|
|
21
96
|
const useHandleClickOutside = (isOpen: boolean, handleClickOutside: (e: MouseEvent) => void) => {
|
|
22
97
|
useEffect(() => {
|
|
23
98
|
if (isOpen) {
|
|
@@ -35,19 +110,32 @@ const useHandleClickOutside = (isOpen: boolean, handleClickOutside: (e: MouseEve
|
|
|
35
110
|
const useToast = () => {
|
|
36
111
|
const [isVisible, setIsVisible] = useState(false);
|
|
37
112
|
const [state, setState] = useState<any>(null);
|
|
113
|
+
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
38
114
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
115
|
+
const toggleToast = useCallback((newState?: any) => {
|
|
116
|
+
if (timeoutRef.current) {
|
|
117
|
+
clearTimeout(timeoutRef.current);
|
|
118
|
+
}
|
|
42
119
|
|
|
43
|
-
|
|
120
|
+
if (newState) {
|
|
121
|
+
setState(newState);
|
|
122
|
+
}
|
|
44
123
|
setIsVisible(true);
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
124
|
+
|
|
125
|
+
timeoutRef.current = setTimeout(() => {
|
|
126
|
+
setIsVisible(false);
|
|
127
|
+
}, 6000);
|
|
128
|
+
}, []);
|
|
129
|
+
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
return () => {
|
|
132
|
+
if (timeoutRef.current) {
|
|
133
|
+
clearTimeout(timeoutRef.current);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
}, []);
|
|
49
137
|
|
|
50
138
|
return { isVisible, setIsVisible, toggleToast, state };
|
|
51
139
|
};
|
|
52
140
|
|
|
53
|
-
export { useModal, useHandleClickOutside, useToast };
|
|
141
|
+
export { useModal, useModals, useHandleClickOutside, useToast };
|
package/src/hooks/users.tsx
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
1
2
|
import { useSelector } from "react-redux";
|
|
2
|
-
|
|
3
|
+
|
|
4
|
+
import type { IRootState } from "@ax/types";
|
|
3
5
|
|
|
4
6
|
const usePermission = (permission: string | string[] | undefined): boolean => {
|
|
5
7
|
const userPermissions = useSelector((state: IRootState) => state.users.currentPermissions);
|
|
6
|
-
const isSuperAdmin = useSelector(
|
|
7
|
-
(state: IRootState) => state.users.currentUser && state.users.currentUser.isSuperAdmin,
|
|
8
|
-
);
|
|
8
|
+
const isSuperAdmin = useSelector((state: IRootState) => state.users.currentUser?.isSuperAdmin);
|
|
9
9
|
|
|
10
10
|
const isAllowedTo = (permissions: string[]) =>
|
|
11
11
|
userPermissions && permissions.some((permission: string) => userPermissions.includes(permission));
|
|
@@ -19,11 +19,28 @@ const usePermission = (permission: string | string[] | undefined): boolean => {
|
|
|
19
19
|
return isAllowedTo(arrayPermission);
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
+
const usePermissions = <T extends Record<string, string | string[]>>(permissions: T): Record<keyof T, boolean> => {
|
|
23
|
+
const userPermissions = useSelector((state: IRootState) => state.users.currentPermissions);
|
|
24
|
+
const isSuperAdmin = useSelector((state: IRootState) => state.users.currentUser?.isSuperAdmin);
|
|
25
|
+
|
|
26
|
+
return useMemo(() => {
|
|
27
|
+
const isAllowedTo = (permission: string | string[]) => {
|
|
28
|
+
if (isSuperAdmin) return true;
|
|
29
|
+
const arrayPermission = Array.isArray(permission) ? permission : [permission];
|
|
30
|
+
return userPermissions && arrayPermission.some((perm: string) => userPermissions.includes(perm));
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const result: Record<string, boolean> = {};
|
|
34
|
+
for (const key of Object.keys(permissions)) {
|
|
35
|
+
result[key] = isAllowedTo(permissions[key]);
|
|
36
|
+
}
|
|
37
|
+
return result as Record<keyof T, boolean>;
|
|
38
|
+
}, [permissions, userPermissions, isSuperAdmin]);
|
|
39
|
+
};
|
|
40
|
+
|
|
22
41
|
const useGlobalPermission = (permission: string | string[] | undefined): boolean => {
|
|
23
42
|
const userPermissions = useSelector((state: IRootState) => state.users.globalPermissions);
|
|
24
|
-
const isSuperAdmin = useSelector(
|
|
25
|
-
(state: IRootState) => state.users.currentUser && state.users.currentUser.isSuperAdmin,
|
|
26
|
-
);
|
|
43
|
+
const isSuperAdmin = useSelector((state: IRootState) => state.users.currentUser?.isSuperAdmin);
|
|
27
44
|
|
|
28
45
|
const isAllowedTo = (permissions: string[]) =>
|
|
29
46
|
userPermissions && permissions.some((permission: string) => userPermissions.includes(permission));
|
|
@@ -37,4 +54,4 @@ const useGlobalPermission = (permission: string | string[] | undefined): boolean
|
|
|
37
54
|
return isAllowedTo(arrayPermission);
|
|
38
55
|
};
|
|
39
56
|
|
|
40
|
-
export { usePermission, useGlobalPermission };
|
|
57
|
+
export { usePermission, usePermissions, useGlobalPermission };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import type { Dispatch, SetStateAction } from "react";
|
|
2
2
|
|
|
3
|
-
import { IModal } from "@ax/types";
|
|
3
|
+
import type { IModal } from "@ax/types";
|
|
4
4
|
import { Modal, FieldsBehavior, AsyncSelect } from "@ax/components";
|
|
5
5
|
|
|
6
6
|
import * as S from "./style";
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
|
+
import { createPortal } from "react-dom";
|
|
3
|
+
|
|
4
|
+
import * as S from "./style";
|
|
5
|
+
|
|
6
|
+
const headColors: Record<string, string> = {
|
|
7
|
+
h1: "#FFF06D",
|
|
8
|
+
h2: "#FFB8F8",
|
|
9
|
+
h3: "#73F8C8",
|
|
10
|
+
h4: "#9BEDFF",
|
|
11
|
+
h5: "#C6C1FF",
|
|
12
|
+
h6: "#FFCE95",
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const isEffectivelyVisible = (el: HTMLElement): boolean => {
|
|
16
|
+
let node: HTMLElement | null = el;
|
|
17
|
+
while (node && node !== document.body) {
|
|
18
|
+
const style = window.getComputedStyle(node);
|
|
19
|
+
if (style.display === "none" || style.visibility === "hidden") return false;
|
|
20
|
+
if (parseFloat(style.opacity) === 0) return false;
|
|
21
|
+
node = node.parentElement;
|
|
22
|
+
}
|
|
23
|
+
return true;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const HeadingsOverlay = ({ headingFilter }: IHeadingsOverlayProps) => {
|
|
27
|
+
const [boxes, setBoxes] = useState<HeadingBox[]>([]);
|
|
28
|
+
const rafRef = useRef<number>(0);
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
const update = () => {
|
|
32
|
+
cancelAnimationFrame(rafRef.current);
|
|
33
|
+
rafRef.current = requestAnimationFrame(() => {
|
|
34
|
+
const selector = headingFilter || "h1, h2, h3, h4, h5, h6";
|
|
35
|
+
const headings = Array.from(document.querySelectorAll<HTMLElement>(selector));
|
|
36
|
+
const scrollX = window.scrollX;
|
|
37
|
+
const scrollY = window.scrollY;
|
|
38
|
+
const boxes: HeadingBox[] = [];
|
|
39
|
+
for (let i = 0; i < headings.length; i++) {
|
|
40
|
+
const el = headings[i];
|
|
41
|
+
const rect = el.getBoundingClientRect();
|
|
42
|
+
if (rect.width === 0 && rect.height === 0) continue;
|
|
43
|
+
if (!isEffectivelyVisible(el)) continue;
|
|
44
|
+
boxes.push({
|
|
45
|
+
id: el.dataset.griddoid || `heading-${i}`,
|
|
46
|
+
tag: el.tagName.toLowerCase(),
|
|
47
|
+
rect,
|
|
48
|
+
scrollX,
|
|
49
|
+
scrollY,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
setBoxes(boxes);
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
update();
|
|
57
|
+
|
|
58
|
+
window.addEventListener("resize", update);
|
|
59
|
+
window.addEventListener("scroll", update, true);
|
|
60
|
+
document.addEventListener("animationend", update, true);
|
|
61
|
+
document.addEventListener("transitionend", update, true);
|
|
62
|
+
|
|
63
|
+
const observer = new MutationObserver(update);
|
|
64
|
+
observer.observe(document.body, { childList: true, subtree: true, attributes: true });
|
|
65
|
+
|
|
66
|
+
return () => {
|
|
67
|
+
cancelAnimationFrame(rafRef.current);
|
|
68
|
+
window.removeEventListener("resize", update);
|
|
69
|
+
window.removeEventListener("scroll", update, true);
|
|
70
|
+
document.removeEventListener("animationend", update, true);
|
|
71
|
+
document.removeEventListener("transitionend", update, true);
|
|
72
|
+
observer.disconnect();
|
|
73
|
+
};
|
|
74
|
+
}, [headingFilter]);
|
|
75
|
+
|
|
76
|
+
return createPortal(
|
|
77
|
+
<div data-testid="headings-overlay">
|
|
78
|
+
{boxes.map(({ id, tag, rect, scrollX, scrollY }) => {
|
|
79
|
+
const color = headColors[tag];
|
|
80
|
+
if (!color) return null;
|
|
81
|
+
return (
|
|
82
|
+
<S.Box
|
|
83
|
+
key={id}
|
|
84
|
+
$color={color}
|
|
85
|
+
style={{
|
|
86
|
+
top: rect.top + scrollY,
|
|
87
|
+
left: rect.left + scrollX,
|
|
88
|
+
width: rect.width,
|
|
89
|
+
height: rect.height,
|
|
90
|
+
}}
|
|
91
|
+
>
|
|
92
|
+
<S.Label $color={color}>{tag.toUpperCase()}</S.Label>
|
|
93
|
+
</S.Box>
|
|
94
|
+
);
|
|
95
|
+
})}
|
|
96
|
+
</div>,
|
|
97
|
+
document.body,
|
|
98
|
+
);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
interface HeadingBox {
|
|
102
|
+
id: string;
|
|
103
|
+
tag: string;
|
|
104
|
+
rect: DOMRect;
|
|
105
|
+
scrollX: number;
|
|
106
|
+
scrollY: number;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface IHeadingsOverlayProps {
|
|
110
|
+
headingFilter: string | null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export default HeadingsOverlay;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import styled from "styled-components";
|
|
2
|
+
|
|
3
|
+
const Box = styled.div<{ $color: string }>`
|
|
4
|
+
position: absolute;
|
|
5
|
+
outline: 2px solid ${(p) => p.$color};
|
|
6
|
+
z-index: 1001;
|
|
7
|
+
pointer-events: none;
|
|
8
|
+
`;
|
|
9
|
+
|
|
10
|
+
const Label = styled.span<{ $color: string }>`
|
|
11
|
+
position: absolute;
|
|
12
|
+
background-color: ${(p) => p.$color};
|
|
13
|
+
color: ${(p) => p.theme.colors.textHighEmphasis};
|
|
14
|
+
${(p) => p.theme.textStyle.uiS};
|
|
15
|
+
padding: ${(p) => `0 ${p.theme.spacing.xxs}`};
|
|
16
|
+
transform: rotate(-90deg);
|
|
17
|
+
top: 1px;
|
|
18
|
+
left: -23px;
|
|
19
|
+
font-family: Source Sans Pro;
|
|
20
|
+
font-style: normal;
|
|
21
|
+
text-decoration: none;
|
|
22
|
+
`;
|
|
23
|
+
|
|
24
|
+
export { Box, Label };
|