From 7f8ddf53b32f0db5311bfd6230b231b9b3f42aed Mon Sep 17 00:00:00 2001 From: Fons van der Plas Date: Tue, 12 Dec 2023 14:14:51 +0100 Subject: [PATCH] little cleanup: useEventListener hook (#2749) --- frontend/common/useDialog.js | 12 +-- frontend/common/useEventListener.js | 10 +++ frontend/components/BottomRightPanel.js | 14 ++-- frontend/components/Cell.js | 55 ++++++------- .../components/CellInput/tab_help_plugin.js | 2 - frontend/components/ExportBanner.js | 27 ++++--- frontend/components/FrontmatterInput.js | 62 ++++++-------- .../HijackExternalLinksToOpenInNewTab.js | 18 +++-- frontend/components/NonCellOutput.js | 14 ++-- frontend/components/PasteHandler.js | 17 ++-- frontend/components/Popup.js | 81 +++++++++---------- frontend/components/Scroller.js | 17 ++-- frontend/components/UndoDelete.js | 2 +- 13 files changed, 161 insertions(+), 170 deletions(-) create mode 100644 frontend/common/useEventListener.js diff --git a/frontend/common/useDialog.js b/frontend/common/useDialog.js index 3c03f5fb70..9a81064704 100644 --- a/frontend/common/useDialog.js +++ b/frontend/common/useDialog.js @@ -1,7 +1,7 @@ //@ts-ignore import dialogPolyfill from "https://cdn.jsdelivr.net/npm/dialog-polyfill@0.5.6/dist/dialog-polyfill.esm.min.js" -import { useEffect, useLayoutEffect, useRef } from "../imports/Preact.js" +import { useLayoutEffect, useMemo, useRef } from "../imports/Preact.js" /** * @returns {[import("../imports/Preact.js").Ref, () => void, () => void, () => void]} @@ -13,9 +13,11 @@ export const useDialog = () => { if (dialog_ref.current != null) dialogPolyfill.registerDialog(dialog_ref.current) }, [dialog_ref.current]) - const open = () => dialog_ref.current?.showModal() - const close = () => dialog_ref.current?.close() - const toggle = () => (dialog_ref.current?.open === true ? dialog_ref.current?.close() : dialog_ref.current?.showModal()) + return useMemo(() => { + const open = () => dialog_ref.current?.showModal() + const close = () => dialog_ref.current?.close() + const toggle = () => (dialog_ref.current?.open === true ? dialog_ref.current?.close() : dialog_ref.current?.showModal()) - return [dialog_ref, open, close, toggle] + return [dialog_ref, open, close, toggle] + }, [dialog_ref]) } diff --git a/frontend/common/useEventListener.js b/frontend/common/useEventListener.js new file mode 100644 index 0000000000..1fcb44514a --- /dev/null +++ b/frontend/common/useEventListener.js @@ -0,0 +1,10 @@ +import { useCallback, useEffect } from "../imports/Preact.js" + +export const useEventListener = (element, event_name, handler, deps) => { + let handler_cached = useCallback(handler, deps) + useEffect(() => { + if (element == null) return + element.addEventListener(event_name, handler_cached) + return () => element.removeEventListener(event_name, handler_cached) + }, [element, event_name, handler_cached]) +} diff --git a/frontend/components/BottomRightPanel.js b/frontend/components/BottomRightPanel.js index f7ade801c4..df7c8b2a2c 100644 --- a/frontend/components/BottomRightPanel.js +++ b/frontend/components/BottomRightPanel.js @@ -5,6 +5,7 @@ import { LiveDocsTab } from "./LiveDocsTab.js" import { is_finished, ProcessTab, total_done, total_tasks, useStatusItem } from "./ProcessTab.js" import { useMyClockIsAheadBy } from "../common/clock sync.js" import { BackendLaunchPhase } from "../common/Binder.js" +import { useEventListener } from "../common/useEventListener.js" /** * @typedef PanelTabName @@ -40,8 +41,10 @@ export let BottomRightPanel = ({ const hidden = open_tab == null // Open panel when "open_bottom_right_panel" event is triggered - useEffect(() => { - let handler = (/** @type {CustomEvent} */ e) => { + useEventListener( + window, + "open_bottom_right_panel", + (/** @type {CustomEvent} */ e) => { console.log(e.detail) // https://github.com/fonsp/Pluto.jl/issues/321 focus_docs_on_open_ref.current = false @@ -49,10 +52,9 @@ export let BottomRightPanel = ({ if (window.getComputedStyle(container_ref.current).display === "none") { alert("This browser window is too small to show docs.\n\nMake the window bigger, or try zooming out.") } - } - window.addEventListener("open_bottom_right_panel", handler) - return () => window.removeEventListener("open_bottom_right_panel", handler) - }, []) + }, + [set_open_tab] + ) const status = useWithBackendStatus(notebook, backend_launch_phase) diff --git a/frontend/components/Cell.js b/frontend/components/Cell.js index a6e90612ff..81afa26d80 100644 --- a/frontend/components/Cell.js +++ b/frontend/components/Cell.js @@ -9,6 +9,7 @@ import { cl } from "../common/ClassTable.js" import { PlutoActionsContext } from "../common/PlutoContext.js" import { open_pluto_popup } from "../common/open_pluto_popup.js" import { SafePreviewOutput } from "./SafePreviewUI.js" +import { useEventListener } from "../common/useEventListener.js" const useCellApi = (node_ref, published_object_keys, pluto_actions) => { const [cell_api_ready, set_cell_api_ready] = useState(false) @@ -145,30 +146,34 @@ export const Cell = ({ const [cm_highlighted_line, set_cm_highlighted_line] = useState(null) const [cm_diagnostics, set_cm_diagnostics] = useState([]) - useEffect(() => { - const diagnosticListener = (e) => { + useEventListener( + window, + "cell_diagnostics", + (e) => { if (e.detail.cell_id === cell_id) { set_cm_diagnostics(e.detail.diagnostics) } - } - window.addEventListener("cell_diagnostics", diagnosticListener) - return () => window.removeEventListener("cell_diagnostics", diagnosticListener) - }, [cell_id]) + }, + [cell_id, set_cm_diagnostics] + ) - useEffect(() => { - const highlightRangeListener = (e) => { + useEventListener( + window, + "cell_highlight_range", + (e) => { if (e.detail.cell_id == cell_id && e.detail.from != null && e.detail.to != null) { set_cm_highlighted_range({ from: e.detail.from, to: e.detail.to }) } else { set_cm_highlighted_range(null) } - } - window.addEventListener("cell_highlight_range", highlightRangeListener) - return () => window.removeEventListener("cell_highlight_range", highlightRangeListener) - }, [cell_id]) + }, + [cell_id] + ) - useEffect(() => { - const focusListener = (e) => { + useEventListener( + window, + "cell_focus", + useCallback((e) => { if (e.detail.cell_id === cell_id) { if (e.detail.line != null) { const ch = e.detail.ch @@ -187,13 +192,8 @@ export const Cell = ({ } } } - } - window.addEventListener("cell_focus", focusListener) - // cleanup - return () => { - window.removeEventListener("cell_focus", focusListener) - } - }, []) + }, []) + ) // When you click to run a cell, we use `waiting_to_run` to immediately set the cell's traffic light to 'queued', while waiting for the backend to catch up. const [waiting_to_run, set_waiting_to_run] = useState(false) @@ -219,13 +219,14 @@ export const Cell = ({ disable_input_ref.current = disable_input const should_set_waiting_to_run_ref = useRef(true) should_set_waiting_to_run_ref.current = !running_disabled && !depends_on_disabled_cells - useEffect(() => { - const handler = (e) => { + useEventListener( + window, + "set_waiting_to_run_smart", + (e) => { if (e.detail.cell_ids.includes(cell_id)) set_waiting_to_run(should_set_waiting_to_run_ref.current) - } - window.addEventListener("set_waiting_to_run_smart", handler) - return () => window.removeEventListener("set_waiting_to_run_smart", handler) - }, [cell_id]) + }, + [cell_id, set_waiting_to_run] + ) const cell_api_ready = useCellApi(node_ref, published_object_keys, pluto_actions) const on_delete = useCallback(() => { diff --git a/frontend/components/CellInput/tab_help_plugin.js b/frontend/components/CellInput/tab_help_plugin.js index 0e35d264cd..228088a6c9 100644 --- a/frontend/components/CellInput/tab_help_plugin.js +++ b/frontend/components/CellInput/tab_help_plugin.js @@ -56,10 +56,8 @@ export const tab_help_plugin = ViewPlugin.define( this.setready(false) }, keydown: function (event, view) { - console.error(event.key, view.state.field(TabHelp), view.state.field(LastFocusWasForced)) if (event.key == "Tab") { if (view.state.field(TabHelp) && !view.state.field(LastFocusWasForced) && !view.state.readOnly) { - console.log(123) open_pluto_popup({ type: "info", source_element: view.dom, diff --git a/frontend/components/ExportBanner.js b/frontend/components/ExportBanner.js index 043b9afeef..959f659262 100644 --- a/frontend/components/ExportBanner.js +++ b/frontend/components/ExportBanner.js @@ -1,3 +1,4 @@ +import { useEventListener } from "../common/useEventListener.js" import { html, useEffect, useRef } from "../imports/Preact.js" const Circle = ({ fill }) => html` @@ -57,22 +58,24 @@ export const ExportBanner = ({ notebook_id, print_title, open, onClose, notebook // let print_old_title_ref = useRef("") - useEffect(() => { - let a = () => { + useEventListener( + window, + "beforeprint", + () => { console.log("beforeprint") print_old_title_ref.current = document.title document.title = print_title.replace(/\.jl$/, "").replace(/\.plutojl$/, "") - } - let b = () => { + }, + [print_title] + ) + useEventListener( + window, + "afterprint", + () => { document.title = print_old_title_ref.current - } - window.addEventListener("beforeprint", a) - window.addEventListener("afterprint", b) - return () => { - window.removeEventListener("beforeprint", a) - window.removeEventListener("afterprint", b) - } - }, [print_title]) + }, + [print_title] + ) return html`