diff --git a/package-lock.json b/package-lock.json index 53f6ac8a6..1023376b8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2927,6 +2927,24 @@ "resolved": "https://registry.npmjs.org/@microbit/microbit-universal-hex/-/microbit-universal-hex-0.2.2.tgz", "integrity": "sha512-qyFt8ATgxAyPkNz9Yado4HXEeCctwP/8L1/v2hFLeVUqw/HFqVqV4piJbqRMmyOefMcQ9OyVPhLXjtbKn9063Q==" }, + "@monaco-editor/loader": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.0.1.tgz", + "integrity": "sha512-hycGOhLqLYjnD0A/FHs56covEQWnDFrSnm/qLKkB/yoeayQ7ju+Vaj4SdTojGrXeY6jhMDx59map0+Jqwquh1Q==", + "requires": { + "state-local": "^1.0.6" + } + }, + "@monaco-editor/react": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.1.0.tgz", + "integrity": "sha512-Hh895v/KfGgckDLXq8sdDGT4xS89+2hbQOP1l57sLd2XlJycChdzPiCj02nQDIduLmUIVHittjaj1/xmy94C3A==", + "requires": { + "@monaco-editor/loader": "^1.0.1", + "prop-types": "^15.7.2", + "state-local": "^1.0.7" + } + }, "@nodelib/fs.scandir": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", @@ -4039,6 +4057,11 @@ "negotiator": "0.6.2" } }, + "ace-builds": { + "version": "1.4.12", + "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.4.12.tgz", + "integrity": "sha512-G+chJctFPiiLGvs3+/Mly3apXTcfgE45dT5yp12BcWZ1kUs+gm0qd3/fv4gsz6fVag4mM0moHVpjHDIgph6Psg==" + }, "acorn": { "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", @@ -6580,6 +6603,11 @@ "integrity": "sha512-xd4D8kHQtB0KtWW0c9xBZD5LVtm9chkMOfs/3Yn01RhT/sFIsVtzTtypfKoFfWBaL+7xCYLxjOLkhwPXaX/Kcg==", "dev": true }, + "diff-match-patch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", + "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==" + }, "diff-sequences": { "version": "26.6.2", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", @@ -11538,6 +11566,16 @@ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" + }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" + }, "lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -11953,6 +11991,11 @@ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", "dev": true }, + "monaco-editor": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.23.0.tgz", + "integrity": "sha512-q+CP5zMR/aFiMTE9QlIavGyGicKnG2v/H8qVvybLzeFsARM8f6G9fL0sMST2tyVYCwDKkGamZUI6647A0jR/Lg==" + }, "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", @@ -14451,6 +14494,18 @@ "object-assign": "^4.1.1" } }, + "react-ace": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/react-ace/-/react-ace-9.4.0.tgz", + "integrity": "sha512-fpY3AGViE1OglXThgn3wZWcPoAxr0bqRYqeG3jY3m1L7OIHo0GfZ3bJI0grhrADDy2i9jQoip9xZfpOFupQCsw==", + "requires": { + "ace-builds": "^1.4.12", + "diff-match-patch": "^1.0.4", + "lodash.get": "^4.4.2", + "lodash.isequal": "^4.5.0", + "prop-types": "^15.7.2" + } + }, "react-app-polyfill": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/react-app-polyfill/-/react-app-polyfill-2.0.0.tgz", @@ -16329,6 +16384,11 @@ "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.2.0.tgz", "integrity": "sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA==" }, + "state-local": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz", + "integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==" + }, "static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", diff --git a/package.json b/package.json index 6cc769f4c..e77344041 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "@emotion/react": "^11.1.5", "@emotion/styled": "^11.1.5", "@microbit/microbit-fs": "^0.9.1", + "@monaco-editor/react": "^4.1.0", "@sentry/browser": "^6.2.3", "@testing-library/jest-dom": "^5.11.9", "@testing-library/react": "^11.2.5", @@ -30,10 +31,13 @@ "@types/node": "^14.14.35", "@types/react": "^17.0.3", "@types/react-dom": "^17.0.3", + "ace-builds": "^1.4.12", "dapjs": "2.2.0", "file-saver": "^2.0.5", "framer-motion": "^3.10.6", + "monaco-editor": "^0.23.0", "react": "^17.0.2", + "react-ace": "^9.4.0", "react-dom": "^17.0.2", "react-icons": "^4.2.0", "react-scripts": "4.0.3", diff --git a/src/App.css b/src/App.css index 6cd7e9394..ea401d3bb 100644 --- a/src/App.css +++ b/src/App.css @@ -9,6 +9,6 @@ html, body, #root, #root > div > .spaces-space { - height: var(--ios-vvh, 100vh); - max-height: var(--ios-vvh, 100vh); + height: var(--ios-vvh, -webkit-fill-available, 100vh); + max-height: var(--ios-vvh, -webkit-fill-available, 100vh); } diff --git a/src/editor/EditorArea.tsx b/src/editor/EditorArea.tsx index 93e566f20..8bb720954 100644 --- a/src/editor/EditorArea.tsx +++ b/src/editor/EditorArea.tsx @@ -27,6 +27,7 @@ const EditorArea = ({ filename, onSelectedFileChanged }: EditorAreaProps) => { { + return ( + + ); +}; + +export default Ace; diff --git a/src/editor/codemirror/CodeMirror.tsx b/src/editor/codemirror/CodeMirror.tsx index f6cb0191b..84b6c2cb4 100644 --- a/src/editor/codemirror/CodeMirror.tsx +++ b/src/editor/codemirror/CodeMirror.tsx @@ -1,7 +1,7 @@ import { EditorState } from "@codemirror/state"; -import { Text } from "@codemirror/text"; import { EditorView } from "@codemirror/view"; import { useEffect, useMemo, useRef } from "react"; +import { EditorComponentProps } from "../editor"; import { blocks, blocksCompartment } from "./blocks"; import "./CodeMirror.css"; import { @@ -10,15 +10,6 @@ import { themeExtensionsCompartment, } from "./config"; -interface CodeMirrorProps { - className?: string; - defaultValue: Text; - onChange: (doc: Text) => void; - - fontSize: number; - highlightCodeStructure: boolean; -} - /** * A React component for CodeMirror 6. * @@ -33,7 +24,7 @@ const CodeMirror = ({ onChange, fontSize, highlightCodeStructure, -}: CodeMirrorProps) => { +}: EditorComponentProps) => { const elementRef = useRef(null); const viewRef = useRef(null); diff --git a/src/editor/editor.ts b/src/editor/editor.ts new file mode 100644 index 000000000..a09db675a --- /dev/null +++ b/src/editor/editor.ts @@ -0,0 +1,17 @@ +export interface TextDocument { + toString(): string; +} + +/** + * Common interface for embedded editors. + * + * Useful while we're exploring options. + */ +export interface EditorComponentProps { + className?: string; + defaultValue: string; + onChange: (doc: TextDocument) => void; + + fontSize: number; + highlightCodeStructure: boolean; +} diff --git a/src/editor/monaco/Monaco.tsx b/src/editor/monaco/Monaco.tsx new file mode 100644 index 000000000..d6ed26226 --- /dev/null +++ b/src/editor/monaco/Monaco.tsx @@ -0,0 +1,47 @@ +import Editor, { Monaco, OnChange } from "@monaco-editor/react"; +import { EditorComponentProps } from "../editor"; + +const ptToPixelRatio = 96 / 72; + +const configureMonaco = (monaco: Monaco) => { + monaco.editor.defineTheme("microbit", { + base: "vs", + inherit: true, + rules: [], + colors: { + "editor.background": "#fffff8", // CSS variable doesn't work here + }, + }); +}; + +const OurMonaco = ({ + defaultValue, + onChange, + fontSize, +}: EditorComponentProps) => { + const handleChange: OnChange = (value, event) => { + if (value) { + onChange(value); + } + }; + return ( + + ); +}; + +export default OurMonaco; diff --git a/src/project/project-hooks.tsx b/src/project/project-hooks.tsx index 1c9f49967..ef7a0f0f8 100644 --- a/src/project/project-hooks.tsx +++ b/src/project/project-hooks.tsx @@ -1,15 +1,15 @@ -import { Text } from "@codemirror/state"; import { useCallback, useEffect, useMemo, useState } from "react"; import useActionFeedback from "../common/use-action-feedback"; +import { useDialogs } from "../common/use-dialogs"; import useIsUnmounted from "../common/use-is-unmounted"; import { useDevice } from "../device/device-hooks"; +import { TextDocument } from "../editor/editor"; import { EVENT_PROJECT_UPDATED, Project } from "../fs/fs"; import { useFileSystem } from "../fs/fs-hooks"; import { VersionAction } from "../fs/storage"; import { useLogging } from "../logging/logging-hooks"; -import { useDialogs } from "../common/use-dialogs"; -import { ProjectActions } from "./project-actions"; import { useSelection } from "../workbench/use-selection"; +import { ProjectActions } from "./project-actions"; /** * Hook exposing the main UI actions. @@ -64,10 +64,10 @@ export const useProject = (): Project => { */ export const useProjectFileText = ( filename: string -): [Text | undefined, (text: Text) => void] => { +): [string | undefined, (doc: TextDocument) => void] => { const fs = useFileSystem(); const actionFeedback = useActionFeedback(); - const [initialValue, setInitialValue] = useState(); + const [initialValue, setInitialValue] = useState(); const isUnmounted = useIsUnmounted(); useEffect(() => { const loadData = async () => { @@ -76,7 +76,7 @@ export const useProjectFileText = ( const { data } = await fs.read(filename); const text = new TextDecoder().decode(data); if (!isUnmounted()) { - setInitialValue(Text.of(text.split("\n"))); + setInitialValue(text); } } } catch (e) { @@ -88,9 +88,9 @@ export const useProjectFileText = ( }, [fs, filename, actionFeedback, isUnmounted]); const handleChange = useCallback( - (text: Text) => { + (text: TextDocument) => { try { - const content = text.sliceString(0, undefined, "\n"); + const content = text.toString(); fs.write(filename, content, VersionAction.MAINTAIN); } catch (e) { actionFeedback.unexpectedError(e);