From 6413d49119a58a806a47071d63889c3c965bc385 Mon Sep 17 00:00:00 2001 From: Evan Simkowitz Date: Tue, 3 Sep 2024 18:43:59 -0700 Subject: [PATCH] Make default monaco theme transparent, remove import errors (#308) This makes the background for the "wave-theme-dark" theme transparent. The light theme is still opaque because otherwise it will look somewhat dark. This also suppresses TypeScript/JavaScript import errors in the default linter, since we don't have support for project directories. This also reworks the useWidth and useHeight hooks to use the useResizeObserver hook, which limits the number of ResizeObserver instances floating around, thereby improving performance --- frontend/app/hook/useDimensions.tsx | 94 ++++++++------------- frontend/app/hook/useHeight.tsx | 46 +++------- frontend/app/hook/useWidth.tsx | 46 +++------- frontend/app/view/codeeditor/codeeditor.tsx | 23 +++-- 4 files changed, 79 insertions(+), 130 deletions(-) diff --git a/frontend/app/hook/useDimensions.tsx b/frontend/app/hook/useDimensions.tsx index 7455b2622..ac9f9086f 100644 --- a/frontend/app/hook/useDimensions.tsx +++ b/frontend/app/hook/useDimensions.tsx @@ -1,6 +1,13 @@ -import debounce from "lodash.debounce"; -import { useCallback, useEffect, useRef, useState } from "react"; +import useResizeObserver from "@react-hook/resize-observer"; +import { useCallback, useRef, useState } from "react"; +import { debounce } from "throttle-debounce"; +/** + * Get the current dimensions for the specified element, and whether it is currently changing size. Update when the element resizes. + * @param ref The reference to the element to observe. + * @param delay The debounce delay to use for updating the dimensions. + * @returns The dimensions of the element, and direction in which the dimensions are changing. + */ const useDimensions = (ref: React.RefObject, delay = 0) => { const [dimensions, setDimensions] = useState<{ height: number | null; @@ -17,73 +24,42 @@ const useDimensions = (ref: React.RefObject, delay = 0) => { width: null, }); - const updateDimensions = useCallback(() => { - if (ref.current) { - const element = ref.current; - const style = window.getComputedStyle(element); - const paddingTop = parseFloat(style.paddingTop); - const paddingBottom = parseFloat(style.paddingBottom); - const paddingLeft = parseFloat(style.paddingLeft); - const paddingRight = parseFloat(style.paddingRight); - const marginTop = parseFloat(style.marginTop); - const marginBottom = parseFloat(style.marginBottom); - const marginLeft = parseFloat(style.marginLeft); - const marginRight = parseFloat(style.marginRight); + const updateDimensions = useCallback((entry: ResizeObserverEntry) => { + const parentHeight = entry.contentRect.height; + const parentWidth = entry.contentRect.width; - const parentHeight = element.clientHeight - paddingTop - paddingBottom - marginTop - marginBottom; - const parentWidth = element.clientWidth - paddingLeft - paddingRight - marginLeft - marginRight; + let widthDirection = ""; + let heightDirection = ""; - let widthDirection = ""; - let heightDirection = ""; - - if (previousDimensions.current.width !== null && previousDimensions.current.height !== null) { - if (parentWidth > previousDimensions.current.width) { - widthDirection = "expanding"; - } else if (parentWidth < previousDimensions.current.width) { - widthDirection = "shrinking"; - } else { - widthDirection = "unchanged"; - } - - if (parentHeight > previousDimensions.current.height) { - heightDirection = "expanding"; - } else if (parentHeight < previousDimensions.current.height) { - heightDirection = "shrinking"; - } else { - heightDirection = "unchanged"; - } + if (previousDimensions.current.width !== null && previousDimensions.current.height !== null) { + if (parentWidth > previousDimensions.current.width) { + widthDirection = "expanding"; + } else if (parentWidth < previousDimensions.current.width) { + widthDirection = "shrinking"; + } else { + widthDirection = "unchanged"; } - previousDimensions.current = { height: parentHeight, width: parentWidth }; - - setDimensions({ height: parentHeight, width: parentWidth, widthDirection, heightDirection }); + if (parentHeight > previousDimensions.current.height) { + heightDirection = "expanding"; + } else if (parentHeight < previousDimensions.current.height) { + heightDirection = "shrinking"; + } else { + heightDirection = "unchanged"; + } } - }, [ref]); - const fUpdateDimensions = useCallback(delay > 0 ? debounce(updateDimensions, delay) : updateDimensions, [ + previousDimensions.current = { height: parentHeight, width: parentWidth }; + + setDimensions({ height: parentHeight, width: parentWidth, widthDirection, heightDirection }); + }, []); + + const fUpdateDimensions = useCallback(delay > 0 ? debounce(delay, updateDimensions) : updateDimensions, [ updateDimensions, delay, ]); - useEffect(() => { - const resizeObserver = new ResizeObserver(() => { - fUpdateDimensions(); - }); - - if (ref.current) { - resizeObserver.observe(ref.current); - fUpdateDimensions(); - } - - return () => { - if (ref.current) { - resizeObserver.unobserve(ref.current); - } - if (delay > 0) { - fUpdateDimensions.cancel(); - } - }; - }, [fUpdateDimensions]); + useResizeObserver(ref, fUpdateDimensions); return dimensions; }; diff --git a/frontend/app/hook/useHeight.tsx b/frontend/app/hook/useHeight.tsx index 74791d0fc..e6ce41c31 100644 --- a/frontend/app/hook/useHeight.tsx +++ b/frontend/app/hook/useHeight.tsx @@ -1,46 +1,26 @@ // Copyright 2024, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 -import debounce from "lodash.debounce"; -import { useCallback, useEffect, useState } from "react"; +import useResizeObserver from "@react-hook/resize-observer"; +import { useCallback, useState } from "react"; +import { debounce } from "throttle-debounce"; +/** + * Get the height of the specified element and update it when the element resizes. + * @param ref The reference to the element to observe. + * @param delay The debounce delay to use for updating the height. + * @returns The current height of the element, or null if the element is not yet mounted. + */ const useHeight = (ref: React.RefObject, delay = 0) => { const [height, setHeight] = useState(null); - const updateHeight = useCallback(() => { - if (ref.current) { - const element = ref.current; - const style = window.getComputedStyle(element); - const paddingTop = parseFloat(style.paddingTop); - const paddingBottom = parseFloat(style.paddingBottom); - const marginTop = parseFloat(style.marginTop); - const marginBottom = parseFloat(style.marginBottom); - const parentHeight = element.clientHeight - paddingTop - paddingBottom - marginTop - marginBottom; - setHeight(parentHeight); - } + const updateHeight = useCallback((entry: ResizeObserverEntry) => { + setHeight(entry.contentRect.height); }, []); - const fUpdateHeight = useCallback(delay > 0 ? debounce(updateHeight, delay) : updateHeight, [updateHeight, delay]); + const fUpdateHeight = useCallback(delay > 0 ? debounce(delay, updateHeight) : updateHeight, [updateHeight, delay]); - useEffect(() => { - const resizeObserver = new ResizeObserver(() => { - fUpdateHeight(); - }); - - if (ref.current) { - resizeObserver.observe(ref.current); - fUpdateHeight(); - } - - return () => { - if (ref.current) { - resizeObserver.unobserve(ref.current); - } - if (delay > 0) { - fUpdateHeight.cancel(); - } - }; - }, [fUpdateHeight]); + useResizeObserver(ref, fUpdateHeight); return height; }; diff --git a/frontend/app/hook/useWidth.tsx b/frontend/app/hook/useWidth.tsx index 3eb7eb74c..c7007d7f4 100644 --- a/frontend/app/hook/useWidth.tsx +++ b/frontend/app/hook/useWidth.tsx @@ -1,46 +1,26 @@ // Copyright 2024, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 -import debounce from "lodash.debounce"; -import { useCallback, useEffect, useState } from "react"; +import useResizeObserver from "@react-hook/resize-observer"; +import { useCallback, useState } from "react"; +import { debounce } from "throttle-debounce"; +/** + * Get the width of the specified element and update it when the element resizes. + * @param ref The reference to the element to observe. + * @param delay The debounce delay to use for updating the width. + * @returns The current width of the element, or null if the element is not yet mounted. + */ const useWidth = (ref: React.RefObject, delay = 0) => { const [width, setWidth] = useState(null); - const updateWidth = useCallback(() => { - if (ref.current) { - const element = ref.current; - const style = window.getComputedStyle(element); - const paddingLeft = parseFloat(style.paddingLeft); - const paddingRight = parseFloat(style.paddingRight); - const marginLeft = parseFloat(style.marginLeft); - const marginRight = parseFloat(style.marginRight); - const parentWidth = element.clientWidth - paddingLeft - paddingRight - marginLeft - marginRight; - setWidth(parentWidth); - } + const updateWidth = useCallback((entry: ResizeObserverEntry) => { + setWidth(entry.contentRect.width); }, []); - const fUpdateWidth = useCallback(delay > 0 ? debounce(updateWidth, delay) : updateWidth, [updateWidth, delay]); + const fUpdateWidth = useCallback(delay > 0 ? debounce(delay, updateWidth) : updateWidth, [updateWidth, delay]); - useEffect(() => { - const resizeObserver = new ResizeObserver(() => { - fUpdateWidth(); - }); - - if (ref.current) { - resizeObserver.observe(ref.current); - fUpdateWidth(); - } - - return () => { - if (ref.current) { - resizeObserver.unobserve(ref.current); - } - if (delay > 0) { - fUpdateWidth.cancel(); - } - }; - }, [fUpdateWidth]); + useResizeObserver(ref, fUpdateWidth); return width; }; diff --git a/frontend/app/view/codeeditor/codeeditor.tsx b/frontend/app/view/codeeditor/codeeditor.tsx index 966b12c25..b6a7e7380 100644 --- a/frontend/app/view/codeeditor/codeeditor.tsx +++ b/frontend/app/view/codeeditor/codeeditor.tsx @@ -2,11 +2,11 @@ // SPDX-License-Identifier: Apache-2.0 import { useHeight } from "@/app/hook/useHeight"; +import { useWidth } from "@/app/hook/useWidth"; import loader from "@monaco-editor/loader"; import { Editor, Monaco } from "@monaco-editor/react"; import type * as MonacoTypes from "monaco-editor/esm/vs/editor/editor.api"; import React, { useRef } from "react"; - import "./codeeditor.less"; // there is a global monaco variable (TODO get the correct TS type) @@ -18,21 +18,29 @@ export function loadMonaco() { .init() .then(() => { monaco.editor.defineTheme("wave-theme-dark", { - base: "hc-black", + base: "vs-dark", inherit: true, rules: [], colors: { - "editor.background": "#000000", + "editor.background": "#00000000", + "minimap.background": "#00000077", + focusBorder: "#00000000", }, }); monaco.editor.defineTheme("wave-theme-light", { - base: "hc-light", + base: "vs", inherit: true, rules: [], colors: { "editor.background": "#fefefe", + focusBorder: "#00000000", }, }); + + // Disable default validation errors for typescript and javascript + monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions({ + noSemanticValidation: true, + }); }) .catch((e) => { console.error("error loading monaco", e); @@ -50,6 +58,9 @@ function defaultEditorOptions(): MonacoTypes.editor.IEditorOptions { verticalScrollbarSize: 5, horizontalScrollbarSize: 5, }, + minimap: { + enabled: true, + }, }; return opts; } @@ -67,6 +78,7 @@ export function CodeEditor({ parentRef, text, language, filename, onChange, onMo const divRef = useRef(null); const unmountRef = useRef<() => void>(null); const parentHeight = useHeight(parentRef); + const parentWidth = useWidth(parentRef); const theme = "wave-theme-dark"; React.useEffect(() => { @@ -97,9 +109,10 @@ export function CodeEditor({ parentRef, text, language, filename, onChange, onMo