From 80fc871a718fc1c7c9426fa57a0c144afb5e14ad Mon Sep 17 00:00:00 2001 From: Marta Religa <marta.religa@cs.ox.ac.uk> Date: Fri, 23 Feb 2024 16:37:09 +0000 Subject: [PATCH 001/106] Example --- src/editor/codemirror/CodeMirror.tsx | 55 ++++++++-- .../codemirror/reactWidgetExtension.tsx | 103 ++++++++++++++++++ 2 files changed, 149 insertions(+), 9 deletions(-) create mode 100644 src/editor/codemirror/reactWidgetExtension.tsx diff --git a/src/editor/codemirror/CodeMirror.tsx b/src/editor/codemirror/CodeMirror.tsx index 31d155dd1..495ed9ef8 100644 --- a/src/editor/codemirror/CodeMirror.tsx +++ b/src/editor/codemirror/CodeMirror.tsx @@ -12,7 +12,15 @@ import { lineNumbers, ViewUpdate, } from "@codemirror/view"; -import { useEffect, useMemo, useRef } from "react"; +import React, { + ReactNode, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from "react"; +import { createPortal } from "react-dom"; import { useIntl } from "react-intl"; import { lineNumFromUint8Array } from "../../common/text-util"; import useActionFeedback from "../../common/use-action-feedback"; @@ -40,6 +48,7 @@ import { languageServer } from "./language-server/view"; import { lintGutter } from "./lint/lint"; import { codeStructure } from "./structure-highlighting"; import themeExtensions from "./themeExtensions"; +import { reactWidgetExtension } from "./reactWidgetExtension"; interface CodeMirrorProps { className?: string; @@ -52,6 +61,20 @@ interface CodeMirrorProps { parameterHelpOption: ParameterHelpOption; } +interface PortalContent { + dom: HTMLElement; + content: ReactNode; +} + +/** + * Creates a React portal for a CodeMirror dom element (e.g. for a widget) and + * returns a clean-up function to call when the widget is destroyed. + */ +export type PortalFactory = ( + dom: HTMLElement, + content: ReactNode +) => () => void; + /** * A React component for CodeMirror 6. * @@ -100,6 +123,13 @@ const CodeMirror = ({ [fontSize, codeStructureOption, parameterHelpOption] ); + const [portals, setPortals] = useState<PortalContent[]>([]); + const portalFactory: PortalFactory = useCallback((dom, content) => { + const portal = { dom, content }; + setPortals((portals) => [...portals, portal]); + return () => setPortals((portals) => portals.filter((p) => p !== portal)); + }, []); + useEffect(() => { const initializing = !viewRef.current; if (initializing) { @@ -118,6 +148,7 @@ const CodeMirror = ({ extensions: [ notify, editorConfig, + reactWidgetExtension(portalFactory), // Extension requires external state. dndSupport({ sessionSettings, setSessionSettings }), // Extensions only relevant for editing: @@ -172,6 +203,9 @@ const CodeMirror = ({ parameterHelpOption, uri, apiReferenceMap, + portals, + portalFactory, + setPortals, ]); useEffect(() => { // Do this separately as we don't want to destroy the view whenever options needed for initialization change. @@ -260,13 +294,16 @@ const CodeMirror = ({ }, [routerState, setRouterState]); return ( - <section - data-testid="editor" - aria-label={intl.formatMessage({ id: "code-editor" })} - style={{ height: "100%" }} - className={className} - ref={elementRef} - /> + <> + <section + data-testid="editor" + aria-label={intl.formatMessage({ id: "code-editor" })} + style={{ height: "100%" }} + className={className} + ref={elementRef} + /> + {portals.map(({ content, dom }) => createPortal(content, dom))} + </> ); }; @@ -293,4 +330,4 @@ const logPastedLineCount = (logging: Logging, update: ViewUpdate) => { ); }; -export default CodeMirror; +export default CodeMirror; \ No newline at end of file diff --git a/src/editor/codemirror/reactWidgetExtension.tsx b/src/editor/codemirror/reactWidgetExtension.tsx new file mode 100644 index 000000000..680ac2d17 --- /dev/null +++ b/src/editor/codemirror/reactWidgetExtension.tsx @@ -0,0 +1,103 @@ +import { Button, HStack, Text } from "@chakra-ui/react"; +import { EditorState, Extension, StateField } from "@codemirror/state"; +import { + Decoration, + DecorationSet, + EditorView, + WidgetType, +} from "@codemirror/view"; +import { useCallback } from "react"; +import { supportedLanguages, useSettings } from "../../settings/settings"; +import { PortalFactory } from "./CodeMirror"; + +/** + * An example react component that we use inside a CodeMirror widget as + * a proof of concept. + */ +const ExampleReactComponent = () => { + // This is a weird thing to do in a CodeMirror widget but proves the point that + // we can use React features to communicate with the rest of the app. + const [settings, setSettings] = useSettings(); + const handleClick = useCallback(() => { + let { languageId } = settings; + while (languageId === settings.languageId) { + languageId = + supportedLanguages[ + Math.floor(Math.random() * supportedLanguages.length) + ].id; + } + setSettings({ + ...settings, + languageId, + }); + }, [settings, setSettings]); + return ( + <HStack fontFamily="body" spacing={5} py={3}> + <Button onClick={handleClick}>Pick random UI language</Button> + <Text fontWeight="semibold">Current language: {settings.languageId}</Text> + </HStack> + ); +}; + +/** + * This widget will have its contents rendered by the code in CodeMirror.tsx + * which it communicates with via the portal factory. + */ +class ExampleReactBlockWidget extends WidgetType { + private portalCleanup: (() => void) | undefined; + + constructor(private createPortal: PortalFactory) { + super(); + } + + toDOM() { + const dom = document.createElement("div"); + this.portalCleanup = this.createPortal(dom, <ExampleReactComponent />); + return dom; + } + + destroy(dom: HTMLElement): void { + if (this.portalCleanup) { + this.portalCleanup(); + } + } + + ignoreEvent() { + return true; + } +} + +/** + * A toy extension that creates a wiget after the first line. + */ +export const reactWidgetExtension = ( + createPortal: PortalFactory +): Extension => { + const decorate = (state: EditorState) => { + // Just put a widget at the start of the document. + // A more interesting example would look at the cursor (selection) and/or syntax tree. + const endOfFirstLine = state.doc.lineAt(0).to; + const widget = Decoration.widget({ + block: true, + widget: new ExampleReactBlockWidget(createPortal), + side: 1, + }); + return Decoration.set(widget.range(endOfFirstLine)); + }; + + const stateField = StateField.define<DecorationSet>({ + create(state) { + return decorate(state); + }, + update(widgets, transaction) { + if (transaction.docChanged) { + return decorate(transaction.state); + } + return widgets.map(transaction.changes); + }, + provide(field) { + return EditorView.decorations.from(field); + }, + }); + return [stateField]; +}; \ No newline at end of file From abf06c59f77eb306f23f898220b310dd76f9153a Mon Sep 17 00:00:00 2001 From: Marta Religa <marta.religa@cs.ox.ac.uk> Date: Fri, 23 Feb 2024 19:12:06 +0000 Subject: [PATCH 002/106] This should popup by select_pixel and let you select pixels interactively --- src/editor/codemirror/CodeMirror.tsx | 55 ++++++-- .../codemirror/reactWidgetExtension.tsx | 129 ++++++++++++++++++ 2 files changed, 175 insertions(+), 9 deletions(-) create mode 100644 src/editor/codemirror/reactWidgetExtension.tsx diff --git a/src/editor/codemirror/CodeMirror.tsx b/src/editor/codemirror/CodeMirror.tsx index 31d155dd1..495ed9ef8 100644 --- a/src/editor/codemirror/CodeMirror.tsx +++ b/src/editor/codemirror/CodeMirror.tsx @@ -12,7 +12,15 @@ import { lineNumbers, ViewUpdate, } from "@codemirror/view"; -import { useEffect, useMemo, useRef } from "react"; +import React, { + ReactNode, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from "react"; +import { createPortal } from "react-dom"; import { useIntl } from "react-intl"; import { lineNumFromUint8Array } from "../../common/text-util"; import useActionFeedback from "../../common/use-action-feedback"; @@ -40,6 +48,7 @@ import { languageServer } from "./language-server/view"; import { lintGutter } from "./lint/lint"; import { codeStructure } from "./structure-highlighting"; import themeExtensions from "./themeExtensions"; +import { reactWidgetExtension } from "./reactWidgetExtension"; interface CodeMirrorProps { className?: string; @@ -52,6 +61,20 @@ interface CodeMirrorProps { parameterHelpOption: ParameterHelpOption; } +interface PortalContent { + dom: HTMLElement; + content: ReactNode; +} + +/** + * Creates a React portal for a CodeMirror dom element (e.g. for a widget) and + * returns a clean-up function to call when the widget is destroyed. + */ +export type PortalFactory = ( + dom: HTMLElement, + content: ReactNode +) => () => void; + /** * A React component for CodeMirror 6. * @@ -100,6 +123,13 @@ const CodeMirror = ({ [fontSize, codeStructureOption, parameterHelpOption] ); + const [portals, setPortals] = useState<PortalContent[]>([]); + const portalFactory: PortalFactory = useCallback((dom, content) => { + const portal = { dom, content }; + setPortals((portals) => [...portals, portal]); + return () => setPortals((portals) => portals.filter((p) => p !== portal)); + }, []); + useEffect(() => { const initializing = !viewRef.current; if (initializing) { @@ -118,6 +148,7 @@ const CodeMirror = ({ extensions: [ notify, editorConfig, + reactWidgetExtension(portalFactory), // Extension requires external state. dndSupport({ sessionSettings, setSessionSettings }), // Extensions only relevant for editing: @@ -172,6 +203,9 @@ const CodeMirror = ({ parameterHelpOption, uri, apiReferenceMap, + portals, + portalFactory, + setPortals, ]); useEffect(() => { // Do this separately as we don't want to destroy the view whenever options needed for initialization change. @@ -260,13 +294,16 @@ const CodeMirror = ({ }, [routerState, setRouterState]); return ( - <section - data-testid="editor" - aria-label={intl.formatMessage({ id: "code-editor" })} - style={{ height: "100%" }} - className={className} - ref={elementRef} - /> + <> + <section + data-testid="editor" + aria-label={intl.formatMessage({ id: "code-editor" })} + style={{ height: "100%" }} + className={className} + ref={elementRef} + /> + {portals.map(({ content, dom }) => createPortal(content, dom))} + </> ); }; @@ -293,4 +330,4 @@ const logPastedLineCount = (logging: Logging, update: ViewUpdate) => { ); }; -export default CodeMirror; +export default CodeMirror; \ No newline at end of file diff --git a/src/editor/codemirror/reactWidgetExtension.tsx b/src/editor/codemirror/reactWidgetExtension.tsx new file mode 100644 index 000000000..58e4cbfc7 --- /dev/null +++ b/src/editor/codemirror/reactWidgetExtension.tsx @@ -0,0 +1,129 @@ +import { Button, HStack, Text } from "@chakra-ui/react"; +import { EditorState, Extension, StateField } from "@codemirror/state"; +import { + Decoration, + DecorationSet, + EditorView, + WidgetType, +} from "@codemirror/view"; +import { useCallback } from "react"; +import { supportedLanguages, useSettings } from "../../settings/settings"; +import { PortalFactory } from "./CodeMirror"; + +/** + * An example react component that we use inside a CodeMirror widget as + * a proof of concept. + */ +const ExampleReactComponent = () => { + // This is a weird thing to do in a CodeMirror widget but proves the point that + // we can use React features to communicate with the rest of the app. + const [settings, setSettings] = useSettings(); + const handleClick = useCallback(() => { + let { languageId } = settings; + while (languageId === settings.languageId) { + languageId = + supportedLanguages[ + Math.floor(Math.random() * supportedLanguages.length) + ].id; + } + setSettings({ + ...settings, + languageId, + }); + }, [settings, setSettings]); + return ( + <HStack fontFamily="body" spacing={5} py={3}> + <Button onClick={handleClick}>Pick random UI language</Button> + <Text fontWeight="semibold">Current language: {settings.languageId}</Text> + </HStack> + ); +}; + +const MicrobitLEDSelector = () => { + const selectedLED = null; // Initially, no LED is selected + + return ( + <div style={{ padding: "10px", border: "1px solid #ccc" }}> + <h4>Select lights to turn on</h4> + <div style={{ display: "grid", gridTemplateColumns: "repeat(5, 20px)", gap: "5px" }}> + {[...Array(5)].map((_, row) => ( + [...Array(5)].map((_, col) => ( + <div + key={`${row},${col}`} + style={{ + width: "20px", + height: "20px", + border: "1px solid #ccc", + background: "white", // Initially all LEDs are white + }} + ></div> + )) + ))} + </div> + </div> + ); + }; + +/** + * This widget will have its contents rendered by the code in CodeMirror.tsx + * which it communicates with via the portal factory. + */ +class ExampleReactBlockWidget extends WidgetType { + private portalCleanup: (() => void) | undefined; + + constructor(private createPortal: PortalFactory) { + super(); + } + + toDOM() { + const dom = document.createElement("div"); + this.portalCleanup = this.createPortal(dom, <MicrobitLEDSelector />); + return dom; + } + + destroy(dom: HTMLElement): void { + if (this.portalCleanup) { + this.portalCleanup(); + } + } + + ignoreEvent() { + return true; + } +} + +/** + * A toy extension that creates a wiget after the first line. + */ +export const reactWidgetExtension = ( + createPortal: PortalFactory +): Extension => { + const decorate = (state: EditorState) => { + // Just put a widget at the start of the document. + // A more interesting example would look at the cursor (selection) and/or syntax tree. + const endOfFirstLine = state.doc.lineAt(0).to; + const widget = Decoration.widget({ + block: true, + widget: new ExampleReactBlockWidget(createPortal), + side: 1, + }); + + return Decoration.set(widget.range(endOfFirstLine)); + }; + + const stateField = StateField.define<DecorationSet>({ + create(state) { + return decorate(state); + }, + update(widgets, transaction) { + if (transaction.docChanged) { + return decorate(transaction.state); + } + return widgets.map(transaction.changes); + }, + provide(field) { + return EditorView.decorations.from(field); + }, + }); + return [stateField]; +}; \ No newline at end of file From 2eef8a968c7d111f495ffb434a8fccd659be50c0 Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Sun, 25 Feb 2024 18:48:11 +0000 Subject: [PATCH 003/106] modified to create incr button --- .../codemirror/reactWidgetExtension.tsx | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/src/editor/codemirror/reactWidgetExtension.tsx b/src/editor/codemirror/reactWidgetExtension.tsx index 680ac2d17..41efed6be 100644 --- a/src/editor/codemirror/reactWidgetExtension.tsx +++ b/src/editor/codemirror/reactWidgetExtension.tsx @@ -6,7 +6,7 @@ import { EditorView, WidgetType, } from "@codemirror/view"; -import { useCallback } from "react"; +import { useState, useCallback } from "react"; import { supportedLanguages, useSettings } from "../../settings/settings"; import { PortalFactory } from "./CodeMirror"; @@ -17,24 +17,16 @@ import { PortalFactory } from "./CodeMirror"; const ExampleReactComponent = () => { // This is a weird thing to do in a CodeMirror widget but proves the point that // we can use React features to communicate with the rest of the app. - const [settings, setSettings] = useSettings(); + // Use the useState hook to store and update the counter value. + const [counter, setCounter] = useState(0); + // Define a callback function that increments the counter by one. const handleClick = useCallback(() => { - let { languageId } = settings; - while (languageId === settings.languageId) { - languageId = - supportedLanguages[ - Math.floor(Math.random() * supportedLanguages.length) - ].id; - } - setSettings({ - ...settings, - languageId, - }); - }, [settings, setSettings]); + setCounter(counter + 1); + }, [counter]); return ( <HStack fontFamily="body" spacing={5} py={3}> - <Button onClick={handleClick}>Pick random UI language</Button> - <Text fontWeight="semibold">Current language: {settings.languageId}</Text> + <Button onClick={handleClick}>Increment</Button> + <Text fontWeight="semibold">Counter: {counter}</Text> </HStack> ); }; From 8d9650a0f0bc3f28e35854452b1d2b098fabe818 Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Wed, 28 Feb 2024 13:22:46 +0000 Subject: [PATCH 004/106] increment button for each comment iterates through syntax tree, printing node.name to console --- .../codemirror/reactWidgetExtension.tsx | 39 ++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/src/editor/codemirror/reactWidgetExtension.tsx b/src/editor/codemirror/reactWidgetExtension.tsx index 41efed6be..082bbf8dc 100644 --- a/src/editor/codemirror/reactWidgetExtension.tsx +++ b/src/editor/codemirror/reactWidgetExtension.tsx @@ -6,9 +6,11 @@ import { EditorView, WidgetType, } from "@codemirror/view"; +import { syntaxTree } from "@codemirror/language" import { useState, useCallback } from "react"; import { supportedLanguages, useSettings } from "../../settings/settings"; import { PortalFactory } from "./CodeMirror"; +import { debug } from "../../editor/codemirror/dnd"; /** * An example react component that we use inside a CodeMirror widget as @@ -22,6 +24,7 @@ const ExampleReactComponent = () => { // Define a callback function that increments the counter by one. const handleClick = useCallback(() => { setCounter(counter + 1); + //console.log(counter) }, [counter]); return ( <HStack fontFamily="body" spacing={5} py={3}> @@ -66,15 +69,33 @@ export const reactWidgetExtension = ( createPortal: PortalFactory ): Extension => { const decorate = (state: EditorState) => { - // Just put a widget at the start of the document. - // A more interesting example would look at the cursor (selection) and/or syntax tree. - const endOfFirstLine = state.doc.lineAt(0).to; - const widget = Decoration.widget({ - block: true, - widget: new ExampleReactBlockWidget(createPortal), - side: 1, - }); - return Decoration.set(widget.range(endOfFirstLine)); + let widgets: any[] = [] + let from = 0 + let to = 100 + + syntaxTree(state).iterate({ + from, to, + enter: (node: any) => { + console.log(node.name) + if (node.name == "Comment") { + let isTrue = state.doc.sliceString(node.from, node.to) == "true" + let deco = Decoration.widget({ + widget: new ExampleReactBlockWidget(createPortal), + side: 1, + }); + widgets.push(deco.range(node.to)) + } + } + }) + return Decoration.set(widgets) + + // const endOfFirstLine = state.doc.lineAt(0).to; + // const widget = Decoration.widget({ + // block: true, + // widget: new ExampleReactBlockWidget(createPortal), + // side: 1, + // }); + // return Decoration.set(widget.range(endOfFirstLine)); }; const stateField = StateField.define<DecorationSet>({ From 452e5a557044d7108eda6c7d94b5c16a6ccfcc77 Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Wed, 28 Feb 2024 13:44:56 +0000 Subject: [PATCH 005/106] print doc as string to console --- src/editor/codemirror/reactWidgetExtension.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/editor/codemirror/reactWidgetExtension.tsx b/src/editor/codemirror/reactWidgetExtension.tsx index 082bbf8dc..16e331c88 100644 --- a/src/editor/codemirror/reactWidgetExtension.tsx +++ b/src/editor/codemirror/reactWidgetExtension.tsx @@ -72,7 +72,8 @@ export const reactWidgetExtension = ( let widgets: any[] = [] let from = 0 let to = 100 - + let t = state.doc.toString() + console.log(t); syntaxTree(state).iterate({ from, to, enter: (node: any) => { From 7f6f08eba175352bde63fb362e93b162077b54c5 Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Wed, 28 Feb 2024 13:59:41 +0000 Subject: [PATCH 006/106] button after all SoundEffect mentions --- src/editor/codemirror/reactWidgetExtension.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/editor/codemirror/reactWidgetExtension.tsx b/src/editor/codemirror/reactWidgetExtension.tsx index 16e331c88..d592bf5ff 100644 --- a/src/editor/codemirror/reactWidgetExtension.tsx +++ b/src/editor/codemirror/reactWidgetExtension.tsx @@ -71,15 +71,16 @@ export const reactWidgetExtension = ( const decorate = (state: EditorState) => { let widgets: any[] = [] let from = 0 - let to = 100 + let to = 10000 // TODO: modify this to be the actual end let t = state.doc.toString() console.log(t); syntaxTree(state).iterate({ from, to, enter: (node: any) => { console.log(node.name) - if (node.name == "Comment") { - let isTrue = state.doc.sliceString(node.from, node.to) == "true" + console.log(state.doc.sliceString(node.from, node.to)) + if (node.name == "VariableName" && state.doc.sliceString(node.from, node.to) == "SoundEffect") { + //let isTrue = state.doc.sliceString(node.from, node.to) == "true" let deco = Decoration.widget({ widget: new ExampleReactBlockWidget(createPortal), side: 1, From dfc2bc24d2cbd82aadf6ad09da63f47620e95166 Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Wed, 28 Feb 2024 14:17:05 +0000 Subject: [PATCH 007/106] only puts incr button after SoundEffect ArgList seems to have intended effect, but might not be best solution --- src/editor/codemirror/reactWidgetExtension.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/editor/codemirror/reactWidgetExtension.tsx b/src/editor/codemirror/reactWidgetExtension.tsx index d592bf5ff..0ef389500 100644 --- a/src/editor/codemirror/reactWidgetExtension.tsx +++ b/src/editor/codemirror/reactWidgetExtension.tsx @@ -73,13 +73,14 @@ export const reactWidgetExtension = ( let from = 0 let to = 10000 // TODO: modify this to be the actual end let t = state.doc.toString() + let sound = false console.log(t); syntaxTree(state).iterate({ from, to, enter: (node: any) => { - console.log(node.name) - console.log(state.doc.sliceString(node.from, node.to)) - if (node.name == "VariableName" && state.doc.sliceString(node.from, node.to) == "SoundEffect") { + //console.log(node.name) + //console.log(state.doc.sliceString(node.from, node.to)) + if(sound && node.name == "ArgList"){ //let isTrue = state.doc.sliceString(node.from, node.to) == "true" let deco = Decoration.widget({ widget: new ExampleReactBlockWidget(createPortal), @@ -87,6 +88,9 @@ export const reactWidgetExtension = ( }); widgets.push(deco.range(node.to)) } + // detected SoundEffect, if next expression is an ArgList, show UI + // TODO: ensure this is the only case of SoundEffect ArgList + sound = node.name == "VariableName" && state.doc.sliceString(node.from, node.to) == "SoundEffect" } }) return Decoration.set(widgets) From e2ae08a93e5f36abdc428200156c3d2383071b85 Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Sun, 3 Mar 2024 17:16:14 +0000 Subject: [PATCH 008/106] decorate now iterates syntax tree to end of file --- src/editor/codemirror/reactWidgetExtension.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/editor/codemirror/reactWidgetExtension.tsx b/src/editor/codemirror/reactWidgetExtension.tsx index 0ef389500..aaf6b25f3 100644 --- a/src/editor/codemirror/reactWidgetExtension.tsx +++ b/src/editor/codemirror/reactWidgetExtension.tsx @@ -62,16 +62,14 @@ class ExampleReactBlockWidget extends WidgetType { } } -/** - * A toy extension that creates a wiget after the first line. - */ +// Iterates through the syntax tree, finding occurences of SoundEffect ArgList, and places toy widget there export const reactWidgetExtension = ( createPortal: PortalFactory ): Extension => { const decorate = (state: EditorState) => { let widgets: any[] = [] let from = 0 - let to = 10000 // TODO: modify this to be the actual end + let to = state.doc.length-1 // TODO: could optimize this to just be lines within view let t = state.doc.toString() let sound = false console.log(t); From d3d0a001fba666afb78beedb63df9cf8f3222ede Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Sun, 3 Mar 2024 17:51:29 +0000 Subject: [PATCH 009/106] begin parsing arguments in SoundEffect --- .../codemirror/reactWidgetExtension.tsx | 53 ++++++++++++++----- 1 file changed, 41 insertions(+), 12 deletions(-) diff --git a/src/editor/codemirror/reactWidgetExtension.tsx b/src/editor/codemirror/reactWidgetExtension.tsx index aaf6b25f3..b70b4ef50 100644 --- a/src/editor/codemirror/reactWidgetExtension.tsx +++ b/src/editor/codemirror/reactWidgetExtension.tsx @@ -71,24 +71,53 @@ export const reactWidgetExtension = ( let from = 0 let to = state.doc.length-1 // TODO: could optimize this to just be lines within view let t = state.doc.toString() - let sound = false - console.log(t); + //console.log(t); + + let sound = false // detected a SoundEffect, waiting to pair with ArgList + let parsingArgs = false; let argEnd = 0; // Found pattern to create widget, parse args + // I: parenthesis = # open paren - # closed paren, G: parenthesis <= 0 + // !G ^ I -> # open paren - # closed paren = 0, reached end of ArgList + let parenthesis = 0; + syntaxTree(state).iterate({ from, to, enter: (node: any) => { //console.log(node.name) //console.log(state.doc.sliceString(node.from, node.to)) - if(sound && node.name == "ArgList"){ - //let isTrue = state.doc.sliceString(node.from, node.to) == "true" - let deco = Decoration.widget({ - widget: new ExampleReactBlockWidget(createPortal), - side: 1, - }); - widgets.push(deco.range(node.to)) + + // Walking through ArgList, trying to match with values + if(parsingArgs){ + if(node.name == "(") parenthesis += 1 + else if(node.name == ")") { + parenthesis -= 1 + if(parenthesis <= 0){ + // finished parsing ArgList + parsingArgs = false + + // create the widget + let deco = Decoration.widget({ + widget: new ExampleReactBlockWidget(createPortal), + side: 1, + }); + widgets.push(deco.range(argEnd)) + } + } + else{ + console.log(node.name) + console.log(state.doc.sliceString(node.from, node.to)) + // Claim: if parenthesis = 1 (and code is well formatted) then we are not nested + // Idea: parse through, adding name and slices until a comma is spotted at parenthesis = 1 + // at the very end, we can figure out if they are valid, and therefore if to modify the visual + } + } + else{ + // Found ArgList, will begin to parse nodes + if(sound && node.name == "ArgList") { sound = false; parsingArgs = true; argEnd = node.to } + + // detected SoundEffect, if next expression is an ArgList, show UI + // TODO: ensure this is the only case of SoundEffect ArgList + sound = node.name == "VariableName" && state.doc.sliceString(node.from, node.to) == "SoundEffect" } - // detected SoundEffect, if next expression is an ArgList, show UI - // TODO: ensure this is the only case of SoundEffect ArgList - sound = node.name == "VariableName" && state.doc.sliceString(node.from, node.to) == "SoundEffect" } }) return Decoration.set(widgets) From 1af8bf283a5488eb6824b7d39392aca081231248 Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Tue, 5 Mar 2024 13:44:17 +0000 Subject: [PATCH 010/106] fixed issues --- src/editor/codemirror/reactWidgetExtension.tsx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/editor/codemirror/reactWidgetExtension.tsx b/src/editor/codemirror/reactWidgetExtension.tsx index b70b4ef50..2714e7e66 100644 --- a/src/editor/codemirror/reactWidgetExtension.tsx +++ b/src/editor/codemirror/reactWidgetExtension.tsx @@ -8,9 +8,7 @@ import { } from "@codemirror/view"; import { syntaxTree } from "@codemirror/language" import { useState, useCallback } from "react"; -import { supportedLanguages, useSettings } from "../../settings/settings"; import { PortalFactory } from "./CodeMirror"; -import { debug } from "../../editor/codemirror/dnd"; /** * An example react component that we use inside a CodeMirror widget as @@ -70,7 +68,7 @@ export const reactWidgetExtension = ( let widgets: any[] = [] let from = 0 let to = state.doc.length-1 // TODO: could optimize this to just be lines within view - let t = state.doc.toString() + //let t = state.doc.toString() //console.log(t); let sound = false // detected a SoundEffect, waiting to pair with ArgList @@ -87,8 +85,8 @@ export const reactWidgetExtension = ( // Walking through ArgList, trying to match with values if(parsingArgs){ - if(node.name == "(") parenthesis += 1 - else if(node.name == ")") { + if(node.name === "(") parenthesis += 1 + else if(node.name === ")") { parenthesis -= 1 if(parenthesis <= 0){ // finished parsing ArgList @@ -112,11 +110,11 @@ export const reactWidgetExtension = ( } else{ // Found ArgList, will begin to parse nodes - if(sound && node.name == "ArgList") { sound = false; parsingArgs = true; argEnd = node.to } + if(sound && node.name === "ArgList") { sound = false; parsingArgs = true; argEnd = node.to } // detected SoundEffect, if next expression is an ArgList, show UI // TODO: ensure this is the only case of SoundEffect ArgList - sound = node.name == "VariableName" && state.doc.sliceString(node.from, node.to) == "SoundEffect" + sound = node.name === "VariableName" && state.doc.sliceString(node.from, node.to) === "SoundEffect" } } }) From 140997b5a1cadb1db51a503e4c5980f663dd23a7 Mon Sep 17 00:00:00 2001 From: Marta Religa <marta.religa@cs.ox.ac.uk> Date: Tue, 5 Mar 2024 16:57:48 +0000 Subject: [PATCH 011/106] I'm proud, I would just need help with connecting this to editorstate logic and so on so it can appear when a user clicks on set_pixel, on the correct line, more like a popover and updates the parameters to the set_pixel function accordingly --- src/editor/codemirror/microbitWidget.tsx | 132 ++++++++++++++++++ .../codemirror/reactWidgetExtension.tsx | 62 +------- 2 files changed, 136 insertions(+), 58 deletions(-) create mode 100644 src/editor/codemirror/microbitWidget.tsx diff --git a/src/editor/codemirror/microbitWidget.tsx b/src/editor/codemirror/microbitWidget.tsx new file mode 100644 index 000000000..e92d7ac4b --- /dev/null +++ b/src/editor/codemirror/microbitWidget.tsx @@ -0,0 +1,132 @@ +import { Box, Grid, GridItem, Button, Slider, SliderTrack, SliderFilledTrack, SliderThumb } from "@chakra-ui/react"; +import { useState } from "react"; + +interface Pixel { + x: number; + y: number; + brightness: number; +} + +const MicrobitPixel: React.FC<{ brightness: number; selected: boolean; onClick: () => void }> = ({ brightness, selected, onClick }) => { + return ( + <Button + size="sm" + h="20px" + w="20px" + p={0} + bgColor={selected ? `rgba(255, 0, 0, ${brightness / 9})` : "rgba(0, 0, 0, 1)"} + _hover={{ bgColor: selected ? `rgba(255, 0, 0, ${brightness / 9})` : "rgba(0, 0, 0, 1)" }} + onClick={onClick} + _focus={{ boxShadow: "none" }} + _active={{ bgColor: selected ? `rgba(255, 0, 0, ${brightness / 9})` : "rgba(0, 0, 0, 1)" }} + /> + ); +}; + +interface MicrobitGridProps { + onClickPixel: (pixel: Pixel) => void; + onSubmit: () => void; + isVisible: boolean; +} + +const MicrobitGrid: React.FC<MicrobitGridProps> = ({ onClickPixel, onSubmit, isVisible }) => { + const [selectedPixel, setSelectedPixel] = useState<Pixel | null>(null); + const [brightness, setBrightness] = useState<number>(5); + + const handleClickPixel = (x: number, y: number) => { + const newPixel: Pixel = { x, y, brightness }; + setSelectedPixel(newPixel); + onClickPixel(newPixel); + }; + + const handleSliderChange = (value: number) => { + if (selectedPixel) { + setBrightness(value); + const updatedPixel: Pixel = { ...selectedPixel, brightness: value }; + onClickPixel(updatedPixel); + } + }; + + const handleOkClick = () => { + onSubmit(); + }; + + return ( + <Box display={isVisible ? "flex" : "none"} flexDirection="row"> + <Box> + <Grid templateColumns={`repeat(5, 1fr)`} gap="2px" maxW="110px"> + {[...Array(5)].map((_, x) => ( + <GridItem key={x}> + <Grid templateColumns={`repeat(1, 1fr)`} gap="2px"> + {[...Array(5)].map((_, y) => ( + <GridItem key={y}> + <MicrobitPixel + brightness={selectedPixel?.x === x && selectedPixel.y === y ? brightness : 0} + selected={selectedPixel?.x === x && selectedPixel.y === y} + onClick={() => handleClickPixel(x, y)} + /> + </GridItem> + ))} + </Grid> + </GridItem> + ))} + </Grid> + </Box> + {selectedPixel && ( + <Box ml="60px" mt="10px"> + <Slider + aria-label="brightness" + defaultValue={brightness} + min={1} + max={9} + step={1} + onChange={handleSliderChange} + orientation="vertical" + _focus={{ boxShadow: "none" }} + _active={{ bgColor: "transparent" }} + > + <SliderTrack> + <SliderFilledTrack /> + </SliderTrack> + <SliderThumb /> + </Slider> + </Box> + )} + {selectedPixel && ( + <Box display="flex" justifyContent="center" ml="10px" mt="100px"> + <Button onClick={handleOkClick} colorScheme="blue" size="sm"> + Looks Good! + </Button> + </Box> + )} + </Box> + ); +}; + +export const MicrobitComponent: React.FC = () => { + const [selectedPixel, setSelectedPixel] = useState<Pixel | null>(null); + const [isVisible, setIsVisible] = useState(true); + + const handleSelectPixel = (pixel: Pixel) => { + setSelectedPixel(pixel); + }; + + const handleSubmit = () => { + setIsVisible(false); + // Implement submit logic here + console.log("Submit button clicked"); + }; + + return ( + <Box> + {isVisible && ( + <MicrobitGrid onClickPixel={handleSelectPixel} onSubmit={handleSubmit} isVisible={isVisible} /> + )} + {selectedPixel && isVisible && ( + <Box mt="4px"> + Selected Pixel: ({selectedPixel.x}, {selectedPixel.y}) - Brightness: {selectedPixel.brightness} + </Box> + )} + </Box> + ); +}; diff --git a/src/editor/codemirror/reactWidgetExtension.tsx b/src/editor/codemirror/reactWidgetExtension.tsx index 58e4cbfc7..ab21dd285 100644 --- a/src/editor/codemirror/reactWidgetExtension.tsx +++ b/src/editor/codemirror/reactWidgetExtension.tsx @@ -1,4 +1,3 @@ -import { Button, HStack, Text } from "@chakra-ui/react"; import { EditorState, Extension, StateField } from "@codemirror/state"; import { Decoration, @@ -6,63 +5,9 @@ import { EditorView, WidgetType, } from "@codemirror/view"; -import { useCallback } from "react"; -import { supportedLanguages, useSettings } from "../../settings/settings"; import { PortalFactory } from "./CodeMirror"; +import { MicrobitComponent } from "./microbitWidget"; -/** - * An example react component that we use inside a CodeMirror widget as - * a proof of concept. - */ -const ExampleReactComponent = () => { - // This is a weird thing to do in a CodeMirror widget but proves the point that - // we can use React features to communicate with the rest of the app. - const [settings, setSettings] = useSettings(); - const handleClick = useCallback(() => { - let { languageId } = settings; - while (languageId === settings.languageId) { - languageId = - supportedLanguages[ - Math.floor(Math.random() * supportedLanguages.length) - ].id; - } - setSettings({ - ...settings, - languageId, - }); - }, [settings, setSettings]); - return ( - <HStack fontFamily="body" spacing={5} py={3}> - <Button onClick={handleClick}>Pick random UI language</Button> - <Text fontWeight="semibold">Current language: {settings.languageId}</Text> - </HStack> - ); -}; - -const MicrobitLEDSelector = () => { - const selectedLED = null; // Initially, no LED is selected - - return ( - <div style={{ padding: "10px", border: "1px solid #ccc" }}> - <h4>Select lights to turn on</h4> - <div style={{ display: "grid", gridTemplateColumns: "repeat(5, 20px)", gap: "5px" }}> - {[...Array(5)].map((_, row) => ( - [...Array(5)].map((_, col) => ( - <div - key={`${row},${col}`} - style={{ - width: "20px", - height: "20px", - border: "1px solid #ccc", - background: "white", // Initially all LEDs are white - }} - ></div> - )) - ))} - </div> - </div> - ); - }; /** * This widget will have its contents rendered by the code in CodeMirror.tsx @@ -77,7 +22,7 @@ class ExampleReactBlockWidget extends WidgetType { toDOM() { const dom = document.createElement("div"); - this.portalCleanup = this.createPortal(dom, <MicrobitLEDSelector />); + this.portalCleanup = this.createPortal(dom, <MicrobitComponent />); return dom; } @@ -126,4 +71,5 @@ export const reactWidgetExtension = ( }, }); return [stateField]; -}; \ No newline at end of file +}; + From cd9c53e74cf7668debdb15c23d85efe2a9064786 Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Mon, 11 Mar 2024 16:47:20 +0000 Subject: [PATCH 012/106] refactored for clarity --- .../codemirror/reactWidgetExtension.tsx | 59 +++++++------------ 1 file changed, 20 insertions(+), 39 deletions(-) diff --git a/src/editor/codemirror/reactWidgetExtension.tsx b/src/editor/codemirror/reactWidgetExtension.tsx index 2714e7e66..e9a7d3e07 100644 --- a/src/editor/codemirror/reactWidgetExtension.tsx +++ b/src/editor/codemirror/reactWidgetExtension.tsx @@ -36,7 +36,7 @@ const ExampleReactComponent = () => { * This widget will have its contents rendered by the code in CodeMirror.tsx * which it communicates with via the portal factory. */ -class ExampleReactBlockWidget extends WidgetType { +class IncrementWidget extends WidgetType { private portalCleanup: (() => void) | undefined; constructor(private createPortal: PortalFactory) { @@ -60,6 +60,17 @@ class ExampleReactBlockWidget extends WidgetType { } } +function createWidget(node: any, createPortal: PortalFactory): Decoration { + console.log(node[1]); + + let deco = Decoration.widget({ + widget: new IncrementWidget(createPortal), + side: 1, + }); + + return deco; +} + // Iterates through the syntax tree, finding occurences of SoundEffect ArgList, and places toy widget there export const reactWidgetExtension = ( createPortal: PortalFactory @@ -72,50 +83,20 @@ export const reactWidgetExtension = ( //console.log(t); let sound = false // detected a SoundEffect, waiting to pair with ArgList - let parsingArgs = false; let argEnd = 0; // Found pattern to create widget, parse args - // I: parenthesis = # open paren - # closed paren, G: parenthesis <= 0 - // !G ^ I -> # open paren - # closed paren = 0, reached end of ArgList - let parenthesis = 0; syntaxTree(state).iterate({ from, to, - enter: (node: any) => { + enter: (node: any) => { // TODO: type is SyntaxNode //console.log(node.name) //console.log(state.doc.sliceString(node.from, node.to)) + console.log(node.name); + console.log(node.node.getChildren()); - // Walking through ArgList, trying to match with values - if(parsingArgs){ - if(node.name === "(") parenthesis += 1 - else if(node.name === ")") { - parenthesis -= 1 - if(parenthesis <= 0){ - // finished parsing ArgList - parsingArgs = false - - // create the widget - let deco = Decoration.widget({ - widget: new ExampleReactBlockWidget(createPortal), - side: 1, - }); - widgets.push(deco.range(argEnd)) - } - } - else{ - console.log(node.name) - console.log(state.doc.sliceString(node.from, node.to)) - // Claim: if parenthesis = 1 (and code is well formatted) then we are not nested - // Idea: parse through, adding name and slices until a comma is spotted at parenthesis = 1 - // at the very end, we can figure out if they are valid, and therefore if to modify the visual - } - } - else{ - // Found ArgList, will begin to parse nodes - if(sound && node.name === "ArgList") { sound = false; parsingArgs = true; argEnd = node.to } - - // detected SoundEffect, if next expression is an ArgList, show UI - // TODO: ensure this is the only case of SoundEffect ArgList - sound = node.name === "VariableName" && state.doc.sliceString(node.from, node.to) === "SoundEffect" - } + // Found ArgList, will begin to parse nodes + if(sound && node.name === "ArgList") widgets.push(createWidget(node, createPortal).range(node.to)); + + // detected SoundEffect, if next expression is an ArgList, show UI + sound = node.name === "VariableName" && state.doc.sliceString(node.from, node.to) === "SoundEffect" } }) return Decoration.set(widgets) From 50d0d65240f52d8371719ab5b86d1ac968dbd157 Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Mon, 11 Mar 2024 16:53:27 +0000 Subject: [PATCH 013/106] initial implementation --- src/editor/codemirror/reactWidgetExtension.tsx | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/editor/codemirror/reactWidgetExtension.tsx b/src/editor/codemirror/reactWidgetExtension.tsx index e9a7d3e07..ffaf4d92c 100644 --- a/src/editor/codemirror/reactWidgetExtension.tsx +++ b/src/editor/codemirror/reactWidgetExtension.tsx @@ -60,9 +60,9 @@ class IncrementWidget extends WidgetType { } } -function createWidget(node: any, createPortal: PortalFactory): Decoration { - console.log(node[1]); - +function createWidget(bool: string, from: number, to: number, createPortal: PortalFactory): Decoration { + console.log(bool); + let deco = Decoration.widget({ widget: new IncrementWidget(createPortal), side: 1, @@ -82,21 +82,15 @@ export const reactWidgetExtension = ( //let t = state.doc.toString() //console.log(t); - let sound = false // detected a SoundEffect, waiting to pair with ArgList - syntaxTree(state).iterate({ from, to, enter: (node: any) => { // TODO: type is SyntaxNode //console.log(node.name) //console.log(state.doc.sliceString(node.from, node.to)) - console.log(node.name); - console.log(node.node.getChildren()); - // Found ArgList, will begin to parse nodes - if(sound && node.name === "ArgList") widgets.push(createWidget(node, createPortal).range(node.to)); - - // detected SoundEffect, if next expression is an ArgList, show UI - sound = node.name === "VariableName" && state.doc.sliceString(node.from, node.to) === "SoundEffect" + if(node.name === "Boolean") { + createWidget(state.doc.sliceString(node.from, node.to), node.from, node.to, createPortal) + } } }) return Decoration.set(widgets) From 2639675e13d59f3fff2079230b449930c1ab7ece Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Mon, 11 Mar 2024 16:55:35 +0000 Subject: [PATCH 014/106] Update reactWidgetExtension.tsx --- src/editor/codemirror/reactWidgetExtension.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/editor/codemirror/reactWidgetExtension.tsx b/src/editor/codemirror/reactWidgetExtension.tsx index ffaf4d92c..01c3af37c 100644 --- a/src/editor/codemirror/reactWidgetExtension.tsx +++ b/src/editor/codemirror/reactWidgetExtension.tsx @@ -93,6 +93,7 @@ export const reactWidgetExtension = ( } } }) + return Decoration.set(widgets) // const endOfFirstLine = state.doc.lineAt(0).to; From 9a38948faa843b1eee438f3f6461305b48af8816 Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Mon, 11 Mar 2024 17:08:35 +0000 Subject: [PATCH 015/106] temp --- src/editor/codemirror/reactWidgetExtension.tsx | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/editor/codemirror/reactWidgetExtension.tsx b/src/editor/codemirror/reactWidgetExtension.tsx index 01c3af37c..b21a50a59 100644 --- a/src/editor/codemirror/reactWidgetExtension.tsx +++ b/src/editor/codemirror/reactWidgetExtension.tsx @@ -14,10 +14,7 @@ import { PortalFactory } from "./CodeMirror"; * An example react component that we use inside a CodeMirror widget as * a proof of concept. */ -const ExampleReactComponent = () => { - // This is a weird thing to do in a CodeMirror widget but proves the point that - // we can use React features to communicate with the rest of the app. - // Use the useState hook to store and update the counter value. +function ToggleReactComponent(bval: boolean): React.ReactNode { const [counter, setCounter] = useState(0); // Define a callback function that increments the counter by one. const handleClick = useCallback(() => { @@ -36,16 +33,17 @@ const ExampleReactComponent = () => { * This widget will have its contents rendered by the code in CodeMirror.tsx * which it communicates with via the portal factory. */ -class IncrementWidget extends WidgetType { +class ToggleWidget extends WidgetType { private portalCleanup: (() => void) | undefined; - constructor(private createPortal: PortalFactory) { + constructor(private bval: boolean, private createPortal: PortalFactory) { super(); } toDOM() { + console.log(this.bval); const dom = document.createElement("div"); - this.portalCleanup = this.createPortal(dom, <ExampleReactComponent />); + this.portalCleanup = this.createPortal(dom, ToggleReactComponent(this.bval)); return dom; } @@ -61,10 +59,10 @@ class IncrementWidget extends WidgetType { } function createWidget(bool: string, from: number, to: number, createPortal: PortalFactory): Decoration { - console.log(bool); + let bval = bool === "True" let deco = Decoration.widget({ - widget: new IncrementWidget(createPortal), + widget: new ToggleWidget(bval, createPortal), side: 1, }); @@ -89,7 +87,7 @@ export const reactWidgetExtension = ( //console.log(state.doc.sliceString(node.from, node.to)) if(node.name === "Boolean") { - createWidget(state.doc.sliceString(node.from, node.to), node.from, node.to, createPortal) + widgets.push(createWidget(state.doc.sliceString(node.from, node.to), node.from, node.to, createPortal).range(node.to)); } } }) From d040fddec3c53d31f51ab5f4ff9b7955609a2d33 Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Mon, 11 Mar 2024 17:15:33 +0000 Subject: [PATCH 016/106] Update reactWidgetExtension.tsx --- src/editor/codemirror/reactWidgetExtension.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/editor/codemirror/reactWidgetExtension.tsx b/src/editor/codemirror/reactWidgetExtension.tsx index b21a50a59..e909437d2 100644 --- a/src/editor/codemirror/reactWidgetExtension.tsx +++ b/src/editor/codemirror/reactWidgetExtension.tsx @@ -14,6 +14,7 @@ import { PortalFactory } from "./CodeMirror"; * An example react component that we use inside a CodeMirror widget as * a proof of concept. */ + function ToggleReactComponent(bval: boolean): React.ReactNode { const [counter, setCounter] = useState(0); // Define a callback function that increments the counter by one. From dba517a2f75c3c491fe2a6a7b1cc9a7db3593d2e Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Mon, 11 Mar 2024 17:34:13 +0000 Subject: [PATCH 017/106] passing arguments into react widgets --- .../codemirror/reactWidgetExtension.tsx | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/editor/codemirror/reactWidgetExtension.tsx b/src/editor/codemirror/reactWidgetExtension.tsx index e909437d2..192d89a6e 100644 --- a/src/editor/codemirror/reactWidgetExtension.tsx +++ b/src/editor/codemirror/reactWidgetExtension.tsx @@ -15,17 +15,15 @@ import { PortalFactory } from "./CodeMirror"; * a proof of concept. */ -function ToggleReactComponent(bval: boolean): React.ReactNode { - const [counter, setCounter] = useState(0); - // Define a callback function that increments the counter by one. +const ToggleReactComponent = ({ bval }: { bval: boolean }) => { + let x = bval ? "True" : "False" const handleClick = useCallback(() => { - setCounter(counter + 1); - //console.log(counter) - }, [counter]); + console.log(); + }, []); return ( <HStack fontFamily="body" spacing={5} py={3}> - <Button onClick={handleClick}>Increment</Button> - <Text fontWeight="semibold">Counter: {counter}</Text> + <Button onClick={handleClick}>Toggle</Button> + <Text fontWeight="semibold">Value: {x}</Text> </HStack> ); }; @@ -44,7 +42,11 @@ class ToggleWidget extends WidgetType { toDOM() { console.log(this.bval); const dom = document.createElement("div"); - this.portalCleanup = this.createPortal(dom, ToggleReactComponent(this.bval)); + const handleClick = () => { + console.log("hi"); + }; + + this.portalCleanup = this.createPortal(dom, < ToggleReactComponent bval={this.bval} />); return dom; } @@ -86,7 +88,7 @@ export const reactWidgetExtension = ( enter: (node: any) => { // TODO: type is SyntaxNode //console.log(node.name) //console.log(state.doc.sliceString(node.from, node.to)) - + //state.replaceSelection() if(node.name === "Boolean") { widgets.push(createWidget(state.doc.sliceString(node.from, node.to), node.from, node.to, createPortal).range(node.to)); } From 10213a5c0d55f47fb07e19799352147c2b8d66da Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Tue, 12 Mar 2024 02:13:43 -0400 Subject: [PATCH 018/106] widget reads values, needs view to dispatch changes --- .../codemirror/reactWidgetExtension.tsx | 56 ++++++++++++++++--- 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/src/editor/codemirror/reactWidgetExtension.tsx b/src/editor/codemirror/reactWidgetExtension.tsx index 192d89a6e..82b72d161 100644 --- a/src/editor/codemirror/reactWidgetExtension.tsx +++ b/src/editor/codemirror/reactWidgetExtension.tsx @@ -40,11 +40,7 @@ class ToggleWidget extends WidgetType { } toDOM() { - console.log(this.bval); const dom = document.createElement("div"); - const handleClick = () => { - console.log("hi"); - }; this.portalCleanup = this.createPortal(dom, < ToggleReactComponent bval={this.bval} />); return dom; @@ -61,6 +57,40 @@ class ToggleWidget extends WidgetType { } } + +const TextComponent = () => { + return ( + <HStack fontFamily="body" spacing={5} py={3}> + <Text fontWeight="semibold">False</Text> + </HStack> + ); +}; + +class TextWidget extends WidgetType { + private portalCleanup: (() => void) | undefined; + + constructor(private createPortal: PortalFactory) { + super(); + } + + toDOM() { + const dom = document.createElement("div"); + + this.portalCleanup = this.createPortal(dom, < TextComponent />); + return dom; + } + + destroy(dom: HTMLElement): void { + if (this.portalCleanup) { + this.portalCleanup(); + } + } + + ignoreEvent() { + return true; + } +} + function createWidget(bool: string, from: number, to: number, createPortal: PortalFactory): Decoration { let bval = bool === "True" @@ -86,10 +116,22 @@ export const reactWidgetExtension = ( syntaxTree(state).iterate({ from, to, enter: (node: any) => { // TODO: type is SyntaxNode - //console.log(node.name) - //console.log(state.doc.sliceString(node.from, node.to)) - //state.replaceSelection() if(node.name === "Boolean") { + // view.dispatch({ + // changes: { + // from: node.from, + // to: node.to, + // insert: state.doc.sliceString(0, 10), + // } + // }); + // widgets.push(tr); + + // let replaceDeco = Decoration.replace({ + // widget: new TextWidget(createPortal), + // inclusive: false, + // }).range(node.from, node.to); + // widgets.push(replaceDeco); + widgets.push(createWidget(state.doc.sliceString(node.from, node.to), node.from, node.to, createPortal).range(node.to)); } } From d25bbda72353b264af186ef44499c406cd69996d Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Tue, 12 Mar 2024 09:14:15 -0400 Subject: [PATCH 019/106] bounds are a bit off, but text changes --- src/editor/codemirror/CodeMirror.tsx | 10 +- .../codemirror/reactWidgetExtension.tsx | 94 +++++-------------- 2 files changed, 26 insertions(+), 78 deletions(-) diff --git a/src/editor/codemirror/CodeMirror.tsx b/src/editor/codemirror/CodeMirror.tsx index 495ed9ef8..aebee0185 100644 --- a/src/editor/codemirror/CodeMirror.tsx +++ b/src/editor/codemirror/CodeMirror.tsx @@ -143,12 +143,15 @@ const CodeMirror = ({ logPastedLineCount(logging, update); } }); + const view = new EditorView({ + parent: elementRef.current!, + }); const state = EditorState.create({ doc: defaultValue, extensions: [ notify, editorConfig, - reactWidgetExtension(portalFactory), + reactWidgetExtension(view, portalFactory), // Extension requires external state. dndSupport({ sessionSettings, setSessionSettings }), // Extensions only relevant for editing: @@ -180,10 +183,7 @@ const CodeMirror = ({ ]), ], }); - const view = new EditorView({ - state, - parent: elementRef.current!, - }); + view.setState(state); viewRef.current = view; setActiveEditor(new EditorActions(view, logging, actionFeedback)); diff --git a/src/editor/codemirror/reactWidgetExtension.tsx b/src/editor/codemirror/reactWidgetExtension.tsx index 82b72d161..d2efbd27d 100644 --- a/src/editor/codemirror/reactWidgetExtension.tsx +++ b/src/editor/codemirror/reactWidgetExtension.tsx @@ -15,15 +15,22 @@ import { PortalFactory } from "./CodeMirror"; * a proof of concept. */ -const ToggleReactComponent = ({ bval }: { bval: boolean }) => { - let x = bval ? "True" : "False" +const ToggleReactComponent = ({ from, to, view }: { from: number, to: number, view: EditorView }) => { + let curVal = view.state.doc.sliceString(from, to); const handleClick = useCallback(() => { - console.log(); - }, []); + let opposite = curVal === "True" ? "False" : "True"; + view.dispatch({ + changes: { + from: from, + to: to, + insert: opposite, + } + }); + }, [curVal, from, to, view]); return ( <HStack fontFamily="body" spacing={5} py={3}> <Button onClick={handleClick}>Toggle</Button> - <Text fontWeight="semibold">Value: {x}</Text> + <Text fontWeight="semibold">Value: {curVal}</Text> </HStack> ); }; @@ -35,14 +42,14 @@ const ToggleReactComponent = ({ bval }: { bval: boolean }) => { class ToggleWidget extends WidgetType { private portalCleanup: (() => void) | undefined; - constructor(private bval: boolean, private createPortal: PortalFactory) { + constructor(private from: number, private to: number, private createPortal: PortalFactory, private view: EditorView) { super(); } toDOM() { const dom = document.createElement("div"); - this.portalCleanup = this.createPortal(dom, < ToggleReactComponent bval={this.bval} />); + this.portalCleanup = this.createPortal(dom, < ToggleReactComponent from={this.from} to={this.to} view={this.view} />); return dom; } @@ -57,45 +64,9 @@ class ToggleWidget extends WidgetType { } } - -const TextComponent = () => { - return ( - <HStack fontFamily="body" spacing={5} py={3}> - <Text fontWeight="semibold">False</Text> - </HStack> - ); -}; - -class TextWidget extends WidgetType { - private portalCleanup: (() => void) | undefined; - - constructor(private createPortal: PortalFactory) { - super(); - } - - toDOM() { - const dom = document.createElement("div"); - - this.portalCleanup = this.createPortal(dom, < TextComponent />); - return dom; - } - - destroy(dom: HTMLElement): void { - if (this.portalCleanup) { - this.portalCleanup(); - } - } - - ignoreEvent() { - return true; - } -} - -function createWidget(bool: string, from: number, to: number, createPortal: PortalFactory): Decoration { - let bval = bool === "True" - +function createWidget(from: number, to: number, createPortal: PortalFactory, view: EditorView): Decoration { let deco = Decoration.widget({ - widget: new ToggleWidget(bval, createPortal), + widget: new ToggleWidget(from, to, createPortal, view), side: 1, }); @@ -104,9 +75,11 @@ function createWidget(bool: string, from: number, to: number, createPortal: Port // Iterates through the syntax tree, finding occurences of SoundEffect ArgList, and places toy widget there export const reactWidgetExtension = ( + view: EditorView, createPortal: PortalFactory ): Extension => { - const decorate = (state: EditorState) => { + const decorate = (state2: EditorState) => { + let state = view.state; let widgets: any[] = [] let from = 0 let to = state.doc.length-1 // TODO: could optimize this to just be lines within view @@ -115,37 +88,12 @@ export const reactWidgetExtension = ( syntaxTree(state).iterate({ from, to, - enter: (node: any) => { // TODO: type is SyntaxNode - if(node.name === "Boolean") { - // view.dispatch({ - // changes: { - // from: node.from, - // to: node.to, - // insert: state.doc.sliceString(0, 10), - // } - // }); - // widgets.push(tr); - - // let replaceDeco = Decoration.replace({ - // widget: new TextWidget(createPortal), - // inclusive: false, - // }).range(node.from, node.to); - // widgets.push(replaceDeco); - - widgets.push(createWidget(state.doc.sliceString(node.from, node.to), node.from, node.to, createPortal).range(node.to)); - } + enter: (node: any) => { // TODO: type is SyntaxNode? + if(node.name === "Boolean") widgets.push(createWidget(node.from, node.to, createPortal, view).range(node.to)); } }) return Decoration.set(widgets) - - // const endOfFirstLine = state.doc.lineAt(0).to; - // const widget = Decoration.widget({ - // block: true, - // widget: new ExampleReactBlockWidget(createPortal), - // side: 1, - // }); - // return Decoration.set(widget.range(endOfFirstLine)); }; const stateField = StateField.define<DecorationSet>({ From be632a8231910054db701d9e37b2444e9f478fd5 Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Tue, 12 Mar 2024 09:44:56 -0400 Subject: [PATCH 020/106] view from DOM --- src/editor/codemirror/CodeMirror.tsx | 11 ++++++----- src/editor/codemirror/reactWidgetExtension.tsx | 16 +++++++--------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/editor/codemirror/CodeMirror.tsx b/src/editor/codemirror/CodeMirror.tsx index aebee0185..32c1d87d6 100644 --- a/src/editor/codemirror/CodeMirror.tsx +++ b/src/editor/codemirror/CodeMirror.tsx @@ -143,15 +143,13 @@ const CodeMirror = ({ logPastedLineCount(logging, update); } }); - const view = new EditorView({ - parent: elementRef.current!, - }); + const state = EditorState.create({ doc: defaultValue, extensions: [ notify, editorConfig, - reactWidgetExtension(view, portalFactory), + reactWidgetExtension(portalFactory), // Extension requires external state. dndSupport({ sessionSettings, setSessionSettings }), // Extensions only relevant for editing: @@ -183,7 +181,10 @@ const CodeMirror = ({ ]), ], }); - view.setState(state); + const view = new EditorView({ + state, + parent: elementRef.current!, + }); viewRef.current = view; setActiveEditor(new EditorActions(view, logging, actionFeedback)); diff --git a/src/editor/codemirror/reactWidgetExtension.tsx b/src/editor/codemirror/reactWidgetExtension.tsx index d2efbd27d..b08d4514c 100644 --- a/src/editor/codemirror/reactWidgetExtension.tsx +++ b/src/editor/codemirror/reactWidgetExtension.tsx @@ -42,14 +42,14 @@ const ToggleReactComponent = ({ from, to, view }: { from: number, to: number, vi class ToggleWidget extends WidgetType { private portalCleanup: (() => void) | undefined; - constructor(private from: number, private to: number, private createPortal: PortalFactory, private view: EditorView) { + constructor(private from: number, private to: number, private createPortal: PortalFactory) { super(); } - toDOM() { + toDOM(view: EditorView) { const dom = document.createElement("div"); - this.portalCleanup = this.createPortal(dom, < ToggleReactComponent from={this.from} to={this.to} view={this.view} />); + this.portalCleanup = this.createPortal(dom, < ToggleReactComponent from={this.from} to={this.to} view={view} />); return dom; } @@ -64,9 +64,9 @@ class ToggleWidget extends WidgetType { } } -function createWidget(from: number, to: number, createPortal: PortalFactory, view: EditorView): Decoration { +function createWidget(from: number, to: number, createPortal: PortalFactory): Decoration { let deco = Decoration.widget({ - widget: new ToggleWidget(from, to, createPortal, view), + widget: new ToggleWidget(from, to, createPortal), side: 1, }); @@ -75,11 +75,9 @@ function createWidget(from: number, to: number, createPortal: PortalFactory, vie // Iterates through the syntax tree, finding occurences of SoundEffect ArgList, and places toy widget there export const reactWidgetExtension = ( - view: EditorView, createPortal: PortalFactory ): Extension => { - const decorate = (state2: EditorState) => { - let state = view.state; + const decorate = (state: EditorState) => { let widgets: any[] = [] let from = 0 let to = state.doc.length-1 // TODO: could optimize this to just be lines within view @@ -89,7 +87,7 @@ export const reactWidgetExtension = ( syntaxTree(state).iterate({ from, to, enter: (node: any) => { // TODO: type is SyntaxNode? - if(node.name === "Boolean") widgets.push(createWidget(node.from, node.to, createPortal, view).range(node.to)); + if(node.name === "Boolean") widgets.push(createWidget(node.from, node.to, createPortal).range(node.to)); } }) From a85b3ce2c2c24a41c07ec65c00d89cf2038a6020 Mon Sep 17 00:00:00 2001 From: Marta Religa <marta.religa@cs.ox.ac.uk> Date: Tue, 12 Mar 2024 14:54:12 +0100 Subject: [PATCH 021/106] . --- src/editor/codemirror/microbitWidget.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/editor/codemirror/microbitWidget.tsx b/src/editor/codemirror/microbitWidget.tsx index e92d7ac4b..a127c368d 100644 --- a/src/editor/codemirror/microbitWidget.tsx +++ b/src/editor/codemirror/microbitWidget.tsx @@ -113,8 +113,8 @@ export const MicrobitComponent: React.FC = () => { const handleSubmit = () => { setIsVisible(false); - // Implement submit logic here - console.log("Submit button clicked"); + // Implement logic here, change the arguments to the function + console.log("S"); }; return ( From ffdf5feb9aaacbddb9227a2a6ed0b3f8cf3a641d Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Tue, 12 Mar 2024 10:09:26 -0400 Subject: [PATCH 022/106] puts widget after set_pixel --- src/editor/codemirror/reactWidgetExtension.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/editor/codemirror/reactWidgetExtension.tsx b/src/editor/codemirror/reactWidgetExtension.tsx index 35332a9e0..a8a754ccf 100644 --- a/src/editor/codemirror/reactWidgetExtension.tsx +++ b/src/editor/codemirror/reactWidgetExtension.tsx @@ -84,11 +84,19 @@ export const reactWidgetExtension = ( let to = state.doc.length-1 // TODO: could optimize this to just be lines within view //let t = state.doc.toString() //console.log(t); + let setpix = false; syntaxTree(state).iterate({ from, to, enter: (node: any) => { // TODO: type is SyntaxNode? - if(node.name === "Boolean") widgets.push(createWidget(node.from, node.to, createPortal).range(node.to)); + //console.log(); + //if(node.name === "Boolean") widgets.push(createWidget(node.from, node.to, createPortal).range(node.to)); + + // Found ArgList, will begin to parse nodes + if(setpix && node.name === "ArgList") widgets.push(createWidget(node.from, node.to, createPortal).range(node.to)); + + // detected SoundEffect, if next expression is an ArgList, show UI + setpix = node.name === "PropertyName" && state.doc.sliceString(node.from, node.to) === "set_pixel" } }) From aeabe522d8113a877e44e7ae35bd15e985afa64b Mon Sep 17 00:00:00 2001 From: Marta Religa <marta.religa@cs.ox.ac.uk> Date: Tue, 12 Mar 2024 15:26:46 +0100 Subject: [PATCH 023/106] . --- src/editor/codemirror/microbitWidget.tsx | 68 ++++++++++++------- .../codemirror/reactWidgetExtension.tsx | 3 +- 2 files changed, 44 insertions(+), 27 deletions(-) diff --git a/src/editor/codemirror/microbitWidget.tsx b/src/editor/codemirror/microbitWidget.tsx index a127c368d..6c2675d29 100644 --- a/src/editor/codemirror/microbitWidget.tsx +++ b/src/editor/codemirror/microbitWidget.tsx @@ -1,5 +1,11 @@ import { Box, Grid, GridItem, Button, Slider, SliderTrack, SliderFilledTrack, SliderThumb } from "@chakra-ui/react"; import { useState } from "react"; +import { + Decoration, + DecorationSet, + EditorView, + WidgetType, + } from "@codemirror/view"; interface Pixel { x: number; @@ -25,7 +31,7 @@ const MicrobitPixel: React.FC<{ brightness: number; selected: boolean; onClick: interface MicrobitGridProps { onClickPixel: (pixel: Pixel) => void; - onSubmit: () => void; + onSubmit: (x:number, y:number, brightness:number) => void; isVisible: boolean; } @@ -48,7 +54,9 @@ const MicrobitGrid: React.FC<MicrobitGridProps> = ({ onClickPixel, onSubmit, isV }; const handleOkClick = () => { - onSubmit(); + if (selectedPixel) { + onSubmit(selectedPixel.x, selectedPixel.y, selectedPixel.brightness); + } }; return ( @@ -103,30 +111,38 @@ const MicrobitGrid: React.FC<MicrobitGridProps> = ({ onClickPixel, onSubmit, isV ); }; -export const MicrobitComponent: React.FC = () => { - const [selectedPixel, setSelectedPixel] = useState<Pixel | null>(null); - const [isVisible, setIsVisible] = useState(true); - - const handleSelectPixel = (pixel: Pixel) => { - setSelectedPixel(pixel); - }; - - const handleSubmit = () => { - setIsVisible(false); - // Implement logic here, change the arguments to the function - console.log("S"); - }; +export const MicrobitComponent = ({ from, to, view }: { from: number, to: number, view: EditorView }) => { + const [selectedPixel, setSelectedPixel] = useState<Pixel | null>(null); + const [isVisible, setIsVisible] = useState(true); + + const handleSelectPixel = (pixel: Pixel) => { + setSelectedPixel(pixel); + }; + + const handleSubmit = (x: number, y: number, brightness: number) => { + setIsVisible(false); + console.log(`Submitted pixel: (${x}, ${y}) - Brightness: ${brightness}`); + view.dispatch({ + changes: { + from: from, + to: to, + insert: ("(${x}, ${y},${y}, ${brightness}) "), + } + }); + }; + - return ( - <Box> - {isVisible && ( - <MicrobitGrid onClickPixel={handleSelectPixel} onSubmit={handleSubmit} isVisible={isVisible} /> - )} - {selectedPixel && isVisible && ( - <Box mt="4px"> - Selected Pixel: ({selectedPixel.x}, {selectedPixel.y}) - Brightness: {selectedPixel.brightness} + return ( + <Box> + {isVisible && ( + <MicrobitGrid onClickPixel={handleSelectPixel} onSubmit={handleSubmit} isVisible={isVisible} /> + )} + {selectedPixel && isVisible && ( + <Box mt="4px"> + Selected Pixel: ({selectedPixel.x}, {selectedPixel.y}) - Brightness: {selectedPixel.brightness} + </Box> + )} </Box> - )} - </Box> - ); + ); }; + diff --git a/src/editor/codemirror/reactWidgetExtension.tsx b/src/editor/codemirror/reactWidgetExtension.tsx index 35332a9e0..549d053c7 100644 --- a/src/editor/codemirror/reactWidgetExtension.tsx +++ b/src/editor/codemirror/reactWidgetExtension.tsx @@ -50,7 +50,7 @@ class ToggleWidget extends WidgetType { toDOM(view: EditorView) { const dom = document.createElement("div"); - this.portalCleanup = this.createPortal(dom, <MicrobitComponent/>); + this.portalCleanup = this.createPortal(dom, <MicrobitComponent from={this.from} to={this.to} view={view} />); return dom; } @@ -68,6 +68,7 @@ class ToggleWidget extends WidgetType { function createWidget(from: number, to: number, createPortal: PortalFactory): Decoration { let deco = Decoration.widget({ widget: new ToggleWidget(from, to, createPortal), + //ToggleWidget(from, to, createPortal), side: 1, }); From f5c8a7b51d36e0f1d753dac2c7341e13a1242b18 Mon Sep 17 00:00:00 2001 From: Marta Religa <marta.religa@cs.ox.ac.uk> Date: Tue, 12 Mar 2024 15:31:18 +0100 Subject: [PATCH 024/106] works yay --- src/editor/codemirror/microbitWidget.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor/codemirror/microbitWidget.tsx b/src/editor/codemirror/microbitWidget.tsx index 6c2675d29..0cf9fc310 100644 --- a/src/editor/codemirror/microbitWidget.tsx +++ b/src/editor/codemirror/microbitWidget.tsx @@ -126,7 +126,7 @@ export const MicrobitComponent = ({ from, to, view }: { from: number, to: number changes: { from: from, to: to, - insert: ("(${x}, ${y},${y}, ${brightness}) "), + insert: (`(${x}, ${y}, ${brightness}) `), } }); }; From 743714b3ebc245cf61d5e2e6c46754578bd84ad2 Mon Sep 17 00:00:00 2001 From: Marta Religa <marta.religa@cs.ox.ac.uk> Date: Mon, 18 Mar 2024 17:03:51 +0100 Subject: [PATCH 025/106] added stuff --- src/editor/codemirror/microbitWidget.tsx | 268 ++++++++++++------ .../codemirror/reactWidgetExtension.tsx | 5 +- 2 files changed, 191 insertions(+), 82 deletions(-) diff --git a/src/editor/codemirror/microbitWidget.tsx b/src/editor/codemirror/microbitWidget.tsx index 0cf9fc310..9588579ec 100644 --- a/src/editor/codemirror/microbitWidget.tsx +++ b/src/editor/codemirror/microbitWidget.tsx @@ -1,10 +1,7 @@ import { Box, Grid, GridItem, Button, Slider, SliderTrack, SliderFilledTrack, SliderThumb } from "@chakra-ui/react"; import { useState } from "react"; import { - Decoration, - DecorationSet, EditorView, - WidgetType, } from "@codemirror/view"; interface Pixel { @@ -13,29 +10,13 @@ interface Pixel { brightness: number; } -const MicrobitPixel: React.FC<{ brightness: number; selected: boolean; onClick: () => void }> = ({ brightness, selected, onClick }) => { - return ( - <Button - size="sm" - h="20px" - w="20px" - p={0} - bgColor={selected ? `rgba(255, 0, 0, ${brightness / 9})` : "rgba(0, 0, 0, 1)"} - _hover={{ bgColor: selected ? `rgba(255, 0, 0, ${brightness / 9})` : "rgba(0, 0, 0, 1)" }} - onClick={onClick} - _focus={{ boxShadow: "none" }} - _active={{ bgColor: selected ? `rgba(255, 0, 0, ${brightness / 9})` : "rgba(0, 0, 0, 1)" }} - /> - ); -}; - -interface MicrobitGridProps { +interface MicrobitSinglePixelGridProps { onClickPixel: (pixel: Pixel) => void; onSubmit: (x:number, y:number, brightness:number) => void; isVisible: boolean; } -const MicrobitGrid: React.FC<MicrobitGridProps> = ({ onClickPixel, onSubmit, isVisible }) => { +const MicrobitSinglePixelGrid: React.FC<MicrobitSinglePixelGridProps> = ({ onClickPixel, onSubmit, isVisible }) => { const [selectedPixel, setSelectedPixel] = useState<Pixel | null>(null); const [brightness, setBrightness] = useState<number>(5); @@ -58,40 +39,59 @@ const MicrobitGrid: React.FC<MicrobitGridProps> = ({ onClickPixel, onSubmit, isV onSubmit(selectedPixel.x, selectedPixel.y, selectedPixel.brightness); } }; - + return ( - <Box display={isVisible ? "flex" : "none"} flexDirection="row"> + <> + {selectedPixel && ( + <Box mt="4px"> + <span style={{ fontSize: "small" }}> + Selected Pixel: ({selectedPixel.x}, {selectedPixel.y}) | Brightness: {selectedPixel.brightness} + </span> + </Box> + )} + <Box display={isVisible ? "flex" : "none"} flexDirection="row" justifyContent="flex-start"> <Box> - <Grid templateColumns={`repeat(5, 1fr)`} gap="2px" maxW="110px"> + <Box bg="black" p="10px" borderRadius="5px"> {[...Array(5)].map((_, x) => ( - <GridItem key={x}> - <Grid templateColumns={`repeat(1, 1fr)`} gap="2px"> - {[...Array(5)].map((_, y) => ( - <GridItem key={y}> - <MicrobitPixel - brightness={selectedPixel?.x === x && selectedPixel.y === y ? brightness : 0} - selected={selectedPixel?.x === x && selectedPixel.y === y} - onClick={() => handleClickPixel(x, y)} - /> - </GridItem> - ))} - </Grid> - </GridItem> + <Box key={x} display="flex"> + {[...Array(5)].map((_, y) => ( + <Box key={y} display="flex" mr="2px"> + <Button + size="xs" + h="15px" + w="15px" + p={0} + bgColor={selectedPixel?.x === x && selectedPixel.y === y ? `rgba(255, 0, 0, ${brightness / 9})` : "rgba(255, 255, 255, 0)"} + _hover={{ bgColor: selectedPixel?.x === x && selectedPixel.y === y ? `rgba(255, 0, 0, ${brightness / 9})` : "rgba(255, 255, 255, 0.5)" }} + onClick={() => handleClickPixel(x, y)} + /> + </Box> + ))} + </Box> ))} - </Grid> + </Box> + {selectedPixel && ( + <Box display="flex" flexDirection="column" alignItems="center" mt="10px"> + <Box bg="white" borderRadius="5px" p="5px" textAlign="center"> + <Button onClick={() => onSubmit(selectedPixel?.x ?? 0, selectedPixel?.y ?? 0, selectedPixel?.brightness ?? 0)} colorScheme="blue" size="sm"> + Looks Good + </Button> + </Box> + </Box> + )} </Box> {selectedPixel && ( - <Box ml="60px" mt="10px"> + <Box ml="10px"> <Slider aria-label="brightness" defaultValue={brightness} - min={1} + min={0} max={9} step={1} - onChange={handleSliderChange} orientation="vertical" _focus={{ boxShadow: "none" }} _active={{ bgColor: "transparent" }} + onChange={handleSliderChange} > <SliderTrack> <SliderFilledTrack /> @@ -100,49 +100,159 @@ const MicrobitGrid: React.FC<MicrobitGridProps> = ({ onClickPixel, onSubmit, isV </Slider> </Box> )} - {selectedPixel && ( - <Box display="flex" justifyContent="center" ml="10px" mt="100px"> - <Button onClick={handleOkClick} colorScheme="blue" size="sm"> - Looks Good! - </Button> - </Box> - )} </Box> + </> +); + +}; + +export const MicrobitSinglePixelComponent = ({ from, to, view }: { from: number, to: number, view: EditorView }) => { + const [isVisible, setIsVisible] = useState(true); + const [selectedPixel, setSelectedPixel] = useState<Pixel | null>(null); + + const handleSelectPixel = (pixel: Pixel) => { + setSelectedPixel(pixel); + }; + + const handleSubmit = (x: number, y: number, brightness: number) => { + setIsVisible(false); + view.dispatch({ + changes: { + from: from, + to: to, + insert: (`(${x}, ${y}, ${brightness}) `), + } + }); + }; + + return ( + <MicrobitSinglePixelGrid onClickPixel={handleSelectPixel} onSubmit={handleSubmit} isVisible={isVisible} /> ); }; -export const MicrobitComponent = ({ from, to, view }: { from: number, to: number, view: EditorView }) => { - const [selectedPixel, setSelectedPixel] = useState<Pixel | null>(null); - const [isVisible, setIsVisible] = useState(true); - - const handleSelectPixel = (pixel: Pixel) => { - setSelectedPixel(pixel); - }; - - const handleSubmit = (x: number, y: number, brightness: number) => { - setIsVisible(false); - console.log(`Submitted pixel: (${x}, ${y}) - Brightness: ${brightness}`); - view.dispatch({ - changes: { - from: from, - to: to, - insert: (`(${x}, ${y}, ${brightness}) `), - } - }); - }; - +interface MultiMicrobitGridProps { + selectedPixels: Pixel[]; + onPixelClick: (x: number, y: number) => void; + onBrightnessChange: (x: number, y: number, brightness: number) => void; + onSubmit: () => void; + isVisible: boolean; +} - return ( - <Box> - {isVisible && ( - <MicrobitGrid onClickPixel={handleSelectPixel} onSubmit={handleSubmit} isVisible={isVisible} /> - )} - {selectedPixel && isVisible && ( - <Box mt="4px"> - Selected Pixel: ({selectedPixel.x}, {selectedPixel.y}) - Brightness: {selectedPixel.brightness} +const MicrobitMultiplePixelsGrid: React.FC<MultiMicrobitGridProps> = ({ + selectedPixels, + onPixelClick, + onBrightnessChange, + onSubmit, + isVisible, +}) => { + return ( + <Box display={isVisible ? "flex" : "none"} flexDirection="row" justifyContent="flex-start"> + <Box> + <Box bg="black" p="10px" borderRadius="5px"> + {[...Array(5)].map((_, x) => ( + <Box key={x} display="flex"> + {[...Array(5)].map((_, y) => ( + <Box key={y} display="flex" mr="2px"> + <Button + size="xs" + h="15px" + w="15px" + p={0} + bgColor={selectedPixels.some(p => p.x === x && p.y === y) ? `rgba(255, 0, 0, ${(selectedPixels.find(p => p.x === x && p.y === y)!.brightness) / 9})` : "rgba(255, 255, 255, 0)"} + _hover={{ bgColor: selectedPixels.some(p => p.x === x && p.y === y) ? `rgba(255, 0, 0, ${(selectedPixels.find(p => p.x === x && p.y === y)!.brightness) / 9})` : "rgba(255, 255, 255, 0.5)" }} + onClick={() => onPixelClick(x, y)} + /> + </Box> + ))} </Box> - )} + ))} </Box> - ); + <Box display="flex" justifyContent="center" mt="10px"> + <Box bg="white" borderRadius="5px" p="5px"> + <Button onClick={onSubmit} colorScheme="blue" size="sm"> + Looks Good + </Button> + </Box> + </Box> + </Box> + <Box ml="10px"> + <Slider + aria-label="brightness" + defaultValue={5} + min={0} + max={9} + step={1} + orientation="vertical" + _focus={{ boxShadow: "none" }} + _active={{ bgColor: "transparent" }} + onChange={(value) => onBrightnessChange(selectedPixels[selectedPixels.length - 1].x, selectedPixels[selectedPixels.length - 1].y, value)} + > + <SliderTrack> + <SliderFilledTrack /> + </SliderTrack> + <SliderThumb /> + </Slider> + </Box> + </Box> + ); }; - + +export const MicrobitMultiplePixelComponent = ({ + from, + to, + view, +}: { + from: number; + to: number; + view: EditorView; +}) => { + const initialSelectedPixels: Pixel[] = []; + + for (let x = from; x <= to; x++) { + for (let y = from; y <= to; y++) { + initialSelectedPixels.push({ x, y, brightness: 0 }); + } + } + + const [selectedPixels, setSelectedPixels] = useState<Pixel[]>(initialSelectedPixels); + const [isVisible, setIsVisible] = useState(true); + + const handlePixelClick = (x: number, y: number) => { + const existingIndex = selectedPixels.findIndex(pixel => pixel.x === x && pixel.y === y); + const brightness = selectedPixels[selectedPixels.length - 1]?.brightness ?? 5; + + if (existingIndex !== -1) { + const updatedPixels = [...selectedPixels]; + updatedPixels[existingIndex].brightness = brightness; + setSelectedPixels(updatedPixels); + } else { + const newPixel: Pixel = { x, y, brightness }; + setSelectedPixels([...selectedPixels, newPixel]); + } + }; + + const handleSubmit = () => { + setIsVisible(false); + }; + + const handleBrightnessChange = (x: number, y: number, brightness: number) => { + setSelectedPixels(prevPixels => { + const updatedPixels = [...prevPixels]; + const pixelIndex = updatedPixels.findIndex(pixel => pixel.x === x && pixel.y === y); + if (pixelIndex !== -1) { + updatedPixels[pixelIndex] = { x, y, brightness }; + } + return updatedPixels; + }); + }; + + return ( + <MicrobitMultiplePixelsGrid + selectedPixels={selectedPixels} + onPixelClick={handlePixelClick} + onBrightnessChange={handleBrightnessChange} + onSubmit={handleSubmit} + isVisible={isVisible} + /> + ); +}; \ No newline at end of file diff --git a/src/editor/codemirror/reactWidgetExtension.tsx b/src/editor/codemirror/reactWidgetExtension.tsx index ef26e83bd..f6bd2040f 100644 --- a/src/editor/codemirror/reactWidgetExtension.tsx +++ b/src/editor/codemirror/reactWidgetExtension.tsx @@ -9,8 +9,7 @@ import { import { syntaxTree } from "@codemirror/language" import { useState, useCallback } from "react"; import { PortalFactory } from "./CodeMirror"; -import {MicrobitComponent} from "./microbitWidget"; - +import {MicrobitSinglePixelComponent, MicrobitMultiplePixelComponent} from "./microbitWidget"; /** * An example react component that we use inside a CodeMirror widget as * a proof of concept. @@ -50,7 +49,7 @@ class ToggleWidget extends WidgetType { toDOM(view: EditorView) { const dom = document.createElement("div"); - this.portalCleanup = this.createPortal(dom, <MicrobitComponent from={this.from} to={this.to} view={view} />); + this.portalCleanup = this.createPortal(dom, <MicrobitMultiplePixelComponent from={this.from} to={this.to} view={view} />); return dom; } From 63555dfd130b1eb4ca1c4b9a0859fc3527ec8605 Mon Sep 17 00:00:00 2001 From: Marta Religa <marta.religa@cs.ox.ac.uk> Date: Mon, 18 Mar 2024 21:35:02 +0100 Subject: [PATCH 026/106] kinda works i think t's good enough as a prototype not gonna do much more --- src/editor/codemirror/microbitWidget.tsx | 173 +++++++++++------------ 1 file changed, 82 insertions(+), 91 deletions(-) diff --git a/src/editor/codemirror/microbitWidget.tsx b/src/editor/codemirror/microbitWidget.tsx index 9588579ec..70e89c613 100644 --- a/src/editor/codemirror/microbitWidget.tsx +++ b/src/editor/codemirror/microbitWidget.tsx @@ -12,7 +12,7 @@ interface Pixel { interface MicrobitSinglePixelGridProps { onClickPixel: (pixel: Pixel) => void; - onSubmit: (x:number, y:number, brightness:number) => void; + onSubmit: () => void; isVisible: boolean; } @@ -33,77 +33,70 @@ const MicrobitSinglePixelGrid: React.FC<MicrobitSinglePixelGridProps> = ({ onCli onClickPixel(updatedPixel); } }; - - const handleOkClick = () => { - if (selectedPixel) { - onSubmit(selectedPixel.x, selectedPixel.y, selectedPixel.brightness); - } - }; return ( - <> - {selectedPixel && ( - <Box mt="4px"> - <span style={{ fontSize: "small" }}> - Selected Pixel: ({selectedPixel.x}, {selectedPixel.y}) | Brightness: {selectedPixel.brightness} - </span> - </Box> - )} - <Box display={isVisible ? "flex" : "none"} flexDirection="row" justifyContent="flex-start"> - <Box> - <Box bg="black" p="10px" borderRadius="5px"> - {[...Array(5)].map((_, x) => ( - <Box key={x} display="flex"> - {[...Array(5)].map((_, y) => ( - <Box key={y} display="flex" mr="2px"> - <Button - size="xs" - h="15px" - w="15px" - p={0} - bgColor={selectedPixel?.x === x && selectedPixel.y === y ? `rgba(255, 0, 0, ${brightness / 9})` : "rgba(255, 255, 255, 0)"} - _hover={{ bgColor: selectedPixel?.x === x && selectedPixel.y === y ? `rgba(255, 0, 0, ${brightness / 9})` : "rgba(255, 255, 255, 0.5)" }} - onClick={() => handleClickPixel(x, y)} - /> - </Box> - ))} + <> + <Box display={isVisible ? "flex" : "none"} flexDirection="row" justifyContent="flex-start"> + <Box> + <Box bg="black" p="10px" borderRadius="5px"> + {[...Array(5)].map((_, x) => ( + <Box key={x} display="flex"> + {[...Array(5)].map((_, y) => ( + <Box key={y} display="flex" mr="2px"> + <Button + size="xs" + h="15px" + w="15px" + p={0} + bgColor={selectedPixel?.x === x && selectedPixel.y === y ? `rgba(255, 0, 0, ${brightness / 9})` : "rgba(255, 255, 255, 0)"} + _hover={{ bgColor: selectedPixel?.x === x && selectedPixel.y === y ? `rgba(255, 0, 0, ${brightness / 9})` : "rgba(255, 255, 255, 0.5)" }} + onClick={() => handleClickPixel(x, y)} + /> + </Box> + ))} + </Box> + ))} + </Box> + {selectedPixel && ( + <Box display="flex" flexDirection="column" alignItems="center" mt="10px"> + <Box bg="white" borderRadius="5px" p="5px" textAlign="center"> + <Button onClick={() => onSubmit()} colorScheme="blue" size="sm"> + Looks Good + </Button> + </Box> </Box> - ))} + )} </Box> {selectedPixel && ( - <Box display="flex" flexDirection="column" alignItems="center" mt="10px"> - <Box bg="white" borderRadius="5px" p="5px" textAlign="center"> - <Button onClick={() => onSubmit(selectedPixel?.x ?? 0, selectedPixel?.y ?? 0, selectedPixel?.brightness ?? 0)} colorScheme="blue" size="sm"> - Looks Good - </Button> - </Box> + <Box ml="10px"> + <Slider + aria-label="brightness" + defaultValue={brightness} + min={0} + max={9} + step={1} + orientation="vertical" + _focus={{ boxShadow: "none" }} + _active={{ bgColor: "transparent" }} + onChange={handleSliderChange} + > + <SliderTrack> + <SliderFilledTrack /> + </SliderTrack> + <SliderThumb /> + </Slider> </Box> )} </Box> {selectedPixel && ( - <Box ml="10px"> - <Slider - aria-label="brightness" - defaultValue={brightness} - min={0} - max={9} - step={1} - orientation="vertical" - _focus={{ boxShadow: "none" }} - _active={{ bgColor: "transparent" }} - onChange={handleSliderChange} - > - <SliderTrack> - <SliderFilledTrack /> - </SliderTrack> - <SliderThumb /> - </Slider> + <Box mt="4px"> + <span style={{ fontSize: "small" }}> + Selected Pixel: ({selectedPixel.x}, {selectedPixel.y}) | Brightness: {selectedPixel.brightness} + </span> </Box> )} - </Box> - </> -); - + </> + ); }; export const MicrobitSinglePixelComponent = ({ from, to, view }: { from: number, to: number, view: EditorView }) => { @@ -114,20 +107,21 @@ export const MicrobitSinglePixelComponent = ({ from, to, view }: { from: number, setSelectedPixel(pixel); }; - const handleSubmit = (x: number, y: number, brightness: number) => { - setIsVisible(false); - view.dispatch({ - changes: { - from: from, - to: to, - insert: (`(${x}, ${y}, ${brightness}) `), - } - }); + const handleSubmit = () => { + if (selectedPixel !== null) { + const { x, y, brightness } = selectedPixel; + setIsVisible(false); + view.dispatch({ + changes: { + from: from, + to: to, + insert: `(${x}, ${y}, ${brightness}) `, + } + }); + } }; - return ( - <MicrobitSinglePixelGrid onClickPixel={handleSelectPixel} onSubmit={handleSubmit} isVisible={isVisible} /> - ); + return (<MicrobitSinglePixelGrid onClickPixel={handleSelectPixel} onSubmit={handleSubmit} isVisible={isVisible} />); }; interface MultiMicrobitGridProps { @@ -136,6 +130,7 @@ interface MultiMicrobitGridProps { onBrightnessChange: (x: number, y: number, brightness: number) => void; onSubmit: () => void; isVisible: boolean; + currentBrightness : number; } const MicrobitMultiplePixelsGrid: React.FC<MultiMicrobitGridProps> = ({ @@ -144,6 +139,7 @@ const MicrobitMultiplePixelsGrid: React.FC<MultiMicrobitGridProps> = ({ onBrightnessChange, onSubmit, isVisible, + currentBrightness }) => { return ( <Box display={isVisible ? "flex" : "none"} flexDirection="row" justifyContent="flex-start"> @@ -178,7 +174,7 @@ const MicrobitMultiplePixelsGrid: React.FC<MultiMicrobitGridProps> = ({ <Box ml="10px"> <Slider aria-label="brightness" - defaultValue={5} + value={currentBrightness} min={0} max={9} step={1} @@ -197,50 +193,44 @@ const MicrobitMultiplePixelsGrid: React.FC<MultiMicrobitGridProps> = ({ ); }; -export const MicrobitMultiplePixelComponent = ({ - from, - to, - view, -}: { - from: number; - to: number; - view: EditorView; -}) => { +export const MicrobitMultiplePixelComponent = ({ from, to, view }: { from: number; to: number; view: EditorView; }) => { const initialSelectedPixels: Pixel[] = []; - - for (let x = from; x <= to; x++) { - for (let y = from; y <= to; y++) { + /* + for (let x = 0; x <= 4; x++) { + for (let y = 0; y <= 4; y++) { initialSelectedPixels.push({ x, y, brightness: 0 }); } } + */ const [selectedPixels, setSelectedPixels] = useState<Pixel[]>(initialSelectedPixels); const [isVisible, setIsVisible] = useState(true); + const [currentBrightness, setCurrentBrightness] = useState(5); const handlePixelClick = (x: number, y: number) => { const existingIndex = selectedPixels.findIndex(pixel => pixel.x === x && pixel.y === y); - const brightness = selectedPixels[selectedPixels.length - 1]?.brightness ?? 5; - if (existingIndex !== -1) { const updatedPixels = [...selectedPixels]; - updatedPixels[existingIndex].brightness = brightness; + updatedPixels[existingIndex].brightness = currentBrightness; setSelectedPixels(updatedPixels); } else { - const newPixel: Pixel = { x, y, brightness }; + const newPixel: Pixel = { x, y, brightness: currentBrightness }; setSelectedPixels([...selectedPixels, newPixel]); } }; const handleSubmit = () => { + //add the logic to change the arguments to the function setIsVisible(false); }; const handleBrightnessChange = (x: number, y: number, brightness: number) => { + setCurrentBrightness(brightness); setSelectedPixels(prevPixels => { const updatedPixels = [...prevPixels]; const pixelIndex = updatedPixels.findIndex(pixel => pixel.x === x && pixel.y === y); if (pixelIndex !== -1) { - updatedPixels[pixelIndex] = { x, y, brightness }; + updatedPixels[pixelIndex].brightness = brightness; } return updatedPixels; }); @@ -253,6 +243,7 @@ export const MicrobitMultiplePixelComponent = ({ onBrightnessChange={handleBrightnessChange} onSubmit={handleSubmit} isVisible={isVisible} + currentBrightness={currentBrightness} /> ); }; \ No newline at end of file From 5ebb7b8a3efc2a33668b1ebd073cfaf039e1a50d Mon Sep 17 00:00:00 2001 From: Marta Religa <marta.religa@cs.ox.ac.uk> Date: Mon, 18 Mar 2024 21:35:57 +0100 Subject: [PATCH 027/106] comment --- src/editor/codemirror/microbitWidget.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/editor/codemirror/microbitWidget.tsx b/src/editor/codemirror/microbitWidget.tsx index 70e89c613..263c87214 100644 --- a/src/editor/codemirror/microbitWidget.tsx +++ b/src/editor/codemirror/microbitWidget.tsx @@ -196,6 +196,7 @@ const MicrobitMultiplePixelsGrid: React.FC<MultiMicrobitGridProps> = ({ export const MicrobitMultiplePixelComponent = ({ from, to, view }: { from: number; to: number; view: EditorView; }) => { const initialSelectedPixels: Pixel[] = []; /* + //Probably unnecessary to intialize the state, we can set it to 0 in the arguments by default anyway and it messes up some other logic for (let x = 0; x <= 4; x++) { for (let y = 0; y <= 4; y++) { initialSelectedPixels.push({ x, y, brightness: 0 }); From 287de483a3df289168583a55b6324ceb5656ddb2 Mon Sep 17 00:00:00 2001 From: Marta Religa <marta.religa@cs.ox.ac.uk> Date: Mon, 18 Mar 2024 22:03:00 +0100 Subject: [PATCH 028/106] lint --- src/editor/codemirror/microbitWidget.tsx | 2 +- src/editor/codemirror/reactWidgetExtension.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/editor/codemirror/microbitWidget.tsx b/src/editor/codemirror/microbitWidget.tsx index 263c87214..0b8d9f073 100644 --- a/src/editor/codemirror/microbitWidget.tsx +++ b/src/editor/codemirror/microbitWidget.tsx @@ -1,4 +1,4 @@ -import { Box, Grid, GridItem, Button, Slider, SliderTrack, SliderFilledTrack, SliderThumb } from "@chakra-ui/react"; +import { Box, Button, Slider, SliderTrack, SliderFilledTrack, SliderThumb } from "@chakra-ui/react"; import { useState } from "react"; import { EditorView, diff --git a/src/editor/codemirror/reactWidgetExtension.tsx b/src/editor/codemirror/reactWidgetExtension.tsx index f6bd2040f..0810b62f3 100644 --- a/src/editor/codemirror/reactWidgetExtension.tsx +++ b/src/editor/codemirror/reactWidgetExtension.tsx @@ -7,9 +7,9 @@ import { WidgetType, } from "@codemirror/view"; import { syntaxTree } from "@codemirror/language" -import { useState, useCallback } from "react"; +import { useCallback } from "react"; import { PortalFactory } from "./CodeMirror"; -import {MicrobitSinglePixelComponent, MicrobitMultiplePixelComponent} from "./microbitWidget"; +import { MicrobitMultiplePixelComponent} from "./microbitWidget"; /** * An example react component that we use inside a CodeMirror widget as * a proof of concept. From 161f92eaa9e6081cb4dcf44bbf00acf7ea9629f6 Mon Sep 17 00:00:00 2001 From: Marta Religa <marta.religa@cs.ox.ac.uk> Date: Mon, 18 Mar 2024 22:05:06 +0100 Subject: [PATCH 029/106] to show the single on cloudeflare --- src/editor/codemirror/reactWidgetExtension.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor/codemirror/reactWidgetExtension.tsx b/src/editor/codemirror/reactWidgetExtension.tsx index 0810b62f3..45690f983 100644 --- a/src/editor/codemirror/reactWidgetExtension.tsx +++ b/src/editor/codemirror/reactWidgetExtension.tsx @@ -9,7 +9,7 @@ import { import { syntaxTree } from "@codemirror/language" import { useCallback } from "react"; import { PortalFactory } from "./CodeMirror"; -import { MicrobitMultiplePixelComponent} from "./microbitWidget"; +import {MicrobitSinglePixelComponent, MicrobitMultiplePixelComponent} from "./microbitWidget"; /** * An example react component that we use inside a CodeMirror widget as * a proof of concept. From e12ab3aacaaf2e4bb4f3455bca13e7ac3359ed44 Mon Sep 17 00:00:00 2001 From: Marta Religa <marta.religa@cs.ox.ac.uk> Date: Tue, 19 Mar 2024 07:29:15 +0100 Subject: [PATCH 030/106] ye --- src/editor/codemirror/microbitWidget.tsx | 45 ++++++++++++++++-------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/src/editor/codemirror/microbitWidget.tsx b/src/editor/codemirror/microbitWidget.tsx index 0b8d9f073..920186497 100644 --- a/src/editor/codemirror/microbitWidget.tsx +++ b/src/editor/codemirror/microbitWidget.tsx @@ -39,10 +39,10 @@ const MicrobitSinglePixelGrid: React.FC<MicrobitSinglePixelGridProps> = ({ onCli <Box display={isVisible ? "flex" : "none"} flexDirection="row" justifyContent="flex-start"> <Box> <Box bg="black" p="10px" borderRadius="5px"> - {[...Array(5)].map((_, x) => ( - <Box key={x} display="flex"> - {[...Array(5)].map((_, y) => ( - <Box key={y} display="flex" mr="2px"> + {[...Array(5)].map((_, y) => ( + <Box key={y} display="flex"> + {[...Array(5)].map((_, x) => ( + <Box key={x} display="flex" mr="2px"> <Button size="xs" h="15px" @@ -66,8 +66,16 @@ const MicrobitSinglePixelGrid: React.FC<MicrobitSinglePixelGridProps> = ({ onCli </Box> </Box> )} + {!selectedPixel && ( + <Box display="flex" flexDirection="column" alignItems="center" mt="10px"> + <Box bg="white" borderRadius="5px" p="5px" textAlign="center"> + <Button disabled colorScheme="blue" size="sm"> + Looks Good + </Button> + </Box> + </Box> + )} </Box> - {selectedPixel && ( <Box ml="10px"> <Slider aria-label="brightness" @@ -78,20 +86,25 @@ const MicrobitSinglePixelGrid: React.FC<MicrobitSinglePixelGridProps> = ({ onCli orientation="vertical" _focus={{ boxShadow: "none" }} _active={{ bgColor: "transparent" }} - onChange={handleSliderChange} - > + onChange={handleSliderChange}> <SliderTrack> <SliderFilledTrack /> </SliderTrack> <SliderThumb /> </Slider> </Box> - )} </Box> {selectedPixel && ( <Box mt="4px"> <span style={{ fontSize: "small" }}> - Selected Pixel: ({selectedPixel.x}, {selectedPixel.y}) | Brightness: {selectedPixel.brightness} + Selected pixel: ({selectedPixel.x}, {selectedPixel.y}) | Brightness: {selectedPixel.brightness} + </span> + </Box> + )} + {!selectedPixel && ( + <Box mt="4px"> + <span style={{ fontSize: "small" }}> + Select a pixel </span> </Box> )} @@ -145,10 +158,10 @@ const MicrobitMultiplePixelsGrid: React.FC<MultiMicrobitGridProps> = ({ <Box display={isVisible ? "flex" : "none"} flexDirection="row" justifyContent="flex-start"> <Box> <Box bg="black" p="10px" borderRadius="5px"> - {[...Array(5)].map((_, x) => ( - <Box key={x} display="flex"> - {[...Array(5)].map((_, y) => ( - <Box key={y} display="flex" mr="2px"> + {[...Array(5)].map((_, y) => ( + <Box key={y} display="flex"> + {[...Array(5)].map((_, x) => ( + <Box key={x} display="flex" mr="2px"> <Button size="xs" h="15px" @@ -181,8 +194,10 @@ const MicrobitMultiplePixelsGrid: React.FC<MultiMicrobitGridProps> = ({ orientation="vertical" _focus={{ boxShadow: "none" }} _active={{ bgColor: "transparent" }} - onChange={(value) => onBrightnessChange(selectedPixels[selectedPixels.length - 1].x, selectedPixels[selectedPixels.length - 1].y, value)} - > + onChange={(value) => { + const lastPixel = selectedPixels.length > 0 ? selectedPixels[selectedPixels.length - 1] : { x: -1, y: -1 }; + onBrightnessChange(lastPixel.x, lastPixel.y, value); + }} > <SliderTrack> <SliderFilledTrack /> </SliderTrack> From d1f4e7171f19ee7918e5e870ffe1804609ebc343 Mon Sep 17 00:00:00 2001 From: Marta Religa <marta.religa@cs.ox.ac.uk> Date: Tue, 19 Mar 2024 12:24:52 +0100 Subject: [PATCH 031/106] . --- .../codemirror/reactWidgetExtension.tsx | 30 ++----------------- 1 file changed, 3 insertions(+), 27 deletions(-) diff --git a/src/editor/codemirror/reactWidgetExtension.tsx b/src/editor/codemirror/reactWidgetExtension.tsx index 45690f983..669fb0e14 100644 --- a/src/editor/codemirror/reactWidgetExtension.tsx +++ b/src/editor/codemirror/reactWidgetExtension.tsx @@ -7,33 +7,9 @@ import { WidgetType, } from "@codemirror/view"; import { syntaxTree } from "@codemirror/language" -import { useCallback } from "react"; import { PortalFactory } from "./CodeMirror"; -import {MicrobitSinglePixelComponent, MicrobitMultiplePixelComponent} from "./microbitWidget"; -/** - * An example react component that we use inside a CodeMirror widget as - * a proof of concept. - */ - -const ToggleReactComponent = ({ from, to, view }: { from: number, to: number, view: EditorView }) => { - let curVal = view.state.doc.sliceString(from, to); - const handleClick = useCallback(() => { - let opposite = curVal === "True" ? "False" : "True"; - view.dispatch({ - changes: { - from: from, - to: to, - insert: opposite, - } - }); - }, [curVal, from, to, view]); - return ( - <HStack fontFamily="body" spacing={5} py={3}> - <Button onClick={handleClick}>Toggle</Button> - <Text fontWeight="semibold">Value: {curVal}</Text> - </HStack> - ); -}; +import {MicrobitSinglePixelComponent} from "./microbitWidget"; +//MicrobitMultiplePixelComponent /** * This widget will have its contents rendered by the code in CodeMirror.tsx @@ -49,7 +25,7 @@ class ToggleWidget extends WidgetType { toDOM(view: EditorView) { const dom = document.createElement("div"); - this.portalCleanup = this.createPortal(dom, <MicrobitMultiplePixelComponent from={this.from} to={this.to} view={view} />); + this.portalCleanup = this.createPortal(dom, <MicrobitSinglePixelComponent from={this.from} to={this.to} view={view} />); return dom; } From 67e51f85e23600b28a1b076de43e6c30d867b74a Mon Sep 17 00:00:00 2001 From: Marta Religa <marta.religa@cs.ox.ac.uk> Date: Tue, 19 Mar 2024 12:41:36 +0100 Subject: [PATCH 032/106] . --- src/editor/codemirror/reactWidgetExtension.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/editor/codemirror/reactWidgetExtension.tsx b/src/editor/codemirror/reactWidgetExtension.tsx index 669fb0e14..ca01ac3bb 100644 --- a/src/editor/codemirror/reactWidgetExtension.tsx +++ b/src/editor/codemirror/reactWidgetExtension.tsx @@ -1,4 +1,3 @@ -import { Button, HStack, Text } from "@chakra-ui/react"; import { EditorState, Extension, StateField } from "@codemirror/state"; import { Decoration, From e590d039694e8f754d0ef45297d546fb7d557584 Mon Sep 17 00:00:00 2001 From: Marta Religa <marta.religa@cs.ox.ac.uk> Date: Tue, 19 Mar 2024 15:05:10 +0100 Subject: [PATCH 033/106] . --- src/editor/codemirror/reactWidgetExtension.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/editor/codemirror/reactWidgetExtension.tsx b/src/editor/codemirror/reactWidgetExtension.tsx index ca01ac3bb..30e1147a6 100644 --- a/src/editor/codemirror/reactWidgetExtension.tsx +++ b/src/editor/codemirror/reactWidgetExtension.tsx @@ -7,7 +7,7 @@ import { } from "@codemirror/view"; import { syntaxTree } from "@codemirror/language" import { PortalFactory } from "./CodeMirror"; -import {MicrobitSinglePixelComponent} from "./microbitWidget"; +import {MicrobitMultiplePixelComponent} from "./microbitWidget"; //MicrobitMultiplePixelComponent /** @@ -24,7 +24,7 @@ class ToggleWidget extends WidgetType { toDOM(view: EditorView) { const dom = document.createElement("div"); - this.portalCleanup = this.createPortal(dom, <MicrobitSinglePixelComponent from={this.from} to={this.to} view={view} />); + this.portalCleanup = this.createPortal(dom, <MicrobitMultiplePixelComponent from={this.from} to={this.to} view={view} />); return dom; } From 19b583009f2b63ee8438bf042ba7fd7d394ac428 Mon Sep 17 00:00:00 2001 From: Marta Religa <marta.religa@cs.ox.ac.uk> Date: Tue, 19 Mar 2024 15:53:05 +0100 Subject: [PATCH 034/106] interfac --- src/editor/codemirror/reactWidgetExtension.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/editor/codemirror/reactWidgetExtension.tsx b/src/editor/codemirror/reactWidgetExtension.tsx index 30e1147a6..815acac18 100644 --- a/src/editor/codemirror/reactWidgetExtension.tsx +++ b/src/editor/codemirror/reactWidgetExtension.tsx @@ -49,6 +49,14 @@ function createWidget(from: number, to: number, createPortal: PortalFactory): De return deco; } + +interface WidgetProps{ + from : number, + to : number, + view : EditorView, + arguments : any +} + // Iterates through the syntax tree, finding occurences of SoundEffect ArgList, and places toy widget there export const reactWidgetExtension = ( createPortal: PortalFactory From 6aade46352b9e3cca9608662faff5a9b34d64d93 Mon Sep 17 00:00:00 2001 From: Marta Religa <marta.religa@cs.ox.ac.uk> Date: Tue, 19 Mar 2024 16:05:21 +0100 Subject: [PATCH 035/106] . --- src/editor/codemirror/reactWidgetExtension.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/editor/codemirror/reactWidgetExtension.tsx b/src/editor/codemirror/reactWidgetExtension.tsx index 815acac18..d1923761e 100644 --- a/src/editor/codemirror/reactWidgetExtension.tsx +++ b/src/editor/codemirror/reactWidgetExtension.tsx @@ -8,6 +8,7 @@ import { import { syntaxTree } from "@codemirror/language" import { PortalFactory } from "./CodeMirror"; import {MicrobitMultiplePixelComponent} from "./microbitWidget"; +import React from "react"; //MicrobitMultiplePixelComponent /** @@ -17,14 +18,13 @@ import {MicrobitMultiplePixelComponent} from "./microbitWidget"; class ToggleWidget extends WidgetType { private portalCleanup: (() => void) | undefined; - constructor(private from: number, private to: number, private createPortal: PortalFactory) { + constructor(private from: number, private to: number, private createPortal: PortalFactory, private component : React.ComponentType<any>) { super(); } toDOM(view: EditorView) { const dom = document.createElement("div"); - - this.portalCleanup = this.createPortal(dom, <MicrobitMultiplePixelComponent from={this.from} to={this.to} view={view} />); + this.portalCleanup = this.createPortal(dom, React.createElement(this.component, { from: this.from, to: this.to, view: view })); return dom; } @@ -41,7 +41,7 @@ class ToggleWidget extends WidgetType { function createWidget(from: number, to: number, createPortal: PortalFactory): Decoration { let deco = Decoration.widget({ - widget: new ToggleWidget(from, to, createPortal), + widget: new ToggleWidget(from, to, createPortal, MicrobitMultiplePixelComponent), //ToggleWidget(from, to, createPortal), side: 1, }); @@ -49,12 +49,11 @@ function createWidget(from: number, to: number, createPortal: PortalFactory): De return deco; } - interface WidgetProps{ from : number, to : number, view : EditorView, - arguments : any + arguments : any } // Iterates through the syntax tree, finding occurences of SoundEffect ArgList, and places toy widget there From b58ec2c8a1b02dc86fcef62d6bc3ddce4a2ea684 Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Wed, 20 Mar 2024 11:56:38 -0400 Subject: [PATCH 036/106] better property passing to widgets --- .../codemirror/reactWidgetExtension.tsx | 41 ++++++++++++------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/src/editor/codemirror/reactWidgetExtension.tsx b/src/editor/codemirror/reactWidgetExtension.tsx index d1923761e..c4760686e 100644 --- a/src/editor/codemirror/reactWidgetExtension.tsx +++ b/src/editor/codemirror/reactWidgetExtension.tsx @@ -11,20 +11,26 @@ import {MicrobitMultiplePixelComponent} from "./microbitWidget"; import React from "react"; //MicrobitMultiplePixelComponent +interface WidgetProps<T>{ + from : number, + to : number, + arguments : T[] +} + /** * This widget will have its contents rendered by the code in CodeMirror.tsx * which it communicates with via the portal factory. */ -class ToggleWidget extends WidgetType { +class Widget<T> extends WidgetType { private portalCleanup: (() => void) | undefined; - constructor(private from: number, private to: number, private createPortal: PortalFactory, private component : React.ComponentType<any>) { + constructor(private component : React.ComponentType<any>, private props: WidgetProps<T>, private createPortal: PortalFactory, ) { super(); } toDOM(view: EditorView) { const dom = document.createElement("div"); - this.portalCleanup = this.createPortal(dom, React.createElement(this.component, { from: this.from, to: this.to, view: view })); + this.portalCleanup = this.createPortal(dom, React.createElement(this.component, { props: this.props, view: view })); return dom; } @@ -39,23 +45,20 @@ class ToggleWidget extends WidgetType { } } -function createWidget(from: number, to: number, createPortal: PortalFactory): Decoration { +function createWidget<T>(comp: React.ComponentType<any>, from: number, to: number, args: T[], createPortal: PortalFactory): Decoration { + let props = { + from : from, + to : to, + arguments : args + } let deco = Decoration.widget({ - widget: new ToggleWidget(from, to, createPortal, MicrobitMultiplePixelComponent), - //ToggleWidget(from, to, createPortal), + widget: new Widget(comp, props, createPortal), side: 1, }); return deco; } -interface WidgetProps{ - from : number, - to : number, - view : EditorView, - arguments : any -} - // Iterates through the syntax tree, finding occurences of SoundEffect ArgList, and places toy widget there export const reactWidgetExtension = ( createPortal: PortalFactory @@ -75,10 +78,18 @@ export const reactWidgetExtension = ( //if(node.name === "Boolean") widgets.push(createWidget(node.from, node.to, createPortal).range(node.to)); // Found ArgList, will begin to parse nodes - if(setpix && node.name === "ArgList") widgets.push(createWidget(node.from, node.to, createPortal).range(node.to)); - + if(setpix && node.name === "ArgList") { + widgets.push(createWidget<number>( + MicrobitMultiplePixelComponent, + node.from, node.to, + [1, 2, 3], + createPortal).range(node.to)); + } // detected SoundEffect, if next expression is an ArgList, show UI setpix = node.name === "PropertyName" && state.doc.sliceString(node.from, node.to) === "set_pixel" + if(setpix) { + + } } }) From 64a876b7bad7363fb371b822c62bddf2ad76ad20 Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Wed, 20 Mar 2024 12:16:35 -0400 Subject: [PATCH 037/106] organized into subdir helper-widgets --- src/editor/codemirror/CodeMirror.tsx | 2 +- .../helper-widgets/argumentParser.tsx | 10 +++++ .../{ => helper-widgets}/microbitWidget.tsx | 0 .../reactWidgetExtension.tsx | 42 +++++++++++-------- 4 files changed, 36 insertions(+), 18 deletions(-) create mode 100644 src/editor/codemirror/helper-widgets/argumentParser.tsx rename src/editor/codemirror/{ => helper-widgets}/microbitWidget.tsx (100%) rename src/editor/codemirror/{ => helper-widgets}/reactWidgetExtension.tsx (69%) diff --git a/src/editor/codemirror/CodeMirror.tsx b/src/editor/codemirror/CodeMirror.tsx index 32c1d87d6..7173852e7 100644 --- a/src/editor/codemirror/CodeMirror.tsx +++ b/src/editor/codemirror/CodeMirror.tsx @@ -48,7 +48,7 @@ import { languageServer } from "./language-server/view"; import { lintGutter } from "./lint/lint"; import { codeStructure } from "./structure-highlighting"; import themeExtensions from "./themeExtensions"; -import { reactWidgetExtension } from "./reactWidgetExtension"; +import { reactWidgetExtension } from "./helper-widgets/reactWidgetExtension"; interface CodeMirrorProps { className?: string; diff --git a/src/editor/codemirror/helper-widgets/argumentParser.tsx b/src/editor/codemirror/helper-widgets/argumentParser.tsx new file mode 100644 index 000000000..f5cba3157 --- /dev/null +++ b/src/editor/codemirror/helper-widgets/argumentParser.tsx @@ -0,0 +1,10 @@ +import { EditorState } from "@codemirror/state"; + +// Pre: args are all of type number +export function numberArgs(state: EditorState, args: any[]): number[] { + let nums = [] + args.forEach(function (value) { + + }); + return [] +} \ No newline at end of file diff --git a/src/editor/codemirror/microbitWidget.tsx b/src/editor/codemirror/helper-widgets/microbitWidget.tsx similarity index 100% rename from src/editor/codemirror/microbitWidget.tsx rename to src/editor/codemirror/helper-widgets/microbitWidget.tsx diff --git a/src/editor/codemirror/reactWidgetExtension.tsx b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx similarity index 69% rename from src/editor/codemirror/reactWidgetExtension.tsx rename to src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx index c4760686e..eb98d11e1 100644 --- a/src/editor/codemirror/reactWidgetExtension.tsx +++ b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx @@ -6,10 +6,10 @@ import { WidgetType, } from "@codemirror/view"; import { syntaxTree } from "@codemirror/language" -import { PortalFactory } from "./CodeMirror"; -import {MicrobitMultiplePixelComponent} from "./microbitWidget"; +import { PortalFactory } from "../CodeMirror"; import React from "react"; -//MicrobitMultiplePixelComponent +import {MicrobitMultiplePixelComponent, MicrobitSinglePixelComponent} from "./microbitWidget"; +import { numberArgs } from "./argumentParser"; interface WidgetProps<T>{ from : number, @@ -70,26 +70,34 @@ export const reactWidgetExtension = ( //let t = state.doc.toString() //console.log(t); let setpix = false; - + let image = false; syntaxTree(state).iterate({ from, to, - enter: (node: any) => { // TODO: type is SyntaxNode? - //console.log(); - //if(node.name === "Boolean") widgets.push(createWidget(node.from, node.to, createPortal).range(node.to)); + enter: (ref) => { + //console.log(ref.name); // Found ArgList, will begin to parse nodes - if(setpix && node.name === "ArgList") { - widgets.push(createWidget<number>( - MicrobitMultiplePixelComponent, - node.from, node.to, - [1, 2, 3], - createPortal).range(node.to)); - } - // detected SoundEffect, if next expression is an ArgList, show UI - setpix = node.name === "PropertyName" && state.doc.sliceString(node.from, node.to) === "set_pixel" - if(setpix) { + if(setpix && ref.name === "ArgList") { + let cs = ref.node.getChildren("Number"); + if(cs.length === 3) { + widgets.push(createWidget<number>( + MicrobitSinglePixelComponent, + ref.from, ref.to, + numberArgs(state, cs), + createPortal).range(ref.to)); + } + } + if(image && ref.name === "ArgList"){ + let s = ref.node.getChild("ContinuedString"); } + + // detected set_pixel, if next expression is an ArgList, show UI + setpix = ref.name === "PropertyName" && state.doc.sliceString(ref.from, ref.to) === "set_pixel" + if(setpix){ + console.log(ref.node.nextSibling); + } + image = ref.name === "VariableName" && state.doc.sliceString(ref.from, ref.to) === "Image" } }) From 31fddfca119c6c813e4ce2710b304d2eef313126 Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Fri, 22 Mar 2024 22:44:28 -0400 Subject: [PATCH 038/106] better argument passing and syntax tree detection --- .../helper-widgets/argumentParser.tsx | 3 + .../helper-widgets/reactWidgetExtension.tsx | 90 ++++++++++--------- 2 files changed, 50 insertions(+), 43 deletions(-) diff --git a/src/editor/codemirror/helper-widgets/argumentParser.tsx b/src/editor/codemirror/helper-widgets/argumentParser.tsx index f5cba3157..fa5bf1e42 100644 --- a/src/editor/codemirror/helper-widgets/argumentParser.tsx +++ b/src/editor/codemirror/helper-widgets/argumentParser.tsx @@ -1,5 +1,8 @@ import { EditorState } from "@codemirror/state"; +// TODO: might move parsing to here once arguments are no longer literals +// see index.d.ts + // Pre: args are all of type number export function numberArgs(state: EditorState, args: any[]): number[] { let nums = [] diff --git a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx index eb98d11e1..e50928a3c 100644 --- a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx +++ b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx @@ -12,9 +12,11 @@ import {MicrobitMultiplePixelComponent, MicrobitSinglePixelComponent} from "./mi import { numberArgs } from "./argumentParser"; interface WidgetProps<T>{ + // Where to insert the changed values from : number, to : number, - arguments : T[] + // Note: always an array, can be singleton + arguments : T[] } /** @@ -45,59 +47,61 @@ class Widget<T> extends WidgetType { } } -function createWidget<T>(comp: React.ComponentType<any>, from: number, to: number, args: T[], createPortal: PortalFactory): Decoration { - let props = { - from : from, - to : to, - arguments : args - } - let deco = Decoration.widget({ - widget: new Widget(comp, props, createPortal), - side: 1, - }); - - return deco; -} - // Iterates through the syntax tree, finding occurences of SoundEffect ArgList, and places toy widget there export const reactWidgetExtension = ( createPortal: PortalFactory ): Extension => { const decorate = (state: EditorState) => { let widgets: any[] = [] - let from = 0 - let to = state.doc.length-1 // TODO: could optimize this to just be lines within view - //let t = state.doc.toString() - //console.log(t); - let setpix = false; - let image = false; + // Creates a widget which accepts arguments of type T + function createWidget<T>(comp: React.ComponentType<any>, from: number, to: number, args: T[]) { + args.forEach(function(value) { console.log(value); }) + + let props = { + from: from, + to: to, + arguments: args + } + let deco = Decoration.widget({ + widget: new Widget(comp, props, createPortal), + side: 1, + }); + + widgets.push(deco.range(to)); + } + syntaxTree(state).iterate({ - from, to, enter: (ref) => { - //console.log(ref.name); - - // Found ArgList, will begin to parse nodes - if(setpix && ref.name === "ArgList") { - let cs = ref.node.getChildren("Number"); - if(cs.length === 3) { - widgets.push(createWidget<number>( - MicrobitSinglePixelComponent, - ref.from, ref.to, - numberArgs(state, cs), - createPortal).range(ref.to)); - } - } - if(image && ref.name === "ArgList"){ - let s = ref.node.getChild("ContinuedString"); + // Found an ArgList, parent will be a CallExpression + if(ref.name === "ArgList" && ref.node.parent){ + //console.log(state.doc.sliceString(ref.node.parent.from, ref.from)); - } + // Match CallExpression name to our widgets + switch(state.doc.sliceString(ref.node.parent.from, ref.from)){ + case "display.set_pixel": + // TODO: assuming all literals for now, will probably want a way to detect other types of arguments + let args: number[] = []; + ref.node.getChildren("Number").forEach( function(child) { args.push(+state.doc.sliceString(child.from, child.to)) }); - // detected set_pixel, if next expression is an ArgList, show UI - setpix = ref.name === "PropertyName" && state.doc.sliceString(ref.from, ref.to) === "set_pixel" - if(setpix){ - console.log(ref.node.nextSibling); + createWidget<number>(MicrobitSinglePixelComponent, ref.from, ref.to, args); + break; + case "Image": + // TODO: does not handle comments properly + let imArg: string[] = [] + let arg = ref.node.getChild("ContinuedString"); + if(arg) imArg.push(state.doc.sliceString(arg.from, arg.to).replaceAll(/[' \n]/g, "")); + else{ + arg = ref.node.getChild("String"); + if(arg) imArg.push() + } + + createWidget<string>(MicrobitMultiplePixelComponent, ref.from, ref.to, imArg); + break; + default: + // No widget implemented for this function + break; + } } - image = ref.name === "VariableName" && state.doc.sliceString(ref.from, ref.to) === "Image" } }) From f1d3522f099999fc4c5e08c6bd97c83736cd34de Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Sat, 23 Mar 2024 12:36:16 -0400 Subject: [PATCH 039/106] moved view into props --- .../helper-widgets/reactWidgetExtension.tsx | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx index e50928a3c..4e643dd10 100644 --- a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx +++ b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx @@ -12,11 +12,13 @@ import {MicrobitMultiplePixelComponent, MicrobitSinglePixelComponent} from "./mi import { numberArgs } from "./argumentParser"; interface WidgetProps<T>{ + // Note: always an array, can be singleton + arguments : T[] // Where to insert the changed values from : number, to : number, - // Note: always an array, can be singleton - arguments : T[] + // Widget will change textfile + view: EditorView } /** @@ -26,13 +28,22 @@ interface WidgetProps<T>{ class Widget<T> extends WidgetType { private portalCleanup: (() => void) | undefined; - constructor(private component : React.ComponentType<any>, private props: WidgetProps<T>, private createPortal: PortalFactory, ) { + constructor(private component : React.ComponentType<any>, + private args: T[], private from: number, private to: number, + private createPortal: PortalFactory, ) { super(); } toDOM(view: EditorView) { const dom = document.createElement("div"); - this.portalCleanup = this.createPortal(dom, React.createElement(this.component, { props: this.props, view: view })); + let props = { + arguments: this.args, + from: this.from, + to: this.to, + view: view + } + + this.portalCleanup = this.createPortal(dom, React.createElement(this.component, { props: props })); return dom; } @@ -54,16 +65,11 @@ export const reactWidgetExtension = ( const decorate = (state: EditorState) => { let widgets: any[] = [] // Creates a widget which accepts arguments of type T - function createWidget<T>(comp: React.ComponentType<any>, from: number, to: number, args: T[]) { + function createWidget<T>(comp: React.ComponentType<any>, args: T[], from: number, to: number) { args.forEach(function(value) { console.log(value); }) - - let props = { - from: from, - to: to, - arguments: args - } + let deco = Decoration.widget({ - widget: new Widget(comp, props, createPortal), + widget: new Widget(comp, args, to, from, createPortal), side: 1, }); @@ -83,7 +89,7 @@ export const reactWidgetExtension = ( let args: number[] = []; ref.node.getChildren("Number").forEach( function(child) { args.push(+state.doc.sliceString(child.from, child.to)) }); - createWidget<number>(MicrobitSinglePixelComponent, ref.from, ref.to, args); + createWidget<number>(MicrobitSinglePixelComponent, args, ref.from, ref.to); break; case "Image": // TODO: does not handle comments properly @@ -95,7 +101,7 @@ export const reactWidgetExtension = ( if(arg) imArg.push() } - createWidget<string>(MicrobitMultiplePixelComponent, ref.from, ref.to, imArg); + createWidget<string>(MicrobitMultiplePixelComponent, imArg, ref.from, ref.to); break; default: // No widget implemented for this function From ff395ddfb59fe18da764bc80f56a3977789a145c Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Sat, 23 Mar 2024 12:37:40 -0400 Subject: [PATCH 040/106] fixed generic createWidget --- src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx index 4e643dd10..8b5ad32e2 100644 --- a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx +++ b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx @@ -69,7 +69,7 @@ export const reactWidgetExtension = ( args.forEach(function(value) { console.log(value); }) let deco = Decoration.widget({ - widget: new Widget(comp, args, to, from, createPortal), + widget: new Widget<T>(comp, args, to, from, createPortal), side: 1, }); From a7f544d6abaf330cc7aad547d411d01d634784a7 Mon Sep 17 00:00:00 2001 From: Marta Religa <marta.religa@cs.ox.ac.uk> Date: Sat, 23 Mar 2024 21:07:39 +0100 Subject: [PATCH 041/106] changed look of pixels and name in interace, need to pass in view from reactwidgetextension --- .../helper-widgets/microbitWidget.tsx | 169 ++++++------------ .../helper-widgets/reactWidgetExtension.tsx | 4 +- 2 files changed, 58 insertions(+), 115 deletions(-) diff --git a/src/editor/codemirror/helper-widgets/microbitWidget.tsx b/src/editor/codemirror/helper-widgets/microbitWidget.tsx index 920186497..de2ca352f 100644 --- a/src/editor/codemirror/helper-widgets/microbitWidget.tsx +++ b/src/editor/codemirror/helper-widgets/microbitWidget.tsx @@ -1,8 +1,6 @@ import { Box, Button, Slider, SliderTrack, SliderFilledTrack, SliderThumb } from "@chakra-ui/react"; import { useState } from "react"; -import { - EditorView, - } from "@codemirror/view"; +import {WidgetProps} from "./reactWidgetExtension"; interface Pixel { x: number; @@ -12,11 +10,9 @@ interface Pixel { interface MicrobitSinglePixelGridProps { onClickPixel: (pixel: Pixel) => void; - onSubmit: () => void; - isVisible: boolean; } -const MicrobitSinglePixelGrid: React.FC<MicrobitSinglePixelGridProps> = ({ onClickPixel, onSubmit, isVisible }) => { +const MicrobitSinglePixelGrid: React.FC<MicrobitSinglePixelGridProps> = ({ onClickPixel }) => { const [selectedPixel, setSelectedPixel] = useState<Pixel | null>(null); const [brightness, setBrightness] = useState<number>(5); @@ -33,97 +29,62 @@ const MicrobitSinglePixelGrid: React.FC<MicrobitSinglePixelGridProps> = ({ onCli onClickPixel(updatedPixel); } }; - + return ( - <> - <Box display={isVisible ? "flex" : "none"} flexDirection="row" justifyContent="flex-start"> - <Box> - <Box bg="black" p="10px" borderRadius="5px"> - {[...Array(5)].map((_, y) => ( - <Box key={y} display="flex"> - {[...Array(5)].map((_, x) => ( - <Box key={x} display="flex" mr="2px"> - <Button - size="xs" - h="15px" - w="15px" - p={0} - bgColor={selectedPixel?.x === x && selectedPixel.y === y ? `rgba(255, 0, 0, ${brightness / 9})` : "rgba(255, 255, 255, 0)"} - _hover={{ bgColor: selectedPixel?.x === x && selectedPixel.y === y ? `rgba(255, 0, 0, ${brightness / 9})` : "rgba(255, 255, 255, 0.5)" }} - onClick={() => handleClickPixel(x, y)} - /> - </Box> - ))} - </Box> - ))} - </Box> - {selectedPixel && ( - <Box display="flex" flexDirection="column" alignItems="center" mt="10px"> - <Box bg="white" borderRadius="5px" p="5px" textAlign="center"> - <Button onClick={() => onSubmit()} colorScheme="blue" size="sm"> - Looks Good - </Button> - </Box> - </Box> - )} - {!selectedPixel && ( - <Box display="flex" flexDirection="column" alignItems="center" mt="10px"> - <Box bg="white" borderRadius="5px" p="5px" textAlign="center"> - <Button disabled colorScheme="blue" size="sm"> - Looks Good - </Button> - </Box> + <Box display="flex" flexDirection="row" justifyContent="flex-start"> + <Box> + <Box bg="black" p="10px" borderRadius="5px"> + {[...Array(5)].map((_, y) => ( + <Box key={y} display="flex"> + {[...Array(5)].map((_, x) => ( + <Box key={x} display="flex" mr="2px"> + <Button + size="xs" + h="15px" + w="15px" + p={0} + bgColor={selectedPixel?.x === x && selectedPixel.y === y ? `rgba(255, 0, 0, ${brightness / 9})` : "rgba(255, 255, 255, 0)"} + _hover={{ bgColor: selectedPixel?.x === x && selectedPixel.y === y ? `rgba(255, 0, 0, ${brightness / 9})` : "rgba(255, 255, 255, 0.5)" }} + onClick={() => handleClickPixel(x, y)} + /> + </Box> + ))} </Box> - )} + ))} </Box> - <Box ml="10px"> - <Slider - aria-label="brightness" - defaultValue={brightness} - min={0} - max={9} - step={1} - orientation="vertical" - _focus={{ boxShadow: "none" }} - _active={{ bgColor: "transparent" }} - onChange={handleSliderChange}> - <SliderTrack> - <SliderFilledTrack /> - </SliderTrack> - <SliderThumb /> - </Slider> - </Box> </Box> - {selectedPixel && ( - <Box mt="4px"> - <span style={{ fontSize: "small" }}> - Selected pixel: ({selectedPixel.x}, {selectedPixel.y}) | Brightness: {selectedPixel.brightness} - </span> - </Box> - )} - {!selectedPixel && ( - <Box mt="4px"> - <span style={{ fontSize: "small" }}> - Select a pixel - </span> - </Box> - )} - </> + <Box ml="10px"> + <Slider + aria-label="brightness" + defaultValue={brightness} + min={0} + max={9} + step={1} + orientation="vertical" + _focus={{ boxShadow: "none" }} + _active={{ bgColor: "transparent" }} + onChange={handleSliderChange}> + <SliderTrack> + <SliderFilledTrack /> + </SliderTrack> + <SliderThumb /> + </Slider> + </Box> + </Box> ); }; -export const MicrobitSinglePixelComponent = ({ from, to, view }: { from: number, to: number, view: EditorView }) => { - const [isVisible, setIsVisible] = useState(true); +export const MicrobitSinglePixelComponent = ({ args, from, to, view }: WidgetProps<number>) => { const [selectedPixel, setSelectedPixel] = useState<Pixel | null>(null); + if (Array.isArray(args) && args.length === 3) { + const [x, y, brightness] = args; + setSelectedPixel({ x, y, brightness }); + } const handleSelectPixel = (pixel: Pixel) => { setSelectedPixel(pixel); - }; - - const handleSubmit = () => { if (selectedPixel !== null) { const { x, y, brightness } = selectedPixel; - setIsVisible(false); view.dispatch({ changes: { from: from, @@ -134,28 +95,26 @@ export const MicrobitSinglePixelComponent = ({ from, to, view }: { from: number, } }; - return (<MicrobitSinglePixelGrid onClickPixel={handleSelectPixel} onSubmit={handleSubmit} isVisible={isVisible} />); + return (<MicrobitSinglePixelGrid onClickPixel={handleSelectPixel} />); }; + interface MultiMicrobitGridProps { selectedPixels: Pixel[]; onPixelClick: (x: number, y: number) => void; onBrightnessChange: (x: number, y: number, brightness: number) => void; onSubmit: () => void; - isVisible: boolean; - currentBrightness : number; + currentBrightness: number; } const MicrobitMultiplePixelsGrid: React.FC<MultiMicrobitGridProps> = ({ selectedPixels, onPixelClick, onBrightnessChange, - onSubmit, - isVisible, currentBrightness }) => { return ( - <Box display={isVisible ? "flex" : "none"} flexDirection="row" justifyContent="flex-start"> + <Box display="flex" flexDirection="row" justifyContent="flex-start"> <Box> <Box bg="black" p="10px" borderRadius="5px"> {[...Array(5)].map((_, y) => ( @@ -176,13 +135,6 @@ const MicrobitMultiplePixelsGrid: React.FC<MultiMicrobitGridProps> = ({ </Box> ))} </Box> - <Box display="flex" justifyContent="center" mt="10px"> - <Box bg="white" borderRadius="5px" p="5px"> - <Button onClick={onSubmit} colorScheme="blue" size="sm"> - Looks Good - </Button> - </Box> - </Box> </Box> <Box ml="10px"> <Slider @@ -208,19 +160,10 @@ const MicrobitMultiplePixelsGrid: React.FC<MultiMicrobitGridProps> = ({ ); }; -export const MicrobitMultiplePixelComponent = ({ from, to, view }: { from: number; to: number; view: EditorView; }) => { +export const MicrobitMultiplePixelComponent = ({args, from, to, view }: WidgetProps<number>) => { const initialSelectedPixels: Pixel[] = []; - /* - //Probably unnecessary to intialize the state, we can set it to 0 in the arguments by default anyway and it messes up some other logic - for (let x = 0; x <= 4; x++) { - for (let y = 0; y <= 4; y++) { - initialSelectedPixels.push({ x, y, brightness: 0 }); - } - } - */ const [selectedPixels, setSelectedPixels] = useState<Pixel[]>(initialSelectedPixels); - const [isVisible, setIsVisible] = useState(true); const [currentBrightness, setCurrentBrightness] = useState(5); const handlePixelClick = (x: number, y: number) => { @@ -233,11 +176,7 @@ export const MicrobitMultiplePixelComponent = ({ from, to, view }: { from: numbe const newPixel: Pixel = { x, y, brightness: currentBrightness }; setSelectedPixels([...selectedPixels, newPixel]); } - }; - - const handleSubmit = () => { - //add the logic to change the arguments to the function - setIsVisible(false); + handleSubmit(); }; const handleBrightnessChange = (x: number, y: number, brightness: number) => { @@ -250,6 +189,11 @@ export const MicrobitMultiplePixelComponent = ({ from, to, view }: { from: numbe } return updatedPixels; }); + handleSubmit(); + }; + + const handleSubmit = () => { + console.log("Submitting..."); }; return ( @@ -258,7 +202,6 @@ export const MicrobitMultiplePixelComponent = ({ from, to, view }: { from: numbe onPixelClick={handlePixelClick} onBrightnessChange={handleBrightnessChange} onSubmit={handleSubmit} - isVisible={isVisible} currentBrightness={currentBrightness} /> ); diff --git a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx index 8b5ad32e2..9c34a5137 100644 --- a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx +++ b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx @@ -11,9 +11,9 @@ import React from "react"; import {MicrobitMultiplePixelComponent, MicrobitSinglePixelComponent} from "./microbitWidget"; import { numberArgs } from "./argumentParser"; -interface WidgetProps<T>{ +export interface WidgetProps<T>{ // Note: always an array, can be singleton - arguments : T[] + args : T[] // Where to insert the changed values from : number, to : number, From c35a9a8cfdd3fbc1a582992ae2c6af91c01261fc Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Sat, 23 Mar 2024 23:19:58 -0400 Subject: [PATCH 042/106] renaming --- src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx index 9c34a5137..efd042804 100644 --- a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx +++ b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx @@ -37,13 +37,13 @@ class Widget<T> extends WidgetType { toDOM(view: EditorView) { const dom = document.createElement("div"); let props = { - arguments: this.args, + args: this.args, from: this.from, to: this.to, view: view } - this.portalCleanup = this.createPortal(dom, React.createElement(this.component, { props: props })); + this.portalCleanup = this.createPortal(dom, React.createElement(this.component, { props })); return dom; } From 62a147fe5acb8d7c3484f7aea1dcc7925fc00765 Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Sat, 23 Mar 2024 23:37:10 -0400 Subject: [PATCH 043/106] fixed props.view --- src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx index efd042804..f846bcea1 100644 --- a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx +++ b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx @@ -43,7 +43,7 @@ class Widget<T> extends WidgetType { view: view } - this.portalCleanup = this.createPortal(dom, React.createElement(this.component, { props })); + this.portalCleanup = this.createPortal(dom, React.createElement(this.component, props)); return dom; } From c0b378d5b51199241297170205dfb4dfd3c931c9 Mon Sep 17 00:00:00 2001 From: Marta Religa <marta.religa@cs.ox.ac.uk> Date: Sun, 24 Mar 2024 09:39:28 +0100 Subject: [PATCH 044/106] should work, gets invalid isnertion range or something idk --- .../helper-widgets/microbitWidget.tsx | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/editor/codemirror/helper-widgets/microbitWidget.tsx b/src/editor/codemirror/helper-widgets/microbitWidget.tsx index de2ca352f..e1846c946 100644 --- a/src/editor/codemirror/helper-widgets/microbitWidget.tsx +++ b/src/editor/codemirror/helper-widgets/microbitWidget.tsx @@ -85,6 +85,7 @@ export const MicrobitSinglePixelComponent = ({ args, from, to, view }: WidgetPro setSelectedPixel(pixel); if (selectedPixel !== null) { const { x, y, brightness } = selectedPixel; + console.log(`(${x}, ${y}, ${brightness}) `); view.dispatch({ changes: { from: from, @@ -160,6 +161,25 @@ const MicrobitMultiplePixelsGrid: React.FC<MultiMicrobitGridProps> = ({ ); }; + +function pixelsToString(pixels: Pixel[]): string { + let outputString = ''; + for (let y = 0; y < 5; y++) { + for (let x = 0; x < 5; x++) { + const pixel = pixels.find(p => p.x === x && p.y === y); + if (pixel) { + outputString += pixel.brightness.toString(); + } else { + outputString += '0'; + } + } + outputString += ':'; + } + outputString = outputString.slice(0, -1); + + return outputString; +} + export const MicrobitMultiplePixelComponent = ({args, from, to, view }: WidgetProps<number>) => { const initialSelectedPixels: Pixel[] = []; @@ -193,7 +213,14 @@ export const MicrobitMultiplePixelComponent = ({args, from, to, view }: WidgetPr }; const handleSubmit = () => { - console.log("Submitting..."); + let insertion = pixelsToString(selectedPixels); + console.log(insertion) + view.dispatch({ + changes: { + from: from, + to: to, + insert: insertion} + }); }; return ( From 3199b945f684155833c94cf6486c8669665492ce Mon Sep 17 00:00:00 2001 From: Marta Religa <marta.religa@cs.ox.ac.uk> Date: Sun, 24 Mar 2024 11:53:07 +0100 Subject: [PATCH 045/106] okay im done with the frontend, just need to add correct initialization and fix the insertions --- .../helper-widgets/microbitWidget.tsx | 125 ++++++++---------- 1 file changed, 53 insertions(+), 72 deletions(-) diff --git a/src/editor/codemirror/helper-widgets/microbitWidget.tsx b/src/editor/codemirror/helper-widgets/microbitWidget.tsx index e1846c946..cb0659131 100644 --- a/src/editor/codemirror/helper-widgets/microbitWidget.tsx +++ b/src/editor/codemirror/helper-widgets/microbitWidget.tsx @@ -85,14 +85,15 @@ export const MicrobitSinglePixelComponent = ({ args, from, to, view }: WidgetPro setSelectedPixel(pixel); if (selectedPixel !== null) { const { x, y, brightness } = selectedPixel; - console.log(`(${x}, ${y}, ${brightness}) `); - view.dispatch({ + console.log(`(${x}, ${y}, ${brightness}) `) + /*view.dispatch({ changes: { from: from, to: to, insert: `(${x}, ${y}, ${brightness}) `, } }); + */ } }; @@ -101,35 +102,45 @@ export const MicrobitSinglePixelComponent = ({ args, from, to, view }: WidgetPro interface MultiMicrobitGridProps { - selectedPixels: Pixel[]; - onPixelClick: (x: number, y: number) => void; - onBrightnessChange: (x: number, y: number, brightness: number) => void; - onSubmit: () => void; - currentBrightness: number; + selectedPixels: number[][]; + onPixelChange: (x: number, y: number, brightness: number) => void; } const MicrobitMultiplePixelsGrid: React.FC<MultiMicrobitGridProps> = ({ selectedPixels, - onPixelClick, - onBrightnessChange, - currentBrightness + onPixelChange, }) => { + const [currentBrightness, setCurrentBrightness] = useState<number>(5); + const [selectedPixel, setSelectedPixel] = useState<{ x: number; y: number } | null>(null); + + const handlePixelClick = (x: number, y: number) => { + setSelectedPixel({ x, y }); + onPixelChange(x, y, currentBrightness); + }; + + const handleBrightnessChange = (brightness: number) => { + setCurrentBrightness(brightness); + if (selectedPixel) { + onPixelChange(selectedPixel.x, selectedPixel.y, brightness); + } + }; + return ( <Box display="flex" flexDirection="row" justifyContent="flex-start"> <Box> <Box bg="black" p="10px" borderRadius="5px"> - {[...Array(5)].map((_, y) => ( + {selectedPixels.map((row, y) => ( <Box key={y} display="flex"> - {[...Array(5)].map((_, x) => ( + {row.map((brightness, x) => ( <Box key={x} display="flex" mr="2px"> <Button size="xs" h="15px" w="15px" p={0} - bgColor={selectedPixels.some(p => p.x === x && p.y === y) ? `rgba(255, 0, 0, ${(selectedPixels.find(p => p.x === x && p.y === y)!.brightness) / 9})` : "rgba(255, 255, 255, 0)"} - _hover={{ bgColor: selectedPixels.some(p => p.x === x && p.y === y) ? `rgba(255, 0, 0, ${(selectedPixels.find(p => p.x === x && p.y === y)!.brightness) / 9})` : "rgba(255, 255, 255, 0.5)" }} - onClick={() => onPixelClick(x, y)} + bgColor={`rgba(255, 0, 0, ${brightness / 9})`} + _hover={{ bgColor: brightness > 0 ? `rgba(255, 0, 0, ${brightness / 9} + 0.1)` : "rgba(255, 255, 255, 0.5)" }} + onClick={() => handlePixelClick(x, y)} /> </Box> ))} @@ -147,10 +158,8 @@ const MicrobitMultiplePixelsGrid: React.FC<MultiMicrobitGridProps> = ({ orientation="vertical" _focus={{ boxShadow: "none" }} _active={{ bgColor: "transparent" }} - onChange={(value) => { - const lastPixel = selectedPixels.length > 0 ? selectedPixels[selectedPixels.length - 1] : { x: -1, y: -1 }; - onBrightnessChange(lastPixel.x, lastPixel.y, value); - }} > + onChange={(value) => handleBrightnessChange(value)} + > <SliderTrack> <SliderFilledTrack /> </SliderTrack> @@ -162,74 +171,46 @@ const MicrobitMultiplePixelsGrid: React.FC<MultiMicrobitGridProps> = ({ }; -function pixelsToString(pixels: Pixel[]): string { - let outputString = ''; - for (let y = 0; y < 5; y++) { - for (let x = 0; x < 5; x++) { - const pixel = pixels.find(p => p.x === x && p.y === y); - if (pixel) { - outputString += pixel.brightness.toString(); - } else { - outputString += '0'; - } - } - outputString += ':'; - } - outputString = outputString.slice(0, -1); - - return outputString; -} +export const MicrobitMultiplePixelComponent = ({ args, from, to, view }: WidgetProps<number>) => { + const initialSelectedPixels: number[][] = Array.from({ length: 5 }, () => Array(5).fill(0)); -export const MicrobitMultiplePixelComponent = ({args, from, to, view }: WidgetProps<number>) => { - const initialSelectedPixels: Pixel[] = []; + const [selectedPixels, setSelectedPixels] = useState<number[][]>(initialSelectedPixels); - const [selectedPixels, setSelectedPixels] = useState<Pixel[]>(initialSelectedPixels); - const [currentBrightness, setCurrentBrightness] = useState(5); - - const handlePixelClick = (x: number, y: number) => { - const existingIndex = selectedPixels.findIndex(pixel => pixel.x === x && pixel.y === y); - if (existingIndex !== -1) { - const updatedPixels = [...selectedPixels]; - updatedPixels[existingIndex].brightness = currentBrightness; - setSelectedPixels(updatedPixels); - } else { - const newPixel: Pixel = { x, y, brightness: currentBrightness }; - setSelectedPixels([...selectedPixels, newPixel]); - } - handleSubmit(); - }; - - const handleBrightnessChange = (x: number, y: number, brightness: number) => { - setCurrentBrightness(brightness); - setSelectedPixels(prevPixels => { - const updatedPixels = [...prevPixels]; - const pixelIndex = updatedPixels.findIndex(pixel => pixel.x === x && pixel.y === y); - if (pixelIndex !== -1) { - updatedPixels[pixelIndex].brightness = brightness; - } - return updatedPixels; - }); + const handlePixelChange = (x: number, y: number, brightness: number) => { + const updatedPixels = [...selectedPixels]; + updatedPixels[y][x] = brightness; + setSelectedPixels(updatedPixels); handleSubmit(); }; const handleSubmit = () => { let insertion = pixelsToString(selectedPixels); - console.log(insertion) - view.dispatch({ + console.log(insertion); + /*view.dispatch({ changes: { from: from, to: to, insert: insertion} }); - }; + */ + } return ( <MicrobitMultiplePixelsGrid selectedPixels={selectedPixels} - onPixelClick={handlePixelClick} - onBrightnessChange={handleBrightnessChange} - onSubmit={handleSubmit} - currentBrightness={currentBrightness} + onPixelChange={handlePixelChange} /> ); -}; \ No newline at end of file +}; + +function pixelsToString(pixels: number[][]): string { + let outputString = ''; + for (let y = 0; y < 5; y++) { + for (let x = 0; x < 5; x++) { + outputString += pixels[y][x].toString(); + } + outputString += ':'; + } + outputString = outputString.slice(0, -1); + return outputString; +} \ No newline at end of file From 6a11655a5a3da7fbc8517fb4d550a3abb6997597 Mon Sep 17 00:00:00 2001 From: Marta Religa <marta.religa@cs.ox.ac.uk> Date: Sun, 24 Mar 2024 12:10:20 +0100 Subject: [PATCH 046/106] reorganized --- .../helper-widgets/argumentParser.tsx | 4 +- .../helper-widgets/reactWidgetExtension.tsx | 43 ++++---- .../helper-widgets/setPixelWidget.tsx | 101 +++++++++++++++++ ...microbitWidget.tsx => showImageWidget.tsx} | 103 +----------------- 4 files changed, 127 insertions(+), 124 deletions(-) create mode 100644 src/editor/codemirror/helper-widgets/setPixelWidget.tsx rename src/editor/codemirror/helper-widgets/{microbitWidget.tsx => showImageWidget.tsx} (52%) diff --git a/src/editor/codemirror/helper-widgets/argumentParser.tsx b/src/editor/codemirror/helper-widgets/argumentParser.tsx index fa5bf1e42..e5d88be25 100644 --- a/src/editor/codemirror/helper-widgets/argumentParser.tsx +++ b/src/editor/codemirror/helper-widgets/argumentParser.tsx @@ -7,7 +7,7 @@ import { EditorState } from "@codemirror/state"; export function numberArgs(state: EditorState, args: any[]): number[] { let nums = [] args.forEach(function (value) { - - }); + + }); return [] } \ No newline at end of file diff --git a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx index f846bcea1..036013643 100644 --- a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx +++ b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx @@ -8,15 +8,16 @@ import { import { syntaxTree } from "@codemirror/language" import { PortalFactory } from "../CodeMirror"; import React from "react"; -import {MicrobitMultiplePixelComponent, MicrobitSinglePixelComponent} from "./microbitWidget"; -import { numberArgs } from "./argumentParser"; +import { MicrobitSinglePixelComponent } from "./setPixelWidget"; +import { MicrobitMultiplePixelComponent } from "./showImageWidget" +//import { numberArgs } from "./argumentParser"; -export interface WidgetProps<T>{ +export interface WidgetProps<T> { // Note: always an array, can be singleton - args : T[] + args: T[] // Where to insert the changed values - from : number, - to : number, + from: number, + to: number, // Widget will change textfile view: EditorView } @@ -28,9 +29,9 @@ export interface WidgetProps<T>{ class Widget<T> extends WidgetType { private portalCleanup: (() => void) | undefined; - constructor(private component : React.ComponentType<any>, - private args: T[], private from: number, private to: number, - private createPortal: PortalFactory, ) { + constructor(private component: React.ComponentType<any>, + private args: T[], private from: number, private to: number, + private createPortal: PortalFactory,) { super(); } @@ -65,29 +66,29 @@ export const reactWidgetExtension = ( const decorate = (state: EditorState) => { let widgets: any[] = [] // Creates a widget which accepts arguments of type T - function createWidget<T>(comp: React.ComponentType<any>, args: T[], from: number, to: number) { - args.forEach(function(value) { console.log(value); }) + function createWidget<T>(comp: React.ComponentType<any>, args: T[], from: number, to: number) { + args.forEach(function (value) { console.log(value); }) let deco = Decoration.widget({ widget: new Widget<T>(comp, args, to, from, createPortal), side: 1, }); - + widgets.push(deco.range(to)); } syntaxTree(state).iterate({ enter: (ref) => { // Found an ArgList, parent will be a CallExpression - if(ref.name === "ArgList" && ref.node.parent){ + if (ref.name === "ArgList" && ref.node.parent) { //console.log(state.doc.sliceString(ref.node.parent.from, ref.from)); - + // Match CallExpression name to our widgets - switch(state.doc.sliceString(ref.node.parent.from, ref.from)){ + switch (state.doc.sliceString(ref.node.parent.from, ref.from)) { case "display.set_pixel": // TODO: assuming all literals for now, will probably want a way to detect other types of arguments let args: number[] = []; - ref.node.getChildren("Number").forEach( function(child) { args.push(+state.doc.sliceString(child.from, child.to)) }); + ref.node.getChildren("Number").forEach(function (child) { args.push(+state.doc.sliceString(child.from, child.to)) }); createWidget<number>(MicrobitSinglePixelComponent, args, ref.from, ref.to); break; @@ -95,12 +96,12 @@ export const reactWidgetExtension = ( // TODO: does not handle comments properly let imArg: string[] = [] let arg = ref.node.getChild("ContinuedString"); - if(arg) imArg.push(state.doc.sliceString(arg.from, arg.to).replaceAll(/[' \n]/g, "")); - else{ + if (arg) imArg.push(state.doc.sliceString(arg.from, arg.to).replaceAll(/[' \n]/g, "")); + else { arg = ref.node.getChild("String"); - if(arg) imArg.push() - } - + if (arg) imArg.push() + } + createWidget<string>(MicrobitMultiplePixelComponent, imArg, ref.from, ref.to); break; default: diff --git a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx new file mode 100644 index 000000000..096df32bd --- /dev/null +++ b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx @@ -0,0 +1,101 @@ +import { Box, Button, Slider, SliderTrack, SliderFilledTrack, SliderThumb } from "@chakra-ui/react"; +import { useState } from "react"; +import { WidgetProps } from "./reactWidgetExtension"; + +interface Pixel { + x: number; + y: number; + brightness: number; +} + +interface MicrobitSinglePixelGridProps { + onClickPixel: (pixel: Pixel) => void; +} + +const MicrobitSinglePixelGrid: React.FC<MicrobitSinglePixelGridProps> = ({ onClickPixel }) => { + const [selectedPixel, setSelectedPixel] = useState<Pixel | null>(null); + const [brightness, setBrightness] = useState<number>(5); + + const handleClickPixel = (x: number, y: number) => { + const newPixel: Pixel = { x, y, brightness }; + setSelectedPixel(newPixel); + onClickPixel(newPixel); + }; + + const handleSliderChange = (value: number) => { + setBrightness(value); + if (selectedPixel) { + const updatedPixel: Pixel = { ...selectedPixel, brightness: value }; + onClickPixel(updatedPixel); + } + }; + + return ( + <Box display="flex" flexDirection="row" justifyContent="flex-start"> + <Box> + <Box bg="black" p="10px" borderRadius="5px"> + {[...Array(5)].map((_, y) => ( + <Box key={y} display="flex"> + {[...Array(5)].map((_, x) => ( + <Box key={x} display="flex" mr="2px"> + <Button + size="xs" + h="15px" + w="15px" + p={0} + bgColor={selectedPixel?.x === x && selectedPixel.y === y ? `rgba(255, 0, 0, ${brightness / 9})` : "rgba(255, 255, 255, 0)"} + _hover={{ bgColor: selectedPixel?.x === x && selectedPixel.y === y ? `rgba(255, 0, 0, ${brightness / 9})` : "rgba(255, 255, 255, 0.5)" }} + onClick={() => handleClickPixel(x, y)} + /> + </Box> + ))} + </Box> + ))} + </Box> + </Box> + <Box ml="10px"> + <Slider + aria-label="brightness" + defaultValue={brightness} + min={0} + max={9} + step={1} + orientation="vertical" + _focus={{ boxShadow: "none" }} + _active={{ bgColor: "transparent" }} + onChange={handleSliderChange}> + <SliderTrack> + <SliderFilledTrack /> + </SliderTrack> + <SliderThumb /> + </Slider> + </Box> + </Box> + ); +}; + +export const MicrobitSinglePixelComponent = ({ args, from, to, view }: WidgetProps<number>) => { + const [selectedPixel, setSelectedPixel] = useState<Pixel | null>(null); + if (Array.isArray(args) && args.length === 3) { + const [x, y, brightness] = args; + setSelectedPixel({ x, y, brightness }); + } + + const handleSelectPixel = (pixel: Pixel) => { + setSelectedPixel(pixel); + if (selectedPixel !== null) { + const { x, y, brightness } = selectedPixel; + console.log(`(${x}, ${y}, ${brightness}) `) + /*view.dispatch({ + changes: { + from: from, + to: to, + insert: `(${x}, ${y}, ${brightness}) `, + } + }); + */ + } + }; + + return (<MicrobitSinglePixelGrid onClickPixel={handleSelectPixel} />); +}; diff --git a/src/editor/codemirror/helper-widgets/microbitWidget.tsx b/src/editor/codemirror/helper-widgets/showImageWidget.tsx similarity index 52% rename from src/editor/codemirror/helper-widgets/microbitWidget.tsx rename to src/editor/codemirror/helper-widgets/showImageWidget.tsx index cb0659131..67039b38e 100644 --- a/src/editor/codemirror/helper-widgets/microbitWidget.tsx +++ b/src/editor/codemirror/helper-widgets/showImageWidget.tsx @@ -1,105 +1,6 @@ import { Box, Button, Slider, SliderTrack, SliderFilledTrack, SliderThumb } from "@chakra-ui/react"; -import { useState } from "react"; -import {WidgetProps} from "./reactWidgetExtension"; - -interface Pixel { - x: number; - y: number; - brightness: number; -} - -interface MicrobitSinglePixelGridProps { - onClickPixel: (pixel: Pixel) => void; -} - -const MicrobitSinglePixelGrid: React.FC<MicrobitSinglePixelGridProps> = ({ onClickPixel }) => { - const [selectedPixel, setSelectedPixel] = useState<Pixel | null>(null); - const [brightness, setBrightness] = useState<number>(5); - - const handleClickPixel = (x: number, y: number) => { - const newPixel: Pixel = { x, y, brightness }; - setSelectedPixel(newPixel); - onClickPixel(newPixel); - }; - - const handleSliderChange = (value: number) => { - if (selectedPixel) { - setBrightness(value); - const updatedPixel: Pixel = { ...selectedPixel, brightness: value }; - onClickPixel(updatedPixel); - } - }; - - return ( - <Box display="flex" flexDirection="row" justifyContent="flex-start"> - <Box> - <Box bg="black" p="10px" borderRadius="5px"> - {[...Array(5)].map((_, y) => ( - <Box key={y} display="flex"> - {[...Array(5)].map((_, x) => ( - <Box key={x} display="flex" mr="2px"> - <Button - size="xs" - h="15px" - w="15px" - p={0} - bgColor={selectedPixel?.x === x && selectedPixel.y === y ? `rgba(255, 0, 0, ${brightness / 9})` : "rgba(255, 255, 255, 0)"} - _hover={{ bgColor: selectedPixel?.x === x && selectedPixel.y === y ? `rgba(255, 0, 0, ${brightness / 9})` : "rgba(255, 255, 255, 0.5)" }} - onClick={() => handleClickPixel(x, y)} - /> - </Box> - ))} - </Box> - ))} - </Box> - </Box> - <Box ml="10px"> - <Slider - aria-label="brightness" - defaultValue={brightness} - min={0} - max={9} - step={1} - orientation="vertical" - _focus={{ boxShadow: "none" }} - _active={{ bgColor: "transparent" }} - onChange={handleSliderChange}> - <SliderTrack> - <SliderFilledTrack /> - </SliderTrack> - <SliderThumb /> - </Slider> - </Box> - </Box> - ); -}; - -export const MicrobitSinglePixelComponent = ({ args, from, to, view }: WidgetProps<number>) => { - const [selectedPixel, setSelectedPixel] = useState<Pixel | null>(null); - if (Array.isArray(args) && args.length === 3) { - const [x, y, brightness] = args; - setSelectedPixel({ x, y, brightness }); - } - - const handleSelectPixel = (pixel: Pixel) => { - setSelectedPixel(pixel); - if (selectedPixel !== null) { - const { x, y, brightness } = selectedPixel; - console.log(`(${x}, ${y}, ${brightness}) `) - /*view.dispatch({ - changes: { - from: from, - to: to, - insert: `(${x}, ${y}, ${brightness}) `, - } - }); - */ - } - }; - - return (<MicrobitSinglePixelGrid onClickPixel={handleSelectPixel} />); -}; - +import React, { useState } from "react"; +import { WidgetProps } from "./reactWidgetExtension"; interface MultiMicrobitGridProps { selectedPixels: number[][]; From 7fbe7cc211585019094f007455b0bd161e942b65 Mon Sep 17 00:00:00 2001 From: Marta Religa <marta.religa@cs.ox.ac.uk> Date: Sun, 24 Mar 2024 14:04:47 +0100 Subject: [PATCH 047/106] ye --- .../helper-widgets/setPixelWidget.tsx | 32 +++++++++++-------- .../helper-widgets/showImageWidget.tsx | 4 +-- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx index 096df32bd..62ce88b94 100644 --- a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx +++ b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx @@ -9,24 +9,24 @@ interface Pixel { } interface MicrobitSinglePixelGridProps { - onClickPixel: (pixel: Pixel) => void; + onPixelClick: (pixel: Pixel) => void; } -const MicrobitSinglePixelGrid: React.FC<MicrobitSinglePixelGridProps> = ({ onClickPixel }) => { +const MicrobitSinglePixelGrid: React.FC<MicrobitSinglePixelGridProps> = ({ onPixelClick }) => { const [selectedPixel, setSelectedPixel] = useState<Pixel | null>(null); - const [brightness, setBrightness] = useState<number>(5); + const [currentBrightness, setCurrentBrightness] = useState<number>(5); - const handleClickPixel = (x: number, y: number) => { - const newPixel: Pixel = { x, y, brightness }; + const handlePixelClick = (x: number, y: number) => { + const newPixel: Pixel = { x: x, y: y, brightness: currentBrightness }; setSelectedPixel(newPixel); - onClickPixel(newPixel); + onPixelClick(newPixel); }; const handleSliderChange = (value: number) => { - setBrightness(value); + setCurrentBrightness(value); if (selectedPixel) { const updatedPixel: Pixel = { ...selectedPixel, brightness: value }; - onClickPixel(updatedPixel); + onPixelClick(updatedPixel); } }; @@ -43,9 +43,9 @@ const MicrobitSinglePixelGrid: React.FC<MicrobitSinglePixelGridProps> = ({ onCli h="15px" w="15px" p={0} - bgColor={selectedPixel?.x === x && selectedPixel.y === y ? `rgba(255, 0, 0, ${brightness / 9})` : "rgba(255, 255, 255, 0)"} - _hover={{ bgColor: selectedPixel?.x === x && selectedPixel.y === y ? `rgba(255, 0, 0, ${brightness / 9})` : "rgba(255, 255, 255, 0.5)" }} - onClick={() => handleClickPixel(x, y)} + bgColor={selectedPixel?.x === x && selectedPixel.y === y ? `rgba(255, 0, 0, ${currentBrightness / 9})` : "rgba(255, 255, 255, 0)"} + _hover={{ bgColor: selectedPixel?.x === x && selectedPixel.y === y ? `rgba(255, 0, 0, ${currentBrightness / 9})` : "rgba(255, 255, 255, 0.5)" }} + onClick={() => handlePixelClick(x, y)} /> </Box> ))} @@ -56,7 +56,7 @@ const MicrobitSinglePixelGrid: React.FC<MicrobitSinglePixelGridProps> = ({ onCli <Box ml="10px"> <Slider aria-label="brightness" - defaultValue={brightness} + defaultValue={currentBrightness} min={0} max={9} step={1} @@ -83,9 +83,13 @@ export const MicrobitSinglePixelComponent = ({ args, from, to, view }: WidgetPro const handleSelectPixel = (pixel: Pixel) => { setSelectedPixel(pixel); + updateView(); + }; + + const updateView = () => { if (selectedPixel !== null) { const { x, y, brightness } = selectedPixel; - console.log(`(${x}, ${y}, ${brightness}) `) + console.log(`(${x}, ${y}, ${brightness}) `); /*view.dispatch({ changes: { from: from, @@ -97,5 +101,5 @@ export const MicrobitSinglePixelComponent = ({ args, from, to, view }: WidgetPro } }; - return (<MicrobitSinglePixelGrid onClickPixel={handleSelectPixel} />); + return (<MicrobitSinglePixelGrid onPixelClick={handleSelectPixel} />); }; diff --git a/src/editor/codemirror/helper-widgets/showImageWidget.tsx b/src/editor/codemirror/helper-widgets/showImageWidget.tsx index 67039b38e..6511a9160 100644 --- a/src/editor/codemirror/helper-widgets/showImageWidget.tsx +++ b/src/editor/codemirror/helper-widgets/showImageWidget.tsx @@ -81,10 +81,10 @@ export const MicrobitMultiplePixelComponent = ({ args, from, to, view }: WidgetP const updatedPixels = [...selectedPixels]; updatedPixels[y][x] = brightness; setSelectedPixels(updatedPixels); - handleSubmit(); + updateView(); }; - const handleSubmit = () => { + const updateView = () => { let insertion = pixelsToString(selectedPixels); console.log(insertion); /*view.dispatch({ From 00f833d93c3fe57e6b5b49f412fc6a885c04b6f8 Mon Sep 17 00:00:00 2001 From: Marta Religa <marta.religa@cs.ox.ac.uk> Date: Sun, 24 Mar 2024 21:00:37 +0100 Subject: [PATCH 048/106] . --- .../helper-widgets/setPixelWidget.tsx | 22 ++++++++++++++----- .../helper-widgets/showImageWidget.tsx | 18 +++++++++++---- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx index 62ce88b94..76174186e 100644 --- a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx +++ b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx @@ -10,10 +10,11 @@ interface Pixel { interface MicrobitSinglePixelGridProps { onPixelClick: (pixel: Pixel) => void; + initialPixel : Pixel | null; } -const MicrobitSinglePixelGrid: React.FC<MicrobitSinglePixelGridProps> = ({ onPixelClick }) => { - const [selectedPixel, setSelectedPixel] = useState<Pixel | null>(null); +const MicrobitSinglePixelGrid: React.FC<MicrobitSinglePixelGridProps> = ({ onPixelClick, initialPixel }) => { + const [selectedPixel, setSelectedPixel] = useState<Pixel | null>(initialPixel); const [currentBrightness, setCurrentBrightness] = useState<number>(5); const handlePixelClick = (x: number, y: number) => { @@ -74,12 +75,21 @@ const MicrobitSinglePixelGrid: React.FC<MicrobitSinglePixelGridProps> = ({ onPix ); }; +const parseArgs = (args : number[]) => { + return args +}; + +const validateArgs = (args : number[]) => { + return Array.isArray(args) && args.length === 3 +}; + + export const MicrobitSinglePixelComponent = ({ args, from, to, view }: WidgetProps<number>) => { const [selectedPixel, setSelectedPixel] = useState<Pixel | null>(null); - if (Array.isArray(args) && args.length === 3) { - const [x, y, brightness] = args; + if (validateArgs(args)){ + const [x, y, brightness] = parseArgs(args); setSelectedPixel({ x, y, brightness }); - } + } const handleSelectPixel = (pixel: Pixel) => { setSelectedPixel(pixel); @@ -101,5 +111,5 @@ export const MicrobitSinglePixelComponent = ({ args, from, to, view }: WidgetPro } }; - return (<MicrobitSinglePixelGrid onPixelClick={handleSelectPixel} />); + return (<MicrobitSinglePixelGrid onPixelClick={handleSelectPixel} initialPixel={selectedPixel} />); }; diff --git a/src/editor/codemirror/helper-widgets/showImageWidget.tsx b/src/editor/codemirror/helper-widgets/showImageWidget.tsx index 6511a9160..156851560 100644 --- a/src/editor/codemirror/helper-widgets/showImageWidget.tsx +++ b/src/editor/codemirror/helper-widgets/showImageWidget.tsx @@ -40,7 +40,7 @@ const MicrobitMultiplePixelsGrid: React.FC<MultiMicrobitGridProps> = ({ w="15px" p={0} bgColor={`rgba(255, 0, 0, ${brightness / 9})`} - _hover={{ bgColor: brightness > 0 ? `rgba(255, 0, 0, ${brightness / 9} + 0.1)` : "rgba(255, 255, 255, 0.5)" }} + _hover={{ bgColor: brightness > 0 ? `rgba(255, 100, 100, ${selectedPixels[y][x] / 9})` : "rgba(255, 255, 255, 0.5)" }} onClick={() => handlePixelClick(x, y)} /> </Box> @@ -71,10 +71,11 @@ const MicrobitMultiplePixelsGrid: React.FC<MultiMicrobitGridProps> = ({ ); }; - export const MicrobitMultiplePixelComponent = ({ args, from, to, view }: WidgetProps<number>) => { - const initialSelectedPixels: number[][] = Array.from({ length: 5 }, () => Array(5).fill(0)); - + let initialSelectedPixels: number[][] = Array.from({ length: 5 }, () => Array(5).fill(0)); + if (validateArgs(args)) { + initialSelectedPixels = parseArgs(args) + } const [selectedPixels, setSelectedPixels] = useState<number[][]>(initialSelectedPixels); const handlePixelChange = (x: number, y: number, brightness: number) => { @@ -104,6 +105,15 @@ export const MicrobitMultiplePixelComponent = ({ args, from, to, view }: WidgetP ); }; +const parseArgs = (args: number[]) => { + return [] +}; + +const validateArgs = (args: number[]) => { + return false +}; + + function pixelsToString(pixels: number[][]): string { let outputString = ''; for (let y = 0; y < 5; y++) { From 7be22fffb212fccffcdd4dc6646ed1b49a707d44 Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Tue, 26 Mar 2024 09:43:02 -0400 Subject: [PATCH 049/106] syntaxNode argument --- src/editor/codemirror/helper-widgets/argumentParser.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/editor/codemirror/helper-widgets/argumentParser.tsx b/src/editor/codemirror/helper-widgets/argumentParser.tsx index e5d88be25..7e4b79f9f 100644 --- a/src/editor/codemirror/helper-widgets/argumentParser.tsx +++ b/src/editor/codemirror/helper-widgets/argumentParser.tsx @@ -1,10 +1,10 @@ import { EditorState } from "@codemirror/state"; - +import { SyntaxNode } from "@lezer/common"; // TODO: might move parsing to here once arguments are no longer literals // see index.d.ts // Pre: args are all of type number -export function numberArgs(state: EditorState, args: any[]): number[] { +export function numberArgs(state: EditorState, args: any[], node:SyntaxNode): number[] { let nums = [] args.forEach(function (value) { From dd120844dab1ddbb7fcf7ea24206ecf460b287a9 Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Tue, 26 Mar 2024 10:18:10 -0400 Subject: [PATCH 050/106] from to --- src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx index 036013643..20177d1bb 100644 --- a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx +++ b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx @@ -70,7 +70,7 @@ export const reactWidgetExtension = ( args.forEach(function (value) { console.log(value); }) let deco = Decoration.widget({ - widget: new Widget<T>(comp, args, to, from, createPortal), + widget: new Widget<T>(comp, args, from, to, createPortal), side: 1, }); From 902bb1c13420d117cdbf2c499839d34ef387ebaf Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Tue, 26 Mar 2024 10:33:00 -0400 Subject: [PATCH 051/106] updates to props removed view from props, added ranges to props --- .../helper-widgets/reactWidgetExtension.tsx | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx index 20177d1bb..7b673cbc8 100644 --- a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx +++ b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx @@ -15,11 +15,11 @@ import { MicrobitMultiplePixelComponent } from "./showImageWidget" export interface WidgetProps<T> { // Note: always an array, can be singleton args: T[] + // Ranges of where to insert each argument + ranges: {from:number, to:number} [] // Where to insert the changed values from: number, - to: number, - // Widget will change textfile - view: EditorView + to: number } /** @@ -30,21 +30,15 @@ class Widget<T> extends WidgetType { private portalCleanup: (() => void) | undefined; constructor(private component: React.ComponentType<any>, - private args: T[], private from: number, private to: number, - private createPortal: PortalFactory,) { + private props:WidgetProps<T>, + private createPortal: PortalFactory) { super(); } toDOM(view: EditorView) { const dom = document.createElement("div"); - let props = { - args: this.args, - from: this.from, - to: this.to, - view: view - } - this.portalCleanup = this.createPortal(dom, React.createElement(this.component, props)); + this.portalCleanup = this.createPortal(dom, React.createElement(this.component, this.props, view)); return dom; } @@ -68,9 +62,15 @@ export const reactWidgetExtension = ( // Creates a widget which accepts arguments of type T function createWidget<T>(comp: React.ComponentType<any>, args: T[], from: number, to: number) { args.forEach(function (value) { console.log(value); }) + let props = { + args: args, + ranges: [], + from: from, + to: to, + } let deco = Decoration.widget({ - widget: new Widget<T>(comp, args, from, to, createPortal), + widget: new Widget<T>(comp, props, createPortal), side: 1, }); From 6c240095e0ff4d13d1e824d95a599cef80339317 Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Sat, 30 Mar 2024 14:19:23 -0400 Subject: [PATCH 052/106] Better syntax tree iteration handle parsing in a separate file, and allows for insertion by range --- .../helper-widgets/argumentParser.tsx | 13 ---- .../helper-widgets/reactWidgetExtension.tsx | 64 +++++------------ .../helper-widgets/widgetArgParser.tsx | 72 +++++++++++++++++++ 3 files changed, 88 insertions(+), 61 deletions(-) delete mode 100644 src/editor/codemirror/helper-widgets/argumentParser.tsx create mode 100644 src/editor/codemirror/helper-widgets/widgetArgParser.tsx diff --git a/src/editor/codemirror/helper-widgets/argumentParser.tsx b/src/editor/codemirror/helper-widgets/argumentParser.tsx deleted file mode 100644 index 7e4b79f9f..000000000 --- a/src/editor/codemirror/helper-widgets/argumentParser.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { EditorState } from "@codemirror/state"; -import { SyntaxNode } from "@lezer/common"; -// TODO: might move parsing to here once arguments are no longer literals -// see index.d.ts - -// Pre: args are all of type number -export function numberArgs(state: EditorState, args: any[], node:SyntaxNode): number[] { - let nums = [] - args.forEach(function (value) { - - }); - return [] -} \ No newline at end of file diff --git a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx index 7b673cbc8..e2a6aba92 100644 --- a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx +++ b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx @@ -10,13 +10,15 @@ import { PortalFactory } from "../CodeMirror"; import React from "react"; import { MicrobitSinglePixelComponent } from "./setPixelWidget"; import { MicrobitMultiplePixelComponent } from "./showImageWidget" -//import { numberArgs } from "./argumentParser"; +import { createWidget } from "./widgetArgParser"; -export interface WidgetProps<T> { +export interface WidgetProps { // Note: always an array, can be singleton - args: T[] + args: any[] // Ranges of where to insert each argument ranges: {from:number, to:number} [] + // Whether or not each argument is a literal + literals: boolean[] // Where to insert the changed values from: number, to: number @@ -26,11 +28,11 @@ export interface WidgetProps<T> { * This widget will have its contents rendered by the code in CodeMirror.tsx * which it communicates with via the portal factory. */ -class Widget<T> extends WidgetType { +class Widget extends WidgetType { private portalCleanup: (() => void) | undefined; constructor(private component: React.ComponentType<any>, - private props:WidgetProps<T>, + private props:WidgetProps, private createPortal: PortalFactory) { super(); } @@ -59,54 +61,20 @@ export const reactWidgetExtension = ( ): Extension => { const decorate = (state: EditorState) => { let widgets: any[] = [] - // Creates a widget which accepts arguments of type T - function createWidget<T>(comp: React.ComponentType<any>, args: T[], from: number, to: number) { - args.forEach(function (value) { console.log(value); }) - let props = { - args: args, - ranges: [], - from: from, - to: to, - } - - let deco = Decoration.widget({ - widget: new Widget<T>(comp, props, createPortal), - side: 1, - }); - - widgets.push(deco.range(to)); - } syntaxTree(state).iterate({ enter: (ref) => { // Found an ArgList, parent will be a CallExpression - if (ref.name === "ArgList" && ref.node.parent) { - //console.log(state.doc.sliceString(ref.node.parent.from, ref.from)); - + if (ref.name === "ArgList" && ref.node.parent) { // Match CallExpression name to our widgets - switch (state.doc.sliceString(ref.node.parent.from, ref.from)) { - case "display.set_pixel": - // TODO: assuming all literals for now, will probably want a way to detect other types of arguments - let args: number[] = []; - ref.node.getChildren("Number").forEach(function (child) { args.push(+state.doc.sliceString(child.from, child.to)) }); - - createWidget<number>(MicrobitSinglePixelComponent, args, ref.from, ref.to); - break; - case "Image": - // TODO: does not handle comments properly - let imArg: string[] = [] - let arg = ref.node.getChild("ContinuedString"); - if (arg) imArg.push(state.doc.sliceString(arg.from, arg.to).replaceAll(/[' \n]/g, "")); - else { - arg = ref.node.getChild("String"); - if (arg) imArg.push() - } - - createWidget<string>(MicrobitMultiplePixelComponent, imArg, ref.from, ref.to); - break; - default: - // No widget implemented for this function - break; + let name = state.doc.sliceString(ref.node.parent.from, ref.from) + let widget = createWidget(name, state, ref.node); + if(widget) { + let deco = Decoration.widget({ + widget: new Widget(widget.comp, widget.props, createPortal), + side: 1, + }); + widgets.push(deco.range(ref.to)); } } } diff --git a/src/editor/codemirror/helper-widgets/widgetArgParser.tsx b/src/editor/codemirror/helper-widgets/widgetArgParser.tsx new file mode 100644 index 000000000..6f343e567 --- /dev/null +++ b/src/editor/codemirror/helper-widgets/widgetArgParser.tsx @@ -0,0 +1,72 @@ +import { EditorState } from "@codemirror/state"; +import { + Decoration, + DecorationSet, + EditorView, + WidgetType, +} from "@codemirror/view"; +import { SyntaxNode } from "@lezer/common"; +import { WidgetProps } from "./reactWidgetExtension"; +import { MicrobitSinglePixelComponent } from "./setPixelWidget"; +import { MicrobitMultiplePixelComponent } from "./showImageWidget"; + +export interface CompProps { + comp: React.ComponentType<any>, + props: WidgetProps +} + +export function createWidget(name: string, state: EditorState, node: SyntaxNode): CompProps | null { + switch (name) { + case "display.set_pixel": + return { + comp: MicrobitSinglePixelComponent, + props: { + args: [], + ranges: [], + literals: [], + from: 0, + to: 0 + } + } + // // TODO: assuming all literals for now, will probably want a way to detect other types of arguments + // let args: number[] = []; + // ref.node.getChildren("Number").forEach(function (child) { args.push(+state.doc.sliceString(child.from, child.to)) }); + + // createWidget<number>(MicrobitSinglePixelComponent, args, ref.from, ref.to); + case "Image": + return { + comp: MicrobitMultiplePixelComponent, + props: { + args: [], + ranges: [], + literals: [], + from: 0, + to: 0 + } + } + + // TODO: does not handle comments properly + // let imArg: string[] = [] + // let arg = ref.node.getChild("ContinuedString"); + // if (arg) imArg.push(state.doc.sliceString(arg.from, arg.to).replaceAll(/[' \n]/g, "")); + // else { + // arg = ref.node.getChild("String"); + // if (arg) imArg.push() + // } + + // createWidget<string>(MicrobitMultiplePixelComponent, imArg, ref.from, ref.to); + // break; + default: + // No widget implemented for this function + return null; + } +} + +// Pre: args are all of type number +export function numberArgs(state: EditorState, args: any[], node:SyntaxNode): number[] { + let nums = [] + args.forEach(function (value) { + + }); + return [] +} \ No newline at end of file From 56b1afa5b567c02a95703f6644266be2f7230858 Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Sat, 30 Mar 2024 14:27:50 -0400 Subject: [PATCH 053/106] from to --- src/editor/codemirror/helper-widgets/widgetArgParser.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/editor/codemirror/helper-widgets/widgetArgParser.tsx b/src/editor/codemirror/helper-widgets/widgetArgParser.tsx index 6f343e567..e78084afc 100644 --- a/src/editor/codemirror/helper-widgets/widgetArgParser.tsx +++ b/src/editor/codemirror/helper-widgets/widgetArgParser.tsx @@ -24,8 +24,8 @@ export function createWidget(name: string, state: EditorState, node: SyntaxNode) args: [], ranges: [], literals: [], - from: 0, - to: 0 + from: node.from, + to: node.to } } // // TODO: assuming all literals for now, will probably want a way to detect other types of arguments @@ -40,8 +40,8 @@ export function createWidget(name: string, state: EditorState, node: SyntaxNode) args: [], ranges: [], literals: [], - from: 0, - to: 0 + from: node.from, + to: node.to } } From 33341fadb51c29f00c508568d5403add7b85eaaa Mon Sep 17 00:00:00 2001 From: Marta Religa <marta.religa@cs.ox.ac.uk> Date: Tue, 2 Apr 2024 15:54:10 +0200 Subject: [PATCH 054/106] . --- src/editor/codemirror/helper-widgets/setPixelWidget.tsx | 2 +- src/editor/codemirror/helper-widgets/showImageWidget.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx index 76174186e..620a92e94 100644 --- a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx +++ b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx @@ -84,7 +84,7 @@ const validateArgs = (args : number[]) => { }; -export const MicrobitSinglePixelComponent = ({ args, from, to, view }: WidgetProps<number>) => { +export const MicrobitSinglePixelComponent = ({ args, ranges, literals, from, to }: WidgetProps) => { const [selectedPixel, setSelectedPixel] = useState<Pixel | null>(null); if (validateArgs(args)){ const [x, y, brightness] = parseArgs(args); diff --git a/src/editor/codemirror/helper-widgets/showImageWidget.tsx b/src/editor/codemirror/helper-widgets/showImageWidget.tsx index 156851560..22d58acba 100644 --- a/src/editor/codemirror/helper-widgets/showImageWidget.tsx +++ b/src/editor/codemirror/helper-widgets/showImageWidget.tsx @@ -71,7 +71,7 @@ const MicrobitMultiplePixelsGrid: React.FC<MultiMicrobitGridProps> = ({ ); }; -export const MicrobitMultiplePixelComponent = ({ args, from, to, view }: WidgetProps<number>) => { +export const MicrobitMultiplePixelComponent = ({ args, ranges, literals, from, to }: WidgetProps) => { let initialSelectedPixels: number[][] = Array.from({ length: 5 }, () => Array(5).fill(0)); if (validateArgs(args)) { initialSelectedPixels = parseArgs(args) From 3bb2500ba90e66fe500cb3cf802ed1662ed94ac5 Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Tue, 2 Apr 2024 09:59:31 -0400 Subject: [PATCH 055/106] open button for widgets --- .../helper-widgets/reactWidgetExtension.tsx | 56 ++++++++++++++++--- 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx index e2a6aba92..265688af7 100644 --- a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx +++ b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx @@ -1,3 +1,4 @@ +import { Button, HStack, Text } from "@chakra-ui/react"; import { EditorState, Extension, StateField } from "@codemirror/state"; import { Decoration, @@ -8,6 +9,7 @@ import { import { syntaxTree } from "@codemirror/language" import { PortalFactory } from "../CodeMirror"; import React from "react"; +import { useCallback } from "react"; import { MicrobitSinglePixelComponent } from "./setPixelWidget"; import { MicrobitMultiplePixelComponent } from "./showImageWidget" import { createWidget } from "./widgetArgParser"; @@ -24,6 +26,27 @@ export interface WidgetProps { to: number } +// Location of currently open widget, -1 if all closed +export let openWidgetLoc = -1; +const OpenReactComponent = ({ loc, view }: { loc: number, view: EditorView }) => { + const handleClick = useCallback(() => { + openWidgetLoc = loc; + // TODO: not sure how to force a view update without a list of changes + view.dispatch({ + changes: { + from: 0, + to: 1, + insert: view.state.doc.sliceString(0, 1), + } + }); + }, []); + return ( + <HStack fontFamily="body" spacing={5} py={3}> + <Button onClick={handleClick}>Open</Button> + </HStack> + ); +}; + /** * This widget will have its contents rendered by the code in CodeMirror.tsx * which it communicates with via the portal factory. @@ -32,7 +55,7 @@ class Widget extends WidgetType { private portalCleanup: (() => void) | undefined; constructor(private component: React.ComponentType<any>, - private props:WidgetProps, + private props:WidgetProps, private inline:boolean, private createPortal: PortalFactory) { super(); } @@ -40,7 +63,11 @@ class Widget extends WidgetType { toDOM(view: EditorView) { const dom = document.createElement("div"); - this.portalCleanup = this.createPortal(dom, React.createElement(this.component, this.props, view)); + if(this.inline) { + dom.style.display = 'inline-block'; // want it inline for the open-close widget + this.portalCleanup = this.createPortal(dom, <OpenReactComponent loc={this.props.to} view={view} />); + } + else this.portalCleanup = this.createPortal(dom, React.createElement(this.component, this.props, view)); return dom; } @@ -55,6 +82,7 @@ class Widget extends WidgetType { } } + // Iterates through the syntax tree, finding occurences of SoundEffect ArgList, and places toy widget there export const reactWidgetExtension = ( createPortal: PortalFactory @@ -70,11 +98,20 @@ export const reactWidgetExtension = ( let name = state.doc.sliceString(ref.node.parent.from, ref.from) let widget = createWidget(name, state, ref.node); if(widget) { - let deco = Decoration.widget({ - widget: new Widget(widget.comp, widget.props, createPortal), - side: 1, - }); - widgets.push(deco.range(ref.to)); + if(widget.props.to == openWidgetLoc){ + let deco = Decoration.widget({ + widget: new Widget(widget.comp, widget.props, true, createPortal), + side: 1, + }); + widgets.push(deco.range(ref.to)); + } + else{ + let deco = Decoration.widget({ + widget: new Widget(widget.comp, widget.props, false, createPortal), + side: 1, + }); + widgets.push(deco.range(ref.to)); + } } } } @@ -89,6 +126,11 @@ export const reactWidgetExtension = ( }, update(widgets, transaction) { if (transaction.docChanged) { + transaction.changes.iterChangedRanges((_fromA, _toA, _fromB, _toB) => { + if(_toA <= openWidgetLoc){ + openWidgetLoc += (_toB - _fromB) - (_toA - _fromA) + } + }); return decorate(transaction.state); } return widgets.map(transaction.changes); From 15a997129e49438bacc9213b0386a56c6e9faf1c Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Tue, 2 Apr 2024 10:02:33 -0400 Subject: [PATCH 056/106] != --- src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx index 265688af7..2a83755fb 100644 --- a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx +++ b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx @@ -98,7 +98,7 @@ export const reactWidgetExtension = ( let name = state.doc.sliceString(ref.node.parent.from, ref.from) let widget = createWidget(name, state, ref.node); if(widget) { - if(widget.props.to == openWidgetLoc){ + if(widget.props.to != openWidgetLoc){ let deco = Decoration.widget({ widget: new Widget(widget.comp, widget.props, true, createPortal), side: 1, From d879e083b7558eaecc90afe3d2a80366f7886d78 Mon Sep 17 00:00:00 2001 From: Marta Religa <marta.religa@cs.ox.ac.uk> Date: Tue, 2 Apr 2024 16:04:27 +0200 Subject: [PATCH 057/106] . --- src/editor/codemirror/helper-widgets/setPixelWidget.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx index 620a92e94..64daaebb8 100644 --- a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx +++ b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx @@ -1,6 +1,9 @@ import { Box, Button, Slider, SliderTrack, SliderFilledTrack, SliderThumb } from "@chakra-ui/react"; import { useState } from "react"; import { WidgetProps } from "./reactWidgetExtension"; +import { + EditorView, +} from "@codemirror/view"; interface Pixel { x: number; @@ -84,7 +87,7 @@ const validateArgs = (args : number[]) => { }; -export const MicrobitSinglePixelComponent = ({ args, ranges, literals, from, to }: WidgetProps) => { +export const MicrobitSinglePixelComponent = ({ args, ranges, literals, from, to }: WidgetProps, view:EditorView) => { const [selectedPixel, setSelectedPixel] = useState<Pixel | null>(null); if (validateArgs(args)){ const [x, y, brightness] = parseArgs(args); @@ -100,14 +103,13 @@ export const MicrobitSinglePixelComponent = ({ args, ranges, literals, from, to if (selectedPixel !== null) { const { x, y, brightness } = selectedPixel; console.log(`(${x}, ${y}, ${brightness}) `); - /*view.dispatch({ + view.dispatch({ changes: { from: from, to: to, insert: `(${x}, ${y}, ${brightness}) `, } }); - */ } }; From f2b9ccf146f6fc73adcca1364466117ec8995976 Mon Sep 17 00:00:00 2001 From: Marta Religa <marta.religa@cs.ox.ac.uk> Date: Tue, 2 Apr 2024 16:12:31 +0200 Subject: [PATCH 058/106] state --- .../helper-widgets/setPixelWidget.tsx | 112 +++++++++--------- 1 file changed, 59 insertions(+), 53 deletions(-) diff --git a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx index 64daaebb8..6da193f48 100644 --- a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx +++ b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx @@ -1,9 +1,16 @@ -import { Box, Button, Slider, SliderTrack, SliderFilledTrack, SliderThumb } from "@chakra-ui/react"; -import { useState } from "react"; -import { WidgetProps } from "./reactWidgetExtension"; + +import { + Box, + Button, + Slider, + SliderTrack, + SliderFilledTrack, + SliderThumb, +} from "@chakra-ui/react"; import { EditorView, } from "@codemirror/view"; +import { WidgetProps } from "./reactWidgetExtension"; interface Pixel { x: number; @@ -13,43 +20,48 @@ interface Pixel { interface MicrobitSinglePixelGridProps { onPixelClick: (pixel: Pixel) => void; - initialPixel : Pixel | null; + initialPixel: Pixel | null; } -const MicrobitSinglePixelGrid: React.FC<MicrobitSinglePixelGridProps> = ({ onPixelClick, initialPixel }) => { - const [selectedPixel, setSelectedPixel] = useState<Pixel | null>(initialPixel); - const [currentBrightness, setCurrentBrightness] = useState<number>(5); - +const MicrobitSinglePixelGrid: React.FC<MicrobitSinglePixelGridProps> = ({ + onPixelClick, + initialPixel, +}) => { + const { x, y, brightness } = initialPixel ?? { x: 0, y: 0, brightness: 9 }; const handlePixelClick = (x: number, y: number) => { - const newPixel: Pixel = { x: x, y: y, brightness: currentBrightness }; - setSelectedPixel(newPixel); + const newPixel: Pixel = { x, y, brightness }; onPixelClick(newPixel); }; - const handleSliderChange = (value: number) => { - setCurrentBrightness(value); - if (selectedPixel) { - const updatedPixel: Pixel = { ...selectedPixel, brightness: value }; - onPixelClick(updatedPixel); - } + const updatedPixel: Pixel = { x, y, brightness: value }; + onPixelClick(updatedPixel); }; return ( <Box display="flex" flexDirection="row" justifyContent="flex-start"> <Box> <Box bg="black" p="10px" borderRadius="5px"> - {[...Array(5)].map((_, y) => ( + {[...Array(5)].map((_, gridY) => ( <Box key={y} display="flex"> - {[...Array(5)].map((_, x) => ( + {[...Array(5)].map((_, gridX) => ( <Box key={x} display="flex" mr="2px"> <Button size="xs" h="15px" w="15px" p={0} - bgColor={selectedPixel?.x === x && selectedPixel.y === y ? `rgba(255, 0, 0, ${currentBrightness / 9})` : "rgba(255, 255, 255, 0)"} - _hover={{ bgColor: selectedPixel?.x === x && selectedPixel.y === y ? `rgba(255, 0, 0, ${currentBrightness / 9})` : "rgba(255, 255, 255, 0.5)" }} - onClick={() => handlePixelClick(x, y)} + bgColor={ + gridX === x && gridY === y + ? `rgba(255, 0, 0, ${brightness / 9})` + : "rgba(255, 255, 255, 0)" + } + _hover={{ + bgColor: + gridX === x && gridY === y + ? `rgba(255, 0, 0, ${brightness / 9})` + : "rgba(255, 255, 255, 0.5)", + }} + onClick={() => handlePixelClick(gridX, gridY)} /> </Box> ))} @@ -60,14 +72,15 @@ const MicrobitSinglePixelGrid: React.FC<MicrobitSinglePixelGridProps> = ({ onPix <Box ml="10px"> <Slider aria-label="brightness" - defaultValue={currentBrightness} + defaultValue={brightness} min={0} max={9} step={1} orientation="vertical" _focus={{ boxShadow: "none" }} _active={{ bgColor: "transparent" }} - onChange={handleSliderChange}> + onChange={handleSliderChange} + > <SliderTrack> <SliderFilledTrack /> </SliderTrack> @@ -78,40 +91,33 @@ const MicrobitSinglePixelGrid: React.FC<MicrobitSinglePixelGridProps> = ({ onPix ); }; -const parseArgs = (args : number[]) => { - return args -}; - -const validateArgs = (args : number[]) => { - return Array.isArray(args) && args.length === 3 +const parseArgs = (args: number[]): Pixel | null => { + if (Array.isArray(args) && args.length === 3) { + const [x, y, brightness] = args; + return { x, y, brightness }; + }; + return {x:1, y:1, brightness:1}; }; - -export const MicrobitSinglePixelComponent = ({ args, ranges, literals, from, to }: WidgetProps, view:EditorView) => { - const [selectedPixel, setSelectedPixel] = useState<Pixel | null>(null); - if (validateArgs(args)){ - const [x, y, brightness] = parseArgs(args); - setSelectedPixel({ x, y, brightness }); - } +export const MicrobitSinglePixelComponent = ({ args, ranges, literals, from, to }: WidgetProps, view:EditorView) => { + const selectedPixel = parseArgs(args); const handleSelectPixel = (pixel: Pixel) => { - setSelectedPixel(pixel); - updateView(); + const { x, y, brightness } = pixel; + console.log(`(${x}, ${y}, ${brightness}) `); + view.dispatch({ + changes: { + from: from, + to: to, + insert: `(${x}, ${y}, ${brightness}) `, + }, + }); }; - const updateView = () => { - if (selectedPixel !== null) { - const { x, y, brightness } = selectedPixel; - console.log(`(${x}, ${y}, ${brightness}) `); - view.dispatch({ - changes: { - from: from, - to: to, - insert: `(${x}, ${y}, ${brightness}) `, - } - }); - } - }; - - return (<MicrobitSinglePixelGrid onPixelClick={handleSelectPixel} initialPixel={selectedPixel} />); + return ( + <MicrobitSinglePixelGrid + onPixelClick={handleSelectPixel} + initialPixel={selectedPixel} + /> + ); }; From 4cbdaf14788b4d85d69471f2b30536a0bb68e05b Mon Sep 17 00:00:00 2001 From: Marta Religa <marta.religa@cs.ox.ac.uk> Date: Tue, 2 Apr 2024 16:15:34 +0200 Subject: [PATCH 059/106] . --- .../codemirror/helper-widgets/showImageWidget.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/editor/codemirror/helper-widgets/showImageWidget.tsx b/src/editor/codemirror/helper-widgets/showImageWidget.tsx index 22d58acba..03658f3a2 100644 --- a/src/editor/codemirror/helper-widgets/showImageWidget.tsx +++ b/src/editor/codemirror/helper-widgets/showImageWidget.tsx @@ -1,6 +1,9 @@ import { Box, Button, Slider, SliderTrack, SliderFilledTrack, SliderThumb } from "@chakra-ui/react"; import React, { useState } from "react"; import { WidgetProps } from "./reactWidgetExtension"; +import { + EditorView, +} from "@codemirror/view"; interface MultiMicrobitGridProps { selectedPixels: number[][]; @@ -71,7 +74,7 @@ const MicrobitMultiplePixelsGrid: React.FC<MultiMicrobitGridProps> = ({ ); }; -export const MicrobitMultiplePixelComponent = ({ args, ranges, literals, from, to }: WidgetProps) => { +export const MicrobitMultiplePixelComponent = ({ args, ranges, literals, from, to }: WidgetProps, view:EditorView) => { let initialSelectedPixels: number[][] = Array.from({ length: 5 }, () => Array(5).fill(0)); if (validateArgs(args)) { initialSelectedPixels = parseArgs(args) @@ -88,13 +91,12 @@ export const MicrobitMultiplePixelComponent = ({ args, ranges, literals, from, t const updateView = () => { let insertion = pixelsToString(selectedPixels); console.log(insertion); - /*view.dispatch({ + view.dispatch({ changes: { from: from, to: to, insert: insertion} - }); - */ + }); } return ( From 8b1d34e3c62e43e71dcec86b8d0a5360e13a3ae6 Mon Sep 17 00:00:00 2001 From: Marta Religa <marta.religa@cs.ox.ac.uk> Date: Tue, 2 Apr 2024 19:07:04 +0200 Subject: [PATCH 060/106] added close button --- .../helper-widgets/setPixelWidget.tsx | 37 ++++++++++++++----- .../helper-widgets/showImageWidget.tsx | 16 +++++++- 2 files changed, 42 insertions(+), 11 deletions(-) diff --git a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx index 6da193f48..ff6e67b68 100644 --- a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx +++ b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx @@ -1,4 +1,4 @@ - +import React from "react"; import { Box, Button, @@ -7,9 +7,7 @@ import { SliderFilledTrack, SliderThumb, } from "@chakra-ui/react"; -import { - EditorView, -} from "@codemirror/view"; +import { EditorView } from "@codemirror/view"; import { WidgetProps } from "./reactWidgetExtension"; interface Pixel { @@ -21,11 +19,13 @@ interface Pixel { interface MicrobitSinglePixelGridProps { onPixelClick: (pixel: Pixel) => void; initialPixel: Pixel | null; + onCloseClick: () => void; } const MicrobitSinglePixelGrid: React.FC<MicrobitSinglePixelGridProps> = ({ onPixelClick, initialPixel, + onCloseClick, }) => { const { x, y, brightness } = initialPixel ?? { x: 0, y: 0, brightness: 9 }; const handlePixelClick = (x: number, y: number) => { @@ -39,8 +39,18 @@ const MicrobitSinglePixelGrid: React.FC<MicrobitSinglePixelGridProps> = ({ return ( <Box display="flex" flexDirection="row" justifyContent="flex-start"> + <Box ml="10px" style={{ marginRight: "4px" }}> + <Button size="xs" onClick={onCloseClick}> + X + </Button> + </Box> <Box> - <Box bg="black" p="10px" borderRadius="5px"> + <Box + bg="black" + p="10px" + borderRadius="5px" + style={{ marginTop: "15px" }} + > {[...Array(5)].map((_, gridY) => ( <Box key={y} display="flex"> {[...Array(5)].map((_, gridX) => ( @@ -69,7 +79,7 @@ const MicrobitSinglePixelGrid: React.FC<MicrobitSinglePixelGridProps> = ({ ))} </Box> </Box> - <Box ml="10px"> + <Box ml="10px" style={{ marginTop: "15px" }}> <Slider aria-label="brightness" defaultValue={brightness} @@ -95,15 +105,23 @@ const parseArgs = (args: number[]): Pixel | null => { if (Array.isArray(args) && args.length === 3) { const [x, y, brightness] = args; return { x, y, brightness }; - }; - return {x:1, y:1, brightness:1}; + } + return { x: 1, y: 1, brightness: 1 }; }; -export const MicrobitSinglePixelComponent = ({ args, ranges, literals, from, to }: WidgetProps, view:EditorView) => { +export const MicrobitSinglePixelComponent = ( + { args, ranges, literals, from, to }: WidgetProps, + view: EditorView +) => { const selectedPixel = parseArgs(args); + const handleCloseClick = () => { + console.log("closed"); + }; + const handleSelectPixel = (pixel: Pixel) => { const { x, y, brightness } = pixel; + console.log("ye" + view.inView); console.log(`(${x}, ${y}, ${brightness}) `); view.dispatch({ changes: { @@ -118,6 +136,7 @@ export const MicrobitSinglePixelComponent = ({ args, ranges, literals, from, to <MicrobitSinglePixelGrid onPixelClick={handleSelectPixel} initialPixel={selectedPixel} + onCloseClick={handleCloseClick} /> ); }; diff --git a/src/editor/codemirror/helper-widgets/showImageWidget.tsx b/src/editor/codemirror/helper-widgets/showImageWidget.tsx index 03658f3a2..58d60bc3f 100644 --- a/src/editor/codemirror/helper-widgets/showImageWidget.tsx +++ b/src/editor/codemirror/helper-widgets/showImageWidget.tsx @@ -7,11 +7,13 @@ import { interface MultiMicrobitGridProps { selectedPixels: number[][]; + onCloseClick: () => void; onPixelChange: (x: number, y: number, brightness: number) => void; } const MicrobitMultiplePixelsGrid: React.FC<MultiMicrobitGridProps> = ({ selectedPixels, + onCloseClick, onPixelChange, }) => { const [currentBrightness, setCurrentBrightness] = useState<number>(5); @@ -31,8 +33,13 @@ const MicrobitMultiplePixelsGrid: React.FC<MultiMicrobitGridProps> = ({ return ( <Box display="flex" flexDirection="row" justifyContent="flex-start"> + <Box ml="10px" style={{ marginRight: '4px' }}> + <Button size="xs" onClick={onCloseClick}> + X + </Button> + </Box> <Box> - <Box bg="black" p="10px" borderRadius="5px"> + <Box bg="black" p="10px" borderRadius="5px" style={{ marginTop: '15px' }}> {selectedPixels.map((row, y) => ( <Box key={y} display="flex"> {row.map((brightness, x) => ( @@ -52,7 +59,7 @@ const MicrobitMultiplePixelsGrid: React.FC<MultiMicrobitGridProps> = ({ ))} </Box> </Box> - <Box ml="10px"> + <Box ml="10px" style={{ marginTop: '15px' }}> <Slider aria-label="brightness" value={currentBrightness} @@ -99,10 +106,15 @@ export const MicrobitMultiplePixelComponent = ({ args, ranges, literals, from, }); } + const handleCloseClick = () => { + console.log("closed"); + }; + return ( <MicrobitMultiplePixelsGrid selectedPixels={selectedPixels} onPixelChange={handlePixelChange} + onCloseClick={handleCloseClick} /> ); }; From 5e9f6cdbff36d6c24dc2dd6dcc1fbab99e122526 Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Sun, 7 Apr 2024 10:39:52 -0400 Subject: [PATCH 061/106] Better argument parsing + fixed warnings - properly pass arguments - passes ranges for insertion --- .../helper-widgets/reactWidgetExtension.tsx | 29 ++--- .../helper-widgets/setPixelWidget.tsx | 2 +- .../helper-widgets/showImageWidget.tsx | 2 +- .../helper-widgets/widgetArgParser.tsx | 107 ++++++++++-------- 4 files changed, 69 insertions(+), 71 deletions(-) diff --git a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx index 2a83755fb..81f1933d3 100644 --- a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx +++ b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx @@ -1,4 +1,4 @@ -import { Button, HStack, Text } from "@chakra-ui/react"; +import { Button, HStack } from "@chakra-ui/react"; import { EditorState, Extension, StateField } from "@codemirror/state"; import { Decoration, @@ -10,8 +10,6 @@ import { syntaxTree } from "@codemirror/language" import { PortalFactory } from "../CodeMirror"; import React from "react"; import { useCallback } from "react"; -import { MicrobitSinglePixelComponent } from "./setPixelWidget"; -import { MicrobitMultiplePixelComponent } from "./showImageWidget" import { createWidget } from "./widgetArgParser"; export interface WidgetProps { @@ -19,8 +17,8 @@ export interface WidgetProps { args: any[] // Ranges of where to insert each argument ranges: {from:number, to:number} [] - // Whether or not each argument is a literal - literals: boolean[] + // Type of each argument, can be checked in widget to determine if it is editable + types: string[] // Where to insert the changed values from: number, to: number @@ -39,7 +37,7 @@ const OpenReactComponent = ({ loc, view }: { loc: number, view: EditorView }) => insert: view.state.doc.sliceString(0, 1), } }); - }, []); + }, [loc, view]); return ( <HStack fontFamily="body" spacing={5} py={3}> <Button onClick={handleClick}>Open</Button> @@ -98,20 +96,11 @@ export const reactWidgetExtension = ( let name = state.doc.sliceString(ref.node.parent.from, ref.from) let widget = createWidget(name, state, ref.node); if(widget) { - if(widget.props.to != openWidgetLoc){ - let deco = Decoration.widget({ - widget: new Widget(widget.comp, widget.props, true, createPortal), - side: 1, - }); - widgets.push(deco.range(ref.to)); - } - else{ - let deco = Decoration.widget({ - widget: new Widget(widget.comp, widget.props, false, createPortal), - side: 1, - }); - widgets.push(deco.range(ref.to)); - } + let deco = Decoration.widget({ + widget: new Widget(widget.comp, widget.props, widget.props.to !== openWidgetLoc, createPortal), + side: 1, + }); + widgets.push(deco.range(ref.to)); } } } diff --git a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx index ff6e67b68..5f5a110e6 100644 --- a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx +++ b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx @@ -110,7 +110,7 @@ const parseArgs = (args: number[]): Pixel | null => { }; export const MicrobitSinglePixelComponent = ( - { args, ranges, literals, from, to }: WidgetProps, + { args, ranges, types, from, to }: WidgetProps, view: EditorView ) => { const selectedPixel = parseArgs(args); diff --git a/src/editor/codemirror/helper-widgets/showImageWidget.tsx b/src/editor/codemirror/helper-widgets/showImageWidget.tsx index 58d60bc3f..65c698ca1 100644 --- a/src/editor/codemirror/helper-widgets/showImageWidget.tsx +++ b/src/editor/codemirror/helper-widgets/showImageWidget.tsx @@ -81,7 +81,7 @@ const MicrobitMultiplePixelsGrid: React.FC<MultiMicrobitGridProps> = ({ ); }; -export const MicrobitMultiplePixelComponent = ({ args, ranges, literals, from, to }: WidgetProps, view:EditorView) => { +export const MicrobitMultiplePixelComponent = ({ args, ranges, types, from, to }: WidgetProps, view:EditorView) => { let initialSelectedPixels: number[][] = Array.from({ length: 5 }, () => Array(5).fill(0)); if (validateArgs(args)) { initialSelectedPixels = parseArgs(args) diff --git a/src/editor/codemirror/helper-widgets/widgetArgParser.tsx b/src/editor/codemirror/helper-widgets/widgetArgParser.tsx index e78084afc..e0ba7ef18 100644 --- a/src/editor/codemirror/helper-widgets/widgetArgParser.tsx +++ b/src/editor/codemirror/helper-widgets/widgetArgParser.tsx @@ -1,10 +1,4 @@ import { EditorState } from "@codemirror/state"; -import { - Decoration, - DecorationSet, - EditorView, - WidgetType, -} from "@codemirror/view"; import { SyntaxNode } from "@lezer/common"; import { WidgetProps } from "./reactWidgetExtension"; import { MicrobitSinglePixelComponent } from "./setPixelWidget"; @@ -16,57 +10,72 @@ export interface CompProps { } export function createWidget(name: string, state: EditorState, node: SyntaxNode): CompProps | null { + let children = getChildNodes(node); + let ranges = getRanges(children); + let args = getArgs(state, ranges); + let types = getTypes(children); + let component: React.ComponentType<any> | null = null + switch (name) { case "display.set_pixel": - return { - comp: MicrobitSinglePixelComponent, - props: { - args: [], - ranges: [], - literals: [], - from: node.from, - to: node.to - } - } - // // TODO: assuming all literals for now, will probably want a way to detect other types of arguments - // let args: number[] = []; - // ref.node.getChildren("Number").forEach(function (child) { args.push(+state.doc.sliceString(child.from, child.to)) }); - - // createWidget<number>(MicrobitSinglePixelComponent, args, ref.from, ref.to); + component = MicrobitSinglePixelComponent; + break; case "Image": - return { - comp: MicrobitMultiplePixelComponent, - props: { - args: [], - ranges: [], - literals: [], - from: node.from, - to: node.to - } - } - - // TODO: does not handle comments properly - // let imArg: string[] = [] - // let arg = ref.node.getChild("ContinuedString"); - // if (arg) imArg.push(state.doc.sliceString(arg.from, arg.to).replaceAll(/[' \n]/g, "")); - // else { - // arg = ref.node.getChild("String"); - // if (arg) imArg.push() - // } - - // createWidget<string>(MicrobitMultiplePixelComponent, imArg, ref.from, ref.to); - // break; + component = MicrobitMultiplePixelComponent; + break; default: // No widget implemented for this function return null; } + if(component){ + return { + comp: component, + props: { + args: args, + ranges: ranges, + types: types, + from: node.from, + to: node.to + } + } + } + return null; } -// Pre: args are all of type number -export function numberArgs(state: EditorState, args: any[], node:SyntaxNode): number[] { - let nums = [] - args.forEach(function (value) { +// Gets all child nodes of a CallExpression, no typechecking +function getChildNodes(node:SyntaxNode): SyntaxNode[] { + let child = node.firstChild?.nextSibling; + let children = [] + while(child && child.name !== ")"){ + if(child.name !== ",") children.push(child); + child = child.nextSibling; + } + return children; +} + +// Gets ranges for insertion into arguments +function getRanges(nodes:SyntaxNode[]):{from:number, to:number}[] { + let ranges: {from:number, to:number}[] = [] + nodes.forEach(function(value) { + ranges.push({from:value.from, to:value.to}) + }) + return ranges; +} + +// Gets arguments as string +function getArgs(state:EditorState, ranges:{from:number, to:number}[]): string[] { + let args: string[] = [] + ranges.forEach(function(value) { + args.push(state.doc.sliceString(value.from, value.to)); + }) + return args; +} - }); - return [] +// Gets types of each arg to determine if it is editable +function getTypes(nodes:SyntaxNode[]):string[] { + let types: string[] = [] + nodes.forEach(function(value) { + types.push(value.name) + }) + return types; } \ No newline at end of file From 94ab44737499d83e3b2d1e3c8d8a861124d71268 Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Sun, 7 Apr 2024 12:39:17 -0400 Subject: [PATCH 062/106] open/close now uses effects --- .../codemirror/helper-widgets/openWidgets.tsx | 18 ++++++++++ .../helper-widgets/reactWidgetExtension.tsx | 35 +++++++------------ 2 files changed, 31 insertions(+), 22 deletions(-) create mode 100644 src/editor/codemirror/helper-widgets/openWidgets.tsx diff --git a/src/editor/codemirror/helper-widgets/openWidgets.tsx b/src/editor/codemirror/helper-widgets/openWidgets.tsx new file mode 100644 index 000000000..fadb802aa --- /dev/null +++ b/src/editor/codemirror/helper-widgets/openWidgets.tsx @@ -0,0 +1,18 @@ +import { Button, HStack } from "@chakra-ui/react"; +import { StateEffect } from "@codemirror/state"; +import { EditorView } from "@codemirror/view"; +import { useCallback } from "react"; + +export const openWidgetEffect = StateEffect.define<number>(); +export const OpenReactComponent = ({ loc, view }: { loc: number, view: EditorView }) => { + const handleClick = useCallback(() => { + view.dispatch({ + effects: [openWidgetEffect.of(loc)], + }); + }, [loc, view]); + return ( + <HStack fontFamily="body" spacing={5} py={3}> + <Button onClick={handleClick}>Open</Button> + </HStack> + ); +}; \ No newline at end of file diff --git a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx index 81f1933d3..55c132cea 100644 --- a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx +++ b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx @@ -1,4 +1,3 @@ -import { Button, HStack } from "@chakra-ui/react"; import { EditorState, Extension, StateField } from "@codemirror/state"; import { Decoration, @@ -9,8 +8,8 @@ import { import { syntaxTree } from "@codemirror/language" import { PortalFactory } from "../CodeMirror"; import React from "react"; -import { useCallback } from "react"; import { createWidget } from "./widgetArgParser"; +import { OpenReactComponent, openWidgetEffect } from "./openWidgets"; export interface WidgetProps { // Note: always an array, can be singleton @@ -24,26 +23,7 @@ export interface WidgetProps { to: number } -// Location of currently open widget, -1 if all closed -export let openWidgetLoc = -1; -const OpenReactComponent = ({ loc, view }: { loc: number, view: EditorView }) => { - const handleClick = useCallback(() => { - openWidgetLoc = loc; - // TODO: not sure how to force a view update without a list of changes - view.dispatch({ - changes: { - from: 0, - to: 1, - insert: view.state.doc.sliceString(0, 1), - } - }); - }, [loc, view]); - return ( - <HStack fontFamily="body" spacing={5} py={3}> - <Button onClick={handleClick}>Open</Button> - </HStack> - ); -}; + /** * This widget will have its contents rendered by the code in CodeMirror.tsx @@ -109,12 +89,23 @@ export const reactWidgetExtension = ( return Decoration.set(widgets) }; + let openWidgetLoc = -1; const stateField = StateField.define<DecorationSet>({ create(state) { return decorate(state); }, update(widgets, transaction) { + // check for open/close button pressed + for (let effect of transaction.effects) { + if (effect.is(openWidgetEffect)) { + openWidgetLoc = effect.value; + return decorate(transaction.state); + } + } + // else check for other doc edits if (transaction.docChanged) { + // update openWidgetLoc if changes moves it + // transaction.changes.mapPos() transaction.changes.iterChangedRanges((_fromA, _toA, _fromB, _toB) => { if(_toA <= openWidgetLoc){ openWidgetLoc += (_toB - _fromB) - (_toA - _fromA) From 137bd10d7a273b48da675a4da945121fb5190559 Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Sun, 7 Apr 2024 12:56:09 -0400 Subject: [PATCH 063/106] view issue fixed, close button added --- .../helper-widgets/reactWidgetExtension.tsx | 3 ++- .../codemirror/helper-widgets/setPixelWidget.tsx | 10 +++++++--- .../codemirror/helper-widgets/showImageWidget.tsx | 11 +++++++++-- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx index 55c132cea..49aa2c097 100644 --- a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx +++ b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx @@ -45,7 +45,8 @@ class Widget extends WidgetType { dom.style.display = 'inline-block'; // want it inline for the open-close widget this.portalCleanup = this.createPortal(dom, <OpenReactComponent loc={this.props.to} view={view} />); } - else this.portalCleanup = this.createPortal(dom, React.createElement(this.component, this.props, view)); + else this.portalCleanup = this.createPortal(dom, <this.component props={this.props} view={view} />); + //else this.portalCleanup = this.createPortal(dom, React.createElement(this.component, this.props, view)); return dom; } diff --git a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx index 5f5a110e6..717ebbf9b 100644 --- a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx +++ b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx @@ -9,6 +9,7 @@ import { } from "@chakra-ui/react"; import { EditorView } from "@codemirror/view"; import { WidgetProps } from "./reactWidgetExtension"; +import { openWidgetEffect } from "./openWidgets"; interface Pixel { x: number; @@ -110,13 +111,16 @@ const parseArgs = (args: number[]): Pixel | null => { }; export const MicrobitSinglePixelComponent = ( - { args, ranges, types, from, to }: WidgetProps, - view: EditorView + { props, view } : {props: WidgetProps, view: EditorView} ) => { + let args = props.args; let ranges = props.ranges; let types = props.types; let from = props.from; let to = props.to; + const selectedPixel = parseArgs(args); const handleCloseClick = () => { - console.log("closed"); + view.dispatch({ + effects: [openWidgetEffect.of(-1)], + }); }; const handleSelectPixel = (pixel: Pixel) => { diff --git a/src/editor/codemirror/helper-widgets/showImageWidget.tsx b/src/editor/codemirror/helper-widgets/showImageWidget.tsx index 65c698ca1..97aaef777 100644 --- a/src/editor/codemirror/helper-widgets/showImageWidget.tsx +++ b/src/editor/codemirror/helper-widgets/showImageWidget.tsx @@ -4,6 +4,7 @@ import { WidgetProps } from "./reactWidgetExtension"; import { EditorView, } from "@codemirror/view"; +import { openWidgetEffect } from "./openWidgets"; interface MultiMicrobitGridProps { selectedPixels: number[][]; @@ -81,7 +82,11 @@ const MicrobitMultiplePixelsGrid: React.FC<MultiMicrobitGridProps> = ({ ); }; -export const MicrobitMultiplePixelComponent = ({ args, ranges, types, from, to }: WidgetProps, view:EditorView) => { +export const MicrobitMultiplePixelComponent = ( + { props, view } : {props: WidgetProps, view: EditorView} +) => { + let args = props.args; let ranges = props.ranges; let types = props.types; let from = props.from; let to = props.to; + let initialSelectedPixels: number[][] = Array.from({ length: 5 }, () => Array(5).fill(0)); if (validateArgs(args)) { initialSelectedPixels = parseArgs(args) @@ -107,7 +112,9 @@ export const MicrobitMultiplePixelComponent = ({ args, ranges, types, from, to } const handleCloseClick = () => { - console.log("closed"); + view.dispatch({ + effects: [openWidgetEffect.of(-1)], + }); }; return ( From 11431aec7dcd705548a7b88bfc2541129f4eb4ec Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Sun, 7 Apr 2024 13:09:06 -0400 Subject: [PATCH 064/106] use mapPos instead of iterChangedRanges --- .../codemirror/helper-widgets/reactWidgetExtension.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx index 49aa2c097..ea88e64b6 100644 --- a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx +++ b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx @@ -106,12 +106,7 @@ export const reactWidgetExtension = ( // else check for other doc edits if (transaction.docChanged) { // update openWidgetLoc if changes moves it - // transaction.changes.mapPos() - transaction.changes.iterChangedRanges((_fromA, _toA, _fromB, _toB) => { - if(_toA <= openWidgetLoc){ - openWidgetLoc += (_toB - _fromB) - (_toA - _fromA) - } - }); + openWidgetLoc = transaction.changes.mapPos(openWidgetLoc); return decorate(transaction.state); } return widgets.map(transaction.changes); From a5fb3024cc2682744e22ff314b772e03635eae86 Mon Sep 17 00:00:00 2001 From: Marta Religa <marta.religa@cs.ox.ac.uk> Date: Mon, 8 Apr 2024 10:08:10 +0200 Subject: [PATCH 065/106] parsing for setpixel --- .../helper-widgets/setPixelWidget.tsx | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx index 717ebbf9b..ccb82c5f9 100644 --- a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx +++ b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx @@ -102,20 +102,34 @@ const MicrobitSinglePixelGrid: React.FC<MicrobitSinglePixelGridProps> = ({ ); }; -const parseArgs = (args: number[]): Pixel | null => { - if (Array.isArray(args) && args.length === 3) { - const [x, y, brightness] = args; - return { x, y, brightness }; +const parseArgs = (args: string[], types: string[]): Pixel | null => { + if (args.length > 3) { + return null; // If there are more than 3 arguments, return null } - return { x: 1, y: 1, brightness: 1 }; + const parsedArgs: number[] = []; + for (let i = 0; i < args.length; i++) { + let arg = args[i] + if (types[i] === 'Number') { + parsedArgs.push(parseInt(arg)); + } else if (arg == ","){ + parsedArgs.push(0); // For non-number types, default to 0 + } else { + return null + } + } + while (parsedArgs.length < 3) { + parsedArgs.push(0); + } + return {x: parsedArgs[0], y: parsedArgs[1], brightness : parsedArgs[2]} }; export const MicrobitSinglePixelComponent = ( { props, view } : {props: WidgetProps, view: EditorView} ) => { let args = props.args; let ranges = props.ranges; let types = props.types; let from = props.from; let to = props.to; - - const selectedPixel = parseArgs(args); + console.log(args) + console.log(types) + const selectedPixel = parseArgs(args, types); const handleCloseClick = () => { view.dispatch({ @@ -125,8 +139,6 @@ export const MicrobitSinglePixelComponent = ( const handleSelectPixel = (pixel: Pixel) => { const { x, y, brightness } = pixel; - console.log("ye" + view.inView); - console.log(`(${x}, ${y}, ${brightness}) `); view.dispatch({ changes: { from: from, From 6cc60ef8191cb1c31d712de044c5b0cd73d8d10e Mon Sep 17 00:00:00 2001 From: Marta Religa <marta.religa@cs.ox.ac.uk> Date: Mon, 8 Apr 2024 12:09:20 +0200 Subject: [PATCH 066/106] . --- .../helper-widgets/setPixelWidget.tsx | 41 ++++--- .../helper-widgets/showImageWidget.tsx | 107 +++++++++++++----- 2 files changed, 104 insertions(+), 44 deletions(-) diff --git a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx index ccb82c5f9..ee701ab9b 100644 --- a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx +++ b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx @@ -102,35 +102,46 @@ const MicrobitSinglePixelGrid: React.FC<MicrobitSinglePixelGridProps> = ({ ); }; -const parseArgs = (args: string[], types: string[]): Pixel | null => { +const parseArgs = (args: string[], types: string[]): Pixel | null => { if (args.length > 3) { - return null; // If there are more than 3 arguments, return null + return null; } const parsedArgs: number[] = []; for (let i = 0; i < args.length; i++) { - let arg = args[i] - if (types[i] === 'Number') { - parsedArgs.push(parseInt(arg)); - } else if (arg == ","){ - parsedArgs.push(0); // For non-number types, default to 0 + let arg = args[i]; + if (types[i] === "Number") { + parsedArgs.push(parseInt(arg)); + } else if (arg == ",") { + parsedArgs.push(0); } else { - return null + return null; } } while (parsedArgs.length < 3) { parsedArgs.push(0); } - return {x: parsedArgs[0], y: parsedArgs[1], brightness : parsedArgs[2]} + return { x: parsedArgs[0], y: parsedArgs[1], brightness: parsedArgs[2] }; }; -export const MicrobitSinglePixelComponent = ( - { props, view } : {props: WidgetProps, view: EditorView} -) => { - let args = props.args; let ranges = props.ranges; let types = props.types; let from = props.from; let to = props.to; - console.log(args) - console.log(types) +export const MicrobitSinglePixelComponent = ({ + props, + view, +}: { + props: WidgetProps; + view: EditorView; +}) => { + let args = props.args; + let ranges = props.ranges; + let types = props.types; + let from = props.from; + let to = props.to; + console.log(args); + console.log(types); const selectedPixel = parseArgs(args, types); + if (selectedPixel == null) { + } + const handleCloseClick = () => { view.dispatch({ effects: [openWidgetEffect.of(-1)], diff --git a/src/editor/codemirror/helper-widgets/showImageWidget.tsx b/src/editor/codemirror/helper-widgets/showImageWidget.tsx index 97aaef777..0238e36c9 100644 --- a/src/editor/codemirror/helper-widgets/showImageWidget.tsx +++ b/src/editor/codemirror/helper-widgets/showImageWidget.tsx @@ -1,9 +1,14 @@ -import { Box, Button, Slider, SliderTrack, SliderFilledTrack, SliderThumb } from "@chakra-ui/react"; +import { + Box, + Button, + Slider, + SliderTrack, + SliderFilledTrack, + SliderThumb, +} from "@chakra-ui/react"; import React, { useState } from "react"; import { WidgetProps } from "./reactWidgetExtension"; -import { - EditorView, -} from "@codemirror/view"; +import { EditorView } from "@codemirror/view"; import { openWidgetEffect } from "./openWidgets"; interface MultiMicrobitGridProps { @@ -18,7 +23,10 @@ const MicrobitMultiplePixelsGrid: React.FC<MultiMicrobitGridProps> = ({ onPixelChange, }) => { const [currentBrightness, setCurrentBrightness] = useState<number>(5); - const [selectedPixel, setSelectedPixel] = useState<{ x: number; y: number } | null>(null); + const [selectedPixel, setSelectedPixel] = useState<{ + x: number; + y: number; + } | null>(null); const handlePixelClick = (x: number, y: number) => { setSelectedPixel({ x, y }); @@ -34,13 +42,18 @@ const MicrobitMultiplePixelsGrid: React.FC<MultiMicrobitGridProps> = ({ return ( <Box display="flex" flexDirection="row" justifyContent="flex-start"> - <Box ml="10px" style={{ marginRight: '4px' }}> + <Box ml="10px" style={{ marginRight: "4px" }}> <Button size="xs" onClick={onCloseClick}> X </Button> </Box> <Box> - <Box bg="black" p="10px" borderRadius="5px" style={{ marginTop: '15px' }}> + <Box + bg="black" + p="10px" + borderRadius="5px" + style={{ marginTop: "15px" }} + > {selectedPixels.map((row, y) => ( <Box key={y} display="flex"> {row.map((brightness, x) => ( @@ -51,7 +64,12 @@ const MicrobitMultiplePixelsGrid: React.FC<MultiMicrobitGridProps> = ({ w="15px" p={0} bgColor={`rgba(255, 0, 0, ${brightness / 9})`} - _hover={{ bgColor: brightness > 0 ? `rgba(255, 100, 100, ${selectedPixels[y][x] / 9})` : "rgba(255, 255, 255, 0.5)" }} + _hover={{ + bgColor: + brightness > 0 + ? `rgba(255, 100, 100, ${selectedPixels[y][x] / 9})` + : "rgba(255, 255, 255, 0.5)", + }} onClick={() => handlePixelClick(x, y)} /> </Box> @@ -60,7 +78,7 @@ const MicrobitMultiplePixelsGrid: React.FC<MultiMicrobitGridProps> = ({ ))} </Box> </Box> - <Box ml="10px" style={{ marginTop: '15px' }}> + <Box ml="10px" style={{ marginTop: "15px" }}> <Slider aria-label="brightness" value={currentBrightness} @@ -82,16 +100,25 @@ const MicrobitMultiplePixelsGrid: React.FC<MultiMicrobitGridProps> = ({ ); }; -export const MicrobitMultiplePixelComponent = ( - { props, view } : {props: WidgetProps, view: EditorView} -) => { - let args = props.args; let ranges = props.ranges; let types = props.types; let from = props.from; let to = props.to; +export const MicrobitMultiplePixelComponent = ({ + props, + view, +}: { + props: WidgetProps; + view: EditorView; +}) => { + let args = props.args; + let ranges = props.ranges; + let types = props.types; + let from = props.from; + let to = props.to; - let initialSelectedPixels: number[][] = Array.from({ length: 5 }, () => Array(5).fill(0)); - if (validateArgs(args)) { - initialSelectedPixels = parseArgs(args) - } - const [selectedPixels, setSelectedPixels] = useState<number[][]>(initialSelectedPixels); + console.log(args); + console.log(types); + const initialSelectedPixels = parseArgs(args); + const [selectedPixels, setSelectedPixels] = useState<number[][]>( + initialSelectedPixels + ); const handlePixelChange = (x: number, y: number, brightness: number) => { const updatedPixels = [...selectedPixels]; @@ -107,9 +134,10 @@ export const MicrobitMultiplePixelComponent = ( changes: { from: from, to: to, - insert: insertion} + insert: insertion, + }, }); - } + }; const handleCloseClick = () => { view.dispatch({ @@ -126,23 +154,44 @@ export const MicrobitMultiplePixelComponent = ( ); }; -const parseArgs = (args: number[]) => { - return [] -}; +const parseArgs = (args: string[]): number[][] => { + const defaultPixels = Array.from({ length: 5 }, () => Array(5).fill(0)); + // If args is empty, return a 5x5 array filled with zeros + if (args.length === 0) { + return defaultPixels; + } -const validateArgs = (args: number[]) => { - return false -}; + if (args.length !== 1) { + return defaultPixels; + } + const argString = args[0]; + const rows = argString.split(":"); + if (rows.length !== 5) { + return defaultPixels; + } + + const numbers: number[][] = []; + for (let row of rows) { + row = row.trim(); + if (!/^\d{5}$/.test(row)) { + return defaultPixels; + } + const rowNumbers = row.split("").map(Number); + numbers.push(rowNumbers); + } + + return numbers; +}; function pixelsToString(pixels: number[][]): string { - let outputString = ''; + let outputString = ""; for (let y = 0; y < 5; y++) { for (let x = 0; x < 5; x++) { outputString += pixels[y][x].toString(); } - outputString += ':'; + outputString += ":"; } outputString = outputString.slice(0, -1); return outputString; -} \ No newline at end of file +} From 9c4e3f38dc5db64051f690c5f1285de74fb73be5 Mon Sep 17 00:00:00 2001 From: Marta Religa <marta.religa@cs.ox.ac.uk> Date: Tue, 9 Apr 2024 10:40:09 +0200 Subject: [PATCH 067/106] lint --- .../codemirror/helper-widgets/openWidgets.tsx | 10 +- .../helper-widgets/reactWidgetExtension.tsx | 63 +++++---- .../helper-widgets/widgetArgParser.tsx | 125 +++++++++--------- 3 files changed, 111 insertions(+), 87 deletions(-) diff --git a/src/editor/codemirror/helper-widgets/openWidgets.tsx b/src/editor/codemirror/helper-widgets/openWidgets.tsx index fadb802aa..f8df3e5b4 100644 --- a/src/editor/codemirror/helper-widgets/openWidgets.tsx +++ b/src/editor/codemirror/helper-widgets/openWidgets.tsx @@ -4,7 +4,13 @@ import { EditorView } from "@codemirror/view"; import { useCallback } from "react"; export const openWidgetEffect = StateEffect.define<number>(); -export const OpenReactComponent = ({ loc, view }: { loc: number, view: EditorView }) => { +export const OpenReactComponent = ({ + loc, + view, +}: { + loc: number; + view: EditorView; +}) => { const handleClick = useCallback(() => { view.dispatch({ effects: [openWidgetEffect.of(loc)], @@ -15,4 +21,4 @@ export const OpenReactComponent = ({ loc, view }: { loc: number, view: EditorVie <Button onClick={handleClick}>Open</Button> </HStack> ); -}; \ No newline at end of file +}; diff --git a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx index ea88e64b6..4e0d61087 100644 --- a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx +++ b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx @@ -5,7 +5,7 @@ import { EditorView, WidgetType, } from "@codemirror/view"; -import { syntaxTree } from "@codemirror/language" +import { syntaxTree } from "@codemirror/language"; import { PortalFactory } from "../CodeMirror"; import React from "react"; import { createWidget } from "./widgetArgParser"; @@ -13,18 +13,16 @@ import { OpenReactComponent, openWidgetEffect } from "./openWidgets"; export interface WidgetProps { // Note: always an array, can be singleton - args: any[] + args: any[]; // Ranges of where to insert each argument - ranges: {from:number, to:number} [] + ranges: { from: number; to: number }[]; // Type of each argument, can be checked in widget to determine if it is editable - types: string[] + types: string[]; // Where to insert the changed values - from: number, - to: number + from: number; + to: number; } - - /** * This widget will have its contents rendered by the code in CodeMirror.tsx * which it communicates with via the portal factory. @@ -32,20 +30,29 @@ export interface WidgetProps { class Widget extends WidgetType { private portalCleanup: (() => void) | undefined; - constructor(private component: React.ComponentType<any>, - private props:WidgetProps, private inline:boolean, - private createPortal: PortalFactory) { + constructor( + private component: React.ComponentType<any>, + private props: WidgetProps, + private inline: boolean, + private createPortal: PortalFactory + ) { super(); } toDOM(view: EditorView) { const dom = document.createElement("div"); - if(this.inline) { - dom.style.display = 'inline-block'; // want it inline for the open-close widget - this.portalCleanup = this.createPortal(dom, <OpenReactComponent loc={this.props.to} view={view} />); - } - else this.portalCleanup = this.createPortal(dom, <this.component props={this.props} view={view} />); + if (this.inline) { + dom.style.display = "inline-block"; // want it inline for the open-close widget + this.portalCleanup = this.createPortal( + dom, + <OpenReactComponent loc={this.props.to} view={view} /> + ); + } else + this.portalCleanup = this.createPortal( + dom, + <this.component props={this.props} view={view} /> + ); //else this.portalCleanup = this.createPortal(dom, React.createElement(this.component, this.props, view)); return dom; } @@ -61,33 +68,37 @@ class Widget extends WidgetType { } } - // Iterates through the syntax tree, finding occurences of SoundEffect ArgList, and places toy widget there export const reactWidgetExtension = ( createPortal: PortalFactory ): Extension => { const decorate = (state: EditorState) => { - let widgets: any[] = [] + let widgets: any[] = []; syntaxTree(state).iterate({ enter: (ref) => { // Found an ArgList, parent will be a CallExpression - if (ref.name === "ArgList" && ref.node.parent) { + if (ref.name === "ArgList" && ref.node.parent) { // Match CallExpression name to our widgets - let name = state.doc.sliceString(ref.node.parent.from, ref.from) + let name = state.doc.sliceString(ref.node.parent.from, ref.from); let widget = createWidget(name, state, ref.node); - if(widget) { + if (widget) { let deco = Decoration.widget({ - widget: new Widget(widget.comp, widget.props, widget.props.to !== openWidgetLoc, createPortal), + widget: new Widget( + widget.comp, + widget.props, + widget.props.to !== openWidgetLoc, + createPortal + ), side: 1, }); widgets.push(deco.range(ref.to)); } } - } - }) + }, + }); - return Decoration.set(widgets) + return Decoration.set(widgets); }; let openWidgetLoc = -1; @@ -116,4 +127,4 @@ export const reactWidgetExtension = ( }, }); return [stateField]; -} +}; diff --git a/src/editor/codemirror/helper-widgets/widgetArgParser.tsx b/src/editor/codemirror/helper-widgets/widgetArgParser.tsx index e0ba7ef18..3c5b32e85 100644 --- a/src/editor/codemirror/helper-widgets/widgetArgParser.tsx +++ b/src/editor/codemirror/helper-widgets/widgetArgParser.tsx @@ -5,77 +5,84 @@ import { MicrobitSinglePixelComponent } from "./setPixelWidget"; import { MicrobitMultiplePixelComponent } from "./showImageWidget"; export interface CompProps { - comp: React.ComponentType<any>, - props: WidgetProps + comp: React.ComponentType<any>; + props: WidgetProps; } -export function createWidget(name: string, state: EditorState, node: SyntaxNode): CompProps | null { - let children = getChildNodes(node); - let ranges = getRanges(children); - let args = getArgs(state, ranges); - let types = getTypes(children); - let component: React.ComponentType<any> | null = null +export function createWidget( + name: string, + state: EditorState, + node: SyntaxNode +): CompProps | null { + let children = getChildNodes(node); + let ranges = getRanges(children); + let args = getArgs(state, ranges); + let types = getTypes(children); + let component: React.ComponentType<any> | null = null; - switch (name) { - case "display.set_pixel": - component = MicrobitSinglePixelComponent; - break; - case "Image": - component = MicrobitMultiplePixelComponent; - break; - default: - // No widget implemented for this function - return null; - } - if(component){ - return { - comp: component, - props: { - args: args, - ranges: ranges, - types: types, - from: node.from, - to: node.to - } - } - } - return null; + switch (name) { + case "display.set_pixel": + component = MicrobitSinglePixelComponent; + break; + case "Image": + component = MicrobitMultiplePixelComponent; + break; + default: + // No widget implemented for this function + return null; + } + if (component) { + return { + comp: component, + props: { + args: args, + ranges: ranges, + types: types, + from: node.from, + to: node.to, + }, + }; + } + return null; } // Gets all child nodes of a CallExpression, no typechecking -function getChildNodes(node:SyntaxNode): SyntaxNode[] { - let child = node.firstChild?.nextSibling; - let children = [] - while(child && child.name !== ")"){ - if(child.name !== ",") children.push(child); - child = child.nextSibling; - } - return children; +function getChildNodes(node: SyntaxNode): SyntaxNode[] { + let child = node.firstChild?.nextSibling; + let children = []; + while (child && child.name !== ")") { + if (child.name !== ",") children.push(child); + child = child.nextSibling; + } + return children; } // Gets ranges for insertion into arguments -function getRanges(nodes:SyntaxNode[]):{from:number, to:number}[] { - let ranges: {from:number, to:number}[] = [] - nodes.forEach(function(value) { - ranges.push({from:value.from, to:value.to}) - }) - return ranges; +function getRanges(nodes: SyntaxNode[]): { from: number; to: number }[] { + let ranges: { from: number; to: number }[] = []; + nodes.forEach(function (value) { + ranges.push({ from: value.from, to: value.to }); + }); + return ranges; } // Gets arguments as string -function getArgs(state:EditorState, ranges:{from:number, to:number}[]): string[] { - let args: string[] = [] - ranges.forEach(function(value) { - args.push(state.doc.sliceString(value.from, value.to)); - }) - return args; +function getArgs( + state: EditorState, + ranges: { from: number; to: number }[] +): string[] { + let args: string[] = []; + ranges.forEach(function (value) { + args.push(state.doc.sliceString(value.from, value.to)); + }); + return args; } // Gets types of each arg to determine if it is editable -function getTypes(nodes:SyntaxNode[]):string[] { - let types: string[] = [] - nodes.forEach(function(value) { - types.push(value.name) - }) - return types; -} \ No newline at end of file +function getTypes(nodes: SyntaxNode[]): string[] { + let types: string[] = []; + nodes.forEach(function (value) { + types.push(value.name); + }); + return types; +} From 2747d8bd665a202979e4012e9075d129824824e0 Mon Sep 17 00:00:00 2001 From: Marta Religa <marta.religa@cs.ox.ac.uk> Date: Tue, 9 Apr 2024 10:42:26 +0200 Subject: [PATCH 068/106] kinda fixed image insertion, the open just pops up in the wrong place second time --- src/editor/codemirror/helper-widgets/showImageWidget.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/editor/codemirror/helper-widgets/showImageWidget.tsx b/src/editor/codemirror/helper-widgets/showImageWidget.tsx index 0238e36c9..d220d657f 100644 --- a/src/editor/codemirror/helper-widgets/showImageWidget.tsx +++ b/src/editor/codemirror/helper-widgets/showImageWidget.tsx @@ -185,7 +185,7 @@ const parseArgs = (args: string[]): number[][] => { }; function pixelsToString(pixels: number[][]): string { - let outputString = ""; + let outputString = "("; for (let y = 0; y < 5; y++) { for (let x = 0; x < 5; x++) { outputString += pixels[y][x].toString(); @@ -193,5 +193,6 @@ function pixelsToString(pixels: number[][]): string { outputString += ":"; } outputString = outputString.slice(0, -1); + outputString += ")"; return outputString; } From 0f9e3199a573b1d4fbaa9a9218a3548dc2d82ab8 Mon Sep 17 00:00:00 2001 From: Marta Religa <59261954+martarel@users.noreply.github.com> Date: Tue, 9 Apr 2024 09:51:24 +0100 Subject: [PATCH 069/106] Update build.yml --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c74d395cb..baa4d6c17 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,6 +23,7 @@ jobs: STAGING_CLOUDFRONT_DISTRIBUTION_ID: E2ELTBTA2OFPY2 REVIEW_CLOUDFRONT_DISTRIBUTION_ID: E3267W09ZJHQG9 REACT_APP_FOUNDATION_BUILD: ${{ github.repository_owner == 'microbit-foundation' }} + CI: false steps: # Note: This workflow disables deployment steps and micro:bit branding installation on forks. From dd7d6025d3e74d03e4cc3ced29e8b4a155b54891 Mon Sep 17 00:00:00 2001 From: Marta Religa <marta.religa@cs.ox.ac.uk> Date: Tue, 9 Apr 2024 11:09:34 +0200 Subject: [PATCH 070/106] . --- src/editor/codemirror/helper-widgets/setPixelWidget.tsx | 4 ++-- src/editor/codemirror/helper-widgets/showImageWidget.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx index ee701ab9b..aa8b2749f 100644 --- a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx +++ b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx @@ -111,7 +111,7 @@ const parseArgs = (args: string[], types: string[]): Pixel | null => { let arg = args[i]; if (types[i] === "Number") { parsedArgs.push(parseInt(arg)); - } else if (arg == ",") { + } else if (arg === ",") { parsedArgs.push(0); } else { return null; @@ -131,7 +131,7 @@ export const MicrobitSinglePixelComponent = ({ view: EditorView; }) => { let args = props.args; - let ranges = props.ranges; + //let ranges = props.ranges; let types = props.types; let from = props.from; let to = props.to; diff --git a/src/editor/codemirror/helper-widgets/showImageWidget.tsx b/src/editor/codemirror/helper-widgets/showImageWidget.tsx index d220d657f..e4c815adb 100644 --- a/src/editor/codemirror/helper-widgets/showImageWidget.tsx +++ b/src/editor/codemirror/helper-widgets/showImageWidget.tsx @@ -108,7 +108,7 @@ export const MicrobitMultiplePixelComponent = ({ view: EditorView; }) => { let args = props.args; - let ranges = props.ranges; + //let ranges = props.ranges; let types = props.types; let from = props.from; let to = props.to; From b83a1a1f1a1c26d5220d7c8348b9b70e248591ce Mon Sep 17 00:00:00 2001 From: Marta Religa <marta.religa@cs.ox.ac.uk> Date: Tue, 9 Apr 2024 15:28:29 +0200 Subject: [PATCH 071/106] new component yay --- .../codemirror/helper-widgets/soundWidget.tsx | 191 ++++++++++++++++++ .../helper-widgets/widgetArgParser.tsx | 4 + 2 files changed, 195 insertions(+) create mode 100644 src/editor/codemirror/helper-widgets/soundWidget.tsx diff --git a/src/editor/codemirror/helper-widgets/soundWidget.tsx b/src/editor/codemirror/helper-widgets/soundWidget.tsx new file mode 100644 index 000000000..5dd98d5cf --- /dev/null +++ b/src/editor/codemirror/helper-widgets/soundWidget.tsx @@ -0,0 +1,191 @@ +import React, { useState } from "react"; +import { + Box, + Button, + SliderTrack, + SliderFilledTrack, + SliderThumb, + HStack, +} from "@chakra-ui/react"; +import { EditorView } from "@codemirror/view"; +import { WidgetProps } from "./reactWidgetExtension"; +import { openWidgetEffect } from "./openWidgets"; + +interface SliderProps { + min: number; + max: number; + step: number; + defaultValue: number; + onChange: (value: number) => void; + sliderStyle?: React.CSSProperties; + label: string; +} + +const DurationSliderProps: SliderProps = { + min: 0, + max: 100, + step: 1, + defaultValue: 50, + onChange: (value) => { + console.log("Slider value changed:", value); + }, + sliderStyle: { + width: "100%", // Adjust the width of the slider + height: "100px", // Adjust the height of the slider + backgroundColor: "lightgray", // Change the background color of the slider + borderRadius: "10px", // Apply rounded corners to the slider track + border: "none", // Remove the border of the slider track + outline: "none", // Remove the outline when focused + }, + label: "Duration", +}; + +const endSliderProps: SliderProps = { + min: 0, + max: 100, + step: 1, + defaultValue: 50, + onChange: (value) => { + console.log("Slider value changed:", value); + }, + sliderStyle: { + width: "100%", // Adjust the width of the slider + height: "100px", // Adjust the height of the slider + backgroundColor: "lightgray", // Change the background color of the slider + borderRadius: "10px", // Apply rounded corners to the slider track + border: "none", // Remove the border of the slider track + outline: "none", // Remove the outline when focused + }, + label: "End Freq", +}; + +const startSliderProps: SliderProps = { + min: 0, + max: 100, + step: 1, + defaultValue: 50, + onChange: (value) => { + console.log("Slider value changed:", value); + }, + sliderStyle: { + width: "200%", // Adjust the width of the slider + height: "100px", // Adjust the height of the slider + backgroundColor: "lightgray", // Change the background color of the slider + borderRadius: "10px", // Apply rounded corners to the slider track + border: "none", // Remove the border of the slider track + outline: "none", // Remove the outline when focused + }, + label: "Start Freq", +}; + +const customSliderStyle: React.CSSProperties = { + width: "80%", // Adjust the width of the slider + height: "20px", // Adjust the height of the slider +}; + +const Slider: React.FC<SliderProps> = ({ + min, + max, + step, + defaultValue, + onChange, + sliderStyle, + label, +}) => { + const [value, setValue] = useState(defaultValue); + + const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => { + const newValue = parseFloat(event.target.value); + setValue(newValue); + onChange(newValue); + }; + + return ( + <div + style={{ display: "flex", flexDirection: "column", alignItems: "left" }} + > + <input + type="range" + min={min} + max={max} + step={step} + value={value} + onChange={handleChange} + /> + <label style={{ fontSize: "16px" }}> + {label}: {value} + </label> + </div> + ); +}; + +const TripleSliderWidget: React.FC<{ + slider1Props: SliderProps; + slider2Props: SliderProps; + slider3Props: SliderProps; + isOpen: boolean; +}> = ({ slider1Props, slider2Props, slider3Props, isOpen }) => { + if (!isOpen) return null; + return ( + <div> + <div style={{ display: "flex", justifyContent: "left" }}> + <div style={{ marginRight: "40px" }}> + <Slider {...slider1Props} /> + </div> + <div style={{ marginRight: "40px" }}> + <Slider {...slider2Props} /> + </div> + <div> + <Slider {...slider3Props} /> + </div> + </div> + </div> + ); +}; + +export const SoundComponent = ({ + props, + view, +}: { + props: WidgetProps; + view: EditorView; +}) => { + let args = props.args; + //let ranges = props.ranges; + let types = props.types; + let from = props.from; + let to = props.to; + //for future reference add a aclose button + const handleCloseClick = () => { + view.dispatch({ + effects: [openWidgetEffect.of(-1)], + }); + }; + const [isSoundEditorOpen, setIsSoundEditorOpen] = useState(false); + const buttonLabel = isSoundEditorOpen ? "Close" : "Open"; + + const handleButtonClick = () => { + setIsSoundEditorOpen(!isSoundEditorOpen); + // Toggle the state to open/close the DualSlider + }; + /* + view.dispatch({ + changes: { + from: from, + to: to, + insert: //something from state of component`, + }, + */ + + return ( + <HStack fontFamily="body" spacing={5} py={3}> + <Button onClick={handleButtonClick}>{buttonLabel} Sound Editor</Button> + <TripleSliderWidget + slider1Props={startSliderProps} + slider2Props={endSliderProps} + slider3Props={DurationSliderProps} + isOpen={isSoundEditorOpen} + /> + </HStack> + ); +}; diff --git a/src/editor/codemirror/helper-widgets/widgetArgParser.tsx b/src/editor/codemirror/helper-widgets/widgetArgParser.tsx index 3c5b32e85..16d9d4c2d 100644 --- a/src/editor/codemirror/helper-widgets/widgetArgParser.tsx +++ b/src/editor/codemirror/helper-widgets/widgetArgParser.tsx @@ -3,6 +3,7 @@ import { SyntaxNode } from "@lezer/common"; import { WidgetProps } from "./reactWidgetExtension"; import { MicrobitSinglePixelComponent } from "./setPixelWidget"; import { MicrobitMultiplePixelComponent } from "./showImageWidget"; +import { SoundComponent } from "./soundWidget"; export interface CompProps { comp: React.ComponentType<any>; @@ -27,6 +28,9 @@ export function createWidget( case "Image": component = MicrobitMultiplePixelComponent; break; + case "SoundEffect": + component = SoundComponent; + break; default: // No widget implemented for this function return null; From ba834ec8298c8bf74a9f337d7662bd0fd3002bba Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Tue, 9 Apr 2024 09:30:29 -0400 Subject: [PATCH 072/106] soundEffect --- src/editor/codemirror/helper-widgets/widgetArgParser.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/editor/codemirror/helper-widgets/widgetArgParser.tsx b/src/editor/codemirror/helper-widgets/widgetArgParser.tsx index 3c5b32e85..89ab8d4a4 100644 --- a/src/editor/codemirror/helper-widgets/widgetArgParser.tsx +++ b/src/editor/codemirror/helper-widgets/widgetArgParser.tsx @@ -25,6 +25,11 @@ export function createWidget( component = MicrobitSinglePixelComponent; break; case "Image": + component = MicrobitMultiplePixelComponent; + console.log(args); + break; + case "SoundEffect": + // TODO: sound effect component = MicrobitMultiplePixelComponent; break; default: From a94f7851e5e4515812d17528c35eaf640d1e3ba4 Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Tue, 9 Apr 2024 09:35:10 -0400 Subject: [PATCH 073/106] consolelog --- src/editor/codemirror/helper-widgets/widgetArgParser.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/editor/codemirror/helper-widgets/widgetArgParser.tsx b/src/editor/codemirror/helper-widgets/widgetArgParser.tsx index 89ab8d4a4..5d584f979 100644 --- a/src/editor/codemirror/helper-widgets/widgetArgParser.tsx +++ b/src/editor/codemirror/helper-widgets/widgetArgParser.tsx @@ -26,7 +26,6 @@ export function createWidget( break; case "Image": component = MicrobitMultiplePixelComponent; - console.log(args); break; case "SoundEffect": // TODO: sound effect From f8f1793d383a2f62666c9c9d10f64e13fb49161b Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Tue, 9 Apr 2024 09:41:27 -0400 Subject: [PATCH 074/106] validate in reactWdiget --- .../helper-widgets/reactWidgetExtension.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx index 4e0d61087..607784c42 100644 --- a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx +++ b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx @@ -43,11 +43,13 @@ class Widget extends WidgetType { const dom = document.createElement("div"); if (this.inline) { - dom.style.display = "inline-block"; // want it inline for the open-close widget - this.portalCleanup = this.createPortal( - dom, - <OpenReactComponent loc={this.props.to} view={view} /> - ); + if(ValdidateComponentArgs(this.component)){ + dom.style.display = "inline-block"; // want it inline for the open-close widget + this.portalCleanup = this.createPortal( + dom, + <OpenReactComponent loc={this.props.to} view={view} /> + ); + } } else this.portalCleanup = this.createPortal( dom, From 20f223f34246ceef9997b05ba7508594f81cbbbd Mon Sep 17 00:00:00 2001 From: Marta Religa <marta.religa@cs.ox.ac.uk> Date: Tue, 9 Apr 2024 15:44:17 +0200 Subject: [PATCH 075/106] yeyohellohi --- src/editor/codemirror/helper-widgets/openWidgets.tsx | 2 ++ .../codemirror/helper-widgets/reactWidgetExtension.tsx | 3 ++- .../codemirror/helper-widgets/widgetArgParser.tsx | 10 ++++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/editor/codemirror/helper-widgets/openWidgets.tsx b/src/editor/codemirror/helper-widgets/openWidgets.tsx index f8df3e5b4..22a236005 100644 --- a/src/editor/codemirror/helper-widgets/openWidgets.tsx +++ b/src/editor/codemirror/helper-widgets/openWidgets.tsx @@ -22,3 +22,5 @@ export const OpenReactComponent = ({ </HStack> ); }; + + diff --git a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx index 607784c42..5554f7858 100644 --- a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx +++ b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx @@ -10,6 +10,7 @@ import { PortalFactory } from "../CodeMirror"; import React from "react"; import { createWidget } from "./widgetArgParser"; import { OpenReactComponent, openWidgetEffect } from "./openWidgets"; +import { ValidateComponentArgs } from "./widgetArgParser"; export interface WidgetProps { // Note: always an array, can be singleton @@ -43,7 +44,7 @@ class Widget extends WidgetType { const dom = document.createElement("div"); if (this.inline) { - if(ValdidateComponentArgs(this.component)){ + if (ValidateComponentArgs(this.component)) { dom.style.display = "inline-block"; // want it inline for the open-close widget this.portalCleanup = this.createPortal( dom, diff --git a/src/editor/codemirror/helper-widgets/widgetArgParser.tsx b/src/editor/codemirror/helper-widgets/widgetArgParser.tsx index 5d584f979..850f2a601 100644 --- a/src/editor/codemirror/helper-widgets/widgetArgParser.tsx +++ b/src/editor/codemirror/helper-widgets/widgetArgParser.tsx @@ -90,3 +90,13 @@ function getTypes(nodes: SyntaxNode[]): string[] { }); return types; } + +export function ValidateComponentArgs(name: React.ComponentType<any>): Boolean { + switch (name) { + case MicrobitMultiplePixelComponent: + return true; + case MicrobitSinglePixelComponent: + return false; + } + return true; +} From 07eb135238ccd6dc73b94ad97e8849ee466b5c19 Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Tue, 9 Apr 2024 09:57:47 -0400 Subject: [PATCH 076/106] added ranges into setPixel --- .../helper-widgets/setPixelWidget.tsx | 22 ++++++++++++++----- .../helper-widgets/widgetArgParser.tsx | 2 +- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx index aa8b2749f..364f5261a 100644 --- a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx +++ b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx @@ -131,7 +131,7 @@ export const MicrobitSinglePixelComponent = ({ view: EditorView; }) => { let args = props.args; - //let ranges = props.ranges; + let ranges = props.ranges; let types = props.types; let from = props.from; let to = props.to; @@ -151,11 +151,23 @@ export const MicrobitSinglePixelComponent = ({ const handleSelectPixel = (pixel: Pixel) => { const { x, y, brightness } = pixel; view.dispatch({ - changes: { - from: from, - to: to, - insert: `(${x}, ${y}, ${brightness}) `, + changes: [{ + from: ranges[0].from, + to: ranges[0].to, + insert: `${x}`, }, + { + from: ranges[1].from, + to: ranges[1].to, + insert: `${y}`, + }, + { + from: ranges[2].from, + to: ranges[2].to, + insert: `${brightness}`, + }, + ], + effects: [openWidgetEffect.of(to)], }); }; diff --git a/src/editor/codemirror/helper-widgets/widgetArgParser.tsx b/src/editor/codemirror/helper-widgets/widgetArgParser.tsx index 46c733aea..597034466 100644 --- a/src/editor/codemirror/helper-widgets/widgetArgParser.tsx +++ b/src/editor/codemirror/helper-widgets/widgetArgParser.tsx @@ -96,7 +96,7 @@ export function ValidateComponentArgs(name: React.ComponentType<any>): Boolean { case MicrobitMultiplePixelComponent: return true; case MicrobitSinglePixelComponent: - return false; + return true; } return true; } From dc7a3807a6557559361f00df02a770542043b53d Mon Sep 17 00:00:00 2001 From: Marta Religa <marta.religa@cs.ox.ac.uk> Date: Tue, 9 Apr 2024 15:58:24 +0200 Subject: [PATCH 077/106] . --- src/editor/codemirror/helper-widgets/widgetArgParser.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor/codemirror/helper-widgets/widgetArgParser.tsx b/src/editor/codemirror/helper-widgets/widgetArgParser.tsx index 46c733aea..597034466 100644 --- a/src/editor/codemirror/helper-widgets/widgetArgParser.tsx +++ b/src/editor/codemirror/helper-widgets/widgetArgParser.tsx @@ -96,7 +96,7 @@ export function ValidateComponentArgs(name: React.ComponentType<any>): Boolean { case MicrobitMultiplePixelComponent: return true; case MicrobitSinglePixelComponent: - return false; + return true; } return true; } From 2faaf9c4fc89bc27a4bdd28baae8118f8745f96c Mon Sep 17 00:00:00 2001 From: Marta Religa <marta.religa@cs.ox.ac.uk> Date: Tue, 9 Apr 2024 15:58:43 +0200 Subject: [PATCH 078/106] . --- src/editor/codemirror/helper-widgets/widgetArgParser.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor/codemirror/helper-widgets/widgetArgParser.tsx b/src/editor/codemirror/helper-widgets/widgetArgParser.tsx index 597034466..46c733aea 100644 --- a/src/editor/codemirror/helper-widgets/widgetArgParser.tsx +++ b/src/editor/codemirror/helper-widgets/widgetArgParser.tsx @@ -96,7 +96,7 @@ export function ValidateComponentArgs(name: React.ComponentType<any>): Boolean { case MicrobitMultiplePixelComponent: return true; case MicrobitSinglePixelComponent: - return true; + return false; } return true; } From d2658aed9d37a2dff452f3a0dacbfedc7c4c8be9 Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Tue, 9 Apr 2024 10:03:41 -0400 Subject: [PATCH 079/106] Comments dont disrupt arg parser --- src/editor/codemirror/helper-widgets/widgetArgParser.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/editor/codemirror/helper-widgets/widgetArgParser.tsx b/src/editor/codemirror/helper-widgets/widgetArgParser.tsx index 597034466..b854f32b7 100644 --- a/src/editor/codemirror/helper-widgets/widgetArgParser.tsx +++ b/src/editor/codemirror/helper-widgets/widgetArgParser.tsx @@ -55,7 +55,8 @@ function getChildNodes(node: SyntaxNode): SyntaxNode[] { let child = node.firstChild?.nextSibling; let children = []; while (child && child.name !== ")") { - if (child.name !== ",") children.push(child); + console.log(child.name); + if (child.name !== "," && child.name !== "Comment") children.push(child); child = child.nextSibling; } return children; From c0c4c391dc92bf673e05f59f5975136c2d3ab869 Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Tue, 9 Apr 2024 10:04:19 -0400 Subject: [PATCH 080/106] b --- src/editor/codemirror/helper-widgets/widgetArgParser.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor/codemirror/helper-widgets/widgetArgParser.tsx b/src/editor/codemirror/helper-widgets/widgetArgParser.tsx index b854f32b7..b163b536d 100644 --- a/src/editor/codemirror/helper-widgets/widgetArgParser.tsx +++ b/src/editor/codemirror/helper-widgets/widgetArgParser.tsx @@ -92,7 +92,7 @@ function getTypes(nodes: SyntaxNode[]): string[] { return types; } -export function ValidateComponentArgs(name: React.ComponentType<any>): Boolean { +export function ValidateComponentArgs(name: React.ComponentType<any>): boolean { switch (name) { case MicrobitMultiplePixelComponent: return true; From f715d7b586ddcfe6eaedd005c5997d6f3361b519 Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Tue, 9 Apr 2024 10:09:08 -0400 Subject: [PATCH 081/106] audio.SoundEffect --- src/editor/codemirror/helper-widgets/widgetArgParser.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/editor/codemirror/helper-widgets/widgetArgParser.tsx b/src/editor/codemirror/helper-widgets/widgetArgParser.tsx index b163b536d..f143ee157 100644 --- a/src/editor/codemirror/helper-widgets/widgetArgParser.tsx +++ b/src/editor/codemirror/helper-widgets/widgetArgParser.tsx @@ -28,11 +28,12 @@ export function createWidget( case "Image": component = MicrobitMultiplePixelComponent; break; - case "SoundEffect": + case "audio.SoundEffect": component = SoundComponent; break; default: // No widget implemented for this function + console.log("No widget implemented for this function: " + name); return null; } if (component) { @@ -98,6 +99,9 @@ export function ValidateComponentArgs(name: React.ComponentType<any>): boolean { return true; case MicrobitSinglePixelComponent: return true; + case SoundComponent: + return true; + default: + return false; } - return true; } From a0d10eea7de6c90453071099b40966968a53f933 Mon Sep 17 00:00:00 2001 From: Marta Religa <marta.religa@cs.ox.ac.uk> Date: Tue, 9 Apr 2024 16:43:31 +0200 Subject: [PATCH 082/106] . --- .../helper-widgets/setPixelWidget.tsx | 35 ++++++++++--------- .../helper-widgets/showImageWidget.tsx | 4 +-- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx index 364f5261a..f9c1855dd 100644 --- a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx +++ b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx @@ -39,9 +39,9 @@ const MicrobitSinglePixelGrid: React.FC<MicrobitSinglePixelGridProps> = ({ }; return ( - <Box display="flex" flexDirection="row" justifyContent="flex-start"> + <Box display="flex" flexDirection="row" justifyContent="flex-start" bg = "lightgray"> <Box ml="10px" style={{ marginRight: "4px" }}> - <Button size="xs" onClick={onCloseClick}> + <Button size="xs" onClick={onCloseClick} bg = "white"> X </Button> </Box> @@ -151,21 +151,22 @@ export const MicrobitSinglePixelComponent = ({ const handleSelectPixel = (pixel: Pixel) => { const { x, y, brightness } = pixel; view.dispatch({ - changes: [{ - from: ranges[0].from, - to: ranges[0].to, - insert: `${x}`, - }, - { - from: ranges[1].from, - to: ranges[1].to, - insert: `${y}`, - }, - { - from: ranges[2].from, - to: ranges[2].to, - insert: `${brightness}`, - }, + changes: [ + { + from: ranges[0].from, + to: ranges[0].to, + insert: `${x}`, + }, + { + from: ranges[1].from, + to: ranges[1].to, + insert: `${y}`, + }, + { + from: ranges[2].from, + to: ranges[2].to, + insert: `${brightness}`, + }, ], effects: [openWidgetEffect.of(to)], }); diff --git a/src/editor/codemirror/helper-widgets/showImageWidget.tsx b/src/editor/codemirror/helper-widgets/showImageWidget.tsx index e4c815adb..3b3d5d805 100644 --- a/src/editor/codemirror/helper-widgets/showImageWidget.tsx +++ b/src/editor/codemirror/helper-widgets/showImageWidget.tsx @@ -41,9 +41,9 @@ const MicrobitMultiplePixelsGrid: React.FC<MultiMicrobitGridProps> = ({ }; return ( - <Box display="flex" flexDirection="row" justifyContent="flex-start"> + <Box display="flex" flexDirection="row" justifyContent="flex-start" bg = "lightgray"> <Box ml="10px" style={{ marginRight: "4px" }}> - <Button size="xs" onClick={onCloseClick}> + <Button size="xs" onClick={onCloseClick} bg = "white"> X </Button> </Box> From d612f844724a3316d5ba4ff44bc4c96b0cd16a70 Mon Sep 17 00:00:00 2001 From: Marta Religa <marta.religa@cs.ox.ac.uk> Date: Wed, 10 Apr 2024 20:19:44 +0200 Subject: [PATCH 083/106] validation of args --- .../helper-widgets/reactWidgetExtension.tsx | 2 +- .../helper-widgets/setPixelWidget.tsx | 52 +++++++++---------- .../helper-widgets/showImageWidget.tsx | 9 +++- .../helper-widgets/widgetArgParser.tsx | 14 ++++- 4 files changed, 45 insertions(+), 32 deletions(-) diff --git a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx index 5554f7858..e3201bb99 100644 --- a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx +++ b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx @@ -44,7 +44,7 @@ class Widget extends WidgetType { const dom = document.createElement("div"); if (this.inline) { - if (ValidateComponentArgs(this.component)) { + if (ValidateComponentArgs(this.component, [], [])){ dom.style.display = "inline-block"; // want it inline for the open-close widget this.portalCleanup = this.createPortal( dom, diff --git a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx index f9c1855dd..42cf03c8d 100644 --- a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx +++ b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx @@ -39,9 +39,14 @@ const MicrobitSinglePixelGrid: React.FC<MicrobitSinglePixelGridProps> = ({ }; return ( - <Box display="flex" flexDirection="row" justifyContent="flex-start" bg = "lightgray"> + <Box + display="flex" + flexDirection="row" + justifyContent="flex-start" + bg="lightgray" + > <Box ml="10px" style={{ marginRight: "4px" }}> - <Button size="xs" onClick={onCloseClick} bg = "white"> + <Button size="xs" onClick={onCloseClick} bg="white"> X </Button> </Box> @@ -102,27 +107,6 @@ const MicrobitSinglePixelGrid: React.FC<MicrobitSinglePixelGridProps> = ({ ); }; -const parseArgs = (args: string[], types: string[]): Pixel | null => { - if (args.length > 3) { - return null; - } - const parsedArgs: number[] = []; - for (let i = 0; i < args.length; i++) { - let arg = args[i]; - if (types[i] === "Number") { - parsedArgs.push(parseInt(arg)); - } else if (arg === ",") { - parsedArgs.push(0); - } else { - return null; - } - } - while (parsedArgs.length < 3) { - parsedArgs.push(0); - } - return { x: parsedArgs[0], y: parsedArgs[1], brightness: parsedArgs[2] }; -}; - export const MicrobitSinglePixelComponent = ({ props, view, @@ -135,12 +119,8 @@ export const MicrobitSinglePixelComponent = ({ let types = props.types; let from = props.from; let to = props.to; - console.log(args); - console.log(types); - const selectedPixel = parseArgs(args, types); - if (selectedPixel == null) { - } + const selectedPixel = parseArgs(args, types); const handleCloseClick = () => { view.dispatch({ @@ -180,3 +160,19 @@ export const MicrobitSinglePixelComponent = ({ /> ); }; + +const parseArgs = (args: string[], types: string[]): Pixel => { + const parsedArgs: number[] = []; + for (let i = 0; i < args.length; i++) { + let arg = args[i]; + if (types[i] === "Number") { + parsedArgs.push(parseInt(arg)); + } else { + parsedArgs.push(0); + } + } + while (parsedArgs.length < 3) { + parsedArgs.push(0); + } + return { x: parsedArgs[0], y: parsedArgs[1], brightness: parsedArgs[2] }; +}; diff --git a/src/editor/codemirror/helper-widgets/showImageWidget.tsx b/src/editor/codemirror/helper-widgets/showImageWidget.tsx index 3b3d5d805..251313234 100644 --- a/src/editor/codemirror/helper-widgets/showImageWidget.tsx +++ b/src/editor/codemirror/helper-widgets/showImageWidget.tsx @@ -41,9 +41,14 @@ const MicrobitMultiplePixelsGrid: React.FC<MultiMicrobitGridProps> = ({ }; return ( - <Box display="flex" flexDirection="row" justifyContent="flex-start" bg = "lightgray"> + <Box + display="flex" + flexDirection="row" + justifyContent="flex-start" + bg="lightgray" + > <Box ml="10px" style={{ marginRight: "4px" }}> - <Button size="xs" onClick={onCloseClick} bg = "white"> + <Button size="xs" onClick={onCloseClick} bg="white"> X </Button> </Box> diff --git a/src/editor/codemirror/helper-widgets/widgetArgParser.tsx b/src/editor/codemirror/helper-widgets/widgetArgParser.tsx index f143ee157..9edcb4446 100644 --- a/src/editor/codemirror/helper-widgets/widgetArgParser.tsx +++ b/src/editor/codemirror/helper-widgets/widgetArgParser.tsx @@ -93,11 +93,23 @@ function getTypes(nodes: SyntaxNode[]): string[] { return types; } -export function ValidateComponentArgs(name: React.ComponentType<any>): boolean { +export function ValidateComponentArgs( + name: React.ComponentType<any>, + args: string[], + types: string[] +): boolean { switch (name) { case MicrobitMultiplePixelComponent: return true; case MicrobitSinglePixelComponent: + if (args.length > 3) { + return false; + } + for (let i = 0; i < args.length; i++) { + if (types[i] !== "Number" && args[i] != ",") { + return false; + } + } return true; case SoundComponent: return true; From 8150a5586882d8fd25f15cef08991920900240b5 Mon Sep 17 00:00:00 2001 From: Marta Religa <marta.religa@cs.ox.ac.uk> Date: Wed, 10 Apr 2024 20:25:16 +0200 Subject: [PATCH 084/106] ranges n stuff --- src/editor/codemirror/helper-widgets/showImageWidget.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/editor/codemirror/helper-widgets/showImageWidget.tsx b/src/editor/codemirror/helper-widgets/showImageWidget.tsx index 251313234..37e9feb11 100644 --- a/src/editor/codemirror/helper-widgets/showImageWidget.tsx +++ b/src/editor/codemirror/helper-widgets/showImageWidget.tsx @@ -113,7 +113,7 @@ export const MicrobitMultiplePixelComponent = ({ view: EditorView; }) => { let args = props.args; - //let ranges = props.ranges; + let ranges = props.ranges; let types = props.types; let from = props.from; let to = props.to; @@ -137,8 +137,8 @@ export const MicrobitMultiplePixelComponent = ({ console.log(insertion); view.dispatch({ changes: { - from: from, - to: to, + from: ranges[0].from, + to: ranges[0].to, insert: insertion, }, }); @@ -190,7 +190,7 @@ const parseArgs = (args: string[]): number[][] => { }; function pixelsToString(pixels: number[][]): string { - let outputString = "("; + let outputString = ""; for (let y = 0; y < 5; y++) { for (let x = 0; x < 5; x++) { outputString += pixels[y][x].toString(); @@ -198,6 +198,5 @@ function pixelsToString(pixels: number[][]): string { outputString += ":"; } outputString = outputString.slice(0, -1); - outputString += ")"; return outputString; } From 1482a0f4831e202a376062d4f1557cfb4270f4c2 Mon Sep 17 00:00:00 2001 From: Marta Religa <marta.religa@cs.ox.ac.uk> Date: Wed, 10 Apr 2024 21:36:32 +0200 Subject: [PATCH 085/106] . --- src/editor/codemirror/helper-widgets/openWidgets.tsx | 2 -- .../codemirror/helper-widgets/reactWidgetExtension.tsx | 2 +- src/editor/codemirror/helper-widgets/setPixelWidget.tsx | 3 +++ src/editor/codemirror/helper-widgets/showImageWidget.tsx | 6 +----- src/editor/codemirror/helper-widgets/widgetArgParser.tsx | 2 ++ 5 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/editor/codemirror/helper-widgets/openWidgets.tsx b/src/editor/codemirror/helper-widgets/openWidgets.tsx index 22a236005..f8df3e5b4 100644 --- a/src/editor/codemirror/helper-widgets/openWidgets.tsx +++ b/src/editor/codemirror/helper-widgets/openWidgets.tsx @@ -22,5 +22,3 @@ export const OpenReactComponent = ({ </HStack> ); }; - - diff --git a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx index e3201bb99..60f017681 100644 --- a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx +++ b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx @@ -44,7 +44,7 @@ class Widget extends WidgetType { const dom = document.createElement("div"); if (this.inline) { - if (ValidateComponentArgs(this.component, [], [])){ + if (ValidateComponentArgs(this.component, [], [])) { dom.style.display = "inline-block"; // want it inline for the open-close widget this.portalCleanup = this.createPortal( dom, diff --git a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx index 42cf03c8d..53c490f64 100644 --- a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx +++ b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx @@ -29,10 +29,12 @@ const MicrobitSinglePixelGrid: React.FC<MicrobitSinglePixelGridProps> = ({ onCloseClick, }) => { const { x, y, brightness } = initialPixel ?? { x: 0, y: 0, brightness: 9 }; + const handlePixelClick = (x: number, y: number) => { const newPixel: Pixel = { x, y, brightness }; onPixelClick(newPixel); }; + const handleSliderChange = (value: number) => { const updatedPixel: Pixel = { x, y, brightness: value }; onPixelClick(updatedPixel); @@ -171,6 +173,7 @@ const parseArgs = (args: string[], types: string[]): Pixel => { parsedArgs.push(0); } } + //replace missing arguments with 0 while (parsedArgs.length < 3) { parsedArgs.push(0); } diff --git a/src/editor/codemirror/helper-widgets/showImageWidget.tsx b/src/editor/codemirror/helper-widgets/showImageWidget.tsx index 37e9feb11..89f0eb8e2 100644 --- a/src/editor/codemirror/helper-widgets/showImageWidget.tsx +++ b/src/editor/codemirror/helper-widgets/showImageWidget.tsx @@ -161,21 +161,18 @@ export const MicrobitMultiplePixelComponent = ({ const parseArgs = (args: string[]): number[][] => { const defaultPixels = Array.from({ length: 5 }, () => Array(5).fill(0)); - // If args is empty, return a 5x5 array filled with zeros + //if args is empty, return a 5x5 array filled with zeros if (args.length === 0) { return defaultPixels; } - if (args.length !== 1) { return defaultPixels; } const argString = args[0]; const rows = argString.split(":"); - if (rows.length !== 5) { return defaultPixels; } - const numbers: number[][] = []; for (let row of rows) { row = row.trim(); @@ -185,7 +182,6 @@ const parseArgs = (args: string[]): number[][] => { const rowNumbers = row.split("").map(Number); numbers.push(rowNumbers); } - return numbers; }; diff --git a/src/editor/codemirror/helper-widgets/widgetArgParser.tsx b/src/editor/codemirror/helper-widgets/widgetArgParser.tsx index 9edcb4446..6bcac7f1e 100644 --- a/src/editor/codemirror/helper-widgets/widgetArgParser.tsx +++ b/src/editor/codemirror/helper-widgets/widgetArgParser.tsx @@ -102,9 +102,11 @@ export function ValidateComponentArgs( case MicrobitMultiplePixelComponent: return true; case MicrobitSinglePixelComponent: + //if more than 3 arguments, don't open if (args.length > 3) { return false; } + //if some arguments are not numbers or empty, don't open for (let i = 0; i < args.length; i++) { if (types[i] !== "Number" && args[i] != ",") { return false; From 63c206da052ed23703ef33bb2f016b2a8cb23e35 Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Thu, 11 Apr 2024 23:43:40 -0400 Subject: [PATCH 086/106] ValidateComponentArgs args types --- src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx index 60f017681..af419953a 100644 --- a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx +++ b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx @@ -44,7 +44,7 @@ class Widget extends WidgetType { const dom = document.createElement("div"); if (this.inline) { - if (ValidateComponentArgs(this.component, [], [])) { + if (ValidateComponentArgs(this.component, this.props.args, this.props.types)) { dom.style.display = "inline-block"; // want it inline for the open-close widget this.portalCleanup = this.createPortal( dom, From f83adbe0d1595290a5c19a27fcbb1ca96345f4f9 Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Thu, 11 Apr 2024 23:56:55 -0400 Subject: [PATCH 087/106] set_pixel autofills when args are not fully written --- .../helper-widgets/reactWidgetExtension.tsx | 5 ++ .../helper-widgets/setPixelWidget.tsx | 56 ++++++++++++------- 2 files changed, 41 insertions(+), 20 deletions(-) diff --git a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx index af419953a..a7780932f 100644 --- a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx +++ b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx @@ -43,6 +43,11 @@ class Widget extends WidgetType { toDOM(view: EditorView) { const dom = document.createElement("div"); + console.log("TEST") + console.log(this.props.args.length); + console.log(this.props.types.length) + console.log(this.props.ranges.length) + if (this.inline) { if (ValidateComponentArgs(this.component, this.props.args, this.props.types)) { dom.style.display = "inline-block"; // want it inline for the open-close widget diff --git a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx index 53c490f64..29473a515 100644 --- a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx +++ b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx @@ -132,26 +132,42 @@ export const MicrobitSinglePixelComponent = ({ const handleSelectPixel = (pixel: Pixel) => { const { x, y, brightness } = pixel; - view.dispatch({ - changes: [ - { - from: ranges[0].from, - to: ranges[0].to, - insert: `${x}`, - }, - { - from: ranges[1].from, - to: ranges[1].to, - insert: `${y}`, - }, - { - from: ranges[2].from, - to: ranges[2].to, - insert: `${brightness}`, - }, - ], - effects: [openWidgetEffect.of(to)], - }); + if(ranges.length === 3){ + view.dispatch({ + changes: [ + { + from: ranges[0].from, + to: ranges[0].to, + insert: `${x}`, + }, + { + from: ranges[1].from, + to: ranges[1].to, + insert: `${y}`, + }, + { + from: ranges[2].from, + to: ranges[2].to, + insert: `${brightness}`, + }, + ], + effects: [openWidgetEffect.of(to)], + }); + } + else{ + let vals = `${x},${y},${brightness}`; + view.dispatch({ + changes: [ + { + from: from+1, + to: to-1, + insert: vals, + }, + ], + effects: [openWidgetEffect.of(vals.length + from + 2)], + }); + console.log(from, to, ) + } }; return ( From 6b783ad75807d0e51fd7eab2dca3661b2dc255e2 Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Thu, 11 Apr 2024 23:57:30 -0400 Subject: [PATCH 088/106] removed test comments --- .../codemirror/helper-widgets/reactWidgetExtension.tsx | 5 ----- src/editor/codemirror/helper-widgets/setPixelWidget.tsx | 1 - 2 files changed, 6 deletions(-) diff --git a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx index a7780932f..af419953a 100644 --- a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx +++ b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx @@ -43,11 +43,6 @@ class Widget extends WidgetType { toDOM(view: EditorView) { const dom = document.createElement("div"); - console.log("TEST") - console.log(this.props.args.length); - console.log(this.props.types.length) - console.log(this.props.ranges.length) - if (this.inline) { if (ValidateComponentArgs(this.component, this.props.args, this.props.types)) { dom.style.display = "inline-block"; // want it inline for the open-close widget diff --git a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx index 29473a515..be2598cf1 100644 --- a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx +++ b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx @@ -166,7 +166,6 @@ export const MicrobitSinglePixelComponent = ({ ], effects: [openWidgetEffect.of(vals.length + from + 2)], }); - console.log(from, to, ) } }; From 4f5e267c26edcd92b1b5194af20549a5c1a44c93 Mon Sep 17 00:00:00 2001 From: Marta Religa <marta.religa@cs.ox.ac.uk> Date: Fri, 12 Apr 2024 09:47:05 +0200 Subject: [PATCH 089/106] . --- .../helper-widgets/setPixelWidget.tsx | 2 +- .../helper-widgets/showImageWidget.tsx | 30 ++++++++++++++----- .../helper-widgets/widgetArgParser.tsx | 4 +-- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx index be2598cf1..b61725d6b 100644 --- a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx +++ b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx @@ -188,7 +188,7 @@ const parseArgs = (args: string[], types: string[]): Pixel => { parsedArgs.push(0); } } - //replace missing arguments with 0 + // Replace missing arguments with 0 while (parsedArgs.length < 3) { parsedArgs.push(0); } diff --git a/src/editor/codemirror/helper-widgets/showImageWidget.tsx b/src/editor/codemirror/helper-widgets/showImageWidget.tsx index 89f0eb8e2..3f907b011 100644 --- a/src/editor/codemirror/helper-widgets/showImageWidget.tsx +++ b/src/editor/codemirror/helper-widgets/showImageWidget.tsx @@ -135,13 +135,27 @@ export const MicrobitMultiplePixelComponent = ({ const updateView = () => { let insertion = pixelsToString(selectedPixels); console.log(insertion); - view.dispatch({ - changes: { - from: ranges[0].from, - to: ranges[0].to, - insert: insertion, - }, - }); + if (ranges.length === 1) { + view.dispatch({ + changes: { + from: ranges[0].from, + to: ranges[0].to, + insert: insertion, + }, + effects: [openWidgetEffect.of(to)], + }); + } else { + view.dispatch({ + changes: [ + { + from: from + 1, + to: to - 1, + insert: insertion, + }, + ], + effects: [openWidgetEffect.of(insertion.length + from + 2)], + }); + } }; const handleCloseClick = () => { @@ -161,7 +175,7 @@ export const MicrobitMultiplePixelComponent = ({ const parseArgs = (args: string[]): number[][] => { const defaultPixels = Array.from({ length: 5 }, () => Array(5).fill(0)); - //if args is empty, return a 5x5 array filled with zeros + // If args is empty, return a 5x5 array filled with zeros if (args.length === 0) { return defaultPixels; } diff --git a/src/editor/codemirror/helper-widgets/widgetArgParser.tsx b/src/editor/codemirror/helper-widgets/widgetArgParser.tsx index 6bcac7f1e..d956089ed 100644 --- a/src/editor/codemirror/helper-widgets/widgetArgParser.tsx +++ b/src/editor/codemirror/helper-widgets/widgetArgParser.tsx @@ -102,11 +102,11 @@ export function ValidateComponentArgs( case MicrobitMultiplePixelComponent: return true; case MicrobitSinglePixelComponent: - //if more than 3 arguments, don't open + // If more than 3 arguments, don't open if (args.length > 3) { return false; } - //if some arguments are not numbers or empty, don't open + // If some arguments are not numbers or empty, don't open for (let i = 0; i < args.length; i++) { if (types[i] !== "Number" && args[i] != ",") { return false; From afdf2be3e0b237993a27520053dcc17b37f6e408 Mon Sep 17 00:00:00 2001 From: Marta Religa <marta.religa@cs.ox.ac.uk> Date: Fri, 12 Apr 2024 11:06:03 +0200 Subject: [PATCH 090/106] . --- .../helper-widgets/setPixelWidget.tsx | 22 ++++++++++++++----- .../helper-widgets/showImageWidget.tsx | 13 ++++++++++- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx index b61725d6b..1ca2b4bf2 100644 --- a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx +++ b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx @@ -40,6 +40,11 @@ const MicrobitSinglePixelGrid: React.FC<MicrobitSinglePixelGridProps> = ({ onPixelClick(updatedPixel); }; + const calculateColor = () => { + const red = brightness * 25.5; + return `rgb(${red}, 0, 0)`; + }; + return ( <Box display="flex" @@ -68,11 +73,17 @@ const MicrobitSinglePixelGrid: React.FC<MicrobitSinglePixelGridProps> = ({ h="15px" w="15px" p={0} + borderRadius={0} bgColor={ gridX === x && gridY === y ? `rgba(255, 0, 0, ${brightness / 9})` : "rgba(255, 255, 255, 0)" } + border={ + gridX === x && gridY === y + ? "2px solid white" + : "0.5px solid white" + } _hover={{ bgColor: gridX === x && gridY === y @@ -100,7 +111,7 @@ const MicrobitSinglePixelGrid: React.FC<MicrobitSinglePixelGridProps> = ({ onChange={handleSliderChange} > <SliderTrack> - <SliderFilledTrack /> + <SliderFilledTrack bg={calculateColor()} /> </SliderTrack> <SliderThumb /> </Slider> @@ -132,7 +143,7 @@ export const MicrobitSinglePixelComponent = ({ const handleSelectPixel = (pixel: Pixel) => { const { x, y, brightness } = pixel; - if(ranges.length === 3){ + if (ranges.length === 3) { view.dispatch({ changes: [ { @@ -153,14 +164,13 @@ export const MicrobitSinglePixelComponent = ({ ], effects: [openWidgetEffect.of(to)], }); - } - else{ + } else { let vals = `${x},${y},${brightness}`; view.dispatch({ changes: [ { - from: from+1, - to: to-1, + from: from + 1, + to: to - 1, insert: vals, }, ], diff --git a/src/editor/codemirror/helper-widgets/showImageWidget.tsx b/src/editor/codemirror/helper-widgets/showImageWidget.tsx index 3f907b011..071e4d6d3 100644 --- a/src/editor/codemirror/helper-widgets/showImageWidget.tsx +++ b/src/editor/codemirror/helper-widgets/showImageWidget.tsx @@ -40,6 +40,11 @@ const MicrobitMultiplePixelsGrid: React.FC<MultiMicrobitGridProps> = ({ } }; + const calculateColor = () => { + const red = currentBrightness * 25.5; + return `rgb(${red}, 0, 0)`; + }; + return ( <Box display="flex" @@ -68,6 +73,12 @@ const MicrobitMultiplePixelsGrid: React.FC<MultiMicrobitGridProps> = ({ h="15px" w="15px" p={0} + borderRadius={0} + border={ + selectedPixel?.x === x && selectedPixel.y === y + ? "2px solid white" + : "0.5px solid white" + } bgColor={`rgba(255, 0, 0, ${brightness / 9})`} _hover={{ bgColor: @@ -96,7 +107,7 @@ const MicrobitMultiplePixelsGrid: React.FC<MultiMicrobitGridProps> = ({ onChange={(value) => handleBrightnessChange(value)} > <SliderTrack> - <SliderFilledTrack /> + <SliderFilledTrack bg={calculateColor()} /> </SliderTrack> <SliderThumb /> </Slider> From b111f666507312de6fc30ec6bff454cd9f2ef5d6 Mon Sep 17 00:00:00 2001 From: Aryan <aryan.rastogi1@yahoo.co.uk> Date: Tue, 16 Apr 2024 12:08:15 +0100 Subject: [PATCH 091/106] basic dynamic waveform! --- .../codemirror/helper-widgets/soundWidget.tsx | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/editor/codemirror/helper-widgets/soundWidget.tsx b/src/editor/codemirror/helper-widgets/soundWidget.tsx index 5dd98d5cf..cade7dd0f 100644 --- a/src/editor/codemirror/helper-widgets/soundWidget.tsx +++ b/src/editor/codemirror/helper-widgets/soundWidget.tsx @@ -125,19 +125,36 @@ const TripleSliderWidget: React.FC<{ slider3Props: SliderProps; isOpen: boolean; }> = ({ slider1Props, slider2Props, slider3Props, isOpen }) => { + + const [waveHeight, setWaveHeight] = useState(50); + const [waveLength, setWaveLength] = useState(50); + + const handleSlider1Change = (value: number) => { + slider1Props.onChange(value); + setWaveHeight(value); + }; + + const handleSlider2Change = (value: number) => { + slider2Props.onChange(value); + setWaveLength(value); // + }; + if (!isOpen) return null; return ( <div> <div style={{ display: "flex", justifyContent: "left" }}> <div style={{ marginRight: "40px" }}> - <Slider {...slider1Props} /> + <Slider {...slider1Props} onChange={handleSlider1Change} /> </div> <div style={{ marginRight: "40px" }}> - <Slider {...slider2Props} /> + <Slider {...slider2Props} onChange={handleSlider2Change} /> </div> <div> <Slider {...slider3Props} /> </div> + <svg width={waveLength} height={waveHeight} style={{ flexGrow: 1 }}> + <path d={`M0,${waveHeight / 2} Q${waveLength / 4},0 ${waveLength / 2},${waveHeight / 2} T${waveLength},${waveHeight / 2}`} stroke="black" fill="none" /> + </svg> </div> </div> ); @@ -163,7 +180,6 @@ export const SoundComponent = ({ }; const [isSoundEditorOpen, setIsSoundEditorOpen] = useState(false); const buttonLabel = isSoundEditorOpen ? "Close" : "Open"; - const handleButtonClick = () => { setIsSoundEditorOpen(!isSoundEditorOpen); // Toggle the state to open/close the DualSlider From b69bb80f7dbf8b38078daf041e355c3deba8a0f4 Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Tue, 16 Apr 2024 13:51:02 +0100 Subject: [PATCH 092/106] comments --- src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx | 3 +-- src/editor/codemirror/helper-widgets/setPixelWidget.tsx | 2 +- src/editor/codemirror/helper-widgets/widgetArgParser.tsx | 1 + 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx index af419953a..97a6dfea9 100644 --- a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx +++ b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx @@ -56,7 +56,6 @@ class Widget extends WidgetType { dom, <this.component props={this.props} view={view} /> ); - //else this.portalCleanup = this.createPortal(dom, React.createElement(this.component, this.props, view)); return dom; } @@ -71,7 +70,7 @@ class Widget extends WidgetType { } } -// Iterates through the syntax tree, finding occurences of SoundEffect ArgList, and places toy widget there +// Iterates through the syntax tree, finding occurences of SoundEffect ArgList, and places widget there export const reactWidgetExtension = ( createPortal: PortalFactory ): Extension => { diff --git a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx index 1ca2b4bf2..01eaabb90 100644 --- a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx +++ b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx @@ -46,7 +46,7 @@ const MicrobitSinglePixelGrid: React.FC<MicrobitSinglePixelGridProps> = ({ }; return ( - <Box + <Box // TODO: copy to allow other widgets to access bg and close display="flex" flexDirection="row" justifyContent="flex-start" diff --git a/src/editor/codemirror/helper-widgets/widgetArgParser.tsx b/src/editor/codemirror/helper-widgets/widgetArgParser.tsx index d956089ed..371b0f2b4 100644 --- a/src/editor/codemirror/helper-widgets/widgetArgParser.tsx +++ b/src/editor/codemirror/helper-widgets/widgetArgParser.tsx @@ -29,6 +29,7 @@ export function createWidget( component = MicrobitMultiplePixelComponent; break; case "audio.SoundEffect": + case "SoundEffect": component = SoundComponent; break; default: From 2efe920f14dbe7d676f3bf4d99cfb64d6a319924 Mon Sep 17 00:00:00 2001 From: Aryan <aryan.rastogi1@yahoo.co.uk> Date: Fri, 19 Apr 2024 21:08:08 +0100 Subject: [PATCH 093/106] Waveform know changes according to most parameters!!! --- .../codemirror/helper-widgets/soundWidget.tsx | 171 ++++++++++++++---- 1 file changed, 134 insertions(+), 37 deletions(-) diff --git a/src/editor/codemirror/helper-widgets/soundWidget.tsx b/src/editor/codemirror/helper-widgets/soundWidget.tsx index cade7dd0f..d617a2de6 100644 --- a/src/editor/codemirror/helper-widgets/soundWidget.tsx +++ b/src/editor/codemirror/helper-widgets/soundWidget.tsx @@ -19,9 +19,11 @@ interface SliderProps { onChange: (value: number) => void; sliderStyle?: React.CSSProperties; label: string; + vertical: boolean; + colour: string; } -const DurationSliderProps: SliderProps = { +const startVolProps: SliderProps = { min: 0, max: 100, step: 1, @@ -37,10 +39,12 @@ const DurationSliderProps: SliderProps = { border: "none", // Remove the border of the slider track outline: "none", // Remove the outline when focused }, - label: "Duration", + label: "Start Vol", + vertical: true, + colour: 'red' }; -const endSliderProps: SliderProps = { +const endFrequencySliderProps: SliderProps = { min: 0, max: 100, step: 1, @@ -57,9 +61,11 @@ const endSliderProps: SliderProps = { outline: "none", // Remove the outline when focused }, label: "End Freq", + vertical: true, + colour: 'green' }; -const startSliderProps: SliderProps = { +const startFrequencySliderProps: SliderProps = { min: 0, max: 100, step: 1, @@ -76,14 +82,38 @@ const startSliderProps: SliderProps = { outline: "none", // Remove the outline when focused }, label: "Start Freq", + vertical: true, + colour: 'blue' }; +const endVolProps: SliderProps = { + min: 0, + max: 100, + step: 1, + defaultValue: 50, + onChange: (value) => { + console.log("Slider value changed:", value); + }, + sliderStyle: { + width: "200%", // Adjust the width of the slider + height: "100px", // Adjust the height of the slider + backgroundColor: "lightgray", // Change the background color of the slider + borderRadius: "10px", // Apply rounded corners to the slider track + border: "none", // Remove the border of the slider track + outline: "none", // Remove the outline when focused + }, + label: "End Vol", + vertical: true, + colour: 'yellow' +}; + + const customSliderStyle: React.CSSProperties = { width: "80%", // Adjust the width of the slider height: "20px", // Adjust the height of the slider }; -const Slider: React.FC<SliderProps> = ({ +const Slider: React.FC<SliderProps & { vertical?: boolean, colour: string }> = ({ min, max, step, @@ -91,6 +121,8 @@ const Slider: React.FC<SliderProps> = ({ onChange, sliderStyle, label, + vertical, + colour }) => { const [value, setValue] = useState(defaultValue); @@ -104,14 +136,24 @@ const Slider: React.FC<SliderProps> = ({ <div style={{ display: "flex", flexDirection: "column", alignItems: "left" }} > - <input - type="range" - min={min} - max={max} - step={step} - value={value} - onChange={handleChange} - /> + <input + type="range" + min={min} + max={max} + step={step} + defaultValue={defaultValue} + onChange={handleChange} + style={{ + //writingMode: 'bt-lr', // Adjust writing mode for vertical slider + width: vertical ? '100px' : '150px', // Use width to control vertical slider size + height: vertical ? '150px' : '40px', // Use height to control vertical slider size + transform: vertical ? 'rotate(270deg)' : 'none', // Rotate slider to vertical orientation + background: vertical ? 'none' : '#d3d3d3', // Background of the track (horizontal) + accentColor: `${colour}`, // Thumb color in modern browsers + //WebkitAppearance: 'slider-vertical', + //MozAppearance: vertical ? 'slider-vertical' : undefined, + }} + /> <label style={{ fontSize: "16px" }}> {label}: {value} </label> @@ -123,38 +165,88 @@ const TripleSliderWidget: React.FC<{ slider1Props: SliderProps; slider2Props: SliderProps; slider3Props: SliderProps; + slider4Props: SliderProps; isOpen: boolean; -}> = ({ slider1Props, slider2Props, slider3Props, isOpen }) => { +}> = ({ slider1Props, slider2Props, slider3Props, slider4Props, isOpen }) => { - const [waveHeight, setWaveHeight] = useState(50); - const [waveLength, setWaveLength] = useState(50); + const [startAmplitude, setStartAmplitude] = useState(slider3Props.defaultValue); + const [endAmplitude, setEndAmplitude] = useState(50); + const [initialFrequency, setInitialFrequency] = useState(slider1Props.defaultValue); + const [endFrequency, setEndFrequency] = useState(slider2Props.defaultValue); const handleSlider1Change = (value: number) => { slider1Props.onChange(value); - setWaveHeight(value); + setInitialFrequency(value); }; const handleSlider2Change = (value: number) => { slider2Props.onChange(value); - setWaveLength(value); // + setEndFrequency(value); // + }; + + const handleSlider3Change = (value: number) => { + slider1Props.onChange(value); + setStartAmplitude(value); + }; + + const handleSlider4Change = (value: number) => { + slider1Props.onChange(value); + setEndAmplitude(value); }; - if (!isOpen) return null; + + + const generateWavePath = () => { + const waveLength = 400; // Width of the box + const pathData = []; + + // Calculate the change in frequency and amplitude over the length of the waveform + const frequencyDifference = endFrequency - initialFrequency; + const amplitudeDifference = endAmplitude - startAmplitude; + + // Loop through the wave's width to generate the path + for (let x = 0; x <= waveLength; x++) { + // Calculate the frequency and amplitude at the current point + const currentFrequency = initialFrequency + (frequencyDifference * x) / waveLength; + const currentAmplitude = startAmplitude + (amplitudeDifference * x) / waveLength; + + // Calculate the y-coordinate based on the current frequency and amplitude + const y = 50 + currentAmplitude * Math.sin(currentFrequency * x * (2 * Math.PI) / waveLength); + + // Add the point to the path data + pathData.push(`${x},${y}`); + } + + // Join the path data points to create the path + return `M${pathData.join(' ')}`; + }; + + //if (!isOpen) return null; return ( <div> - <div style={{ display: "flex", justifyContent: "left" }}> - <div style={{ marginRight: "40px" }}> - <Slider {...slider1Props} onChange={handleSlider1Change} /> - </div> - <div style={{ marginRight: "40px" }}> - <Slider {...slider2Props} onChange={handleSlider2Change} /> - </div> - <div> - <Slider {...slider3Props} /> - </div> - <svg width={waveLength} height={waveHeight} style={{ flexGrow: 1 }}> - <path d={`M0,${waveHeight / 2} Q${waveLength / 4},0 ${waveLength / 2},${waveHeight / 2} T${waveLength},${waveHeight / 2}`} stroke="black" fill="none" /> - </svg> + <div style={{ display: "flex", justifyContent: "flex-start", alignItems: "center" }}> + {/* Vertical Slider 1 */} + <div style={{ marginRight: "20px" }}> + <Slider {...slider1Props} onChange={handleSlider1Change} vertical /> + </div> + {/* Vertical Slider 2 */} + <div style={{ marginRight: "20px" }}> + <Slider {...slider2Props} onChange={handleSlider2Change} vertical /> + </div> + {/* Vertical Slider 3 */} + <div style={{ marginRight: "20px" }}> + <Slider {...slider3Props} onChange={handleSlider3Change} vertical /> + </div> + {/* Vertical Slider 4 */} + <div style={{ marginRight: "20px" }}> + <Slider {...slider4Props} onChange={handleSlider4Change} vertical /> + </div> + {/* Waveform box */} + <div style={{ width: '200px', height: '100px', border: '1px solid black' }}> + <svg width="100%" height="100%"> + <path d={generateWavePath()} stroke="black" fill="none" /> + </svg> + </div> </div> </div> ); @@ -192,14 +284,19 @@ export const SoundComponent = ({ insert: //something from state of component`, }, */ - +// <Button onClick={handleButtonClick}>{buttonLabel} Sound Editor</Button> return ( <HStack fontFamily="body" spacing={5} py={3}> - <Button onClick={handleButtonClick}>{buttonLabel} Sound Editor</Button> + <Box ml="10px" style={{ marginRight: "4px" }}> + <Button size="xs" onClick={handleCloseClick} bg="white"> + X + </Button> + </Box> <TripleSliderWidget - slider1Props={startSliderProps} - slider2Props={endSliderProps} - slider3Props={DurationSliderProps} + slider1Props={startFrequencySliderProps} + slider2Props={endFrequencySliderProps} + slider3Props={startVolProps} + slider4Props={endVolProps} isOpen={isSoundEditorOpen} /> </HStack> From cdc0e815728bc7c6f68b3edbcde5c35b4bfb78b8 Mon Sep 17 00:00:00 2001 From: Aryan <aryan.rastogi1@yahoo.co.uk> Date: Sat, 20 Apr 2024 20:36:51 +0100 Subject: [PATCH 094/106] sound widget complete --- package-lock.json | 6 + package.json | 1 + .../codemirror/helper-widgets/soundWidget.tsx | 185 +++++++++++++----- 3 files changed, 146 insertions(+), 46 deletions(-) diff --git a/package-lock.json b/package-lock.json index 225f778d7..e629fd6cb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,6 +47,7 @@ "lzma": "^2.3.2", "marked": "^4.0.15", "mobile-drag-drop": "^2.3.0-rc.2", + "perlin-noise": "^0.0.1", "react": "^17.0.2", "react-dom": "^17.0.2", "react-icons": "^4.8.0", @@ -15851,6 +15852,11 @@ "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", "dev": true }, + "node_modules/perlin-noise": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/perlin-noise/-/perlin-noise-0.0.1.tgz", + "integrity": "sha512-33wNN1FN7jZPF0ISkSF8BLag71wjBWzrpzd/m00iFsxtIhKeZ8VaKBQtzPX3TBegK9GYPXwGzR3oJp9v2T7QuQ==" + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", diff --git a/package.json b/package.json index b7e3739bb..591c857da 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "lzma": "^2.3.2", "marked": "^4.0.15", "mobile-drag-drop": "^2.3.0-rc.2", + "perlin-noise": "^0.0.1", "react": "^17.0.2", "react-dom": "^17.0.2", "react-icons": "^4.8.0", diff --git a/src/editor/codemirror/helper-widgets/soundWidget.tsx b/src/editor/codemirror/helper-widgets/soundWidget.tsx index d617a2de6..b34fbadaf 100644 --- a/src/editor/codemirror/helper-widgets/soundWidget.tsx +++ b/src/editor/codemirror/helper-widgets/soundWidget.tsx @@ -46,9 +46,9 @@ const startVolProps: SliderProps = { const endFrequencySliderProps: SliderProps = { min: 0, - max: 100, + max: 999, step: 1, - defaultValue: 50, + defaultValue: 500, onChange: (value) => { console.log("Slider value changed:", value); }, @@ -67,9 +67,9 @@ const endFrequencySliderProps: SliderProps = { const startFrequencySliderProps: SliderProps = { min: 0, - max: 100, + max: 999, step: 1, - defaultValue: 50, + defaultValue: 500, onChange: (value) => { console.log("Slider value changed:", value); }, @@ -104,13 +104,7 @@ const endVolProps: SliderProps = { }, label: "End Vol", vertical: true, - colour: 'yellow' -}; - - -const customSliderStyle: React.CSSProperties = { - width: "80%", // Adjust the width of the slider - height: "20px", // Adjust the height of the slider + colour: 'black' }; const Slider: React.FC<SliderProps & { vertical?: boolean, colour: string }> = ({ @@ -133,34 +127,42 @@ const Slider: React.FC<SliderProps & { vertical?: boolean, colour: string }> = ( }; return ( - <div - style={{ display: "flex", flexDirection: "column", alignItems: "left" }} - > + <div style={{ position: 'relative', height: '80px', width: '45px', display: "flex", flexDirection: "column", alignItems: "center" }}> <input - type="range" - min={min} - max={max} - step={step} - defaultValue={defaultValue} - onChange={handleChange} - style={{ - //writingMode: 'bt-lr', // Adjust writing mode for vertical slider - width: vertical ? '100px' : '150px', // Use width to control vertical slider size - height: vertical ? '150px' : '40px', // Use height to control vertical slider size - transform: vertical ? 'rotate(270deg)' : 'none', // Rotate slider to vertical orientation - background: vertical ? 'none' : '#d3d3d3', // Background of the track (horizontal) - accentColor: `${colour}`, // Thumb color in modern browsers - //WebkitAppearance: 'slider-vertical', - //MozAppearance: vertical ? 'slider-vertical' : undefined, - }} + type="range" + min={min} + max={max} + step={step} + value={value} + onChange={handleChange} + style={{ + position: 'absolute', + width: '115px', // Width of the slider + height: '40px', // Height of the slider + transform: 'rotate(-90deg)', // Rotate the slider to vertical orientation + accentColor: colour, + bottom: '0%', + }} /> - <label style={{ fontSize: "16px" }}> - {label}: {value} - </label> + <div + style={{ + position: 'absolute', + left: 'calc(100% - 15px)', // Position the label to the right of the slider + bottom: `${((value - min) / (max - min)) * 100}%`, // Calculate the position based on value + transform: 'translateY(50%)', // Center the label vertically with the thumb + fontSize: '13px', // Font size of the label + }} + > + {value} </div> + <div style={{ marginTop: '120px', textAlign: 'center', fontSize: '11px', }}> + <b>{label}</b> + </div> +</div> ); }; + const TripleSliderWidget: React.FC<{ slider1Props: SliderProps; slider2Props: SliderProps; @@ -171,17 +173,25 @@ const TripleSliderWidget: React.FC<{ const [startAmplitude, setStartAmplitude] = useState(slider3Props.defaultValue); const [endAmplitude, setEndAmplitude] = useState(50); - const [initialFrequency, setInitialFrequency] = useState(slider1Props.defaultValue); - const [endFrequency, setEndFrequency] = useState(slider2Props.defaultValue); + const [initialFrequency, setInitialFrequency] = useState(50); + const [endFrequency, setEndFrequency] = useState(50); + const [waveType, setWaveType] = useState('sine') + const waveformOptions = ["None", "Vibrato", "Tremolo", "Warble"] + const [textBoxValue, setTextBoxValue] = useState("2000"); + + const handleTextInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { + const newValue = e.target.value; + setTextBoxValue(newValue); + }; const handleSlider1Change = (value: number) => { slider1Props.onChange(value); - setInitialFrequency(value); + setInitialFrequency(value/10); }; const handleSlider2Change = (value: number) => { slider2Props.onChange(value); - setEndFrequency(value); // + setEndFrequency(value/10); // }; const handleSlider3Change = (value: number) => { @@ -194,6 +204,10 @@ const TripleSliderWidget: React.FC<{ setEndAmplitude(value); }; + const handleWaveTypeChange = (value: string) => { + setWaveType(value); + }; + const generateWavePath = () => { @@ -209,9 +223,35 @@ const TripleSliderWidget: React.FC<{ // Calculate the frequency and amplitude at the current point const currentFrequency = initialFrequency + (frequencyDifference * x) / waveLength; const currentAmplitude = startAmplitude + (amplitudeDifference * x) / waveLength; + const period = waveLength/currentFrequency + // Calculate the y-coordinate based on the current frequency and amplitude - const y = 50 + currentAmplitude * Math.sin(currentFrequency * x * (2 * Math.PI) / waveLength); + let y = 0; + switch (waveType) { + case 'sine': + y = 65 + currentAmplitude * Math.sin((x / period) * 2 * Math.PI); + break; + case 'square': + y = x % period < period / 2 ? 65 + currentAmplitude : 65 - currentAmplitude; + break; + case 'sawtooth': + y = 65 + currentAmplitude - ((x % period) / period) * (2 * currentAmplitude); + break; + case 'triangle': + const tPeriod = x % period; + y = tPeriod < period / 2 + ? 65 + (2 * currentAmplitude / period) * tPeriod + : 65 - (2 * currentAmplitude / period) * (tPeriod - period / 2); + break; + case 'noisy': + // Generate noisy wave based on sine wave and random noise + // Add a random noise value to the sine wave + const baseWave = 65 + currentAmplitude * Math.sin((x / period) * 2 * Math.PI); + const randomNoise = Math.random() * 2 - 1; // Random noise between -1 and 1 + y = baseWave + randomNoise * (currentAmplitude*0.3); + break; + } // Add the point to the path data pathData.push(`${x},${y}`); @@ -221,37 +261,90 @@ const TripleSliderWidget: React.FC<{ return `M${pathData.join(' ')}`; }; - //if (!isOpen) return null; return ( <div> - <div style={{ display: "flex", justifyContent: "flex-start", alignItems: "center" }}> + <div style={{ display: "flex", justifyContent: "flex-start", backgroundColor: 'white', width: '575px', height: '150px', border: '1px solid lightgray'}}> {/* Vertical Slider 1 */} - <div style={{ marginRight: "20px" }}> + <div style={{marginLeft: "6px", marginRight: "20px", height: '100px', marginTop: '9px'}}> <Slider {...slider1Props} onChange={handleSlider1Change} vertical /> </div> {/* Vertical Slider 2 */} - <div style={{ marginRight: "20px" }}> + <div style={{ marginRight: "20px", height: '100px', marginTop: '9px'}}> <Slider {...slider2Props} onChange={handleSlider2Change} vertical /> </div> {/* Vertical Slider 3 */} - <div style={{ marginRight: "20px" }}> + <div style={{ marginRight: "20px", height: '100px', marginTop: '9px' }}> <Slider {...slider3Props} onChange={handleSlider3Change} vertical /> </div> {/* Vertical Slider 4 */} - <div style={{ marginRight: "20px" }}> + <div style={{ marginRight: "25px", height: '100px', marginTop: '9px' }}> <Slider {...slider4Props} onChange={handleSlider4Change} vertical /> </div> + + + <div style={{ marginRight: '10px', height: '100px', fontSize: '12px' }}> + + {/* waveform type selection */} + <label style={{ display: 'block', marginBottom: '5px', marginTop: '7px',}}> + <b>Waveform:</b> + </label> + <select onChange={(e) => handleWaveTypeChange(e.target.value)}> + <option value="sine">Sine</option> + <option value="square">Square</option> + <option value="sawtooth">Sawtooth</option> + <option value="triangle">Triangle</option> + <option value="noisy">Noisy</option> + </select> + + {/* fx type selection */} + + <label style={{ display: 'block', marginBottom: '5px', marginTop: '10px'}}> + <b>Effects:</b> + </label> + <select onChange={(e) => handleWaveTypeChange(e.target.value)}> + <option value="sine">None</option> + <option value="square">Vibrato</option> + <option value="sawtooth">Tremelo</option> + <option value="triangle">Warble</option> + </select> + + {/* Duration selctor */} + + <label style={{ display: 'block', marginBottom: '5px', marginTop: '10px' }}> + <b>Duration(ms):</b> + </label> + {/* Input field with associated datalist */} + <input + type="text" + value={textBoxValue} + onChange={handleTextInputChange} // Handle the selected or typed-in value + defaultValue="2000" + style={{ width: '75px' }} + /> + + </div> {/* Waveform box */} - <div style={{ width: '200px', height: '100px', border: '1px solid black' }}> + <div style={{ width: '200px', height: '130px', backgroundColor: 'linen', marginTop: '9px', marginLeft: '5px'}}> <svg width="100%" height="100%"> <path d={generateWavePath()} stroke="black" fill="none" /> + <line + x1="0%" // Start of the line + y1="50%" // Vertically center the line + x2="100%" // End of the line + y2="50%" // Keep the line horizontal + stroke="gray" // Line color + strokeWidth="0.5" // Line thickness + /> </svg> </div> - </div> </div> +</div> + ); }; + + export const SoundComponent = ({ props, view, From 9cac1832c7e7dad1a44eef388894b3b24fdfa6ae Mon Sep 17 00:00:00 2001 From: Aryan <aryan.rastogi1@yahoo.co.uk> Date: Sat, 20 Apr 2024 21:24:08 +0100 Subject: [PATCH 095/106] minor UI changes --- src/editor/codemirror/helper-widgets/soundWidget.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/editor/codemirror/helper-widgets/soundWidget.tsx b/src/editor/codemirror/helper-widgets/soundWidget.tsx index b34fbadaf..da2bf4356 100644 --- a/src/editor/codemirror/helper-widgets/soundWidget.tsx +++ b/src/editor/codemirror/helper-widgets/soundWidget.tsx @@ -140,7 +140,7 @@ const Slider: React.FC<SliderProps & { vertical?: boolean, colour: string }> = ( width: '115px', // Width of the slider height: '40px', // Height of the slider transform: 'rotate(-90deg)', // Rotate the slider to vertical orientation - accentColor: colour, + accentColor: colour, bottom: '0%', }} /> @@ -263,7 +263,7 @@ const TripleSliderWidget: React.FC<{ return ( <div> - <div style={{ display: "flex", justifyContent: "flex-start", backgroundColor: 'white', width: '575px', height: '150px', border: '1px solid lightgray'}}> + <div style={{ display: "flex", justifyContent: "flex-start", backgroundColor: 'snow', width: '575px', height: '150px', border: '1px solid lightgray'}}> {/* Vertical Slider 1 */} <div style={{marginLeft: "6px", marginRight: "20px", height: '100px', marginTop: '9px'}}> <Slider {...slider1Props} onChange={handleSlider1Change} vertical /> From 236ae83914a183ad930e8b19a9578ebbfaecb06e Mon Sep 17 00:00:00 2001 From: Marta Religa <marta.religa@cs.ox.ac.uk> Date: Sun, 21 Apr 2024 11:52:17 +0100 Subject: [PATCH 096/106] ye --- src/editor/codemirror/helper-widgets/showImageWidget.tsx | 5 +++-- src/editor/codemirror/helper-widgets/widgetArgParser.tsx | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/editor/codemirror/helper-widgets/showImageWidget.tsx b/src/editor/codemirror/helper-widgets/showImageWidget.tsx index 071e4d6d3..c7d8d8201 100644 --- a/src/editor/codemirror/helper-widgets/showImageWidget.tsx +++ b/src/editor/codemirror/helper-widgets/showImageWidget.tsx @@ -153,7 +153,7 @@ export const MicrobitMultiplePixelComponent = ({ to: ranges[0].to, insert: insertion, }, - effects: [openWidgetEffect.of(to)], + effects: [openWidgetEffect.of(insertion.length + from + 2)], }); } else { view.dispatch({ @@ -211,7 +211,7 @@ const parseArgs = (args: string[]): number[][] => { }; function pixelsToString(pixels: number[][]): string { - let outputString = ""; + let outputString = '"'; for (let y = 0; y < 5; y++) { for (let x = 0; x < 5; x++) { outputString += pixels[y][x].toString(); @@ -219,5 +219,6 @@ function pixelsToString(pixels: number[][]): string { outputString += ":"; } outputString = outputString.slice(0, -1); + outputString += '"'; return outputString; } diff --git a/src/editor/codemirror/helper-widgets/widgetArgParser.tsx b/src/editor/codemirror/helper-widgets/widgetArgParser.tsx index 371b0f2b4..6dbcf922c 100644 --- a/src/editor/codemirror/helper-widgets/widgetArgParser.tsx +++ b/src/editor/codemirror/helper-widgets/widgetArgParser.tsx @@ -109,7 +109,7 @@ export function ValidateComponentArgs( } // If some arguments are not numbers or empty, don't open for (let i = 0; i < args.length; i++) { - if (types[i] !== "Number" && args[i] != ",") { + if (types[i] !== "Number" && args[i] !== ",") { return false; } } From 372763863357cf104529d0cea40dfbf28f613051 Mon Sep 17 00:00:00 2001 From: Marta Religa <marta.religa@cs.ox.ac.uk> Date: Sun, 21 Apr 2024 12:03:20 +0100 Subject: [PATCH 097/106] . --- src/editor/codemirror/helper-widgets/showImageWidget.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/editor/codemirror/helper-widgets/showImageWidget.tsx b/src/editor/codemirror/helper-widgets/showImageWidget.tsx index c7d8d8201..484747ebb 100644 --- a/src/editor/codemirror/helper-widgets/showImageWidget.tsx +++ b/src/editor/codemirror/helper-widgets/showImageWidget.tsx @@ -129,8 +129,6 @@ export const MicrobitMultiplePixelComponent = ({ let from = props.from; let to = props.to; - console.log(args); - console.log(types); const initialSelectedPixels = parseArgs(args); const [selectedPixels, setSelectedPixels] = useState<number[][]>( initialSelectedPixels @@ -193,7 +191,7 @@ const parseArgs = (args: string[]): number[][] => { if (args.length !== 1) { return defaultPixels; } - const argString = args[0]; + const argString = args[0].replace(/"/g, ""); const rows = argString.split(":"); if (rows.length !== 5) { return defaultPixels; From aa5fa41b8ef4d4583b231e4cb40707db37c48f15 Mon Sep 17 00:00:00 2001 From: Aryan <aryan.rastogi1@yahoo.co.uk> Date: Sun, 21 Apr 2024 12:19:22 +0100 Subject: [PATCH 098/106] Props to triple widget --- .../codemirror/helper-widgets/soundWidget.tsx | 38 +++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/src/editor/codemirror/helper-widgets/soundWidget.tsx b/src/editor/codemirror/helper-widgets/soundWidget.tsx index da2bf4356..8aa7756ad 100644 --- a/src/editor/codemirror/helper-widgets/soundWidget.tsx +++ b/src/editor/codemirror/helper-widgets/soundWidget.tsx @@ -53,12 +53,12 @@ const endFrequencySliderProps: SliderProps = { console.log("Slider value changed:", value); }, sliderStyle: { - width: "100%", // Adjust the width of the slider - height: "100px", // Adjust the height of the slider - backgroundColor: "lightgray", // Change the background color of the slider - borderRadius: "10px", // Apply rounded corners to the slider track - border: "none", // Remove the border of the slider track - outline: "none", // Remove the outline when focused + width: "100%", + height: "100px", + backgroundColor: "lightgray", + borderRadius: "10px", + border: "none", + outline: "none", }, label: "End Freq", vertical: true, @@ -168,8 +168,15 @@ const TripleSliderWidget: React.FC<{ slider2Props: SliderProps; slider3Props: SliderProps; slider4Props: SliderProps; - isOpen: boolean; -}> = ({ slider1Props, slider2Props, slider3Props, slider4Props, isOpen }) => { + props: WidgetProps; + view: EditorView; +}> = ({ slider1Props, slider2Props, slider3Props, slider4Props, props}) => { + + let args = props.args; + let ranges = props.ranges; + let types = props.types; + let from = props.from; + let to = props.to; const [startAmplitude, setStartAmplitude] = useState(slider3Props.defaultValue); const [endAmplitude, setEndAmplitude] = useState(50); @@ -352,23 +359,13 @@ export const SoundComponent = ({ props: WidgetProps; view: EditorView; }) => { - let args = props.args; - //let ranges = props.ranges; - let types = props.types; - let from = props.from; - let to = props.to; + //for future reference add a aclose button const handleCloseClick = () => { view.dispatch({ effects: [openWidgetEffect.of(-1)], }); }; - const [isSoundEditorOpen, setIsSoundEditorOpen] = useState(false); - const buttonLabel = isSoundEditorOpen ? "Close" : "Open"; - const handleButtonClick = () => { - setIsSoundEditorOpen(!isSoundEditorOpen); - // Toggle the state to open/close the DualSlider - }; /* view.dispatch({ changes: { @@ -390,7 +387,8 @@ export const SoundComponent = ({ slider2Props={endFrequencySliderProps} slider3Props={startVolProps} slider4Props={endVolProps} - isOpen={isSoundEditorOpen} + props={props} + view={view} /> </HStack> ); From d5479dc692e31072fcbf7a1e602d231c0f1aeaaf Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Tue, 23 Apr 2024 14:03:45 +0100 Subject: [PATCH 099/106] eq check for Widget --- .../codemirror/helper-widgets/reactWidgetExtension.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx index 97a6dfea9..9936cbb84 100644 --- a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx +++ b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx @@ -40,6 +40,11 @@ class Widget extends WidgetType { super(); } + eq(other: WidgetType): boolean { + const them = other as Widget; + return them.component === this.component && them.props.to === this.props.to && them.inline === this.inline; + } + toDOM(view: EditorView) { const dom = document.createElement("div"); From f67a3d290dee1d1846ecda44aba1e885e05285c5 Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Tue, 23 Apr 2024 14:45:09 +0100 Subject: [PATCH 100/106] option to have different open buttons --- .../helper-widgets/widgetArgParser.tsx | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/editor/codemirror/helper-widgets/widgetArgParser.tsx b/src/editor/codemirror/helper-widgets/widgetArgParser.tsx index 6dbcf922c..e4238ca8c 100644 --- a/src/editor/codemirror/helper-widgets/widgetArgParser.tsx +++ b/src/editor/codemirror/helper-widgets/widgetArgParser.tsx @@ -8,6 +8,7 @@ import { SoundComponent } from "./soundWidget"; export interface CompProps { comp: React.ComponentType<any>; props: WidgetProps; + open: boolean } export function createWidget( @@ -47,6 +48,7 @@ export function createWidget( from: node.from, to: node.to, }, + open: OpenButtonDesign(component, args, types) }; } return null; @@ -94,6 +96,24 @@ function getTypes(nodes: SyntaxNode[]): string[] { return types; } +function OpenButtonDesign( + name: React.ComponentType<any>, + args: string[], + types: string[] +): boolean { + switch (name) { + case MicrobitMultiplePixelComponent: + return true; + case MicrobitSinglePixelComponent: + return true; + case SoundComponent: + return true; + default: + // shouldnt be called so just null + return false; + } +} + export function ValidateComponentArgs( name: React.ComponentType<any>, args: string[], From 0221179d049205b38b19f76fed4865e3ff096253 Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Tue, 23 Apr 2024 14:53:27 +0100 Subject: [PATCH 101/106] different open button displays backend --- .../helper-widgets/reactWidgetExtension.tsx | 4 +++- .../codemirror/helper-widgets/widgetArgParser.tsx | 13 +++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx index 9936cbb84..1b053b52c 100644 --- a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx +++ b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx @@ -34,6 +34,7 @@ class Widget extends WidgetType { constructor( private component: React.ComponentType<any>, private props: WidgetProps, + private open: React.ComponentType<any>, private inline: boolean, private createPortal: PortalFactory ) { @@ -53,7 +54,7 @@ class Widget extends WidgetType { dom.style.display = "inline-block"; // want it inline for the open-close widget this.portalCleanup = this.createPortal( dom, - <OpenReactComponent loc={this.props.to} view={view} /> + <this.open loc={this.props.to} view={view} /> ); } } else @@ -94,6 +95,7 @@ export const reactWidgetExtension = ( widget: new Widget( widget.comp, widget.props, + widget.open, widget.props.to !== openWidgetLoc, createPortal ), diff --git a/src/editor/codemirror/helper-widgets/widgetArgParser.tsx b/src/editor/codemirror/helper-widgets/widgetArgParser.tsx index e4238ca8c..20a911282 100644 --- a/src/editor/codemirror/helper-widgets/widgetArgParser.tsx +++ b/src/editor/codemirror/helper-widgets/widgetArgParser.tsx @@ -4,11 +4,12 @@ import { WidgetProps } from "./reactWidgetExtension"; import { MicrobitSinglePixelComponent } from "./setPixelWidget"; import { MicrobitMultiplePixelComponent } from "./showImageWidget"; import { SoundComponent } from "./soundWidget"; +import { OpenReactComponent } from "./openWidgets"; export interface CompProps { comp: React.ComponentType<any>; props: WidgetProps; - open: boolean + open: React.ComponentType<any> } export function createWidget( @@ -100,17 +101,17 @@ function OpenButtonDesign( name: React.ComponentType<any>, args: string[], types: string[] -): boolean { +): React.ComponentType<any> { switch (name) { case MicrobitMultiplePixelComponent: - return true; + return OpenReactComponent; case MicrobitSinglePixelComponent: - return true; + return OpenReactComponent; case SoundComponent: - return true; + return OpenReactComponent; default: // shouldnt be called so just null - return false; + return OpenReactComponent; } } From ff5a9ae6c59ee22c10211014460ebb001ea507c9 Mon Sep 17 00:00:00 2001 From: Aryan <aryan.rastogi1@yahoo.co.uk> Date: Tue, 23 Apr 2024 17:13:00 +0100 Subject: [PATCH 102/106] Open button now customised to component. Fixed line indent issue. Messed around with set pixel UI --- .../codemirror/helper-widgets/openWidgets.tsx | 66 +++++++++++++++++-- .../helper-widgets/setPixelWidget.tsx | 8 +-- .../codemirror/helper-widgets/soundWidget.tsx | 15 +++-- .../helper-widgets/widgetArgParser.tsx | 4 +- 4 files changed, 77 insertions(+), 16 deletions(-) diff --git a/src/editor/codemirror/helper-widgets/openWidgets.tsx b/src/editor/codemirror/helper-widgets/openWidgets.tsx index f8df3e5b4..fcb392471 100644 --- a/src/editor/codemirror/helper-widgets/openWidgets.tsx +++ b/src/editor/codemirror/helper-widgets/openWidgets.tsx @@ -1,4 +1,4 @@ -import { Button, HStack } from "@chakra-ui/react"; +import { Button, Center, HStack } from "@chakra-ui/react"; import { StateEffect } from "@codemirror/state"; import { EditorView } from "@codemirror/view"; import { useCallback } from "react"; @@ -17,8 +17,66 @@ export const OpenReactComponent = ({ }); }, [loc, view]); return ( - <HStack fontFamily="body" spacing={5} py={3}> - <Button onClick={handleClick}>Open</Button> - </HStack> + <Button onClick={handleClick} size="xs">Open</Button> ); }; + + +function createSoundWavePath(): string { + let pathData = 'M0,12'; + + const totalPoints = 18; + + + const stepSize = 24 / totalPoints; + + for (let i = 0; i < totalPoints; i++) { + const x = i * stepSize; + const angle = (x / totalPoints) * 3 * Math.PI; + + const heightVariation = Math.cos(angle) * 6; + const y1 = 12 + heightVariation; + const y2 = 12 - heightVariation; + + pathData += ` M${x},${y1} L${x},${y2}`; + } + + return pathData; +} + +export const OpenSoundComponent = ({ + loc, + view, +}: { + loc: number; + view: EditorView; +}) => { + + + + const handleClick = useCallback(() => { + view.dispatch({ + effects: [openWidgetEffect.of(loc)], + }); + }, [loc, view]); + + const soundWavePath = createSoundWavePath(); + + return ( + <Button onClick={handleClick} size="sm" height="25px" marginBottom="3px" marginLeft="5px" style={{ padding: '3px 3px' }}> + <svg + width="20" + height="18" + viewBox="0 0 24 24" + fill="none" + > + <path + d={soundWavePath} + stroke="green" + strokeWidth="1" + fill="none" + /> + </svg> + </Button> + ); +}; \ No newline at end of file diff --git a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx index 01eaabb90..d389ad563 100644 --- a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx +++ b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx @@ -22,7 +22,6 @@ interface MicrobitSinglePixelGridProps { initialPixel: Pixel | null; onCloseClick: () => void; } - const MicrobitSinglePixelGrid: React.FC<MicrobitSinglePixelGridProps> = ({ onPixelClick, initialPixel, @@ -67,11 +66,10 @@ const MicrobitSinglePixelGrid: React.FC<MicrobitSinglePixelGridProps> = ({ {[...Array(5)].map((_, gridY) => ( <Box key={y} display="flex"> {[...Array(5)].map((_, gridX) => ( - <Box key={x} display="flex" mr="2px"> + <Box key={x} display="flex" mr="0px"> <Button - size="xs" - h="15px" - w="15px" + height="30px" + width="30x" p={0} borderRadius={0} bgColor={ diff --git a/src/editor/codemirror/helper-widgets/soundWidget.tsx b/src/editor/codemirror/helper-widgets/soundWidget.tsx index 8aa7756ad..76405569a 100644 --- a/src/editor/codemirror/helper-widgets/soundWidget.tsx +++ b/src/editor/codemirror/helper-widgets/soundWidget.tsx @@ -186,6 +186,14 @@ const TripleSliderWidget: React.FC<{ const waveformOptions = ["None", "Vibrato", "Tremolo", "Warble"] const [textBoxValue, setTextBoxValue] = useState("2000"); + + + for (let i = 0; i < args.length; i++) { + let arg = args[i]; + console.log("arg: ", arg); + }; + + const handleTextInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { const newValue = e.target.value; setTextBoxValue(newValue); @@ -221,13 +229,11 @@ const TripleSliderWidget: React.FC<{ const waveLength = 400; // Width of the box const pathData = []; - // Calculate the change in frequency and amplitude over the length of the waveform const frequencyDifference = endFrequency - initialFrequency; const amplitudeDifference = endAmplitude - startAmplitude; // Loop through the wave's width to generate the path for (let x = 0; x <= waveLength; x++) { - // Calculate the frequency and amplitude at the current point const currentFrequency = initialFrequency + (frequencyDifference * x) / waveLength; const currentAmplitude = startAmplitude + (amplitudeDifference * x) / waveLength; const period = waveLength/currentFrequency @@ -253,9 +259,8 @@ const TripleSliderWidget: React.FC<{ break; case 'noisy': // Generate noisy wave based on sine wave and random noise - // Add a random noise value to the sine wave const baseWave = 65 + currentAmplitude * Math.sin((x / period) * 2 * Math.PI); - const randomNoise = Math.random() * 2 - 1; // Random noise between -1 and 1 + const randomNoise = Math.random() * 2 - 1; y = baseWave + randomNoise * (currentAmplitude*0.3); break; } @@ -270,7 +275,7 @@ const TripleSliderWidget: React.FC<{ return ( <div> - <div style={{ display: "flex", justifyContent: "flex-start", backgroundColor: 'snow', width: '575px', height: '150px', border: '1px solid lightgray'}}> + <div style={{ display: "flex", justifyContent: "flex-start", backgroundColor: 'snow', width: '575px', height: '150px', border: '1px solid lightgray', boxShadow: '0 0 10px 5px rgba(173, 216, 230, 0.7)'}}> {/* Vertical Slider 1 */} <div style={{marginLeft: "6px", marginRight: "20px", height: '100px', marginTop: '9px'}}> <Slider {...slider1Props} onChange={handleSlider1Change} vertical /> diff --git a/src/editor/codemirror/helper-widgets/widgetArgParser.tsx b/src/editor/codemirror/helper-widgets/widgetArgParser.tsx index 20a911282..db6da1964 100644 --- a/src/editor/codemirror/helper-widgets/widgetArgParser.tsx +++ b/src/editor/codemirror/helper-widgets/widgetArgParser.tsx @@ -4,7 +4,7 @@ import { WidgetProps } from "./reactWidgetExtension"; import { MicrobitSinglePixelComponent } from "./setPixelWidget"; import { MicrobitMultiplePixelComponent } from "./showImageWidget"; import { SoundComponent } from "./soundWidget"; -import { OpenReactComponent } from "./openWidgets"; +import { OpenReactComponent, OpenSoundComponent } from "./openWidgets"; export interface CompProps { comp: React.ComponentType<any>; @@ -108,7 +108,7 @@ function OpenButtonDesign( case MicrobitSinglePixelComponent: return OpenReactComponent; case SoundComponent: - return OpenReactComponent; + return OpenSoundComponent; default: // shouldnt be called so just null return OpenReactComponent; From a76eb56300a370135741a0b9af1b2561d78399ff Mon Sep 17 00:00:00 2001 From: Aryan <aryan.rastogi1@yahoo.co.uk> Date: Tue, 23 Apr 2024 18:23:55 +0100 Subject: [PATCH 103/106] Minor changes to unify look --- .../helper-widgets/setPixelWidget.tsx | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx index d389ad563..7769e701b 100644 --- a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx +++ b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx @@ -45,31 +45,36 @@ const MicrobitSinglePixelGrid: React.FC<MicrobitSinglePixelGridProps> = ({ }; return ( + <div> + <Box ml="10px" style={{ marginRight: "4px" }}> + <Button size="xs" onClick={onCloseClick} bg="white"> + X + </Button> + </Box> <Box // TODO: copy to allow other widgets to access bg and close display="flex" flexDirection="row" justifyContent="flex-start" - bg="lightgray" + width="250px" + background="snon" + border='1px solid lightgray' + boxShadow='0 0 10px 5px rgba(173, 216, 230, 0.7)' > - <Box ml="10px" style={{ marginRight: "4px" }}> - <Button size="xs" onClick={onCloseClick} bg="white"> - X - </Button> - </Box> <Box> <Box - bg="black" + bg="white" p="10px" - borderRadius="5px" - style={{ marginTop: "15px" }} + borderRadius="0px" + border="1px solid black" + style={{ marginLeft: "15px", marginTop: "15px", marginBottom: "15px" }} > {[...Array(5)].map((_, gridY) => ( <Box key={y} display="flex"> {[...Array(5)].map((_, gridX) => ( <Box key={x} display="flex" mr="0px"> <Button - height="30px" - width="30x" + height="32px" + width="30px" p={0} borderRadius={0} bgColor={ @@ -79,8 +84,8 @@ const MicrobitSinglePixelGrid: React.FC<MicrobitSinglePixelGridProps> = ({ } border={ gridX === x && gridY === y - ? "2px solid white" - : "0.5px solid white" + ? "2px solid black" + : "1px solid black" } _hover={{ bgColor: @@ -103,6 +108,7 @@ const MicrobitSinglePixelGrid: React.FC<MicrobitSinglePixelGridProps> = ({ min={0} max={9} step={1} + height="182px" orientation="vertical" _focus={{ boxShadow: "none" }} _active={{ bgColor: "transparent" }} @@ -115,6 +121,7 @@ const MicrobitSinglePixelGrid: React.FC<MicrobitSinglePixelGridProps> = ({ </Slider> </Box> </Box> + </div> ); }; From f6795a8a4dcbd5740b45be7e04029f0beb529c1c Mon Sep 17 00:00:00 2001 From: Aryan <aryan.rastogi1@yahoo.co.uk> Date: Wed, 24 Apr 2024 21:04:32 +0100 Subject: [PATCH 104/106] Sound widget now updates parameters in editor. Super glitchy though. --- .../codemirror/helper-widgets/soundWidget.tsx | 318 +++++++++++------- 1 file changed, 204 insertions(+), 114 deletions(-) diff --git a/src/editor/codemirror/helper-widgets/soundWidget.tsx b/src/editor/codemirror/helper-widgets/soundWidget.tsx index 76405569a..1fea4bdfe 100644 --- a/src/editor/codemirror/helper-widgets/soundWidget.tsx +++ b/src/editor/codemirror/helper-widgets/soundWidget.tsx @@ -10,6 +10,10 @@ import { import { EditorView } from "@codemirror/view"; import { WidgetProps } from "./reactWidgetExtension"; import { openWidgetEffect } from "./openWidgets"; +import { zIndexAboveDialogs } from "../../../common/zIndex"; +import { start } from "repl"; + +type FixedLengthArray = [number, number, number, number, number, string, string]; interface SliderProps { min: number; @@ -25,7 +29,7 @@ interface SliderProps { const startVolProps: SliderProps = { min: 0, - max: 100, + max: 255, step: 1, defaultValue: 50, onChange: (value) => { @@ -46,19 +50,19 @@ const startVolProps: SliderProps = { const endFrequencySliderProps: SliderProps = { min: 0, - max: 999, + max: 9999, step: 1, - defaultValue: 500, + defaultValue: 5000, onChange: (value) => { console.log("Slider value changed:", value); }, sliderStyle: { - width: "100%", - height: "100px", - backgroundColor: "lightgray", - borderRadius: "10px", - border: "none", - outline: "none", + width: "100%", + height: "100px", + backgroundColor: "lightgray", + borderRadius: "10px", + border: "none", + outline: "none", }, label: "End Freq", vertical: true, @@ -67,9 +71,9 @@ const endFrequencySliderProps: SliderProps = { const startFrequencySliderProps: SliderProps = { min: 0, - max: 999, + max: 9999, step: 1, - defaultValue: 500, + defaultValue: 5000, onChange: (value) => { console.log("Slider value changed:", value); }, @@ -88,7 +92,7 @@ const startFrequencySliderProps: SliderProps = { const endVolProps: SliderProps = { min: 0, - max: 100, + max: 255, step: 1, defaultValue: 50, onChange: (value) => { @@ -128,7 +132,7 @@ const Slider: React.FC<SliderProps & { vertical?: boolean, colour: string }> = ( return ( <div style={{ position: 'relative', height: '80px', width: '45px', display: "flex", flexDirection: "column", alignItems: "center" }}> - <input + <input type="range" min={min} max={max} @@ -136,41 +140,41 @@ const Slider: React.FC<SliderProps & { vertical?: boolean, colour: string }> = ( value={value} onChange={handleChange} style={{ - position: 'absolute', - width: '115px', // Width of the slider - height: '40px', // Height of the slider - transform: 'rotate(-90deg)', // Rotate the slider to vertical orientation - accentColor: colour, - bottom: '0%', + position: 'absolute', + width: '115px', // Width of the slider + height: '40px', // Height of the slider + transform: 'rotate(-90deg)', // Rotate the slider to vertical orientation + accentColor: colour, + bottom: '0%', }} - /> - <div + /> + <div style={{ - position: 'absolute', - left: 'calc(100% - 15px)', // Position the label to the right of the slider - bottom: `${((value - min) / (max - min)) * 100}%`, // Calculate the position based on value - transform: 'translateY(50%)', // Center the label vertically with the thumb - fontSize: '13px', // Font size of the label + position: 'absolute', + left: 'calc(100% - 15px)', // Position the label to the right of the slider + bottom: `${((value - min) / (max - min)) * 100}%`, // Calculate the position based on value + transform: 'translateY(50%)', // Center the label vertically with the thumb + fontSize: '13px', // Font size of the label }} - > + > {value} - </div> - <div style={{ marginTop: '120px', textAlign: 'center', fontSize: '11px', }}> + </div> + <div style={{ marginTop: '120px', textAlign: 'center', fontSize: '11px', }}> <b>{label}</b> + </div> </div> -</div> ); }; const TripleSliderWidget: React.FC<{ - slider1Props: SliderProps; - slider2Props: SliderProps; - slider3Props: SliderProps; - slider4Props: SliderProps; + freqStartProps: SliderProps; + freqEndProps: SliderProps; + volStartProps: SliderProps; + volEndprops: SliderProps; props: WidgetProps; view: EditorView; -}> = ({ slider1Props, slider2Props, slider3Props, slider4Props, props}) => { +}> = ({ freqStartProps, freqEndProps, volStartProps, volEndprops, props, view}) => { let args = props.args; let ranges = props.ranges; @@ -178,45 +182,94 @@ const TripleSliderWidget: React.FC<{ let from = props.from; let to = props.to; - const [startAmplitude, setStartAmplitude] = useState(slider3Props.defaultValue); - const [endAmplitude, setEndAmplitude] = useState(50); - const [initialFrequency, setInitialFrequency] = useState(50); - const [endFrequency, setEndFrequency] = useState(50); - const [waveType, setWaveType] = useState('sine') - const waveformOptions = ["None", "Vibrato", "Tremolo", "Warble"] - const [textBoxValue, setTextBoxValue] = useState("2000"); - + //parse args - - for (let i = 0; i < args.length; i++) { + let argsToBeUsed: FixedLengthArray = [200, 500, 2000, 50, 50, "sine", "None"] // default args + let count = 0 + for (let i = 2; i < args.length; i += 3) { //Update default args with user args where they exist + argsToBeUsed[count] = args[i] let arg = args[i]; console.log("arg: ", arg); + count += 1; + }; + + console.log("args", argsToBeUsed) + + const [initialFrequency, setInitialFrequency] = useState(Math.min(argsToBeUsed[0], 9999)); + freqStartProps.defaultValue = initialFrequency + + const [endFrequency, setEndFrequency] = useState(Math.min(argsToBeUsed[1], 9999)); + freqEndProps.defaultValue = endFrequency + + + const [startAmplitude, setStartAmplitude] = useState(Math.min(argsToBeUsed[3], 255)); + volStartProps.defaultValue = startAmplitude + + const [endAmplitude, setEndAmplitude] = useState(Math.min(argsToBeUsed[4], 9999)); + volEndprops.defaultValue = endAmplitude + + + const [waveType, setWaveType] = useState("sine") + + const waveformOptions = ["None", "Vibrato", "Tremolo", "Warble"] + const [textBoxValue, setTextBoxValue] = useState(Number(argsToBeUsed[2])); + + + const updateView = () => { + let insertion = statesToString(initialFrequency, endFrequency, textBoxValue, startAmplitude, endAmplitude); + console.log(insertion); + if (ranges.length === 1) { + view.dispatch({ + changes: { + from: ranges[0].from, + to: ranges[0].to, + insert: insertion, + }, + effects: [openWidgetEffect.of(insertion.length + from + 2)], + }); + } else { + view.dispatch({ + changes: [ + { + from: from + 1, + to: to - 1, + insert: insertion, + }, + ], + effects: [openWidgetEffect.of(insertion.length + from + 2)], + }); + } }; const handleTextInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { const newValue = e.target.value; - setTextBoxValue(newValue); + setTextBoxValue(Number(newValue)); + updateView(); }; const handleSlider1Change = (value: number) => { - slider1Props.onChange(value); - setInitialFrequency(value/10); + freqStartProps.onChange(value); + setInitialFrequency(value); + updateView(); }; const handleSlider2Change = (value: number) => { - slider2Props.onChange(value); - setEndFrequency(value/10); // + freqEndProps.onChange(value); + setEndFrequency(value); // + updateView(); }; const handleSlider3Change = (value: number) => { - slider1Props.onChange(value); + freqStartProps.onChange(value); setStartAmplitude(value); + updateView(); }; const handleSlider4Change = (value: number) => { - slider1Props.onChange(value); + freqStartProps.onChange(value); setEndAmplitude(value); + updateView(); }; const handleWaveTypeChange = (value: string) => { @@ -228,16 +281,16 @@ const TripleSliderWidget: React.FC<{ const generateWavePath = () => { const waveLength = 400; // Width of the box const pathData = []; - + const frequencyDifference = endFrequency - initialFrequency; const amplitudeDifference = endAmplitude - startAmplitude; // Loop through the wave's width to generate the path for (let x = 0; x <= waveLength; x++) { - const currentFrequency = initialFrequency + (frequencyDifference * x) / waveLength; - const currentAmplitude = startAmplitude + (amplitudeDifference * x) / waveLength; - const period = waveLength/currentFrequency - + const currentFrequency = (initialFrequency + (frequencyDifference * x) / waveLength)/100; + const currentAmplitude = (startAmplitude + (amplitudeDifference * x) / waveLength)/2.2; + const period = waveLength / currentFrequency + // Calculate the y-coordinate based on the current frequency and amplitude let y = 0; @@ -260,11 +313,11 @@ const TripleSliderWidget: React.FC<{ case 'noisy': // Generate noisy wave based on sine wave and random noise const baseWave = 65 + currentAmplitude * Math.sin((x / period) * 2 * Math.PI); - const randomNoise = Math.random() * 2 - 1; - y = baseWave + randomNoise * (currentAmplitude*0.3); + const randomNoise = Math.random() * 2 - 1; + y = baseWave + randomNoise * (currentAmplitude * 0.3); break; } - + // Add the point to the path data pathData.push(`${x},${y}`); } @@ -275,30 +328,30 @@ const TripleSliderWidget: React.FC<{ return ( <div> - <div style={{ display: "flex", justifyContent: "flex-start", backgroundColor: 'snow', width: '575px', height: '150px', border: '1px solid lightgray', boxShadow: '0 0 10px 5px rgba(173, 216, 230, 0.7)'}}> - {/* Vertical Slider 1 */} - <div style={{marginLeft: "6px", marginRight: "20px", height: '100px', marginTop: '9px'}}> - <Slider {...slider1Props} onChange={handleSlider1Change} vertical /> - </div> - {/* Vertical Slider 2 */} - <div style={{ marginRight: "20px", height: '100px', marginTop: '9px'}}> - <Slider {...slider2Props} onChange={handleSlider2Change} vertical /> - </div> - {/* Vertical Slider 3 */} - <div style={{ marginRight: "20px", height: '100px', marginTop: '9px' }}> - <Slider {...slider3Props} onChange={handleSlider3Change} vertical /> - </div> - {/* Vertical Slider 4 */} - <div style={{ marginRight: "25px", height: '100px', marginTop: '9px' }}> - <Slider {...slider4Props} onChange={handleSlider4Change} vertical /> - </div> - + <div style={{ display: "flex", justifyContent: "flex-start", backgroundColor: 'snow', width: '575px', height: '150px', border: '1px solid lightgray', boxShadow: '0 0 10px 5px rgba(173, 216, 230, 0.7)', zIndex: 10 }}> + {/* Vertical Slider 1 */} + <div style={{ marginLeft: "6px", marginRight: "20px", height: '100px', marginTop: '9px' }}> + <Slider {...freqStartProps} onChange={handleSlider1Change} vertical /> + </div> + {/* Vertical Slider 2 */} + <div style={{ marginRight: "20px", height: '100px', marginTop: '9px' }}> + <Slider {...freqEndProps} onChange={handleSlider2Change} vertical /> + </div> + {/* Vertical Slider 3 */} + <div style={{ marginRight: "20px", height: '100px', marginTop: '9px' }}> + <Slider {...volStartProps} onChange={handleSlider3Change} vertical /> + </div> + {/* Vertical Slider 4 */} + <div style={{ marginRight: "25px", height: '100px', marginTop: '9px' }}> + <Slider {...volEndprops} onChange={handleSlider4Change} vertical /> + </div> + <div style={{ marginRight: '10px', height: '100px', fontSize: '12px' }}> - {/* waveform type selection */} - <label style={{ display: 'block', marginBottom: '5px', marginTop: '7px',}}> - <b>Waveform:</b> + {/* waveform type selection */} + <label style={{ display: 'block', marginBottom: '5px', marginTop: '7px', }}> + <b>Waveform:</b> </label> <select onChange={(e) => handleWaveTypeChange(e.target.value)}> <option value="sine">Sine</option> @@ -310,8 +363,8 @@ const TripleSliderWidget: React.FC<{ {/* fx type selection */} - <label style={{ display: 'block', marginBottom: '5px', marginTop: '10px'}}> - <b>Effects:</b> + <label style={{ display: 'block', marginBottom: '5px', marginTop: '10px' }}> + <b>Effects:</b> </label> <select onChange={(e) => handleWaveTypeChange(e.target.value)}> <option value="sine">None</option> @@ -323,34 +376,34 @@ const TripleSliderWidget: React.FC<{ {/* Duration selctor */} <label style={{ display: 'block', marginBottom: '5px', marginTop: '10px' }}> - <b>Duration(ms):</b> + <b>Duration(ms):</b> </label> {/* Input field with associated datalist */} <input type="text" value={textBoxValue} onChange={handleTextInputChange} // Handle the selected or typed-in value - defaultValue="2000" + defaultValue="2000" style={{ width: '75px' }} /> </div> - {/* Waveform box */} - <div style={{ width: '200px', height: '130px', backgroundColor: 'linen', marginTop: '9px', marginLeft: '5px'}}> - <svg width="100%" height="100%"> - <path d={generateWavePath()} stroke="black" fill="none" /> - <line - x1="0%" // Start of the line - y1="50%" // Vertically center the line - x2="100%" // End of the line - y2="50%" // Keep the line horizontal - stroke="gray" // Line color - strokeWidth="0.5" // Line thickness - /> - </svg> - </div> + {/* Waveform box */} + <div style={{ width: '200px', height: '130px', backgroundColor: 'linen', marginTop: '9px', marginLeft: '5px' }}> + <svg width="100%" height="100%"> + <path d={generateWavePath()} stroke="black" fill="none" /> + <line + x1="0%" // Start of the line + y1="50%" // Vertically center the line + x2="100%" // End of the line + y2="50%" // Keep the line horizontal + stroke="gray" // Line color + strokeWidth="0.5" // Line thickness + /> + </svg> + </div> + </div> </div> -</div> ); }; @@ -365,36 +418,73 @@ export const SoundComponent = ({ view: EditorView; }) => { + let args = props.args; + let ranges = props.ranges; + let types = props.types; + let from = props.from; + let to = props.to + //for future reference add a aclose button const handleCloseClick = () => { view.dispatch({ effects: [openWidgetEffect.of(-1)], }); }; - /* - view.dispatch({ - changes: { - from: from, - to: to, - insert: //something from state of component`, - }, - */ -// <Button onClick={handleButtonClick}>{buttonLabel} Sound Editor</Button> + + const updateView = () => { + let insertion = "test"; + console.log(insertion); + if (ranges.length === 1) { + view.dispatch({ + changes: { + from: ranges[0].from, + to: ranges[0].to, + insert: insertion, + }, + effects: [openWidgetEffect.of(insertion.length + from + 2)], + }); + } else { + view.dispatch({ + changes: [ + { + from: from + 1, + to: to - 1, + insert: insertion, + }, + ], + effects: [openWidgetEffect.of(insertion.length + from + 2)], + }); + } + }; + return ( - <HStack fontFamily="body" spacing={5} py={3}> + <HStack fontFamily="body" spacing={5} py={3} zIndex={10}> <Box ml="10px" style={{ marginRight: "4px" }}> <Button size="xs" onClick={handleCloseClick} bg="white"> X </Button> </Box> <TripleSliderWidget - slider1Props={startFrequencySliderProps} - slider2Props={endFrequencySliderProps} - slider3Props={startVolProps} - slider4Props={endVolProps} + freqStartProps={startFrequencySliderProps} + freqEndProps={endFrequencySliderProps} + volStartProps={startVolProps} + volEndprops={endVolProps} props={props} view={view} /> </HStack> ); }; + +//(startFreq: number, endFreq: Number, duration: Number, startVol: number, endVol: Number, waveform: string, fx: string) + +function statesToString(startFreq: number, endFreq: Number, duration: Number, startVol: number, endVol: Number): string { + return `\n` + + ` freq_start=${startFreq},\n` + + ` freq_end=${endFreq},\n` + + ` duration=${duration},\n` + + ` vol_start=${startVol},\n` + + ` vol_end=${endVol},\n` + + ` waveform=SoundEffect.FX_WARBLE,\n` + + ` fx=SoundEffect.FX_VIBRATO`; +} From 06826ad958ac0f3b3f0a49070b1f457dc51a3009 Mon Sep 17 00:00:00 2001 From: wilsonkooi <160124853+wilsonkooi@users.noreply.github.com> Date: Tue, 23 Apr 2024 13:55:14 +0100 Subject: [PATCH 105/106] Fix CodeMirror-Portal integration to handle updates eq should check all props updateDOM now re-renders the react component - it's called when eq returns false. This maintains state and allows props to change. We take care to cope with the transition to/from rendering the "inline" case. --- src/editor/codemirror/CodeMirror.tsx | 26 ++++++++-- .../helper-widgets/reactWidgetExtension.tsx | 49 +++++++++++++------ .../helper-widgets/setPixelWidget.tsx | 4 +- .../helper-widgets/widgetArgParser.tsx | 3 +- 4 files changed, 58 insertions(+), 24 deletions(-) diff --git a/src/editor/codemirror/CodeMirror.tsx b/src/editor/codemirror/CodeMirror.tsx index 7173852e7..15b4c4c5e 100644 --- a/src/editor/codemirror/CodeMirror.tsx +++ b/src/editor/codemirror/CodeMirror.tsx @@ -125,9 +125,25 @@ const CodeMirror = ({ const [portals, setPortals] = useState<PortalContent[]>([]); const portalFactory: PortalFactory = useCallback((dom, content) => { - const portal = { dom, content }; - setPortals((portals) => [...portals, portal]); - return () => setPortals((portals) => portals.filter((p) => p !== portal)); + setPortals((portals) => { + let found = false; + let updated = portals.map((p) => { + if (p.dom === dom) { + found = true; + return { + dom, + content, + }; + } + return p; + }); + if (!found) { + updated = [...portals, { dom, content }]; + } + return updated; + }); + + return () => setPortals((portals) => portals.filter((p) => p.dom !== dom)); }, []); useEffect(() => { @@ -143,7 +159,7 @@ const CodeMirror = ({ logPastedLineCount(logging, update); } }); - + const state = EditorState.create({ doc: defaultValue, extensions: [ @@ -331,4 +347,4 @@ const logPastedLineCount = (logging: Logging, update: ViewUpdate) => { ); }; -export default CodeMirror; \ No newline at end of file +export default CodeMirror; diff --git a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx index 1b053b52c..2480b27e0 100644 --- a/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx +++ b/src/editor/codemirror/helper-widgets/reactWidgetExtension.tsx @@ -9,7 +9,7 @@ import { syntaxTree } from "@codemirror/language"; import { PortalFactory } from "../CodeMirror"; import React from "react"; import { createWidget } from "./widgetArgParser"; -import { OpenReactComponent, openWidgetEffect } from "./openWidgets"; +import { openWidgetEffect } from "./openWidgets"; import { ValidateComponentArgs } from "./widgetArgParser"; export interface WidgetProps { @@ -43,25 +43,44 @@ class Widget extends WidgetType { eq(other: WidgetType): boolean { const them = other as Widget; - return them.component === this.component && them.props.to === this.props.to && them.inline === this.inline; + let args1 = this.props.args; + let args2 = them.props.args; + let eqArgs = + args1.length === args2.length && + args1.every((element, index) => element === args2[index]); + + return ( + them.component === this.component && + them.props.to === this.props.to && + eqArgs && + them.inline === this.inline + ); + } + + updateDOM(dom: HTMLElement, view: EditorView): boolean { + dom.style.display = this.inline ? "inline-block" : "unset"; + this.portalCleanup = this.createPortal(dom, this.toComponent(view)); + return true; + } + + private toComponent(view: EditorView) { + if (this.inline) { + return <this.open loc={this.props.to} view={view} />; + } + return <this.component props={this.props} view={view} />; } toDOM(view: EditorView) { const dom = document.createElement("div"); - if (this.inline) { - if (ValidateComponentArgs(this.component, this.props.args, this.props.types)) { - dom.style.display = "inline-block"; // want it inline for the open-close widget - this.portalCleanup = this.createPortal( - dom, - <this.open loc={this.props.to} view={view} /> - ); - } - } else - this.portalCleanup = this.createPortal( - dom, - <this.component props={this.props} view={view} /> - ); + if ( + this.inline && + !ValidateComponentArgs(this.component, this.props.args, this.props.types) + ) { + return dom; + } + dom.style.display = this.inline ? "inline-block" : "unset"; + this.portalCleanup = this.createPortal(dom, this.toComponent(view)); return dom; } diff --git a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx index 7769e701b..f61a93202 100644 --- a/src/editor/codemirror/helper-widgets/setPixelWidget.tsx +++ b/src/editor/codemirror/helper-widgets/setPixelWidget.tsx @@ -69,9 +69,9 @@ const MicrobitSinglePixelGrid: React.FC<MicrobitSinglePixelGridProps> = ({ style={{ marginLeft: "15px", marginTop: "15px", marginBottom: "15px" }} > {[...Array(5)].map((_, gridY) => ( - <Box key={y} display="flex"> + <Box key={gridY} display="flex"> {[...Array(5)].map((_, gridX) => ( - <Box key={x} display="flex" mr="0px"> + <Box key={gridX} display="flex" mr="0px"> <Button height="32px" width="30px" diff --git a/src/editor/codemirror/helper-widgets/widgetArgParser.tsx b/src/editor/codemirror/helper-widgets/widgetArgParser.tsx index db6da1964..176cd8b6e 100644 --- a/src/editor/codemirror/helper-widgets/widgetArgParser.tsx +++ b/src/editor/codemirror/helper-widgets/widgetArgParser.tsx @@ -36,7 +36,7 @@ export function createWidget( break; default: // No widget implemented for this function - console.log("No widget implemented for this function: " + name); + // console.log("No widget implemented for this function: " + name); return null; } if (component) { @@ -60,7 +60,6 @@ function getChildNodes(node: SyntaxNode): SyntaxNode[] { let child = node.firstChild?.nextSibling; let children = []; while (child && child.name !== ")") { - console.log(child.name); if (child.name !== "," && child.name !== "Comment") children.push(child); child = child.nextSibling; } From 52fa8528b6b5f3fc1db3a31934340f38b850e05a Mon Sep 17 00:00:00 2001 From: Matt Hillsdon <matt.hillsdon@microbit.org> Date: Tue, 30 Apr 2024 14:44:26 +0100 Subject: [PATCH 106/106] Illustration of stateless version --- .../codemirror/helper-widgets/soundWidget.tsx | 444 ++++++++++-------- 1 file changed, 255 insertions(+), 189 deletions(-) diff --git a/src/editor/codemirror/helper-widgets/soundWidget.tsx b/src/editor/codemirror/helper-widgets/soundWidget.tsx index 1fea4bdfe..f999f19e3 100644 --- a/src/editor/codemirror/helper-widgets/soundWidget.tsx +++ b/src/editor/codemirror/helper-widgets/soundWidget.tsx @@ -1,25 +1,24 @@ -import React, { useState } from "react"; -import { - Box, - Button, - SliderTrack, - SliderFilledTrack, - SliderThumb, - HStack, -} from "@chakra-ui/react"; +import { Box, Button, HStack } from "@chakra-ui/react"; import { EditorView } from "@codemirror/view"; -import { WidgetProps } from "./reactWidgetExtension"; +import React, { useState } from "react"; import { openWidgetEffect } from "./openWidgets"; -import { zIndexAboveDialogs } from "../../../common/zIndex"; -import { start } from "repl"; +import { WidgetProps } from "./reactWidgetExtension"; -type FixedLengthArray = [number, number, number, number, number, string, string]; +type FixedLengthArray = [ + number, + number, + number, + number, + number, + string, + string +]; interface SliderProps { min: number; max: number; step: number; - defaultValue: number; + value: number; onChange: (value: number) => void; sliderStyle?: React.CSSProperties; label: string; @@ -27,14 +26,10 @@ interface SliderProps { colour: string; } -const startVolProps: SliderProps = { +const startVolProps: Omit<SliderProps, "value" | "onChange"> = { min: 0, max: 255, step: 1, - defaultValue: 50, - onChange: (value) => { - console.log("Slider value changed:", value); - }, sliderStyle: { width: "100%", // Adjust the width of the slider height: "100px", // Adjust the height of the slider @@ -45,17 +40,13 @@ const startVolProps: SliderProps = { }, label: "Start Vol", vertical: true, - colour: 'red' + colour: "red", }; -const endFrequencySliderProps: SliderProps = { +const endFrequencySliderProps: Omit<SliderProps, "value" | "onChange"> = { min: 0, max: 9999, step: 1, - defaultValue: 5000, - onChange: (value) => { - console.log("Slider value changed:", value); - }, sliderStyle: { width: "100%", height: "100px", @@ -66,17 +57,13 @@ const endFrequencySliderProps: SliderProps = { }, label: "End Freq", vertical: true, - colour: 'green' + colour: "green", }; -const startFrequencySliderProps: SliderProps = { +const startFrequencySliderProps: Omit<SliderProps, "value" | "onChange"> = { min: 0, max: 9999, step: 1, - defaultValue: 5000, - onChange: (value) => { - console.log("Slider value changed:", value); - }, sliderStyle: { width: "200%", // Adjust the width of the slider height: "100px", // Adjust the height of the slider @@ -87,17 +74,13 @@ const startFrequencySliderProps: SliderProps = { }, label: "Start Freq", vertical: true, - colour: 'blue' + colour: "blue", }; -const endVolProps: SliderProps = { +const endVolProps: Omit<SliderProps, "value" | "onChange"> = { min: 0, max: 255, step: 1, - defaultValue: 50, - onChange: (value) => { - console.log("Slider value changed:", value); - }, sliderStyle: { width: "200%", // Adjust the width of the slider height: "100px", // Adjust the height of the slider @@ -108,74 +91,88 @@ const endVolProps: SliderProps = { }, label: "End Vol", vertical: true, - colour: 'black' + colour: "black", }; -const Slider: React.FC<SliderProps & { vertical?: boolean, colour: string }> = ({ - min, - max, - step, - defaultValue, - onChange, - sliderStyle, - label, - vertical, - colour -}) => { - const [value, setValue] = useState(defaultValue); - - const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => { - const newValue = parseFloat(event.target.value); - setValue(newValue); - onChange(newValue); - }; - - return ( - <div style={{ position: 'relative', height: '80px', width: '45px', display: "flex", flexDirection: "column", alignItems: "center" }}> - <input - type="range" - min={min} - max={max} - step={step} - value={value} - onChange={handleChange} - style={{ - position: 'absolute', - width: '115px', // Width of the slider - height: '40px', // Height of the slider - transform: 'rotate(-90deg)', // Rotate the slider to vertical orientation - accentColor: colour, - bottom: '0%', - }} - /> +const Slider: React.FC<SliderProps & { vertical?: boolean; colour: string }> = + ({ + min, + max, + step, + value, + onChange, + sliderStyle, + label, + vertical, + colour, + }) => { + const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => { + const newValue = parseFloat(event.target.value); + onChange(newValue); + }; + + return ( <div style={{ - position: 'absolute', - left: 'calc(100% - 15px)', // Position the label to the right of the slider - bottom: `${((value - min) / (max - min)) * 100}%`, // Calculate the position based on value - transform: 'translateY(50%)', // Center the label vertically with the thumb - fontSize: '13px', // Font size of the label + position: "relative", + height: "80px", + width: "45px", + display: "flex", + flexDirection: "column", + alignItems: "center", }} > - {value} - </div> - <div style={{ marginTop: '120px', textAlign: 'center', fontSize: '11px', }}> - <b>{label}</b> + <input + type="range" + min={min} + max={max} + step={step} + value={value} + onChange={handleChange} + style={{ + position: "absolute", + width: "115px", // Width of the slider + height: "40px", // Height of the slider + transform: "rotate(-90deg)", // Rotate the slider to vertical orientation + accentColor: colour, + bottom: "0%", + }} + /> + <div + style={{ + position: "absolute", + left: "calc(100% - 15px)", // Position the label to the right of the slider + bottom: `${((value - min) / (max - min)) * 100}%`, // Calculate the position based on value + transform: "translateY(50%)", // Center the label vertically with the thumb + fontSize: "13px", // Font size of the label + }} + > + {value} + </div> + <div + style={{ marginTop: "120px", textAlign: "center", fontSize: "11px" }} + > + <b>{label}</b> + </div> </div> - </div> - ); -}; - + ); + }; const TripleSliderWidget: React.FC<{ - freqStartProps: SliderProps; - freqEndProps: SliderProps; - volStartProps: SliderProps; - volEndprops: SliderProps; + freqStartProps: Omit<SliderProps, "value" | "onChange">; + freqEndProps: Omit<SliderProps, "value" | "onChange">; + volStartProps: Omit<SliderProps, "value" | "onChange">; + volEndprops: Omit<SliderProps, "value" | "onChange">; props: WidgetProps; view: EditorView; -}> = ({ freqStartProps, freqEndProps, volStartProps, volEndprops, props, view}) => { - +}> = ({ + freqStartProps, + freqEndProps, + volStartProps, + volEndprops, + props, + view, +}) => { let args = props.args; let ranges = props.ranges; let types = props.types; @@ -184,39 +181,37 @@ const TripleSliderWidget: React.FC<{ //parse args - let argsToBeUsed: FixedLengthArray = [200, 500, 2000, 50, 50, "sine", "None"] // default args - let count = 0 - for (let i = 2; i < args.length; i += 3) { //Update default args with user args where they exist - argsToBeUsed[count] = args[i] + let argsToBeUsed: FixedLengthArray = [200, 500, 2000, 50, 50, "sine", "None"]; // default args + let count = 0; + for (let i = 2; i < args.length; i += 3) { + //Update default args with user args where they exist + argsToBeUsed[count] = args[i]; let arg = args[i]; console.log("arg: ", arg); count += 1; - }; - - console.log("args", argsToBeUsed) - - const [initialFrequency, setInitialFrequency] = useState(Math.min(argsToBeUsed[0], 9999)); - freqStartProps.defaultValue = initialFrequency - - const [endFrequency, setEndFrequency] = useState(Math.min(argsToBeUsed[1], 9999)); - freqEndProps.defaultValue = endFrequency + } + console.log("args", argsToBeUsed); - const [startAmplitude, setStartAmplitude] = useState(Math.min(argsToBeUsed[3], 255)); - volStartProps.defaultValue = startAmplitude + const startFreq = Math.min(argsToBeUsed[0], 9999); + const endFreq = Math.min(argsToBeUsed[1], 9999); + const startVol = Math.min(argsToBeUsed[3], 255); + const endVol = Math.min(argsToBeUsed[4], 9999); - const [endAmplitude, setEndAmplitude] = useState(Math.min(argsToBeUsed[4], 9999)); - volEndprops.defaultValue = endAmplitude + const [waveType, setWaveType] = useState("sine"); - - const [waveType, setWaveType] = useState("sine") + const waveformOptions = ["None", "Vibrato", "Tremolo", "Warble"]; + const textBoxValue = Number(argsToBeUsed[2]); - const waveformOptions = ["None", "Vibrato", "Tremolo", "Warble"] - const [textBoxValue, setTextBoxValue] = useState(Number(argsToBeUsed[2])); - - - const updateView = () => { - let insertion = statesToString(initialFrequency, endFrequency, textBoxValue, startAmplitude, endAmplitude); + const updateView = (change: Partial<ParsedArgs>) => { + let insertion = statesToString({ + startFreq, + endFreq, + duration: textBoxValue, + startVol, + endVol, + ...change, + }); console.log(insertion); if (ranges.length === 1) { view.dispatch({ @@ -241,78 +236,86 @@ const TripleSliderWidget: React.FC<{ } }; - const handleTextInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { - const newValue = e.target.value; - setTextBoxValue(Number(newValue)); - updateView(); + //const newValue = e.target.value; + //setTextBoxValue(Number(newValue)); + updateView({}); }; const handleSlider1Change = (value: number) => { - freqStartProps.onChange(value); - setInitialFrequency(value); - updateView(); + //freqStartProps.onChange(value); + //setInitialFrequency(value); + updateView({ + startFreq: value, + }); }; const handleSlider2Change = (value: number) => { - freqEndProps.onChange(value); - setEndFrequency(value); // - updateView(); + //freqEndProps.onChange(value); + //setEndFrequency(value); // + updateView({}); }; const handleSlider3Change = (value: number) => { - freqStartProps.onChange(value); - setStartAmplitude(value); - updateView(); + //freqStartProps.onChange(value); + //setStartAmplitude(value); + updateView({}); }; const handleSlider4Change = (value: number) => { - freqStartProps.onChange(value); - setEndAmplitude(value); - updateView(); + //freqStartProps.onChange(value); + //setEndAmplitude(value); + updateView({}); }; const handleWaveTypeChange = (value: string) => { setWaveType(value); }; - - const generateWavePath = () => { const waveLength = 400; // Width of the box const pathData = []; - const frequencyDifference = endFrequency - initialFrequency; - const amplitudeDifference = endAmplitude - startAmplitude; + const frequencyDifference = endFreq - startFreq; + const amplitudeDifference = endVol - startVol; // Loop through the wave's width to generate the path for (let x = 0; x <= waveLength; x++) { - const currentFrequency = (initialFrequency + (frequencyDifference * x) / waveLength)/100; - const currentAmplitude = (startAmplitude + (amplitudeDifference * x) / waveLength)/2.2; - const period = waveLength / currentFrequency - + const currentFrequency = + (startFreq + (frequencyDifference * x) / waveLength) / 100; + const currentAmplitude = + (startVol + (amplitudeDifference * x) / waveLength) / 2.2; + const period = waveLength / currentFrequency; // Calculate the y-coordinate based on the current frequency and amplitude let y = 0; switch (waveType) { - case 'sine': + case "sine": y = 65 + currentAmplitude * Math.sin((x / period) * 2 * Math.PI); break; - case 'square': - y = x % period < period / 2 ? 65 + currentAmplitude : 65 - currentAmplitude; + case "square": + y = + x % period < period / 2 + ? 65 + currentAmplitude + : 65 - currentAmplitude; break; - case 'sawtooth': - y = 65 + currentAmplitude - ((x % period) / period) * (2 * currentAmplitude); + case "sawtooth": + y = + 65 + + currentAmplitude - + ((x % period) / period) * (2 * currentAmplitude); break; - case 'triangle': + case "triangle": const tPeriod = x % period; - y = tPeriod < period / 2 - ? 65 + (2 * currentAmplitude / period) * tPeriod - : 65 - (2 * currentAmplitude / period) * (tPeriod - period / 2); + y = + tPeriod < period / 2 + ? 65 + ((2 * currentAmplitude) / period) * tPeriod + : 65 - ((2 * currentAmplitude) / period) * (tPeriod - period / 2); break; - case 'noisy': + case "noisy": // Generate noisy wave based on sine wave and random noise - const baseWave = 65 + currentAmplitude * Math.sin((x / period) * 2 * Math.PI); + const baseWave = + 65 + currentAmplitude * Math.sin((x / period) * 2 * Math.PI); const randomNoise = Math.random() * 2 - 1; y = baseWave + randomNoise * (currentAmplitude * 0.3); break; @@ -323,34 +326,74 @@ const TripleSliderWidget: React.FC<{ } // Join the path data points to create the path - return `M${pathData.join(' ')}`; + return `M${pathData.join(" ")}`; }; return ( <div> - <div style={{ display: "flex", justifyContent: "flex-start", backgroundColor: 'snow', width: '575px', height: '150px', border: '1px solid lightgray', boxShadow: '0 0 10px 5px rgba(173, 216, 230, 0.7)', zIndex: 10 }}> + <div + style={{ + display: "flex", + justifyContent: "flex-start", + backgroundColor: "snow", + width: "575px", + height: "150px", + border: "1px solid lightgray", + boxShadow: "0 0 10px 5px rgba(173, 216, 230, 0.7)", + zIndex: 10, + }} + > {/* Vertical Slider 1 */} - <div style={{ marginLeft: "6px", marginRight: "20px", height: '100px', marginTop: '9px' }}> - <Slider {...freqStartProps} onChange={handleSlider1Change} vertical /> + <div + style={{ + marginLeft: "6px", + marginRight: "20px", + height: "100px", + marginTop: "9px", + }} + > + <Slider + {...freqStartProps} + value={startFreq} + onChange={handleSlider1Change} + vertical + /> </div> {/* Vertical Slider 2 */} - <div style={{ marginRight: "20px", height: '100px', marginTop: '9px' }}> - <Slider {...freqEndProps} onChange={handleSlider2Change} vertical /> + <div style={{ marginRight: "20px", height: "100px", marginTop: "9px" }}> + <Slider + {...freqEndProps} + // TODO: for this and all the following sliders we need value to come from the parsed args above + // and the handleXXXChange functions need to be updated to pass the relevant change to updateView + value={0} + onChange={handleSlider2Change} + vertical + /> </div> {/* Vertical Slider 3 */} - <div style={{ marginRight: "20px", height: '100px', marginTop: '9px' }}> - <Slider {...volStartProps} onChange={handleSlider3Change} vertical /> + <div style={{ marginRight: "20px", height: "100px", marginTop: "9px" }}> + <Slider + {...volStartProps} + value={0} + onChange={handleSlider3Change} + vertical + /> </div> {/* Vertical Slider 4 */} - <div style={{ marginRight: "25px", height: '100px', marginTop: '9px' }}> - <Slider {...volEndprops} onChange={handleSlider4Change} vertical /> + <div style={{ marginRight: "25px", height: "100px", marginTop: "9px" }}> + <Slider + {...volEndprops} + value={0} + onChange={handleSlider4Change} + vertical + /> </div> - - <div style={{ marginRight: '10px', height: '100px', fontSize: '12px' }}> - + <div style={{ marginRight: "10px", height: "100px", fontSize: "12px" }}> {/* waveform type selection */} - <label style={{ display: 'block', marginBottom: '5px', marginTop: '7px', }}> + <label + style={{ display: "block", marginBottom: "5px", marginTop: "7px" }} + > <b>Waveform:</b> </label> <select onChange={(e) => handleWaveTypeChange(e.target.value)}> @@ -363,7 +406,9 @@ const TripleSliderWidget: React.FC<{ {/* fx type selection */} - <label style={{ display: 'block', marginBottom: '5px', marginTop: '10px' }}> + <label + style={{ display: "block", marginBottom: "5px", marginTop: "10px" }} + > <b>Effects:</b> </label> <select onChange={(e) => handleWaveTypeChange(e.target.value)}> @@ -375,7 +420,9 @@ const TripleSliderWidget: React.FC<{ {/* Duration selctor */} - <label style={{ display: 'block', marginBottom: '5px', marginTop: '10px' }}> + <label + style={{ display: "block", marginBottom: "5px", marginTop: "10px" }} + > <b>Duration(ms):</b> </label> {/* Input field with associated datalist */} @@ -384,12 +431,19 @@ const TripleSliderWidget: React.FC<{ value={textBoxValue} onChange={handleTextInputChange} // Handle the selected or typed-in value defaultValue="2000" - style={{ width: '75px' }} + style={{ width: "75px" }} /> - </div> {/* Waveform box */} - <div style={{ width: '200px', height: '130px', backgroundColor: 'linen', marginTop: '9px', marginLeft: '5px' }}> + <div + style={{ + width: "200px", + height: "130px", + backgroundColor: "linen", + marginTop: "9px", + marginLeft: "5px", + }} + > <svg width="100%" height="100%"> <path d={generateWavePath()} stroke="black" fill="none" /> <line @@ -404,12 +458,9 @@ const TripleSliderWidget: React.FC<{ </div> </div> </div> - ); }; - - export const SoundComponent = ({ props, view, @@ -417,12 +468,11 @@ export const SoundComponent = ({ props: WidgetProps; view: EditorView; }) => { - let args = props.args; let ranges = props.ranges; let types = props.types; let from = props.from; - let to = props.to + let to = props.to; //for future reference add a aclose button const handleCloseClick = () => { @@ -430,7 +480,7 @@ export const SoundComponent = ({ effects: [openWidgetEffect.of(-1)], }); }; - + const updateView = () => { let insertion = "test"; console.log(insertion); @@ -456,7 +506,7 @@ export const SoundComponent = ({ }); } }; - + return ( <HStack fontFamily="body" spacing={5} py={3} zIndex={10}> <Box ml="10px" style={{ marginRight: "4px" }}> @@ -478,13 +528,29 @@ export const SoundComponent = ({ //(startFreq: number, endFreq: Number, duration: Number, startVol: number, endVol: Number, waveform: string, fx: string) -function statesToString(startFreq: number, endFreq: Number, duration: Number, startVol: number, endVol: Number): string { - return `\n` - + ` freq_start=${startFreq},\n` - + ` freq_end=${endFreq},\n` - + ` duration=${duration},\n` - + ` vol_start=${startVol},\n` - + ` vol_end=${endVol},\n` - + ` waveform=SoundEffect.FX_WARBLE,\n` - + ` fx=SoundEffect.FX_VIBRATO`; +interface ParsedArgs { + startFreq: number; + endFreq: number; + duration: number; + startVol: number; + endVol: number; +} + +function statesToString({ + startFreq, + endFreq, + duration, + startVol, + endVol, +}: ParsedArgs): string { + return ( + `\n` + + ` freq_start=${startFreq},\n` + + ` freq_end=${endFreq},\n` + + ` duration=${duration},\n` + + ` vol_start=${startVol},\n` + + ` vol_end=${endVol},\n` + + ` waveform=SoundEffect.FX_WARBLE,\n` + + ` fx=SoundEffect.FX_VIBRATO` + ); }