diff --git a/gui/src/app/FileEditor/StanFileEditor.tsx b/gui/src/app/FileEditor/StanFileEditor.tsx index 1ed6df8a..47e1a081 100644 --- a/gui/src/app/FileEditor/StanFileEditor.tsx +++ b/gui/src/app/FileEditor/StanFileEditor.tsx @@ -1,8 +1,8 @@ import { AutoFixHigh, Cancel, Settings } from "@mui/icons-material"; -import useMediaQuery from "@mui/material/useMediaQuery"; import { SplitDirection, Splitter } from "@SpComponents/Splitter"; import StanCompileResultWindow from "@SpComponents/StanCompileResultWindow"; -import TextEditor, { ToolbarItem } from "@SpComponents/TextEditor"; +import TextEditor from "@SpComponents/TextEditor"; +import { ToolbarItem } from "@SpComponents/ToolBar"; import compileStanProgram from "@SpStanc/compileStanProgram"; import { stancErrorsToCodeMarkers } from "@SpStanc/Linting"; import useStanc from "@SpStanc/useStanc"; @@ -123,7 +123,6 @@ const StanFileEditor: FunctionComponent = ({ } }, [fileContent, handleCompile, didInitialCompile]); - const showLabelsOnButtons = useMediaQuery("(min-width:600px)"); const [syntaxWindowVisible, setSyntaxWindowVisible] = useState(false); const toolbarItems: ToolbarItem[] = useMemo(() => { @@ -134,7 +133,7 @@ const StanFileEditor: FunctionComponent = ({ ret.push({ type: "button", icon: , - label: showLabelsOnButtons ? "Syntax error" : "", + label: "Syntax error", color: "darkred", tooltip: "Syntax error in Stan file", onClick: () => { @@ -145,7 +144,7 @@ const StanFileEditor: FunctionComponent = ({ ret.push({ type: "button", icon: , - label: showLabelsOnButtons ? "Syntax warning" : "", + label: "Syntax warning", color: "blue", tooltip: "Syntax warning in Stan file", onClick: () => { @@ -155,17 +154,15 @@ const StanFileEditor: FunctionComponent = ({ } // auto format - if (!readOnly) { - if (editedFileContent) { - ret.push({ - type: "button", - icon: , - tooltip: "Auto format this stan file", - label: showLabelsOnButtons ? "auto format" : undefined, - onClick: requestFormat, - color: "darkblue", - }); - } + if (!readOnly && editedFileContent && validSyntax) { + ret.push({ + type: "button", + icon: , + tooltip: "Auto format this stan file", + label: "Auto format", + onClick: requestFormat, + color: "darkblue", + }); } if (editedFileContent && editedFileContent === fileContent) { if (compileStatus !== "compiling") { @@ -173,7 +170,7 @@ const StanFileEditor: FunctionComponent = ({ ret.push({ type: "button", tooltip: "Compile Stan model", - label: "compile", + label: "Compile", icon: , onClick: handleCompile, color: "darkblue", @@ -183,7 +180,8 @@ const StanFileEditor: FunctionComponent = ({ if (compileStatus !== "") { ret.push({ type: "text", - label: compileMessage, + label: + compileMessage.charAt(0).toUpperCase() + compileMessage.slice(1), color: compileStatus === "compiled" ? "green" @@ -200,7 +198,6 @@ const StanFileEditor: FunctionComponent = ({ fileContent, handleCompile, requestFormat, - showLabelsOnButtons, validSyntax, compileStatus, compileMessage, diff --git a/gui/src/app/FileEditor/TextEditor.tsx b/gui/src/app/FileEditor/TextEditor.tsx index 0156df67..609b7c08 100644 --- a/gui/src/app/FileEditor/TextEditor.tsx +++ b/gui/src/app/FileEditor/TextEditor.tsx @@ -1,16 +1,15 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { SmallIconButton } from "@fi-sci/misc"; import { Editor, loader, useMonaco } from "@monaco-editor/react"; -import { Save } from "@mui/icons-material"; -import Link from "@mui/material/Link"; + import monacoAddStanLang from "@SpComponents/stanLang"; +import { ToolBar, ToolbarItem } from "@SpComponents/ToolBar"; import { CodeMarker } from "@SpStanc/Linting"; import { editor, MarkerSeverity } from "monaco-editor"; import { FunctionComponent, - PropsWithChildren, useCallback, useEffect, + useMemo, useState, } from "react"; @@ -30,21 +29,6 @@ type Props = { contentOnEmpty?: string | HTMLSpanElement; }; -export type ToolbarItem = - | { - type: "button"; - tooltip?: string; - label?: string; - icon?: any; - onClick?: () => void; - color?: string; - } - | { - type: "text"; - label: string; - color?: string; - }; - const TextEditor: FunctionComponent = ({ text, onSaveText, @@ -131,32 +115,19 @@ const TextEditor: FunctionComponent = ({ [onSaveText, readOnly], ); + const edited = useMemo(() => { + return editedText !== text; + }, [editedText, text]); + return (
- -
- {label} -     - {!readOnly && text !== editedText && ( - } - title="Save file" - disabled={text === editedText} - label="save" - /> - )} -     - {editedText !== text && edited} -     - {readOnly && read only} -     - {toolbarItems && - toolbarItems.map((item, i) => ( - - ))} -
-
+ = ({ - item, -}) => { - if (item.type === "button") { - const { onClick, color, label, tooltip, icon } = item; - if (icon) { - return ( - - -     - - ); - } else { - if (!onClick) { - return ( - - {label}    - - ); - } - return ( - - - {label} - -     - - ); - } - } else if (item.type === "text") { - return ( - - {item.label}    - - ); - } else { - return unknown toolbar item type; - } -}; - -const NotSelectable: FunctionComponent = ({ children }) => { - return
{children}
; -}; - const createHintTextContentWidget = (content: string | HTMLSpanElement) => { return { getDomNode: () => { diff --git a/gui/src/app/FileEditor/ToolBar.tsx b/gui/src/app/FileEditor/ToolBar.tsx new file mode 100644 index 00000000..51ced36b --- /dev/null +++ b/gui/src/app/FileEditor/ToolBar.tsx @@ -0,0 +1,125 @@ +import { FunctionComponent, useMemo } from "react"; +import { Save } from "@mui/icons-material"; +import Link from "@mui/material/Link"; +import IconButton from "@mui/material/IconButton"; + +export type ToolbarItem = + | { + type: "button"; + tooltip?: string; + label?: string; + icon?: any; + onClick: () => void; + color?: string; + } + | { + type: "text"; + label: string; + color?: string; + }; + +type ToolbarProps = { + items: ToolbarItem[]; + label: string; + onSaveText: () => void; + edited: boolean; + readOnly: boolean; +}; + +export const ToolBar: FunctionComponent = ({ + items, + label, + onSaveText, + edited, + readOnly, +}) => { + const toolBarItems = useMemo(() => { + const editorItems: ToolbarItem[] = []; + + if (readOnly) { + editorItems.push({ + type: "text", + label: "Read Only", + color: "gray", + }); + } else if (edited) { + editorItems.push({ + type: "button", + icon: , + onClick: onSaveText, + tooltip: "Save file", + label: "Save", + }); + editorItems.push({ + type: "text", + label: "Edited", + color: "red", + }); + } + + return editorItems.concat(items); + }, [edited, items, onSaveText, readOnly]); + + return ( +
+
+ {label} + {toolBarItems && + toolBarItems.map((item, i) => ( + + ))} +
+
+ ); +}; + +const ToolbarItemComponent: FunctionComponent<{ item: ToolbarItem }> = ({ + item, +}) => { + if (item.type === "button") { + const { onClick, color, label, tooltip, icon } = item; + if (icon) { + return ( + + + {icon} + {label && {label}} + + + ); + } else { + return ( + + + {label} + +     + + ); + } + } else if (item.type === "text") { + return ( + + {item.label}    + + ); + } else { + return unknown toolbar item type; + } +}; diff --git a/gui/src/app/Scripting/ScriptEditor.tsx b/gui/src/app/Scripting/ScriptEditor.tsx index 97ec153f..b96c4960 100644 --- a/gui/src/app/Scripting/ScriptEditor.tsx +++ b/gui/src/app/Scripting/ScriptEditor.tsx @@ -1,6 +1,7 @@ import { Help, PlayArrow } from "@mui/icons-material"; import { SplitDirection, Splitter } from "@SpComponents/Splitter"; -import TextEditor, { ToolbarItem } from "@SpComponents/TextEditor"; +import TextEditor from "@SpComponents/TextEditor"; +import { ToolbarItem } from "@SpComponents/ToolBar"; import { FileNames } from "@SpCore/FileMapping"; import { ProjectContext } from "@SpCore/ProjectContextProvider"; import { ProjectKnownFiles } from "@SpCore/ProjectDataModel"; diff --git a/gui/src/localStyles.css b/gui/src/localStyles.css index 1024392e..1654f6cd 100644 --- a/gui/src/localStyles.css +++ b/gui/src/localStyles.css @@ -84,28 +84,56 @@ /* Text Editors ------ */ +:root { + --editor-menu-height: 30px; +} + .EditorMenuBar { background-color: lightgray; - height: 25px; + height: var(--editor-menu-height); + position: relative; + display: flex; + align-content: center; + align-items: center; + container-name: EditorMenuBar; + container-type: inline-size; + white-space: nowrap; + overflow-x: auto; + overflow-y: hidden; } .EditorWithToolbar { - height: calc(100% - 25px); + height: calc(100% - var(--editor-menu-height)); } -span.EditorTitle { - position: relative; - top: 1px; +span.EditorToolbarItem { + padding-left: 15px; +} + +.ToolbarButtonText { + text-transform: none; + font-weight: normal; + letter-spacing: normal; + display: inline-block; + white-space: normal; padding-left: 5px; + font-size: 14px; } -.EditedText { - color: #a33; - font-size: 12px; +@container EditorMenuBar (max-width: 500px) { + .ToolbarButtonText { + display: none; + width: 0; + padding: 0; + } + span.EditorToolbarItem { + padding: 0px; + } } -.ReadOnlyText { - color: gray; +span.EditorTitle { + padding-left: 5px; + font-weight: 500; } .NotSelectable {