mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-21 16:38:23 +01:00
Replace lots of separate ResizeObservers with a single one via useResizeObserver (#24)
This commit is contained in:
parent
ba7d2cf061
commit
2866862253
@ -8,8 +8,9 @@ import { FitAddon } from "@xterm/addon-fit";
|
|||||||
import type { ITheme } from "@xterm/xterm";
|
import type { ITheme } from "@xterm/xterm";
|
||||||
import { Terminal } from "@xterm/xterm";
|
import { Terminal } from "@xterm/xterm";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { debounce } from "throttle-debounce";
|
|
||||||
|
|
||||||
|
import useResizeObserver from "@react-hook/resize-observer";
|
||||||
|
import { debounce } from "throttle-debounce";
|
||||||
import "./view.less";
|
import "./view.less";
|
||||||
import "/public/xterm.css";
|
import "/public/xterm.css";
|
||||||
|
|
||||||
@ -61,91 +62,94 @@ const TerminalView = ({ blockId }: { blockId: string }) => {
|
|||||||
const termRef = React.useRef<Terminal>(null);
|
const termRef = React.useRef<Terminal>(null);
|
||||||
const initialLoadRef = React.useRef<InitialLoadDataType>({ loaded: false, heldData: [] });
|
const initialLoadRef = React.useRef<InitialLoadDataType>({ loaded: false, heldData: [] });
|
||||||
|
|
||||||
React.useEffect(() => {
|
const [fitAddon, setFitAddon] = React.useState<FitAddon>(null);
|
||||||
if (!connectElemRef.current) {
|
const [term, setTerm] = React.useState<Terminal>(null);
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.log("terminal created");
|
|
||||||
const term = new Terminal({
|
|
||||||
theme: getThemeFromCSSVars(connectElemRef.current),
|
|
||||||
fontSize: 12,
|
|
||||||
fontFamily: "Hack",
|
|
||||||
drawBoldTextInBrightColors: false,
|
|
||||||
fontWeight: "normal",
|
|
||||||
fontWeightBold: "bold",
|
|
||||||
});
|
|
||||||
termRef.current = term;
|
|
||||||
const fitAddon = new FitAddon();
|
|
||||||
term.loadAddon(fitAddon);
|
|
||||||
term.open(connectElemRef.current);
|
|
||||||
fitAddon.fit();
|
|
||||||
BlockService.SendCommand(blockId, {
|
|
||||||
command: "controller:input",
|
|
||||||
termsize: { rows: term.rows, cols: term.cols },
|
|
||||||
});
|
|
||||||
term.onData((data) => {
|
|
||||||
const b64data = btoa(data);
|
|
||||||
const inputCmd = { command: "controller:input", blockid: blockId, inputdata64: b64data };
|
|
||||||
BlockService.SendCommand(blockId, inputCmd);
|
|
||||||
});
|
|
||||||
// resize observer
|
|
||||||
const handleResize_debounced = debounce(50, handleResize);
|
|
||||||
const rszObs = new ResizeObserver(() => {
|
|
||||||
handleResize_debounced(fitAddon, blockId, term);
|
|
||||||
});
|
|
||||||
rszObs.observe(connectElemRef.current);
|
|
||||||
|
|
||||||
// block subject
|
|
||||||
const blockSubject = getBlockSubject(blockId);
|
|
||||||
blockSubject.subscribe((data) => {
|
|
||||||
// base64 decode
|
|
||||||
const decodedData = base64ToArray(data.ptydata);
|
|
||||||
if (initialLoadRef.current.loaded) {
|
|
||||||
term.write(decodedData);
|
|
||||||
} else {
|
|
||||||
initialLoadRef.current.heldData.push(decodedData);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
term.dispose();
|
|
||||||
rszObs.disconnect();
|
|
||||||
blockSubject.release();
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!termRef.current) {
|
if (connectElemRef.current && !term) {
|
||||||
return;
|
console.log("terminal created");
|
||||||
}
|
const newTerm = new Terminal({
|
||||||
// load data from filestore
|
theme: getThemeFromCSSVars(connectElemRef.current),
|
||||||
const startTs = Date.now();
|
fontSize: 12,
|
||||||
let loadedBytes = 0;
|
fontFamily: "Hack",
|
||||||
const localTerm = termRef.current; // avoids devmode double effect running issue (terminal gets created twice)
|
drawBoldTextInBrightColors: false,
|
||||||
const usp = new URLSearchParams();
|
fontWeight: "normal",
|
||||||
usp.set("zoneid", blockId);
|
fontWeightBold: "bold",
|
||||||
usp.set("name", "main");
|
|
||||||
fetch("/wave/file?" + usp.toString())
|
|
||||||
.then((resp) => {
|
|
||||||
if (resp.ok) {
|
|
||||||
return resp.arrayBuffer();
|
|
||||||
}
|
|
||||||
console.log("error loading file", resp.status, resp.statusText);
|
|
||||||
})
|
|
||||||
.then((data: ArrayBuffer) => {
|
|
||||||
const uint8View = new Uint8Array(data);
|
|
||||||
localTerm.write(uint8View);
|
|
||||||
loadedBytes = uint8View.byteLength;
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
initialLoadRef.current.heldData.forEach((data) => {
|
|
||||||
localTerm.write(data);
|
|
||||||
});
|
|
||||||
initialLoadRef.current.loaded = true;
|
|
||||||
initialLoadRef.current.heldData = [];
|
|
||||||
console.log(`terminal loaded file ${loadedBytes} bytes, ${Date.now() - startTs}ms`);
|
|
||||||
});
|
});
|
||||||
}, []);
|
termRef.current = newTerm;
|
||||||
|
const newFitAddon = new FitAddon();
|
||||||
|
newTerm.loadAddon(newFitAddon);
|
||||||
|
newTerm.open(connectElemRef.current);
|
||||||
|
newFitAddon.fit();
|
||||||
|
BlockService.SendCommand(blockId, {
|
||||||
|
command: "controller:input",
|
||||||
|
termsize: { rows: newTerm.rows, cols: newTerm.cols },
|
||||||
|
});
|
||||||
|
newTerm.onData((data) => {
|
||||||
|
const b64data = btoa(data);
|
||||||
|
const inputCmd = { command: "controller:input", blockid: blockId, inputdata64: b64data };
|
||||||
|
BlockService.SendCommand(blockId, inputCmd);
|
||||||
|
});
|
||||||
|
|
||||||
|
// block subject
|
||||||
|
const blockSubject = getBlockSubject(blockId);
|
||||||
|
blockSubject.subscribe((data) => {
|
||||||
|
// base64 decode
|
||||||
|
const decodedData = base64ToArray(data.ptydata);
|
||||||
|
if (initialLoadRef.current.loaded) {
|
||||||
|
newTerm.write(decodedData);
|
||||||
|
} else {
|
||||||
|
initialLoadRef.current.heldData.push(decodedData);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setTerm(newTerm);
|
||||||
|
setFitAddon(newFitAddon);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
newTerm.dispose();
|
||||||
|
blockSubject.release();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [connectElemRef]);
|
||||||
|
|
||||||
|
const handleResizeCallback = React.useCallback(() => {
|
||||||
|
debounce(50, () => handleResize(fitAddon, blockId, term));
|
||||||
|
}, [fitAddon, term]);
|
||||||
|
|
||||||
|
useResizeObserver(connectElemRef, handleResizeCallback);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (termRef.current) {
|
||||||
|
// load data from filestore
|
||||||
|
const startTs = Date.now();
|
||||||
|
let loadedBytes = 0;
|
||||||
|
const localTerm = termRef.current; // avoids devmode double effect running issue (terminal gets created twice)
|
||||||
|
const usp = new URLSearchParams();
|
||||||
|
usp.set("zoneid", blockId);
|
||||||
|
usp.set("name", "main");
|
||||||
|
fetch("/wave/file?" + usp.toString())
|
||||||
|
.then((resp) => {
|
||||||
|
if (resp.ok) {
|
||||||
|
return resp.arrayBuffer();
|
||||||
|
}
|
||||||
|
console.log("error loading file", resp.status, resp.statusText);
|
||||||
|
})
|
||||||
|
.then((data: ArrayBuffer) => {
|
||||||
|
const uint8View = new Uint8Array(data);
|
||||||
|
localTerm.write(uint8View);
|
||||||
|
loadedBytes = uint8View.byteLength;
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
initialLoadRef.current.heldData.forEach((data) => {
|
||||||
|
localTerm.write(data);
|
||||||
|
});
|
||||||
|
initialLoadRef.current.loaded = true;
|
||||||
|
initialLoadRef.current.heldData = [];
|
||||||
|
console.log(`terminal loaded file ${loadedBytes} bytes, ${Date.now() - startTs}ms`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [termRef]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="view-term">
|
<div className="view-term">
|
||||||
|
@ -5,6 +5,7 @@ import clsx from "clsx";
|
|||||||
import { CSSProperties, RefObject, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
import { CSSProperties, RefObject, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
||||||
import { useDrag, useDragLayer, useDrop } from "react-dnd";
|
import { useDrag, useDragLayer, useDrop } from "react-dnd";
|
||||||
|
|
||||||
|
import useResizeObserver from "@react-hook/resize-observer";
|
||||||
import { useLayoutTreeStateReducerAtom } from "./layoutAtom.js";
|
import { useLayoutTreeStateReducerAtom } from "./layoutAtom.js";
|
||||||
import {
|
import {
|
||||||
ContentRenderer,
|
ContentRenderer,
|
||||||
@ -126,24 +127,9 @@ export const TileLayout = <T,>({ layoutTreeStateAtom, className, renderContent,
|
|||||||
// Update the transforms whenever we drag something and whenever the layout updates.
|
// Update the transforms whenever we drag something and whenever the layout updates.
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
updateTransforms();
|
updateTransforms();
|
||||||
}, [activeDrag, layoutTreeState]);
|
}, [activeDrag, layoutTreeState, updateTransforms]);
|
||||||
|
|
||||||
// Update the transforms on first render and again whenever the window resizes. I had to do a slightly hacky thing
|
useResizeObserver(overlayContainerRef, () => updateTransforms());
|
||||||
// because I noticed that the window handler wasn't updating when the callback changed so I remove it each time and
|
|
||||||
// reattach the new callback.
|
|
||||||
const [resizeObserver, setResizeObserver] = useState<ResizeObserver>(undefined);
|
|
||||||
useEffect(() => {
|
|
||||||
if (overlayContainerRef.current) {
|
|
||||||
console.log("replace resize listener");
|
|
||||||
if (resizeObserver) resizeObserver.disconnect();
|
|
||||||
const newResizeObserver = new ResizeObserver(updateTransforms);
|
|
||||||
newResizeObserver.observe(overlayContainerRef.current);
|
|
||||||
setResizeObserver(newResizeObserver);
|
|
||||||
return () => {
|
|
||||||
newResizeObserver.disconnect();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}, [updateTransforms, overlayContainerRef]);
|
|
||||||
|
|
||||||
// Ensure that we don't see any jostling in the layout when we're rendering it the first time.
|
// Ensure that we don't see any jostling in the layout when we're rendering it the first time.
|
||||||
// `animate` will be disabled until after the transforms have all applied the first time.
|
// `animate` will be disabled until after the transforms have all applied the first time.
|
||||||
|
@ -51,6 +51,7 @@
|
|||||||
"@monaco-editor/loader": "^1.4.0",
|
"@monaco-editor/loader": "^1.4.0",
|
||||||
"@monaco-editor/react": "^4.6.0",
|
"@monaco-editor/react": "^4.6.0",
|
||||||
"@observablehq/plot": "^0.6.14",
|
"@observablehq/plot": "^0.6.14",
|
||||||
|
"@react-hook/resize-observer": "^2.0.1",
|
||||||
"@tanstack/react-table": "^8.17.3",
|
"@tanstack/react-table": "^8.17.3",
|
||||||
"@xterm/addon-fit": "^0.10.0",
|
"@xterm/addon-fit": "^0.10.0",
|
||||||
"@xterm/xterm": "^5.5.0",
|
"@xterm/xterm": "^5.5.0",
|
||||||
|
39
yarn.lock
39
yarn.lock
@ -1901,6 +1901,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@juggle/resize-observer@npm:^3.3.1":
|
||||||
|
version: 3.4.0
|
||||||
|
resolution: "@juggle/resize-observer@npm:3.4.0"
|
||||||
|
checksum: 10c0/12930242357298c6f2ad5d4ec7cf631dfb344ca7c8c830ab7f64e6ac11eb1aae486901d8d880fd08fb1b257800c160a0da3aee1e7ed9adac0ccbb9b7c5d93347
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@mdx-js/react@npm:^3.0.0":
|
"@mdx-js/react@npm:^3.0.0":
|
||||||
version: 3.0.1
|
version: 3.0.1
|
||||||
resolution: "@mdx-js/react@npm:3.0.1"
|
resolution: "@mdx-js/react@npm:3.0.1"
|
||||||
@ -2324,6 +2331,37 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@react-hook/latest@npm:^1.0.2":
|
||||||
|
version: 1.0.3
|
||||||
|
resolution: "@react-hook/latest@npm:1.0.3"
|
||||||
|
peerDependencies:
|
||||||
|
react: ">=16.8"
|
||||||
|
checksum: 10c0/d6a166c21121da519a516e8089ba28a2779d37b6017732ab55476c0d354754ad215394135765254f8752a7c6661c3fb868d088769a644848602f00f8821248ed
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@react-hook/passive-layout-effect@npm:^1.2.0":
|
||||||
|
version: 1.2.1
|
||||||
|
resolution: "@react-hook/passive-layout-effect@npm:1.2.1"
|
||||||
|
peerDependencies:
|
||||||
|
react: ">=16.8"
|
||||||
|
checksum: 10c0/5c9e6b3df1c91fc2b1d4f711ca96b5f8cb3f6a13a2e97dac7cce623e58d7ee57999c45db3778d0af0b2522b3a5b7463232ef21cb3ee9900437172d48f766d933
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@react-hook/resize-observer@npm:^2.0.1":
|
||||||
|
version: 2.0.1
|
||||||
|
resolution: "@react-hook/resize-observer@npm:2.0.1"
|
||||||
|
dependencies:
|
||||||
|
"@juggle/resize-observer": "npm:^3.3.1"
|
||||||
|
"@react-hook/latest": "npm:^1.0.2"
|
||||||
|
"@react-hook/passive-layout-effect": "npm:^1.2.0"
|
||||||
|
peerDependencies:
|
||||||
|
react: ">=18"
|
||||||
|
checksum: 10c0/f36b181b1faecbe3894a23e3ad9d1206afc64287d60fa55f3018679a03161a2ecefc857575ee2a2dc2008cc6d3ded760d45bf6b4bf2102d5da6460aa349317db
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@rollup/pluginutils@npm:^5.0.2":
|
"@rollup/pluginutils@npm:^5.0.2":
|
||||||
version: 5.1.0
|
version: 5.1.0
|
||||||
resolution: "@rollup/pluginutils@npm:5.1.0"
|
resolution: "@rollup/pluginutils@npm:5.1.0"
|
||||||
@ -11065,6 +11103,7 @@ __metadata:
|
|||||||
"@monaco-editor/loader": "npm:^1.4.0"
|
"@monaco-editor/loader": "npm:^1.4.0"
|
||||||
"@monaco-editor/react": "npm:^4.6.0"
|
"@monaco-editor/react": "npm:^4.6.0"
|
||||||
"@observablehq/plot": "npm:^0.6.14"
|
"@observablehq/plot": "npm:^0.6.14"
|
||||||
|
"@react-hook/resize-observer": "npm:^2.0.1"
|
||||||
"@storybook/addon-essentials": "npm:^8.1.4"
|
"@storybook/addon-essentials": "npm:^8.1.4"
|
||||||
"@storybook/addon-interactions": "npm:^8.1.4"
|
"@storybook/addon-interactions": "npm:^8.1.4"
|
||||||
"@storybook/addon-links": "npm:^8.1.4"
|
"@storybook/addon-links": "npm:^8.1.4"
|
||||||
|
Loading…
Reference in New Issue
Block a user