@basthon/console-react 0.5.8 → 0.6.8

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.
@@ -1,10 +1,14 @@
1
- import { FC, PropsWithChildren } from "react";
1
+ import { FC, PropsWithChildren, type ReactNode } from "react";
2
2
  import { type BasthonContextProps } from "@basthon/gui-react";
3
3
  import { type Theme } from "../state-manager";
4
+ import { type PanelTitles } from "./main-container";
4
5
  interface InnerAppProps {
5
6
  initFromStorage?: boolean;
7
+ storedStates?: string[];
6
8
  forceTheme?: Theme;
7
9
  compactHeader?: boolean;
10
+ header?: ReactNode;
11
+ panelTitles?: PanelTitles;
8
12
  onChangeEditor?: () => void;
9
13
  }
10
14
  interface AppProps extends BasthonContextProps, InnerAppProps {
@@ -13,4 +17,5 @@ interface AppProps extends BasthonContextProps, InnerAppProps {
13
17
  * Main app setting up state manager, header, main container and footer.
14
18
  */
15
19
  declare const ConsoleApp: FC<PropsWithChildren<AppProps>>;
20
+ export { type AppProps };
16
21
  export default ConsoleApp;
@@ -5,36 +5,42 @@ import GUI from "../gui/gui";
5
5
  import { useGUI } from "../hooks/gui";
6
6
  import stateManager from "../state-manager";
7
7
  import { DispatchContext } from "../hooks/dispatch";
8
+ import { StateContext } from "../hooks/state";
8
9
  import { Header } from "./header";
9
10
  import { MainContainer } from "./main-container";
10
11
  import { Footer } from "./footer";
11
12
  import * as styles from "./styles.module.scss";
12
- const InnerApp = ({ initFromStorage, forceTheme, compactHeader, onChangeEditor, children, }) => {
13
+ const InnerApp = ({ initFromStorage, storedStates, forceTheme, compactHeader, header, panelTitles, onChangeEditor, children, }) => {
13
14
  const gui = useGUI();
14
15
  // state
15
- const [state, dispatch] = stateManager.useReducer(gui.language, initFromStorage ?? true);
16
+ const [state, dispatch] = stateManager.useReducer(gui.language, initFromStorage ?? true, storedStates);
16
17
  const language = stateManager.language(state);
17
18
  const theme = forceTheme != null ? forceTheme : stateManager.theme(state);
18
19
  // set data-theme for basthon-loader footer
19
20
  // FIXME: remove this when basthon-loader is a plain react component
20
21
  useEffect(() => {
22
+ gui.setTheme(theme);
21
23
  document.body.setAttribute("data-theme", theme);
22
24
  return () => {
23
25
  document.body.removeAttribute("data-theme");
24
26
  };
25
27
  }, [theme]);
26
28
  // build component
27
- return (_jsxs(DispatchContext.Provider, { value: dispatch, children: [children, _jsxs("div", { className: styles.root, children: [_jsx(Header, { language: language, theme: theme, compact: compactHeader }), stateManager.ready(state) && (_jsx(MainContainer, { language: language, theme: theme, viewMode: stateManager.viewMode(state), rightPanel: stateManager.rightPanel(state), graphicsDirty: stateManager.graphicsDirty(state), editorContent: stateManager.editorContent(state), onChangeEditor: onChangeEditor, compactHeader: compactHeader })), _jsx(Footer, { theme: theme })] })] }));
29
+ return (_jsx(DispatchContext.Provider, { value: dispatch, children: _jsxs(StateContext.Provider, { value: { ...state, theme }, children: [children, _jsxs("div", { className: styles.root, children: [header !== undefined ? (header) : (_jsx(Header, { language: language, theme: theme, compact: compactHeader })), stateManager.ready(state) && (_jsx(MainContainer, { language: language, theme: theme, viewMode: stateManager.viewMode(state), rightPanel: stateManager.rightPanel(state), graphicsDirty: stateManager.graphicsDirty(state), editorContent: stateManager.editorContent(state), onChangeEditor: onChangeEditor, compactHeader: compactHeader, panelTitles: panelTitles })), _jsx(Footer, { theme: theme })] })] }) }));
28
30
  };
29
31
  /**
30
32
  * Main app setting up state manager, header, main container and footer.
31
33
  */
32
- const ConsoleApp = ({ initFromStorage, forceTheme, compactHeader, onChangeEditor, children, ...props }) => {
33
- props.GUI = GUI;
34
+ const ConsoleApp = ({ initFromStorage, storedStates, forceTheme, compactHeader, header, panelTitles, onChangeEditor, children, ...props }) => {
35
+ // custom GUI classes should extend the console's GUI
36
+ props.GUI ?? (props.GUI = GUI);
34
37
  const innerProps = {
35
38
  initFromStorage,
39
+ storedStates,
36
40
  forceTheme,
37
41
  compactHeader,
42
+ header,
43
+ panelTitles,
38
44
  onChangeEditor,
39
45
  };
40
46
  return (_jsx(BasthonContext, { ...props, children: _jsx(InnerApp, { ...innerProps, children: children }) }));
@@ -1,9 +1,12 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import * as styles from "./styles.module.scss";
3
- const Button = ({ children, titled, blink, ...props }) => {
4
- let className = styles.btn;
3
+ const Button = ({ children, titled, blink, className: extraClassName, ...props }) => {
4
+ const classNames = [styles.btn];
5
5
  if (blink === true)
6
- className = [className, styles["btn-eye-catcher"]].join(" ");
6
+ classNames.push(styles["btn-eye-catcher"]);
7
+ if (extraClassName != null)
8
+ classNames.push(extraClassName);
9
+ const className = classNames.join(" ");
7
10
  return (_jsx("button", { className: className, "data-titled": titled, ...props, children: children }));
8
11
  };
9
12
  export { Button };
@@ -1,5 +1,10 @@
1
- import { FC } from "react";
1
+ import { FC, type ReactNode } from "react";
2
2
  import { type Language, type Theme, type ViewMode, type RightPanel } from "../state-manager";
3
+ interface PanelTitles {
4
+ editor?: ReactNode;
5
+ terminal?: ReactNode;
6
+ graphics?: ReactNode;
7
+ }
3
8
  interface MainContainerProps {
4
9
  language: Language;
5
10
  theme: Theme;
@@ -9,6 +14,7 @@ interface MainContainerProps {
9
14
  editorContent: string;
10
15
  onChangeEditor?: () => void;
11
16
  compactHeader?: boolean;
17
+ panelTitles?: PanelTitles;
12
18
  }
13
19
  declare const MainContainer: FC<MainContainerProps>;
14
- export { MainContainer };
20
+ export { MainContainer, type PanelTitles };
@@ -11,13 +11,18 @@ import { faCogs, faFolderOpen, faDownload, faShareAltSquare, faEyeSlash, faUndoA
11
11
  const MainContainer = (props) => {
12
12
  const gui = useGUI();
13
13
  const dispatch = useDispatch();
14
- return (_jsxs("div", { className: styles.container, "data-theme": props.theme, "data-compact-header": props.compactHeader, children: [_jsxs("div", { className: styles["left-panel"], "data-view-mode": props.viewMode, children: [_jsx(Editor, { language: props.language, theme: props.theme, content: props.editorContent, onSetContent: (content) => dispatch({ type: "set-editor-content", payload: content }), onChange: props.onChangeEditor }), _jsxs("div", { className: styles["buttons-container"], children: [_jsxs(Button, { titled: true, title: "Ex\u00E9cuter le script", onClick: () => gui.runScript(), children: [_jsx(FontAwesomeIcon, { icon: faCogs, className: styles["btn-logo"] }), "Ex\u00E9cuter"] }), _jsx(Button, { title: "Ouvrir un script, charger un module ou un fichier", onClick: () => gui.openFile(), children: _jsx(FontAwesomeIcon, { icon: faFolderOpen, className: styles["btn-logo"] }) }), _jsx(Button, { title: "T\u00E9l\u00E9charger le script", onClick: () => gui.download(), children: _jsx(FontAwesomeIcon, { icon: faDownload, className: styles["btn-logo"] }) }), _jsx(Button, { title: "Partager ce code", onClick: () => gui.share(), children: _jsx(FontAwesomeIcon, { icon: faShareAltSquare, className: styles["btn-logo"] }) }), props.viewMode === "script-only" && (_jsx(Button, { title: "Afficher seulement la console", onClick: () => dispatch({
14
+ const titles = props.panelTitles;
15
+ const rightTitle = props.rightPanel === "graphics" ? titles?.graphics : titles?.terminal;
16
+ return (_jsxs("div", { className: styles.container, "data-theme": props.theme, "data-compact-header": props.compactHeader, "data-titled": titles != null, children: [_jsxs("div", { className: styles["left-panel"], "data-view-mode": props.viewMode, children: [titles?.editor != null && (_jsx("div", { className: styles["panel-title"], children: titles.editor })), _jsx(Editor, { id: "editor", language: props.language, theme: props.theme, content: props.editorContent, onSetContent: (content) => dispatch({ type: "set-editor-content", payload: content }), onChange: () => {
17
+ gui.editorChanged();
18
+ props.onChangeEditor?.();
19
+ } }), _jsxs("div", { className: styles["buttons-container"], children: [_jsxs(Button, { titled: true, id: "run", title: "Ex\u00E9cuter le script", onClick: () => gui.runScript(), children: [_jsx(FontAwesomeIcon, { icon: faCogs, className: styles["btn-logo"] }), "Ex\u00E9cuter"] }), _jsx(Button, { id: "open", title: "Ouvrir un script, charger un module ou un fichier", onClick: () => gui.openFile(), children: _jsx(FontAwesomeIcon, { icon: faFolderOpen, className: styles["btn-logo"] }) }), _jsx(Button, { id: "download", title: "T\u00E9l\u00E9charger le script", onClick: () => gui.download(), children: _jsx(FontAwesomeIcon, { icon: faDownload, className: styles["btn-logo"] }) }), _jsx(Button, { id: "share", title: "Partager ce code", onClick: () => gui.share(), children: _jsx(FontAwesomeIcon, { icon: faShareAltSquare, className: styles["btn-logo"] }) }), props.viewMode === "script-only" && (_jsx(Button, { id: "hide-editor", title: "Afficher seulement la console", onClick: () => dispatch({
15
20
  type: "set-view-mode",
16
21
  payload: "terminal-only",
17
- }), children: _jsx(FontAwesomeIcon, { icon: faEyeSlash, className: styles["btn-logo"] }) })), _jsx(Button, { title: "Revenir \u00E0 une version pr\u00E9c\u00E9dente du script", onClick: () => gui.rollback(), children: _jsx(FontAwesomeIcon, { icon: faUndoAlt, className: styles["btn-logo"] }) })] })] }), _jsxs("div", { className: styles["right-panel"], "data-view-mode": props.viewMode, children: [_jsx(Terminal, { "data-right-panel": props.rightPanel }), _jsx(Graphics, { "data-right-panel": props.rightPanel, onContentUpdate: () => dispatch({ type: "graphics-dirty" }) }), _jsxs("div", { className: styles["buttons-container"], children: [_jsx(Button, { title: "Red\u00E9marrer le noyau", onClick: () => gui.restart(), children: _jsx(FontAwesomeIcon, { icon: faBroom, className: styles["btn-logo"] }) }), _jsx(Button, { title: "Afficher la console", onClick: () => dispatch({ type: "set-right-panel", payload: "terminal" }), children: _jsx(FontAwesomeIcon, { icon: faTerminal, className: styles["btn-logo"] }) }), _jsx(Button, { title: "Afficher la vue graphique", onClick: () => dispatch({ type: "set-right-panel", payload: "graphics" }), blink: props.graphicsDirty, children: _jsx(FontAwesomeIcon, { icon: faImage, className: styles["btn-logo"] }) }), _jsx(Button, { title: "Changer le th\u00E8me (sombre/lumineux)", onClick: () => dispatch({ type: "switch-theme" }), children: _jsx(FontAwesomeIcon, { icon: faAdjust, className: styles["btn-logo"] }) }), ["default", "reversed"].includes(props.viewMode) && (_jsxs(_Fragment, { children: [_jsx(Button, { title: "\u00C9changer l'\u00E9diteur et la console", onClick: () => dispatch({ type: "switch-view" }), children: _jsx(FontAwesomeIcon, { icon: faExchangeAlt, className: styles["btn-logo"] }) }), _jsx(Button, { title: "Afficher seulement l'\u00E9diteur", onClick: () => dispatch({
22
+ }), children: _jsx(FontAwesomeIcon, { icon: faEyeSlash, className: styles["btn-logo"] }) })), _jsx(Button, { id: "rollback", title: "Revenir \u00E0 une version pr\u00E9c\u00E9dente du script", onClick: () => gui.rollback(), children: _jsx(FontAwesomeIcon, { icon: faUndoAlt, className: styles["btn-logo"] }) })] })] }), _jsxs("div", { className: styles["right-panel"], "data-view-mode": props.viewMode, children: [rightTitle != null && (_jsx("div", { className: styles["panel-title"], children: rightTitle })), _jsx(Terminal, { id: "shell", "data-right-panel": props.rightPanel }), _jsx(Graphics, { id: "graphics", "data-right-panel": props.rightPanel, onContentUpdate: () => dispatch({ type: "graphics-dirty" }) }), _jsxs("div", { className: styles["buttons-container"], children: [_jsx(Button, { id: "raz", title: "Red\u00E9marrer le noyau", onClick: () => gui.restart(), children: _jsx(FontAwesomeIcon, { icon: faBroom, className: styles["btn-logo"] }) }), _jsx(Button, { id: "btn_shell", title: "Afficher la console", onClick: () => dispatch({ type: "set-right-panel", payload: "terminal" }), children: _jsx(FontAwesomeIcon, { icon: faTerminal, className: styles["btn-logo"] }) }), _jsx(Button, { id: "btn_graph_view", title: "Afficher la vue graphique", onClick: () => dispatch({ type: "set-right-panel", payload: "graphics" }), blink: props.graphicsDirty, children: _jsx(FontAwesomeIcon, { icon: faImage, className: styles["btn-logo"] }) }), _jsx(Button, { id: "darklight", title: "Changer le th\u00E8me (sombre/lumineux)", onClick: () => dispatch({ type: "switch-theme" }), children: _jsx(FontAwesomeIcon, { icon: faAdjust, className: styles["btn-logo"] }) }), ["default", "reversed"].includes(props.viewMode) && (_jsxs(_Fragment, { children: [_jsx(Button, { id: "switch", title: "\u00C9changer l'\u00E9diteur et la console", onClick: () => dispatch({ type: "switch-view" }), children: _jsx(FontAwesomeIcon, { icon: faExchangeAlt, className: styles["btn-logo"] }) }), _jsx(Button, { id: "hide-console", title: "Afficher seulement l'\u00E9diteur", onClick: () => dispatch({
18
23
  type: "set-view-mode",
19
24
  payload: "script-only",
20
- }), children: _jsx(FontAwesomeIcon, { icon: faEyeSlash, className: styles["btn-logo"] }) })] })), props.viewMode === "terminal-only" && (_jsx(Button, { title: "Afficher l'\u00E9diteur et la console", onClick: () => dispatch({
25
+ }), children: _jsx(FontAwesomeIcon, { icon: faEyeSlash, className: styles["btn-logo"] }) })] })), props.viewMode === "terminal-only" && (_jsx(Button, { id: "show-editor-console", title: "Afficher l'\u00E9diteur et la console", onClick: () => dispatch({
21
26
  type: "set-view-mode",
22
27
  payload: "default",
23
28
  }), children: _jsx(FontAwesomeIcon, { icon: faColumns, className: styles["btn-logo"] }) }))] })] })] }));
@@ -11,8 +11,8 @@
11
11
  --logo-height: 12vh;
12
12
  --term-height: 60vh;
13
13
  --term-font-size: 12pt;
14
- --term-font: "Monaco", "Menlo", "Ubuntu Mono", "Consolas", "source-code-pro",
15
- monospace;
14
+ --term-font:
15
+ "Monaco", "Menlo", "Ubuntu Mono", "Consolas", "source-code-pro", monospace;
16
16
  --term-script-color: var(--python-grey);
17
17
  --main-container-width: 90%;
18
18
  --main-container-height: 75vh;
@@ -114,6 +114,7 @@ textarea:focus {
114
114
  }
115
115
 
116
116
  .left-panel {
117
+ position: relative;
117
118
  width: var(--left-container-width);
118
119
  margin: auto;
119
120
  float: left;
@@ -151,6 +152,36 @@ textarea:focus {
151
152
  }
152
153
  }
153
154
 
155
+ .panel-title {
156
+ z-index: 100;
157
+ position: absolute;
158
+ top: -1.3vw;
159
+ left: 1.5vw;
160
+ margin: 0;
161
+ padding: 0vw 0.7vw 0.2vw 0.7vw;
162
+ color: #fff;
163
+ background-color: var(--python-blue);
164
+ border-radius: var(--corner-radius);
165
+ border: none;
166
+ font-size: var(--main-font-size);
167
+ user-select: none;
168
+ }
169
+
170
+ /* leave room for panel titles */
171
+ .container[data-titled="true"] {
172
+ margin-top: 1.3vw;
173
+
174
+ .editor {
175
+ box-sizing: border-box;
176
+ border-top: 1vw solid var(--term-bg-color);
177
+ }
178
+
179
+ .terminal {
180
+ box-sizing: border-box;
181
+ padding-top: 0.7vw;
182
+ }
183
+ }
184
+
154
185
  %darklighted {
155
186
  background-color: var(--term-bg-color);
156
187
  --background: var(--term-bg-color);
@@ -7,6 +7,7 @@ export default class Editor {
7
7
  private _kernel;
8
8
  private _editor;
9
9
  private _onSetContent?;
10
+ private _silent;
10
11
  constructor(element: HTMLElement, kernel: KernelBase, onSetContent?: (_: string) => void, onChange?: () => void);
11
12
  /**
12
13
  * Set Ace mode from language.
package/lib/gui/editor.js CHANGED
@@ -4,6 +4,8 @@ import ace from "./ace";
4
4
  */
5
5
  export default class Editor {
6
6
  constructor(element, kernel, onSetContent, onChange) {
7
+ // true while content is set programmatically (no onChange call)
8
+ this._silent = false;
7
9
  this._kernel = kernel;
8
10
  this._onSetContent = onSetContent;
9
11
  this._editor = ace.edit(element);
@@ -14,9 +16,12 @@ export default class Editor {
14
16
  highlightSelectedWord: true,
15
17
  fontSize: "12pt",
16
18
  });
17
- // connect onchange
19
+ // connect onchange (only for user changes)
18
20
  if (onChange != null)
19
- this._editor.session.on("change", () => onChange());
21
+ this._editor.session.on("change", () => {
22
+ if (!this._silent)
23
+ onChange();
24
+ });
20
25
  /* editor's completion */
21
26
  // removing all completers and keeping track of local completer
22
27
  const localCompleter = this._editor.completers[1]; // any hint to detect this dynamically?
@@ -85,14 +90,24 @@ export default class Editor {
85
90
  * Dispatch action to set content.
86
91
  */
87
92
  setContent(content) {
93
+ // set it directly since state may not change
94
+ // (user modifications are not dispatched)
95
+ this.setContentNoDispatch(content);
88
96
  this._onSetContent?.(content);
89
97
  }
90
98
  /**
91
99
  * Set Ace editor content.
92
100
  */
93
101
  setContentNoDispatch(content) {
94
- if (content != null)
102
+ if (content == null)
103
+ return;
104
+ this._silent = true;
105
+ try {
95
106
  this._editor.setValue(content, -1);
107
+ }
108
+ finally {
109
+ this._silent = false;
110
+ }
96
111
  }
97
112
  /**
98
113
  * Get editor's content
package/lib/gui/gui.d.ts CHANGED
@@ -8,6 +8,8 @@ export default class GUI extends GUIBase {
8
8
  private _editor?;
9
9
  private _term?;
10
10
  private _graphics?;
11
+ private _pendingContent?;
12
+ private _theme?;
11
13
  constructor(options: GUIOptions);
12
14
  /**
13
15
  * Set editor.
@@ -41,6 +43,14 @@ export default class GUI extends GUIBase {
41
43
  * Set editor's content.
42
44
  */
43
45
  setContent(content: string): void;
46
+ /**
47
+ * Set the current theme (called by the app on theme change).
48
+ */
49
+ setTheme(theme: "dark" | "light"): void;
50
+ /**
51
+ * Get the current theme.
52
+ */
53
+ theme(): Promise<"dark" | "light" | undefined>;
44
54
  /**
45
55
  * Notify the user with an error.
46
56
  */
@@ -65,6 +75,19 @@ export default class GUI extends GUIBase {
65
75
  */
66
76
  rollback(): Promise<void>;
67
77
  protected setupUI(options: any): Promise<void>;
78
+ /**
79
+ * Called when the editor's content is modified by the user.
80
+ */
81
+ editorChanged(): void;
82
+ /**
83
+ * Called on ctrl+s (download the script by default).
84
+ */
85
+ protected onSaveShortcut(): void;
86
+ /**
87
+ * Called before closing the page (backup by default).
88
+ * Return a string to ask the user for confirmation.
89
+ */
90
+ protected onBeforeUnload(): string | undefined;
68
91
  /**
69
92
  * Run the editor script in the terminal.
70
93
  */
package/lib/gui/gui.js CHANGED
@@ -22,6 +22,10 @@ export default class GUI extends GUIBase {
22
22
  */
23
23
  setEditor(editor) {
24
24
  this._editor = editor;
25
+ if (this._pendingContent != null) {
26
+ editor.setContent(this._pendingContent);
27
+ this._pendingContent = undefined;
28
+ }
25
29
  }
26
30
  /**
27
31
  * Set terminal.
@@ -58,13 +62,31 @@ export default class GUI extends GUIBase {
58
62
  * Get editor's content.
59
63
  */
60
64
  content() {
61
- return this._editor?.getContent();
65
+ if (this._editor == null)
66
+ return this._pendingContent;
67
+ return this._editor.getContent();
62
68
  }
63
69
  /**
64
70
  * Set editor's content.
65
71
  */
66
72
  setContent(content) {
67
- this._editor?.setContent(content);
73
+ // editor not yet available, delay
74
+ if (this._editor == null)
75
+ this._pendingContent = content;
76
+ else
77
+ this._editor.setContent(content);
78
+ }
79
+ /**
80
+ * Set the current theme (called by the app on theme change).
81
+ */
82
+ setTheme(theme) {
83
+ this._theme = theme;
84
+ }
85
+ /**
86
+ * Get the current theme.
87
+ */
88
+ async theme() {
89
+ return this._theme;
68
90
  }
69
91
  /**
70
92
  * Notify the user with an error.
@@ -135,13 +157,29 @@ export default class GUI extends GUIBase {
135
157
  if ((event.ctrlKey || event.metaKey) &&
136
158
  String.fromCharCode(event.which).toLowerCase() === "s") {
137
159
  event.preventDefault();
138
- this.download();
160
+ this.onSaveShortcut();
139
161
  }
140
162
  });
141
163
  /* backup before closing */
142
- window.onbeforeunload = () => {
143
- this.backup().catch(this.notifyError.bind(this));
144
- };
164
+ window.onbeforeunload = () => this.onBeforeUnload();
165
+ }
166
+ /**
167
+ * Called when the editor's content is modified by the user.
168
+ */
169
+ editorChanged() { }
170
+ /**
171
+ * Called on ctrl+s (download the script by default).
172
+ */
173
+ onSaveShortcut() {
174
+ this.download();
175
+ }
176
+ /**
177
+ * Called before closing the page (backup by default).
178
+ * Return a string to ask the user for confirmation.
179
+ */
180
+ onBeforeUnload() {
181
+ this.backup().catch(this.notifyError.bind(this));
182
+ return undefined;
145
183
  }
146
184
  /**
147
185
  * Run the editor script in the terminal.
@@ -0,0 +1,4 @@
1
+ import { type State } from "../state-manager";
2
+ declare const StateContext: import("react").Context<State | null>;
3
+ declare const useConsoleState: () => State;
4
+ export { StateContext, useConsoleState };
@@ -0,0 +1,4 @@
1
+ import { createContext, useContext } from "react";
2
+ const StateContext = createContext(null);
3
+ const useConsoleState = () => useContext(StateContext);
4
+ export { StateContext, useConsoleState };
package/lib/index.d.ts CHANGED
@@ -1,2 +1,9 @@
1
- import ConsoleApp from "./components/app";
2
- export { ConsoleApp };
1
+ import ConsoleApp, { type AppProps } from "./components/app";
2
+ import GUI from "./gui/gui";
3
+ import { useGUI } from "./hooks/gui";
4
+ import { useDispatch } from "./hooks/dispatch";
5
+ import { useConsoleState } from "./hooks/state";
6
+ import { Button } from "./components/button";
7
+ import { type PanelTitles } from "./components/main-container";
8
+ import { defaultStoredStates, type State, type Action, type Language, type Theme, type ViewMode, type RightPanel } from "./state-manager";
9
+ export { ConsoleApp, GUI, useGUI, useDispatch, useConsoleState, Button, defaultStoredStates, type AppProps, type PanelTitles, type State, type Action, type Language, type Theme, type ViewMode, type RightPanel, };
package/lib/index.js CHANGED
@@ -1,2 +1,8 @@
1
1
  import ConsoleApp from "./components/app";
2
- export { ConsoleApp };
2
+ import GUI from "./gui/gui";
3
+ import { useGUI } from "./hooks/gui";
4
+ import { useDispatch } from "./hooks/dispatch";
5
+ import { useConsoleState } from "./hooks/state";
6
+ import { Button } from "./components/button";
7
+ import { defaultStoredStates, } from "./state-manager";
8
+ export { ConsoleApp, GUI, useGUI, useDispatch, useConsoleState, Button, defaultStoredStates, };
@@ -11,14 +11,17 @@ interface State {
11
11
  rightPanel: RightPanel;
12
12
  graphicsDirty: boolean;
13
13
  editorContent: string;
14
+ storedStates: string[];
14
15
  }
15
16
  interface Action {
16
17
  type: "init" | "set-theme" | "switch-theme" | "set-view-mode" | "switch-view" | "set-right-panel" | "graphics-dirty" | "set-editor-content";
17
18
  payload?: Theme | ViewMode | RightPanel | State | string;
18
19
  }
20
+ declare const defaultStoredStates: string[];
19
21
  declare const stateManager: {
20
- useReducer: (language: Language, initFromStorage: boolean) => readonly [{
22
+ useReducer: (language: Language, initFromStorage: boolean, storedStates?: string[]) => readonly [{
21
23
  language: Language;
24
+ storedStates: string[];
22
25
  ready: boolean;
23
26
  theme: Theme;
24
27
  viewMode: ViewMode;
@@ -34,5 +37,6 @@ declare const stateManager: {
34
37
  graphicsDirty: (state: State) => boolean;
35
38
  editorContent: (state: State) => string;
36
39
  };
37
- export { type Dispatch, type Action, type Language, type Theme, type ViewMode, type RightPanel, };
40
+ export { type State, type Dispatch, type Action, type Language, type Theme, type ViewMode, type RightPanel, };
41
+ export { defaultStoredStates };
38
42
  export default stateManager;
@@ -2,6 +2,13 @@ import { useEffect } from "react";
2
2
  /* we use immer to fake mutable state */
3
3
  import { useImmerReducer } from "use-immer";
4
4
  import { Storage } from "@basthon/checkpoints";
5
+ /* states to store (by default) */
6
+ const defaultStoredStates = [
7
+ "theme",
8
+ "viewMode",
9
+ "rightPanel",
10
+ "editorContent",
11
+ ];
5
12
  /* this initial state */
6
13
  const initialState = {
7
14
  ready: false,
@@ -11,18 +18,17 @@ const initialState = {
11
18
  rightPanel: "terminal",
12
19
  graphicsDirty: false,
13
20
  editorContent: "",
21
+ storedStates: defaultStoredStates,
14
22
  };
15
23
  /* copy throught JSON, should be sufficient for what we do */
16
24
  function copy(obj) {
17
25
  return JSON.parse(JSON.stringify(obj));
18
26
  }
19
- /* states to store */
20
- const storedStates = ["theme", "viewMode", "rightPanel", "editorContent"];
21
27
  /* get state from storage */
22
- const stateFromStorage = async (language) => {
28
+ const stateFromStorage = async (language, storedStates) => {
23
29
  const storage = new Storage(`console.${language}.states`);
24
30
  await storage.ready();
25
- const state = { ...initialState, language };
31
+ const state = { ...initialState, language, storedStates };
26
32
  for (let key of storedStates) {
27
33
  const storageValue = await storage.get(key);
28
34
  if (storageValue != null)
@@ -34,7 +40,7 @@ const stateFromStorage = async (language) => {
34
40
  const stateToStorage = async (state) => {
35
41
  const storage = new Storage(`console.${state.language}.states`);
36
42
  await storage.ready();
37
- for (let key of storedStates)
43
+ for (let key of state.storedStates)
38
44
  await storage.set(key, state[key]);
39
45
  };
40
46
  /* where our global logic takes place */
@@ -78,17 +84,18 @@ const reducer = (state, action) => {
78
84
  };
79
85
  /* stateManager exposed as API */
80
86
  const stateManager = {
81
- useReducer: (language, initFromStorage) => {
87
+ useReducer: (language, initFromStorage, storedStates = defaultStoredStates) => {
82
88
  const [state, dispatch] = useImmerReducer(reducer, {
83
89
  ...initialState,
84
90
  language,
91
+ storedStates,
85
92
  });
86
93
  // dispatch init from storage
87
94
  useEffect(() => {
88
95
  (async () => {
89
96
  let payload = undefined;
90
97
  if (initFromStorage)
91
- payload = await stateFromStorage(language);
98
+ payload = await stateFromStorage(language, storedStates);
92
99
  dispatch({ type: "init", payload });
93
100
  })();
94
101
  }, []);
@@ -102,4 +109,5 @@ const stateManager = {
102
109
  graphicsDirty: (state) => state.graphicsDirty,
103
110
  editorContent: (state) => state.editorContent,
104
111
  };
112
+ export { defaultStoredStates };
105
113
  export default stateManager;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@basthon/console-react",
3
- "version": "0.5.8",
3
+ "version": "0.6.8",
4
4
  "description": "Basthon - Console React Component",
5
5
  "homepage": "https://basthon.fr",
6
6
  "bugs": {