diff --git a/docusaurus.config.ts b/docusaurus.config.ts index a737dc21d..884c7cd5a 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -29,9 +29,9 @@ import { sassPluginConfig, dynamicRouterPluginConfig, rsDoctorPluginConfig, - aliasConfigurationPluginConfig, sentryPluginConfig, - socketIoNoDepWarningsPluginConfig + socketIoNoDepWarningsPluginConfig, + aliasConfigurationPlugin } from './src/siteConfig/pluginConfigs'; import { useTdevContentPath } from './src/siteConfig/helpers'; import path from 'path'; @@ -362,7 +362,12 @@ const docusaurusConfig = withSiteConfig().then(async (siteConfig) => { sassPluginConfig, dynamicRouterPluginConfig, rsDoctorPluginConfig, - aliasConfigurationPluginConfig, + [ + aliasConfigurationPlugin, + { + websiteDir: siteConfig.websiteDir ?? './website' + } + ], sentryPluginConfig, remarkPdfPluginConfig, socketIoNoDepWarningsPluginConfig, diff --git a/package.json b/package.json index 222a490b9..c042074e7 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,6 @@ "remark-math": "^6.0.0", "sass": "^1.93.2", "socket.io-client": "^4.8.1", - "svg-parser": "^2.0.4", "uuid": "^13.0.0" }, "devDependencies": { @@ -102,7 +101,6 @@ "@types/minimist": "^1.2.5", "@types/react-dom": "^19.0.3", "@types/react-katex": "^3.0.4", - "@types/svg-parser": "^2.0.6", "@types/uuid": "^10.0.0", "@types/wicg-file-system-access": "^2023.10.6", "@vitest/coverage-v8": "^2.0.5", diff --git a/packages/tdev/brython-code/README.mdx b/packages/tdev/brython-code/README.mdx new file mode 100644 index 000000000..4ed2b55c1 --- /dev/null +++ b/packages/tdev/brython-code/README.mdx @@ -0,0 +1,641 @@ +--- +page_id: e7bde0ab-c0fe-4225-8325-e4ebc22d414b +tags: + - '@tdev/' + - python + - code +sidebar_position: 50 +--- + +import BrowserWindow from '@tdev-components/BrowserWindow'; + +# brython-code + + +```py live_py id=7cf4fb1c-8495-4600-bf67-23fb7bd29deb +print('hello world') +``` + +### Title (`title`) + +Mit dem `title`-Parameter kann ein Titel für den Code-Block hinzugefügt werden. + +:::warning[Spaces] +Ein Leerschlag wird nicht unterstützt, da er als Trennzeichen im Meta-String eines Code-Blocks verwendet wird. Stattdessen können Unterstriche `_` oder Bindestriche `-` verwendet werden. +::: + +````md +```py live_py title=example.py +``` +```` + +:::tip[Use `_` in the title] +Um einen Unterstrich zu verwenden, kann folgende Syntax verwendet werden: + +````md +```py live_py title=example__file.py +``` +```` + + + ```py live_py title=example__file.py id=8ba96031-71e6-47dd-be6e-9164761653ee + print('Hello Title') + ``` + +::: + +### Slim Mode (`slim`) + +Manchmal ist ein Editor mit einem Header visuell zu gross. Für kleine Code-Schnipsel kann der `slim`-Modus verwendet werden, um die Grösse des Editors zu reduzieren. Dies entfernt den Header und blendet die Zeilennummern aus. + +````md +```py live_py slim +print('Hello Slim Mode') +``` +```` + + + ```py live_py slim + print('Hello Slim Mode') + ``` + + +:::warning[API-Speicherung] +Bei der Verwendung des `slim`-Modus wird der `id`-Parameter ignoriert. +::: + +### Read-Only Mode (`readonly`) + +Ein Editor kann auch im Read-Only-Modus angezeigt werden, indem der `readonly`-Parameter hinzugefügt wird. + +````md +```py live_py readonly +print('Hello Read-Only Mode') +``` +```` + + + ```py live_py readonly hideWarning + print('Hello Read-Only Mode') + ``` + + +::::tip[Works with Persisted Code] +Auch wenn der Code von der API geladen wird, wird der Read-Only-Modus beibehalten. + +:::details[Use Case: Schulunterricht] +Dies ist besonders nützlich im Schulunterricht, wenn einige Übungen/Prüfungen nur während einer bestimmten Zeit durchgeführt werden sollen, die Schüler:innen aber später die eingereichte Lösungen anzeigen können sollen. Dann wird das `readonly`-Attribut hinzugefügt und die Schüler:innen sehen ihre Änderungen, können diese aber nicht bearbeiten. +::: +:::: + +### Download Button verstecken (`noDownload`) + +Der Download-Button ermöglicht es, den Code als Datei herunterzuladen. Mit dem `noDownload`-Meta-String kann der Download-Button ausgeblendet werden. + +````md +```py live_py noDownload +print('Hello No Download Button') +``` +```` + + + ```py live_py noDownload hideWarning + print('Hello No Download Button') + ``` + + +### Vergleich-Button ausblenden (`noCompare`) + +Wenn Änderungen am Code vorgenommen wurden, können diese mit dem Originalcode verglichen werden. Der Vergleichsbutton kann über den `noCompare`-Meta-String ausgeblendet werden. + +````md +```py live_py noCompare hideWarning +print('Hello No Compare Button') +``` +```` + + + Ändere den Code und beachte, dass kein Vergleichsbutton angezeigt wird. + + ```py live_py noCompare hideWarning + print('Hello No Compare Button') + ``` + + +### Reset-Button ausblenden (`noReset`) + +Der Reset-Button ermöglicht es, den Code auf den ursprünglichen Code zurückzusetzen. Der Reset-Button kann über den `noReset`-Meta-String ausgeblendet werden. + +````md +```py live_py noReset +print('Hello No Reset Button') +``` +```` + + + Ändere den Code und beachte, dass der Reset-Button ausgeblendet ist. + + ```py live_py noReset hideWarning + print('Hello No Reset Button') + ``` + + +### Max Line Number bevor gescrollt wird (`maxLines`) + +Es kann die maximale Anzahl von Zeilen angegeben werden, bevor der Editor scrollt. Dies ist nützlich für lange Code-Schnipsel. Der Standardwert ist `25`. + +````md +```py live_py maxLines=5 +print('Line 1') +print('Line 2') +print('Line 3') +print('Line 4') +print('Line 5') +print('Line 6 - scrolled') +``` +```` + + + ```py live_py maxLines=5 hideWarning + print('Line 1') + print('Line 2') + print('Line 3') + print('Line 4') + print('Line 5') + print('Line 6 - scrolled') + ``` + + + +### Minimale Anzahl Zeilen (`minLines`) + +Es kann die minimale Anzahl von Zeilen angegeben werden, die der Editor mindestens anzeigen soll. +Dies ist nützlich falls Code-Blöcke miteinander verglichen werden sollen. + +````md +```py live_py minLines=5 +print('Line 1') +print('Line 2') +``` +```` + + + ```py live_py minLines=5 hideWarning + print('Line 1') + print('Line 2') + ``` + + + +### Imports + +Standardmässig können alle Python-Skripte, die sich im Verzeichnis `static/bry-libs/` befinden, direkt aus den Code-Blöcken importiert werden. Dies ermöglicht es Ihnen, wiederverwendbare Module zu erstellen, die in Ihre Code-Blöcke importiert werden können. + +Das folgende Beispiel zeigt, wie ein Modul importiert und verwendet werden kann. Das Skript `grid.py` ist standardmässig in `static/bry-libs/grid.py` enthalten und kann somit importiert werden. +The script `grid.py` is added by default to `static/bry-libs/grid.py` when you run the dev-server. + +:::details[Sourcecode von `grid.py`] +```py +from browser import document # type: ignore +from config import Config # type: ignore + +class Rectangle(): + col: int + row: int + ctx = None + grid = None + init_draw = False + def __init__(self, grid, col: int, row: int, color: str = ''): + self.col = col + self.row = row + self.grid = grid + self.init_draw = False + try: + canvas = document[Config.CANVAS_ID] + self.ctx = canvas.getContext('2d') + except: + pass + self._color = color + + def get(self, offset_x: int, offset_y: int): + y = (self.row + offset_y) % len(self.grid) # type: ignore + x = (self.col + offset_x) % len(self.grid[y]) # type: ignore + return self.grid[y][x] # type: ignore + + @property + def color(self): + return self._color + + @color.setter + def color(self, color: str): + if color == '': + color = 'rgba(0,0,0,0)' + + if self._color == color and self.init_draw: + return + self._color = color + self.init_draw = True + self.draw() + + def draw(self): + scale = self.grid.scale # type: ignore + x = self.col * scale + y = self.row * scale + try: + self.ctx.clearRect(x, y, scale, scale) # type: ignore + self.ctx.lineWidth = 0 # type: ignore + self.ctx.fillStyle = self.color # type: ignore + self.ctx.fillRect(x, y, scale, scale) # type: ignore + except: + pass + + def copy(self, grid): + return Rectangle(grid, self.col, self.row, self.color) + + def __repr__(self): + return self.color + +class RectLine(): + line: list = [] + n = 0 + max = 0 + def __init__(self, grid, row, cols: int | list, color: str = ''): + self.grid = grid + if type(cols) == list: + self.line = cols # type: ignore + else: + self.line = [Rectangle(grid, col, row, color) for col in range(cols)] # type: ignore + self.max = len(self.line) # type: ignore + + def __getitem__(self, key): + return self.line[key] + + def __setitem__(self, key, value): + self.line[key].color = value + + def __repr__(self): + return ', '.join([f'{r.color}' for r in self.line]) + + def __iter__(self): + self.n = 0 + return self + + def __next__(self): + if self.n < self.max: + result = self[self.n] + self.n += 1 + return result + else: + raise StopIteration + + def __len__(self): + return self.max + + def draw(self): + for rect in self.line: + rect.draw() + + def copy(self, grid): + return RectLine(grid, self.line[0].row, [l.copy(grid) for l in self.line]) # type: ignore + +class Grid(): + lines = [] + n = 0 + max = 0 + CANVAS_ID = '' + WIDTH = 500 + HEIGHT = 500 + scale = 10 + record_gif = False + frames = {} + + def __init__(self, rows: int, cols: int, color: str = '', scale: int = -1): + if scale < 0: + if rows > 0 and cols > 0: + scale = min(Grid.WIDTH // cols, Grid.HEIGHT // rows) + else: + scale = 10 + self.scale = scale + self.lines = [RectLine(self, row, cols, color) for row in range(rows)] + self.max = rows + + @staticmethod + def setup(width: int, height: int, record_gif: bool = False): + Grid.HEIGHT = height + Grid.WIDTH = width + Grid.record_gif = record_gif + Grid.frames = {} + canvas = document[Config.CANVAS_ID] + parent = canvas.parent + parent.replaceChildren() + + canv = document.createElement('canvas') + canv.style.display = 'block' + canv.id = Config.CANVAS_ID; + canv.attrs['height'] = height + canv.attrs['width'] = width + canv.style.width = f'{width}px' + canv.style.height = f'{height}px' + parent.appendChild(canv) + + @staticmethod + def from_bin_text(bin_text: str, colors={'s': 'black', '1': 'black', 'x': 'black', 'bg': ''}): + lines = bin_text.lower().splitlines() + if 'bg' not in colors: + colors['bg'] = '' + while len(lines) > 0 and len(lines[0]) == 0: + lines.pop(0) + size_y = len(lines) + if size_y < 1: + raise Exception('Grid must have at least one non empty line') + size_x = max(map(lambda x: len(x), lines)) + + scale = min(Grid.WIDTH // size_x, Grid.HEIGHT // size_y) + grid = Grid(0, 0, colors['bg'], scale) + raw_grid = [] + for line in lines: + raw_line = [] + for x in range(size_x): + if x < len(line): + raw_line.append(Rectangle(grid, x, len(raw_grid), colors.get(line[x], colors['bg']))) + else: + raw_line.append(Rectangle(grid, x, len(raw_grid), colors['bg'])) + raw_grid.append(RectLine(grid, len(raw_grid), raw_line)) + grid.set_lines(raw_grid) + grid.draw() + return grid + + + def set_lines(self, lines): + self.lines = lines + self.max = len(lines) + + + def tolist(self): + return [[c.color for c in l.line] for l in self.lines] + + @property + def color_grid(self): + return self.tolist() + + @property + def grid(self): + return self.tolist() + + @property + def size(self): + return (self.dim_y, self.dim_x) + + @property + def dim_x(self): + if self.max < 1: + return 0 + return len(self[0]) + + @property + def dim_y(self): + return len(self.lines) + + @staticmethod + def clear_canvas(): + try: + canvas = document[Config.CANVAS_ID] + ctx = canvas.getContext('2d') + ctx.clearRect(0, 0, Grid.WIDTH, Grid.HEIGHT) # type: ignore + except: + pass + + + def draw(self): + for line in self.lines: + line.draw() + + @staticmethod + def gif_add(): + if Grid.record_gif: + canvas = document[Config.CANVAS_ID] + frameName = 'frame_' + str(len(Grid.frames)).rjust(3, '0') + Grid.frames[frameName] = canvas.toDataURL('image/png'); + + + + def fill(self, color: str = ''): + for line in self.lines: + for cell in line: + cell.color = color + + def copy(self): + cp = Grid(0, 0) + lines = [l.copy(cp) for l in self.lines] + cp.set_lines(lines) + return cp + + + def __getitem__(self, key): + return self.lines[key] + + def __setitem__(self, key, value): + self.lines[key] = value + + def __repr__(self): + rep = '' + for line in self.lines: + rep += f'{line}' + rep += '\n' + return rep + + def __iter__(self): + self.n = 0 + return self + + def __next__(self): + if self.n < self.max: + result = self[self.n] + self.n += 1 + return result + else: + raise StopIteration + + def __len__(self): + return self.max +``` +::: + + + ```py live_py title=grid__example.py hideWarning + from grid import Grid + Grid.clear_canvas() + smile = Grid.from_text(''' + + x x + + + + x x + xxxxxx + + ''') + smile.draw() + ``` + + +### Änderungen abspeichern (`id`=uuid) + +Änderungen im Code-Editor können durch Hinzufügen einer `id` zum Code-Block persistiert werden. Die Änderungen werden über die `teaching-api` gespeicher und der Inhalt wird beim Neuladen der Seite wiederhergestellt. + +````md +```py live_py id=50fa8065-0d3b-4cb1-b03f-8244a6582d60 +# Änderungen in diesem Code-Block werden für angemeldete User im Backend persistiert. +``` +```` + + + ```py live_py id=50fa8065-0d3b-4cb1-b03f-8244a6582d60 + # Änderungen in diesem Code-Block werden für angemeldete User im Backend persistiert. + ``` + + +:::warning[Eindeutige IDs] +Stelle sicher dass die IDs auf der gesamten Website (nicht nur auf dieser Seite) eindeutig sind, ansonsten kann das Verhalten für deine Benutzer unerwartet sein. (Der Code wird durch den zuletzt geänderten Code-Block mit derselben ID überschrieben). +::: + +:::tip[UUID] +Ein guter Weg, um eindeutige IDs zu gewährleisten, ist die Verwendung einer UUID. Für VS Code-Benutzer ist die Erweiterung [UUID Generator von Motivesoft](https://marketplace.visualstudio.com/items?itemName=motivesoft.vscode-uuid-generator) praktisch, um neue UUIDs mit `Alt+Shift+U` einzufügen. +::: + +### Versionen speichern (`versioned`) + +Es können Versionen des Codes gespeichert werden, indem der `versioned`-Meta-String hinzugefügt wird. Dies fügt eine Versionshistorie zum Editor hinzu. Jede Änderung wird als neue Version gespeichert, aber nicht öfter als einmal alle 1 Sekunde. + +````md +```py live_py versioned id=b8ab041e-ce58-4803-b504-6b1011524370 +print('Hello Versioned Mode') +``` +```` + + + Probiere es aus, indem du den Code mehrmals (min. 2x) änderst und speicherst und dann auf die Versionshistorie-Details klickst. + + ```py live_py versioned id=b8ab041e-ce58-4803-b504-6b1011524370 + print('Hello Versioned Mode') + ``` + + +:::warning[Only in Persisted Mode] +Der `versioned`-Modus funktioniert nur in Kombination mit dem `id`-Attribut. Das `id`-Attribut wird verwendet, um die Versionen im backend zu persistieren. +::: + +### Version History verstecken (`noHistory`) + +Die Versionen des Codes werden standardmässig angezeigt. Mit dem `noHistory`-Meta-String kann die Versionshistorie ausgeblendet werden. + +````md +```py live_py versioned noHistory id=07300009-a743-4adc-bdc5-cacbfdf5230a +print('Hello No History') +``` +```` + + + ```py live_py versioned noHistory id=07300009-a743-4adc-bdc5-cacbfdf5230a + print('Hello Versioned Mode') + ``` + + +### Pre- und Post-Code + +Mit Kommentaren lassen sich Code-Teile zu Beginn oder am Ende verstecken und auf Wunsch aufklappen. Dies ist nützlich, um den Fokus auf den Hauptteil des Codes zu legen. + +`### PRE` +: Die Code-Sequenz **vor** dem `### POST`-Kommentar ist standardmässig eingeklappt und wird nicht angezeigt. +`### POST` +: Die Code-Sequenz **nach** dem `### POST`-Kommentar ist standardmässig eingeklappt und wird nicht angezeigt. + +````md +```py live_py id=5ecb46e8-4ab5-428b-bb3f-467aa0d47d01 +# This is a pre code block +from time import time +t0 = time.time() +PI = 3.14159265359 +### PRE +radius = 10 +print(f'Der Umfang eines Kreises mit Radius {radius} beträgt {2 * PI * radius}') +### POST +print('--------------------------') +print(f'Die Berechnung dauerte {time.time() - t0} Sekunden') +``` +```` + + + Codeblock mit eingeklapptem Pre- und Post-Code. Die Ausklapp-Knöpfe werden angezeigt, wenn mit der Maus über den Code gefahren wird. + + ```py live_py id=5ecb46e8-4ab5-428b-bb3f-467aa0d47d01 + from time import time + t0 = time() + PI = 3.14159265359 + ### PRE + radius = 10 + print(f'Der Umfang eines Kreises mit Radius {radius} beträgt {2 * PI * radius}') + ### POST + print('----------------------------------------------------------') + print(f'Die Berechnung dauerte {(time() - t0) * 1000} Millisekunden') + ``` + + +### Nicht abgespeicherte Code Blocks + +Wenn keine `id` angegeben ist, wird der Code nicht gespeichert, was mit einem :mdi[flash-triangle-outline]{.orange} und einem orangen Header signalisiert wird. +Dies ist nützlich, wenn Code-Beispiele nicht gespeichert werden sollen. + +````md +```py live_py +print('Hello Unpersisted Code Block') +``` +```` + + + ```py live_py + print('Hello Unpersisted Code Block') + ``` + + +#### Hide the Warning (`hideWarning`) + +Soll die Warnung ausgeblendet werden, kann der `hideWarning`-Parameter hinzugefügt werden. + +````md +```py live_py hideWarning +print('Hello Unpersisted Code Block') +``` +```` + + + ```py live_py hideWarning + print('Hello Unpersisted Code Block') + ``` + + + + +## Installation + +:::info[`packages/tdev/brython-code`] +Kopiere des `packages/tdev/brython-code`-Verzeichnis in das `tdev-website/website/packages`-Verzeichnis oder über `updateTdev.config.yaml` hinzufügen. +::: + +Hinzufügen des `brython-code`-Package zu den `apiDocumentProviders` im `siteConfig.ts`: + +```ts title="siteConfig.ts" +const getSiteConfig: SiteConfigProvider = () => { + return { + apiDocumentProviders: [ + require.resolve('@tdev/brython-code/register'), + ] + }; +}; +``` + +Danach muss erneut installiert werden: + +```bash +yarn install +``` \ No newline at end of file diff --git a/packages/tdev/brython-code/components/Footer/index.tsx b/packages/tdev/brython-code/components/Footer/index.tsx new file mode 100644 index 000000000..e377180fd --- /dev/null +++ b/packages/tdev/brython-code/components/Footer/index.tsx @@ -0,0 +1,27 @@ +import * as React from 'react'; +import Logs from '@tdev-components/documents/CodeEditor/Editor/Footer/Logs'; +import { observer } from 'mobx-react-lite'; +import type Script from '@tdev/brython-code/models/Script'; +import Container from '@tdev-components/documents/CodeEditor/Editor/Footer/Container'; + +interface Props { + code: Script; +} + +const Footer = observer((props: Props) => { + const { code } = props; + return ( + + {code.messages.length > 0 && ( + { + code.clearMessages(); + }} + /> + )} + + ); +}); + +export default Footer; diff --git a/packages/tdev/brython-code/components/Header/index.tsx b/packages/tdev/brython-code/components/Header/index.tsx new file mode 100644 index 000000000..0b0305547 --- /dev/null +++ b/packages/tdev/brython-code/components/Header/index.tsx @@ -0,0 +1,25 @@ +import * as React from 'react'; +import RunCode from '@tdev-components/documents/CodeEditor/Actions/RunCode'; +import { observer } from 'mobx-react-lite'; +import Container from '@tdev-components/documents/CodeEditor/Editor/Header/Container'; +import Script from '@tdev/brython-code/models/Script'; +import Content from '@tdev-components/documents/CodeEditor/Editor/Header/Content'; + +interface Props { + code: Script; +} + +const Header = observer((props: Props) => { + const { code: code } = props; + if (!code) { + return null; + } + return ( + + {!code.meta.slim && } + {code.canExecute && code.runCode()} />} + + ); +}); + +export default Header; diff --git a/packages/tdev/brython-code/components/Header/styles.module.scss b/packages/tdev/brython-code/components/Header/styles.module.scss new file mode 100644 index 000000000..9c26c6795 --- /dev/null +++ b/packages/tdev/brython-code/components/Header/styles.module.scss @@ -0,0 +1,42 @@ +.controls { + grid-area: controls; + display: flex; + align-items: center; + letter-spacing: 0.08em; + + z-index: calc(var(--ifm-z-index-fixed) - 2); + padding: 0.2em; + padding-left: 0.5em; + text-transform: uppercase; + font-weight: bold; + background: var(--ifm-color-emphasis-200); + color: var(--ifm-color-content); + font-size: calc(var(--ifm-code-font-size) * 1.2); + .title { + flex-shrink: 1; + overflow-x: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + .dummyIndicatorIcon { + margin-left: 1em; + } + .spacer { + flex-grow: 1; + flex-basis: 0; + } + &.slim { + padding: 0; + } + &.unpersisted { + background: var(--ifm-color-warning-lightest); + } +} + +[data-theme='dark'] { + .controls { + &.unpersisted { + background: var(--ifm-color-warning-darkest); + } + } +} diff --git a/src/components/documents/CodeEditor/BrythonCommunicator.tsx b/packages/tdev/brython-code/components/Meta/BrythonCommunicator.tsx similarity index 68% rename from src/components/documents/CodeEditor/BrythonCommunicator.tsx rename to packages/tdev/brython-code/components/Meta/BrythonCommunicator.tsx index 05366dead..7a357e171 100644 --- a/src/components/documents/CodeEditor/BrythonCommunicator.tsx +++ b/packages/tdev/brython-code/components/Meta/BrythonCommunicator.tsx @@ -1,12 +1,14 @@ import * as React from 'react'; -import { BRYTHON_NOTIFICATION_EVENT, DOM_ELEMENT_IDS } from '@tdev-components/documents/CodeEditor/constants'; -import { useDocument } from '@tdev-hooks/useContextDocument'; -import { DocumentType } from '@tdev-api/document'; import { observer } from 'mobx-react-lite'; -import { type LogMessage } from '@tdev-models/documents/Script'; +import { BRYTHON_NOTIFICATION_EVENT, DOM_ELEMENT_IDS } from '@tdev/brython-code'; +import Script, { LogMessage } from '@tdev/brython-code/models/Script'; -const BrythonCommunicator = observer(() => { - const script = useDocument<'script'>(); +interface Props { + code: Script; +} + +const BrythonCommunicator = observer((props: Props) => { + const { code } = props; const ref = React.useRef(null); React.useEffect(() => { const { current } = ref; @@ -18,18 +20,18 @@ const BrythonCommunicator = observer(() => { const data = event.detail as LogMessage; switch (data.type) { case 'start': - script.clearLogMessages(); - script.setExecuting(true); + code.clearLogMessages(); + code.setExecuting(true); break; case 'done': const isRunning = current.getAttribute('data--is-running'); if (isRunning) { return; } - script.setExecuting(false); + code.setExecuting(false); break; default: - script.addLogMessage({ + code.addLogMessage({ type: data.type, output: data.output, timeStamp: data.timeStamp @@ -42,9 +44,13 @@ const BrythonCommunicator = observer(() => { return () => { current.removeEventListener(BRYTHON_NOTIFICATION_EVENT, onBryNotify as EventListener); }; - }, [ref, script]); + }, [ref, code]); + + if (!code || code.lang !== 'python') { + return null; + } - return
; + return
; }); export default BrythonCommunicator; diff --git a/src/components/documents/CodeEditor/Editor/Result/Graphics/Canvas.tsx b/packages/tdev/brython-code/components/Meta/Graphics/Canvas.tsx similarity index 80% rename from src/components/documents/CodeEditor/Editor/Result/Graphics/Canvas.tsx rename to packages/tdev/brython-code/components/Meta/Graphics/Canvas.tsx index 4badd44d0..f0e005b07 100644 --- a/src/components/documents/CodeEditor/Editor/Result/Graphics/Canvas.tsx +++ b/packages/tdev/brython-code/components/Meta/Graphics/Canvas.tsx @@ -1,12 +1,11 @@ import * as React from 'react'; -import { DOM_ELEMENT_IDS } from '@tdev-components/documents/CodeEditor/constants'; -import Graphics from '@tdev-components/documents/CodeEditor/Editor/Result/Graphics'; import styles from './styles.module.scss'; import Button from '@tdev-components/documents/CodeEditor/Button'; -import { useDocument } from '@tdev-hooks/useContextDocument'; -import { DocumentType } from '@tdev-api/document'; import { observer } from 'mobx-react-lite'; import { mdiDownload } from '@mdi/js'; +import Script from '@tdev/brython-code/models/Script'; +import Graphics from '.'; +import { DOM_ELEMENT_IDS } from '@tdev/brython-code'; const downloadCanvas = (canvasId: string) => { const canvas = document.getElementById(canvasId) as HTMLCanvasElement; @@ -31,17 +30,22 @@ const downloadCanvas = (canvasId: string) => { document.body.removeChild(downloadLink); }; -const Canvas = observer(() => { - const script = useDocument<'script'>(); +interface Props { + code: Script; +} + +const Canvas = observer((props: Props) => { + const { code } = props; return ( { - downloadCanvas(DOM_ELEMENT_IDS.canvasContainer(script.codeId)); + downloadCanvas(DOM_ELEMENT_IDS.canvasContainer(code.codeId)); }} title="Download SVG" className={styles.slimStrippedButton} @@ -49,7 +53,7 @@ const Canvas = observer(() => { } main={ { - const script = useDocument<'script'>(); +interface Props { + code: Script; +} + +const Turtle = observer((props: Props) => { + const { code } = props; return (