docusaurus-live-brython 3.0.0-beta.9 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. package/.devcontainer/devcontainer.json +38 -0
  2. package/.prettierignore +17 -0
  3. package/.prettierrc +9 -8
  4. package/CHANGELOG.md +5 -0
  5. package/README.md +7 -4
  6. package/lib/assets/py_back_trace.py +2 -1
  7. package/lib/index.d.ts +1 -7
  8. package/lib/index.d.ts.map +1 -0
  9. package/lib/index.js +94 -16
  10. package/lib/options.d.ts +1 -0
  11. package/lib/options.d.ts.map +1 -0
  12. package/lib/options.js +2 -2
  13. package/lib/theme/CodeBlock/index.d.ts +3 -3
  14. package/lib/theme/CodeBlock/index.d.ts.map +1 -0
  15. package/lib/theme/CodeBlock/index.jsx +24 -14
  16. package/lib/theme/CodeEditor/Actions/DownloadCode.d.ts +1 -0
  17. package/lib/theme/CodeEditor/Actions/DownloadCode.d.ts.map +1 -0
  18. package/lib/theme/CodeEditor/Actions/DownloadCode.jsx +8 -8
  19. package/lib/theme/CodeEditor/Actions/Reset.d.ts +1 -0
  20. package/lib/theme/CodeEditor/Actions/Reset.d.ts.map +1 -0
  21. package/lib/theme/CodeEditor/Actions/Reset.jsx +5 -5
  22. package/lib/theme/CodeEditor/Actions/RunCode.d.ts +2 -1
  23. package/lib/theme/CodeEditor/Actions/RunCode.d.ts.map +1 -0
  24. package/lib/theme/CodeEditor/Actions/RunCode.jsx +4 -4
  25. package/lib/theme/CodeEditor/Actions/ShowRaw.d.ts +1 -0
  26. package/lib/theme/CodeEditor/Actions/ShowRaw.d.ts.map +1 -0
  27. package/lib/theme/CodeEditor/Actions/ShowRaw.jsx +13 -7
  28. package/lib/theme/CodeEditor/Actions/ShowSyncStatus.d.ts +1 -0
  29. package/lib/theme/CodeEditor/Actions/ShowSyncStatus.d.ts.map +1 -0
  30. package/lib/theme/CodeEditor/Actions/ShowSyncStatus.jsx +21 -16
  31. package/lib/theme/CodeEditor/Actions/styles.module.css +2 -3
  32. package/lib/theme/CodeEditor/BrythonCommunicator.d.ts +2 -1
  33. package/lib/theme/CodeEditor/BrythonCommunicator.d.ts.map +1 -0
  34. package/lib/theme/CodeEditor/BrythonCommunicator.jsx +13 -9
  35. package/lib/theme/CodeEditor/Button/index.d.ts +3 -2
  36. package/lib/theme/CodeEditor/Button/index.d.ts.map +1 -0
  37. package/lib/theme/CodeEditor/Button/index.jsx +1 -1
  38. package/lib/theme/CodeEditor/Button/styles.module.css +1 -1
  39. package/lib/theme/CodeEditor/CodeHistory/index.d.ts +1 -0
  40. package/lib/theme/CodeEditor/CodeHistory/index.d.ts.map +1 -0
  41. package/lib/theme/CodeEditor/CodeHistory/index.jsx +26 -15
  42. package/lib/theme/CodeEditor/CodeHistory/styles.module.css +31 -31
  43. package/lib/theme/CodeEditor/ContextEditor/index.d.ts +22 -0
  44. package/lib/theme/CodeEditor/ContextEditor/index.d.ts.map +1 -0
  45. package/lib/theme/CodeEditor/ContextEditor/index.jsx +36 -0
  46. package/lib/theme/CodeEditor/Editor/EditorAce.d.ts +2 -1
  47. package/lib/theme/CodeEditor/Editor/EditorAce.d.ts.map +1 -0
  48. package/lib/theme/CodeEditor/Editor/EditorAce.jsx +17 -14
  49. package/lib/theme/CodeEditor/Editor/Header/index.d.ts +2 -1
  50. package/lib/theme/CodeEditor/Editor/Header/index.d.ts.map +1 -0
  51. package/lib/theme/CodeEditor/Editor/Header/index.jsx +12 -12
  52. package/lib/theme/CodeEditor/Editor/Header/styles.module.css +7 -7
  53. package/lib/theme/CodeEditor/Editor/HiddenCode/index.d.ts +8 -0
  54. package/lib/theme/CodeEditor/Editor/HiddenCode/index.d.ts.map +1 -0
  55. package/lib/theme/CodeEditor/Editor/HiddenCode/index.jsx +27 -0
  56. package/lib/theme/CodeEditor/Editor/HiddenCode/styles.module.css +52 -0
  57. package/lib/theme/CodeEditor/Editor/Result/Graphics/Canvas.d.ts +1 -0
  58. package/lib/theme/CodeEditor/Editor/Result/Graphics/Canvas.d.ts.map +1 -0
  59. package/lib/theme/CodeEditor/Editor/Result/Graphics/Canvas.jsx +8 -9
  60. package/lib/theme/CodeEditor/Editor/Result/Graphics/Turtle.d.ts +1 -0
  61. package/lib/theme/CodeEditor/Editor/Result/Graphics/Turtle.d.ts.map +1 -0
  62. package/lib/theme/CodeEditor/Editor/Result/Graphics/Turtle.jsx +11 -11
  63. package/lib/theme/CodeEditor/Editor/Result/Graphics/index.d.ts +4 -3
  64. package/lib/theme/CodeEditor/Editor/Result/Graphics/index.d.ts.map +1 -0
  65. package/lib/theme/CodeEditor/Editor/Result/Graphics/index.jsx +12 -10
  66. package/lib/theme/CodeEditor/Editor/Result/Graphics/styles.module.css +2 -2
  67. package/lib/theme/CodeEditor/Editor/Result/index.d.ts +2 -3
  68. package/lib/theme/CodeEditor/Editor/Result/index.d.ts.map +1 -0
  69. package/lib/theme/CodeEditor/Editor/Result/index.jsx +6 -9
  70. package/lib/theme/CodeEditor/Editor/Result/styles.module.css +15 -10
  71. package/lib/theme/CodeEditor/Editor/index.d.ts +6 -3
  72. package/lib/theme/CodeEditor/Editor/index.d.ts.map +1 -0
  73. package/lib/theme/CodeEditor/Editor/index.jsx +34 -28
  74. package/lib/theme/CodeEditor/Editor/styles.module.css +15 -7
  75. package/lib/theme/CodeEditor/Editor/utils/checkForButtonClick.d.ts +1 -0
  76. package/lib/theme/CodeEditor/Editor/utils/checkForButtonClick.d.ts.map +1 -0
  77. package/lib/theme/CodeEditor/Editor/utils/saveSvg.d.ts +1 -0
  78. package/lib/theme/CodeEditor/Editor/utils/saveSvg.d.ts.map +1 -0
  79. package/lib/theme/CodeEditor/Editor/utils/saveSvg.js +19 -8
  80. package/lib/theme/CodeEditor/Editor/utils/svgWithoutAnimations.d.ts +1 -0
  81. package/lib/theme/CodeEditor/Editor/utils/svgWithoutAnimations.d.ts.map +1 -0
  82. package/lib/theme/CodeEditor/Editor/utils/svgWithoutAnimations.js +43 -49
  83. package/lib/theme/CodeEditor/Icon/icons.d.ts +4 -1
  84. package/lib/theme/CodeEditor/Icon/icons.d.ts.map +1 -0
  85. package/lib/theme/CodeEditor/Icon/icons.js +3 -1
  86. package/lib/theme/CodeEditor/Icon/index.d.ts +4 -2
  87. package/lib/theme/CodeEditor/Icon/index.d.ts.map +1 -0
  88. package/lib/theme/CodeEditor/Icon/index.jsx +10 -3
  89. package/lib/theme/CodeEditor/Icon/styles.module.css +1 -1
  90. package/lib/theme/CodeEditor/WithScript/ScriptContext.d.ts +8 -0
  91. package/lib/theme/CodeEditor/WithScript/ScriptContext.d.ts.map +1 -0
  92. package/lib/theme/CodeEditor/WithScript/ScriptContext.jsx +27 -0
  93. package/lib/theme/CodeEditor/WithScript/Storage.d.ts +2 -1
  94. package/lib/theme/CodeEditor/WithScript/Storage.d.ts.map +1 -0
  95. package/lib/theme/CodeEditor/WithScript/Types.d.ts +12 -4
  96. package/lib/theme/CodeEditor/WithScript/Types.d.ts.map +1 -0
  97. package/lib/theme/CodeEditor/WithScript/bryRunner.d.ts +3 -0
  98. package/lib/theme/CodeEditor/WithScript/bryRunner.d.ts.map +1 -0
  99. package/lib/theme/CodeEditor/WithScript/bryRunner.js +29 -0
  100. package/lib/theme/CodeEditor/WithScript/createStore.d.ts +4 -0
  101. package/lib/theme/CodeEditor/WithScript/createStore.d.ts.map +1 -0
  102. package/lib/theme/CodeEditor/WithScript/{Store.jsx → createStore.js} +62 -74
  103. package/lib/theme/CodeEditor/WithScript/helpers.d.ts +1 -4
  104. package/lib/theme/CodeEditor/WithScript/helpers.d.ts.map +1 -0
  105. package/lib/theme/CodeEditor/WithScript/helpers.js +4 -14
  106. package/lib/theme/CodeEditor/constants.d.ts +1 -0
  107. package/lib/theme/CodeEditor/constants.d.ts.map +1 -0
  108. package/lib/theme/CodeEditor/hooks/index.d.ts +3 -0
  109. package/lib/theme/CodeEditor/hooks/index.d.ts.map +1 -0
  110. package/lib/theme/CodeEditor/hooks/index.js +2 -0
  111. package/lib/theme/CodeEditor/hooks/useScript.d.ts +3 -0
  112. package/lib/theme/CodeEditor/hooks/useScript.d.ts.map +1 -0
  113. package/lib/theme/CodeEditor/hooks/useScript.js +4 -0
  114. package/lib/theme/CodeEditor/hooks/useStore.d.ts +3 -0
  115. package/lib/theme/CodeEditor/hooks/useStore.d.ts.map +1 -0
  116. package/lib/theme/CodeEditor/hooks/useStore.js +10 -0
  117. package/lib/theme/CodeEditor/index.d.ts +24 -5
  118. package/lib/theme/CodeEditor/index.d.ts.map +1 -0
  119. package/lib/theme/CodeEditor/index.jsx +17 -16
  120. package/lib/theme/CodeEditor/styles.module.css +28 -30
  121. package/og-image.md +23 -0
  122. package/package.json +35 -20
  123. package/src/assets/py_back_trace.py +2 -1
  124. package/src/index.ts +96 -25
  125. package/src/options.ts +12 -12
  126. package/src/theme/CodeBlock/index.tsx +44 -68
  127. package/src/theme/CodeEditor/Actions/DownloadCode.tsx +23 -22
  128. package/src/theme/CodeEditor/Actions/Reset.tsx +14 -12
  129. package/src/theme/CodeEditor/Actions/RunCode.tsx +14 -11
  130. package/src/theme/CodeEditor/Actions/ShowRaw.tsx +17 -11
  131. package/src/theme/CodeEditor/Actions/ShowSyncStatus.tsx +32 -27
  132. package/src/theme/CodeEditor/Actions/styles.module.css +2 -3
  133. package/src/theme/CodeEditor/BrythonCommunicator.tsx +16 -19
  134. package/src/theme/CodeEditor/Button/index.tsx +17 -13
  135. package/src/theme/CodeEditor/Button/styles.module.css +1 -1
  136. package/src/theme/CodeEditor/CodeHistory/index.tsx +32 -20
  137. package/src/theme/CodeEditor/CodeHistory/styles.module.css +31 -31
  138. package/src/theme/CodeEditor/ContextEditor/index.tsx +74 -0
  139. package/src/theme/CodeEditor/Editor/EditorAce.tsx +20 -16
  140. package/src/theme/CodeEditor/Editor/Header/index.tsx +13 -19
  141. package/src/theme/CodeEditor/Editor/Header/styles.module.css +7 -7
  142. package/src/theme/CodeEditor/Editor/HiddenCode/index.tsx +49 -0
  143. package/src/theme/CodeEditor/Editor/HiddenCode/styles.module.css +52 -0
  144. package/src/theme/CodeEditor/Editor/Result/Graphics/Canvas.tsx +25 -22
  145. package/src/theme/CodeEditor/Editor/Result/Graphics/Turtle.tsx +23 -19
  146. package/src/theme/CodeEditor/Editor/Result/Graphics/index.tsx +16 -16
  147. package/src/theme/CodeEditor/Editor/Result/Graphics/styles.module.css +2 -2
  148. package/src/theme/CodeEditor/Editor/Result/index.tsx +7 -13
  149. package/src/theme/CodeEditor/Editor/Result/styles.module.css +15 -10
  150. package/src/theme/CodeEditor/Editor/index.tsx +67 -65
  151. package/src/theme/CodeEditor/Editor/styles.module.css +15 -7
  152. package/src/theme/CodeEditor/Editor/utils/checkForButtonClick.ts +5 -5
  153. package/src/theme/CodeEditor/Editor/utils/saveSvg.ts +63 -53
  154. package/src/theme/CodeEditor/Editor/utils/svgWithoutAnimations.ts +182 -201
  155. package/src/theme/CodeEditor/Icon/icons.ts +27 -13
  156. package/src/theme/CodeEditor/Icon/index.tsx +31 -11
  157. package/src/theme/CodeEditor/Icon/styles.module.css +1 -1
  158. package/src/theme/CodeEditor/WithScript/ScriptContext.tsx +36 -0
  159. package/src/theme/CodeEditor/WithScript/Storage.ts +3 -3
  160. package/src/theme/CodeEditor/WithScript/Types.ts +17 -11
  161. package/src/theme/CodeEditor/WithScript/bryRunner.ts +39 -0
  162. package/src/theme/CodeEditor/WithScript/createStore.ts +276 -0
  163. package/src/theme/CodeEditor/WithScript/helpers.ts +16 -26
  164. package/src/theme/CodeEditor/constants.ts +9 -11
  165. package/src/theme/CodeEditor/hooks/index.ts +2 -0
  166. package/src/theme/CodeEditor/hooks/useScript.ts +9 -0
  167. package/src/theme/CodeEditor/hooks/useStore.ts +15 -0
  168. package/src/theme/CodeEditor/index.tsx +45 -31
  169. package/src/theme/CodeEditor/styles.module.css +28 -30
  170. package/src/typings.d.ts +11 -0
  171. package/lib/theme/CodeEditor/WithScript/Store.d.ts +0 -15
  172. package/lib/types.d.ts +0 -28
  173. package/lib/types.js +0 -1
  174. package/src/theme/CodeEditor/WithScript/Store.tsx +0 -294
  175. package/src/types.ts +0 -29
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import * as Icons from 'docusaurus-live-brython/theme/CodeEditor/Icon/icons';
2
+ import * as Icons from '@theme/CodeEditor/Icon/icons';
3
3
  import styles from './styles.module.css';
