Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion src/visualBuilder/components/fieldLabelWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { VisualBuilderCslpEventDetails } from "../types/visualBuilder.types";
import { FieldSchemaMap } from "../utils/fieldSchemaMap";
import { DisableReason, isFieldDisabled } from "../utils/isFieldDisabled";
import visualBuilderPostMessage from "../utils/visualBuilderPostMessage";
import { CaretIcon, CaretRightIcon, InfoIcon } from "./icons";
import { CaretIcon, CaretRightIcon, InfoIcon, LockIcon } from "./icons";
import { LoadingIcon } from "./icons/loading";
import { FieldTypeIconsMap, getFieldIcon } from "../generators/generateCustomCursor";
import { uniqBy } from "lodash-es";
Expand Down Expand Up @@ -64,6 +64,7 @@ interface FieldLabelWrapperProps {
fieldMetadata: CslpData;
eventDetails: VisualBuilderCslpEventDetails;
parentPaths: string[];
isLocked?: boolean;
getParentEditableElement: (cslp: string) => HTMLElement | null;
}

Expand Down Expand Up @@ -339,6 +340,15 @@ function FieldLabelWrapperComponent(
[visualBuilderStyles()[
"visual-builder__focused-toolbar--variant"
]]: currentField.isVariant,
},
{
"visual-builder__focused-toolbar--field-locked":
props.isLocked,
},
{
[visualBuilderStyles()[
"visual-builder__focused-toolbar--field-locked"
]]: props.isLocked,
}
)}
onClick={() => setIsDropdownOpen((prev) => !prev)}
Expand Down Expand Up @@ -430,6 +440,19 @@ function FieldLabelWrapperComponent(
{currentField.text}
</div>
) : null}
{props.isLocked ? (
<div
className={classNames(
"visual-builder__lock-icon",
visualBuilderStyles()[
"visual-builder__lock-icon"
]
)}
data-testid="visual-builder__lock-icon"
>
<LockIcon />
</div>
) : null}
{getCurrentFieldIcon()}
{error ? <CslpError /> : null}
</button>
Expand Down
20 changes: 20 additions & 0 deletions src/visualBuilder/components/icons/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -358,3 +358,23 @@ export function CaretRightIcon(): JSX.Element {

)
}

