diff --git a/newIDE/app/src/EventsFunctionsExtensionsLoader/EventsFunctionsExtensionsProvider.js b/newIDE/app/src/EventsFunctionsExtensionsLoader/EventsFunctionsExtensionsProvider.js index a1cec729e461..e03cd1ddf43b 100644 --- a/newIDE/app/src/EventsFunctionsExtensionsLoader/EventsFunctionsExtensionsProvider.js +++ b/newIDE/app/src/EventsFunctionsExtensionsLoader/EventsFunctionsExtensionsProvider.js @@ -30,58 +30,42 @@ type Props = {| eventsFunctionsExtensionOpener: ?EventsFunctionsExtensionOpener, |}; -type State = EventsFunctionsExtensionsState; - /** * Allow children components to request the loading (or unloading) of * the events functions extensions of the project. * Useful when dealing with events functions extensions (new extension created, * removed, pasted, installed, etc...). */ -export default class EventsFunctionsExtensionsProvider extends React.Component< - Props, - State -> { - _eventsFunctionCodeWriter: ?EventsFunctionCodeWriter = this.props.makeEventsFunctionCodeWriter( - { - onWriteFile: this._onWriteFile.bind(this), - } +export const EventsFunctionsExtensionsProvider = ({ + children, + i18n, + makeEventsFunctionCodeWriter, + eventsFunctionsExtensionWriter, + eventsFunctionsExtensionOpener, +}: Props) => { + const [ + eventsFunctionsExtensionsError, + setEventsFunctionsExtensionsError, + ] = React.useState(null); + const includeFileHashs = React.useRef<{ [string]: number }>({}); + const lastLoadPromise = React.useRef>(null); + + const onWriteFile = React.useCallback( + ({ includeFile, content }: IncludeFileContent) => { + includeFileHashs.current[includeFile] = xxhashjs + .h32(content, 0xabcd) + .toNumber(); + }, + [] + ); + + const eventsFunctionCodeWriter: ?EventsFunctionCodeWriter = React.useMemo( + () => makeEventsFunctionCodeWriter({ onWriteFile }), + [onWriteFile, makeEventsFunctionCodeWriter] ); - _includeFileHashs: { [string]: number } = {}; - _lastLoadPromise: ?Promise = null; - state = { - eventsFunctionsExtensionsError: null, - loadProjectEventsFunctionsExtensions: this._loadProjectEventsFunctionsExtensions.bind( - this - ), - unloadProjectEventsFunctionsExtensions: this._unloadProjectEventsFunctionsExtensions.bind( - this - ), - unloadProjectEventsFunctionsExtension: this._unloadProjectEventsFunctionsExtension.bind( - this - ), - reloadProjectEventsFunctionsExtensions: this._reloadProjectEventsFunctionsExtensions.bind( - this - ), - reloadProjectEventsFunctionsExtensionMetadata: this._reloadProjectEventsFunctionsExtensionMetadata.bind( - this - ), - ensureLoadFinished: this._ensureLoadFinished.bind(this), - getEventsFunctionsExtensionWriter: () => - this.props.eventsFunctionsExtensionWriter, - getEventsFunctionsExtensionOpener: () => - this.props.eventsFunctionsExtensionOpener, - getIncludeFileHashs: () => this._includeFileHashs, - }; - - _onWriteFile({ includeFile, content }: IncludeFileContent) { - this._includeFileHashs[includeFile] = xxhashjs - .h32(content, 0xabcd) - .toNumber(); - } - - _ensureLoadFinished(): Promise { - if (this._lastLoadPromise) { + + const ensureLoadFinished = React.useCallback((): Promise => { + if (lastLoadPromise.current) { console.info( 'Waiting on the events functions extensions to finish loading...' ); @@ -89,37 +73,61 @@ export default class EventsFunctionsExtensionsProvider extends React.Component< console.info('Events functions extensions are ready.'); } - return this._lastLoadPromise - ? this._lastLoadPromise.then(() => { + return lastLoadPromise.current + ? lastLoadPromise.current.then(() => { console.info('Events functions extensions finished loading.'); }) : Promise.resolve(); - } + }, []); + + const _loadProjectEventsFunctionsExtensions = React.useCallback( + (project: ?gdProject): Promise => { + if (!project || !eventsFunctionCodeWriter) return Promise.resolve(); + + const previousLastLoadPromise = + lastLoadPromise.current || Promise.resolve(); + + lastLoadPromise.current = previousLastLoadPromise + .then(() => + loadProjectEventsFunctionsExtensions( + project, + eventsFunctionCodeWriter, + i18n + ) + ) + .then(() => setEventsFunctionsExtensionsError(null)) + .catch((eventsFunctionsExtensionsError: Error) => { + setEventsFunctionsExtensionsError(eventsFunctionsExtensionsError); + showErrorBox({ + message: i18n._( + t`An error has occurred during functions generation. If GDevelop is installed, verify that nothing is preventing GDevelop from writing on disk. If you're running GDevelop online, verify your internet connection and refresh functions from the Project Manager.` + ), + rawError: eventsFunctionsExtensionsError, + errorId: 'events-functions-extensions-load-error', + }); + }) + .then(() => { + lastLoadPromise.current = null; + }); - _loadProjectEventsFunctionsExtensions(project: ?gdProject): Promise { - const { i18n } = this.props; - const eventsFunctionCodeWriter = this._eventsFunctionCodeWriter; - if (!project || !eventsFunctionCodeWriter) return Promise.resolve(); + return lastLoadPromise.current; + }, + [eventsFunctionCodeWriter, i18n] + ); - const lastLoadPromise = this._lastLoadPromise || Promise.resolve(); + const _reloadProjectEventsFunctionsExtensionMetadata = React.useCallback( + (project: ?gdProject, extension: gdEventsFunctionsExtension): void => { + if (!project || !eventsFunctionCodeWriter) return; - this._lastLoadPromise = lastLoadPromise - .then(() => - loadProjectEventsFunctionsExtensions( + try { + reloadProjectEventsFunctionsExtensionMetadata( project, + extension, eventsFunctionCodeWriter, i18n - ) - ) - .then(() => - this.setState({ - eventsFunctionsExtensionsError: null, - }) - ) - .catch((eventsFunctionsExtensionsError: Error) => { - this.setState({ - eventsFunctionsExtensionsError, - }); + ); + } catch (eventsFunctionsExtensionsError) { + setEventsFunctionsExtensionsError(eventsFunctionsExtensionsError); showErrorBox({ message: i18n._( t`An error has occurred during functions generation. If GDevelop is installed, verify that nothing is preventing GDevelop from writing on disk. If you're running GDevelop online, verify your internet connection and refresh functions from the Project Manager.` @@ -127,66 +135,67 @@ export default class EventsFunctionsExtensionsProvider extends React.Component< rawError: eventsFunctionsExtensionsError, errorId: 'events-functions-extensions-load-error', }); - }) - .then(() => { - this._lastLoadPromise = null; - }); - - return this._lastLoadPromise; - } - - _reloadProjectEventsFunctionsExtensionMetadata( - project: ?gdProject, - extension: gdEventsFunctionsExtension - ): void { - const { i18n } = this.props; - const eventsFunctionCodeWriter = this._eventsFunctionCodeWriter; - if (!project || !eventsFunctionCodeWriter) return; - - try { - reloadProjectEventsFunctionsExtensionMetadata( - project, - extension, - eventsFunctionCodeWriter, - i18n - ); - } catch (eventsFunctionsExtensionsError) { - this.setState({ - eventsFunctionsExtensionsError, - }); - showErrorBox({ - message: i18n._( - t`An error has occurred during functions generation. If GDevelop is installed, verify that nothing is preventing GDevelop from writing on disk. If you're running GDevelop online, verify your internet connection and refresh functions from the Project Manager.` - ), - rawError: eventsFunctionsExtensionsError, - errorId: 'events-functions-extensions-load-error', - }); - } - } - - _unloadProjectEventsFunctionsExtensions(project: gdProject) { - unloadProjectEventsFunctionsExtensions(project); - } - - _unloadProjectEventsFunctionsExtension( - project: gdProject, - extensionName: string - ) { - unloadProjectEventsFunctionsExtension(project, extensionName); - } - - _reloadProjectEventsFunctionsExtensions(project: ?gdProject): Promise { - if (project) { - this._unloadProjectEventsFunctionsExtensions(project); - } - return this._loadProjectEventsFunctionsExtensions(project); - } - - render() { - return ( - - {this.props.children} - - ); - } -} + } + }, + [eventsFunctionCodeWriter, i18n] + ); + + const _unloadProjectEventsFunctionsExtensions = React.useCallback( + (project: gdProject) => { + unloadProjectEventsFunctionsExtensions(project); + }, + [] + ); + + const _unloadProjectEventsFunctionsExtension = React.useCallback( + (project: gdProject, extensionName: string) => { + unloadProjectEventsFunctionsExtension(project, extensionName); + }, + [] + ); + + const _reloadProjectEventsFunctionsExtensions = React.useCallback( + (project: ?gdProject): Promise => { + if (project) { + _unloadProjectEventsFunctionsExtensions(project); + } + return _loadProjectEventsFunctionsExtensions(project); + }, + [ + _loadProjectEventsFunctionsExtensions, + _unloadProjectEventsFunctionsExtensions, + ] + ); + + const state = React.useMemo( + () => ({ + eventsFunctionsExtensionsError, + loadProjectEventsFunctionsExtensions: _loadProjectEventsFunctionsExtensions, + unloadProjectEventsFunctionsExtensions: _unloadProjectEventsFunctionsExtensions, + unloadProjectEventsFunctionsExtension: _unloadProjectEventsFunctionsExtension, + reloadProjectEventsFunctionsExtensions: _reloadProjectEventsFunctionsExtensions, + reloadProjectEventsFunctionsExtensionMetadata: _reloadProjectEventsFunctionsExtensionMetadata, + ensureLoadFinished, + getEventsFunctionsExtensionWriter: () => eventsFunctionsExtensionWriter, + getEventsFunctionsExtensionOpener: () => eventsFunctionsExtensionOpener, + getIncludeFileHashs: () => includeFileHashs.current, + }), + [ + ensureLoadFinished, + _loadProjectEventsFunctionsExtensions, + _reloadProjectEventsFunctionsExtensionMetadata, + _reloadProjectEventsFunctionsExtensions, + _unloadProjectEventsFunctionsExtension, + _unloadProjectEventsFunctionsExtensions, + eventsFunctionsExtensionOpener, + eventsFunctionsExtensionWriter, + eventsFunctionsExtensionsError, + ] + ); + + return ( + + {children} + + ); +}; diff --git a/newIDE/app/src/MainFrame/Providers.js b/newIDE/app/src/MainFrame/Providers.js index bbf400b61abb..51792b3d33d7 100644 --- a/newIDE/app/src/MainFrame/Providers.js +++ b/newIDE/app/src/MainFrame/Providers.js @@ -9,7 +9,7 @@ import PreferencesContext from './Preferences/PreferencesContext'; import GDI18nProvider from '../Utils/i18n/GDI18nProvider'; import { I18n } from '@lingui/react'; import { type I18n as I18nType } from '@lingui/core'; -import EventsFunctionsExtensionsProvider from '../EventsFunctionsExtensionsLoader/EventsFunctionsExtensionsProvider'; +import { EventsFunctionsExtensionsProvider } from '../EventsFunctionsExtensionsLoader/EventsFunctionsExtensionsProvider'; import { type EventsFunctionCodeWriter, type EventsFunctionCodeWriterCallbacks, diff --git a/newIDE/app/src/stories/componentStories/AssetStore/ExtensionStore/ExtensionsSearchDialog.stories.js b/newIDE/app/src/stories/componentStories/AssetStore/ExtensionStore/ExtensionsSearchDialog.stories.js index dff8f5851dce..928155f0e514 100644 --- a/newIDE/app/src/stories/componentStories/AssetStore/ExtensionStore/ExtensionsSearchDialog.stories.js +++ b/newIDE/app/src/stories/componentStories/AssetStore/ExtensionStore/ExtensionsSearchDialog.stories.js @@ -5,7 +5,7 @@ import { action } from '@storybook/addon-actions'; import paperDecorator from '../../../PaperDecorator'; import ExtensionsSearchDialog from '../../../../AssetStore/ExtensionStore/ExtensionsSearchDialog'; import { I18n } from '@lingui/react'; -import EventsFunctionsExtensionsProvider from '../../../../EventsFunctionsExtensionsLoader/EventsFunctionsExtensionsProvider'; +import { EventsFunctionsExtensionsProvider } from '../../../../EventsFunctionsExtensionsLoader/EventsFunctionsExtensionsProvider'; import { ExtensionStoreStateProvider } from '../../../../AssetStore/ExtensionStore/ExtensionStoreContext'; import { testProject } from '../../../GDevelopJsInitializerDecorator'; import { GDevelopAssetApi } from '../../../../Utils/GDevelopServices/ApiConfigs'; diff --git a/newIDE/app/src/stories/componentStories/EventsFunctionsExtensionEditor/OptionsEditorDialog.stories.js b/newIDE/app/src/stories/componentStories/EventsFunctionsExtensionEditor/OptionsEditorDialog.stories.js index 8283151b6330..7dd1db924ed6 100644 --- a/newIDE/app/src/stories/componentStories/EventsFunctionsExtensionEditor/OptionsEditorDialog.stories.js +++ b/newIDE/app/src/stories/componentStories/EventsFunctionsExtensionEditor/OptionsEditorDialog.stories.js @@ -9,7 +9,7 @@ import fakeResourceManagementProps from '../../FakeResourceManagement'; import { testProject } from '../../GDevelopJsInitializerDecorator'; import OptionsEditorDialog from '../../../EventsFunctionsExtensionEditor/OptionsEditorDialog'; -import EventsFunctionsExtensionsProvider from '../../../EventsFunctionsExtensionsLoader/EventsFunctionsExtensionsProvider'; +import { EventsFunctionsExtensionsProvider } from '../../../EventsFunctionsExtensionsLoader/EventsFunctionsExtensionsProvider'; export default { title: 'EventsFunctionsExtensionEditor/OptionsEditorDialog',