4
4
  import clsx from 'clsx';
5
5
 
@@ -13,10 +13,11 @@ export enum Color {
13
13
  Link = 'var(--ifm-color-link)'
14
14
  }
15
15
 
16
- interface Props {
16
+ export interface Props {
17
17
  icon: keyof typeof Icons;
18
18
  size?: number | string;
19
19
  spin?: boolean;
20
+ rotate?: number;
20
21
  color?: string;
21
22
  className?: string;
22
23
  }
@@ -26,20 +27,39 @@ const Icon = (props: Props) => {
26
27
 
27
28
  if (props.spin) {
28
29
  return (
29
- <svg viewBox="0 0 24 24" role="presentation" style={{width: size, height: size}} className={clsx(props.className, styles.icon, styles.spin)}>
30
+ <svg
31
+ viewBox="0 0 24 24"
32
+ role="presentation"
33
+ style={{ width: size, height: size }}
34
+ className={clsx(props.className, styles.icon, styles.spin)}
35
+ >
30
36
  <style>{`@keyframes spin-inverse { to { transform: rotate(-360deg) } }`}</style>
31
- <g style={{animation: '2s linear 0s infinite normal none running spin-inverse', transformOrigin: 'center center'}}>
32
- <path d={Icons[props.icon]} style={{fill: props.color || 'currentcolor'}}></path>
37
+ <g
38
+ style={{
39
+ animation: '2s linear 0s infinite normal none running spin-inverse',
40
+ transformOrigin: 'center center'
41
+ }}
42
+ >
43
+ <path d={Icons[props.icon]} style={{ fill: props.color || 'currentcolor' }}></path>
33
44
  </g>
34
45
  </svg>
35
- )
46
+ );
36
47
  }
37
48
 
38
49
  return (
39
- <svg viewBox="0 0 24 24" role="presentation" style={{width: size, height: size}} className={clsx(props.className, styles.icon)}>
40
- <path d={Icons[props.icon]} style={{fill: props.color || 'currentcolor'}}></path>
50
+ <svg
51
+ viewBox="0 0 24 24"
52
+ role="presentation"
53
+ style={{
54
+ width: size,
55
+ height: size,
56
+ transform: `translateY(15%) rotate(${props.rotate || 0}deg)`
57
+ }}
58
+ className={clsx(props.className, styles.icon)}
59
+ >
60
+ <path d={Icons[props.icon]} style={{ fill: props.color || 'currentcolor' }}></path>
41
61
  </svg>
42
- )
43
- }
62
+ );
63
+ };
44
64
 
