diff --git a/src/main/endpoints/deselectNode.ts b/src/main/endpoints/deselectNode.ts new file mode 100644 index 0000000..3af41df --- /dev/null +++ b/src/main/endpoints/deselectNode.ts @@ -0,0 +1,44 @@ +import { createEndpoint } from "../utils/createEndpoint"; + +export type DeselectNodeProps = { + id: string; +}; + +export const deselectNodeEndpoint = createEndpoint( + "DESELECT_NODE", + async ({ id }: DeselectNodeProps) => { + const currentSelection = figma.currentPage.selection; + + if (currentSelection.length === 0) { + return; + } + + // Check if all selected nodes are text nodes removed from the selection + if (currentSelection.every((node) => node.type === "TEXT")) { + figma.currentPage.selection = currentSelection.filter( + (node) => node.id !== id + ); + + return; + } + + // Expand selection to include all text nodes + const expandedSelection = currentSelection.reduce((acc, node) => { + if (node.type === "TEXT") { + acc.push(node); + } else if (node.type === "FRAME" || node.type === "COMPONENT") { + acc.push( + ...node.findAllWithCriteria({ + types: ["TEXT"], + }) + ); + } + + return acc; + }, [] as TextNode[]); + + figma.currentPage.selection = expandedSelection.filter( + (node) => node.id !== id + ); + } +); diff --git a/src/main/main.ts b/src/main/main.ts index 13a20b6..7534528 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -26,6 +26,7 @@ import { } from "./utils/settingsTools"; import { DEFAULT_SIZE } from "@/ui/hooks/useWindowSize"; import { highlightNodeEndpoint } from "./endpoints/highlightNode"; +import { deselectNodeEndpoint } from "./endpoints/deselectNode"; const getAllPages = () => { const document = figma.root; @@ -93,6 +94,7 @@ export default async function () { updateNodesEndpoint.register(); setNodesDataEndpoint.register(); highlightNodeEndpoint.register(); + deselectNodeEndpoint.register(); const config = await getPluginData(); diff --git a/src/ui/components/DeselectNodeButton/DeselectNodeButton.css b/src/ui/components/DeselectNodeButton/DeselectNodeButton.css new file mode 100644 index 0000000..ca3c8e2 --- /dev/null +++ b/src/ui/components/DeselectNodeButton/DeselectNodeButton.css @@ -0,0 +1,9 @@ +.deselectButton { + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + width: 20px; + height: 20px; + color: var(--figma-color-text-secondary); +} diff --git a/src/ui/components/DeselectNodeButton/DeselectNodeButton.css.d.ts b/src/ui/components/DeselectNodeButton/DeselectNodeButton.css.d.ts new file mode 100644 index 0000000..f084746 --- /dev/null +++ b/src/ui/components/DeselectNodeButton/DeselectNodeButton.css.d.ts @@ -0,0 +1,5 @@ +declare const styles: { + readonly "deselectButton": string; +}; +export = styles; + diff --git a/src/ui/components/DeselectNodeButton/DeselectNodeButton.tsx b/src/ui/components/DeselectNodeButton/DeselectNodeButton.tsx new file mode 100644 index 0000000..efaa120 --- /dev/null +++ b/src/ui/components/DeselectNodeButton/DeselectNodeButton.tsx @@ -0,0 +1,29 @@ +import { h } from "preact"; + +import styles from "./DeselectNodeButton.css"; +import { RemoveFromList } from "@/ui/icons/SvgIcons"; +import { useDeselectNodeMutation } from "@/ui/hooks/useDeselectNodeMutation"; + +type Props = { + nodeId: string; +}; + +export const DeselectNodeButton = ({ nodeId }: Props) => { + const deselectMutation = useDeselectNodeMutation(); + + const handleDeselect = () => { + deselectMutation.mutate({ id: nodeId }); + }; + + return ( +
+ +
+ ); +}; diff --git a/src/ui/hooks/useDeselectNodeMutation.ts b/src/ui/hooks/useDeselectNodeMutation.ts new file mode 100644 index 0000000..c5fd29c --- /dev/null +++ b/src/ui/hooks/useDeselectNodeMutation.ts @@ -0,0 +1,12 @@ +import { + DeselectNodeProps, + deselectNodeEndpoint, +} from "@/main/endpoints/deselectNode"; +import { useMutation } from "react-query"; + +export const useDeselectNodeMutation = () => { + return useMutation( + [deselectNodeEndpoint.name], + (data: DeselectNodeProps) => deselectNodeEndpoint.call(data) + ); +}; diff --git a/src/ui/icons/SvgIcons.tsx b/src/ui/icons/SvgIcons.tsx index fd715ce..ee7ac5a 100644 --- a/src/ui/icons/SvgIcons.tsx +++ b/src/ui/icons/SvgIcons.tsx @@ -61,3 +61,14 @@ export const MyLocation = (props: h.JSX.SVGAttributes) => { ); }; + +export const RemoveFromList = (props: h.JSX.SVGAttributes) => { + return ( + + + + ); +}; diff --git a/src/ui/views/Index/Index.tsx b/src/ui/views/Index/Index.tsx index a53ff29..571d556 100644 --- a/src/ui/views/Index/Index.tsx +++ b/src/ui/views/Index/Index.tsx @@ -30,6 +30,7 @@ import { TopBar } from "../../components/TopBar/TopBar"; import styles from "./Index.css"; import { KeyInput } from "./KeyInput"; import { useSetNodesDataMutation } from "@/ui/hooks/useSetNodesDataMutation"; +import { DeselectNodeButton } from "@/ui/components/DeselectNodeButton/DeselectNodeButton"; export const Index = () => { const selectionLoadable = useSelectedNodes(); @@ -271,6 +272,8 @@ export const Index = () => { /> )} + + ); }}