Skip to content

Commit d2c1c70

Browse files
authored
Merge pull request #449 from contentstack/Field-modifiers-in-canvas
Field modifiers in canvas
2 parents 83cfac3 + a0ff5b0 commit d2c1c70

11 files changed

Lines changed: 862 additions & 3 deletions
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
import React, { useState, useEffect, useMemo } from "preact/compat";
2+
import { EmptyAppIcon } from "./icons/EmptyAppIcon";
3+
import { VisualBuilderPostMessageEvents } from "../utils/types/postMessage.types";
4+
import visualBuilderPostMessage from "../utils/visualBuilderPostMessage";
5+
import { visualBuilderStyles } from "../visualBuilder.style";
6+
import classNames from "classnames";
7+
8+
interface App {
9+
app_installation_uid: string;
10+
app_uid: string;
11+
contentTypeUid: string;
12+
entryUid: string;
13+
fieldDataType: string;
14+
fieldDisplayName: string;
15+
fieldPath: string;
16+
icon?: string;
17+
locale: string;
18+
manifest: {
19+
uid: string;
20+
name: string;
21+
description: string;
22+
icon: string;
23+
visibility: string;
24+
};
25+
title: string;
26+
uid: string;
27+
}
28+
29+
interface FieldLocationAppListProps {
30+
apps: App[];
31+
position: "left" | "right";
32+
toolbarRef: React.RefObject<HTMLDivElement>;
33+
}
34+
35+
const normalize = (text: string) =>
36+
text
37+
.toLowerCase()
38+
.replace(/[^a-z0-9 ]/gi, "")
39+
.trim();
40+
41+
export const FieldLocationAppList = ({
42+
apps,
43+
position,
44+
toolbarRef,
45+
}: FieldLocationAppListProps) => {
46+
const remainingApps = apps.filter((app, index) => index !== 0);
47+
const [search, setSearch] = useState("");
48+
49+
const filteredApps = useMemo(() => {
50+
if (!search.trim()) return remainingApps;
51+
52+
const normalizedSearch = normalize(search);
53+
return remainingApps.filter((app) => {
54+
return (
55+
normalize(app.title).includes(normalizedSearch)
56+
);
57+
});
58+
}, [search, remainingApps]);
59+
60+
const handleAppClick = (app: App) => {
61+
visualBuilderPostMessage?.send(
62+
VisualBuilderPostMessageEvents.FIELD_LOCATION_SELECTED_APP,
63+
{
64+
app: app,
65+
position: toolbarRef.current?.getBoundingClientRect(),
66+
}
67+
);
68+
};
69+
70+
return (
71+
<div
72+
className={classNames(
73+
visualBuilderStyles()[
74+
"visual-builder__field-location-app-list"
75+
],
76+
{
77+
[visualBuilderStyles()[
78+
"visual-builder__field-location-app-list--left"
79+
]]: position === "left",
80+
[visualBuilderStyles()[
81+
"visual-builder__field-location-app-list--right"
82+
]]: position === "right",
83+
}
84+
)}
85+
>
86+
<div
87+
className={
88+
visualBuilderStyles()[
89+
"visual-builder__field-location-app-list__search-container"
90+
]
91+
}
92+
>
93+
<svg
94+
width="14"
95+
height="14"
96+
viewBox="0 0 14 14"
97+
fill="none"
98+
xmlns="http://www.w3.org/2000/svg"
99+
className={classNames(
100+
"Search__search-icon Icon--mini",
101+
visualBuilderStyles()[
102+
"visual-builder__field-location-app-list__search-icon"
103+
]
104+
)}
105+
>
106+
<path
107+
d="M12.438 12.438L9.624 9.624M6.25 10.75a4.5 4.5 0 100-9 4.5 4.5 0 000 9z"
108+
stroke="#A9B6CB"
109+
strokeWidth="2"
110+
strokeMiterlimit="10"
111+
strokeLinecap="round"
112+
strokeLinejoin="round"
113+
></path>
114+
</svg>
115+
<input
116+
type="text"
117+
value={search}
118+
onInput={(e) =>
119+
setSearch((e.target as HTMLInputElement).value)
120+
}
121+
placeholder="Search for Apps"
122+
className={
123+
visualBuilderStyles()[
124+
"visual-builder__field-location-app-list__search-input"
125+
]
126+
}
127+
/>
128+
</div>
129+
130+
<div
131+
className={
132+
visualBuilderStyles()[
133+
"visual-builder__field-location-app-list__content"
134+
]
135+
}
136+
>
137+
{filteredApps.length === 0 && (
138+
<div
139+
className={
140+
visualBuilderStyles()[
141+
"visual-builder__field-location-app-list__no-results"
142+
]
143+
}
144+
>
145+
<span
146+
className={
147+
visualBuilderStyles()[
148+
"visual-builder__field-location-app-list__no-results-text"
149+
]
150+
}
151+
>
152+
No matching results found!
153+
</span>
154+
</div>
155+
)}
156+
{filteredApps.map((app) => (
157+
<div
158+
key={app.uid}
159+
className={
160+
visualBuilderStyles()[
161+
"visual-builder__field-location-app-list__item"
162+
]
163+
}
164+
onClick={() => handleAppClick(app)}
165+
>
166+
<div
167+
className={
168+
visualBuilderStyles()[
169+
"visual-builder__field-location-app-list__item-icon-container"
170+
]
171+
}
172+
>
173+
{app.icon ? (
174+
<img
175+
src={app.icon}
176+
alt={app.title}
177+
className={
178+
visualBuilderStyles()[
179+
"visual-builder__field-location-app-list__item-icon"
180+
]
181+
}
182+
/>
183+
) : (
184+
<EmptyAppIcon id={app.app_installation_uid} />
185+
)}
186+
</div>
187+
<span
188+
className={
189+
visualBuilderStyles()[
190+
"visual-builder__field-location-app-list__item-title"
191+
]
192+
}
193+
>
194+
{app.title}
195+
</span>
196+
</div>
197+
))}
198+
</div>
199+
</div>
200+
);
201+
};
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import classNames from "classnames";
2+
import { visualBuilderStyles } from "../visualBuilder.style";
3+
import { EmptyAppIcon } from "./icons/EmptyAppIcon";
4+
import { MoreIcon } from "./icons";
5+
import React, { useRef } from "preact/compat";
6+
import { LoadingIcon } from "./icons/loading";
7+
import { VisualBuilderPostMessageEvents } from "../utils/types/postMessage.types";
8+
import visualBuilderPostMessage from "../utils/visualBuilderPostMessage";
9+
10+
export const FieldLocationIcon = ({
11+
fieldLocationData,
12+
multipleFieldToolbarButtonClasses,
13+
handleMoreIconClick,
14+
moreButtonRef,
15+
toolbarRef,
16+
}: {
17+
fieldLocationData: any;
18+
multipleFieldToolbarButtonClasses: any;
19+
handleMoreIconClick: () => void;
20+
moreButtonRef: any;
21+
toolbarRef: any;
22+
}) => {
23+
24+
25+
26+
if (!fieldLocationData?.apps || fieldLocationData.apps.length === 0) {
27+
return null;
28+
}
29+
30+
const handleAppClick = (app: any) => {
31+
if(!toolbarRef.current) return
32+
visualBuilderPostMessage?.send(VisualBuilderPostMessageEvents.FIELD_LOCATION_SELECTED_APP, {
33+
app,
34+
position: toolbarRef.current?.getBoundingClientRect(),
35+
});
36+
};
37+
38+
return (
39+
<div
40+
ref={toolbarRef}
41+
className={classNames(
42+
visualBuilderStyles()[
43+
"visual-builder__field-location-icons-container"
44+
]
45+
)}
46+
>
47+
<hr
48+
className={visualBuilderStyles()["visual-builder__field-location-icons-container__divider"]}
49+
/>
50+
<button
51+
key={`${fieldLocationData.apps[0].uid}`}
52+
title={fieldLocationData.apps[0].title}
53+
className={multipleFieldToolbarButtonClasses}
54+
data-tooltip={fieldLocationData.apps[0].title}
55+
onClick={(e) => {
56+
e.preventDefault();
57+
e.stopPropagation();
58+
handleAppClick(fieldLocationData.apps[0]);
59+
}}
60+
data-testid="field-location-icon"
61+
>
62+
{fieldLocationData.apps[0].icon ? (
63+
<img
64+
src={fieldLocationData.apps[0].icon}
65+
alt={fieldLocationData.apps[0].title}
66+
className={visualBuilderStyles()["visual-builder__field-location-icons-container__app-icon"]}
67+
/>
68+
) : (
69+
<EmptyAppIcon
70+
id={fieldLocationData.apps[0].app_installation_uid}
71+
/>
72+
)}
73+
</button>
74+
75+
76+
{
77+
fieldLocationData.apps.length > 1 && (
78+
<button
79+
ref={moreButtonRef}
80+
className={multipleFieldToolbarButtonClasses}
81+
data-tooltip={"More"}
82+
onClick={handleMoreIconClick}
83+
data-testid="field-location-more-button"
84+
>
85+
<MoreIcon />
86+
</button>
87+
)
88+
}
89+
</div>
90+
);
91+
};

0 commit comments

Comments
 (0)