@griddo/ax 11.12.0 → 11.12.1-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 +294 -149
- 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 +22 -22
- 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 +148 -0
- package/src/components/HeadingsPreviewModal/style.tsx +82 -0
- package/src/components/HeadingsPreviewModal/utils.tsx +329 -0
- package/src/components/Icon/index.tsx +1 -2
- 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 +116 -0
- package/src/modules/FramePreview/HeadingsOverlay/style.tsx +34 -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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useRef, useEffect } from "react";
|
|
2
2
|
import { useLocation } from "react-router-dom";
|
|
3
3
|
|
|
4
4
|
import { createPortal } from "react-dom";
|
|
@@ -14,7 +14,9 @@ const Toast = (props: IToastProps): JSX.Element => {
|
|
|
14
14
|
const isEditor = pathname.includes("/editor");
|
|
15
15
|
|
|
16
16
|
let temp: NodeJS.Timeout;
|
|
17
|
-
const setTemp = (time: number) =>
|
|
17
|
+
const setTemp = (time: number) => {
|
|
18
|
+
temp = setTimeout(() => setIsVisible(false), time);
|
|
19
|
+
};
|
|
18
20
|
|
|
19
21
|
const close = () => {
|
|
20
22
|
if (toast.current) {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { ReactNode } from "react";
|
|
2
1
|
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
3
2
|
|
|
4
3
|
import { useHandleClickOutside } from "@ax/hooks";
|
|
@@ -6,7 +5,7 @@ import { useHandleClickOutside } from "@ax/hooks";
|
|
|
6
5
|
import * as S from "./style";
|
|
7
6
|
|
|
8
7
|
const Tooltip = (props: ITooltipProps) => {
|
|
9
|
-
const { content, children, hideOnClick = true, bottom, left, expanded, top } = props;
|
|
8
|
+
const { content, children, hideOnClick = true, bottom, left, expanded, top, className } = props;
|
|
10
9
|
|
|
11
10
|
const initialState: IState = {
|
|
12
11
|
active: false,
|
|
@@ -100,6 +99,7 @@ const Tooltip = (props: ITooltipProps) => {
|
|
|
100
99
|
onMouseLeave={hideTip}
|
|
101
100
|
onMouseDown={handleClick}
|
|
102
101
|
expanded={expanded}
|
|
102
|
+
className={className}
|
|
103
103
|
>
|
|
104
104
|
<div ref={childrenRef}>{children}</div>
|
|
105
105
|
<S.Tip
|
|
@@ -127,13 +127,14 @@ interface IState {
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
export interface ITooltipProps {
|
|
130
|
-
content?:
|
|
130
|
+
content?: React.ReactNode;
|
|
131
131
|
children: any;
|
|
132
132
|
hideOnClick?: boolean;
|
|
133
133
|
bottom?: boolean;
|
|
134
134
|
left?: number;
|
|
135
135
|
expanded?: boolean;
|
|
136
136
|
top?: number;
|
|
137
|
+
className?: string;
|
|
137
138
|
}
|
|
138
139
|
|
|
139
140
|
export default Tooltip;
|
package/src/components/index.tsx
CHANGED
|
@@ -45,6 +45,7 @@ import {
|
|
|
45
45
|
RadioGroup,
|
|
46
46
|
ReferenceField,
|
|
47
47
|
RichText,
|
|
48
|
+
SEOPreview,
|
|
48
49
|
Select,
|
|
49
50
|
SummaryButton,
|
|
50
51
|
TextArea,
|
|
@@ -64,13 +65,16 @@ import FilterTagsBar from "./FilterTagsBar";
|
|
|
64
65
|
import Flag from "./Flag";
|
|
65
66
|
import FloatingButton from "./FloatingButton";
|
|
66
67
|
import FloatingMenu from "./FloatingMenu";
|
|
68
|
+
import FloatingNote from "./FloatingNote";
|
|
67
69
|
import FloatingPanel from "./FloatingPanel";
|
|
68
70
|
import Gallery from "./Gallery";
|
|
69
71
|
import GuardModal from "./GuardModal";
|
|
72
|
+
import HeadingsPreviewModal from "./HeadingsPreviewModal";
|
|
70
73
|
import Icon from "./Icon";
|
|
71
74
|
import IconAction from "./IconAction";
|
|
72
75
|
import Image from "./Image";
|
|
73
76
|
import InformativeMenu from "./InformativeMenu";
|
|
77
|
+
import KeywordsPreviewModal from "./KeywordsPreviewModal";
|
|
74
78
|
import LanguageMenu from "./LanguageMenu";
|
|
75
79
|
import { ListItem, ListTitle } from "./Lists";
|
|
76
80
|
import Loader from "./Loader";
|
|
@@ -164,12 +168,14 @@ export {
|
|
|
164
168
|
Flag,
|
|
165
169
|
FloatingButton,
|
|
166
170
|
FloatingMenu,
|
|
171
|
+
FloatingNote,
|
|
167
172
|
FloatingPanel,
|
|
168
173
|
FormCategorySelect,
|
|
169
174
|
FormContainer,
|
|
170
175
|
FormFieldArray,
|
|
171
176
|
Gallery,
|
|
172
177
|
GuardModal,
|
|
178
|
+
HeadingsPreviewModal,
|
|
173
179
|
HeadingField,
|
|
174
180
|
HiddenField,
|
|
175
181
|
Icon,
|
|
@@ -177,6 +183,7 @@ export {
|
|
|
177
183
|
Image,
|
|
178
184
|
ImageField,
|
|
179
185
|
InformativeMenu,
|
|
186
|
+
KeywordsPreviewModal,
|
|
180
187
|
IntegrationsField,
|
|
181
188
|
LanguageMenu,
|
|
182
189
|
LastAccessFilter,
|
|
@@ -213,6 +220,7 @@ export {
|
|
|
213
220
|
SearchField,
|
|
214
221
|
SearchTagsBar,
|
|
215
222
|
Select,
|
|
223
|
+
SEOPreview,
|
|
216
224
|
SharePageModal,
|
|
217
225
|
SideModal,
|
|
218
226
|
SiteFilter,
|
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,116 @@
|
|
|
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
|
+
const labelAbove = rect.left < 30;
|
|
82
|
+
return (
|
|
83
|
+
<S.Box
|
|
84
|
+
key={id}
|
|
85
|
+
$color={color}
|
|
86
|
+
style={{
|
|
87
|
+
top: rect.top + scrollY,
|
|
88
|
+
left: rect.left + scrollX,
|
|
89
|
+
width: rect.width,
|
|
90
|
+
height: rect.height,
|
|
91
|
+
}}
|
|
92
|
+
>
|
|
93
|
+
<S.Label $color={color} $above={labelAbove}>
|
|
94
|
+
{tag.toUpperCase()}
|
|
95
|
+
</S.Label>
|
|
96
|
+
</S.Box>
|
|
97
|
+
);
|
|
98
|
+
})}
|
|
99
|
+
</div>,
|
|
100
|
+
document.body,
|
|
101
|
+
);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
interface HeadingBox {
|
|
105
|
+
id: string;
|
|
106
|
+
tag: string;
|
|
107
|
+
rect: DOMRect;
|
|
108
|
+
scrollX: number;
|
|
109
|
+
scrollY: number;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface IHeadingsOverlayProps {
|
|
113
|
+
headingFilter: string | null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export default HeadingsOverlay;
|