From af6b2f909161c4d2138c19741f0502f291345313 Mon Sep 17 00:00:00 2001 From: Rob Gordon Date: Tue, 2 Jan 2024 11:38:13 -0500 Subject: [PATCH] Double-Clicking Node Jumps Cursor to Relevant Text (#634) * Basic jump to line in editor * Move mobile tab state into zustand * Toggle tab if necessary before focusing --- app/src/components/AppContextProvider.tsx | 15 ------------- app/src/components/EditWrapper.tsx | 8 +++---- app/src/components/Graph.tsx | 16 +++++++++++++- app/src/components/MobileTabToggle.tsx | 10 ++++----- app/src/lib/useEditorStore.ts | 22 ++++++++++++++++++ app/src/lib/useMobileStore.ts | 27 +++++++++++++++++++++++ 6 files changed, 73 insertions(+), 25 deletions(-) create mode 100644 app/src/lib/useMobileStore.ts diff --git a/app/src/components/AppContextProvider.tsx b/app/src/components/AppContextProvider.tsx index 55dc08b4a..88b799388 100644 --- a/app/src/components/AppContextProvider.tsx +++ b/app/src/components/AppContextProvider.tsx @@ -34,16 +34,12 @@ const defaultLanguage = Object.keys(languages).includes(browserLanguage) ? browserLanguage : "en"; -type mobileEditorTab = "text" | "graph"; - type TAppContext = { updateUserSettings: (newSettings: Partial) => void; theme: Theme; language: string; shareModal: boolean; setShareModal: Dispatch>; - mobileEditorTab: mobileEditorTab; - toggleMobileEditorTab: () => void; session: Session | null; customer?: CustomerInfo; customerIsLoading: boolean; @@ -64,15 +60,6 @@ const Provider = ({ children }: { children?: ReactNode }) => { LOCAL_STORAGE_SETTINGS_KEY, "{}" ); - const [mobileEditorTab, setMobileEditorTab] = - useState("text"); - const toggleMobileEditorTab = useCallback( - () => - setMobileEditorTab((currentTab) => - currentTab === "text" ? "graph" : "text" - ), - [] - ); const { settings, theme } = useMemo<{ settings: UserSettings; @@ -157,8 +144,6 @@ const Provider = ({ children }: { children?: ReactNode }) => { updateUserSettings, setShareModal, shareModal, - mobileEditorTab, - toggleMobileEditorTab, session, customer, customerIsLoading, diff --git a/app/src/components/EditWrapper.tsx b/app/src/components/EditWrapper.tsx index 2db9ba5e4..46f3bdf53 100644 --- a/app/src/components/EditWrapper.tsx +++ b/app/src/components/EditWrapper.tsx @@ -1,22 +1,22 @@ -import { ReactNode, useContext } from "react"; +import { ReactNode } from "react"; import { useFullscreen } from "../lib/hooks"; import { Box } from "../slang"; -import { AppContext } from "./AppContextProvider"; import styles from "./EditWrapper.module.css"; import MobileTabToggle from "./MobileTabToggle"; +import { useMobileStore } from "../lib/useMobileStore"; /** * Adds the wrapper for the toggle between the editor and the graph * on mobile. */ export function EditWrapper({ children }: { children: ReactNode }) { - const { mobileEditorTab } = useContext(AppContext); + const tab = useMobileStore((state) => state.tab); const isFullscreen = useFullscreen(); return ( {children} diff --git a/app/src/components/Graph.tsx b/app/src/components/Graph.tsx index bd0464f94..4b140eb2e 100644 --- a/app/src/components/Graph.tsx +++ b/app/src/components/Graph.tsx @@ -24,7 +24,11 @@ import { } from "../lib/preprocessStyle"; import { useContextMenuState } from "../lib/useContextMenuState"; import { Doc, useDoc, useParseErrorStore } from "../lib/useDoc"; -import { updateModelMarkers, useEditorStore } from "../lib/useEditorStore"; +import { + moveCursorToLine, + updateModelMarkers, + useEditorStore, +} from "../lib/useEditorStore"; import { useGraphStore } from "../lib/useGraphStore"; import { Box } from "../slang"; import { getNodePositionsFromCy } from "./getNodePositionsFromCy"; @@ -222,6 +226,16 @@ function initializeGraph({ } }); + // on double click, focus the line number in the editor + cyCurrent.on( + "dblclick", + "node, edge", + function handleDblClick(this: NodeSingular | EdgeSingular) { + const { lineNumber } = this.data(); + moveCursorToLine(lineNumber); + } + ); + cyCurrent.on("dragfree", handleDragFree); // on zoom diff --git a/app/src/components/MobileTabToggle.tsx b/app/src/components/MobileTabToggle.tsx index 2ae7176a4..6c3034ca0 100644 --- a/app/src/components/MobileTabToggle.tsx +++ b/app/src/components/MobileTabToggle.tsx @@ -1,11 +1,11 @@ import { t } from "@lingui/macro"; -import { useContext } from "react"; import { Box } from "../slang"; -import { AppContext } from "./AppContextProvider"; +import { useMobileStore } from "../lib/useMobileStore"; export default function MobileTabToggle() { - const { toggleMobileEditorTab, mobileEditorTab } = useContext(AppContext); + const tab = useMobileStore((state) => state.tab); + const toggleTab = useMobileStore((state) => state.toggleTab); return ( - {mobileEditorTab === "graph" ? t`Editor` : t`Graph`} + {tab === "graph" ? t`Editor` : t`Graph`} diff --git a/app/src/lib/useEditorStore.ts b/app/src/lib/useEditorStore.ts index 60506b585..7a1badce5 100644 --- a/app/src/lib/useEditorStore.ts +++ b/app/src/lib/useEditorStore.ts @@ -1,5 +1,6 @@ import type { editor } from "monaco-editor"; import { create } from "zustand"; +import { useMobileStore } from "./useMobileStore"; /** * Stores client-side state related to the editor. @@ -29,3 +30,24 @@ export function updateModelMarkers() { if (!model) return; monaco.editor.setModelMarkers(model, "editor", markers); } + +/** + * Focuses the editor. Then moves the cursor to the given line. + */ +export function moveCursorToLine(line: number) { + // We toggle the tab just in case we're in mobile view + const { tab, toggleTab } = useMobileStore.getState(); + if (tab === "graph") { + toggleTab(); + requestAnimationFrame(focus); + } else { + focus(); + } + + function focus() { + const { editor } = useEditorStore.getState(); + if (!editor) return; + editor.focus(); + editor.setPosition({ lineNumber: line, column: Infinity }); + } +} diff --git a/app/src/lib/useMobileStore.ts b/app/src/lib/useMobileStore.ts new file mode 100644 index 000000000..069bb67a9 --- /dev/null +++ b/app/src/lib/useMobileStore.ts @@ -0,0 +1,27 @@ +import { create } from "zustand"; +import { persist } from "zustand/middleware"; + +export type MobileEditorTab = "text" | "graph"; + +export type MobileStore = { + tab: MobileEditorTab; + toggleTab: () => void; +}; + +/** + * Stores client-side state related to the editor. + */ +export const useMobileStore = create()( + persist( + (set) => ({ + tab: "text", + toggleTab: () => + set((state) => ({ + tab: state.tab === "text" ? "graph" : "text", + })), + }), + { + name: "ff-mobile-store", + } + ) +);