diff --git a/newIDE/app/src/AssetStore/BehaviorStore/BehaviorListItem.js b/newIDE/app/src/AssetStore/BehaviorStore/BehaviorListItem.js index e30258984519..6b394c13c602 100644 --- a/newIDE/app/src/AssetStore/BehaviorStore/BehaviorListItem.js +++ b/newIDE/app/src/AssetStore/BehaviorStore/BehaviorListItem.js @@ -4,13 +4,17 @@ import { type BehaviorShortHeader } from '../../Utils/GDevelopServices/Extension import ButtonBase from '@material-ui/core/ButtonBase'; import Text from '../../UI/Text'; import { Trans } from '@lingui/macro'; -import { Column } from '../../UI/Grid'; +import { Line, Column } from '../../UI/Grid'; import { IconContainer } from '../../UI/IconContainer'; import HighlightedText from '../../UI/Search/HighlightedText'; import { type SearchMatch } from '../../UI/Search/UseSearchStructuredItem'; import Chip from '../../UI/Chip'; import { LineStackLayout } from '../../UI/Layout'; import { type SearchableBehaviorMetadata } from './BehaviorStoreContext'; +import { UserPublicProfileChip } from '../../UI/User/UserPublicProfileChip'; +import Tooltip from '@material-ui/core/Tooltip'; +import CircledInfo from '../../UI/CustomSvgIcons/SmallCircledInfo'; +import IconButton from '../../UI/IconButton'; const styles = { button: { width: '100%' }, @@ -20,9 +24,6 @@ const styles = { overflow: 'hidden', width: '100%', }, - icon: { - paddingTop: 4, - }, }; type Props = {| @@ -32,6 +33,7 @@ type Props = {| behaviorShortHeader: BehaviorShortHeader | SearchableBehaviorMetadata, matches: ?Array, onChoose: () => void, + onShowDetails: () => void, onHeightComputed: number => void, |}; @@ -42,6 +44,7 @@ export const BehaviorListItem = ({ behaviorShortHeader, matches, onChoose, + onShowDetails, onHeightComputed, }: Props) => { const alreadyAdded = objectBehaviorsTypes.includes(behaviorShortHeader.type); @@ -83,6 +86,16 @@ export const BehaviorListItem = ({ [isEnabled, onChoose] ); + const hasChip = + alreadyAdded || + isObjectIncompatible || + behaviorShortHeader.tier === 'community' || + (behaviorShortHeader.isDeprecated || false); + const hasInfoButton = behaviorShortHeader.authors || false; + const iconStyle = { + paddingTop: hasInfoButton ? 10 : hasChip ? 6 : 4, + }; + return ( -
+
- + )} + {behaviorShortHeader.authors && ( + +
+ {behaviorShortHeader.authors.map(author => ( + + ))} +
+ + } + > + { + e.stopPropagation(); + onShowDetails(); + }} + > + + +
+ )}
{ const { showConfirmation } = useAlertDialog(); @@ -60,6 +61,10 @@ export const BehaviorStore = ({ onChoose, }: Props) => { const preferences = React.useContext(PreferencesContext); + const [ + selectedBehaviorShortHeader, + setSelectedBehaviorShortHeader, + ] = React.useState(null); const { filters, searchResults, @@ -258,10 +263,23 @@ export const BehaviorStore = ({ onChoose={() => { installAndChoose(behaviorShortHeader); }} + onShowDetails={() => { + if (behaviorShortHeader.headerUrl) { + setSelectedBehaviorShortHeader(behaviorShortHeader); + } + }} /> )} /> + {!!selectedBehaviorShortHeader && ( + setSelectedBehaviorShortHeader(null)} + /> + )} ); }; diff --git a/newIDE/app/src/AssetStore/ExtensionStore/ExtensionInstallDialog.js b/newIDE/app/src/AssetStore/ExtensionStore/ExtensionInstallDialog.js index 49d8d869a795..c3b17309b9cf 100644 --- a/newIDE/app/src/AssetStore/ExtensionStore/ExtensionInstallDialog.js +++ b/newIDE/app/src/AssetStore/ExtensionStore/ExtensionInstallDialog.js @@ -6,6 +6,7 @@ import FlatButton from '../../UI/FlatButton'; import { type ExtensionShortHeader, type ExtensionHeader, + type BehaviorShortHeader, getExtensionHeader, isCompatibleWithExtension, } from '../../Utils/GDevelopServices/Extension'; @@ -55,10 +56,10 @@ const getTransformedDescription = (extensionHeader: ExtensionHeader) => { }; type Props = {| - extensionShortHeader: ExtensionShortHeader, + extensionShortHeader: ExtensionShortHeader | BehaviorShortHeader, isInstalling: boolean, onClose: () => void, - onInstall: () => Promise, + onInstall?: () => Promise, onEdit?: () => void, project: gdProject, |}; @@ -114,7 +115,7 @@ const ExtensionInstallDialog = ({ const canInstallExtension = !isInstalling && isCompatible; const onInstallExtension = React.useCallback( () => { - if (canInstallExtension) { + if (canInstallExtension && onInstall) { if (alreadyInstalled) { 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?'; @@ -164,35 +165,37 @@ const ExtensionInstallDialog = ({ onClick={onClose} disabled={isInstalling} />, - - Not compatible - ) : alreadyInstalled ? ( - fromStore ? ( - extensionUpdate ? ( - extensionShortHeader.tier === 'community' ? ( - Update (could break the project) + onInstall ? ( + + Not compatible + ) : alreadyInstalled ? ( + fromStore ? ( + extensionUpdate ? ( + extensionShortHeader.tier === 'community' ? ( + Update (could break the project) + ) : ( + Update + ) ) : ( - Update + Re-install ) ) : ( - Re-install + Replace existing extension ) ) : ( - Replace existing extension + Install in project ) - ) : ( - Install in project - ) - } - primary - onClick={onInstallExtension} - disabled={!canInstallExtension} - /> - , + } + primary + onClick={onInstallExtension} + disabled={!canInstallExtension} + /> + + ) : null, ]} secondaryActions={[ onEdit ? ( @@ -225,7 +228,7 @@ const ExtensionInstallDialog = ({ open cannotBeDismissed={isInstalling} onRequestClose={onClose} - onApply={onInstallExtension} + onApply={onInstall ? onInstallExtension : onClose} > @@ -252,7 +255,11 @@ const ExtensionInstallDialog = ({
- {extensionShortHeader.shortDescription} + + {extensionHeader + ? extensionHeader.shortDescription + : extensionShortHeader.shortDescription || ''} + {extensionHeader && ( { return useMemo( - () => - project.hasEventsFunctionsExtensionNamed(extension.name) + () => { + const extensionName = extension.extensionName || extension.name; + return project.hasEventsFunctionsExtensionNamed(extensionName) ? getUpdateMetadataFromVersions( - project.getEventsFunctionsExtension(extension.name).getVersion(), + project.getEventsFunctionsExtension(extensionName).getVersion(), extension.version ) - : null, + : null; + }, [project, extension] ); }; diff --git a/newIDE/app/src/BehaviorsEditor/NewBehaviorDialog.js b/newIDE/app/src/BehaviorsEditor/NewBehaviorDialog.js index 30cc057d98fb..acffc2c0c809 100644 --- a/newIDE/app/src/BehaviorsEditor/NewBehaviorDialog.js +++ b/newIDE/app/src/BehaviorsEditor/NewBehaviorDialog.js @@ -55,7 +55,10 @@ export default function NewBehaviorDialog({ installExtension ); - const deprecatedBehaviorsInformation = getDeprecatedBehaviorsInformation(); + const deprecatedBehaviorsInformation = React.useMemo( + () => getDeprecatedBehaviorsInformation(), + [] + ); const allInstalledBehaviorMetadataList: Array = React.useMemo( () => { diff --git a/newIDE/app/src/Debugger/DebuggerConsole.js b/newIDE/app/src/Debugger/DebuggerConsole.js index 07af3123db36..46968485057c 100644 --- a/newIDE/app/src/Debugger/DebuggerConsole.js +++ b/newIDE/app/src/Debugger/DebuggerConsole.js @@ -21,7 +21,7 @@ import Checkbox from '../UI/Checkbox'; import TimerIcon from '@material-ui/icons/Timer'; -import InfoIcon from '../UI/CustomSvgIcons/Info'; +import InfoIcon from '../UI/CustomSvgIcons/SquaredInfo'; import WarningIcon from '../UI/CustomSvgIcons/Warning'; import ErrorIcon from '../UI/CustomSvgIcons/Error'; import FilterIcon from '../UI/CustomSvgIcons/Filter'; diff --git a/newIDE/app/src/UI/AlertMessage.js b/newIDE/app/src/UI/AlertMessage.js index 299cf690c83a..bab654d6be52 100644 --- a/newIDE/app/src/UI/AlertMessage.js +++ b/newIDE/app/src/UI/AlertMessage.js @@ -11,7 +11,7 @@ import Paper from './Paper'; import Cross from './CustomSvgIcons/Cross'; import WarningFilled from './CustomSvgIcons/WarningFilled'; import ErrorFilled from './CustomSvgIcons/ErrorFilled'; -import Info from './CustomSvgIcons/Info'; +import SquaredInfo from './CustomSvgIcons/SquaredInfo'; const styles = { icon: { width: 28, height: 28, marginRight: 10, marginLeft: 10 }, @@ -88,7 +88,7 @@ const AlertMessage = ({ ) : ( - {kind === 'info' && } + {kind === 'info' && } {kind === 'warning' && ( ( + + + + + +)); diff --git a/newIDE/app/src/UI/CustomSvgIcons/SmallCircledInfo.js b/newIDE/app/src/UI/CustomSvgIcons/SmallCircledInfo.js new file mode 100644 index 000000000000..582989847af5 --- /dev/null +++ b/newIDE/app/src/UI/CustomSvgIcons/SmallCircledInfo.js @@ -0,0 +1,23 @@ +import React from 'react'; +import SvgIcon from '@material-ui/core/SvgIcon'; + +export default React.memo(props => ( + + + + + +)); diff --git a/newIDE/app/src/UI/CustomSvgIcons/Info.js b/newIDE/app/src/UI/CustomSvgIcons/SquaredInfo.js similarity index 100% rename from newIDE/app/src/UI/CustomSvgIcons/Info.js rename to newIDE/app/src/UI/CustomSvgIcons/SquaredInfo.js diff --git a/newIDE/app/src/UI/User/UserPublicProfileChip.js b/newIDE/app/src/UI/User/UserPublicProfileChip.js index b80d412dfc86..8609e8a787c0 100644 --- a/newIDE/app/src/UI/User/UserPublicProfileChip.js +++ b/newIDE/app/src/UI/User/UserPublicProfileChip.js @@ -8,6 +8,7 @@ import User from '../CustomSvgIcons/User'; const styles = { chip: { marginRight: 2, + marginLeft: 2, marginBottom: 2, }, }; @@ -15,9 +16,14 @@ const styles = { type Props = {| user: UserPublicProfile, isClickable?: boolean, + variant?: 'default' | 'outlined', |}; -export const UserPublicProfileChip = ({ user, isClickable = false }: Props) => { +export const UserPublicProfileChip = ({ + user, + isClickable = false, + variant, +}: Props) => { const { openUserPublicProfile } = React.useContext(PublicProfileContext); return ( @@ -25,6 +31,7 @@ export const UserPublicProfileChip = ({ user, isClickable = false }: Props) => { icon={} size="small" style={styles.chip} + variant={variant} label={user.username} key={user.username} onClick={isClickable ? () => openUserPublicProfile(user.id) : null} diff --git a/newIDE/app/src/Utils/GDevelopServices/Extension.js b/newIDE/app/src/Utils/GDevelopServices/Extension.js index 90e3cda1a39b..5d1f1e644f76 100644 --- a/newIDE/app/src/Utils/GDevelopServices/Extension.js +++ b/newIDE/app/src/Utils/GDevelopServices/Extension.js @@ -133,7 +133,7 @@ const transformTagsAsStringToTagsAsArray = < /** Check if the IDE version, passed as argument, satisfy the version required by the extension. */ export const isCompatibleWithExtension = ( ideVersion: string, - extensionShortHeader: ExtensionShortHeader + extensionShortHeader: ExtensionShortHeader | BehaviorShortHeader ) => extensionShortHeader.gdevelopVersion ? semverSatisfies(ideVersion, extensionShortHeader.gdevelopVersion, { @@ -191,7 +191,7 @@ const adaptBehaviorHeader = ( }; export const getExtensionHeader = ( - extensionShortHeader: ExtensionShortHeader + extensionShortHeader: ExtensionShortHeader | BehaviorShortHeader ): Promise => { return axios.get(extensionShortHeader.headerUrl).then(response => { const data: ExtensionHeaderWithTagsAsString = response.data;