Skip to content

Commit

Permalink
Feat: added section zoom on click
Browse files Browse the repository at this point in the history
  • Loading branch information
Akalanka47000 committed Mar 31, 2024
1 parent 8ec4447 commit 890c093
Show file tree
Hide file tree
Showing 13 changed files with 178 additions and 17 deletions.
1 change: 0 additions & 1 deletion src/components/controls/select/polyline/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ const PolylineSelectControls = () => {
id="stk-free-section-marker"
checked={firstPolyline.freeSeating}
onCheckedChange={onCheckedChange}
disabled={!!firstPolyline.section}
/>
<label
htmlFor="stk-free-section-marker"
Expand Down
26 changes: 25 additions & 1 deletion src/components/controls/select/text.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
import { useCallback } from "react";
import { useSelector } from "react-redux";
import { Checkbox } from "@/components/core";
import { store } from "@/store";
import { selectTextById, updateText } from "@/store/reducers/editor";
import { d3Extended, rgbToHex } from "@/utils";
import { default as ControlInput } from "../control-input";

const TextSelectControls = () => {
const selectedElementIds = useSelector((state: any) => state.editor.selectedElementIds);

const firstTextElement = useSelector(selectTextById(selectedElementIds[0]));
const firstElement = document.getElementById(selectedElementIds[0]);

const onCheckedChange = useCallback(
(value: boolean) => {
store.dispatch(updateText({ ids: selectedElementIds, data: { embraceOffset: value } }));
},
[selectedElementIds]
);

return (
<div className="flex flex-col gap-4 py-1">
<div className="grid grid-cols-3 items-center gap-4">
Expand Down Expand Up @@ -72,6 +83,19 @@ const TextSelectControls = () => {
});
}}
/>
<div className="col-span-3 w-full flex justify-end items-center gap-[2.3rem]">
<Checkbox
id="stk-embrace-offset-marker"
checked={firstTextElement.embraceOffset}
onCheckedChange={onCheckedChange}
/>
<label
htmlFor="stk-embrace-offset-marker"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Embrace Offset
</label>
</div>
</div>
</div>
);
Expand Down
19 changes: 17 additions & 2 deletions src/components/workspace/elements/polyline.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { forwardRef, useMemo } from "react";
import { twMerge } from "tailwind-merge";
import { dataAttributes } from "@/constants";
import { dataAttributes, selectors } from "@/constants";
import { IPolyline, ISTKProps, ISection } from "@/types";
import { d3Extended, getRelativeWorkspaceClickCoords } from "@/utils";
import { panAndZoomWithTransition } from "../zoom";

