From f437d4c3941810c17dde0cae25737cd3e58efea9 Mon Sep 17 00:00:00 2001 From: George Stagg Date: Wed, 19 Jun 2024 15:41:06 +0100 Subject: [PATCH] Add resizable panels to webR REPL app --- src/package-lock.json | 24 ++++++++++----- src/package.json | 1 + src/repl/App.css | 50 +++++--------------------------- src/repl/App.tsx | 30 ++++++++++++++----- src/repl/components/Editor.css | 20 +++++++------ src/repl/components/Editor.tsx | 16 +++++----- src/repl/components/Files.css | 6 ++-- src/repl/components/Files.tsx | 5 ++-- src/repl/components/Plot.css | 6 +--- src/repl/components/Plot.tsx | 5 ++-- src/repl/components/Terminal.css | 9 ++++++ src/repl/components/Terminal.tsx | 13 +++++---- 12 files changed, 94 insertions(+), 91 deletions(-) create mode 100644 src/repl/components/Terminal.css diff --git a/src/package-lock.json b/src/package-lock.json index cb1a0d7b..7fe68f37 100644 --- a/src/package-lock.json +++ b/src/package-lock.json @@ -23,6 +23,7 @@ "react-accessible-treeview": "^2.6.1", "react-dom": "^18.2.0", "react-icons": "^4.10.1", + "react-resizable-panels": "^2.0.19", "tsx": "^4.0.0", "xmlhttprequest-ssl": "^2.1.0", "xterm": "^5.1.0", @@ -2524,12 +2525,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -3739,9 +3740,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -6635,6 +6636,15 @@ "integrity": "sha512-wRiUsea88TjKDc4FBEn+sLvIDesp6brMbGWnJGjew2waAc9evdhja/2LvePc898HJbHw0L+MTWy7NhpnELAvLQ==", "dev": true }, + "node_modules/react-resizable-panels": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-2.0.19.tgz", + "integrity": "sha512-v3E41kfKSuCPIvJVb4nL4mIZjjKIn/gh6YqZF/gDfQDolv/8XnhJBek4EiV2gOr3hhc5A3kOGOayk3DhanpaQw==", + "peerDependencies": { + "react": "^16.14.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz", diff --git a/src/package.json b/src/package.json index c46c66d8..319f66a0 100644 --- a/src/package.json +++ b/src/package.json @@ -69,6 +69,7 @@ "react-accessible-treeview": "^2.6.1", "react-dom": "^18.2.0", "react-icons": "^4.10.1", + "react-resizable-panels": "^2.0.19", "tsx": "^4.0.0", "xmlhttprequest-ssl": "^2.1.0", "xterm": "^5.1.0", diff --git a/src/repl/App.css b/src/repl/App.css index 3c0a33e6..e09c03d2 100644 --- a/src/repl/App.css +++ b/src/repl/App.css @@ -14,54 +14,20 @@ body { } .repl { - display: grid; - grid-template-columns: repeat(2, 1fr); - grid-template-rows: repeat(6, 1fr); width: 100vw; height: 100vh; } -.editor { - grid-row: 1 / 5; - grid-column: 1; - overflow: auto; - border-style: solid; - border-color: var(--bg-secondary); - border-width: 0px 2px 2px 0px; - padding-top: 5px; +div[data-panel] { + display: flex; + flex-direction: column; } -.term { - grid-row-start: 5; - grid-row-end: -1; - grid-column: 1; - border-style: solid; - background-color: var(--bg-primary); - border-color: var(--bg-secondary); - border-width: 2px 2px 0px 0px; - overflow: hidden; - padding: 0px 5px; +div[data-resize-handle] { + padding: 2px; + background-color: var(--bg-secondary); } -.editor[style*='display: none']~.term { - grid-row-start: 1; -} - -.files { - grid-row: 1 / 3; - grid-column: 2; - border-style: solid; - border-color: var(--bg-secondary); - border-width: 0px 0px 2px 2px; - overflow: auto; - padding: 5px 0; -} - -.plot { - grid-row: 3 / -1; - grid-column: 2; - border-style: solid; - border-color: var(--bg-secondary); - border-width: 2px 0px 0px 2px; - overflow: hidden; +.d-none + div[data-resize-handle] { + display: none !important; } diff --git a/src/repl/App.tsx b/src/repl/App.tsx index 233053df..1e169627 100644 --- a/src/repl/App.tsx +++ b/src/repl/App.tsx @@ -5,6 +5,7 @@ import Editor from './components/Editor'; import Plot from './components/Plot'; import Files from './components/Files'; import { Readline } from 'xterm-readline'; +import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels'; import { WebR } from '../webR/webr-main'; import { CanvasMessage, PagerMessage } from '../webR/webr-chan'; import './App.css'; @@ -73,14 +74,27 @@ async function handlePagerMessage(msg: PagerMessage) { function App() { return (
- - - - + + + + + + + + + + + + + + + + +
); } diff --git a/src/repl/components/Editor.css b/src/repl/components/Editor.css index ca454190..688e8c2f 100644 --- a/src/repl/components/Editor.css +++ b/src/repl/components/Editor.css @@ -1,7 +1,5 @@ -.editor { +#editor { position: relative; - display: grid; - grid-template-rows: auto 1fr; } .editor-header { @@ -9,7 +7,12 @@ justify-content: space-between; align-items: center; border-bottom: 1px solid var(--bg-dark); - padding: 0 5px; + padding: 5px 5px 0 5px; +} + +.editor-container { + flex: 1; + overflow: auto; } .editor-actions { @@ -94,11 +97,6 @@ color: var(--primary); } -.editor-container { - min-width: 0; - min-height: 0; -} - .cm-editor { border: none; outline: none; @@ -110,3 +108,7 @@ border: none; outline: none; } + +.d-none { + display: none !important; +} diff --git a/src/repl/components/Editor.tsx b/src/repl/components/Editor.tsx index 75f80c6d..5ff0e915 100644 --- a/src/repl/components/Editor.tsx +++ b/src/repl/components/Editor.tsx @@ -6,6 +6,7 @@ import { EditorState, Compartment, Prec } from '@codemirror/state'; import { autocompletion, CompletionContext } from '@codemirror/autocomplete'; import { keymap } from '@codemirror/view'; import { indentWithTab } from '@codemirror/commands'; +import { Panel } from 'react-resizable-panels'; import { FilesInterface, TerminalInterface } from '../App'; import { r } from 'codemirror-lang-r'; import './Editor.css'; @@ -341,13 +342,14 @@ export function Editor({ }; }, [files, syncActiveFileState, activeFile, editorView]); - const displayStyle = files.length === 0 ? { display: 'none' } : undefined; - return ( -
-

+

This component is an instance of the CodeMirror interactive text editor. The editor has been configured so that the Tab key controls the indentation of code. To move focus away from the editor, press the Escape key, and then press the Tab key directly after it. @@ -382,7 +384,7 @@ export function Editor({

- + ); } diff --git a/src/repl/components/Files.css b/src/repl/components/Files.css index 98189311..36fe2ed6 100644 --- a/src/repl/components/Files.css +++ b/src/repl/components/Files.css @@ -1,6 +1,5 @@ -.files { - display: grid; - grid-template-rows: auto 1fr; +#files { + padding: 5px 0; } .files-header { @@ -34,6 +33,7 @@ user-select: none; padding: 0 10px; overflow: auto; + flex-grow: 1; color: var(--primary); } diff --git a/src/repl/components/Files.tsx b/src/repl/components/Files.tsx index 8dbae1cd..5fdb30b2 100644 --- a/src/repl/components/Files.tsx +++ b/src/repl/components/Files.tsx @@ -1,6 +1,7 @@ import React, { ChangeEventHandler } from 'react'; import * as Fa from 'react-icons/fa'; import TreeView, { flattenTree, INode, ITreeViewProps } from 'react-accessible-treeview'; +import { Panel } from 'react-resizable-panels'; import { WebR, WebRError } from '../../webR/webr-main'; import type { FSNode } from '../../webR/webr-main'; import { FilesInterface } from '../App'; @@ -235,7 +236,7 @@ export function Files({ />; return ( -
+
{treeData[0].name ? treeView : undefined}
-
+
); } diff --git a/src/repl/components/Plot.css b/src/repl/components/Plot.css index d7f3958b..eb191b8d 100644 --- a/src/repl/components/Plot.css +++ b/src/repl/components/Plot.css @@ -1,10 +1,6 @@ -.plot { - display: grid; - grid-template-rows: auto 1fr; -} - .plot-background { background-color: var(--bg-dark); + flex-grow: 1; display: flex; justify-content: center; align-items: center; diff --git a/src/repl/components/Plot.tsx b/src/repl/components/Plot.tsx index 208efeb5..b4ac3dfb 100644 --- a/src/repl/components/Plot.tsx +++ b/src/repl/components/Plot.tsx @@ -2,6 +2,7 @@ import React from 'react'; import './Plot.css'; import { PlotInterface } from '../App'; import { FaArrowCircleLeft, FaArrowCircleRight, FaRegSave, FaTrashAlt } from 'react-icons/fa'; +import { Panel } from 'react-resizable-panels'; export function Plot({ plotInterface, @@ -67,7 +68,7 @@ export function Plot({ const prevPlot = () => setSelectedCanvas((selectedCanvas === null) ? null : selectedCanvas - 1); return ( -
+
-
+
); } diff --git a/src/repl/components/Terminal.css b/src/repl/components/Terminal.css new file mode 100644 index 00000000..42a4824c --- /dev/null +++ b/src/repl/components/Terminal.css @@ -0,0 +1,9 @@ +#terminal { + background-color: var(--bg-primary); + padding: 0px 5px; +} + +.terminal-container { + flex-grow: 1; + overflow: auto; +} diff --git a/src/repl/components/Terminal.tsx b/src/repl/components/Terminal.tsx index 18f4dac0..57ea56a6 100644 --- a/src/repl/components/Terminal.tsx +++ b/src/repl/components/Terminal.tsx @@ -1,7 +1,9 @@ import React from 'react'; +import './Terminal.css'; import { Terminal as XTerminal } from 'xterm'; import { Readline } from 'xterm-readline'; import { FitAddon } from 'xterm-addon-fit'; +import { Panel } from 'react-resizable-panels'; import { TerminalInterface } from '../App'; import { WebR } from '../../webR/webr-main'; import 'xterm/css/xterm.css'; @@ -99,12 +101,11 @@ export function Terminal({ }; }, [readline, terminalInterface]); - return
; + return ( + +
+
+ ); } export default Terminal;