From ee82fc032006904ebe48195909f07f1280089a94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Davy=20H=C3=A9lard?= Date: Sat, 4 Apr 2026 18:58:38 +0200 Subject: [PATCH 1/3] Display extension details side by side with the extension list --- .../ExtensionStore/ExtensionDetailPanel.js | 426 ++++++++++++++++++ .../ExtensionStore/ExtensionInstallDialog.js | 278 +----------- .../ExtensionStore/UseExtensionUpdates.js | 2 +- .../src/AssetStore/ExtensionStore/index.js | 224 +++++---- 4 files changed, 596 insertions(+), 334 deletions(-) create mode 100644 newIDE/app/src/AssetStore/ExtensionStore/ExtensionDetailPanel.js diff --git a/newIDE/app/src/AssetStore/ExtensionStore/ExtensionDetailPanel.js b/newIDE/app/src/AssetStore/ExtensionStore/ExtensionDetailPanel.js new file mode 100644 index 000000000000..9323f8bef2bd --- /dev/null +++ b/newIDE/app/src/AssetStore/ExtensionStore/ExtensionDetailPanel.js @@ -0,0 +1,426 @@ +// @flow +import { t, Trans } from '@lingui/macro'; +import React from 'react'; +import FlatButton from '../../UI/FlatButton'; +import RaisedButton from '../../UI/RaisedButton'; +import { + type ExtensionShortHeader, + type ExtensionHeader, + type BehaviorShortHeader, + type ObjectShortHeader, + getExtensionHeader, +} from '../../Utils/GDevelopServices/Extension'; +import { + getBreakingChanges, + formatOldBreakingChanges, + formatBreakingChanges, + isCompatibleWithGDevelopVersion, +} from '../../Utils/Extension/ExtensionCompatibilityChecker.js'; +import LeftLoader from '../../UI/LeftLoader'; +import PlaceholderLoader from '../../UI/PlaceholderLoader'; +import PlaceholderError from '../../UI/PlaceholderError'; +import { MarkdownText } from '../../UI/MarkdownText'; +import Text from '../../UI/Text'; +import AlertMessage from '../../UI/AlertMessage'; +import { getIDEVersion } from '../../Version'; +import { Column, Line } from '../../UI/Grid'; +import { Divider } from '@material-ui/core'; +import { ColumnStackLayout, LineStackLayout } from '../../UI/Layout'; +import { IconContainer } from '../../UI/IconContainer'; +import { UserPublicProfileChip } from '../../UI/User/UserPublicProfileChip'; +import Window from '../../Utils/Window'; +import { useExtensionUpdate, type UpdateMetadata } from './UseExtensionUpdates'; +import HelpButton from '../../UI/HelpButton'; +import useAlertDialog from '../../UI/Alert/useAlertDialog'; +import { Accordion, AccordionHeader, AccordionBody } from '../../UI/Accordion'; + +export const useOutOfDateAlertDialog = (): (() => Promise) => { + const { showConfirmation } = useAlertDialog(); + return async (): Promise => { + return await showConfirmation({ + title: t`Outdated extension`, + message: t`The extension installed in this project is not up to date. + Consider upgrading it before reporting any issue.`, + confirmButtonLabel: t`Close`, + dismissButtonLabel: t`Report anyway`, + }); + }; +}; + +const getTransformedDescription = (extensionHeader: ExtensionHeader) => { + if ( + extensionHeader.description.substr( + 0, + extensionHeader.shortDescription.length + ) === extensionHeader.shortDescription + ) { + return extensionHeader.description.substr( + extensionHeader.shortDescription.length + ); + } + + return extensionHeader.description; +}; + +type ExtensionDetail = { + isAlreadyInstalled: boolean, + isCompatible: boolean, + canInstallExtension: boolean, + installedExtension: gdEventsFunctionsExtension | null, + extensionUpdate: UpdateMetadata | null, + extensionHeader: ExtensionHeader | null, + error: Error | null, + onInstallExtension: () => void, + loadExtensionheader: () => void, + onUserReportIssue: () => Promise, + renderInstallButtonLabel: () => React.Node, +}; + +export const useExtensionDetail = ({ + extensionShortHeader, + isInstalling, + onInstall, + project, +}: { + extensionShortHeader: + | ExtensionShortHeader + | BehaviorShortHeader + | ObjectShortHeader, + isInstalling: boolean, + onInstall?: () => Promise, + project: gdProject, +}): ExtensionDetail => { + const isAlreadyInstalled: boolean = project.hasEventsFunctionsExtensionNamed( + extensionShortHeader.name + ); + + const installedExtension = isAlreadyInstalled + ? project.getEventsFunctionsExtension(extensionShortHeader.name) + : null; + + const isFromStore = installedExtension + ? installedExtension.getOriginName() === 'gdevelop-extension-store' + : false; + + const extensionUpdate = useExtensionUpdate(project, extensionShortHeader); + + const [error, setError] = React.useState(null); + const [ + extensionHeader, + setExtensionHeader, + ] = React.useState(null); + + const loadExtensionheader = React.useCallback( + () => { + setError(null); + getExtensionHeader(extensionShortHeader).then( + extensionHeader => { + setExtensionHeader(extensionHeader); + }, + error => { + setError(error); + } + ); + }, + [extensionShortHeader] + ); + + React.useEffect(() => loadExtensionheader(), [loadExtensionheader]); + + const isCompatible = isCompatibleWithGDevelopVersion( + getIDEVersion(), + extensionShortHeader.gdevelopVersion + ); + + const canInstallExtension = !isInstalling && isCompatible; + const onInstallExtension = React.useCallback( + () => { + if (canInstallExtension && onInstall) { + if (isAlreadyInstalled) { + let dialogText = + 'This extension is already in your project, this will install the latest version. You may have to do some adaptations to make sure your game still works. Do you want to continue?'; + if (!isFromStore) + dialogText = + 'An other extension with the same name is already in your project. Installing this extension will overwrite your current extension. Do you want to continue?'; + + const answer = Window.showConfirmDialog(dialogText); + if (!answer) return; + onInstall(); + } else { + onInstall(); + } + } + }, + [onInstall, canInstallExtension, isAlreadyInstalled, isFromStore] + ); + + const showOutOfDateAlert = useOutOfDateAlertDialog(); + const onUserReportIssue = React.useCallback( + async () => { + if (extensionUpdate) { + const shouldNotReportIssue = await showOutOfDateAlert(); + if (shouldNotReportIssue) { + return; + } + } + Window.openExternalURL( + `https://github.com/GDevelopApp/GDevelop-extensions/issues/new` + + `?assignees=&labels=&template=bug-report.yml&title=[${ + extensionShortHeader.name + }] Issue short description` + ); + }, + [extensionShortHeader.name, extensionUpdate, showOutOfDateAlert] + ); + + const renderInstallButtonLabel = React.useCallback( + (): React.Node => + !isCompatible ? ( + Not compatible + ) : isAlreadyInstalled ? ( + isFromStore ? ( + extensionUpdate && + installedExtension && + extensionShortHeader.version !== installedExtension.getVersion() ? ( + extensionShortHeader.tier === 'experimental' ? ( + Update (could break the project) + ) : ( + Update + ) + ) : ( + Re-install + ) + ) : ( + Replace existing extension + ) + ) : ( + Install in project + ), + [ + extensionShortHeader.tier, + extensionShortHeader.version, + extensionUpdate, + installedExtension, + isAlreadyInstalled, + isCompatible, + isFromStore, + ] + ); + + return React.useMemo( + () => ({ + isAlreadyInstalled, + isCompatible, + canInstallExtension, + installedExtension, + extensionUpdate, + extensionHeader, + error, + onInstallExtension, + loadExtensionheader, + onUserReportIssue, + renderInstallButtonLabel, + }), + [ + isAlreadyInstalled, + isCompatible, + canInstallExtension, + installedExtension, + extensionUpdate, + extensionHeader, + error, + onInstallExtension, + loadExtensionheader, + onUserReportIssue, + renderInstallButtonLabel, + ] + ); +}; + +type Props = {| + extensionShortHeader: + | ExtensionShortHeader + | BehaviorShortHeader + | ObjectShortHeader, + isInstalling: boolean, + onInstall?: () => Promise, + extensionDetail: ExtensionDetail, + shouldDisplayButtons: boolean, +|}; + +const ExtensionDetailPanel = ({ + extensionShortHeader, + extensionDetail, + isInstalling, + onInstall, + shouldDisplayButtons, +}: Props): React.Node => { + const { + isAlreadyInstalled, + isCompatible, + canInstallExtension, + installedExtension, + extensionUpdate, + extensionHeader, + error, + onInstallExtension, + loadExtensionheader, + onUserReportIssue, + renderInstallButtonLabel, + } = extensionDetail; + + const newBreakingChangesText = installedExtension + ? formatBreakingChanges( + getBreakingChanges( + installedExtension.getVersion(), + extensionShortHeader + ) + ) + : null; + const oldBreakingChangesText = installedExtension + ? formatOldBreakingChanges( + installedExtension.getVersion(), + extensionShortHeader + ) + : null; + + return ( + + + + + + + {extensionUpdate && + installedExtension && + extensionShortHeader.version !== + installedExtension.getVersion() ? ( + {`Version ${installedExtension.getVersion()} (${ + extensionShortHeader.version + } available)`} + ) : ( + {`Version ${extensionShortHeader.version}`} + )} + + +
+ {extensionShortHeader.authors && + extensionShortHeader.authors.map(author => ( + + ))} +
+
+
+ {shouldDisplayButtons && onInstall && ( + + + + + + )} +
+
+ + {extensionHeader + ? extensionHeader.shortDescription + : typeof extensionShortHeader.shortDescription === 'string' + ? extensionShortHeader.shortDescription || '' + : ''} + + + {extensionHeader && ( + + )} + {extensionShortHeader.tier === 'experimental' && ( + + + This is an extension made by a community member and it only got + through a light review by the GDevelop extension team. As such, we + can't guarantee it meets all the quality standards of fully reviewed + extensions. + + + )} + {!isCompatible && ( + + + Unfortunately, this extension requires a newer version of GDevelop + to work. Update GDevelop to be able to use this extension in your + project. + + + )} + {!extensionHeader && !error && } + {!extensionHeader && error && ( + + + Can't load the extension registry. Verify your internet connection + or try again later. + + + )} + {newBreakingChangesText && ( + <> + + Breaking changes + + + + )} + {oldBreakingChangesText && ( + + + + Previous breaking changes (no longer relevant) + + + + + + + )} + + {shouldDisplayButtons && + extensionHeader && + extensionHeader.helpPath && ( + + )} + {shouldDisplayButtons && isAlreadyInstalled && ( + Report an issue} + onClick={() => onUserReportIssue()} + /> + )} + +
+ ); +}; + +export default ExtensionDetailPanel; diff --git a/newIDE/app/src/AssetStore/ExtensionStore/ExtensionInstallDialog.js b/newIDE/app/src/AssetStore/ExtensionStore/ExtensionInstallDialog.js index 70d26795f8ce..72ac6420f47b 100644 --- a/newIDE/app/src/AssetStore/ExtensionStore/ExtensionInstallDialog.js +++ b/newIDE/app/src/AssetStore/ExtensionStore/ExtensionInstallDialog.js @@ -5,34 +5,15 @@ import Dialog, { DialogPrimaryButton } from '../../UI/Dialog'; import FlatButton from '../../UI/FlatButton'; import { type ExtensionShortHeader, - type ExtensionHeader, type BehaviorShortHeader, type ObjectShortHeader, - getExtensionHeader, } from '../../Utils/GDevelopServices/Extension'; -import { - getBreakingChanges, - formatOldBreakingChanges, - formatBreakingChanges, - isCompatibleWithGDevelopVersion, -} from '../../Utils/Extension/ExtensionCompatibilityChecker.js'; import LeftLoader from '../../UI/LeftLoader'; -import PlaceholderLoader from '../../UI/PlaceholderLoader'; -import PlaceholderError from '../../UI/PlaceholderError'; -import { MarkdownText } from '../../UI/MarkdownText'; -import Text from '../../UI/Text'; -import AlertMessage from '../../UI/AlertMessage'; -import { getIDEVersion } from '../../Version'; -import { Column, Line } from '../../UI/Grid'; -import { Divider } from '@material-ui/core'; -import { ColumnStackLayout } from '../../UI/Layout'; -import { IconContainer } from '../../UI/IconContainer'; -import { UserPublicProfileChip } from '../../UI/User/UserPublicProfileChip'; -import Window from '../../Utils/Window'; -import { useExtensionUpdate } from './UseExtensionUpdates'; import HelpButton from '../../UI/HelpButton'; import useAlertDialog from '../../UI/Alert/useAlertDialog'; -import { Accordion, AccordionHeader, AccordionBody } from '../../UI/Accordion'; +import ExtensionDetailPanel, { + useExtensionDetail, +} from './ExtensionDetailPanel'; export const useOutOfDateAlertDialog = (): (() => Promise) => { const { showConfirmation } = useAlertDialog(); @@ -47,21 +28,6 @@ export const useOutOfDateAlertDialog = (): (() => Promise) => { }; }; -const getTransformedDescription = (extensionHeader: ExtensionHeader) => { - if ( - extensionHeader.description.substr( - 0, - extensionHeader.shortDescription.length - ) === extensionHeader.shortDescription - ) { - return extensionHeader.description.substr( - extensionHeader.shortDescription.length - ); - } - - return extensionHeader.description; -}; - type Props = {| extensionShortHeader: | ExtensionShortHeader @@ -82,103 +48,20 @@ const ExtensionInstallDialog = ({ onEdit, project, }: Props): React.Node => { - const isAlreadyInstalled: boolean = project.hasEventsFunctionsExtensionNamed( - extensionShortHeader.name - ); - - const installedExtension = isAlreadyInstalled - ? project.getEventsFunctionsExtension(extensionShortHeader.name) - : null; - - const isFromStore = installedExtension - ? installedExtension.getOriginName() === 'gdevelop-extension-store' - : false; - - const newBreakingChangesText = installedExtension - ? formatBreakingChanges( - getBreakingChanges( - installedExtension.getVersion(), - extensionShortHeader - ) - ) - : null; - const oldBreakingChangesText = installedExtension - ? formatOldBreakingChanges( - installedExtension.getVersion(), - extensionShortHeader - ) - : null; - - const extensionUpdate = useExtensionUpdate(project, extensionShortHeader); - - const [error, setError] = React.useState(null); - const [ + const extensionDetail = useExtensionDetail({ + extensionShortHeader, + isInstalling, + onInstall, + project, + }); + const { + isAlreadyInstalled, + canInstallExtension, extensionHeader, - setExtensionHeader, - ] = React.useState(null); - - const loadExtensionheader = React.useCallback( - () => { - setError(null); - getExtensionHeader(extensionShortHeader).then( - extensionHeader => { - setExtensionHeader(extensionHeader); - }, - error => { - setError(error); - } - ); - }, - [extensionShortHeader] - ); - - React.useEffect(() => loadExtensionheader(), [loadExtensionheader]); - - const isCompatible = isCompatibleWithGDevelopVersion( - getIDEVersion(), - extensionShortHeader.gdevelopVersion - ); - - const canInstallExtension = !isInstalling && isCompatible; - const onInstallExtension = React.useCallback( - () => { - if (canInstallExtension && onInstall) { - if (isAlreadyInstalled) { - let dialogText = - 'This extension is already in your project, this will install the latest version. You may have to do some adaptations to make sure your game still works. Do you want to continue?'; - if (!isFromStore) - dialogText = - 'An other extension with the same name is already in your project. Installing this extension will overwrite your current extension. Do you want to continue?'; - - const answer = Window.showConfirmDialog(dialogText); - if (!answer) return; - onInstall(); - } else { - onInstall(); - } - } - }, - [onInstall, canInstallExtension, isAlreadyInstalled, isFromStore] - ); - - const showOutOfDateAlert = useOutOfDateAlertDialog(); - const onUserReportIssue = React.useCallback( - async () => { - if (extensionUpdate) { - const shouldNotReportIssue = await showOutOfDateAlert(); - if (shouldNotReportIssue) { - return; - } - } - Window.openExternalURL( - `https://github.com/GDevelopApp/GDevelop-extensions/issues/new` + - `?assignees=&labels=&template=bug-report.yml&title=[${ - extensionShortHeader.name - }] Issue short description` - ); - }, - [extensionShortHeader.name, extensionUpdate, showOutOfDateAlert] - ); + onInstallExtension, + onUserReportIssue, + renderInstallButtonLabel, + } = extensionDetail; return ( Not compatible - ) : isAlreadyInstalled ? ( - isFromStore ? ( - extensionUpdate && - installedExtension && - extensionShortHeader.version !== - installedExtension.getVersion() ? ( - extensionShortHeader.tier === 'experimental' ? ( - Update (could break the project) - ) : ( - Update - ) - ) : ( - Re-install - ) - ) : ( - Replace existing extension - ) - ) : ( - Install in project - ) - } + label={renderInstallButtonLabel()} primary onClick={onInstallExtension} disabled={!canInstallExtension} @@ -261,103 +121,13 @@ const ExtensionInstallDialog = ({ onRequestClose={onClose} onApply={onInstall ? onInstallExtension : onClose} > - - - - - - {extensionUpdate && - installedExtension && - extensionShortHeader.version !== - installedExtension.getVersion() ? ( - {`Version ${installedExtension.getVersion()} (${ - extensionShortHeader.version - } available)`} - ) : ( - {`Version ${extensionShortHeader.version}`} - )} - - -
- {extensionShortHeader.authors && - extensionShortHeader.authors.map(author => ( - - ))} -
-
-
-
- - {extensionHeader - ? extensionHeader.shortDescription - : typeof extensionShortHeader.shortDescription === 'string' - ? extensionShortHeader.shortDescription || '' - : ''} - - - {extensionHeader && ( - - )} - {extensionShortHeader.tier === 'experimental' && ( - - - This is an extension made by a community member and it only got - through a light review by the GDevelop extension team. As such, we - can't guarantee it meets all the quality standards of fully - reviewed extensions. - - - )} - {!isCompatible && ( - - - Unfortunately, this extension requires a newer version of GDevelop - to work. Update GDevelop to be able to use this extension in your - project. - - - )} - {!extensionHeader && !error && } - {!extensionHeader && error && ( - - - Can't load the extension registry. Verify your internet connection - or try again later. - - - )} - {newBreakingChangesText && ( - <> - - Breaking changes - - - - )} - {oldBreakingChangesText && ( - - - - Previous breaking changes (no longer relevant) - - - - - - - )} -
+
); }; diff --git a/newIDE/app/src/AssetStore/ExtensionStore/UseExtensionUpdates.js b/newIDE/app/src/AssetStore/ExtensionStore/UseExtensionUpdates.js index 12b9ca8369bb..9b6854abf062 100644 --- a/newIDE/app/src/AssetStore/ExtensionStore/UseExtensionUpdates.js +++ b/newIDE/app/src/AssetStore/ExtensionStore/UseExtensionUpdates.js @@ -8,7 +8,7 @@ import type { } from '../../Utils/GDevelopServices/Extension'; type UpdateType = 'patch' | 'minor' | 'major'; -type UpdateMetadata = {| +export type UpdateMetadata = {| type: UpdateType, currentVersion: string, newestVersion: string, diff --git a/newIDE/app/src/AssetStore/ExtensionStore/index.js b/newIDE/app/src/AssetStore/ExtensionStore/index.js index 3e10e2fd9919..487fea2f1c91 100644 --- a/newIDE/app/src/AssetStore/ExtensionStore/index.js +++ b/newIDE/app/src/AssetStore/ExtensionStore/index.js @@ -1,4 +1,5 @@ // @flow +import { t, Trans } from '@lingui/macro'; import { type I18n as I18nType } from '@lingui/core'; import * as React from 'react'; import SearchBar from '../../UI/SearchBar'; @@ -13,8 +14,7 @@ import { sendExtensionAddedToProject, } from '../../Utils/Analytics/EventSender'; import useDismissableTutorialMessage from '../../Hints/useDismissableTutorialMessage'; -import { t } from '@lingui/macro'; -import { ColumnStackLayout } from '../../UI/Layout'; +import { ColumnStackLayout, LineStackLayout } from '../../UI/Layout'; import { Column, Line } from '../../UI/Grid'; import PreferencesContext from '../../MainFrame/Preferences/PreferencesContext'; import { ResponsiveLineStackLayout } from '../../UI/Layout'; @@ -23,6 +23,40 @@ import SelectOption from '../../UI/SelectOption'; import ElementWithMenu from '../../UI/Menu/ElementWithMenu'; import IconButton from '../../UI/IconButton'; import ThreeDotsMenu from '../../UI/CustomSvgIcons/ThreeDotsMenu'; +import ExtensionDetailPanel, { + useExtensionDetail, +} from './ExtensionDetailPanel'; +import { useResponsiveWindowSize } from '../../UI/Responsive/ResponsiveWindowMeasurer'; +import Text from '../../UI/Text'; + +export const ExtensionDetailSidePanel = ({ + extensionShortHeader, + isInstalling, + onInstall, + project, +}: { + extensionShortHeader: ExtensionShortHeader, + isInstalling: boolean, + onInstall?: () => Promise, + project: gdProject, +}): React.Node => { + const extensionDetail = useExtensionDetail({ + extensionShortHeader, + isInstalling, + onInstall, + project, + }); + + return ( + + ); +}; type Props = {| isInstalling: boolean, @@ -55,6 +89,7 @@ export const ExtensionStore = ({ chosenCategory, setChosenCategory, } = React.useContext(ExtensionStoreContext); + const { isMobile } = useResponsiveWindowSize(); React.useEffect( () => { @@ -87,86 +122,117 @@ export const ExtensionStore = ({ return ( - - - - { - setChosenCategory(value); - }} - > - - {allCategories.map(category => ( - - ))} - - - - {}} - placeholder={t`Search extensions`} - autoFocus="desktop" - /> - - - - - } - buildMenuTemplate={(i18n: I18nType) => [ - { - label: preferences.values.showExperimentalExtensions - ? i18n._(t`Hide experimental extensions`) - : i18n._(t`Show experimental extensions`), - click: () => { - preferences.setShowExperimentalExtensions( - !preferences.values.showExperimentalExtensions - ); + + + + + { + setChosenCategory(value); + }} + > + + {allCategories.map(category => ( + + ))} + + + + {}} + placeholder={t`Search extensions`} + autoFocus="desktop" + /> + + + + + } + buildMenuTemplate={(i18n: I18nType) => [ + { + label: preferences.values.showExperimentalExtensions + ? i18n._(t`Hide experimental extensions`) + : i18n._(t`Show experimental extensions`), + click: () => { + preferences.setShowExperimentalExtensions( + !preferences.values.showExperimentalExtensions + ); + }, }, - }, - ]} + ]} + /> + + + {DismissableTutorialMessage} + + item) + } + getSearchItemUniqueId={getExtensionName} + // $FlowFixMe[missing-local-annot] + renderSearchItem={(extensionShortHeader, onHeightComputed) => ( + { + sendExtensionDetailsOpened(extensionShortHeader.name); + setSelectedExtensionShortHeader(extensionShortHeader); + }} /> - - - {DismissableTutorialMessage} + )} + /> - item) - } - getSearchItemUniqueId={getExtensionName} - // $FlowFixMe[missing-local-annot] - renderSearchItem={(extensionShortHeader, onHeightComputed) => ( - { - sendExtensionDetailsOpened(extensionShortHeader.name); - setSelectedExtensionShortHeader(extensionShortHeader); - }} - /> - )} - /> - - {!!selectedExtensionShortHeader && ( + {!isMobile ? ( + selectedExtensionShortHeader ? ( + + { + sendExtensionAddedToProject( + selectedExtensionShortHeader.name + ); + await onInstall(selectedExtensionShortHeader); + }} + /> + + ) : ( + + + Select an extension + + + ) + ) : null} + + {isMobile && !!selectedExtensionShortHeader && ( Date: Sun, 5 Apr 2026 14:43:14 +0200 Subject: [PATCH 2/3] Add a divider --- .../src/AssetStore/ExtensionStore/index.js | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/newIDE/app/src/AssetStore/ExtensionStore/index.js b/newIDE/app/src/AssetStore/ExtensionStore/index.js index 487fea2f1c91..fac869c5cfd5 100644 --- a/newIDE/app/src/AssetStore/ExtensionStore/index.js +++ b/newIDE/app/src/AssetStore/ExtensionStore/index.js @@ -28,6 +28,7 @@ import ExtensionDetailPanel, { } from './ExtensionDetailPanel'; import { useResponsiveWindowSize } from '../../UI/Responsive/ResponsiveWindowMeasurer'; import Text from '../../UI/Text'; +import { Divider } from '@material-ui/core'; export const ExtensionDetailSidePanel = ({ extensionShortHeader, @@ -204,19 +205,22 @@ export const ExtensionStore = ({ {!isMobile ? ( selectedExtensionShortHeader ? ( - - { - sendExtensionAddedToProject( - selectedExtensionShortHeader.name - ); - await onInstall(selectedExtensionShortHeader); - }} - /> - + + + + { + sendExtensionAddedToProject( + selectedExtensionShortHeader.name + ); + await onInstall(selectedExtensionShortHeader); + }} + /> + + ) : ( Date: Tue, 7 Apr 2026 11:32:35 +0200 Subject: [PATCH 3/3] Fix separator visibility and button alignment --- .../ExtensionStore/ExtensionDetailPanel.js | 85 +++++++++---------- .../src/AssetStore/ExtensionStore/index.js | 34 ++++---- 2 files changed, 55 insertions(+), 64 deletions(-) diff --git a/newIDE/app/src/AssetStore/ExtensionStore/ExtensionDetailPanel.js b/newIDE/app/src/AssetStore/ExtensionStore/ExtensionDetailPanel.js index 9323f8bef2bd..38df6616b672 100644 --- a/newIDE/app/src/AssetStore/ExtensionStore/ExtensionDetailPanel.js +++ b/newIDE/app/src/AssetStore/ExtensionStore/ExtensionDetailPanel.js @@ -286,59 +286,50 @@ const ExtensionDetailPanel = ({ return ( - + - - - - {extensionUpdate && - installedExtension && - extensionShortHeader.version !== - installedExtension.getVersion() ? ( - {`Version ${installedExtension.getVersion()} (${ - extensionShortHeader.version - } available)`} - ) : ( - {`Version ${extensionShortHeader.version}`} - )} - - -
- {extensionShortHeader.authors && - extensionShortHeader.authors.map(author => ( - - ))} -
-
+ + + {extensionUpdate && + installedExtension && + extensionShortHeader.version !== installedExtension.getVersion() ? ( + {`Version ${installedExtension.getVersion()} (${ + extensionShortHeader.version + } available)`} + ) : ( + {`Version ${extensionShortHeader.version}`} + )} + + +
+ {extensionShortHeader.authors && + extensionShortHeader.authors.map(author => ( + + ))} +
+
+
+ {shouldDisplayButtons && onInstall && ( + + + + - {shouldDisplayButtons && onInstall && ( - - - - - - )} -
+ )}
{extensionHeader diff --git a/newIDE/app/src/AssetStore/ExtensionStore/index.js b/newIDE/app/src/AssetStore/ExtensionStore/index.js index fac869c5cfd5..68be4ee90029 100644 --- a/newIDE/app/src/AssetStore/ExtensionStore/index.js +++ b/newIDE/app/src/AssetStore/ExtensionStore/index.js @@ -204,9 +204,9 @@ export const ExtensionStore = ({ />
{!isMobile ? ( - selectedExtensionShortHeader ? ( - - + + + {selectedExtensionShortHeader ? ( - - ) : ( - - - Select an extension - - - ) + ) : ( + + + Select an extension + + + )} + ) : null} {isMobile && !!selectedExtensionShortHeader && (