Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: use zone elevatedBackground evaluated values and upgrade space distribution ux of borderless zones. #33527

Merged
merged 10 commits into from
May 21, 2024
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,17 @@ import { getWidgetErrorCount } from "layoutSystems/anvil/editor/AnvilWidgetName/
import {
getAnvilHighlightShown,
getAnvilSpaceDistributionStatus,
getWidgetsDistributingSpace,
} from "layoutSystems/anvil/integrations/selectors";
import { useSelector } from "react-redux";
import { combinedPreviewModeSelector } from "selectors/editorSelectors";
import { isWidgetFocused, isWidgetSelected } from "selectors/widgetSelectors";

export function useWidgetBorderStyles(widgetId: string, widgetType: string) {
export function useWidgetBorderStyles(
widgetId: string,
widgetType: string,
elevatedBackground?: boolean,
) {
/** Selectors */
const isFocused = useSelector(isWidgetFocused(widgetId));
const isSelected = useSelector(isWidgetSelected(widgetId));
Expand All @@ -28,7 +33,9 @@ export function useWidgetBorderStyles(widgetId: string, widgetType: string) {
const isDistributingSpace: boolean = useSelector(
getAnvilSpaceDistributionStatus,
);

const widgetsEffectedBySpaceDistribution = useSelector(
getWidgetsDistributingSpace,
);
const isPreviewMode = useSelector(combinedPreviewModeSelector);

/** EO selectors */
Expand All @@ -37,11 +44,14 @@ export function useWidgetBorderStyles(widgetId: string, widgetType: string) {
if (isPreviewMode) {
return {};
}

const isZoneDistributingSpace =
widgetsEffectedBySpaceDistribution.zones.includes(widgetId);
// If the widget is a zone and is distributing space and has no elevated background
const isZoneNotElevated = isZoneDistributingSpace && !elevatedBackground;
// Show the border if the widget has widgets being dragged or redistributed inside it
const showDraggedOnBorder =
(highlightShown && highlightShown.canvasId === widgetId) ||
(isDistributingSpace && isSelected);
isZoneNotElevated;

const onCanvasUI = WidgetFactory.getConfig(widgetType)?.onCanvasUI;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export const AnvilEditorFlexComponent = (props: AnvilFlexComponentProps) => {
props.widgetName,
props.isVisible,
props.widgetType,
!!props.elevatedBackground,
ref,
);
useAnvilWidgetDrag(props.widgetId, props.widgetType, props.layoutId, ref);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export const AnvilEditorWidgetOnion = (props: BaseWidgetProps) => {
}, [isPreviewMode, props.type]);
return (
<WidgetWrapper
elevatedBackground={!!props.elevatedBackground}
flexGrow={props.flexGrow}
isVisible={!!props.isVisible}
layoutId={props.layoutId}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import { EVAL_ERROR_PATH } from "utils/DynamicBindingUtils";
import get from "lodash/get";
import { createSelector } from "reselect";
import { getIsDragging } from "selectors/widgetDragSelectors";
import { getAnvilHighlightShown } from "layoutSystems/anvil/integrations/selectors";
import {
getAnvilHighlightShown,
getAnvilSpaceDistributionStatus,
} from "layoutSystems/anvil/integrations/selectors";
import { isWidgetFocused, isWidgetSelected } from "selectors/widgetSelectors";
import { isEditOnlyModeSelector } from "selectors/editorSelectors";

Expand Down Expand Up @@ -58,14 +61,16 @@ export function shouldSelectOrFocus(widgetId: string) {
getAnvilHighlightShown,
isWidgetSelected(widgetId),
isWidgetFocused(widgetId),
getAnvilSpaceDistributionStatus,
(
isEditorOpen,
isDragging,
highlightShown,
isWidgetSelected,
isWidgetFocused,
isDistributingSpace,
) => {
const baseCondition = isEditorOpen && !isDragging;
const baseCondition = isEditorOpen && !isDragging && !isDistributingSpace;
let onCanvasUIState: NameComponentStates = "none";
if (baseCondition) {
if (isWidgetSelected) onCanvasUIState = "select";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,14 @@ import { AnvilViewerCanvas } from "layoutSystems/anvil/viewer/canvas/AnvilViewer
import React, { useCallback, useEffect, useRef } from "react";
import { useSelectWidgetListener } from "./hooks/useSelectWidgetListener";
import { useClickToClearSelections } from "./hooks/useClickToClearSelections";
import {
useAnvilGlobalDnDStates,
type AnvilGlobalDnDStates,
} from "./hooks/useAnvilGlobalDnDStates";
import type { AnvilGlobalDnDStates } from "./hooks/useAnvilGlobalDnDStates";
import { useAnvilGlobalDnDStates } from "./hooks/useAnvilGlobalDnDStates";
import { AnvilDragPreview } from "../canvasArenas/AnvilDragPreview";
import { AnvilWidgetElevationProvider } from "./providers/AnvilWidgetElevationProvider";

export const AnvilDnDStatesContext = React.createContext<
AnvilGlobalDnDStates | undefined
>(undefined);

/**
* Anvil Main Canvas is just a wrapper around AnvilCanvas.
* Why do we need this?
Expand Down Expand Up @@ -58,14 +56,16 @@ export const AnvilEditorCanvas = (props: BaseWidgetProps) => {
// using AnvilDnDStatesContext to provide the states to the child AnvilDraggingArena
const anvilGlobalDnDStates = useAnvilGlobalDnDStates();
return (
<AnvilDnDStatesContext.Provider value={anvilGlobalDnDStates}>
<AnvilViewerCanvas {...props} ref={canvasRef} />
<AnvilDragPreview
dragDetails={anvilGlobalDnDStates.dragDetails}
draggedBlocks={anvilGlobalDnDStates.draggedBlocks}
isDragging={anvilGlobalDnDStates.isDragging}
isNewWidget={anvilGlobalDnDStates.isNewWidget}
/>
</AnvilDnDStatesContext.Provider>
<AnvilWidgetElevationProvider>
<AnvilDnDStatesContext.Provider value={anvilGlobalDnDStates}>
<AnvilViewerCanvas {...props} ref={canvasRef} />
<AnvilDragPreview
dragDetails={anvilGlobalDnDStates.dragDetails}
draggedBlocks={anvilGlobalDnDStates.draggedBlocks}
isDragging={anvilGlobalDnDStates.isDragging}
isNewWidget={anvilGlobalDnDStates.isNewWidget}
/>
</AnvilDnDStatesContext.Provider>
</AnvilWidgetElevationProvider>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { useEffect } from "react";
import { useAnvilWidgetElevation } from "../providers/AnvilWidgetElevationProvider";

export const useAnvilWidgetElevationSetter = (
widgetId: string,
elevatedBackground: boolean,
) => {
const anvilWidgetElevation = useAnvilWidgetElevation();
const { setWidgetElevation } = anvilWidgetElevation || {};
useEffect(() => {
if (setWidgetElevation) {
setWidgetElevation(widgetId, elevatedBackground);
}
}, [elevatedBackground, setWidgetElevation]);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import React, {
type ReactNode,
createContext,
useState,
useCallback,
useContext,
} from "react";

interface WidgetElevation {
[key: string]: boolean;
}

interface AnvilWidgetElevationContextType {
elevatedWidgets: WidgetElevation;
setWidgetElevation: (widgetId: string, isElevated: boolean) => void;
}

const AnvilWidgetElevationContext = createContext<
AnvilWidgetElevationContextType | undefined
>(undefined);

export const useAnvilWidgetElevation = () =>
useContext(AnvilWidgetElevationContext);
/**
* AnvilWidgetElevationProvider indexes all sections and zones and records their evaluated value of elevation(Visual Separation).
*
* Why not just use the evaluated values directly?
* Because we need to keep track of the elevation of each widget in the editor to apply the correct elevation styles.
* elevation being a bindable property, we need to keep track of the evaluated value of elevation of each sections and zones in the editor.
*
* When adding compensators to the dragged widgets(useAnvilDnDCompensators), we need to know the elevation of the zone widget as well as its corresponding siblings
* to decide if the zone has to treated as a elevated zone or not.
*
* In-order to skip iterating the data tree every time we need to know the elevation of a widget, we are storing the elevation of each widget in this context.
* This way we do not have dependency on the data tree to know the elevation of a widget.
*/
export const AnvilWidgetElevationProvider = ({
children,
}: {
children: ReactNode;
}) => {
const [elevatedWidgets, setElevatedWidgets] = useState<WidgetElevation>({});

const setWidgetElevation = useCallback(
(widgetId: string, isElevated: boolean) => {
setElevatedWidgets((prev) => {
return {
...prev,
[widgetId]: isElevated,
};
});
},
[setElevatedWidgets],
);

return (
<AnvilWidgetElevationContext.Provider
value={{
elevatedWidgets,
setWidgetElevation,
}}
>
{children}
</AnvilWidgetElevationContext.Provider>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export const AnvilModalDropArena = ({
return (
<StyledModalEditorDropArenaWrapper
isModalEmpty={isModalEmpty}
style={{ height: "100%" }}
style={{ height: isModalEmpty ? "100%" : "auto" }}
>
<StyledEmptyModalDropArena
isActive={isCurrentDraggedCanvas}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type { AnvilGlobalDnDStates } from "../../canvas/hooks/useAnvilGlobalDnDS
import { getWidgets } from "sagas/selectors";
import { useMemo } from "react";
import { ZoneWidget } from "widgets/anvil/ZoneWidget";
import { useAnvilWidgetElevation } from "../../canvas/providers/AnvilWidgetElevationProvider";

interface AnvilDnDListenerStatesProps {
anvilGlobalDragStates: AnvilGlobalDnDStates;
Expand Down Expand Up @@ -97,6 +98,8 @@ export const useAnvilDnDListenerStates = ({
mainCanvasLayoutId,
} = anvilGlobalDragStates;
const allWidgets = useSelector(getWidgets);
const anvilWidgetElevation = useAnvilWidgetElevation();
const elevatedWidgets = anvilWidgetElevation?.elevatedWidgets || {};
const widgetProps = allWidgets[widgetId];
const selectedWidgets = useSelector(getSelectedWidgets);
/**
Expand Down Expand Up @@ -134,22 +137,21 @@ export const useAnvilDnDListenerStates = ({
(each) => !allWidgets[each].detachFromLayout,
).length === 0;

const allSiblingsWidgets = useMemo(() => {
const allSiblings =
(widgetProps.parentId && allWidgets[widgetProps.parentId]?.children) ||
[];
return allSiblings.map((each) => allWidgets[each]);
const allSiblingsWidgetIds = useMemo(() => {
return (
(widgetProps.parentId && allWidgets[widgetProps.parentId]?.children) || []
);
}, [widgetProps, allWidgets]);

const isElevatedWidget = useMemo(() => {
if (widgetProps.type === ZoneWidget.type) {
const isAnyZoneElevated = allSiblingsWidgets.some(
(each) => !!each.elevatedBackground,
const isAnyZoneElevated = allSiblingsWidgetIds.some(
(each) => !!elevatedWidgets[each],
);
return isAnyZoneElevated;
}
return !!widgetProps.elevatedBackground;
}, [widgetProps, allSiblingsWidgets]);
return !!elevatedWidgets[widgetId];
}, [widgetProps, elevatedWidgets, allSiblingsWidgetIds]);

const {
edgeCompensatorValues,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const useAnvilWidgetStyles = (
widgetName: string,
isVisible = true,
widgetType: string,
elevatedBackground: boolean,
ref: React.RefObject<HTMLDivElement>, // Ref object to reference the AnvilFlexComponent
) => {
// Selectors to determine whether the widget is selected or dragging
Expand All @@ -18,7 +19,11 @@ export const useAnvilWidgetStyles = (
(state: AppState) => state.ui.widgetDragResize.isDragging,
);
// Get widget border styles using useWidgetBorderStyles
const widgetBorderStyles = useWidgetBorderStyles(widgetId, widgetType);
const widgetBorderStyles = useWidgetBorderStyles(
widgetId,
widgetType,
elevatedBackground,
);

// Effect hook to apply widget border styles to the widget
useEffect(() => {
Expand Down
6 changes: 5 additions & 1 deletion app/client/src/layoutSystems/anvil/integrations/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ export function getDropTargetLayoutId(state: AppState, canvasId: string) {
* Returns a boolean indicating if space distribution is in progress
*/
export function getAnvilSpaceDistributionStatus(state: AppState) {
return state.ui.widgetDragResize.anvil.isDistributingSpace;
return state.ui.widgetDragResize.anvil.spaceDistribution.isDistributingSpace;
}

export function getWidgetsDistributingSpace(state: AppState) {
return state.ui.widgetDragResize.anvil.spaceDistribution.widgetsEffected;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { AnvilReduxActionTypes } from "../integrations/actions/actionTypes";

export const startAnvilSpaceDistributionAction = (payload: {
section: string;
zones: string[];
}) => {
return {
type: AnvilReduxActionTypes.ANVIL_SPACE_DISTRIBUTION_START,
payload,
};
};

export const stopAnvilSpaceDistributionAction = () => {
return {
type: AnvilReduxActionTypes.ANVIL_SPACE_DISTRIBUTION_STOP,
};
};

export const updateSpaceDistributionAction = (
sectionLayoutId: string,
zonesDistributed: {
[widgetId: string]: number;
},
) => {
return {
type: AnvilReduxActionTypes.ANVIL_SPACE_DISTRIBUTION_UPDATE,
payload: {
zonesDistributed,
sectionLayoutId,
},
};
};
Loading
Loading