45
- export default Icon;
65
+ export default Icon;
@@ -1,3 +1,3 @@
1
1
  .icon {
2
2
  transform: translateY(15%);
3
- }
3
+ }
@@ -0,0 +1,36 @@
1
+ import React from 'react';
2
+ import { usePluginData } from '@docusaurus/useGlobalData';
3
+ import { type InitState, type Document } from '@theme/CodeEditor/WithScript/Types';
4
+ import { createStore } from '@theme/CodeEditor/WithScript/createStore';
5
+ import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
6
+ import BrowserOnly from '@docusaurus/BrowserOnly';
7
+ import CodeBlock from '@theme/CodeBlock';
8
+ export const Context = React.createContext<Document | undefined>(undefined);
9
+
10
+ const ScriptContext = (props: InitState & { children: React.ReactNode }) => {
11
+ const { libDir, syncMaxOnceEvery } = usePluginData('docusaurus-live-brython') as {
12
+ libDir: string;
13
+ syncMaxOnceEvery: number;
14
+ };
15
+ const [store, setStore] = React.useState<Document | null>(null);
16
+ const { siteConfig } = useDocusaurusContext();
17
+ React.useEffect(() => {
18
+ const router = siteConfig.future.experimental_router;
19
+ const store = createStore(props, libDir, syncMaxOnceEvery, router);
20
+ setStore(store);
21
+ store.load();
22
+ }, [props.id, libDir, siteConfig]);
23
+
24
+ return (
25
+ <BrowserOnly fallback={<CodeBlock language={props.lang}>{props.code}</CodeBlock>}>
26
+ {() => {
27
+ if (!store) {
28
+ return <CodeBlock language={props.lang}>{props.code}</CodeBlock>;
29
+ }
30
+ return <Context.Provider value={store}>{props.children}</Context.Provider>;
31
+ }}
32
+ </BrowserOnly>
33
+ );
34
+ };
35
+
36
+ export default ScriptContext;
@@ -1,4 +1,4 @@
1
- import type { StorageSlot, StoredScript } from "docusaurus-live-brython/theme/CodeEditor/WithScript/Types";
1
+ import type { StorageSlot, StoredScript } from '@theme/CodeEditor/WithScript/Types';
2
2
 
