diff --git a/packages/noodl-editor/src/editor/index.ts b/packages/noodl-editor/src/editor/index.ts index c3ab7b6..ab6572f 100644 --- a/packages/noodl-editor/src/editor/index.ts +++ b/packages/noodl-editor/src/editor/index.ts @@ -1,7 +1,7 @@ import * as remote from '@electron/remote'; import { ipcRenderer } from 'electron'; import React from 'react'; -import ReactDOM from 'react-dom'; +import { createRoot } from 'react-dom/client'; import './process-setup'; @@ -83,5 +83,5 @@ window.addEventListener('DOMContentLoaded', () => { // Create the main element const rootElement = document.getElementById('root'); - ReactDOM.render(React.createElement(Router, { uri: remote.process.env.noodlURI }), rootElement); + createRoot(rootElement).render(React.createElement(Router, { uri: remote.process.env.noodlURI })); }); diff --git a/packages/noodl-editor/src/editor/src/hooks/useInterval.ts b/packages/noodl-editor/src/editor/src/hooks/useInterval.ts index 7155caf..9826f39 100644 --- a/packages/noodl-editor/src/editor/src/hooks/useInterval.ts +++ b/packages/noodl-editor/src/editor/src/hooks/useInterval.ts @@ -3,7 +3,7 @@ import { useEffect, useRef } from 'react'; type IntervalCallback = () => Promise; export function useInterval(callback: IntervalCallback, delay: number) { - const savedCallback = useRef(); + const savedCallback = useRef(callback); // Remember the latest callback. useEffect(() => { diff --git a/packages/noodl-editor/src/editor/src/router.tsx b/packages/noodl-editor/src/editor/src/router.tsx index a86ff7c..de254a1 100644 --- a/packages/noodl-editor/src/editor/src/router.tsx +++ b/packages/noodl-editor/src/editor/src/router.tsx @@ -1,6 +1,6 @@ import { ipcRenderer } from 'electron'; import React from 'react'; -import ReactDOM from 'react-dom'; +import { createRoot } from 'react-dom/client'; import { EventDispatcher } from '../../shared/utils/EventDispatcher'; import LessonTemplatesModel from './models/lessontemplatesmodel'; @@ -24,11 +24,11 @@ function createToastLayer() { toastLayer.classList.add('toast-layer'); $('body').append(toastLayer); - ReactDOM.render(React.createElement(ToastLayerContainer), toastLayer); + createRoot(toastLayer).render(React.createElement(ToastLayerContainer)); if (import.meta.webpackHot) { import.meta.webpackHot.accept('./views/ToastLayer', () => { - ReactDOM.render(React.createElement(ToastLayerContainer), toastLayer); + createRoot(toastLayer).render(React.createElement(ToastLayerContainer)); }); } } @@ -47,11 +47,11 @@ function createDialogLayer() { dialogLayer.classList.add('dialog-layer'); $('body').append(dialogLayer); - ReactDOM.render(React.createElement(DialogLayerContainer), dialogLayer); + createRoot(dialogLayer).render(React.createElement(DialogLayerContainer)); if (import.meta.webpackHot) { import.meta.webpackHot.accept('./views/DialogLayer', () => { - ReactDOM.render(React.createElement(DialogLayerContainer), dialogLayer); + createRoot(dialogLayer).render(React.createElement(DialogLayerContainer)); }); } } diff --git a/packages/noodl-editor/src/editor/src/views/Clippy/Clippy.tsx b/packages/noodl-editor/src/editor/src/views/Clippy/Clippy.tsx index 43721b5..7e9902e 100644 --- a/packages/noodl-editor/src/editor/src/views/Clippy/Clippy.tsx +++ b/packages/noodl-editor/src/editor/src/views/Clippy/Clippy.tsx @@ -51,7 +51,7 @@ export default function Clippy() { const firstInputRef = useRef(null); const secondInputRef = useRef(null); const secondTextAreaRef = useRef(null); - const ref = useRef(); + const ref = useRef(undefined); const [hasApiKey, setHasApiKey] = useState(false); const aiAssistantModel = useModernModel(AiAssistantModel.instance); const nodeGraphContext = useNodeGraphContext(); diff --git a/packages/noodl-editor/src/editor/src/views/ShowContextMenuInPopup.tsx b/packages/noodl-editor/src/editor/src/views/ShowContextMenuInPopup.tsx index 45df1d9..c2dc7bc 100644 --- a/packages/noodl-editor/src/editor/src/views/ShowContextMenuInPopup.tsx +++ b/packages/noodl-editor/src/editor/src/views/ShowContextMenuInPopup.tsx @@ -1,6 +1,6 @@ import { getCurrentWindow, screen } from '@electron/remote'; import React from 'react'; -import ReactDOM from 'react-dom'; +import { createRoot } from 'react-dom/client'; import { DialogRenderDirection } from '@noodl-core-ui/components/layout/BaseDialog'; import { MenuDialog, MenuDialogItem, MenuDialogWidth } from '@noodl-core-ui/components/popups/MenuDialog'; @@ -23,6 +23,7 @@ export function showContextMenuInPopup({ const container = document.createElement('div'); const screenPoint = screen.getCursorScreenPoint(); const [winX, winY] = getCurrentWindow().getPosition(); + const root = createRoot(container) const popout = PopupLayer.instance.showPopout({ content: { el: $(container) }, @@ -33,11 +34,11 @@ export function showContextMenuInPopup({ }, position: 'top', onClose: () => { - ReactDOM.unmountComponentAtNode(container); + root.unmount(); } }); - ReactDOM.render( + root.render( , - container - ); + />); } diff --git a/packages/noodl-editor/src/editor/src/views/commentlayer.ts b/packages/noodl-editor/src/editor/src/views/commentlayer.ts index fceaea0..6d5a16b 100644 --- a/packages/noodl-editor/src/editor/src/views/commentlayer.ts +++ b/packages/noodl-editor/src/editor/src/views/commentlayer.ts @@ -1,6 +1,6 @@ import _ from 'underscore'; import React from 'react'; -import ReactDOM from 'react-dom'; +import { createRoot, Root } from 'react-dom/client'; import { Comment, CommentsModel } from '@noodl-models/commentsmodel'; import KeyboardHandler from '@noodl-utils/keyboardhandler'; @@ -36,6 +36,8 @@ export default class CommentLayer { backgroundDiv: HTMLDivElement; activeCommentId: string; foregroundDiv: HTMLDivElement; + backgroundRoot: Root; + foregroundRoot: Root; constructor(nodegraphEditor) { this.nodegraphEditor = nodegraphEditor; @@ -140,14 +142,16 @@ export default class CommentLayer { return; } - ReactDOM.render(React.createElement(CommentLayerView.Background, this.props), this.backgroundDiv); - ReactDOM.render(React.createElement(CommentLayerView.Foreground, this.props), this.foregroundDiv); + this.backgroundRoot = createRoot(this.backgroundDiv); + this.backgroundRoot.render(React.createElement(CommentLayerView.Background, this.props)); + this.foregroundRoot = createRoot(this.foregroundDiv); + this.foregroundRoot.render(React.createElement(CommentLayerView.Foreground, this.props)); } renderTo(backgroundDiv, foregroundDiv) { if (this.backgroundDiv) { - ReactDOM.unmountComponentAtNode(this.backgroundDiv); - ReactDOM.unmountComponentAtNode(this.foregroundDiv); + this.backgroundRoot.unmount(); + this.foregroundRoot.unmount(); } this.backgroundDiv = backgroundDiv; @@ -297,8 +301,8 @@ export default class CommentLayer { } dispose() { - ReactDOM.unmountComponentAtNode(this.foregroundDiv); - ReactDOM.unmountComponentAtNode(this.backgroundDiv); + this.foregroundRoot.unmount(); + this.backgroundRoot.unmount(); //hack to remove all event listeners without having to keep track of them const newForegroundDiv = this.foregroundDiv.cloneNode(true); diff --git a/packages/noodl-editor/src/editor/src/views/nodegrapheditor.ts b/packages/noodl-editor/src/editor/src/views/nodegrapheditor.ts index 690005d..c839780 100644 --- a/packages/noodl-editor/src/editor/src/views/nodegrapheditor.ts +++ b/packages/noodl-editor/src/editor/src/views/nodegrapheditor.ts @@ -1,7 +1,7 @@ import { clipboard, ipcRenderer } from 'electron'; import _ from 'underscore'; import React from 'react'; -import ReactDOM from 'react-dom'; +import { createRoot, Root } from 'react-dom/client'; import { NodeGraphColors } from '@noodl-constants/NodeGraphColors'; import { AiAssistantEvent, AiAssistantModel } from '@noodl-models/AiAssistant/AiAssistantModel'; @@ -227,6 +227,8 @@ export class NodeGraphEditor extends View { nodesIdsAnimating: string[]; isPlayingNodeAnimations: boolean; + toolbarRoots: Root[]; + constructor(args) { super(); @@ -1418,8 +1420,8 @@ export class NodeGraphEditor extends View { } updateTitle() { - const root = this.el[0].querySelector('.nodegraph-component-trail-root'); - + const rootElem = this.el[0].querySelector('.nodegraph-component-trail-root'); + const root = createRoot(rootElem); if (this.activeComponent) { const fullName = this.activeComponent.fullName; const nameParts = fullName.split('/'); @@ -1464,9 +1466,9 @@ export class NodeGraphEditor extends View { canNavigateForward: this.navigationHistory.canNavigateForward }; - ReactDOM.render(React.createElement(NodeGraphComponentTrail, props), root); + root.render(React.createElement(NodeGraphComponentTrail, props)); } else { - ReactDOM.unmountComponentAtNode(root); + root.unmount(); } } @@ -1761,10 +1763,10 @@ export class NodeGraphEditor extends View { // @ts-expect-error toProps.sourcePort = fromPort; toProps.disabled = false; - ReactDOM.render(React.createElement(ConnectionPopup, toProps), toDiv); + createRoot(toDiv).render(React.createElement(ConnectionPopup, toProps)); fromProps.disabled = true; - ReactDOM.render(React.createElement(ConnectionPopup, fromProps), fromDiv); + createRoot(fromDiv).render(React.createElement(ConnectionPopup, fromProps)); fromNode.borderHighlighted = false; toNode.borderHighlighted = true; @@ -1772,7 +1774,8 @@ export class NodeGraphEditor extends View { } }; const fromDiv = document.createElement('div'); - ReactDOM.render(React.createElement(ConnectionPopup, fromProps), fromDiv); + const root = createRoot(fromDiv); + root.render(React.createElement(ConnectionPopup, fromProps)); const fromPosition = toNode.global.x > fromNodeXPos ? 'left' : 'right'; @@ -1790,7 +1793,7 @@ export class NodeGraphEditor extends View { y: (fromNode.global.y + panAndScale.y) * panAndScale.scale + tl[1] + 20 * panAndScale.scale }, onClose: () => { - ReactDOM.unmountComponentAtNode(fromDiv); + root.unmount(); ipcRenderer.send('viewer-show'); } }); @@ -1824,10 +1827,10 @@ export class NodeGraphEditor extends View { // @ts-expect-error toProps.sourcePort = undefined; toProps.disabled = true; - ReactDOM.render(React.createElement(ConnectionPopup, toProps), toDiv); + createRoot(toDiv).render(React.createElement(ConnectionPopup, toProps)); fromProps.disabled = false; - ReactDOM.render(React.createElement(ConnectionPopup, fromProps), fromDiv); + createRoot(fromDiv).render(React.createElement(ConnectionPopup, fromProps)); fromNode.borderHighlighted = true; toNode.borderHighlighted = false; @@ -1836,7 +1839,7 @@ export class NodeGraphEditor extends View { } }; const toDiv = document.createElement('div'); - ReactDOM.render(React.createElement(ConnectionPopup, toProps), toDiv); + createRoot(toDiv).render(React.createElement(ConnectionPopup, toProps)); const toPosition = fromNodeXPos >= toNode.global.x ? 'left' : 'right'; const toPopout = PopupLayer.instance.showPopout({ @@ -1851,7 +1854,7 @@ export class NodeGraphEditor extends View { y: (toNode.global.y + panAndScale.y) * panAndScale.scale + tl[1] + 20 * panAndScale.scale }, onClose: () => { - ReactDOM.unmountComponentAtNode(toDiv); + root.unmount(); this.clearSelection(); this.repaint(); } @@ -2203,20 +2206,23 @@ export class NodeGraphEditor extends View { div.style.position = 'absolute'; div.style.left = pos.x + 'px'; div.style.top = pos.y + 'px'; - - ReactDOM.render( + const root = createRoot(div); + this.toolbarRoots.push(root); + root.render( React.createElement(PopupToolbar, { menuItems, contextMenuItems: this.getContextMenuActions() - } as PopupToolbarProps), - div + } as PopupToolbarProps) ); } hideNodeToolbar() { + for (const root of this.toolbarRoots) { + root.unmount(); + } + this.toolbarRoots = []; const toolbars = this.domElementContainer.querySelectorAll('.nodegraph-node-toolbar'); for (const toolbar of toolbars) { - ReactDOM.unmountComponentAtNode(toolbar); this.domElementContainer.removeChild(toolbar); } } diff --git a/packages/noodl-editor/src/editor/src/views/nodegrapheditor/InspectJSONView/InspectPopup.tsx b/packages/noodl-editor/src/editor/src/views/nodegrapheditor/InspectJSONView/InspectPopup.tsx index 4a36ca8..3f555b2 100644 --- a/packages/noodl-editor/src/editor/src/views/nodegrapheditor/InspectJSONView/InspectPopup.tsx +++ b/packages/noodl-editor/src/editor/src/views/nodegrapheditor/InspectJSONView/InspectPopup.tsx @@ -1,6 +1,6 @@ import classNames from 'classnames'; import React from 'react'; -import ReactJson from 'react-json-view'; +import ReactJson from '@microlink/react-json-view'; import { ProjectModel } from '@noodl-models/projectmodel'; diff --git a/packages/noodl-editor/src/editor/src/views/panels/VersionControlPanel/VersionControlPanel.tsx b/packages/noodl-editor/src/editor/src/views/panels/VersionControlPanel/VersionControlPanel.tsx index 9724bb6..bad2e03 100644 --- a/packages/noodl-editor/src/editor/src/views/panels/VersionControlPanel/VersionControlPanel.tsx +++ b/packages/noodl-editor/src/editor/src/views/panels/VersionControlPanel/VersionControlPanel.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useRef, useState } from 'react'; -import ReactDOM from 'react-dom'; +import { createRoot } from 'react-dom/client'; import { Git } from '@noodl/git'; import { platform } from '@noodl/platform'; @@ -126,8 +126,8 @@ function BaseVersionControlPanel() { function openGitSettingsPopout() { const popoutDiv = document.createElement('div'); - - ReactDOM.render(React.createElement(GitProviderPopout, { git }), popoutDiv); + const root = createRoot(popoutDiv); + root.render(React.createElement(GitProviderPopout, { git })); //the timeout is needed to solve a bug when the popout us opened from the git status button //it causes timing issues between native events and react where the popout is instantly closed @@ -138,7 +138,7 @@ function BaseVersionControlPanel() { position: 'right', disableDynamicPositioning: true, onClose: () => { - ReactDOM.unmountComponentAtNode(popoutDiv); + root.unmount(); fetch.fetchRemote(); } }); diff --git a/packages/noodl-editor/src/editor/src/views/panels/propertyeditor/components/AiChat/AiChat.tsx b/packages/noodl-editor/src/editor/src/views/panels/propertyeditor/components/AiChat/AiChat.tsx index 47f8e74..5711d29 100644 --- a/packages/noodl-editor/src/editor/src/views/panels/propertyeditor/components/AiChat/AiChat.tsx +++ b/packages/noodl-editor/src/editor/src/views/panels/propertyeditor/components/AiChat/AiChat.tsx @@ -1,7 +1,7 @@ import { useModernModel } from '@noodl-hooks/useModel'; import { OpenAiStore } from '@noodl-store/AiAssistantStore'; import React, { useEffect, useMemo, useRef, useState } from 'react'; -import ReactDOM from 'react-dom'; +import { createRoot } from 'react-dom/client'; import { AiAssistantModel } from '@noodl-models/AiAssistant/AiAssistantModel'; import { AiCopilotContext } from '@noodl-models/AiAssistant/AiCopilotContext'; @@ -332,7 +332,8 @@ function AiMessageFunctionNodeAffix({ context, onUpdated }: AiMessageFunctionNod }; const popoutDiv = document.createElement('div'); - ReactDOM.render(React.createElement(CodeEditor, props), popoutDiv); + const root = createRoot(popoutDiv); + root.render(React.createElement(CodeEditor, props)); const popout = PopupLayer.instance.showPopout({ content: { el: [popoutDiv] }, diff --git a/packages/noodl-editor/src/editor/src/whats-new.ts b/packages/noodl-editor/src/editor/src/whats-new.ts index 070363e..0fff969 100644 --- a/packages/noodl-editor/src/editor/src/whats-new.ts +++ b/packages/noodl-editor/src/editor/src/whats-new.ts @@ -1,11 +1,12 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; import { ipcRenderer } from 'electron'; +import React from 'react'; +import { createRoot } from 'react-dom/client'; import { LocalStorageKey } from '@noodl-constants/LocalStorageKey'; import getDocsEndpoint from '@noodl-utils/getDocsEndpoint'; -import PopupLayer from './views/popuplayer'; + import { NewsModal } from './views/NewsModal'; +import PopupLayer from './views/popuplayer'; /** * Display latest whats-new-post if the user hasn't seen one after it was last published @@ -34,12 +35,11 @@ export async function whatsnewRender() { modalContainer.classList.add('popup-layer-react-modal'); PopupLayer.instance.el.find('.popup-layer-modal').before(modalContainer); - ReactDOM.render( + createRoot(modalContainer).render( React.createElement(NewsModal, { content: latestChangelogPost.content_html, onFinished: () => ipcRenderer.send('viewer-show') - }), - modalContainer + }) ); localStorage.setItem(LocalStorageKey.lastSeenChangelogDate, latestChangelogDate.toString());