export interface IPolylineProps extends IPolyline {
className?: string;
Expand All @@ -16,7 +18,20 @@ const Polyline: React.FC<IPolylineProps> = forwardRef(

const localOnClick = (e) => {
onClick(e);
if (sectionObject) consumer.events?.onSectionClick?.(sectionObject);
if (sectionObject) {
consumer.events?.onSectionClick?.(sectionObject);
if (!sectionObject.freeSeating) {
const visibilityOffset = +d3Extended.select(selectors.workspaceGroup).attr(dataAttributes.visibilityOffset);
if (visibilityOffset > 0) {
const coords = getRelativeWorkspaceClickCoords(e);
panAndZoomWithTransition({
k: visibilityOffset,
x: coords.x - coords.x * visibilityOffset,
y: coords.y - coords.y * visibilityOffset
});
}
}
}
};

return (
Expand Down
16 changes: 15 additions & 1 deletion src/components/workspace/elements/text.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { forwardRef } from "react";
import { twMerge } from "tailwind-merge";
import { dataAttributes } from "@/constants";
import { ISTKProps, IText } from "@/types";

export const textFontSize = 35;
Expand All @@ -11,7 +12,19 @@ export interface ITextProps extends IText {

const Text: React.FC<ITextProps> = forwardRef(
(
{ x, y, id, label, fontSize = textFontSize, fontWeight = 200, letterSpacing = 3, color, consumer, ...props },
{
x,
y,
id,
label,
fontSize = textFontSize,
fontWeight = 200,
letterSpacing = 3,
color,
consumer,
embraceOffset,
...props
},
ref: any
) => {
return (
Expand All @@ -28,6 +41,7 @@ const Text: React.FC<ITextProps> = forwardRef(
{...props}
className={twMerge(props.className, consumer.styles?.elements?.text?.base?.className)}
style={consumer.styles?.elements?.text?.base?.properties}
{...{ [dataAttributes.embraceOffset]: embraceOffset }}
>
{label}
</text>
Expand Down
63 changes: 63 additions & 0 deletions src/components/workspace/elements/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as d3 from "d3";
import { dataAttributes } from "@/constants";
import { resizeCursors } from "@/hooks/interactions";
import { d3Extended } from "@/utils";
import Booth from "./booth";
Expand Down Expand Up @@ -83,3 +84,65 @@ export const handlePolylineDrag = d3.drag().on("drag", function (event) {
.join(" ");
me.attr("points", points);
});

export const showSeat = (seat: d3.Selection<Element, {}, HTMLElement, any>) => {
seat.style("opacity", "1");
seat.style("pointer-events", "all");
const label = d3Extended.selectById(`${seat.attr("id")}-label`);
label?.style("opacity", "1");
label?.style("pointer-events", "all");
};

export const hideSeat = (seat: d3.Selection<Element, {}, HTMLElement, any>) => {
seat.style("opacity", "0");
seat.style("pointer-events", "none");
const label = d3Extended.selectById(`${seat.attr("id")}-label`);
label?.style("opacity", "0");
label?.style("pointer-events", "none");
};

export const showPreOffsetElements = () => {
const seats = d3Extended.selectAll(`[${dataAttributes.elementType}="${ElementType.Seat}"]`);
const booths = d3Extended.selectAll(`[${dataAttributes.elementType}="${ElementType.Booth}"]`);
const sections = d3Extended.selectAll(`[${dataAttributes.elementType}="${ElementType.Polyline}"]`);
const elementsEmbracingOffset = d3Extended.selectAll(`[${dataAttributes.embraceOffset}="true"]`);
if (+seats.style("opacity") !== 0) {
seats.forEach(hideSeat);
booths.forEach((booth) => {
booth.style("opacity", 0);
booth.style("pointer-events", "none");
});
sections.forEach((section) => {
section.style("opacity", 1);
section.style("pointer-events", "all");
});
elementsEmbracingOffset.forEach((element) => {
element.style("opacity", 1);
element.style("pointer-events", "all");
});
}
};

export const showPostOffsetElements = () => {
const seats = d3Extended.selectAll(`[${dataAttributes.elementType}="${ElementType.Seat}"]`);
const booths = d3Extended.selectAll(`[${dataAttributes.elementType}="${ElementType.Booth}"]`);
const sections = d3Extended.selectAll(`[${dataAttributes.elementType}="${ElementType.Polyline}"]`);
const elementsEmbracingOffset = d3Extended.selectAll(`[${dataAttributes.embraceOffset}="true"]`);
if (+seats.style("opacity") !== 1) {
seats.forEach(showSeat);
booths.forEach((booth) => {
booth.style("opacity", 1);
booth.style("pointer-events", "all");
});
sections.forEach((section) => {
if (section.attr(dataAttributes.section)) {
section.style("opacity", 0);
section.style("pointer-events", "none");
}
});
elementsEmbracingOffset.forEach((element) => {
element.style("opacity", 0);
element.style("pointer-events", "none");
});
}
};
5 changes: 3 additions & 2 deletions src/components/workspace/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useCallback } from "react";
import { useSelector } from "react-redux";
import { twMerge } from "tailwind-merge";
import { ids } from "@/constants";
import { dataAttributes, ids } from "@/constants";
import { type ISTKProps } from "@/types";
import { Tool, tools } from "../toolbar/data";
import { default as Crosshairs } from "./crosshairs";
Expand Down Expand Up @@ -60,7 +60,7 @@ export const Workspace: React.FC<ISTKProps> = (props) => {
style={props.styles?.workspace?.root?.properties}
>
<svg id={ids.workspace} className="w-full h-full flex-1">
<g>
<g {...{ [dataAttributes.visibilityOffset]: "0" }}>
{seats.map((e) => (
<Element
key={e.id}
Expand All @@ -82,6 +82,7 @@ export const Workspace: React.FC<ISTKProps> = (props) => {
fontSize={e.fontSize}
fontWeight={e.fontWeight}
letterSpacing={e.letterSpacing}
embraceOffset={e.embraceOffset}
{...elementProps(e)}
/>
))}
Expand Down
16 changes: 12 additions & 4 deletions src/components/workspace/visibility.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,30 @@
import { memo } from "react";
import { useSelector } from "react-redux";
import { twMerge } from "tailwind-merge";
import { ids, selectors } from "@/constants";
import { dataAttributes, ids, selectors } from "@/constants";
import { store } from "@/store";
import { setInitialViewBoxScale, setVisibilityOffset } from "@/store/reducers/editor";
import type { ISTKProps } from "@/types";
import { d3Extended } from "@/utils";
import { Button } from "../core";
import { showPostOffsetElements } from "./elements";

const freeze = () =>
store.dispatch(setInitialViewBoxScale(d3Extended.zoomTransform(document.querySelector(selectors.workspaceGroup)).k));

const unfreeze = () => store.dispatch(setInitialViewBoxScale(null));

const setVisibility = () =>
store.dispatch(setVisibilityOffset(d3Extended.zoomTransform(document.querySelector(selectors.workspaceGroup)).k));
const setVisibility = () => {
const offset = d3Extended.zoomTransform(document.querySelector(selectors.workspaceGroup)).k;
store.dispatch(setVisibilityOffset(offset));
d3Extended.select(selectors.workspaceGroup).attr(dataAttributes.visibilityOffset, offset);
};

const unsetVisibility = () => store.dispatch(setVisibilityOffset(0));
const unsetVisibility = () => {
d3Extended.select(selectors.workspaceGroup).attr(dataAttributes.visibilityOffset, 0);
store.dispatch(setVisibilityOffset(0));
showPostOffsetElements();
};

const VisibilityControls = (props: ISTKProps) => {
const initialViewBoxScale = useSelector((state: any) => state.editor.initialViewBoxScale);
Expand Down
15 changes: 13 additions & 2 deletions src/components/workspace/zoom.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,21 @@ import { memo, useLayoutEffect } from "react";
import { ChevronDown, ChevronLeft, ChevronRight, ChevronUp, Minus, Plus } from "lucide-react";
import { useSelector } from "react-redux";
import { twMerge } from "tailwind-merge";
import { ids, selectors } from "@/constants";
import { dataAttributes, ids, selectors } from "@/constants";
import { useSkipFirstRender } from "@/hooks";
import type { ISTKProps } from "@/types";
import { d3Extended } from "@/utils";
import { Tool } from "../toolbar/data";
import { showPostOffsetElements, showPreOffsetElements } from "./elements";

function handleZoom(e) {
const workspace = d3Extended.select(selectors.workspaceGroup);
const visibilityOffset = +workspace.attr(dataAttributes.visibilityOffset) || 0;
if (e.transform.k * 1.1 < visibilityOffset) {
showPreOffsetElements();
} else {
showPostOffsetElements();
}
workspace.attr("transform", e.transform);
}

Expand Down Expand Up @@ -40,10 +47,14 @@ const panDown = () => {
};

export const panAndZoom = ({ k, x, y }) => {
d3Extended.selectById(ids.workspace).call(zoom.transform, d3Extended.zoomIdentity.translate(x, y).scale(k));
};

export const panAndZoomWithTransition = ({ k, x, y }) => {
d3Extended
.selectById(ids.workspace)
.transition()
.duration(0)
.duration(1000)
.call(zoom.transform, d3Extended.zoomIdentity.translate(x, y).scale(k));
};

Expand Down
4 changes: 3 additions & 1 deletion src/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ export const dataAttributes = {
shape: "data-shape",
category: "data-category",
status: "data-status",
section: "data-section"
section: "data-section",
visibilityOffset: "data-seat-visibility-offset",
embraceOffset: "data-embrace-offset"
};

export const seatStatusColors = {
Expand Down
8 changes: 6 additions & 2 deletions src/hooks/events/workspace-load.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useLayoutEffect } from "react";
import { panAndZoom } from "@/components/workspace/zoom";
import { ids, selectors } from "@/constants";
import { dataAttributes, ids, selectors } from "@/constants";
import { store } from "@/store";
import { initializeElements, initializeWorkspace, sync } from "@/store/reducers/editor";
import { ISTKProps } from "@/types";
Expand All @@ -14,13 +14,17 @@ const useWorkspaceLoad = (props: ISTKProps) => {
const { height: workspaceheight, width: workspaceWidth } = d3Extended.selectionBounds(
d3Extended.selectById(ids.workspace)
);
const workspaceGroup = d3Extended.select(selectors.workspaceGroup);
const {
left: wgOffsetLeft,
top: wgOffsetTop,
height: workspaceGroupHeight,
width: workspaceGroupWidth
} = d3Extended.selectionBounds(d3Extended.select(selectors.workspaceGroup));
} = d3Extended.selectionBounds(workspaceGroup);
let scaleFactor = props.data.workspace?.initialViewBoxScale ?? 1;
if (props.data.workspace?.visibilityOffset) {
workspaceGroup.attr(dataAttributes.visibilityOffset, props.data.workspace.visibilityOffset);
}
scaleFactor *= 1.05;
panAndZoom({
k: scaleFactor,
Expand Down
14 changes: 13 additions & 1 deletion src/store/reducers/editor/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const initialState = {
name: "Section 1",
color: "#000000",
stroke: "#000000",
freeSeating: true
freeSeating: false
},
{
id: uuidv4(),
Expand Down Expand Up @@ -158,6 +158,11 @@ export const slice = createSlice({
deleteText(state, action) {
state.text = state.text.filter((text) => text.id !== action.payload);
},
updateText(state, action) {
state.text = state.text.map((text) =>
action.payload.ids.includes(text.id) ? { ...text, ...action.payload.data } : text
);
},
addShape(state, action) {
state.shapes.push(action.payload);
},
Expand Down Expand Up @@ -265,6 +270,7 @@ export const {
deleteBooth,
addText,
deleteText,
updateText,
addShape,
deleteShape,
addPolyline,
Expand Down Expand Up @@ -292,4 +298,10 @@ export const selectPolylineById = (id: string) =>
(polylines) => polylines.find((polyline) => polyline.id === id)
);

export const selectTextById = (id: string) =>
createSelector(
(state: any) => state.editor.text,
(text) => text.find((t) => t.id === id)
);

export default slice.reducer as Reducer<typeof initialState>;
1 change: 1 addition & 0 deletions src/types/elements/text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ export interface IText {
fontWeight?: number;
letterSpacing?: number;
color?: string;
embraceOffset?: boolean;
}
7 changes: 7 additions & 0 deletions src/utils/d3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ declare module "d3" {
moveToBack(): Selection<GElement, Datum, PElement, PDatum>;
moveToFront(): Selection<GElement, Datum, PElement, PDatum>;
map<T>(callback: (d: Selection<GElement, Datum, PElement, PDatum>, i: number) => T): T[];
forEach<T>(callback: (d: Selection<GElement, Datum, PElement, PDatum>, i: number) => T): T[];
}
}

Expand All @@ -31,6 +32,12 @@ d3.selection.prototype.map = function (callback) {
return results;
};

d3.selection.prototype.forEach = function (callback) {
this.each(function (_, i) {
callback(d3.select(this), i);
});
};

export const d3Extended = {
...d3,
selectById(id: string): d3.Selection<Element, {}, HTMLElement, any> {
Expand Down

0 comments on commit 890c093

Please sign in to comment.