export function LockIcon(): JSX.Element {
return (
<svg
data-testid="visual-builder__lock-icon"
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M5.5 6.5V5.5C5.5 3.567 7.067 2 9 2C10.933 2 12.5 3.567 12.5 5.5V6.5C13.0523 6.5 13.5 6.94772 13.5 7.5V12.5C13.5 13.0523 13.0523 13.5 12.5 13.5H5.5C4.94772 13.5 4.5 13.0523 4.5 12.5V7.5C4.5 6.94772 4.94772 6.5 5.5 6.5ZM6.5 5.5V6.5H11.5V5.5C11.5 4.39543 10.6046 3.5 9 3.5C7.39543 3.5 6.5 4.39543 6.5 5.5ZM5.5 7.5V12.5H12.5V7.5H5.5Z"
fill="white"
/>
</svg>
);
}
21 changes: 19 additions & 2 deletions src/visualBuilder/generators/generateHoverOutline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import { visualBuilderStyles } from "../visualBuilder.style";
export function addHoverOutline(
targetElement: Element,
disabled?: boolean,
isVariant?: boolean
isVariant?: boolean,
isLocked?: boolean
): void {
const targetElementDimension = targetElement.getBoundingClientRect();

Expand All @@ -22,14 +23,30 @@ export function addHoverOutline(
visualBuilderStyles()["visual-builder__hover-outline--hidden"]
);

if (disabled) {
if (isLocked) {
hoverOutline.classList.add(
visualBuilderStyles()["visual-builder__hover-outline--locked"]
);
hoverOutline.classList.remove(
visualBuilderStyles()["visual-builder__hover-outline--disabled"]
);
hoverOutline.classList.remove(
visualBuilderStyles()["visual-builder__hover-outline--variant"]
);
} else if (disabled) {
hoverOutline.classList.add(
visualBuilderStyles()["visual-builder__hover-outline--disabled"]
);
hoverOutline.classList.remove(
visualBuilderStyles()["visual-builder__hover-outline--locked"]
);
} else {
hoverOutline.classList.remove(
visualBuilderStyles()["visual-builder__hover-outline--disabled"]
);
hoverOutline.classList.remove(
visualBuilderStyles()["visual-builder__hover-outline--locked"]
);
if (isVariant) {
hoverOutline.classList.add(
visualBuilderStyles()["visual-builder__hover-outline--variant"]
Expand Down
42 changes: 41 additions & 1 deletion src/visualBuilder/generators/generateOverlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,14 @@ export function hideFocusOverlay(elements: HideOverlayParams): void {
} = elements;

if (visualBuilderOverlayWrapper) {
const previousSelectedEditableDOM =
VisualBuilder.VisualBuilderGlobalState.value
.previousSelectedEditableDOM;

if (previousSelectedEditableDOM) {
sendUnlockFieldEvent(previousSelectedEditableDOM);
}

visualBuilderOverlayWrapper.classList.remove("visible");

// Cleanup overlay styles: Top, Right, Bottom, Left & Outline
Expand All @@ -128,7 +136,7 @@ export function hideFocusOverlay(elements: HideOverlayParams): void {
eventType: VisualBuilderPostMessageEvents.UPDATE_FIELD,
});
} else if (noTrigger) {
const { previousSelectedEditableDOM, focusFieldValue } =
const { focusFieldValue } =
VisualBuilder.VisualBuilderGlobalState.value || {};
if (
previousSelectedEditableDOM &&
Expand Down Expand Up @@ -209,6 +217,38 @@ export function sendFieldEvent(options: ISendFieldEventParams): void {
});
}
}

export function sendUnlockFieldEvent(editableElement: Element | null): void {
if (!editableElement) {
return;
}

const cslpData = editableElement.getAttribute("data-cslp");
if (!cslpData) {
return;
}

const isLocked =
editableElement.getAttribute("data-field-locked") === "true";
if (!isLocked) {
return;
}

const fieldMetadata = extractDetailsFromCslp(cslpData);

visualBuilderPostMessage?.send(
VisualBuilderPostMessageEvents.UNLOCK_FIELD,
{
fieldMetadata: {
content_type_uid: fieldMetadata.content_type_uid,
entry_uid: fieldMetadata.entry_uid,
fieldPath: fieldMetadata.fieldPath,
locale: fieldMetadata.locale,
variant: fieldMetadata.variant,
},
}
);
}
interface HideOverlayParams
extends Pick<
EventListenerHandlerParams,
Expand Down
10 changes: 8 additions & 2 deletions src/visualBuilder/generators/generateToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export function appendFocusedToolbar(
}
): void {
appendFieldPathDropdown(eventDetails, focusedToolbarElement, options);
if(options?.isHover) {
if (options?.isHover) {
return;
}
appendFieldToolbar(
Expand Down Expand Up @@ -79,9 +79,10 @@ export function appendFieldPathDropdown(
focusedToolbarElement: HTMLDivElement,
options?: {
isHover?: boolean;
isLocked?: boolean;
}
): void {
const { isHover } = options || {};
const { isHover, isLocked: providedIsLocked } = options || {};
const fieldLabelWrapper = document.querySelector(
".visual-builder__focused-toolbar__field-label-wrapper"
) as HTMLDivElement | null;
Expand Down Expand Up @@ -134,13 +135,18 @@ export function appendFieldPathDropdown(
focusedToolbarElement.style.top = `${adjustedDistanceFromTop}px`;

const parentPaths = collectParentCSLPPaths(targetElement, 2);
const isLocked =
providedIsLocked !== undefined
? providedIsLocked
: targetElement.getAttribute("data-field-locked") === "true";

const wrapper = document.createDocumentFragment();
render(
<FieldLabelWrapperComponent
fieldMetadata={fieldMetadata}
eventDetails={eventDetails}
parentPaths={parentPaths}
isLocked={isLocked}
getParentEditableElement={(cslp: string) => {
const parentElement = targetElement.closest(
`[${DATA_CSLP_ATTR_SELECTOR}="${cslp}"]`
Expand Down
1 change: 1 addition & 0 deletions src/visualBuilder/listeners/__test__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ vi.mock("../mouseHover", () => ({
hideCustomCursor: vi.fn(),
hideHoverOutline: vi.fn(),
showCustomCursor: vi.fn(),
removeAllLockedFieldStyling: vi.fn(),
}));

vi.mock("../../generators/generateToolbar", () => ({
Expand Down
9 changes: 7 additions & 2 deletions src/visualBuilder/listeners/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import handleMouseHover, {
hideCustomCursor,
hideHoverOutline,
showCustomCursor,
removeAllLockedFieldStyling,
} from "./mouseHover";
import EventListenerHandlerParams from "./types";

Expand Down Expand Up @@ -47,10 +48,14 @@ const eventHandlers = {
cancelPendingMouseHover();
cancelPendingHoverToolbar();
cancelPendingAddOutline();

hideCustomCursor(params.customCursor);
hideHoverOutline(params.visualBuilderContainer);
if(!VisualBuilder?.VisualBuilderGlobalState?.value?.isFocussed && params?.focusedToolbar) {
removeAllLockedFieldStyling();
if (
!VisualBuilder?.VisualBuilderGlobalState?.value?.isFocussed &&
params?.focusedToolbar
) {
removeFieldToolbar(params.focusedToolbar);
}
},
Expand Down
71 changes: 62 additions & 9 deletions src/visualBuilder/listeners/mouseClick.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ import {

import { appendFocusedToolbar } from "../generators/generateToolbar";

import { addFocusOverlay, hideOverlay } from "../generators/generateOverlay";
import {
addFocusOverlay,
hideOverlay,
sendUnlockFieldEvent,
} from "../generators/generateOverlay";

import visualBuilderPostMessage from "../utils/visualBuilderPostMessage";

Expand All @@ -32,6 +36,7 @@ import { fixSvgXPath } from "../utils/collabUtils";
import { v4 as uuidV4 } from "uuid";
import { CslpData } from "../../cslp/types/cslp.types";
import { fetchEntryPermissionsAndStageDetails } from "../utils/fetchEntryPermissionsAndStageDetails";
import { checkAndApplyFieldLockStatus } from "./mouseHover";

export type HandleBuilderInteractionParams = Omit<
EventListenerHandlerParams,
Expand Down Expand Up @@ -172,6 +177,28 @@ export async function handleBuilderInteraction(
}

const { editableElement, fieldMetadata } = eventDetails;

const previousSelectedEditableDOM =
VisualBuilder.VisualBuilderGlobalState.value
.previousSelectedEditableDOM;

if (
previousSelectedEditableDOM &&
previousSelectedEditableDOM !== editableElement
) {
sendUnlockFieldEvent(previousSelectedEditableDOM);
}

const isFieldLocked = await checkFieldLockStatus(fieldMetadata);
if (isFieldLocked) {
await checkAndApplyFieldLockStatus(
editableElement,
fieldMetadata,
"click"
);
return;
}

const variantStatus = await getFieldVariantStatus(fieldMetadata);
const isVariant = variantStatus
? Object.values(variantStatus).some((value) => value === true)
Expand Down Expand Up @@ -316,14 +343,17 @@ async function handleFieldSchemaAndIndividualFields(
content_type_uid,
fieldPath
);
const { acl: entryAcl, workflowStage: entryWorkflowStageDetails, resolvedVariantPermissions } =
await fetchEntryPermissionsAndStageDetails({
entryUid: entry_uid,
contentTypeUid: content_type_uid,
locale,
variantUid,
fieldPathWithIndex,
});
const {
acl: entryAcl,
workflowStage: entryWorkflowStageDetails,
resolvedVariantPermissions,
} = await fetchEntryPermissionsAndStageDetails({
entryUid: entry_uid,
contentTypeUid: content_type_uid,
locale,
variantUid,
fieldPathWithIndex,
});

if (fieldSchema) {
const { isDisabled } = isFieldDisabled(
Expand Down Expand Up @@ -376,4 +406,27 @@ function observeEditableElementChanges(
focusElementObserver.observe(editableElement, { attributes: true });
}

async function checkFieldLockStatus(fieldMetadata: CslpData): Promise<boolean> {
try {
const response = (await visualBuilderPostMessage?.send(
VisualBuilderPostMessageEvents.CHECK_OR_ACQUIRE_FIELD_LOCK,
{
fieldMetadata: {
content_type_uid: fieldMetadata.content_type_uid,
entry_uid: fieldMetadata.entry_uid,
fieldPath: fieldMetadata.fieldPath,
locale: fieldMetadata.locale,
variant: fieldMetadata.variant,
},
type: "click",
}
)) as { isLocked?: boolean } | undefined;

return response?.isLocked || false;
} catch (error) {
console.warn("Failed to check field lock status:", error);
return false;
}
}

export default handleBuilderInteraction;
Loading
Loading