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
This commit is contained in:
Evan Simkowitz 2024-09-03 18:43:59 -07:00 committed by GitHub
parent eeceb17c75
commit 6413d49119
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 79 additions and 130 deletions

View File

@ -1,6 +1,13 @@
import debounce from "lodash.debounce"; import useResizeObserver from "@react-hook/resize-observer";
import { useCallback, useEffect, useRef, useState } from "react"; 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<HTMLElement>, delay = 0) => { const useDimensions = (ref: React.RefObject<HTMLElement>, delay = 0) => {
const [dimensions, setDimensions] = useState<{ const [dimensions, setDimensions] = useState<{
height: number | null; height: number | null;
@ -17,73 +24,42 @@ const useDimensions = (ref: React.RefObject<HTMLElement>, delay = 0) => {
width: null, width: null,
}); });
const updateDimensions = useCallback(() => { const updateDimensions = useCallback((entry: ResizeObserverEntry) => {
if (ref.current) { const parentHeight = entry.contentRect.height;
const element = ref.current; const parentWidth = entry.contentRect.width;
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 parentHeight = element.clientHeight - paddingTop - paddingBottom - marginTop - marginBottom; let widthDirection = "";
const parentWidth = element.clientWidth - paddingLeft - paddingRight - marginLeft - marginRight; let heightDirection = "";
let widthDirection = ""; if (previousDimensions.current.width !== null && previousDimensions.current.height !== null) {
let heightDirection = ""; if (parentWidth > previousDimensions.current.width) {
widthDirection = "expanding";
if (previousDimensions.current.width !== null && previousDimensions.current.height !== null) { } else if (parentWidth < previousDimensions.current.width) {
if (parentWidth > previousDimensions.current.width) { widthDirection = "shrinking";
widthDirection = "expanding"; } else {
} else if (parentWidth < previousDimensions.current.width) { widthDirection = "unchanged";
widthDirection = "shrinking";
} else {
widthDirection = "unchanged";
}
if (parentHeight > previousDimensions.current.height) {
heightDirection = "expanding";
} else if (parentHeight < previousDimensions.current.height) {
heightDirection = "shrinking";
} else {
heightDirection = "unchanged";
}
} }
previousDimensions.current = { height: parentHeight, width: parentWidth }; if (parentHeight > previousDimensions.current.height) {
heightDirection = "expanding";
setDimensions({ height: parentHeight, width: parentWidth, widthDirection, heightDirection }); } 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, updateDimensions,
delay, delay,
]); ]);
useEffect(() => { useResizeObserver(ref, fUpdateDimensions);
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]);
return dimensions; return dimensions;
}; };

View File

@ -1,46 +1,26 @@
// Copyright 2024, Command Line Inc. // Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
import debounce from "lodash.debounce"; import useResizeObserver from "@react-hook/resize-observer";
import { useCallback, useEffect, useState } from "react"; 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<HTMLElement>, delay = 0) => { const useHeight = (ref: React.RefObject<HTMLElement>, delay = 0) => {
const [height, setHeight] = useState<number | null>(null); const [height, setHeight] = useState<number | null>(null);
const updateHeight = useCallback(() => { const updateHeight = useCallback((entry: ResizeObserverEntry) => {
if (ref.current) { setHeight(entry.contentRect.height);
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 fUpdateHeight = useCallback(delay > 0 ? debounce(updateHeight, delay) : updateHeight, [updateHeight, delay]); const fUpdateHeight = useCallback(delay > 0 ? debounce(delay, updateHeight) : updateHeight, [updateHeight, delay]);
useEffect(() => { useResizeObserver(ref, fUpdateHeight);
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]);
return height; return height;
}; };

View File

@ -1,46 +1,26 @@
// Copyright 2024, Command Line Inc. // Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
import debounce from "lodash.debounce"; import useResizeObserver from "@react-hook/resize-observer";
import { useCallback, useEffect, useState } from "react"; 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<HTMLElement>, delay = 0) => { const useWidth = (ref: React.RefObject<HTMLElement>, delay = 0) => {
const [width, setWidth] = useState<number | null>(null); const [width, setWidth] = useState<number | null>(null);
const updateWidth = useCallback(() => { const updateWidth = useCallback((entry: ResizeObserverEntry) => {
if (ref.current) { setWidth(entry.contentRect.width);
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 fUpdateWidth = useCallback(delay > 0 ? debounce(updateWidth, delay) : updateWidth, [updateWidth, delay]); const fUpdateWidth = useCallback(delay > 0 ? debounce(delay, updateWidth) : updateWidth, [updateWidth, delay]);
useEffect(() => { useResizeObserver(ref, fUpdateWidth);
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]);
return width; return width;
}; };

View File

@ -2,11 +2,11 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
import { useHeight } from "@/app/hook/useHeight"; import { useHeight } from "@/app/hook/useHeight";
import { useWidth } from "@/app/hook/useWidth";
import loader from "@monaco-editor/loader"; import loader from "@monaco-editor/loader";
import { Editor, Monaco } from "@monaco-editor/react"; import { Editor, Monaco } from "@monaco-editor/react";
import type * as MonacoTypes from "monaco-editor/esm/vs/editor/editor.api"; import type * as MonacoTypes from "monaco-editor/esm/vs/editor/editor.api";
import React, { useRef } from "react"; import React, { useRef } from "react";
import "./codeeditor.less"; import "./codeeditor.less";
// there is a global monaco variable (TODO get the correct TS type) // there is a global monaco variable (TODO get the correct TS type)
@ -18,21 +18,29 @@ export function loadMonaco() {
.init() .init()
.then(() => { .then(() => {
monaco.editor.defineTheme("wave-theme-dark", { monaco.editor.defineTheme("wave-theme-dark", {
base: "hc-black", base: "vs-dark",
inherit: true, inherit: true,
rules: [], rules: [],
colors: { colors: {
"editor.background": "#000000", "editor.background": "#00000000",
"minimap.background": "#00000077",
focusBorder: "#00000000",
}, },
}); });
monaco.editor.defineTheme("wave-theme-light", { monaco.editor.defineTheme("wave-theme-light", {
base: "hc-light", base: "vs",
inherit: true, inherit: true,
rules: [], rules: [],
colors: { colors: {
"editor.background": "#fefefe", "editor.background": "#fefefe",
focusBorder: "#00000000",
}, },
}); });
// Disable default validation errors for typescript and javascript
monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions({
noSemanticValidation: true,
});
}) })
.catch((e) => { .catch((e) => {
console.error("error loading monaco", e); console.error("error loading monaco", e);
@ -50,6 +58,9 @@ function defaultEditorOptions(): MonacoTypes.editor.IEditorOptions {
verticalScrollbarSize: 5, verticalScrollbarSize: 5,
horizontalScrollbarSize: 5, horizontalScrollbarSize: 5,
}, },
minimap: {
enabled: true,
},
}; };
return opts; return opts;
} }
@ -67,6 +78,7 @@ export function CodeEditor({ parentRef, text, language, filename, onChange, onMo
const divRef = useRef<HTMLDivElement>(null); const divRef = useRef<HTMLDivElement>(null);
const unmountRef = useRef<() => void>(null); const unmountRef = useRef<() => void>(null);
const parentHeight = useHeight(parentRef); const parentHeight = useHeight(parentRef);
const parentWidth = useWidth(parentRef);
const theme = "wave-theme-dark"; const theme = "wave-theme-dark";
React.useEffect(() => { React.useEffect(() => {
@ -97,9 +109,10 @@ export function CodeEditor({ parentRef, text, language, filename, onChange, onMo
<div className="code-editor" ref={divRef}> <div className="code-editor" ref={divRef}>
<Editor <Editor
theme={theme} theme={theme}
height={parentHeight}
value={text} value={text}
options={editorOpts} options={editorOpts}
height={parentHeight}
width={parentWidth}
onChange={handleEditorChange} onChange={handleEditorChange}
onMount={handleEditorOnMount} onMount={handleEditorOnMount}
path={filename} path={filename}