3
3
  export const getStorageScript = (storage: StorageSlot): StoredScript | undefined => {
4
4
  const storedCode = storage.get();
@@ -14,7 +14,7 @@ export const getStorageScript = (storage: StorageSlot): StoredScript | undefined
14
14
  }
15
15
  }
16
16
  return;
17
- }
17
+ };
18
18
 
19
19
  export const syncStorageScript = (script: StoredScript, storage: StorageSlot): boolean => {
20
20
  try {
@@ -24,4 +24,4 @@ export const syncStorageScript = (script: StoredScript, storage: StorageSlot): b
24
24
  console.warn(`Failed to save the code ${script}`, e);
25
25
  return false;
26
26
  }
27
- }
27
+ };
@@ -1,4 +1,3 @@
1
-
2
1
  export interface Version {
3
2
  code: string;
4
3
  createdAt: Date;
@@ -13,6 +12,7 @@ export interface StoredScript {
13
12
  versions: Version[];
14
13
  }
15
14
 
15
+ export type Selector<T, R> = (state: T) => R;
16
16
  export interface Script extends StoredScript {
17
17
  /**
18
18
  * this is normally a uuid
@@ -22,7 +22,7 @@ export interface Script extends StoredScript {
22
22
  * this is the codeId used to
23
23
  * - identify dom elements for this block
24
24
  * - setup the brython communicator with this id
25
- * - when using the default storage, this is the key used to
25
+ * - when using the default storage, this is the key used to
26
26
  * store the code to local storage
27
27
  */
28
28
  codeId: string;
@@ -30,20 +30,21 @@ export interface Script extends StoredScript {
30
30
  showRaw: boolean;
31
31
  isExecuting?: boolean;
32
32
  preCode: string;
33
+ postCode: string;
33
34
  lang: 'py' | string;
34
35
  logs: LogMessage[];
35
- isGraphicsmodalOpen: boolean;
36
+ graphicsModalExecutionNr: number;
36
37
  hasGraphicsOutput: boolean;
37
38
  hasTurtleOutput: boolean;
38
39
  hasCanvasOutput: boolean;
39
40
  hasEdits: boolean;
40
41
  /**
41
42
  * Storage props
42
- */
43
- isLoaded: boolean;
44
- status: Status;
45
- versionsLoaded: boolean;
46
- isPasted: boolean;
43
+ */
44
+ isLoaded: boolean;
45
+ status: Status;
46
+ versionsLoaded: boolean;
47
+ isPasted: boolean;
47
48
  }
48
49
 
49
50
  export interface LogMessage {
@@ -70,23 +71,28 @@ export interface InitState {
70
71
  id: string | undefined;
71
72
  lang: 'py' | string;
72
73
  title: string;
73
- raw: string;
74
+ code: string;
75
+ preCode: string;
76
+ postCode: string;
74
77
  readonly: boolean;
75
78
  versioned: boolean;
76
79
  }
77
80
 
78
- export interface Store<T = Script> {
81
+ export interface Document<T = Script> {
79
82
  getState: () => T;
80
83
  setState: (fn: (state: Script) => Script) => void;
81
84
  subscribe: (listener: () => void) => () => void;
82
85
  saveNow: () => Promise<Status>;
83
86
  setCode: (code: string, action?: 'insert' | 'remove' | string) => void;
84
87
  setExecuting: (executing: boolean) => void;
85
- execScript: () => void,
88
+ execScript: () => void;
86
89
  stopScript: () => void;
87
90
  closeGraphicsModal: () => void;
88
91
  addLogMessage: (log: LogMessage) => void;
89
92
  clearLogMessages: () => void;
90
93
  load: () => Promise<Status>;
91
94
  loadVersions: () => Promise<void>;
95
+ setIsPasted: (isPasted: boolean) => void;
96
+ setShowRaw: (showRaw: boolean) => void;
97
+ setStatus: (status: Status) => void;
92
98
  }
@@ -0,0 +1,39 @@
1
+ import { RouterType } from '@docusaurus/types';
2
+ import { DOM_ELEMENT_IDS } from '../constants';
3
+ import { sanitizePyScript } from './helpers';
4
+
5
+ export const runCode = (
6
+ code: string,
7
+ preCode: string,
8
+ postCode: string,
9
+ codeId: string,
10
+ libDir: string,
11
+ router: RouterType,
12
+ cache: boolean = true
13
+ ) => {
14
+ const lineShift = preCode
15
+ .trim()
16
+ .split(/\n/)
17
+ .filter((l) => l.length > 0).length;
18
+ const pre = lineShift > 0 ? `${preCode.trim()}\n` : '';
19
+ const post = postCode.trim().length > 0 ? `\n${postCode.trim()}` : '';
20
+ const toExec = `${pre}${code}${post}`;
21
+ const src = `from brython_runner import run\nrun("""${sanitizePyScript(toExec || '')}""", '${codeId}', ${lineShift})\n`;
22
+ if (!(window as any).__BRYTHON__) {
23
+ alert('Brython not loaded');
24
+ return;
25
+ }
26
+ const active = document.getElementById(DOM_ELEMENT_IDS.communicator(codeId));
27
+ active!.setAttribute('data--start-time', `${Date.now()}`);
28
+ /**
29
+ * ensure that the script is executed after the current event loop.
30
+ * Otherwise, the brython script will not be able to access the graphics output.
31
+ */
32
+ setTimeout(() => {
33
+ (window as any).__BRYTHON__.runPythonSource(src, {
34
+ pythonpath: router === 'hash' ? [] : [libDir],
35
+ cache: cache
36
+ });
37
+ }, 0);
38
+ return src;
39
+ };
@@ -0,0 +1,276 @@
1
+ import { v4 as uuidv4 } from 'uuid';
2
+ import { createStorageSlot } from '@docusaurus/theme-common';
3
+ import { getStorageScript, syncStorageScript } from '@theme/CodeEditor/WithScript/Storage';
4
+ import {
5
+ checkCanvasOutput,
6
+ checkGraphicsOutput,
7
+ checkTurtleOutput
8
+ } from '@theme/CodeEditor/WithScript/helpers';
9
+ import {
10
+ type InitState,
11
+ type LogMessage,
12
+ type Script,
13
+ Status,
14
+ type Document,
15
+ type StoredScript,
16
+ type Version
17
+ } from '@theme/CodeEditor/WithScript/Types';
18
+ import { DOM_ELEMENT_IDS } from '@theme/CodeEditor/constants';
19
+ import throttle from 'lodash/throttle';
20
+ import { RouterType } from '@docusaurus/types';
21
+ import { runCode } from '@theme/CodeEditor/WithScript/bryRunner';
22
+
23
+ export const createStore = (
24
+ props: InitState,
25
+ libDir: string,
26
+ syncMaxOnceEvery: number,
27
+ router: RouterType
28
+ ): Document => {
29
+ const canSave = !!props.id;
30
+ const id = props.id || uuidv4();
31
+ const codeId = `code.${props.title || props.lang}.${id}`.replace(/(-|\.)/g, '_');
32
+ const createdAt = new Date();
33
+ const storageKey = `code.${props.title || 'code_block'}.${id}`;
34
+ const storage = createStorageSlot(storageKey);
35
+ storage.listen((e) => {
36
+ if (e.key === storageKey) {
37
+ try {
38
+ if (e.newValue) {
39
+ const script = JSON.parse(e.newValue) as StoredScript;
40
+ if (new Date(script.updatedAt) > state.updatedAt) {
41
+ loadData(storage);
42
+ }
43
+ }
44
+ } catch (err) {
45
+ console.warn(err);
46
+ }
47
+ }
48
+ });
49
+
50
+ const loadData = (store) => {
51
+ setState((s) => ({ ...s, status: canSave ? Status.SYNCING : s.status }));
52
+ const script = getStorageScript(store);
53
+ const loadedCode = script?.code ? prepareCode(script.code, { codeOnly: true }) : {};
54
+ addVersion.cancel();
55
+ if (!state.isLoaded) {
56
+ setState((s) => ({
57
+ ...s,
58
+ isLoaded: true,
59
+ ...(script || {}),
60
+ ...loadedCode,
61
+ versions: script?.versions || [],
62
+ versionsLoaded: true,
63
+ status: canSave ? Status.SUCCESS : s.status
64
+ }));
65
+ return Status.SUCCESS;
66
+ }
67
+ if (script) {
68
+ setState((s) => ({
69
+ ...s,
70
+ ...script,
71
+ ...loadedCode,
72
+ status: canSave ? Status.SUCCESS : s.status,
73
+ versionsLoaded: true
74
+ }));
75
+ return Status.SUCCESS;
76
+ }
77
+ setState((s) => ({ ...s, status: canSave ? Status.ERROR : s.status }));
78
+ return Status.ERROR;
79
+ };
80
+
81
+ const prepareCode = (
82
+ code: string,
83
+ config: { codeOnly?: boolean; stateNotInitialized?: boolean } = {}
84
+ ) => {
85
+ const hasEdits = code !== (config.stateNotInitialized ? code : state.pristineCode);
86
+ const updatedAt = new Date();
87
+ const allCode = `${props.preCode}\n${code}\n${props.postCode}`;
88
+ const hasCanvasOutput = checkCanvasOutput(allCode);
89
+ const hasTurtleOutput = checkTurtleOutput(allCode);
90
+ const hasGraphicsOutput = checkGraphicsOutput(allCode);
91
+ if (props.versioned && !config.stateNotInitialized) {
92
+ addVersion({
93
+ code: code,
94
+ createdAt: updatedAt,
95
+ version: state.versions.length + 1
96
+ });
97
+ }
98
+ return {
99
+ code: code,
100
+ hasCanvasOutput: hasCanvasOutput,
101
+ hasTurtleOutput: hasTurtleOutput,
102
+ hasGraphicsOutput: hasGraphicsOutput,
103
+ hasEdits: hasEdits,
104
+ updatedAt: updatedAt
105
+ };
106
+ };
107
+
108
+ const setCode = (raw: string, action?: 'insert' | 'remove' | string) => {
109
+ if (state.isPasted && action === 'remove') {
110
+ return;
111
+ }
112
+ const data = prepareCode(raw);
113
+ setState((state) => ({
114
+ ...state,
115
+ ...data
116
+ }));
117
+ if (props.id) {
118
+ const toStore: StoredScript = {
119
+ code: data.code,
120
+ createdAt: state.createdAt,
121
+ updatedAt: data.updatedAt,
122
+ versions: state.versions
123
+ };
124
+ if (state.isPasted) {
125
+ addVersion.flush();
126
+ if (toStore.versions.length > 0) {
127
+ toStore.versions[toStore.versions.length - 1].pasted = true;
128
+ }
129
+ set(toStore);
130
+ set.flush();
131
+ state.isPasted = false;
132
+ } else {
133
+ set(toStore);
134
+ }
135
+ }
136
+ };
137
+
138
+ const execScript = () => {
139
+ setState((s) => ({
140
+ ...s,
141
+ isExecuting: true,
142
+ graphicsModalExecutionNr: state.hasGraphicsOutput ? state.graphicsModalExecutionNr + 1 : 0
143
+ }));
144
+ runCode(state.code, state.preCode, state.postCode, codeId, libDir, router);
145
+ };
146
+
147
+ const load = async () => {
148
+ return loadData(storage);
149
+ };
150
+
151
+ const _set = async (script: StoredScript) => {
152
+ setState((s) => ({ ...s, status: canSave ? Status.SYNCING : s.status }));
153
+ if (syncStorageScript(script, storage)) {
154
+ setState((s) => ({ ...s, status: canSave ? Status.SUCCESS : s.status }));
155
+ return Status.SUCCESS;
156
+ }
157
+ setState((s) => ({ ...s, status: canSave ? Status.ERROR : s.status }));
158
+ return Status.ERROR;
159
+ };
160
+
161
+ const setIsPasted = (isPasted: boolean) => {
162
+ setState((s) => ({ ...s, isPasted: isPasted }));
163
+ };
164
+
165
+ const set = throttle(_set, syncMaxOnceEvery, {
166
+ leading: false,
167
+ trailing: true
168
+ });
169
+
170
+ const _addVersion = (version: Version) => {
171
+ if (!props.versioned || !props.id) {
172
+ return;
173
+ }
174
+ const versions = [...state.versions];
175
+ versions.push(version);
176
+ setState((s) => ({ ...s, versions: versions }));
177
+ };
178
+ const addVersion = throttle(_addVersion, syncMaxOnceEvery, {
179
+ leading: false,
180
+ trailing: true
181
+ });
182
+
183
+ const saveNow = async () => {
184
+ addVersion.flush();
185
+ return set.flush();
186
+ };
187
+
188
+ const del = async () => {
189
+ storage.del();
190
+ return Status.SUCCESS;
191
+ };
192
+ const codeData = prepareCode(props.code, { stateNotInitialized: true });
193
+ const setExecuting = (isExecuting: boolean) => {
194
+ setState((s) => ({ ...s, isExecuting: isExecuting }));
195
+ };
196
+ const addLogMessage = (log: LogMessage) => {
197
+ setState((s) => ({ ...s, logs: [...s.logs, log] }));
198
+ };
199
+ const clearLogMessages = () => {
200
+ setState((s) => ({ ...s, logs: [] }));
201
+ };
202
+ const closeGraphicsModal = () => {
203
+ setState((s) => ({ ...s, graphicsModalExecutionNr: 0 }));
204
+ };
205
+ const stopScript = () => {
206
+ const code = document.getElementById(DOM_ELEMENT_IDS.communicator(state.codeId));
207
+ if (code) {
208
+ code.removeAttribute('data--start-time');
209
+ }
210
+ };
211
+ let state: Script = {
212
+ id: id,
213
+ codeId: codeId,
214
+ lang: props.lang,
215
+ showRaw: false,
216
+ pristineCode: codeData.code,
217
+ isExecuting: false,
218
+ logs: [],
219
+ graphicsModalExecutionNr: 0,
220
+ hasEdits: false,
221
+ createdAt: createdAt,
222
+ isLoaded: false,
223
+ status: Status.IDLE,
224
+ versions: [],
225
+ versionsLoaded: false,
226
+ isPasted: false,
227
+ preCode: props.preCode,
228
+ postCode: props.postCode,
229
+ ...codeData
230
+ };
231
+
232
+ const getState = () => state;
233
+ const listeners = new Set<() => void>();
234
+ const setState = (fn: (state: Script) => Script) => {
235
+ state = fn(state);
236
+ listeners.forEach((l) => l());
237
+ };
238
+ const subscribe = (listener: () => void) => {
239
+ listeners.add(listener);
240
+ return () => listeners.delete(listener);
241
+ };
242
+ const loadVersions = async () => {
243
+ // noop
244
+ state.isLoaded = false;
245
+ load();
246
+ setState((s) => ({ ...s, versionsLoaded: true }));
247
+ return Promise.resolve();
248
+ };
249
+
250
+ const setShowRaw = (showRaw: boolean) => {
251
+ setState((s) => ({ ...s, showRaw: showRaw }));
252
+ };
253
+
254
+ const setStatus = (status: Status) => {
255
+ setState((s) => ({ ...s, status: status }));
256
+ };
257
+
258
+ return {
259
+ getState,
260
+ setState,
261
+ subscribe,
262
+ saveNow,
263
+ addLogMessage,
264
+ clearLogMessages,
265
+ closeGraphicsModal,
266
+ setCode,
267
+ execScript,
268
+ setExecuting,
269
+ stopScript,
270
+ load,
271
+ setIsPasted,
272
+ setShowRaw,
273
+ setStatus,
274
+ loadVersions
275
+ } satisfies Document;
276
+ };
@@ -1,40 +1,30 @@
1
-
2
- const TURTLE_IMPORTS_TESTER = /(^from turtle import)|(^import turtle)/m
3
- const GRID_IMPORTS_TESTER = /(^from grid import)|(^import grid)/m
4
- const GRAPHICS_OUTPUT_TESTER = /^(SETUP_)?GRAPHICS_OUTPUT\s*=\s*(True|1)/m
5
- const CANVAS_OUTPUT_TESTER = /^(SETUP_)?CANVAS_OUTPUT\s*=\s*(True|1)/m
1
+ const TURTLE_IMPORTS_TESTER = /(^from turtle import)|(^import turtle)/m;
2
+ const GRID_IMPORTS_TESTER = /(^from grid import)|(^import grid)/m;
3
+ const GRAPHICS_OUTPUT_TESTER = /^(SETUP_)?GRAPHICS_OUTPUT\s*=\s*(True|1)/m;
4
+ const CANVAS_OUTPUT_TESTER = /^(SETUP_)?CANVAS_OUTPUT\s*=\s*(True|1)/m;
6
5
 
7
6
  export const checkGraphicsOutput = (raw: string): boolean => {
8
- return CANVAS_OUTPUT_TESTER.test(raw) || GRAPHICS_OUTPUT_TESTER.test(raw) || TURTLE_IMPORTS_TESTER.test(raw) || GRID_IMPORTS_TESTER.test(raw);
9
- }
7
+ return (
8
+ CANVAS_OUTPUT_TESTER.test(raw) ||
9
+ GRAPHICS_OUTPUT_TESTER.test(raw) ||
10
+ TURTLE_IMPORTS_TESTER.test(raw) ||
11
+ GRID_IMPORTS_TESTER.test(raw)
12
+ );
13
+ };
10
14
 
11
15
  export const checkTurtleOutput = (raw: string): boolean => {
12
16
  return TURTLE_IMPORTS_TESTER.test(raw);
13
- }
17
+ };
14
18
 
15
19
  export const checkCanvasOutput = (raw: string): boolean => {
16
20
  return CANVAS_OUTPUT_TESTER.test(raw) || GRID_IMPORTS_TESTER.test(raw);
17
- }
18
-
19
- export const getPreCode = (rawcode: string) => {
20
- const match = rawcode.match(/\n###\s*PRE.*?\n/);
21
- if (match) {
22
- return {
23
- pre: rawcode.slice(0, match.index || 0),
24
- code: rawcode.slice((match.index || 0) + match[0].length)
25
- }
26
- }
27
- return {
28
- pre: '',
29
- code: rawcode
30
- };
31
- }
21
+ };
32
22
 
33
23
  /**
34
24
  * The python script is transformed to a string by embedding it with """ characters.
35
25
  * So we must prevent the script itself to contain this sequence of characters.
36
- * @param {String} script
26
+ * @param {String} script
37
27
  */
38
28
  export const sanitizePyScript = (script: string) => {
39
- return script.replace(/"{3}/g, "'''").replace(/\\n/g, '\\\\n').replace(/\\r/g, '\\\\r')
40
- }
29
+ return script.replace(/"{3}/g, "'''").replace(/\\n/g, '\\\\n').replace(/\\r/g, '\\\\r');
30
+ };
@@ -1,4 +1,3 @@
1
-
2
1
  const DOM_ELEMENT_IDS = {
3
2
  communicator: (codeId: string) => `py_${codeId}`,
4
3
  graphicsResult: (codeId: string) => `${codeId}_graphics`,
@@ -6,16 +5,15 @@ const DOM_ELEMENT_IDS = {
6
5
  aceEditor: (codeId: string) => `${codeId}_editor`,
7
6
  turtleSvgContainer: (codeId: string) => `${codeId}_svg`,
8
7
  canvasContainer: (codeId: string) => `${codeId}_canvas`
9
- }
10
-
11
- const BRYTHON_NOTIFICATION_EVENT = 'bry_notify'
12
- const CLOSE_TURTLE_MODAL_EVENT = 'close_turtle_modal'
13
- const TURTLE_IMPORTS_TESTER = /(^from turtle import)|(^import turtle)/m
14
- const TURTLE3D_IMPORTS_TESTER = /(^from turtle3d import)|(^import turtle3d)/m
15
- const GRID_IMPORTS_TESTER = /(^from grid import)|(^import grid)/m
16
- const GRAPHICS_OUTPUT_TESTER = /^(SETUP_)?GRAPHICS_OUTPUT\s*=\s*(True|1)/m
17
- const CANVAS_OUTPUT_TESTER = /^(SETUP_)?CANVAS_OUTPUT\s*=\s*(True|1)/m
8
+ };
18
9
 
10
+ const BRYTHON_NOTIFICATION_EVENT = 'bry_notify';
11
+ const CLOSE_TURTLE_MODAL_EVENT = 'close_turtle_modal';
12
+ const TURTLE_IMPORTS_TESTER = /(^from turtle import)|(^import turtle)/m;
13
+ const TURTLE3D_IMPORTS_TESTER = /(^from turtle3d import)|(^import turtle3d)/m;
14
+ const GRID_IMPORTS_TESTER = /(^from grid import)|(^import grid)/m;
15
+ const GRAPHICS_OUTPUT_TESTER = /^(SETUP_)?GRAPHICS_OUTPUT\s*=\s*(True|1)/m;
16
+ const CANVAS_OUTPUT_TESTER = /^(SETUP_)?CANVAS_OUTPUT\s*=\s*(True|1)/m;
19
17
 
20
18
  export {
21
19
  DOM_ELEMENT_IDS,
@@ -26,4 +24,4 @@ export {
26
24
  CANVAS_OUTPUT_TESTER,
27
25
  GRID_IMPORTS_TESTER,
28
26
  TURTLE3D_IMPORTS_TESTER
29
- }
27
+ };
@@ -0,0 +1,2 @@
1
+ export { useStore as useStore } from '@theme/CodeEditor/hooks/useStore';
2
+ export { useScript as useScript } from '@theme/CodeEditor/hooks/useScript';
@@ -0,0 +1,9 @@
1
+ import { Selector, type Script, type Document } from '@theme/CodeEditor/WithScript/Types';
2
+ import { useCallback, useSyncExternalStore } from 'react';
3
+
4
+ export const useScript = <T extends keyof Script>(store: Document, selector: T): Script[T] => {
5
+ return useSyncExternalStore(
6
+ store.subscribe,
7
+ useCallback(() => store.getState()[selector], [store, selector])
8
+ );
9
+ };
@@ -0,0 +1,15 @@
1
+ import { Context } from '@theme/CodeEditor/WithScript/ScriptContext';
2
+ import { type Document } from '@theme/CodeEditor/WithScript/Types';
3
+ import { ReactContextError } from '@docusaurus/theme-common';
4
+ import { useContext } from 'react';
5
+
6
+ export function useStore(): Document {
7
+ const context = useContext(Context);
8
+ if (context === null) {
9
+ throw new ReactContextError(
10
+ 'ScriptContextProvider',
11
+ 'The Component must be a child of the ScriptContextProvider component'
12
+ );
13
+ }
14
+ return context;
15
+ }