{fullWidth ? : null}
- {
+ const minPage = 1;
+ const maxPage = games ? Math.ceil(games.length / pageSize) : 1;
+ if (newPage < minPage) {
+ setCurrentPage(minPage);
+ } else if (newPage > maxPage) {
+ setCurrentPage(maxPage);
+ } else {
+ setCurrentPage(newPage);
+ }
+ },
+ [setCurrentPage, games]
+ );
+
const unregisterGame = React.useCallback(
async (game: Game, i18n: I18nType) => {
if (!profile) return;
@@ -347,6 +364,11 @@ const CreateSection = ({
+
+
+
+
+
{isMobile && limits && hasTooManyCloudProjects && (
{
@@ -63,7 +63,11 @@ const MarketingPlans = ({ game }: Props) => {
const fetchGameFeaturings = React.useCallback(
async () => {
- if (!profile) return;
+ if (!profile || !game) {
+ setGameFeaturings([]);
+ return;
+ }
+
try {
setGameFeaturingsError(null);
const gameFeaturings = await listGameFeaturings(
@@ -143,9 +147,10 @@ const MarketingPlans = ({ game }: Props) => {
activeGameFeaturings
);
- const requirementsErrors = isPlanActive
- ? getRequirementsErrors(game, marketingPlan)
- : [];
+ const requirementsErrors =
+ isPlanActive && game
+ ? getRequirementsErrors(game, marketingPlan)
+ : [];
return (
Promise,
|};
@@ -41,6 +41,14 @@ const usePurchaseMarketingPlan = ({
async (i18n: I18nType, marketingPlan: MarketingPlan) => {
if (!profile || !limits) return;
+ if (!game) {
+ await showAlert({
+ title: t`Select a game`,
+ message: t`In order to purchase a marketing boost, select a game in your dashboard.`,
+ });
+ return;
+ }
+
const {
id,
nameByLocale,
diff --git a/newIDE/app/src/UI/RaisedButtonWithSplitMenu.js b/newIDE/app/src/UI/RaisedButtonWithSplitMenu.js
index c421482011ad..f7d3fad06857 100644
--- a/newIDE/app/src/UI/RaisedButtonWithSplitMenu.js
+++ b/newIDE/app/src/UI/RaisedButtonWithSplitMenu.js
@@ -14,7 +14,7 @@ type Props = {|
primary: true, // Force making only primary raised split buttons.
disabled?: boolean,
icon?: React.Node,
- onClick: ?() => void,
+ onClick: ?() => void | Promise,
buildMenuTemplate: (i18n: I18nType) => Array,
style?: {|
marginTop?: number,
@@ -24,6 +24,7 @@ type Props = {|
margin?: number,
flexShrink?: 0,
|},
+ fullWidth?: boolean,
|};
const shouldNeverBeCalled = () => {
@@ -48,7 +49,15 @@ const styles = {
* when the dropdown arrow is clicked.
*/
const RaisedButtonWithSplitMenu = (props: Props) => {
- const { id, buildMenuTemplate, onClick, label, icon, disabled } = props;
+ const {
+ id,
+ buildMenuTemplate,
+ onClick,
+ label,
+ icon,
+ disabled,
+ fullWidth,
+ } = props;
// In theory, focus ripple is only shown after a keyboard interaction
// (see https://github.com/mui-org/material-ui/issues/12067). However, as
@@ -64,6 +73,7 @@ const RaisedButtonWithSplitMenu = (props: Props) => {
disabled={disabled}
size="small"
style={props.style}
+ fullWidth={fullWidth}
>
);
}
diff --git a/newIDE/app/src/stories/componentStories/GameDashboard/GameCard.stories.js b/newIDE/app/src/stories/componentStories/GameDashboard/GameCard.stories.js
deleted file mode 100644
index f9bddcaa8045..000000000000
--- a/newIDE/app/src/stories/componentStories/GameDashboard/GameCard.stories.js
+++ /dev/null
@@ -1,105 +0,0 @@
-// @flow
-
-import * as React from 'react';
-import { action } from '@storybook/addon-actions';
-
-import paperDecorator from '../../PaperDecorator';
-import GameCard from '../../../GameDashboard/GameCard';
-import {
- fakeSilverAuthenticatedUser,
- game1,
- game2,
-} from '../../../fixtures/GDevelopServicesTestData';
-import AuthenticatedUserContext from '../../../Profile/AuthenticatedUserContext';
-import CloudStorageProvider from '../../../ProjectsStorage/CloudStorageProvider';
-
-export default {
- title: 'GameDashboard/GameCard',
- component: GameCard,
- decorators: [paperDecorator],
-};
-
-export const UnpublishedGame = () => (
-
-
-
-);
-
-export const PublishedGame = () => (
-
-
-
-);
-
-export const CurrentlyOpened = () => (
-
-
-
-);
-
-export const Saving = () => (
-
-
-
-);
-
-export const Disabled = () => (
-
-
-
-);
diff --git a/newIDE/app/src/stories/componentStories/GameDashboard/GameDashboard.stories.js b/newIDE/app/src/stories/componentStories/GameDashboard/GameDashboard.stories.js
index c8c4f59b8597..865a105e83e3 100644
--- a/newIDE/app/src/stories/componentStories/GameDashboard/GameDashboard.stories.js
+++ b/newIDE/app/src/stories/componentStories/GameDashboard/GameDashboard.stories.js
@@ -161,10 +161,6 @@ export const Default = ({
exports: 'None' | 'Some ongoing' | 'All complete',
|}) => {
const [game, setGame] = React.useState(game1);
- const [
- gameUnregisterErrorText,
- setGameUnregisterErrorText,
- ] = React.useState(null);
const [tab, setTab] = React.useState('details');
const [renderCount, setRenderCount] = React.useState(0);
const feedbacksToDisplay =
@@ -332,16 +328,10 @@ export const Default = ({
setCurrentView={setTab}
onBack={() => action('Back')}
onGameUpdated={() => action('onGameUpdated')}
- onUnregisterGame={async () =>
- setGameUnregisterErrorText(
- gameUnregisterErrorText
- ? null
- : 'You cannot unregister a game in a story'
- )
- }
- gameUnregisterErrorText={gameUnregisterErrorText}
+ onUnregisterGame={() => action('onUnregisterGame')}
closeProject={action('closeProject')}
disabled={false}
+ onDeleteCloudProject={action('onDeleteCloudProject')}
/>
diff --git a/newIDE/app/src/stories/componentStories/GameDashboard/GameDashboardCard.stories.js b/newIDE/app/src/stories/componentStories/GameDashboard/GameDashboardCard.stories.js
new file mode 100644
index 000000000000..68dea99f25cf
--- /dev/null
+++ b/newIDE/app/src/stories/componentStories/GameDashboard/GameDashboardCard.stories.js
@@ -0,0 +1,298 @@
+// @flow
+
+import * as React from 'react';
+import { action } from '@storybook/addon-actions';
+
+import paperDecorator from '../../PaperDecorator';
+import GameDashboardCard from '../../../GameDashboard/GameDashboardCard';
+import {
+ fakeSilverAuthenticatedUser,
+ game1,
+ game2,
+ fakeFileMetadataAndStorageProviderNameForCloudProject,
+ fakeFileMetadataAndStorageProviderNameForLocalProject,
+} from '../../../fixtures/GDevelopServicesTestData';
+import AuthenticatedUserContext from '../../../Profile/AuthenticatedUserContext';
+import CloudStorageProvider from '../../../ProjectsStorage/CloudStorageProvider';
+import LocalFileStorageProvider from '../../../ProjectsStorage/LocalFileStorageProvider';
+
+export default {
+ title: 'GameDashboard/GameDashboardCard',
+ component: GameDashboardCard,
+ decorators: [paperDecorator],
+};
+
+export const UnpublishedGame = () => (
+
+
+
+);
+
+export const PublishedGame = () => (
+
+
+
+);
+
+export const CurrentlyOpenedGame = () => (
+
+
+
+);
+
+export const SavingGame = () => (
+
+
+
+);
+
+export const DisabledGame = () => (
+
+
+
+);
+
+export const LocalProject = () => (
+
+
+
+);
+
+export const OpenedLocalProject = () => (
+
+
+
+);
+
+export const DisabledLocalProject = () => (
+
+
+
+);
+
+export const CloudProject = () => (
+
+
+
+);
+
+export const OpenedCloudProject = () => (
+
+
+
+);
+
+export const DisabledCloudProject = () => (
+
+
+
+);
diff --git a/newIDE/app/src/stories/componentStories/GameDashboard/GamesList.stories.js b/newIDE/app/src/stories/componentStories/GameDashboard/GamesList.stories.js
index 85d0b582618c..0d1e08525068 100644
--- a/newIDE/app/src/stories/componentStories/GameDashboard/GamesList.stories.js
+++ b/newIDE/app/src/stories/componentStories/GameDashboard/GamesList.stories.js
@@ -61,6 +61,8 @@ export const NoGamesOrProjects = () => {
onUnregisterGame={action('onUnregisterGame')}
currentPage={1}
onCurrentPageChange={action('onCurrentPageChange')}
+ onDeleteCloudProject={action('onDeleteCloudProject')}
+ onRegisterProject={action('onRegisterProject')}
/>
@@ -109,6 +111,8 @@ export const WithOnlyGames = () => {
onUnregisterGame={action('onUnregisterGame')}
currentPage={1}
onCurrentPageChange={action('onCurrentPageChange')}
+ onDeleteCloudProject={action('onDeleteCloudProject')}
+ onRegisterProject={action('onRegisterProject')}
/>
@@ -152,6 +156,8 @@ export const WithOnlyProjects = () => {
onUnregisterGame={action('onUnregisterGame')}
currentPage={1}
onCurrentPageChange={action('onCurrentPageChange')}
+ onDeleteCloudProject={action('onDeleteCloudProject')}
+ onRegisterProject={action('onRegisterProject')}
/>
@@ -202,6 +208,8 @@ export const WithGamesAndProjects = () => {
onUnregisterGame={action('onUnregisterGame')}
currentPage={1}
onCurrentPageChange={action('onCurrentPageChange')}
+ onDeleteCloudProject={action('onDeleteCloudProject')}
+ onRegisterProject={action('onRegisterProject')}
/>
diff --git a/newIDE/app/src/stories/componentStories/GameDashboard/ProjectCard.stories.js b/newIDE/app/src/stories/componentStories/GameDashboard/ProjectCard.stories.js
deleted file mode 100644
index cbf34052669a..000000000000
--- a/newIDE/app/src/stories/componentStories/GameDashboard/ProjectCard.stories.js
+++ /dev/null
@@ -1,143 +0,0 @@
-// @flow
-
-import * as React from 'react';
-import { action } from '@storybook/addon-actions';
-
-import paperDecorator from '../../PaperDecorator';
-import ProjectCard from '../../../GameDashboard/ProjectCard';
-import {
- fakeFileMetadataAndStorageProviderNameForCloudProject,
- fakeFileMetadataAndStorageProviderNameForLocalProject,
- fakeSilverAuthenticatedUser,
-} from '../../../fixtures/GDevelopServicesTestData';
-import AuthenticatedUserContext from '../../../Profile/AuthenticatedUserContext';
-import CloudStorageProvider from '../../../ProjectsStorage/CloudStorageProvider';
-import LocalFileStorageProvider from '../../../ProjectsStorage/LocalFileStorageProvider';
-
-export default {
- title: 'GameDashboard/ProjectCard',
- component: ProjectCard,
- decorators: [paperDecorator],
-};
-
-export const LocalProject = () => (
-
-
-
-);
-
-export const OpenedLocalProject = () => (
-
-
-
-);
-
-export const DisabledLocalProject = () => (
-
-
-
-);
-
-export const CloudProject = () => (
-
-
-
-);
-
-export const OpenedCloudProject = () => (
-
-
-
-);
-
-export const DisabledCloudProject = () => (
-
-
-
-);
From 6fbb7646a389ab1f5143bd3c05dd8868337b0781 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Cl=C3=A9ment=20Pasteau?=
<4895034+ClementPasteau@users.noreply.github.com>
Date: Tue, 10 Dec 2024 17:23:37 +0100
Subject: [PATCH 12/26] Some fixes
---
.../src/GameDashboard/GameDashboardCard.js | 36 ++++++++++++-------
.../CloudProjectWriter.js | 4 ++-
newIDE/app/src/UI/TextButton.js | 1 +
.../app/src/Utils/GDevelopServices/Project.js | 2 +-
4 files changed, 28 insertions(+), 15 deletions(-)
diff --git a/newIDE/app/src/GameDashboard/GameDashboardCard.js b/newIDE/app/src/GameDashboard/GameDashboardCard.js
index cb00313717c4..50578a3553fb 100644
--- a/newIDE/app/src/GameDashboard/GameDashboardCard.js
+++ b/newIDE/app/src/GameDashboard/GameDashboardCard.js
@@ -49,6 +49,7 @@ import WarningRound from '../UI/CustomSvgIcons/WarningRound';
import PreferencesContext from '../MainFrame/Preferences/PreferencesContext';
import { textEllipsisStyle } from '../UI/TextEllipsis';
import FileWithLines from '../UI/CustomSvgIcons/FileWithLines';
+import TextButton from '../UI/TextButton';
const electron = optionalRequire('electron');
const path = optionalRequire('path');
@@ -64,10 +65,10 @@ const styles = {
...textEllipsisStyle,
overflowWrap: 'break-word',
},
+ projectFilesButton: { minWidth: 32 },
fileIcon: {
width: 16,
height: 16,
- marginRight: 4,
},
};
@@ -263,13 +264,20 @@ const GameDashboardCard = ({
{gameName}
- {projectsList.length > 0 && (
+ {projectsList.length > 0 && game && (
<>
-
-
- {projectsList.length}
-
+ onOpenGameManager(game)}
+ icon={}
+ label={
+
+ {projectsList.length}
+
+ }
+ disabled={disabled}
+ style={styles.projectFilesButton}
+ />
>
)}
@@ -309,6 +317,8 @@ const GameDashboardCard = ({
);
const name = itemStorageProvider ? (
i18n._(itemStorageProvider.name)
+ ) : isCurrentProjectOpened ? (
+ Project not saved
) : (
Project not found
);
@@ -411,16 +421,16 @@ const GameDashboardCard = ({
});
}
- if (actions.length > 0) {
- actions.push({
- type: 'separator',
- });
- }
-
// Delete actions.
// Don't allow removing project if opened, as it would not result in any change in the list.
// (because an opened project is always displayed)
if (!isCurrentProjectOpened && projectsList.length < 2) {
+ if (actions.length > 0) {
+ actions.push({
+ type: 'separator',
+ });
+ }
+
const file = projectsList[0];
actions.push({
label: i18n._(t`Delete`),
@@ -517,7 +527,7 @@ const GameDashboardCard = ({
: () => {
showAlert({
title: t`No project found`,
- message: t`We couldn't find a project for this game. Try to open the project file manually to get it automatically linked.`,
+ message: t`We couldn't find a project for this game. You may have saved it in a different location? You can open it manually to get it linked to this game.`,
});
};
diff --git a/newIDE/app/src/ProjectsStorage/CloudStorageProvider/CloudProjectWriter.js b/newIDE/app/src/ProjectsStorage/CloudStorageProvider/CloudProjectWriter.js
index e93bbf8eb269..104eb4e99d48 100644
--- a/newIDE/app/src/ProjectsStorage/CloudStorageProvider/CloudProjectWriter.js
+++ b/newIDE/app/src/ProjectsStorage/CloudStorageProvider/CloudProjectWriter.js
@@ -236,13 +236,15 @@ export const generateOnSaveProjectAs = (
) => {
if (!saveAsLocation)
throw new Error('A location was not chosen before saving as.');
- const { name, gameId } = saveAsLocation;
+ const { name } = saveAsLocation;
if (!name) throw new Error('A name was not chosen before saving as.');
if (!authenticatedUser.authenticated) {
return { wasSaved: false, fileMetadata: null };
}
options.onStartSaving();
+ const gameId = saveAsLocation.gameId || project.getProjectUuid();
+
try {
// Create a new cloud project.
const cloudProject = await createCloudProject(authenticatedUser, {
diff --git a/newIDE/app/src/UI/TextButton.js b/newIDE/app/src/UI/TextButton.js
index 1212c3d3a3d1..831cddb59f2f 100644
--- a/newIDE/app/src/UI/TextButton.js
+++ b/newIDE/app/src/UI/TextButton.js
@@ -22,6 +22,7 @@ type Props = {|
marginRight?: number,
margin?: number,
flexShrink?: 0,
+ minWidth?: number,
|},
target?: '_blank',
id?: ?string,
diff --git a/newIDE/app/src/Utils/GDevelopServices/Project.js b/newIDE/app/src/Utils/GDevelopServices/Project.js
index 7818cecf50e6..5bbe8b8f4e6d 100644
--- a/newIDE/app/src/Utils/GDevelopServices/Project.js
+++ b/newIDE/app/src/Utils/GDevelopServices/Project.js
@@ -261,7 +261,7 @@ export const clearCloudProjectCredentials = async (): Promise => {
export const createCloudProject = async (
authenticatedUser: AuthenticatedUser,
- cloudProjectCreationPayload: {| name: string, gameId?: string |}
+ cloudProjectCreationPayload: {| name: string, gameId: string |}
): Promise => {
const { getAuthorizationHeader, firebaseUser } = authenticatedUser;
if (!firebaseUser) return null;
From 9e1caf037e2891a8035257065eaa1c615a1913d0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Cl=C3=A9ment=20Pasteau?=
<4895034+ClementPasteau@users.noreply.github.com>
Date: Wed, 11 Dec 2024 09:58:55 +0100
Subject: [PATCH 13/26] Small design fixes
---
.../src/GameDashboard/GameDashboardCard.js | 34 +++++++++++--------
1 file changed, 20 insertions(+), 14 deletions(-)
diff --git a/newIDE/app/src/GameDashboard/GameDashboardCard.js b/newIDE/app/src/GameDashboard/GameDashboardCard.js
index 50578a3553fb..8c6a79208f19 100644
--- a/newIDE/app/src/GameDashboard/GameDashboardCard.js
+++ b/newIDE/app/src/GameDashboard/GameDashboardCard.js
@@ -41,7 +41,7 @@ import useAlertDialog from '../UI/Alert/useAlertDialog';
import LastModificationInfo from '../MainFrame/EditorContainers/HomePage/CreateSection/LastModificationInfo';
import optionalRequire from '../Utils/OptionalRequire';
import RaisedButton from '../UI/RaisedButton';
-import { Line, Spacer } from '../UI/Grid';
+import { Column, Line, Spacer } from '../UI/Grid';
import ElementWithMenu from '../UI/Menu/ElementWithMenu';
import IconButton from '../UI/IconButton';
import ThreeDotsMenu from '../UI/CustomSvgIcons/ThreeDotsMenu';
@@ -193,7 +193,9 @@ const GameDashboardCard = ({
'Unknown game'
: 'Unknown game';
- const { isMobile } = useResponsiveWindowSize();
+ const { isMobile, windowSize } = useResponsiveWindowSize();
+ const isSmallOrMediumScreen =
+ windowSize === 'small' || windowSize === 'medium';
const gdevelopTheme = React.useContext(GDevelopThemeContext);
const itemStorageProvider = projectFileMetadataAndStorageProviderName
? getStorageProviderByInternalName(
@@ -296,10 +298,10 @@ const GameDashboardCard = ({
/>
) : game ? (
-
+
Last edited:
-
+
{i18n.date(game.updatedAt * 1000)}
@@ -567,7 +569,12 @@ const GameDashboardCard = ({
};
const renderShareUrl = (i18n: I18nType) =>
- gameUrl ? : null;
+ gameUrl ? (
+
+ ) : null;
return (
@@ -579,15 +586,14 @@ const GameDashboardCard = ({
>
{isMobile ? (
-
- {renderTitle()}
- {renderLastModification(i18n)}
+
+
+ {renderTitle()}
+ {renderLastModification(i18n)}
+
+ {renderAdditionalActions()}
-
+
{renderThumbnail()}
{renderPublicInfo()}
@@ -613,7 +619,7 @@ const GameDashboardCard = ({
{renderTitle()}
- {renderStorageProvider(i18n)}
+ {!isSmallOrMediumScreen && renderStorageProvider(i18n)}
{renderAdditionalActions()}
From 021f143067054e1a6e122b0f470e9ef9ed3bb480 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Cl=C3=A9ment=20Pasteau?=
<4895034+ClementPasteau@users.noreply.github.com>
Date: Wed, 11 Dec 2024 16:36:44 +0100
Subject: [PATCH 14/26] Improvements & fixes
---
.../src/GameDashboard/GameDashboardCard.js | 26 ++--
newIDE/app/src/GameDashboard/GamesList.js | 11 +-
.../Monetization/UserEarningsWidget.js | 6 +-
.../src/GameDashboard/Wallet/WalletWidget.js | 1 +
.../Widgets/AllFeedbacksWidget.js | 49 -------
.../GameDashboard/Widgets/AnalyticsWidget.js | 1 +
.../src/GameDashboard/Widgets/BuildsWidget.js | 1 +
.../GameDashboard/Widgets/DashboardWidget.js | 13 +-
.../GameDashboard/Widgets/FeedbackWidget.js | 1 +
.../GameDashboard/Widgets/ProjectsWidget.js | 6 +-
.../GameDashboard/Widgets/ServicesWidget.js | 6 +-
.../GameDashboard/Widgets/TotalPlaysWidget.js | 126 ------------------
newIDE/app/src/GameDashboard/index.js | 23 +++-
.../HomePage/CreateSection/index.js | 17 ++-
.../app/src/UI/RaisedButtonWithSplitMenu.js | 2 +
.../GameDashboard/GamesList.stories.js | 8 +-
16 files changed, 93 insertions(+), 204 deletions(-)
delete mode 100644 newIDE/app/src/GameDashboard/Widgets/AllFeedbacksWidget.js
delete mode 100644 newIDE/app/src/GameDashboard/Widgets/TotalPlaysWidget.js
diff --git a/newIDE/app/src/GameDashboard/GameDashboardCard.js b/newIDE/app/src/GameDashboard/GameDashboardCard.js
index 8c6a79208f19..839b91f03f28 100644
--- a/newIDE/app/src/GameDashboard/GameDashboardCard.js
+++ b/newIDE/app/src/GameDashboard/GameDashboardCard.js
@@ -120,7 +120,7 @@ type Props = {|
dashboardItem: DashboardItem,
storageProviders: Array
,
isCurrentProjectOpened: boolean,
- onOpenGameManager: (game: Game) => void,
+ onOpenGameManager: ({ game: Game, widgetToScrollTo?: 'projects' }) => void,
onOpenProject: (file: FileMetadataAndStorageProviderName) => Promise,
onUnregisterGame: () => Promise,
disabled: boolean,
@@ -270,7 +270,9 @@ const GameDashboardCard = ({
<>
onOpenGameManager(game)}
+ onClick={() =>
+ onOpenGameManager({ game, widgetToScrollTo: 'projects' })
+ }
icon={}
label={
@@ -376,7 +378,7 @@ const GameDashboardCard = ({
{ type: 'separator' },
{
label: i18n._(t`See all in the game dashboard`),
- click: game ? () => onOpenGameManager(game) : undefined,
+ click: game ? () => onOpenGameManager({ game }) : undefined,
},
]
);
@@ -419,7 +421,7 @@ const GameDashboardCard = ({
// If there are multiple projects, suggest opening the game dashboard.
actions.push({
label: i18n._(t`See all projects`),
- click: game ? () => onOpenGameManager(game) : undefined,
+ click: game ? () => onOpenGameManager({ game }) : undefined,
});
}
@@ -484,7 +486,7 @@ const GameDashboardCard = ({
const onManageGame = React.useCallback(
async () => {
if (game) {
- onOpenGameManager(game);
+ onOpenGameManager({ game });
return;
} else {
if (!authenticatedUser.profile) {
@@ -502,7 +504,7 @@ const GameDashboardCard = ({
if (!registeredGame) return;
await onRefreshGames();
- onOpenGameManager(registeredGame);
+ onOpenGameManager({ game: registeredGame });
}
},
[
@@ -586,13 +588,13 @@ const GameDashboardCard = ({
>
{isMobile ? (
-
-
+
+
{renderTitle()}
- {renderLastModification(i18n)}
-
- {renderAdditionalActions()}
-
+ {renderAdditionalActions()}
+
+ {renderLastModification(i18n)}
+
{renderThumbnail()}
{renderPublicInfo()}
diff --git a/newIDE/app/src/GameDashboard/GamesList.js b/newIDE/app/src/GameDashboard/GamesList.js
index 52c984041f4c..5fcf3146dbb1 100644
--- a/newIDE/app/src/GameDashboard/GamesList.js
+++ b/newIDE/app/src/GameDashboard/GamesList.js
@@ -222,7 +222,10 @@ type Props = {|
currentFileMetadata: ?FileMetadata,
games: Array,
onRefreshGames: () => Promise,
- onOpenGameId: (gameId: ?string) => void,
+ onOpenGameManager: ({
+ game: Game,
+ widgetToScrollTo?: 'projects',
+ }) => void,
onOpenProject: (file: FileMetadataAndStorageProviderName) => Promise,
onUnregisterGame: (
gameId: string,
@@ -254,7 +257,7 @@ const GamesList = ({
currentFileMetadata,
games,
onRefreshGames,
- onOpenGameId,
+ onOpenGameManager,
onOpenProject,
onUnregisterGame,
onRegisterProject,
@@ -544,9 +547,7 @@ const GamesList = ({
dashboardItem={dashboardItem}
storageProviders={storageProviders}
isCurrentProjectOpened={isCurrentProjectOpened}
- onOpenGameManager={(gameToOpen: Game) => {
- onOpenGameId(gameToOpen.id);
- }}
+ onOpenGameManager={onOpenGameManager}
onOpenProject={onOpenProject}
onUnregisterGame={async () => {
if (!game) return;
diff --git a/newIDE/app/src/GameDashboard/Monetization/UserEarningsWidget.js b/newIDE/app/src/GameDashboard/Monetization/UserEarningsWidget.js
index ba3a080f4a1d..602a6bd86350 100644
--- a/newIDE/app/src/GameDashboard/Monetization/UserEarningsWidget.js
+++ b/newIDE/app/src/GameDashboard/Monetization/UserEarningsWidget.js
@@ -220,7 +220,11 @@ const UserEarningsWidget = ({ fullWidth }: Props) => {
return (
<>
-
+
{content}
{selectedCashOutType && userEarningsBalance && (
diff --git a/newIDE/app/src/GameDashboard/Wallet/WalletWidget.js b/newIDE/app/src/GameDashboard/Wallet/WalletWidget.js
index 3f6f9ea3a770..17d95a0a5b2c 100644
--- a/newIDE/app/src/GameDashboard/Wallet/WalletWidget.js
+++ b/newIDE/app/src/GameDashboard/Wallet/WalletWidget.js
@@ -40,6 +40,7 @@ const WalletWidget = ({
onClick={profile ? onOpenProfile : onOpenCreateAccountDialog}
/>
}
+ widgetName="wallet"
>
,
-|};
-
-const AllFeedbacksWidget = ({ feedbacks }: Props) => {
- const unprocessedFeedbacks = feedbacks.filter(
- comment => !comment.processedAt
- );
-
- return (
-
- {({ i18n }) => (
- Feedbacks}
- topRightAction={
-
- {!!unprocessedFeedbacks.length && (
-
- )}
-
- {unprocessedFeedbacks.length === 0 ? (
- No new feedback
- ) : unprocessedFeedbacks.length === 1 ? (
- 1 new feedback
- ) : (
- {unprocessedFeedbacks.length} new feedbacks
- )}
-
-
- }
- />
- )}
-
- );
-};
-
-export default AllFeedbacksWidget;
diff --git a/newIDE/app/src/GameDashboard/Widgets/AnalyticsWidget.js b/newIDE/app/src/GameDashboard/Widgets/AnalyticsWidget.js
index e609e5f21853..0a7bdee9e4a2 100644
--- a/newIDE/app/src/GameDashboard/Widgets/AnalyticsWidget.js
+++ b/newIDE/app/src/GameDashboard/Widgets/AnalyticsWidget.js
@@ -63,6 +63,7 @@ const AnalyticsWidget = ({ game, onSeeAll, gameMetrics, gameUrl }: Props) => {
primary
/>
}
+ widgetName="analytics"
>
{!gameMetrics ? (
diff --git a/newIDE/app/src/GameDashboard/Widgets/BuildsWidget.js b/newIDE/app/src/GameDashboard/Widgets/BuildsWidget.js
index bcbdd45ae84b..e582b3274f74 100644
--- a/newIDE/app/src/GameDashboard/Widgets/BuildsWidget.js
+++ b/newIDE/app/src/GameDashboard/Widgets/BuildsWidget.js
@@ -42,6 +42,7 @@ const BuildsWidget = ({ builds, onSeeAllBuilds }: Props) => {
primary
/>
}
+ widgetName="builds"
>
{pendingBuilds && pendingBuilds.length > 0 && (
diff --git a/newIDE/app/src/GameDashboard/Widgets/DashboardWidget.js b/newIDE/app/src/GameDashboard/Widgets/DashboardWidget.js
index 89df8d717977..4b027f046572 100644
--- a/newIDE/app/src/GameDashboard/Widgets/DashboardWidget.js
+++ b/newIDE/app/src/GameDashboard/Widgets/DashboardWidget.js
@@ -23,6 +23,15 @@ const styles = {
},
};
+type GameDashboardWidgetName =
+ | 'analytics'
+ | 'feedback'
+ | 'services'
+ | 'projects'
+ | 'builds'
+ | 'wallet'
+ | 'earnings';
+
type Props = {|
title: React.Node,
topRightAction?: React.Node,
@@ -30,6 +39,7 @@ type Props = {|
gridSize: number,
children?: React.Node,
minHeight?: boolean,
+ widgetName: GameDashboardWidgetName,
|};
const DashboardWidget = ({
@@ -39,10 +49,11 @@ const DashboardWidget = ({
renderSubtitle,
children,
minHeight,
+ widgetName,
}: Props) => {
const { isMobile } = useResponsiveWindowSize();
return (
-
+
)
}
+ widgetName="feedback"
minHeight
renderSubtitle={() =>
shouldDisplayControlToCollectFeedback ? null : unprocessedFeedbacks &&
diff --git a/newIDE/app/src/GameDashboard/Widgets/ProjectsWidget.js b/newIDE/app/src/GameDashboard/Widgets/ProjectsWidget.js
index f98beb95f04d..81b99dd8fa41 100644
--- a/newIDE/app/src/GameDashboard/Widgets/ProjectsWidget.js
+++ b/newIDE/app/src/GameDashboard/Widgets/ProjectsWidget.js
@@ -28,7 +28,11 @@ type Props = {|
const ProjectsWidget = (props: Props) => {
return (
- Projects}>
+ Projects}
+ widgetName="projects"
+ >
);
diff --git a/newIDE/app/src/GameDashboard/Widgets/ServicesWidget.js b/newIDE/app/src/GameDashboard/Widgets/ServicesWidget.js
index db2276b582ed..02b791e63d0f 100644
--- a/newIDE/app/src/GameDashboard/Widgets/ServicesWidget.js
+++ b/newIDE/app/src/GameDashboard/Widgets/ServicesWidget.js
@@ -38,7 +38,11 @@ const ServicesWidget = ({
SubscriptionSuggestionContext
);
return (
- Player services}>
+ Player services}
+ widgetName="services"
+ >
diff --git a/newIDE/app/src/GameDashboard/Widgets/TotalPlaysWidget.js b/newIDE/app/src/GameDashboard/Widgets/TotalPlaysWidget.js
deleted file mode 100644
index 787ccc18bc0a..000000000000
--- a/newIDE/app/src/GameDashboard/Widgets/TotalPlaysWidget.js
+++ /dev/null
@@ -1,126 +0,0 @@
-// @flow
-import { Trans } from '@lingui/macro';
-import * as React from 'react';
-
-import { LineStackLayout } from '../../UI/Layout';
-import Text from '../../UI/Text';
-import { Column, Spacer } from '../../UI/Grid';
-import BackgroundText from '../../UI/BackgroundText';
-import GDevelopThemeContext from '../../UI/Theme/GDevelopThemeContext';
-import DashboardWidget from './DashboardWidget';
-import { type Game } from '../../Utils/GDevelopServices/Game';
-
-const styles = {
- separator: {
- height: 50,
- },
-};
-
-// Helper to display 5.5k instead of 5502, or 1.2m instead of 1234567
-export const formatPlays = (plays: number) => {
- if (plays < 1000) return plays.toString();
- if (plays < 1000000) return `${(plays / 1000).toFixed(1)}k`;
- return `${(plays / 1000000).toFixed(1)}m`;
-};
-
-type Props = {|
- games: Array,
- fullWidth?: boolean,
-|};
-
-const TotalPlaysWidget = ({ games, fullWidth }: Props) => {
- const theme = React.useContext(GDevelopThemeContext);
-
- const {
- allGamesLastWeekPlays,
- allGamesLastYearPlays,
- allGamesTotalPlays,
- } = React.useMemo(
- () => {
- const allGamesLastWeekPlays = games.reduce(
- (acc, game) => acc + (game.cachedLastWeekSessionsCount || 0),
- 0
- );
- const allGamesLastYearPlays = games.reduce(
- (acc, game) => acc + (game.cachedLastYearSessionsCount || 0),
- 0
- );
- const allGamesTotalPlays = games.reduce(
- (acc, game) => acc + (game.cachedTotalSessionsCount || 0),
- 0
- );
-
- return {
- allGamesLastWeekPlays,
- allGamesLastYearPlays,
- allGamesTotalPlays,
- };
- },
- [games]
- );
-
- return (
- Total plays}
- >
-
-
-
-
- {formatPlays(allGamesLastWeekPlays)}
-
-
- Last week
-
-
-
-
-
-
-
- {formatPlays(allGamesLastYearPlays)}
-
-
- Last year
-
-
-
-
-
-
-
- {formatPlays(allGamesTotalPlays)}
-
-
- Overall
-
-
-
-
-
-
- );
-};
-
-export default TotalPlaysWidget;
diff --git a/newIDE/app/src/GameDashboard/index.js b/newIDE/app/src/GameDashboard/index.js
index d75fcd010dfc..58814648d46e 100644
--- a/newIDE/app/src/GameDashboard/index.js
+++ b/newIDE/app/src/GameDashboard/index.js
@@ -88,6 +88,7 @@ type Props = {|
setCurrentView: GameDetailsTab => void,
onBack: () => void,
disabled: boolean,
+ initialWidgetToScrollTo?: ?string,
|};
const GameDashboard = ({
@@ -109,7 +110,12 @@ const GameDashboard = ({
setCurrentView,
onBack,
disabled,
+ initialWidgetToScrollTo,
}: Props) => {
+ const grid = React.useRef(null);
+ const [widgetToScrollTo, setWidgetToScrollTo] = React.useState(
+ initialWidgetToScrollTo
+ );
const [
gameDetailsDialogOpen,
setGameDetailsDialogOpen,
@@ -414,6 +420,21 @@ const GameDashboard = ({
[fetchPublicGame]
);
+ React.useEffect(
+ () => {
+ if (widgetToScrollTo && grid.current) {
+ const widget = grid.current.querySelector(
+ `[data-widget-name="${widgetToScrollTo}"]`
+ );
+ if (widget) {
+ widget.scrollIntoView({ behavior: 'smooth', inline: 'start' });
+ setWidgetToScrollTo(null);
+ }
+ }
+ },
+ [initialWidgetToScrollTo, widgetToScrollTo]
+ );
+
React.useEffect(
() => {
if (!profile) {
@@ -538,7 +559,7 @@ const GameDashboard = ({
: null
}
/>
-
+
setCurrentView('analytics')}
gameMetrics={gameRollingMetrics}
diff --git a/newIDE/app/src/MainFrame/EditorContainers/HomePage/CreateSection/index.js b/newIDE/app/src/MainFrame/EditorContainers/HomePage/CreateSection/index.js
index 3b08e0992176..d3157d55e1f2 100644
--- a/newIDE/app/src/MainFrame/EditorContainers/HomePage/CreateSection/index.js
+++ b/newIDE/app/src/MainFrame/EditorContainers/HomePage/CreateSection/index.js
@@ -143,6 +143,9 @@ const CreateSection = ({
showAlert,
} = useAlertDialog();
const [isUpdatingGame, setIsUpdatingGame] = React.useState(false);
+ const [initialWidgetToScrollTo, setInitialWidgetToScrollTo] = React.useState(
+ null
+ );
const [showAllGameTemplates, setShowAllGameTemplates] = React.useState(false);
const { routeArguments, removeRouteArguments } = React.useContext(
RouterContext
@@ -386,8 +389,6 @@ const CreateSection = ({
or retry later.`,
});
return;
-
- // TODO: should we generate a gameId in this case?
}
const { id, username } = authenticatedUser.profile;
@@ -451,6 +452,7 @@ const CreateSection = ({
disabled={isUpdatingGame}
onUnregisterGame={() => onUnregisterGame(openedGame.id, i18n)}
onDeleteCloudProject={onDeleteCloudProject}
+ initialWidgetToScrollTo={initialWidgetToScrollTo}
/>
);
@@ -527,7 +529,16 @@ const CreateSection = ({
project={project}
games={games || []}
onRefreshGames={onRefreshGames}
- onOpenGameId={setOpenedGameId}
+ onOpenGameManager={({
+ game,
+ widgetToScrollTo,
+ }: {
+ game: Game,
+ widgetToScrollTo?: 'projects',
+ }) => {
+ setInitialWidgetToScrollTo(widgetToScrollTo);
+ setOpenedGameId(game.id);
+ }}
onOpenProject={onOpenProject}
isUpdatingGame={isUpdatingGame}
onUnregisterGame={onUnregisterGame}
diff --git a/newIDE/app/src/UI/RaisedButtonWithSplitMenu.js b/newIDE/app/src/UI/RaisedButtonWithSplitMenu.js
index f7d3fad06857..09d6fe3a8b69 100644
--- a/newIDE/app/src/UI/RaisedButtonWithSplitMenu.js
+++ b/newIDE/app/src/UI/RaisedButtonWithSplitMenu.js
@@ -41,6 +41,8 @@ const styles = {
minWidth: 30,
paddingLeft: 0,
paddingRight: 0,
+ // Make the button shrink to its minimum size.
+ flexBasis: 0,
},
};
diff --git a/newIDE/app/src/stories/componentStories/GameDashboard/GamesList.stories.js b/newIDE/app/src/stories/componentStories/GameDashboard/GamesList.stories.js
index 0d1e08525068..e4ba7bf13d60 100644
--- a/newIDE/app/src/stories/componentStories/GameDashboard/GamesList.stories.js
+++ b/newIDE/app/src/stories/componentStories/GameDashboard/GamesList.stories.js
@@ -47,7 +47,7 @@ export const NoGamesOrProjects = () => {
project={null}
games={[]}
onRefreshGames={action('onRefreshGames')}
- onOpenGameId={action('onOpenGameId')}
+ onOpenGameManager={action('onOpenGameManager')}
onOpenProject={action('onOpenProject')}
canOpen={true}
askToCloseProject={action('askToCloseProject')}
@@ -97,7 +97,7 @@ export const WithOnlyGames = () => {
project={null}
games={[game1, game2]}
onRefreshGames={action('onRefreshGames')}
- onOpenGameId={action('onOpenGameId')}
+ onOpenGameManager={action('onOpenGameManager')}
onOpenProject={action('onOpenProject')}
canOpen={true}
askToCloseProject={action('askToCloseProject')}
@@ -142,7 +142,7 @@ export const WithOnlyProjects = () => {
project={null}
games={[]}
onRefreshGames={action('onRefreshGames')}
- onOpenGameId={action('onOpenGameId')}
+ onOpenGameManager={action('onOpenGameManager')}
onOpenProject={action('onOpenProject')}
canOpen={true}
askToCloseProject={action('askToCloseProject')}
@@ -194,7 +194,7 @@ export const WithGamesAndProjects = () => {
project={null}
games={[game1, game2]}
onRefreshGames={action('onRefreshGames')}
- onOpenGameId={action('onOpenGameId')}
+ onOpenGameManager={action('onOpenGameManager')}
onOpenProject={action('onOpenProject')}
canOpen={true}
askToCloseProject={action('askToCloseProject')}
From f5138807457db1e699145fb4237189ebf5f8bf30 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Cl=C3=A9ment=20Pasteau?=
<4895034+ClementPasteau@users.noreply.github.com>
Date: Wed, 11 Dec 2024 17:44:39 +0100
Subject: [PATCH 15/26] Fix search
---
newIDE/app/src/GameDashboard/GamesList.js | 27 ++++++++++---------
.../CreateSection/LastModificationInfo.js | 4 +--
.../HomePage/GetStartedSection/EarnBadges.js | 6 ++++-
3 files changed, 22 insertions(+), 15 deletions(-)
diff --git a/newIDE/app/src/GameDashboard/GamesList.js b/newIDE/app/src/GameDashboard/GamesList.js
index 5fcf3146dbb1..9011fb4566ba 100644
--- a/newIDE/app/src/GameDashboard/GamesList.js
+++ b/newIDE/app/src/GameDashboard/GamesList.js
@@ -14,7 +14,7 @@ import {
import SearchBar from '../UI/SearchBar';
import { useDebounce } from '../Utils/UseDebounce';
import {
- getFuseSearchQueryForSimpleArray,
+ getFuseSearchQueryForMultipleKeys,
sharedFuseConfiguration,
} from '../UI/Search/UseSearchStructuredItem';
import IconButton from '../UI/IconButton';
@@ -135,22 +135,25 @@ const getDashboardItemsToDisplay = ({
|}): Array => {
let itemsToDisplay: DashboardItem[] = allDashboardItems;
- // Always order items, with or without search.
- itemsToDisplay =
- orderBy === 'totalSessions'
- ? itemsToDisplay.sort(totalSessionsSort)
- : orderBy === 'weeklySessions'
- ? itemsToDisplay.sort(lastWeekSessionsSort)
- : orderBy === 'lastModifiedAt'
- ? itemsToDisplay.sort(lastModifiedAtSort)
- : itemsToDisplay;
-
if (searchText) {
const searchResults = searchClient.search(
- getFuseSearchQueryForSimpleArray(searchText)
+ getFuseSearchQueryForMultipleKeys(searchText, [
+ 'game.gameName',
+ 'projectFiles.fileMetadata.name',
+ ])
);
itemsToDisplay = searchResults.map(result => result.item);
} else {
+ // Order items first.
+ itemsToDisplay =
+ orderBy === 'totalSessions'
+ ? itemsToDisplay.sort(totalSessionsSort)
+ : orderBy === 'weeklySessions'
+ ? itemsToDisplay.sort(lastWeekSessionsSort)
+ : orderBy === 'lastModifiedAt'
+ ? itemsToDisplay.sort(lastModifiedAtSort)
+ : itemsToDisplay;
+
// If a project is opened and no search is performed, display it first.
if (project) {
const currentProjectId = project.getProjectUuid();
diff --git a/newIDE/app/src/MainFrame/EditorContainers/HomePage/CreateSection/LastModificationInfo.js b/newIDE/app/src/MainFrame/EditorContainers/HomePage/CreateSection/LastModificationInfo.js
index 2f9197f2aef9..4cabbcb80daf 100644
--- a/newIDE/app/src/MainFrame/EditorContainers/HomePage/CreateSection/LastModificationInfo.js
+++ b/newIDE/app/src/MainFrame/EditorContainers/HomePage/CreateSection/LastModificationInfo.js
@@ -77,7 +77,7 @@ const LastModificationInfo = ({
{({ i18n }) => (
{textPrefix && (
-
+
{textPrefix}
)}
@@ -97,7 +97,7 @@ const LastModificationInfo = ({
hideStatus={!isProjectOpenedNotTheLatestVersion}
/>
)}
-
+
{isCurrentProjectOpened ? (
Modifying
) : (
diff --git a/newIDE/app/src/MainFrame/EditorContainers/HomePage/GetStartedSection/EarnBadges.js b/newIDE/app/src/MainFrame/EditorContainers/HomePage/GetStartedSection/EarnBadges.js
index 6e53533a50bc..7539163c7671 100644
--- a/newIDE/app/src/MainFrame/EditorContainers/HomePage/GetStartedSection/EarnBadges.js
+++ b/newIDE/app/src/MainFrame/EditorContainers/HomePage/GetStartedSection/EarnBadges.js
@@ -224,7 +224,11 @@ export const EarnBadges = ({
forceMobileLayout={isMobileOrMediumWidth}
>
{badgesSlicedInArraysOfTwo.map((badges, index) => (
-
+
{badges.map(badge => (
Date: Thu, 12 Dec 2024 12:18:21 +0100
Subject: [PATCH 16/26] Fixes
---
.../src/GameDashboard/GameDashboardCard.js | 35 ++--
newIDE/app/src/GameDashboard/GamesList.js | 161 +++++++++++-------
.../GameDashboard/LeaderboardAdmin/index.js | 16 +-
.../Monetization/UserEarningsWidget.js | 20 ++-
.../src/GameDashboard/Wallet/WalletWidget.js | 3 +-
.../GameDashboard/Widgets/AnalyticsWidget.js | 5 +-
newIDE/app/src/GameDashboard/index.js | 81 ++++-----
.../src/Leaderboard/UseLeaderboardReplacer.js | 2 +-
.../HomePage/CreateSection/index.js | 29 ++--
.../UseMultiplayerLobbyConfigurator.js | 2 +-
.../src/UI/ShareDialog/SocialShareButtons.js | 2 +-
newIDE/app/src/Utils/UseCreateProject.js | 6 +-
.../app/src/Utils/UseGameAndBuildsManager.js | 9 +-
.../GameDashboard/GamesList.stories.js | 48 +++++-
...ories.js => UserEarningsWidget.stories.js} | 0
15 files changed, 250 insertions(+), 169 deletions(-)
rename newIDE/app/src/stories/componentStories/GameDashboard/Monetization/{UserEarnings.stories.js => UserEarningsWidget.stories.js} (100%)
diff --git a/newIDE/app/src/GameDashboard/GameDashboardCard.js b/newIDE/app/src/GameDashboard/GameDashboardCard.js
index 839b91f03f28..7e479a00360b 100644
--- a/newIDE/app/src/GameDashboard/GameDashboardCard.js
+++ b/newIDE/app/src/GameDashboard/GameDashboardCard.js
@@ -50,6 +50,7 @@ import PreferencesContext from '../MainFrame/Preferences/PreferencesContext';
import { textEllipsisStyle } from '../UI/TextEllipsis';
import FileWithLines from '../UI/CustomSvgIcons/FileWithLines';
import TextButton from '../UI/TextButton';
+import { Tooltip } from '@material-ui/core';
const electron = optionalRequire('electron');
const path = optionalRequire('path');
@@ -269,19 +270,29 @@ const GameDashboardCard = ({
{projectsList.length > 0 && game && (
<>
-
- onOpenGameManager({ game, widgetToScrollTo: 'projects' })
- }
- icon={}
- label={
-
- {projectsList.length}
-
+ {projectsList.length} project
+ ) : (
+ {projectsList.length} projects
+ )
}
- disabled={disabled}
- style={styles.projectFilesButton}
- />
+ >
+
+ onOpenGameManager({ game, widgetToScrollTo: 'projects' })
+ }
+ icon={}
+ label={
+
+ {projectsList.length}
+
+ }
+ disabled={disabled}
+ style={styles.projectFilesButton}
+ />
+
>
)}
diff --git a/newIDE/app/src/GameDashboard/GamesList.js b/newIDE/app/src/GameDashboard/GamesList.js
index 9011fb4566ba..1f4095ea59e0 100644
--- a/newIDE/app/src/GameDashboard/GamesList.js
+++ b/newIDE/app/src/GameDashboard/GamesList.js
@@ -46,14 +46,14 @@ const electron = optionalRequire('electron');
const isDesktop = !!electron;
-export const pageSize = 10;
+const pageSize = 10;
const styles = {
noGameMessageContainer: { padding: 10 },
refreshIconContainer: { fontSize: 20, display: 'flex', alignItems: 'center' },
};
-type OrderBy = 'totalSessions' | 'weeklySessions' | 'lastModifiedAt';
+export type OrderBy = 'totalSessions' | 'weeklySessions' | 'lastModifiedAt';
const totalSessionsSort = (
itemA: DashboardItem,
@@ -203,20 +203,20 @@ const getDashboardItemsToDisplay = ({
itemsToDisplay = [openedProjectDashboardItem, ...itemsToDisplay];
}
}
+
+ itemsToDisplay = itemsToDisplay.slice(
+ (currentPage - 1) * pageSize,
+ currentPage * pageSize
+ );
}
- const itemsWithoutUnsavedGames = itemsToDisplay.filter(
+ return itemsToDisplay.filter(
item =>
// Filter out unsaved games, unless they are the opened project.
!item.game ||
item.game.savedStatus !== 'draft' ||
(project && item.game.id === project.getProjectUuid())
);
-
- return itemsWithoutUnsavedGames.slice(
- (currentPage - 1) * pageSize,
- currentPage * pageSize
- );
};
type Props = {|
@@ -246,13 +246,18 @@ type Props = {|
askToCloseProject: () => Promise,
onSaveProject: () => Promise,
canSaveProject: boolean,
- currentPage: number,
- onCurrentPageChange: (currentPage: number) => void,
onDeleteCloudProject: (
i18n: I18nType,
file: FileMetadataAndStorageProviderName,
options?: { skipConfirmation: boolean }
) => Promise,
+ // Controls
+ currentPage: number,
+ setCurrentPage: (currentPage: number) => void,
+ orderBy: OrderBy,
+ setGamesListOrderBy: (orderBy: OrderBy) => void,
+ searchText: string,
+ setSearchText: (searchText: string) => void,
|};
const GamesList = ({
@@ -276,15 +281,15 @@ const GamesList = ({
canSaveProject,
// Make the page controlled, so that it can be saved when navigating to a game.
currentPage,
- onCurrentPageChange,
+ setCurrentPage,
+ orderBy,
+ setGamesListOrderBy,
+ searchText,
+ setSearchText,
}: Props) => {
const { cloudProjects, profile, onCloudProjectsChanged } = React.useContext(
AuthenticatedUserContext
);
- const [orderBy, setGamesListOrderBy] = React.useState(
- 'lastModifiedAt'
- );
- const [searchText, setSearchText] = React.useState('');
const { isMobile } = useResponsiveWindowSize();
const allRecentProjectFiles = useProjectsListFor(null);
@@ -306,6 +311,22 @@ const GamesList = ({
[games, allRecentProjectFiles]
);
+ const totalNumberOfPages = Math.ceil(allDashboardItems.length / pageSize);
+ const onCurrentPageChange = React.useCallback(
+ newPage => {
+ const minPage = 1;
+ const maxPage = totalNumberOfPages;
+ if (newPage < minPage) {
+ setCurrentPage(minPage);
+ } else if (newPage > maxPage) {
+ setCurrentPage(maxPage);
+ } else {
+ setCurrentPage(newPage);
+ }
+ },
+ [setCurrentPage, totalNumberOfPages]
+ );
+
const searchClient = React.useMemo(
() =>
new Fuse(allDashboardItems, {
@@ -467,60 +488,70 @@ const GamesList = ({
{allDashboardItems.length > 0 && (
-
- // $FlowFixMe
- setGamesListOrderBy(value)
- }
- >
-
-
-
+ {}}
+ placeholder={t`Search by name`}
/>
-
-
-
- {}}
- placeholder={t`Search by name`}
- />
-
- onCurrentPageChange(currentPage - 1)}
- disabled={!!searchText || currentPage === 1}
- size="small"
+
+
+
+ // $FlowFixMe
+ setGamesListOrderBy(value)
+ }
>
-
-
-
+
+
+
+
- {searchText ? 1 : currentPage}
-
- onCurrentPageChange(currentPage + 1)}
- disabled={
- !!searchText || currentPage * pageSize >= games.length
- }
- size="small"
+ expand
+ alignItems="center"
+ justifyContent="flex-end"
>
-
-
+ onCurrentPageChange(currentPage - 1)}
+ disabled={!!searchText || currentPage === 1}
+ size="small"
+ >
+
+
+
+ {searchText || totalNumberOfPages === 1
+ ? 1
+ : `${currentPage}/${totalNumberOfPages}`}
+
+ onCurrentPageChange(currentPage + 1)}
+ disabled={!!searchText || currentPage >= totalNumberOfPages}
+ size="small"
+ >
+
+
+
)}
diff --git a/newIDE/app/src/GameDashboard/LeaderboardAdmin/index.js b/newIDE/app/src/GameDashboard/LeaderboardAdmin/index.js
index 432b213565d8..1b0ec778aaa8 100644
--- a/newIDE/app/src/GameDashboard/LeaderboardAdmin/index.js
+++ b/newIDE/app/src/GameDashboard/LeaderboardAdmin/index.js
@@ -73,14 +73,6 @@ import Paper from '../../UI/Paper';
import SwitchHorizontal from '../../UI/CustomSvgIcons/SwitchHorizontal';
import { extractGDevelopApiErrorStatusAndCode } from '../../Utils/GDevelopServices/Errors';
-type Props = {|
- onLoading: boolean => void,
- project?: gdProject,
- leaderboardIdToSelectAtOpening?: string,
-|};
-
-type ContainerProps = {| ...Props, gameId: string |};
-
type ApiError = {|
action:
| 'entriesFetching'
@@ -187,6 +179,12 @@ const getSortOrderText = (currentLeaderboard: Leaderboard) => {
return Higher is better;
};
+type Props = {|
+ onLoading: boolean => void,
+ project?: gdProject,
+ leaderboardIdToSelectAtOpening?: string,
+|};
+
export const LeaderboardAdmin = ({
onLoading,
project,
@@ -1156,6 +1154,8 @@ export const LeaderboardAdmin = ({
);
};
+type ContainerProps = {| ...Props, gameId: string |};
+
const LeaderboardAdminContainer = ({
gameId,
...otherProps
diff --git a/newIDE/app/src/GameDashboard/Monetization/UserEarningsWidget.js b/newIDE/app/src/GameDashboard/Monetization/UserEarningsWidget.js
index 602a6bd86350..4efe862f6f92 100644
--- a/newIDE/app/src/GameDashboard/Monetization/UserEarningsWidget.js
+++ b/newIDE/app/src/GameDashboard/Monetization/UserEarningsWidget.js
@@ -34,9 +34,11 @@ type Props = {|
|};
const UserEarningsWidget = ({ fullWidth }: Props) => {
- const { userEarningsBalance, onRefreshEarningsBalance } = React.useContext(
- AuthenticatedUserContext
- );
+ const {
+ userEarningsBalance,
+ onRefreshEarningsBalance,
+ onRefreshLimits,
+ } = React.useContext(AuthenticatedUserContext);
const theme = React.useContext(GDevelopThemeContext);
const { isMobile } = useResponsiveWindowSize();
@@ -111,6 +113,14 @@ const UserEarningsWidget = ({ fullWidth }: Props) => {
[]
);
+ const onCashOrCreditOut = React.useCallback(
+ async () => {
+ await onRefreshEarningsBalance();
+ await onRefreshLimits();
+ },
+ [onRefreshEarningsBalance, onRefreshLimits]
+ );
+
const canCashout =
userEarningsBalance &&
earningsInMilliUsd >= userEarningsBalance.minAmountToCashoutInMilliUSDs;
@@ -222,7 +232,7 @@ const UserEarningsWidget = ({ fullWidth }: Props) => {
<>
Game earnings}
widgetName="earnings"
>
{content}
@@ -231,7 +241,7 @@ const UserEarningsWidget = ({ fullWidth }: Props) => {
setSelectedCashOutType(null)}
- onSuccess={onRefreshEarningsBalance}
+ onSuccess={onCashOrCreditOut}
type={selectedCashOutType}
/>
)}
diff --git a/newIDE/app/src/GameDashboard/Wallet/WalletWidget.js b/newIDE/app/src/GameDashboard/Wallet/WalletWidget.js
index 17d95a0a5b2c..e592b6add0aa 100644
--- a/newIDE/app/src/GameDashboard/Wallet/WalletWidget.js
+++ b/newIDE/app/src/GameDashboard/Wallet/WalletWidget.js
@@ -7,6 +7,7 @@ import Coin from '../../Credits/Icons/Coin';
import AuthenticatedUserContext from '../../Profile/AuthenticatedUserContext';
import { EarnBadges } from '../../MainFrame/EditorContainers/HomePage/GetStartedSection/EarnBadges';
import TextButton from '../../UI/TextButton';
+import { Trans } from '@lingui/macro';
type Props = {|
onOpenProfile: () => void,
@@ -32,7 +33,7 @@ const WalletWidget = ({
return (
Wallet}
topRightAction={
}
diff --git a/newIDE/app/src/GameDashboard/Widgets/AnalyticsWidget.js b/newIDE/app/src/GameDashboard/Widgets/AnalyticsWidget.js
index 0a7bdee9e4a2..56e9b8287d9f 100644
--- a/newIDE/app/src/GameDashboard/Widgets/AnalyticsWidget.js
+++ b/newIDE/app/src/GameDashboard/Widgets/AnalyticsWidget.js
@@ -22,10 +22,7 @@ import { getHelpLink } from '../../Utils/HelpLink';
import Window from '../../Utils/Window';
import Link from '../../UI/Link';
-const publishingHelpLink = getHelpLink(
- 'gdevelop5/publishing',
- 'publish-your-game'
-);
+const publishingHelpLink = getHelpLink('/publishing', 'publish-your-game');
const styles = { loadingSpace: { height: 100 } };
diff --git a/newIDE/app/src/GameDashboard/index.js b/newIDE/app/src/GameDashboard/index.js
index 58814648d46e..38dad0ee54d2 100644
--- a/newIDE/app/src/GameDashboard/index.js
+++ b/newIDE/app/src/GameDashboard/index.js
@@ -435,8 +435,8 @@ const GameDashboard = ({
[initialWidgetToScrollTo, widgetToScrollTo]
);
- React.useEffect(
- () => {
+ const fetchAuthenticatedData = React.useCallback(
+ async () => {
if (!profile) {
setFeedbacks(null);
setBuilds(null);
@@ -447,46 +447,45 @@ const GameDashboard = ({
return;
}
- const fetchAuthenticatedData = async () => {
- const [
- feedbacks,
- builds,
- gameRollingMetrics,
- leaderboards,
- recommendedMarketingPlan,
- ] = await Promise.all([
- listComments(getAuthorizationHeader, profile.id, {
- gameId: game.id,
- type: 'FEEDBACK',
- }),
- getBuilds(getAuthorizationHeader, profile.id, game.id),
- getGameMetricsFrom(
- getAuthorizationHeader,
- profile.id,
- game.id,
- oneWeekAgo.current.toISOString()
- ),
- listGameActiveLeaderboards(
- getAuthorizationHeader,
- profile.id,
- game.id
- ),
- getRecommendedMarketingPlan(getAuthorizationHeader, {
- gameId: game.id,
- userId: profile.id,
- }),
- fetchGameFeaturings(),
- ]);
- setFeedbacks(feedbacks);
- setBuilds(builds);
- setGameMetrics(gameRollingMetrics);
- setLeaderboards(leaderboards);
- setRecommendedMarketingPlan(recommendedMarketingPlan);
- };
+ const [
+ feedbacks,
+ builds,
+ gameRollingMetrics,
+ leaderboards,
+ recommendedMarketingPlan,
+ ] = await Promise.all([
+ listComments(getAuthorizationHeader, profile.id, {
+ gameId: game.id,
+ type: 'FEEDBACK',
+ }),
+ getBuilds(getAuthorizationHeader, profile.id, game.id),
+ getGameMetricsFrom(
+ getAuthorizationHeader,
+ profile.id,
+ game.id,
+ oneWeekAgo.current.toISOString()
+ ),
+ listGameActiveLeaderboards(getAuthorizationHeader, profile.id, game.id),
+ getRecommendedMarketingPlan(getAuthorizationHeader, {
+ gameId: game.id,
+ userId: profile.id,
+ }),
+ fetchGameFeaturings(),
+ ]);
+ setFeedbacks(feedbacks);
+ setBuilds(builds);
+ setGameMetrics(gameRollingMetrics);
+ setLeaderboards(leaderboards);
+ setRecommendedMarketingPlan(recommendedMarketingPlan);
+ },
+ [fetchGameFeaturings, game.id, getAuthorizationHeader, profile]
+ );
+ React.useEffect(
+ () => {
fetchAuthenticatedData();
},
- [getAuthorizationHeader, profile, fetchGameFeaturings, game.id]
+ [fetchAuthenticatedData]
);
const onClickBack = React.useCallback(
@@ -494,10 +493,12 @@ const GameDashboard = ({
if (currentView === 'details') {
onBack();
} else {
+ // Refresh the data when going back to the main view.
+ fetchAuthenticatedData();
setCurrentView('details');
}
},
- [currentView, onBack, setCurrentView]
+ [currentView, onBack, setCurrentView, fetchAuthenticatedData]
);
return (
diff --git a/newIDE/app/src/Leaderboard/UseLeaderboardReplacer.js b/newIDE/app/src/Leaderboard/UseLeaderboardReplacer.js
index b04ff8ee2f98..50fa5f4a5881 100644
--- a/newIDE/app/src/Leaderboard/UseLeaderboardReplacer.js
+++ b/newIDE/app/src/Leaderboard/UseLeaderboardReplacer.js
@@ -245,7 +245,7 @@ export const replaceLeaderboardsInProject = async ({
projectName: project.getName(),
projectAuthor: project.getAuthor(),
// Assume the project is not saved at this stage.
- isProjectSaved: false,
+ savedStatus: 'draft',
})
);
} catch (error) {
diff --git a/newIDE/app/src/MainFrame/EditorContainers/HomePage/CreateSection/index.js b/newIDE/app/src/MainFrame/EditorContainers/HomePage/CreateSection/index.js
index d3157d55e1f2..58659876ca2f 100644
--- a/newIDE/app/src/MainFrame/EditorContainers/HomePage/CreateSection/index.js
+++ b/newIDE/app/src/MainFrame/EditorContainers/HomePage/CreateSection/index.js
@@ -6,7 +6,7 @@ import { I18n as I18nType } from '@lingui/core';
import SectionContainer, { SectionRow } from '../SectionContainer';
import ErrorBoundary from '../../../../UI/ErrorBoundary';
import AuthenticatedUserContext from '../../../../Profile/AuthenticatedUserContext';
-import GamesList, { pageSize } from '../../../../GameDashboard/GamesList';
+import GamesList, { type OrderBy } from '../../../../GameDashboard/GamesList';
import {
deleteGame,
registerGame,
@@ -198,20 +198,10 @@ const CreateSection = ({
);
const [currentPage, setCurrentPage] = React.useState(1);
- const onCurrentPageChange = React.useCallback(
- newPage => {
- const minPage = 1;
- const maxPage = games ? Math.ceil(games.length / pageSize) : 1;
- if (newPage < minPage) {
- setCurrentPage(minPage);
- } else if (newPage > maxPage) {
- setCurrentPage(maxPage);
- } else {
- setCurrentPage(newPage);
- }
- },
- [setCurrentPage, games]
+ const [orderBy, setGamesListOrderBy] = React.useState(
+ 'lastModifiedAt'
);
+ const [searchText, setSearchText] = React.useState('');
const onUnregisterGame = React.useCallback(
async (
@@ -402,7 +392,7 @@ const CreateSection = ({
projectName: file.fileMetadata.name,
projectAuthor: username,
// A project is always saved when appearing in the list of recent projects.
- isProjectSaved: true,
+ savedStatus: 'saved',
})
);
return game;
@@ -550,10 +540,15 @@ const CreateSection = ({
askToCloseProject={askToCloseProject}
onSaveProject={onSaveProject}
canSaveProject={canSaveProject}
- currentPage={currentPage}
- onCurrentPageChange={onCurrentPageChange}
onDeleteCloudProject={onDeleteCloudProject}
onRegisterProject={onRegisterProject}
+ // Controls
+ currentPage={currentPage}
+ setCurrentPage={setCurrentPage}
+ orderBy={orderBy}
+ setGamesListOrderBy={setGamesListOrderBy}
+ searchText={searchText}
+ setSearchText={setSearchText}
/>
{isMobile && limits && hasTooManyCloudProjects && (
{
url={url}
className={classNames.root}
style={styles.icon}
- quote={`Try the game I just created with GDevelop.io`}
+ // Quote has been deprecated by Facebook, we can't fill the text of the share dialog, only the hashtag.
hashtag="#gdevelop"
>
diff --git a/newIDE/app/src/Utils/UseCreateProject.js b/newIDE/app/src/Utils/UseCreateProject.js
index 8f4eff82c95c..16cedd3e51b4 100644
--- a/newIDE/app/src/Utils/UseCreateProject.js
+++ b/newIDE/app/src/Utils/UseCreateProject.js
@@ -171,10 +171,12 @@ const useCreateProject = ({
projectName: currentProject.getName(),
projectAuthor: currentProject.getAuthor(),
// Project is saved if choosing cloud or local storage provider.
- isProjectSaved:
+ savedStatus:
newProjectSetup.storageProvider.internalName ===
'LocalFile' ||
- newProjectSetup.storageProvider.internalName === 'Cloud',
+ newProjectSetup.storageProvider.internalName === 'Cloud'
+ ? 'saved'
+ : 'draft',
})
);
await onGameRegistered();
diff --git a/newIDE/app/src/Utils/UseGameAndBuildsManager.js b/newIDE/app/src/Utils/UseGameAndBuildsManager.js
index 250e17356d4c..1edbf166b9ff 100644
--- a/newIDE/app/src/Utils/UseGameAndBuildsManager.js
+++ b/newIDE/app/src/Utils/UseGameAndBuildsManager.js
@@ -11,6 +11,7 @@ import {
registerGame,
setGameUserAcls,
type Game,
+ type SavedStatus,
} from './GDevelopServices/Game';
import { extractGDevelopApiErrorStatusAndCode } from './GDevelopServices/Errors';
import { useMultiplayerLobbyConfigurator } from '../MainFrame/UseMultiplayerLobbyConfigurator';
@@ -23,17 +24,17 @@ export const getDefaultRegisterGameProperties = ({
projectId,
projectName,
projectAuthor,
- isProjectSaved,
+ savedStatus,
}: {|
projectId: string,
projectName: ?string,
projectAuthor: ?string,
- isProjectSaved: boolean,
+ savedStatus: SavedStatus,
|}) => ({
gameId: projectId,
authorName: projectAuthor || 'Unspecified publisher',
gameName: projectName || 'Untitled game',
- savedStatus: isProjectSaved ? 'saved' : 'draft',
+ savedStatus,
});
export type GameManager = {|
@@ -163,7 +164,7 @@ export const useGameManager = ({
projectAuthor: project.getAuthor(),
// Assume a project going through the export process is not saved yet.
// It will be marked as saved when the user saves it next anyway.
- isProjectSaved: false,
+ savedStatus: 'draft',
})
);
diff --git a/newIDE/app/src/stories/componentStories/GameDashboard/GamesList.stories.js b/newIDE/app/src/stories/componentStories/GameDashboard/GamesList.stories.js
index e4ba7bf13d60..c32e889c9c4a 100644
--- a/newIDE/app/src/stories/componentStories/GameDashboard/GamesList.stories.js
+++ b/newIDE/app/src/stories/componentStories/GameDashboard/GamesList.stories.js
@@ -39,6 +39,10 @@ export const NoGamesOrProjects = () => {
getRecentProjectFiles: () => projectFiles,
};
+ const [currentPage, setCurrentPage] = React.useState(1);
+ const [orderBy, setOrderBy] = React.useState('lastModifiedAt');
+ const [searchText, setSearchText] = React.useState('');
+
return (
@@ -59,10 +63,14 @@ export const NoGamesOrProjects = () => {
onOpenNewProjectSetupDialog={action('onOpenNewProjectSetupDialog')}
onSaveProject={action('onSaveProject')}
onUnregisterGame={action('onUnregisterGame')}
- currentPage={1}
- onCurrentPageChange={action('onCurrentPageChange')}
onDeleteCloudProject={action('onDeleteCloudProject')}
onRegisterProject={action('onRegisterProject')}
+ currentPage={currentPage}
+ setCurrentPage={setCurrentPage}
+ orderBy={orderBy}
+ setGamesListOrderBy={setOrderBy}
+ searchText={searchText}
+ setSearchText={setSearchText}
/>
@@ -89,6 +97,10 @@ export const WithOnlyGames = () => {
getRecentProjectFiles: () => projectFiles,
};
+ const [currentPage, setCurrentPage] = React.useState(1);
+ const [orderBy, setOrderBy] = React.useState('lastModifiedAt');
+ const [searchText, setSearchText] = React.useState('');
+
return (
@@ -109,10 +121,14 @@ export const WithOnlyGames = () => {
onOpenNewProjectSetupDialog={action('onOpenNewProjectSetupDialog')}
onSaveProject={action('onSaveProject')}
onUnregisterGame={action('onUnregisterGame')}
- currentPage={1}
- onCurrentPageChange={action('onCurrentPageChange')}
onDeleteCloudProject={action('onDeleteCloudProject')}
onRegisterProject={action('onRegisterProject')}
+ currentPage={currentPage}
+ setCurrentPage={setCurrentPage}
+ orderBy={orderBy}
+ setGamesListOrderBy={setOrderBy}
+ searchText={searchText}
+ setSearchText={setSearchText}
/>
@@ -134,6 +150,10 @@ export const WithOnlyProjects = () => {
getRecentProjectFiles: () => projectFiles,
};
+ const [currentPage, setCurrentPage] = React.useState(1);
+ const [orderBy, setOrderBy] = React.useState('lastModifiedAt');
+ const [searchText, setSearchText] = React.useState('');
+
return (
@@ -154,10 +174,14 @@ export const WithOnlyProjects = () => {
onOpenNewProjectSetupDialog={action('onOpenNewProjectSetupDialog')}
onSaveProject={action('onSaveProject')}
onUnregisterGame={action('onUnregisterGame')}
- currentPage={1}
- onCurrentPageChange={action('onCurrentPageChange')}
onDeleteCloudProject={action('onDeleteCloudProject')}
onRegisterProject={action('onRegisterProject')}
+ currentPage={currentPage}
+ setCurrentPage={setCurrentPage}
+ orderBy={orderBy}
+ setGamesListOrderBy={setOrderBy}
+ searchText={searchText}
+ setSearchText={setSearchText}
/>
@@ -186,6 +210,10 @@ export const WithGamesAndProjects = () => {
getRecentProjectFiles: () => projectFiles,
};
+ const [currentPage, setCurrentPage] = React.useState(1);
+ const [orderBy, setOrderBy] = React.useState('lastModifiedAt');
+ const [searchText, setSearchText] = React.useState('');
+
return (
@@ -206,10 +234,14 @@ export const WithGamesAndProjects = () => {
onOpenNewProjectSetupDialog={action('onOpenNewProjectSetupDialog')}
onSaveProject={action('onSaveProject')}
onUnregisterGame={action('onUnregisterGame')}
- currentPage={1}
- onCurrentPageChange={action('onCurrentPageChange')}
onDeleteCloudProject={action('onDeleteCloudProject')}
onRegisterProject={action('onRegisterProject')}
+ currentPage={currentPage}
+ setCurrentPage={setCurrentPage}
+ orderBy={orderBy}
+ setGamesListOrderBy={setOrderBy}
+ searchText={searchText}
+ setSearchText={setSearchText}
/>
diff --git a/newIDE/app/src/stories/componentStories/GameDashboard/Monetization/UserEarnings.stories.js b/newIDE/app/src/stories/componentStories/GameDashboard/Monetization/UserEarningsWidget.stories.js
similarity index 100%
rename from newIDE/app/src/stories/componentStories/GameDashboard/Monetization/UserEarnings.stories.js
rename to newIDE/app/src/stories/componentStories/GameDashboard/Monetization/UserEarningsWidget.stories.js
From a3c56ce1784f1f0b4fbd352da4493e85212c12ac Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Cl=C3=A9ment=20Pasteau?=
<4895034+ClementPasteau@users.noreply.github.com>
Date: Thu, 12 Dec 2024 14:08:00 +0100
Subject: [PATCH 17/26] Add footer & skeleton while loading
---
.../src/GameDashboard/GameDashboardCard.js | 10 +--
newIDE/app/src/GameDashboard/GamesList.js | 79 +++++++++++++------
newIDE/app/src/GameDashboard/UseGamesList.js | 2 +-
newIDE/app/src/GameDashboard/index.js | 14 ++++
.../HomePage/CreateSection/index.js | 2 +-
newIDE/app/src/UI/Slideshow/Slideshow.js | 4 -
6 files changed, 75 insertions(+), 36 deletions(-)
diff --git a/newIDE/app/src/GameDashboard/GameDashboardCard.js b/newIDE/app/src/GameDashboard/GameDashboardCard.js
index 7e479a00360b..50e33596a8ee 100644
--- a/newIDE/app/src/GameDashboard/GameDashboardCard.js
+++ b/newIDE/app/src/GameDashboard/GameDashboardCard.js
@@ -54,6 +54,9 @@ import { Tooltip } from '@material-ui/core';
const electron = optionalRequire('electron');
const path = optionalRequire('path');
+export const getThumbnailWidth = ({ isMobile }: { isMobile: boolean }) =>
+ isMobile ? undefined : Math.min(245, Math.max(130, window.innerWidth / 4));
+
const styles = {
buttonsContainer: {
display: 'flex',
@@ -359,12 +362,7 @@ const GameDashboardCard = ({
gameId={game ? game.id : undefined}
thumbnailUrl={gameThumbnailUrl}
background="light"
- width={
- isMobile
- ? undefined
- : // On medium/large screens, adapt the size to the width of the window.
- Math.min(245, Math.max(130, window.innerWidth / 4))
- }
+ width={getThumbnailWidth({ isMobile })}
/>
);
diff --git a/newIDE/app/src/GameDashboard/GamesList.js b/newIDE/app/src/GameDashboard/GamesList.js
index 1f4095ea59e0..351decce543e 100644
--- a/newIDE/app/src/GameDashboard/GamesList.js
+++ b/newIDE/app/src/GameDashboard/GamesList.js
@@ -5,7 +5,10 @@ import { I18n } from '@lingui/react';
import { type I18n as I18nType } from '@lingui/core';
import { Trans, t } from '@lingui/macro';
import { type Game } from '../Utils/GDevelopServices/Game';
-import GameDashboardCard, { type DashboardItem } from './GameDashboardCard';
+import GameDashboardCard, {
+ getThumbnailWidth,
+ type DashboardItem,
+} from './GameDashboardCard';
import {
ColumnStackLayout,
LineStackLayout,
@@ -42,6 +45,7 @@ import AuthenticatedUserContext from '../Profile/AuthenticatedUserContext';
import Refresh from '../UI/CustomSvgIcons/Refresh';
import optionalRequire from '../Utils/OptionalRequire';
import TextButton from '../UI/TextButton';
+import Skeleton from '@material-ui/lab/Skeleton';
const electron = optionalRequire('electron');
const isDesktop = !!electron;
@@ -51,6 +55,10 @@ const pageSize = 10;
const styles = {
noGameMessageContainer: { padding: 10 },
refreshIconContainer: { fontSize: 20, display: 'flex', alignItems: 'center' },
+ gameLoadingSkeleton: {
+ // Display a skeleton with the same aspect and border as the game card:
+ borderRadius: 8,
+ },
};
export type OrderBy = 'totalSessions' | 'weeklySessions' | 'lastModifiedAt';
@@ -127,12 +135,13 @@ const getDashboardItemsToDisplay = ({
}: {|
project: ?gdProject,
currentFileMetadata: ?FileMetadata,
- allDashboardItems: Array,
+ allDashboardItems: ?Array,
searchText: string,
searchClient: Fuse,
currentPage: number,
orderBy: OrderBy,
-|}): Array => {
+|}): ?Array => {
+ if (!allDashboardItems) return null;
let itemsToDisplay: DashboardItem[] = allDashboardItems;
if (searchText) {
@@ -223,7 +232,7 @@ type Props = {|
storageProviders: Array,
project: ?gdProject,
currentFileMetadata: ?FileMetadata,
- games: Array,
+ games: ?Array,
onRefreshGames: () => Promise,
onOpenGameManager: ({
game: Game,
@@ -291,10 +300,12 @@ const GamesList = ({
AuthenticatedUserContext
);
const { isMobile } = useResponsiveWindowSize();
+ const gameThumbnailWidth = getThumbnailWidth({ isMobile });
const allRecentProjectFiles = useProjectsListFor(null);
- const allDashboardItems: DashboardItem[] = React.useMemo(
+ const allDashboardItems: ?(DashboardItem[]) = React.useMemo(
() => {
+ if (!games) return null;
const projectFilesWithGame = games.map(game => {
const projectFiles = allRecentProjectFiles.filter(
file => file.fileMetadata.gameId === game.id
@@ -311,7 +322,9 @@ const GamesList = ({
[games, allRecentProjectFiles]
);
- const totalNumberOfPages = Math.ceil(allDashboardItems.length / pageSize);
+ const totalNumberOfPages = allDashboardItems
+ ? Math.ceil(allDashboardItems.length / pageSize)
+ : 1;
const onCurrentPageChange = React.useCallback(
newPage => {
const minPage = 1;
@@ -329,7 +342,7 @@ const GamesList = ({
const searchClient = React.useMemo(
() =>
- new Fuse(allDashboardItems, {
+ new Fuse(allDashboardItems || [], {
...sharedFuseConfiguration,
keys: [
{ name: 'game.gameName', weight: 1 },
@@ -339,9 +352,10 @@ const GamesList = ({
[allDashboardItems]
);
- const [displayedDashboardItems, setDisplayedDashboardItems] = React.useState<
- Array
- >(
+ const [
+ displayedDashboardItems,
+ setDisplayedDashboardItems,
+ ] = React.useState>(
getDashboardItemsToDisplay({
project,
currentFileMetadata,
@@ -443,18 +457,20 @@ const GamesList = ({
Games
- {allDashboardItems.length > 0 && (
-
-
-
-
-
- )}
+
+
+
+
+
- {allDashboardItems.length > 0 && (
+ {allDashboardItems && allDashboardItems.length > 0 && (
)}
- {displayedDashboardItems.length > 0 ? (
+ {!displayedDashboardItems &&
+ Array.from({ length: pageSize }).map((_, i) => (
+
+
+
+ ))}
+ {displayedDashboardItems && displayedDashboardItems.length > 0 ? (
displayedDashboardItems
.map((dashboardItem, index) => {
const game = dashboardItem.game;
diff --git a/newIDE/app/src/GameDashboard/UseGamesList.js b/newIDE/app/src/GameDashboard/UseGamesList.js
index 6232b2e5c721..771c7d5712b2 100644
--- a/newIDE/app/src/GameDashboard/UseGamesList.js
+++ b/newIDE/app/src/GameDashboard/UseGamesList.js
@@ -33,7 +33,7 @@ const useGamesList = (): GamesList => {
const fetchGames = React.useCallback(
async (): Promise => {
if (!authenticated || !firebaseUser) {
- setGames(null);
+ setGames([]);
return;
}
if (gamesFetchingPromise.current) return gamesFetchingPromise.current;
diff --git a/newIDE/app/src/GameDashboard/index.js b/newIDE/app/src/GameDashboard/index.js
index 38dad0ee54d2..f2c419d2a862 100644
--- a/newIDE/app/src/GameDashboard/index.js
+++ b/newIDE/app/src/GameDashboard/index.js
@@ -57,6 +57,16 @@ import PublicGamePropertiesDialog, {
import useAlertDialog from '../UI/Alert/useAlertDialog';
import { showErrorBox } from '../UI/Messages/MessageBox';
import ProjectsWidget from './Widgets/ProjectsWidget';
+import { useResponsiveWindowSize } from '../UI/Responsive/ResponsiveWindowMeasurer';
+
+const styles = {
+ mobileFooter: {
+ height: 150,
+ },
+ desktopFooter: {
+ height: 200,
+ },
+};
export type GameDetailsTab =
| 'details'
@@ -113,6 +123,7 @@ const GameDashboard = ({
initialWidgetToScrollTo,
}: Props) => {
const grid = React.useRef(null);
+ const { isMobile } = useResponsiveWindowSize();
const [widgetToScrollTo, setWidgetToScrollTo] = React.useState(
initialWidgetToScrollTo
);
@@ -599,6 +610,9 @@ const GameDashboard = ({
onSeeAllBuilds={() => setCurrentView('builds')}
/>
+
)}
diff --git a/newIDE/app/src/MainFrame/EditorContainers/HomePage/CreateSection/index.js b/newIDE/app/src/MainFrame/EditorContainers/HomePage/CreateSection/index.js
index 58659876ca2f..c32a8d8e09c2 100644
--- a/newIDE/app/src/MainFrame/EditorContainers/HomePage/CreateSection/index.js
+++ b/newIDE/app/src/MainFrame/EditorContainers/HomePage/CreateSection/index.js
@@ -517,7 +517,7 @@ const CreateSection = ({
Date: Thu, 12 Dec 2024 15:41:33 +0100
Subject: [PATCH 18/26] Fix showing last week chart data when no sessions in
the last week
---
.../GameDashboard/Widgets/AnalyticsWidget.js | 17 ++++++++++++++---
newIDE/app/src/GameDashboard/index.js | 18 +++++++++++++-----
2 files changed, 27 insertions(+), 8 deletions(-)
diff --git a/newIDE/app/src/GameDashboard/Widgets/AnalyticsWidget.js b/newIDE/app/src/GameDashboard/Widgets/AnalyticsWidget.js
index 56e9b8287d9f..65ece341d105 100644
--- a/newIDE/app/src/GameDashboard/Widgets/AnalyticsWidget.js
+++ b/newIDE/app/src/GameDashboard/Widgets/AnalyticsWidget.js
@@ -36,14 +36,25 @@ type Props = {|
const AnalyticsWidget = ({ game, onSeeAll, gameMetrics, gameUrl }: Props) => {
const hasNoSession = gameMetrics && gameMetrics.length === 0;
const { isMobile } = useResponsiveWindowSize();
- const chartData = React.useMemo(() => buildLastWeekChartData(gameMetrics), [
- gameMetrics,
- ]);
+ const oneWeekAgoIsoDate = new Date(
+ new Date().setHours(0, 0, 0, 0) - 7 * 24 * 3600 * 1000
+ ).toISOString();
const [
marketingPlansDialogOpen,
setMarketingPlansDialogOpen,
] = React.useState(false);
+ const chartData = React.useMemo(
+ () => {
+ const lastWeekGameMetrics = gameMetrics
+ ? gameMetrics.filter(metrics => metrics.date > oneWeekAgoIsoDate)
+ : null;
+
+ return buildLastWeekChartData(lastWeekGameMetrics);
+ },
+ [gameMetrics, oneWeekAgoIsoDate]
+ );
+
return (
<>
diff --git a/newIDE/app/src/GameDashboard/index.js b/newIDE/app/src/GameDashboard/index.js
index f2c419d2a862..ee848a8e900c 100644
--- a/newIDE/app/src/GameDashboard/index.js
+++ b/newIDE/app/src/GameDashboard/index.js
@@ -58,6 +58,8 @@ import useAlertDialog from '../UI/Alert/useAlertDialog';
import { showErrorBox } from '../UI/Messages/MessageBox';
import ProjectsWidget from './Widgets/ProjectsWidget';
import { useResponsiveWindowSize } from '../UI/Responsive/ResponsiveWindowMeasurer';
+import { formatISO, subDays } from 'date-fns';
+import { daysShownForYear } from './GameAnalyticsEvaluator';
const styles = {
mobileFooter: {
@@ -152,9 +154,9 @@ const GameDashboard = ({
const [leaderboards, setLeaderboards] = React.useState>(
null
);
- const oneWeekAgo = React.useRef(
- new Date(new Date().setHours(0, 0, 0, 0) - 7 * 24 * 3600 * 1000)
- );
+ const lastYearIsoDate = formatISO(subDays(new Date(), daysShownForYear), {
+ representation: 'date',
+ });
const webBuilds = builds
? builds.filter(build => build.type === 'web-build')
@@ -474,7 +476,7 @@ const GameDashboard = ({
getAuthorizationHeader,
profile.id,
game.id,
- oneWeekAgo.current.toISOString()
+ lastYearIsoDate
),
listGameActiveLeaderboards(getAuthorizationHeader, profile.id, game.id),
getRecommendedMarketingPlan(getAuthorizationHeader, {
@@ -489,7 +491,13 @@ const GameDashboard = ({
setLeaderboards(leaderboards);
setRecommendedMarketingPlan(recommendedMarketingPlan);
},
- [fetchGameFeaturings, game.id, getAuthorizationHeader, profile]
+ [
+ fetchGameFeaturings,
+ game.id,
+ getAuthorizationHeader,
+ profile,
+ lastYearIsoDate,
+ ]
);
React.useEffect(
From 5c1ecf6c2769347cac9d89c7e64677735bdbdc7a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Cl=C3=A9ment=20Pasteau?=
<4895034+ClementPasteau@users.noreply.github.com>
Date: Thu, 12 Dec 2024 16:12:52 +0100
Subject: [PATCH 19/26] Fix bunch of exact types
---
newIDE/app/src/AssetStore/ShopTiles.js | 2 +-
.../CommandPalette/AutocompletePicker.js | 2 +-
.../src/Debugger/Profiler/MeasuresTable.js | 8 ++-
.../src/EventsBasedBehaviorEditor/index.js | 3 +-
.../app/src/EventsSheet/EventsTree/index.js | 8 +--
.../src/ExportAndShare/Builds/BuildCard.js | 2 +-
.../ShareDialog/ExportLauncher.js | 2 +-
.../ExportAndShare/ShareDialog/PublishHome.js | 2 +-
.../GameDashboard/Feedbacks/GameFeedback.js | 2 +-
.../src/GameDashboard/GameAnalyticsCharts.js | 2 +-
.../src/GameDashboard/GameDashboardCard.js | 64 +++++++++++--------
.../InstancesEditor/InstancesList/index.js | 6 +-
newIDE/app/src/InstancesEditor/index.js | 2 +-
.../HomePage/InAppTutorials/GuidedLessons.js | 6 +-
.../LearnSection/EducationCurriculumLesson.js | 2 +-
.../HomePage/TeamSection/TeamSection.spec.js | 2 +-
newIDE/app/src/MainFrame/index.js | 4 +-
.../Editors/SpriteEditor/SpritesList.js | 2 +-
.../src/Profile/AuthenticatedUserProvider.js | 2 +-
.../src/QuickCustomization/QuickPublish.js | 2 +-
newIDE/app/src/UI/Dialog.js | 6 +-
newIDE/app/src/UI/GravatarUrl.js | 2 +-
.../app/src/Utils/GDevelopServices/Build.js | 2 +-
.../app/src/Utils/GDevelopServices/Project.js | 2 +-
newIDE/app/src/Utils/UseDisplayNewFeature.js | 4 +-
.../AssetStore/AssetDetails.stories.js | 2 +-
.../AssetPackInstallDialog.stories.js | 2 +-
.../AssetStore/AssetStore.stories.js | 2 +-
.../CustomObjectPackResults.stories.js | 2 +-
.../ResourceStore/ResourceStore.stories.js | 2 +-
.../QuickPublish.stories.js | 2 +-
31 files changed, 89 insertions(+), 64 deletions(-)
diff --git a/newIDE/app/src/AssetStore/ShopTiles.js b/newIDE/app/src/AssetStore/ShopTiles.js
index d44da0068d64..d1e3488908bf 100644
--- a/newIDE/app/src/AssetStore/ShopTiles.js
+++ b/newIDE/app/src/AssetStore/ShopTiles.js
@@ -112,7 +112,7 @@ const styles = {
},
};
-const useStylesForGridListItem = ({ disabled }: { disabled?: boolean }) =>
+const useStylesForGridListItem = ({ disabled }: {| disabled?: boolean |}) =>
makeStyles(theme =>
createStyles({
tile: !disabled
diff --git a/newIDE/app/src/CommandPalette/CommandPalette/AutocompletePicker.js b/newIDE/app/src/CommandPalette/CommandPalette/AutocompletePicker.js
index dc7999144ea3..8a01b7e49ca4 100644
--- a/newIDE/app/src/CommandPalette/CommandPalette/AutocompletePicker.js
+++ b/newIDE/app/src/CommandPalette/CommandPalette/AutocompletePicker.js
@@ -55,7 +55,7 @@ type Item = NamedCommand | CommandOption | GoToWikiCommand;
const HitPrimaryText = (
hit: any,
- { removeLastLevel }: { removeLastLevel: boolean }
+ { removeLastLevel }: {| removeLastLevel: boolean |}
) => {
const classes = useStyles();
diff --git a/newIDE/app/src/Debugger/Profiler/MeasuresTable.js b/newIDE/app/src/Debugger/Profiler/MeasuresTable.js
index 7ef18fb810a4..d7cbf08321cd 100644
--- a/newIDE/app/src/Debugger/Profiler/MeasuresTable.js
+++ b/newIDE/app/src/Debugger/Profiler/MeasuresTable.js
@@ -89,7 +89,7 @@ const MeasuresTable = (props: Props) => {
});
};
- const rowClassName = ({ index }: { index: number }) => {
+ const rowClassName = ({ index }: {| index: number |}) => {
if (index < 0) {
return 'tableHeaderRow';
} else {
@@ -97,7 +97,11 @@ const MeasuresTable = (props: Props) => {
}
};
- const renderSectionNameCell = ({ rowData }: { rowData: ProfilerRowData }) => {
+ const renderSectionNameCell = ({
+ rowData,
+ }: {|
+ rowData: ProfilerRowData,
+ |}) => {
return (
diff --git a/newIDE/app/src/EventsBasedBehaviorEditor/index.js b/newIDE/app/src/EventsBasedBehaviorEditor/index.js
index 4e301cec0734..89a6335b6ea9 100644
--- a/newIDE/app/src/EventsBasedBehaviorEditor/index.js
+++ b/newIDE/app/src/EventsBasedBehaviorEditor/index.js
@@ -2,7 +2,6 @@
import { Trans } from '@lingui/macro';
import { t } from '@lingui/macro';
import { I18n } from '@lingui/react';
-import { type I18n as I18nType } from '@lingui/core';
import * as React from 'react';
import TextField from '../UI/TextField';
@@ -67,7 +66,7 @@ export default function EventsBasedBehaviorEditor({
return (
- {({ i18n }: { i18n: I18nType }) => (
+ {({ i18n }) => (
Node,
+ title: (node: {| node: SortableTreeNode |}) => Node,
children: Array,
expanded: boolean,
@@ -700,7 +700,7 @@ export default class ThemableEventsTree extends Component<
}
};
- _onVisibilityToggle = ({ node }: { node: SortableTreeNode }) => {
+ _onVisibilityToggle = ({ node }: {| node: SortableTreeNode |}) => {
const { event } = node;
if (!event) return;
@@ -768,7 +768,7 @@ export default class ThemableEventsTree extends Component<
this.forceEventsUpdate();
};
- _getRowHeight = ({ node }: { node: ?SortableTreeNode }) => {
+ _getRowHeight = ({ node }: {| node: ?SortableTreeNode |}) => {
if (!node) return 0;
if (!node.event) return node.fixedHeight || 0;
@@ -787,7 +787,7 @@ export default class ThemableEventsTree extends Component<
}
};
- _renderEvent = ({ node }: { node: SortableTreeNode }) => {
+ _renderEvent = ({ node }: {| node: SortableTreeNode |}) => {
const { event, depth, disabled } = node;
if (!event) return null;
const { DragSourceAndDropTarget, DropTarget } = this;
diff --git a/newIDE/app/src/ExportAndShare/Builds/BuildCard.js b/newIDE/app/src/ExportAndShare/Builds/BuildCard.js
index 246563e78e43..0de2a075cac5 100644
--- a/newIDE/app/src/ExportAndShare/Builds/BuildCard.js
+++ b/newIDE/app/src/ExportAndShare/Builds/BuildCard.js
@@ -96,7 +96,7 @@ const getIcon = (
}
};
-const BuildAndCreatedAt = ({ build }: { build: Build }) => (
+const BuildAndCreatedAt = ({ build }: {| build: Build |}) => (
{getIcon(build.type)}
diff --git a/newIDE/app/src/ExportAndShare/ShareDialog/ExportLauncher.js b/newIDE/app/src/ExportAndShare/ShareDialog/ExportLauncher.js
index bb9b7bb53b9d..c46b8041cba8 100644
--- a/newIDE/app/src/ExportAndShare/ShareDialog/ExportLauncher.js
+++ b/newIDE/app/src/ExportAndShare/ShareDialog/ExportLauncher.js
@@ -58,7 +58,7 @@ type Props = {|
uiMode?: 'minimal',
onExportLaunched?: () => void,
- onExportSucceeded?: ({ build: ?Build }) => Promise,
+ onExportSucceeded?: ({| build: ?Build |}) => Promise,
onExportErrored?: () => void,
|};
diff --git a/newIDE/app/src/ExportAndShare/ShareDialog/PublishHome.js b/newIDE/app/src/ExportAndShare/ShareDialog/PublishHome.js
index 8273a1d7d174..15e05becd39e 100644
--- a/newIDE/app/src/ExportAndShare/ShareDialog/PublishHome.js
+++ b/newIDE/app/src/ExportAndShare/ShareDialog/PublishHome.js
@@ -73,7 +73,7 @@ const styles = {
},
};
-const getSectionLabel = ({ section }: { section: ExporterSection }) => {
+const getSectionLabel = ({ section }: {| section: ExporterSection |}) => {
switch (section) {
case 'browser':
return Browser;
diff --git a/newIDE/app/src/GameDashboard/Feedbacks/GameFeedback.js b/newIDE/app/src/GameDashboard/Feedbacks/GameFeedback.js
index 515ed45a88f7..8939685988c9 100644
--- a/newIDE/app/src/GameDashboard/Feedbacks/GameFeedback.js
+++ b/newIDE/app/src/GameDashboard/Feedbacks/GameFeedback.js
@@ -71,7 +71,7 @@ const pushOrCreateKey = (
const groupFeedbacks = (
i18n: I18nType,
feedbacks: Array,
- { build, date }: { build: boolean, date: boolean }
+ { build, date }: {| build: boolean, date: boolean |}
): { [buildIdOrDate: string]: Array } => {
const feedbacksByBuild = feedbacks.reduce((acc, feedback) => {
if (build) {
diff --git a/newIDE/app/src/GameDashboard/GameAnalyticsCharts.js b/newIDE/app/src/GameDashboard/GameAnalyticsCharts.js
index cad54965d5db..ec48a174ea38 100644
--- a/newIDE/app/src/GameDashboard/GameAnalyticsCharts.js
+++ b/newIDE/app/src/GameDashboard/GameAnalyticsCharts.js
@@ -75,7 +75,7 @@ const CustomTooltip = ({
name,
unit,
value,
- }: { name: string, unit: ?string, value: number },
+ }: {| name: string, unit: ?string, value: number |},
index
) => (
{`${name}: ${
diff --git a/newIDE/app/src/GameDashboard/GameDashboardCard.js b/newIDE/app/src/GameDashboard/GameDashboardCard.js
index 50e33596a8ee..c7784e5aa184 100644
--- a/newIDE/app/src/GameDashboard/GameDashboardCard.js
+++ b/newIDE/app/src/GameDashboard/GameDashboardCard.js
@@ -54,7 +54,7 @@ import { Tooltip } from '@material-ui/core';
const electron = optionalRequire('electron');
const path = optionalRequire('path');
-export const getThumbnailWidth = ({ isMobile }: { isMobile: boolean }) =>
+export const getThumbnailWidth = ({ isMobile }: {| isMobile: boolean |}) =>
isMobile ? undefined : Math.min(245, Math.max(130, window.innerWidth / 4));
const styles = {
@@ -176,6 +176,7 @@ const GameDashboardCard = ({
: null;
const authenticatedUser = React.useContext(AuthenticatedUserContext);
+ const { profile, onOpenLoginDialog } = authenticatedUser;
const { removeRecentProjectFile } = React.useContext(PreferencesContext);
const {
showAlert,
@@ -193,9 +194,8 @@ const GameDashboardCard = ({
const gameName = game
? game.gameName
: projectFileMetadataAndStorageProviderName
- ? projectFileMetadataAndStorageProviderName.fileMetadata.name ||
- 'Unknown game'
- : 'Unknown game';
+ ? projectFileMetadataAndStorageProviderName.fileMetadata.name
+ : null;
const { isMobile, windowSize } = useResponsiveWindowSize();
const isSmallOrMediumScreen =
@@ -268,7 +268,7 @@ const GameDashboardCard = ({
const renderTitle = () => (
- {gameName}
+ {gameName || Unknown game}
{projectsList.length > 0 && game && (
<>
@@ -358,7 +358,7 @@ const GameDashboardCard = ({
const renderThumbnail = () => (
);
- const buildOpenContextMenu = (
- i18n: I18nType,
- projectsList: FileMetadataAndStorageProviderName[]
+ const buildOpenProjectContextMenu = (
+ i18n: I18nType
): Array => {
const actions = [];
if (projectsList.length > 1) {
@@ -384,13 +383,20 @@ const GameDashboardCard = ({
click: () => onOpenProject(fileMetadataAndStorageProviderName),
};
}),
- { type: 'separator' },
- {
- label: i18n._(t`See all in the game dashboard`),
- click: game ? () => onOpenGameManager({ game }) : undefined,
- },
]
);
+
+ if (game) {
+ actions.push(
+ ...[
+ { type: 'separator' },
+ {
+ label: i18n._(t`See all in the game dashboard`),
+ click: () => onOpenGameManager({ game }),
+ },
+ ]
+ );
+ }
}
return actions;
@@ -418,7 +424,11 @@ const GameDashboardCard = ({
}
// Management actions.
- if (projectsList.length < 2) {
+ if (projectsList.length === 0) {
+ // No management possible, it's a game without a project found.
+ }
+
+ if (projectsList.length === 1) {
const file = projectsList[0];
if (file && file.storageProviderName === 'LocalFile') {
actions.push({
@@ -426,7 +436,9 @@ const GameDashboardCard = ({
click: () => locateProjectFile(file),
});
}
- } else {
+ }
+
+ if (projectsList.length > 1) {
// If there are multiple projects, suggest opening the game dashboard.
actions.push({
label: i18n._(t`See all projects`),
@@ -437,14 +449,15 @@ const GameDashboardCard = ({
// Delete actions.
// Don't allow removing project if opened, as it would not result in any change in the list.
// (because an opened project is always displayed)
- if (!isCurrentProjectOpened && projectsList.length < 2) {
+ if (isCurrentProjectOpened || projectsList.length > 1) {
+ // No delete action possible.
+ } else {
if (actions.length > 0) {
actions.push({
type: 'separator',
});
}
- const file = projectsList[0];
actions.push({
label: i18n._(t`Delete`),
click: async () => {
@@ -473,6 +486,8 @@ const GameDashboardCard = ({
}
// If there is a project file (local or cloud), remove it.
+ // There can be only one here, thanks to the check above.
+ const file = projectsList[0];
if (file) {
if (file.storageProviderName === 'Cloud') {
await onDeleteCloudProject(file);
@@ -498,8 +513,8 @@ const GameDashboardCard = ({
onOpenGameManager({ game });
return;
} else {
- if (!authenticatedUser.profile) {
- authenticatedUser.onOpenLoginDialog();
+ if (!profile) {
+ onOpenLoginDialog();
return;
}
const answer = await showConfirmation({
@@ -523,11 +538,12 @@ const GameDashboardCard = ({
onRegisterProject,
projectsList,
onRefreshGames,
- authenticatedUser,
+ onOpenLoginDialog,
+ profile,
]
);
- const renderButtons = ({ fullWidth }: { fullWidth: boolean }) => {
+ const renderButtons = ({ fullWidth }: {| fullWidth: boolean |}) => {
const openProjectLabel = isCurrentProjectOpened ? (
Save
) : (
@@ -568,9 +584,7 @@ const GameDashboardCard = ({
fullWidth={fullWidth}
label={openProjectLabel}
onClick={mainAction}
- buildMenuTemplate={i18n =>
- buildOpenContextMenu(i18n, projectsList)
- }
+ buildMenuTemplate={i18n => buildOpenProjectContextMenu(i18n)}
disabled={disabled || (isCurrentProjectOpened && !canSaveProject)}
/>
)}
diff --git a/newIDE/app/src/InstancesEditor/InstancesList/index.js b/newIDE/app/src/InstancesEditor/InstancesList/index.js
index 1111072aa6f8..e91a534357d5 100644
--- a/newIDE/app/src/InstancesEditor/InstancesList/index.js
+++ b/newIDE/app/src/InstancesEditor/InstancesList/index.js
@@ -124,7 +124,7 @@ class InstancesList extends Component {
if (this.instanceRowRenderer) this.instanceRowRenderer.delete();
}
- _onRowClick = ({ index }: { index: number }) => {
+ _onRowClick = ({ index }: {| index: number |}) => {
if (!this.renderedRows[index]) return;
this.props.onSelectInstances(
@@ -133,11 +133,11 @@ class InstancesList extends Component {
);
};
- _rowGetter = ({ index }: { index: number }) => {
+ _rowGetter = ({ index }: {| index: number |}) => {
return this.renderedRows[index];
};
- _rowClassName = ({ index }: { index: number }) => {
+ _rowClassName = ({ index }: {| index: number |}) => {
if (index < 0) {
return 'tableHeaderRow';
} else {
diff --git a/newIDE/app/src/InstancesEditor/index.js b/newIDE/app/src/InstancesEditor/index.js
index b6351e454ee3..2a47c7bc6060 100644
--- a/newIDE/app/src/InstancesEditor/index.js
+++ b/newIDE/app/src/InstancesEditor/index.js
@@ -1426,7 +1426,7 @@ export default class InstancesEditor extends Component {
fitViewToRectangle(
rectangle: Rectangle,
- { adaptZoom }: { adaptZoom: boolean }
+ { adaptZoom }: {| adaptZoom: boolean |}
) {
const idealZoom = this.viewPosition.fitToRectangle(rectangle);
if (adaptZoom) this.setZoomFactor(idealZoom);
diff --git a/newIDE/app/src/MainFrame/EditorContainers/HomePage/InAppTutorials/GuidedLessons.js b/newIDE/app/src/MainFrame/EditorContainers/HomePage/InAppTutorials/GuidedLessons.js
index bce4f8e46741..7baf42c9bea3 100644
--- a/newIDE/app/src/MainFrame/EditorContainers/HomePage/InAppTutorials/GuidedLessons.js
+++ b/newIDE/app/src/MainFrame/EditorContainers/HomePage/InAppTutorials/GuidedLessons.js
@@ -98,7 +98,11 @@ const GuidedLessons = ({ selectInAppTutorial, lessonsIds }: Props) => {
const authenticatedUser = React.useContext(AuthenticatedUserContext);
const { windowSize, isLandscape } = useResponsiveWindowSize();
- const getTutorialPartProgress = ({ tutorialId }: { tutorialId: string }) => {
+ const getTutorialPartProgress = ({
+ tutorialId,
+ }: {|
+ tutorialId: string,
+ |}) => {
const tutorialProgress = getTutorialProgress({
tutorialId,
userId: authenticatedUser.profile
diff --git a/newIDE/app/src/MainFrame/EditorContainers/HomePage/LearnSection/EducationCurriculumLesson.js b/newIDE/app/src/MainFrame/EditorContainers/HomePage/LearnSection/EducationCurriculumLesson.js
index 7f1191153de9..248f6241a9d5 100644
--- a/newIDE/app/src/MainFrame/EditorContainers/HomePage/LearnSection/EducationCurriculumLesson.js
+++ b/newIDE/app/src/MainFrame/EditorContainers/HomePage/LearnSection/EducationCurriculumLesson.js
@@ -79,7 +79,7 @@ const LockedOverlay = () => (
);
-const UpcomingOverlay = ({ message }: { message: React.Node }) => (
+const UpcomingOverlay = ({ message }: {| message: React.Node |}) => (
{message}
diff --git a/newIDE/app/src/MainFrame/EditorContainers/HomePage/TeamSection/TeamSection.spec.js b/newIDE/app/src/MainFrame/EditorContainers/HomePage/TeamSection/TeamSection.spec.js
index 1a904c7c1a41..287b557e8574 100644
--- a/newIDE/app/src/MainFrame/EditorContainers/HomePage/TeamSection/TeamSection.spec.js
+++ b/newIDE/app/src/MainFrame/EditorContainers/HomePage/TeamSection/TeamSection.spec.js
@@ -38,7 +38,7 @@ const getDefaultMembership = ({
createdAt: 16798698390,
});
-const getDefaultGroup = ({ id }: { id: string }) => ({
+const getDefaultGroup = ({ id }: {| id: string |}) => ({
id,
name: 'Group',
});
diff --git a/newIDE/app/src/MainFrame/index.js b/newIDE/app/src/MainFrame/index.js
index 7582d18ca737..7787843ee31b 100644
--- a/newIDE/app/src/MainFrame/index.js
+++ b/newIDE/app/src/MainFrame/index.js
@@ -1793,7 +1793,7 @@ const MainFrame = (props: Props) => {
{
openEventsEditor,
openSceneEditor,
- }: { openEventsEditor: boolean, openSceneEditor: boolean }
+ }: {| openEventsEditor: boolean, openSceneEditor: boolean |}
): EditorTabsState => {
const sceneEditorOptions = getEditorOpeningOptions({
kind: 'layout',
@@ -1821,7 +1821,7 @@ const MainFrame = (props: Props) => {
{
openEventsEditor = true,
openSceneEditor = true,
- }: { openEventsEditor: boolean, openSceneEditor: boolean } = {},
+ }: {| openEventsEditor: boolean, openSceneEditor: boolean |} = {},
editorTabs?: EditorTabsState
): void => {
setState(state => ({
diff --git a/newIDE/app/src/ObjectEditor/Editors/SpriteEditor/SpritesList.js b/newIDE/app/src/ObjectEditor/Editors/SpriteEditor/SpritesList.js
index d632126aa4a3..ef6b7b0122f3 100644
--- a/newIDE/app/src/ObjectEditor/Editors/SpriteEditor/SpritesList.js
+++ b/newIDE/app/src/ObjectEditor/Editors/SpriteEditor/SpritesList.js
@@ -341,7 +341,7 @@ const SpritesList = ({
);
const onSortEnd = React.useCallback(
- ({ oldIndex, newIndex }: { oldIndex: number, newIndex: number }) => {
+ ({ oldIndex, newIndex }: {| oldIndex: number, newIndex: number |}) => {
if (oldIndex === newIndex) return;
// We store the selection value of the moved sprite, as its pointer will
// be changed by the move.
diff --git a/newIDE/app/src/Profile/AuthenticatedUserProvider.js b/newIDE/app/src/Profile/AuthenticatedUserProvider.js
index 7e1e26e7fe88..f8fc2425b91c 100644
--- a/newIDE/app/src/Profile/AuthenticatedUserProvider.js
+++ b/newIDE/app/src/Profile/AuthenticatedUserProvider.js
@@ -1322,7 +1322,7 @@ export default class AuthenticatedUserProvider extends React.Component<
});
};
- showUserSnackbar = ({ message }: { message: ?React.Node }) => {
+ showUserSnackbar = ({ message }: {| message: ?React.Node |}) => {
this.setState({
// The message is wrapped here to prevent crashes when Google Translate
// translates the website. See https://github.com/4ian/GDevelop/issues/3453.
diff --git a/newIDE/app/src/QuickCustomization/QuickPublish.js b/newIDE/app/src/QuickCustomization/QuickPublish.js
index f53c81fc738f..5a7a946fd7e3 100644
--- a/newIDE/app/src/QuickCustomization/QuickPublish.js
+++ b/newIDE/app/src/QuickCustomization/QuickPublish.js
@@ -132,7 +132,7 @@ export const QuickPublish = ({
);
const onExportSucceeded = React.useCallback(
- async ({ build }: { build: ?Build }) => {
+ async ({ build }: {| build: ?Build |}) => {
try {
if (profile && game && build) {
setExportState('updating-game');
diff --git a/newIDE/app/src/UI/Dialog.js b/newIDE/app/src/UI/Dialog.js
index c559c7e8fd69..8f4e67ae7892 100644
--- a/newIDE/app/src/UI/Dialog.js
+++ b/newIDE/app/src/UI/Dialog.js
@@ -148,7 +148,11 @@ const useDangerousStylesForDialog = (dangerLevel?: 'warning' | 'danger') =>
// Customize scrollbar inside Dialog so that it gives a bit of space
// to the content.
-const useStylesForDialogContent = ({ forceScroll }: { forceScroll: boolean }) =>
+const useStylesForDialogContent = ({
+ forceScroll,
+}: {|
+ forceScroll: boolean,
+|}) =>
makeStyles({
root: {
...(forceScroll ? { overflowY: 'scroll' } : {}), // Force a scrollbar to prevent layout shifts.
diff --git a/newIDE/app/src/UI/GravatarUrl.js b/newIDE/app/src/UI/GravatarUrl.js
index 69f947b3fbad..fb9525a06117 100644
--- a/newIDE/app/src/UI/GravatarUrl.js
+++ b/newIDE/app/src/UI/GravatarUrl.js
@@ -3,7 +3,7 @@ import md5 from 'blueimp-md5';
export const getGravatarUrl = (
email: string,
- { size }: { size: number } = { size: 40 }
+ { size }: {| size: number |} = { size: 40 }
): string => {
const hash = md5(email.trim().toLowerCase());
return `https://www.gravatar.com/avatar/${hash}?s=${size}&d=retro`;
diff --git a/newIDE/app/src/Utils/GDevelopServices/Build.js b/newIDE/app/src/Utils/GDevelopServices/Build.js
index 540dd3d72796..a484e83fca70 100644
--- a/newIDE/app/src/Utils/GDevelopServices/Build.js
+++ b/newIDE/app/src/Utils/GDevelopServices/Build.js
@@ -361,7 +361,7 @@ export const updateBuild = (
getAuthorizationHeader: () => Promise,
userId: string,
buildId: string,
- { name, description }: { name?: string, description?: string }
+ { name, description }: {| name?: string, description?: string |}
): Promise => {
return getAuthorizationHeader()
.then(authorizationHeader =>
diff --git a/newIDE/app/src/Utils/GDevelopServices/Project.js b/newIDE/app/src/Utils/GDevelopServices/Project.js
index 5bbe8b8f4e6d..b47acc72b777 100644
--- a/newIDE/app/src/Utils/GDevelopServices/Project.js
+++ b/newIDE/app/src/Utils/GDevelopServices/Project.js
@@ -705,7 +705,7 @@ export const deleteProjectUserAcl = async (
export const listProjectUserAcls = async (
authenticatedUser: AuthenticatedUser,
- { projectId }: { projectId: string }
+ { projectId }: {| projectId: string |}
): Promise> => {
const { getAuthorizationHeader, firebaseUser } = authenticatedUser;
if (!firebaseUser) return [];
diff --git a/newIDE/app/src/Utils/UseDisplayNewFeature.js b/newIDE/app/src/Utils/UseDisplayNewFeature.js
index 8e807f68ac1e..ba1564f4cf99 100644
--- a/newIDE/app/src/Utils/UseDisplayNewFeature.js
+++ b/newIDE/app/src/Utils/UseDisplayNewFeature.js
@@ -25,7 +25,7 @@ const useDisplayNewFeature = () => {
} = React.useContext(PreferencesContext);
const shouldDisplayNewFeatureHighlighting = React.useCallback(
- ({ featureId }: { featureId: Feature }): boolean => {
+ ({ featureId }: {| featureId: Feature |}): boolean => {
const programOpeningCount = getProgramOpeningCount();
const settings = featuresDisplaySettings[featureId];
if (!settings) return false;
@@ -50,7 +50,7 @@ const useDisplayNewFeature = () => {
);
const acknowledgeNewFeature = React.useCallback(
- ({ featureId }: { featureId: Feature }) => {
+ ({ featureId }: {| featureId: Feature |}) => {
if (!featuresDisplaySettings[featureId]) return;
const acknowledgments = newFeaturesAcknowledgements[featureId];
diff --git a/newIDE/app/src/stories/componentStories/AssetStore/AssetStore/AssetDetails.stories.js b/newIDE/app/src/stories/componentStories/AssetStore/AssetStore/AssetDetails.stories.js
index c255494de15f..b88cfede07ab 100644
--- a/newIDE/app/src/stories/componentStories/AssetStore/AssetStore/AssetDetails.stories.js
+++ b/newIDE/app/src/stories/componentStories/AssetStore/AssetStore/AssetDetails.stories.js
@@ -18,7 +18,7 @@ export default {
decorators: [paperDecorator],
};
-const Wrapper = ({ children }: { children: React.Node }) => {
+const Wrapper = ({ children }: {| children: React.Node |}) => {
const navigationState = useShopNavigation();
return (
diff --git a/newIDE/app/src/stories/componentStories/AssetStore/AssetStore/AssetPackInstallDialog.stories.js b/newIDE/app/src/stories/componentStories/AssetStore/AssetStore/AssetPackInstallDialog.stories.js
index 774a239f995f..7dc5aa3b94b7 100644
--- a/newIDE/app/src/stories/componentStories/AssetStore/AssetStore/AssetPackInstallDialog.stories.js
+++ b/newIDE/app/src/stories/componentStories/AssetStore/AssetStore/AssetPackInstallDialog.stories.js
@@ -75,7 +75,7 @@ const mockFailedApiDataForPublicAsset1 = [
},
];
-const Wrapper = ({ children }: { children: React.Node }) => {
+const Wrapper = ({ children }: {| children: React.Node |}) => {
const navigationState = useShopNavigation();
return (
{
+const Wrapper = ({ children }: {| children: React.Node |}) => {
const navigationState = useShopNavigation();
return (
diff --git a/newIDE/app/src/stories/componentStories/AssetStore/CustomObjectPackResults.stories.js b/newIDE/app/src/stories/componentStories/AssetStore/CustomObjectPackResults.stories.js
index d4b1b44eb0fc..986eab1c59b3 100644
--- a/newIDE/app/src/stories/componentStories/AssetStore/CustomObjectPackResults.stories.js
+++ b/newIDE/app/src/stories/componentStories/AssetStore/CustomObjectPackResults.stories.js
@@ -14,7 +14,7 @@ export default {
decorators: [paperDecorator],
};
-const Wrapper = ({ children }: { children: React.Node }) => {
+const Wrapper = ({ children }: {| children: React.Node |}) => {
const navigationState = useShopNavigation();
return (
diff --git a/newIDE/app/src/stories/componentStories/AssetStore/ResourceStore/ResourceStore.stories.js b/newIDE/app/src/stories/componentStories/AssetStore/ResourceStore/ResourceStore.stories.js
index b44dee9942a0..600c4937428d 100644
--- a/newIDE/app/src/stories/componentStories/AssetStore/ResourceStore/ResourceStore.stories.js
+++ b/newIDE/app/src/stories/componentStories/AssetStore/ResourceStore/ResourceStore.stories.js
@@ -15,7 +15,7 @@ export default {
decorators: [getPaperDecorator('medium')],
};
-const ResourceStoreStory = ({ kind }: { kind: 'audio' | 'font' | 'svg' }) => {
+const ResourceStoreStory = ({ kind }: {| kind: 'audio' | 'font' | 'svg' |}) => {
const [
selectedResourceIndex,
setSelectedResourceIndex,
diff --git a/newIDE/app/src/stories/componentStories/QuickCustomization/QuickPublish.stories.js b/newIDE/app/src/stories/componentStories/QuickCustomization/QuickPublish.stories.js
index 96cb9c7ff868..9acaa60a65db 100644
--- a/newIDE/app/src/stories/componentStories/QuickCustomization/QuickPublish.stories.js
+++ b/newIDE/app/src/stories/componentStories/QuickCustomization/QuickPublish.stories.js
@@ -50,7 +50,7 @@ const erroringOnlineWebExporter: Exporter = {
exportPipeline: fakeErroringBrowserOnlineWebExportPipeline,
};
-const Template = ({ children }: { children: React.Node }) => {
+const Template = ({ children }: {| children: React.Node |}) => {
const fakeGame = fakeGameAndBuildsManager.game;
if (!fakeGame)
throw new Error(
From a673843194dd4780f8b20a58595668904a8be145 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Cl=C3=A9ment=20Pasteau?=
<4895034+ClementPasteau@users.noreply.github.com>
Date: Thu, 12 Dec 2024 16:38:54 +0100
Subject: [PATCH 20/26] More fixes
---
.../app/src/AssetStore/ExampleStore/index.js | 6 ++--
.../src/GameDashboard/GameDashboardCard.js | 4 +--
.../Monetization/UserEarningsWidget.js | 36 +++++++++----------
.../GameDashboard/Widgets/DashboardWidget.js | 3 +-
.../HomePage/CreateSection/index.js | 2 +-
5 files changed, 24 insertions(+), 27 deletions(-)
diff --git a/newIDE/app/src/AssetStore/ExampleStore/index.js b/newIDE/app/src/AssetStore/ExampleStore/index.js
index a08942b41e1c..09b8bdcfebd0 100644
--- a/newIDE/app/src/AssetStore/ExampleStore/index.js
+++ b/newIDE/app/src/AssetStore/ExampleStore/index.js
@@ -76,7 +76,7 @@ const ExampleStore = ({
rowToInsert,
hideSearch,
}: Props) => {
- const authenticatedUser = React.useContext(AuthenticatedUserContext);
+ const { receivedGameTemplates } = React.useContext(AuthenticatedUserContext);
const {
exampleShortHeadersSearchResults,
fetchExamplesAndFilters,
@@ -140,7 +140,7 @@ const ExampleStore = ({
const resultTiles: React.Node[] = React.useMemo(
() => {
return getExampleAndTemplateTiles({
- receivedGameTemplates: authenticatedUser.receivedGameTemplates,
+ receivedGameTemplates,
privateGameTemplateListingDatas: privateGameTemplateListingDatasSearchResults
? privateGameTemplateListingDatasSearchResults
.map(({ item }) => item)
@@ -187,7 +187,7 @@ const ExampleStore = ({
}).allGridItems;
},
[
- authenticatedUser,
+ receivedGameTemplates,
privateGameTemplateListingDatasSearchResults,
exampleShortHeadersSearchResults,
onSelectPrivateGameTemplateListingData,
diff --git a/newIDE/app/src/GameDashboard/GameDashboardCard.js b/newIDE/app/src/GameDashboard/GameDashboardCard.js
index c7784e5aa184..600127596bf6 100644
--- a/newIDE/app/src/GameDashboard/GameDashboardCard.js
+++ b/newIDE/app/src/GameDashboard/GameDashboardCard.js
@@ -555,8 +555,8 @@ const GameDashboardCard = ({
? () => onOpenProject(projectsList[0])
: () => {
showAlert({
- title: t`No project found`,
- message: t`We couldn't find a project for this game. You may have saved it in a different location? You can open it manually to get it linked to this game.`,
+ title: t`No project to open`,
+ message: t`Looks like your project isn't there!\n\nYou may be using a different computer or opening GDevelop on the web and your project is saved locally.`,
});
};
diff --git a/newIDE/app/src/GameDashboard/Monetization/UserEarningsWidget.js b/newIDE/app/src/GameDashboard/Monetization/UserEarningsWidget.js
index 4efe862f6f92..8f0bf8116f60 100644
--- a/newIDE/app/src/GameDashboard/Monetization/UserEarningsWidget.js
+++ b/newIDE/app/src/GameDashboard/Monetization/UserEarningsWidget.js
@@ -12,11 +12,14 @@ import RaisedButton from '../../UI/RaisedButton';
import Coin from '../../Credits/Icons/Coin';
import AuthenticatedUserContext from '../../Profile/AuthenticatedUserContext';
import PlaceholderError from '../../UI/PlaceholderError';
-import { Tooltip } from '@material-ui/core';
+import Tooltip from '@material-ui/core/Tooltip';
import CreditOutDialog from './CashOutDialog';
import GDevelopThemeContext from '../../UI/Theme/GDevelopThemeContext';
import DashboardWidget from '../Widgets/DashboardWidget';
import { useResponsiveWindowSize } from '../../UI/Responsive/ResponsiveWindowMeasurer';
+import { getHelpLink } from '../../Utils/HelpLink';
+
+const monetizationHelpLink = getHelpLink('/monetization');
const styles = {
separator: {
@@ -51,7 +54,7 @@ const UserEarningsWidget = ({ fullWidth }: Props) => {
?'cash' | 'credits'
>(null);
- const fetchUserEarningsBalance = React.useCallback(
+ const animateEarnings = React.useCallback(
async () => {
if (!userEarningsBalance) return;
@@ -64,11 +67,11 @@ const UserEarningsWidget = ({ fullWidth }: Props) => {
const steps = 30;
const intervalTime = duration / steps;
- const milliUsdIncrement = (targetMilliUsd - earningsInMilliUsd) / steps;
- const creditsIncrement = (targetCredits - earningsInCredits) / steps;
+ const milliUsdIncrement = targetMilliUsd / steps;
+ const creditsIncrement = targetCredits / steps;
- let currentMilliUsd = earningsInMilliUsd;
- let currentCredits = earningsInCredits;
+ let currentMilliUsd = 0;
+ let currentCredits = 0;
let step = 0;
intervalValuesUpdate.current = setInterval(() => {
@@ -91,16 +94,14 @@ const UserEarningsWidget = ({ fullWidth }: Props) => {
setError(error);
}
},
- [userEarningsBalance, earningsInMilliUsd, earningsInCredits]
+ [userEarningsBalance]
);
React.useEffect(
() => {
- fetchUserEarningsBalance();
+ animateEarnings();
},
- // Fetch the earnings once on mount.
- // eslint-disable-next-line react-hooks/exhaustive-deps
- []
+ [animateEarnings]
);
React.useEffect(
@@ -115,8 +116,7 @@ const UserEarningsWidget = ({ fullWidth }: Props) => {
const onCashOrCreditOut = React.useCallback(
async () => {
- await onRefreshEarningsBalance();
- await onRefreshLimits();
+ await Promise.all([onRefreshEarningsBalance(), onRefreshLimits()]);
},
[onRefreshEarningsBalance, onRefreshLimits]
);
@@ -129,7 +129,7 @@ const UserEarningsWidget = ({ fullWidth }: Props) => {
- Can't load the total earnings. Verify your internet connection or try
+ Can't load your game earnings. Verify your internet connection or try
again later.
@@ -143,12 +143,8 @@ const UserEarningsWidget = ({ fullWidth }: Props) => {
>
- Window.openExternalURL(
- 'https://wiki.gdevelop.io/gdevelop5/monetization/'
- )
- }
+ href={monetizationHelpLink}
+ onClick={() => Window.openExternalURL(monetizationHelpLink)}
>
Learn about revenue on gd.games
diff --git a/newIDE/app/src/GameDashboard/Widgets/DashboardWidget.js b/newIDE/app/src/GameDashboard/Widgets/DashboardWidget.js
index 4b027f046572..3e8a1441f1a7 100644
--- a/newIDE/app/src/GameDashboard/Widgets/DashboardWidget.js
+++ b/newIDE/app/src/GameDashboard/Widgets/DashboardWidget.js
@@ -7,6 +7,7 @@ import Text from '../../UI/Text';
import { Column, Line } from '../../UI/Grid';
import { useResponsiveWindowSize } from '../../UI/Responsive/ResponsiveWindowMeasurer';
import { ColumnStackLayout } from '../../UI/Layout';
+import { dataObjectToProps } from '../../Utils/HTMLDataset';
const padding = 16;
const fixedHeight = 300;
@@ -53,7 +54,7 @@ const DashboardWidget = ({
}: Props) => {
const { isMobile } = useResponsiveWindowSize();
return (
-
+
Date: Thu, 12 Dec 2024 18:32:47 +0100
Subject: [PATCH 21/26] New round of improvements
---
.../src/GameDashboard/GameDashboardCard.js | 26 +++++++++------
newIDE/app/src/GameDashboard/GameHeader.js | 10 ++++--
newIDE/app/src/GameDashboard/GamesList.js | 33 ++++++++++++-------
.../Monetization/UserEarningsWidget.js | 10 +++---
.../src/GameDashboard/Wallet/WalletWidget.js | 10 +++---
.../GameDashboard/Widgets/AnalyticsWidget.js | 2 +-
.../src/GameDashboard/Widgets/BuildsWidget.js | 2 +-
.../GameDashboard/Widgets/DashboardWidget.js | 28 ++++++++++++++--
.../GameDashboard/Widgets/FeedbackWidget.js | 2 +-
.../GameDashboard/Widgets/ProjectsWidget.js | 2 +-
.../GameDashboard/Widgets/ServicesWidget.js | 2 +-
.../HomePage/CreateSection/index.js | 27 ++++++++++-----
.../Preferences/PreferencesContext.js | 9 +++++
.../Preferences/PreferencesProvider.js | 14 ++++++++
newIDE/app/src/UI/Layout.js | 1 -
newIDE/app/src/Utils/GDevelopServices/Game.js | 2 +-
.../GameDashboard/GamesList.stories.js | 12 -------
.../UserEarningsWidget.stories.js | 10 +++---
18 files changed, 134 insertions(+), 68 deletions(-)
diff --git a/newIDE/app/src/GameDashboard/GameDashboardCard.js b/newIDE/app/src/GameDashboard/GameDashboardCard.js
index 600127596bf6..76500018bfa2 100644
--- a/newIDE/app/src/GameDashboard/GameDashboardCard.js
+++ b/newIDE/app/src/GameDashboard/GameDashboardCard.js
@@ -84,13 +84,19 @@ const locateProjectFile = (file: FileMetadataAndStorageProviderName) => {
};
const getFileNameWithoutExtensionFromPath = (path: string) => {
- const fileName = path.split('/').pop();
- return fileName
- ? fileName
- .split('.')
- .slice(0, -1)
- .join('.')
- : '';
+ // Normalize path separators for cross-platform compatibility
+ const normalizedPath = path.replace(/\\/g, '/');
+
+ // Extract file name
+ const fileName = normalizedPath.split('/').pop();
+
+ // Handle dotfiles and files without extensions
+ if (fileName) {
+ const parts = fileName.split('.');
+ return parts.length > 1 ? parts.slice(0, -1).join('.') : fileName;
+ }
+
+ return '';
};
const getProjectItemLabel = (
@@ -197,7 +203,7 @@ const GameDashboardCard = ({
? projectFileMetadataAndStorageProviderName.fileMetadata.name
: null;
- const { isMobile, windowSize } = useResponsiveWindowSize();
+ const { isMobile, windowSize, isLandscape } = useResponsiveWindowSize();
const isSmallOrMediumScreen =
windowSize === 'small' || windowSize === 'medium';
const gdevelopTheme = React.useContext(GDevelopThemeContext);
@@ -318,7 +324,7 @@ const GameDashboardCard = ({
Last edited:
- {i18n.date(game.updatedAt * 1000)}
+ {i18n.date((game.updatedAt || 0) * 1000)}
) : null;
@@ -609,7 +615,7 @@ const GameDashboardCard = ({
isHighlighted={isCurrentProjectOpened}
padding={isMobile ? 8 : 16}
>
- {isMobile ? (
+ {isMobile && !isLandscape ? (
diff --git a/newIDE/app/src/GameDashboard/GameHeader.js b/newIDE/app/src/GameDashboard/GameHeader.js
index fa023a2ae1f9..a143a62c899a 100644
--- a/newIDE/app/src/GameDashboard/GameHeader.js
+++ b/newIDE/app/src/GameDashboard/GameHeader.js
@@ -45,7 +45,7 @@ const GameHeader = ({
onPublishOnGdGames,
}: Props) => {
useOnResize(useForceUpdate());
- const { isMobile } = useResponsiveWindowSize();
+ const { isMobile, isLandscape } = useResponsiveWindowSize();
const gdevelopTheme = React.useContext(GDevelopThemeContext);
const gameMainImageUrl = getGameMainImageUrl(game);
@@ -64,7 +64,11 @@ const GameHeader = ({
fontSize: 'small',
};
return (
-
+
@@ -153,7 +157,7 @@ const GameHeader = ({
);
- if (isMobile) {
+ if (isMobile && !isLandscape) {
return (
{({ i18n }) => (
diff --git a/newIDE/app/src/GameDashboard/GamesList.js b/newIDE/app/src/GameDashboard/GamesList.js
index 351decce543e..b07b247d429f 100644
--- a/newIDE/app/src/GameDashboard/GamesList.js
+++ b/newIDE/app/src/GameDashboard/GamesList.js
@@ -46,6 +46,7 @@ import Refresh from '../UI/CustomSvgIcons/Refresh';
import optionalRequire from '../Utils/OptionalRequire';
import TextButton from '../UI/TextButton';
import Skeleton from '@material-ui/lab/Skeleton';
+import PreferencesContext from '../MainFrame/Preferences/PreferencesContext';
const electron = optionalRequire('electron');
const isDesktop = !!electron;
@@ -61,7 +62,10 @@ const styles = {
},
};
-export type OrderBy = 'totalSessions' | 'weeklySessions' | 'lastModifiedAt';
+export type GamesDashboardOrderBy =
+ | 'totalSessions'
+ | 'weeklySessions'
+ | 'lastModifiedAt';
const totalSessionsSort = (
itemA: DashboardItem,
@@ -87,7 +91,7 @@ const getDashboardItemLastModifiedAt = (item: DashboardItem): number => {
);
}
// Then the game, if any.
- return (item.game && item.game.updatedAt * 1000) || 0;
+ return (item.game && (item.game.updatedAt || 0) * 1000) || 0;
};
const lastModifiedAtSort = (
@@ -139,7 +143,7 @@ const getDashboardItemsToDisplay = ({
searchText: string,
searchClient: Fuse,
currentPage: number,
- orderBy: OrderBy,
+ orderBy: GamesDashboardOrderBy,
|}): ?Array => {
if (!allDashboardItems) return null;
let itemsToDisplay: DashboardItem[] = allDashboardItems;
@@ -163,8 +167,9 @@ const getDashboardItemsToDisplay = ({
? itemsToDisplay.sort(lastModifiedAtSort)
: itemsToDisplay;
- // If a project is opened and no search is performed, display it first.
- if (project) {
+ // If a project is opened, no search is done, and sorted by last modified date,
+ // then the opened project should be displayed first.
+ if (project && orderBy === 'lastModifiedAt') {
const currentProjectId = project.getProjectUuid();
const currentFileIdentifier = currentFileMetadata
? currentFileMetadata.fileIdentifier
@@ -263,8 +268,6 @@ type Props = {|
// Controls
currentPage: number,
setCurrentPage: (currentPage: number) => void,
- orderBy: OrderBy,
- setGamesListOrderBy: (orderBy: OrderBy) => void,
searchText: string,
setSearchText: (searchText: string) => void,
|};
@@ -291,14 +294,17 @@ const GamesList = ({
// Make the page controlled, so that it can be saved when navigating to a game.
currentPage,
setCurrentPage,
- orderBy,
- setGamesListOrderBy,
searchText,
setSearchText,
}: Props) => {
const { cloudProjects, profile, onCloudProjectsChanged } = React.useContext(
AuthenticatedUserContext
);
+ const {
+ values: { gamesDashboardOrderBy: orderBy },
+ setGamesDashboardOrderBy,
+ } = React.useContext(PreferencesContext);
+
const { isMobile } = useResponsiveWindowSize();
const gameThumbnailWidth = getThumbnailWidth({ isMobile });
@@ -503,7 +509,12 @@ const GamesList = ({
{allDashboardItems && allDashboardItems.length > 0 && (
-
+
// $FlowFixMe
- setGamesListOrderBy(value)
+ setGamesDashboardOrderBy(value)
}
>
{
+const UserEarningsWidget = ({ size }: Props) => {
const {
userEarningsBalance,
onRefreshEarningsBalance,
@@ -227,7 +229,7 @@ const UserEarningsWidget = ({ fullWidth }: Props) => {
return (
<>
Game earnings}
widgetName="earnings"
>
diff --git a/newIDE/app/src/GameDashboard/Wallet/WalletWidget.js b/newIDE/app/src/GameDashboard/Wallet/WalletWidget.js
index e592b6add0aa..dda51811440e 100644
--- a/newIDE/app/src/GameDashboard/Wallet/WalletWidget.js
+++ b/newIDE/app/src/GameDashboard/Wallet/WalletWidget.js
@@ -1,7 +1,9 @@
// @flow
import * as React from 'react';
-import DashboardWidget from '../Widgets/DashboardWidget';
+import DashboardWidget, {
+ type DashboardWidgetSize,
+} from '../Widgets/DashboardWidget';
import { ColumnStackLayout } from '../../UI/Layout';
import Coin from '../../Credits/Icons/Coin';
import AuthenticatedUserContext from '../../Profile/AuthenticatedUserContext';
@@ -11,14 +13,14 @@ import { Trans } from '@lingui/macro';
type Props = {|
onOpenProfile: () => void,
- fullWidth?: boolean,
+ size: DashboardWidgetSize,
showRandomBadge?: boolean,
showAllBadges?: boolean,
|};
const WalletWidget = ({
onOpenProfile,
- fullWidth,
+ size,
showRandomBadge,
showAllBadges,
}: Props) => {
@@ -32,7 +34,7 @@ const WalletWidget = ({
const creditsAvailable = limits ? limits.credits.userBalance.amount : 0;
return (
Wallet}
topRightAction={
{
{({ i18n }) => (
Analytics}
topRightAction={
diff --git a/newIDE/app/src/GameDashboard/Widgets/BuildsWidget.js b/newIDE/app/src/GameDashboard/Widgets/BuildsWidget.js
index e582b3274f74..4c74a9185b5b 100644
--- a/newIDE/app/src/GameDashboard/Widgets/BuildsWidget.js
+++ b/newIDE/app/src/GameDashboard/Widgets/BuildsWidget.js
@@ -32,7 +32,7 @@ const BuildsWidget = ({ builds, onSeeAllBuilds }: Props) => {
return (
Exports}
topRightAction={
{
+ switch (size) {
+ case 'full':
+ return 12;
+ case 'half':
+ return 6;
+ case 'oneThird':
+ return 4;
+ case 'twoThirds':
+ return 8;
+ default:
+ return 12;
+ }
+};
+
type GameDashboardWidgetName =
| 'analytics'
| 'feedback'
@@ -37,7 +54,7 @@ type Props = {|
title: React.Node,
topRightAction?: React.Node,
renderSubtitle?: ?() => React.Node,
- gridSize: number,
+ widgetSize: DashboardWidgetSize,
children?: React.Node,
minHeight?: boolean,
widgetName: GameDashboardWidgetName,
@@ -46,7 +63,7 @@ type Props = {|
const DashboardWidget = ({
title,
topRightAction,
- gridSize,
+ widgetSize,
renderSubtitle,
children,
minHeight,
@@ -54,7 +71,12 @@ const DashboardWidget = ({
}: Props) => {
const { isMobile } = useResponsiveWindowSize();
return (
-
+
{({ i18n }) => (
Feedbacks}
topRightAction={
!feedbacks || feedbacks.length === 0 ? null : (
diff --git a/newIDE/app/src/GameDashboard/Widgets/ProjectsWidget.js b/newIDE/app/src/GameDashboard/Widgets/ProjectsWidget.js
index 81b99dd8fa41..b1463521f180 100644
--- a/newIDE/app/src/GameDashboard/Widgets/ProjectsWidget.js
+++ b/newIDE/app/src/GameDashboard/Widgets/ProjectsWidget.js
@@ -29,7 +29,7 @@ type Props = {|
const ProjectsWidget = (props: Props) => {
return (
Projects}
widgetName="projects"
>
diff --git a/newIDE/app/src/GameDashboard/Widgets/ServicesWidget.js b/newIDE/app/src/GameDashboard/Widgets/ServicesWidget.js
index 02b791e63d0f..7b161d7c10d6 100644
--- a/newIDE/app/src/GameDashboard/Widgets/ServicesWidget.js
+++ b/newIDE/app/src/GameDashboard/Widgets/ServicesWidget.js
@@ -39,7 +39,7 @@ const ServicesWidget = ({
);
return (
Player services}
widgetName="services"
>
diff --git a/newIDE/app/src/MainFrame/EditorContainers/HomePage/CreateSection/index.js b/newIDE/app/src/MainFrame/EditorContainers/HomePage/CreateSection/index.js
index 138e68bbcf80..156d288d3811 100644
--- a/newIDE/app/src/MainFrame/EditorContainers/HomePage/CreateSection/index.js
+++ b/newIDE/app/src/MainFrame/EditorContainers/HomePage/CreateSection/index.js
@@ -6,7 +6,7 @@ import { I18n as I18nType } from '@lingui/core';
import SectionContainer, { SectionRow } from '../SectionContainer';
import ErrorBoundary from '../../../../UI/ErrorBoundary';
import AuthenticatedUserContext from '../../../../Profile/AuthenticatedUserContext';
-import GamesList, { type OrderBy } from '../../../../GameDashboard/GamesList';
+import GamesList from '../../../../GameDashboard/GamesList';
import {
deleteGame,
registerGame,
@@ -198,9 +198,6 @@ const CreateSection = ({
);
const [currentPage, setCurrentPage] = React.useState(1);
- const [orderBy, setGamesListOrderBy] = React.useState(
- 'lastModifiedAt'
- );
const [searchText, setSearchText] = React.useState('');
const onUnregisterGame = React.useCallback(
@@ -500,17 +497,31 @@ const CreateSection = ({
{hidePerformanceDashboard ? null : hasAProjectOpenedOrSavedOrGameRegistered ? (
-
+
) : (
-
+
)}
@@ -545,8 +556,6 @@ const CreateSection = ({
// Controls
currentPage={currentPage}
setCurrentPage={setCurrentPage}
- orderBy={orderBy}
- setGamesListOrderBy={setGamesListOrderBy}
searchText={searchText}
setSearchText={setSearchText}
/>
diff --git a/newIDE/app/src/MainFrame/Preferences/PreferencesContext.js b/newIDE/app/src/MainFrame/Preferences/PreferencesContext.js
index 9c07b8e8d176..83bc869bebb1 100644
--- a/newIDE/app/src/MainFrame/Preferences/PreferencesContext.js
+++ b/newIDE/app/src/MainFrame/Preferences/PreferencesContext.js
@@ -10,6 +10,7 @@ import { type FileMetadataAndStorageProviderName } from '../../ProjectsStorage';
import { type ShortcutMap } from '../../KeyboardShortcuts/DefaultShortcuts';
import { type CommandName } from '../../CommandPalette/CommandsList';
import { type EditorTabsPersistedState } from '../EditorTabs/EditorTabsHandler';
+import { type GamesDashboardOrderBy } from '../../GameDashboard/GamesList';
import optionalRequire from '../../Utils/OptionalRequire';
import { findDefaultFolder } from '../../ProjectsStorage/LocalFileStorageProvider/LocalPathFinder';
import { isWebGLSupported } from '../../Utils/WebGL';
@@ -234,6 +235,7 @@ export type PreferencesValues = {|
editorStateByProject: { [string]: { editorTabs: EditorTabsPersistedState } },
fetchPlayerTokenForPreviewAutomatically: boolean,
previewCrashReportUploadLevel: string,
+ gamesDashboardOrderBy: GamesDashboardOrderBy,
takeScreenshotOnPreview: boolean,
|};
@@ -333,6 +335,9 @@ export type Preferences = {|
) => void,
setFetchPlayerTokenForPreviewAutomatically: (enabled: boolean) => void,
setPreviewCrashReportUploadLevel: (level: string) => void,
+ setGamesDashboardOrderBy: (
+ orderBy: 'lastModifiedAt' | 'totalSessions' | 'weeklySessions'
+ ) => void,
setTakeScreenshotOnPreview: (enabled: boolean) => void,
|};
@@ -389,6 +394,7 @@ export const initialPreferences = {
editorStateByProject: {},
fetchPlayerTokenForPreviewAutomatically: true,
previewCrashReportUploadLevel: 'exclude-javascript-code-events',
+ gamesDashboardOrderBy: 'lastModifiedAt',
takeScreenshotOnPreview: true,
},
setLanguage: () => {},
@@ -460,6 +466,9 @@ export const initialPreferences = {
setEditorStateForProject: (projectId, editorState) => {},
setFetchPlayerTokenForPreviewAutomatically: (enabled: boolean) => {},
setPreviewCrashReportUploadLevel: (level: string) => {},
+ setGamesDashboardOrderBy: (
+ orderBy: 'lastModifiedAt' | 'totalSessions' | 'weeklySessions'
+ ) => {},
setTakeScreenshotOnPreview: (enabled: boolean) => {},
};
diff --git a/newIDE/app/src/MainFrame/Preferences/PreferencesProvider.js b/newIDE/app/src/MainFrame/Preferences/PreferencesProvider.js
index c8747e31fed7..5e6c75ee3e09 100644
--- a/newIDE/app/src/MainFrame/Preferences/PreferencesProvider.js
+++ b/newIDE/app/src/MainFrame/Preferences/PreferencesProvider.js
@@ -26,6 +26,7 @@ import {
setLanguageInDOM,
selectLanguageOrLocale,
} from '../../Utils/Language';
+import { type GamesDashboardOrderBy } from '../../GameDashboard/GamesList';
import { CHECK_APP_UPDATES_TIMEOUT } from '../../Utils/GlobalFetchTimeouts';
const electron = optionalRequire('electron');
const ipcRenderer = electron ? electron.ipcRenderer : null;
@@ -196,6 +197,7 @@ export default class PreferencesProvider extends React.Component {
setPreviewCrashReportUploadLevel: this._setPreviewCrashReportUploadLevel.bind(
this
),
+ setGamesDashboardOrderBy: this._setGamesDashboardOrderBy.bind(this),
setTakeScreenshotOnPreview: this._setTakeScreenshotOnPreview.bind(this),
};
@@ -1007,6 +1009,18 @@ export default class PreferencesProvider extends React.Component {
);
}
+ _setGamesDashboardOrderBy(newValue: GamesDashboardOrderBy) {
+ this.setState(
+ state => ({
+ values: {
+ ...state.values,
+ gamesDashboardOrderBy: newValue,
+ },
+ }),
+ () => this._persistValuesToLocalStorage(this.state)
+ );
+ }
+
_setTakeScreenshotOnPreview(newValue: boolean) {
this.setState(
state => ({
diff --git a/newIDE/app/src/UI/Layout.js b/newIDE/app/src/UI/Layout.js
index 1b4cbf81d866..8efeee6a1e8d 100644
--- a/newIDE/app/src/UI/Layout.js
+++ b/newIDE/app/src/UI/Layout.js
@@ -171,7 +171,6 @@ export const ResponsiveLineStackLayout = ({
diff --git a/newIDE/app/src/Utils/GDevelopServices/Game.js b/newIDE/app/src/Utils/GDevelopServices/Game.js
index 793d60115123..2fcd7169e427 100644
--- a/newIDE/app/src/Utils/GDevelopServices/Game.js
+++ b/newIDE/app/src/Utils/GDevelopServices/Game.js
@@ -54,7 +54,7 @@ export type Game = {|
categories?: string[],
authorName: string, // this corresponds to the publisher name
createdAt: number,
- updatedAt: number,
+ updatedAt?: number, // Some old games don't have this field
publicWebBuildId?: ?string,
description?: string,
thumbnailUrl?: string,
diff --git a/newIDE/app/src/stories/componentStories/GameDashboard/GamesList.stories.js b/newIDE/app/src/stories/componentStories/GameDashboard/GamesList.stories.js
index c32e889c9c4a..6ac086e6ba8f 100644
--- a/newIDE/app/src/stories/componentStories/GameDashboard/GamesList.stories.js
+++ b/newIDE/app/src/stories/componentStories/GameDashboard/GamesList.stories.js
@@ -40,7 +40,6 @@ export const NoGamesOrProjects = () => {
};
const [currentPage, setCurrentPage] = React.useState(1);
- const [orderBy, setOrderBy] = React.useState('lastModifiedAt');
const [searchText, setSearchText] = React.useState('');
return (
@@ -67,8 +66,6 @@ export const NoGamesOrProjects = () => {
onRegisterProject={action('onRegisterProject')}
currentPage={currentPage}
setCurrentPage={setCurrentPage}
- orderBy={orderBy}
- setGamesListOrderBy={setOrderBy}
searchText={searchText}
setSearchText={setSearchText}
/>
@@ -98,7 +95,6 @@ export const WithOnlyGames = () => {
};
const [currentPage, setCurrentPage] = React.useState(1);
- const [orderBy, setOrderBy] = React.useState('lastModifiedAt');
const [searchText, setSearchText] = React.useState('');
return (
@@ -125,8 +121,6 @@ export const WithOnlyGames = () => {
onRegisterProject={action('onRegisterProject')}
currentPage={currentPage}
setCurrentPage={setCurrentPage}
- orderBy={orderBy}
- setGamesListOrderBy={setOrderBy}
searchText={searchText}
setSearchText={setSearchText}
/>
@@ -151,7 +145,6 @@ export const WithOnlyProjects = () => {
};
const [currentPage, setCurrentPage] = React.useState(1);
- const [orderBy, setOrderBy] = React.useState('lastModifiedAt');
const [searchText, setSearchText] = React.useState('');
return (
@@ -178,8 +171,6 @@ export const WithOnlyProjects = () => {
onRegisterProject={action('onRegisterProject')}
currentPage={currentPage}
setCurrentPage={setCurrentPage}
- orderBy={orderBy}
- setGamesListOrderBy={setOrderBy}
searchText={searchText}
setSearchText={setSearchText}
/>
@@ -211,7 +202,6 @@ export const WithGamesAndProjects = () => {
};
const [currentPage, setCurrentPage] = React.useState(1);
- const [orderBy, setOrderBy] = React.useState('lastModifiedAt');
const [searchText, setSearchText] = React.useState('');
return (
@@ -238,8 +228,6 @@ export const WithGamesAndProjects = () => {
onRegisterProject={action('onRegisterProject')}
currentPage={currentPage}
setCurrentPage={setCurrentPage}
- orderBy={orderBy}
- setGamesListOrderBy={setOrderBy}
searchText={searchText}
setSearchText={setSearchText}
/>
diff --git a/newIDE/app/src/stories/componentStories/GameDashboard/Monetization/UserEarningsWidget.stories.js b/newIDE/app/src/stories/componentStories/GameDashboard/Monetization/UserEarningsWidget.stories.js
index 6e62434f1835..14858fe78eaa 100644
--- a/newIDE/app/src/stories/componentStories/GameDashboard/Monetization/UserEarningsWidget.stories.js
+++ b/newIDE/app/src/stories/componentStories/GameDashboard/Monetization/UserEarningsWidget.stories.js
@@ -30,7 +30,7 @@ export const Errored = () => {
return (
-
+
);
};
@@ -58,7 +58,7 @@ export const NoEarnings = () => {
return (
-
+
);
};
@@ -86,7 +86,7 @@ export const LittleEarnings = () => {
return (
-
+
);
};
@@ -114,7 +114,7 @@ export const SomeEarnings = () => {
return (
-
+
);
};
@@ -142,7 +142,7 @@ export const ALotOfEarnings = () => {
return (
-
+
);
};
From 949b8f857fc1943be53b833db57f31108491537a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Cl=C3=A9ment=20Pasteau?=
<4895034+ClementPasteau@users.noreply.github.com>
Date: Fri, 13 Dec 2024 16:11:54 +0100
Subject: [PATCH 22/26] More fixes
---
newIDE/app/src/AssetStore/AssetsHome.js | 4 +-
.../src/GameDashboard/GameDashboardCard.js | 35 ++-
.../src/GameDashboard/Wallet/WalletWidget.js | 17 +-
.../HomePage/CreateSection/index.js | 10 +-
.../{EarnBadges.js => EarnCredits.js} | 278 +++++++++++++-----
newIDE/app/src/MainFrame/index.js | 14 +
6 files changed, 250 insertions(+), 108 deletions(-)
rename newIDE/app/src/MainFrame/EditorContainers/HomePage/GetStartedSection/{EarnBadges.js => EarnCredits.js} (50%)
diff --git a/newIDE/app/src/AssetStore/AssetsHome.js b/newIDE/app/src/AssetStore/AssetsHome.js
index 092650f42eab..e92d5f3c89fb 100644
--- a/newIDE/app/src/AssetStore/AssetsHome.js
+++ b/newIDE/app/src/AssetStore/AssetsHome.js
@@ -28,7 +28,7 @@ import {
import { useDebounce } from '../Utils/UseDebounce';
import PromotionsSlideshow from '../Promotions/PromotionsSlideshow';
import { ColumnStackLayout } from '../UI/Layout';
-import { EarnBadges } from '../MainFrame/EditorContainers/HomePage/GetStartedSection/EarnBadges';
+import { EarnCredits } from '../MainFrame/EditorContainers/HomePage/GetStartedSection/EarnCredits';
const cellSpacing = 2;
@@ -403,7 +403,7 @@ export const AssetsHome = React.forwardRef(
{onOpenProfile && (
-
isMobile ? undefined : Math.min(245, Math.max(130, window.innerWidth / 4));
const styles = {
+ tooltipButtonContainer: {
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'stretch',
+ },
buttonsContainer: {
display: 'flex',
flexShrink: 0,
@@ -288,19 +293,23 @@ const GameDashboardCard = ({
)
}
>
-
- onOpenGameManager({ game, widgetToScrollTo: 'projects' })
- }
- icon={}
- label={
-
- {projectsList.length}
-
- }
- disabled={disabled}
- style={styles.projectFilesButton}
- />
+ {/* Button must be wrapped in a container so that the parent tooltip
+ can display even if the button is disabled. */}
+
+
+ onOpenGameManager({ game, widgetToScrollTo: 'projects' })
+ }
+ icon={}
+ label={
+
+ {projectsList.length}
+
+ }
+ disabled={disabled}
+ style={styles.projectFilesButton}
+ />
+
>
)}
diff --git a/newIDE/app/src/GameDashboard/Wallet/WalletWidget.js b/newIDE/app/src/GameDashboard/Wallet/WalletWidget.js
index dda51811440e..98f6d039d826 100644
--- a/newIDE/app/src/GameDashboard/Wallet/WalletWidget.js
+++ b/newIDE/app/src/GameDashboard/Wallet/WalletWidget.js
@@ -7,22 +7,22 @@ import DashboardWidget, {
import { ColumnStackLayout } from '../../UI/Layout';
import Coin from '../../Credits/Icons/Coin';
import AuthenticatedUserContext from '../../Profile/AuthenticatedUserContext';
-import { EarnBadges } from '../../MainFrame/EditorContainers/HomePage/GetStartedSection/EarnBadges';
+import { EarnCredits } from '../../MainFrame/EditorContainers/HomePage/GetStartedSection/EarnCredits';
import TextButton from '../../UI/TextButton';
import { Trans } from '@lingui/macro';
type Props = {|
onOpenProfile: () => void,
size: DashboardWidgetSize,
- showRandomBadge?: boolean,
- showAllBadges?: boolean,
+ showOneItem?: boolean,
+ showAllItems?: boolean,
|};
const WalletWidget = ({
onOpenProfile,
size,
- showRandomBadge,
- showAllBadges,
+ showOneItem,
+ showAllItems,
}: Props) => {
const {
profile,
@@ -32,6 +32,7 @@ const WalletWidget = ({
onOpenCreateAccountDialog,
} = React.useContext(AuthenticatedUserContext);
const creditsAvailable = limits ? limits.credits.userBalance.amount : 0;
+
return (
-
diff --git a/newIDE/app/src/MainFrame/EditorContainers/HomePage/CreateSection/index.js b/newIDE/app/src/MainFrame/EditorContainers/HomePage/CreateSection/index.js
index 156d288d3811..df6b7d4144ac 100644
--- a/newIDE/app/src/MainFrame/EditorContainers/HomePage/CreateSection/index.js
+++ b/newIDE/app/src/MainFrame/EditorContainers/HomePage/CreateSection/index.js
@@ -508,7 +508,7 @@ const CreateSection = ({
/>
- {!hasAProjectOpenedOrSavedOrGameRegistered ? (
- Publish your first game
- ) : (
- Publish a game in 1 minute
- )}
+ Remix a game in 2 minutes
- Remix an existing game
+ Start from a template
setShowAllGameTemplates(true)}
diff --git a/newIDE/app/src/MainFrame/EditorContainers/HomePage/GetStartedSection/EarnBadges.js b/newIDE/app/src/MainFrame/EditorContainers/HomePage/GetStartedSection/EarnCredits.js
similarity index 50%
rename from newIDE/app/src/MainFrame/EditorContainers/HomePage/GetStartedSection/EarnBadges.js
rename to newIDE/app/src/MainFrame/EditorContainers/HomePage/GetStartedSection/EarnCredits.js
index 7539163c7671..ae1ecf8a6a44 100644
--- a/newIDE/app/src/MainFrame/EditorContainers/HomePage/GetStartedSection/EarnBadges.js
+++ b/newIDE/app/src/MainFrame/EditorContainers/HomePage/GetStartedSection/EarnCredits.js
@@ -18,21 +18,19 @@ import { I18n } from '@lingui/react';
import { useResponsiveWindowSize } from '../../../../UI/Responsive/ResponsiveWindowMeasurer';
import TextButton from '../../../../UI/TextButton';
-const getAchievement = (achievements: ?Array, id: string) =>
- achievements && achievements.find(achievement => achievement.id === id);
-
-const hasBadge = (badges: ?Array, achievementId: string) =>
- !!badges && badges.some(badge => badge.achievementId === achievementId);
-
-export const hasMissingBadges = (
- badges: ?Array,
- achievements: ?Array
-) =>
- // Not connected
- !badges ||
- !achievements ||
- // Connected but some achievements are not yet claimed
- achievements.some(achievement => !hasBadge(badges, achievement.id));
+type CreditItemType = 'badge' | 'feedback';
+type BadgeInfo = {|
+ id: string,
+ label: React.Node,
+ linkUrl: string,
+ hasThisBadge?: boolean,
+ type: 'badge',
+|};
+type FeedbackInfo = {|
+ id: string,
+ type: 'feedback',
+|};
+type CreditItem = BadgeInfo | FeedbackInfo;
const styles = {
badgeContainer: {
@@ -63,12 +61,93 @@ const styles = {
gap: 4,
color: 'white',
},
- badgeItemPlaceholder: {
+ itemPlaceholder: {
display: 'flex',
flex: 1,
},
};
+const FeedbackItem = () => {
+ return (
+
+
+
+
+
+
+ 10
+
+
+
+
+
+
+ Community helper
+
+
+
+ Give feedback on a game!
+
+
+ Play a game}
+ secondary
+ onClick={() => {
+ Window.openExternalURL('https://gd.games/games/random');
+ }}
+ />
+
+ );
+};
+
+const allBadgesInfo: BadgeInfo[] = [
+ {
+ id: 'github-star',
+ label: Star GDevelop,
+ linkUrl: 'https://github.com/4ian/GDevelop',
+ type: 'badge',
+ },
+ {
+ id: 'tiktok-follow',
+ label: Follow,
+ linkUrl: 'https://www.tiktok.com/@gdevelop',
+ type: 'badge',
+ },
+ {
+ id: 'twitter-follow',
+ label: Follow,
+ linkUrl: 'https://x.com/GDevelopApp',
+ type: 'badge',
+ },
+];
+
+const getAllBadgesWithOwnedStatus = (badges: ?Array): BadgeInfo[] => {
+ return allBadgesInfo.map(badgeInfo => ({
+ ...badgeInfo,
+ hasThisBadge: hasBadge(badges, badgeInfo.id),
+ }));
+};
+
+const getAchievement = (achievements: ?Array, id: string) =>
+ achievements && achievements.find(achievement => achievement.id === id);
+
+const hasBadge = (badges: ?Array, achievementId: string) =>
+ !!badges && badges.some(badge => badge.achievementId === achievementId);
+
+export const hasMissingBadges = (
+ badges: ?Array,
+ achievements: ?Array
+) =>
+ // Not connected
+ !badges ||
+ !achievements ||
+ // Connected but some achievements are not yet claimed
+ achievements.some(achievement => !hasBadge(badges, achievement.id));
+
const BadgeItem = ({
achievement,
hasThisBadge,
@@ -137,84 +216,117 @@ const BadgeItem = ({
);
};
-const allBadgesInfo = [
- {
- id: 'github-star',
- label: Star GDevelop,
- linkUrl: 'https://github.com/4ian/GDevelop',
- },
- {
- id: 'tiktok-follow',
- label: Follow,
- linkUrl: 'https://www.tiktok.com/@gdevelop',
- },
- {
- id: 'twitter-follow',
- label: Follow,
- linkUrl: 'https://x.com/GDevelopApp',
- },
-];
-
type Props = {|
achievements: ?Array,
badges: ?Array,
onOpenProfile: () => void,
- showRandomBadge?: boolean,
- showAllBadges?: boolean,
+ showRandomItem?: boolean,
+ showAllItems?: boolean,
|};
-export const EarnBadges = ({
+export const EarnCredits = ({
achievements,
badges,
onOpenProfile,
- showRandomBadge,
- showAllBadges,
+ showRandomItem,
+ showAllItems,
}: Props) => {
const { windowSize, isMobile } = useResponsiveWindowSize();
const isMobileOrMediumWidth =
windowSize === 'small' || windowSize === 'medium';
+ const allBadgesWithOwnedStatus = React.useMemo(
+ () => getAllBadgesWithOwnedStatus(badges),
+ [badges]
+ );
+
+ const missingBadges = React.useMemo(
+ () => {
+ return allBadgesWithOwnedStatus.filter(badge => !badge.hasThisBadge);
+ },
+ [allBadgesWithOwnedStatus]
+ );
+
+ const randomItemToShow: ?CreditItemType = React.useMemo(
+ () => {
+ // If on mobile, and not forcing all items, show only 1 item to avoid taking too much space.
+ if (showRandomItem || (isMobile && !showAllItems)) {
+ if (missingBadges.length === 0) {
+ return 'feedback';
+ }
+
+ const totalPossibilities = missingBadges.length + 1; // +1 for feedback
+ // Randomize between badge and feedback, with the weight of the number of badges missing.
+ const randomIndex = Math.floor(Math.random() * totalPossibilities);
+ if (randomIndex === totalPossibilities - 1) {
+ return 'feedback';
+ }
+
+ return 'badge';
+ }
+
+ return null;
+ },
+ [missingBadges, showRandomItem, showAllItems, isMobile]
+ );
+
const badgesToShow = React.useMemo(
() => {
- const allBadgesWithOwnedStatus = allBadgesInfo.map(badgeInfo => ({
- ...badgeInfo,
- hasThisBadge: hasBadge(badges, badgeInfo.id),
- }));
- const notOwnedBadges = allBadgesWithOwnedStatus.filter(
- badge => !badge.hasThisBadge
- );
-
- // If on mobile, and not forcing all badges, show only 1 badge to avoid taking too much space.
- if (showRandomBadge || (isMobile && !showAllBadges)) {
- if (notOwnedBadges.length === 0) {
+ if (!!randomItemToShow && randomItemToShow !== 'badge') {
+ return [];
+ }
+
+ if (randomItemToShow === 'badge') {
+ if (missingBadges.length === 0) {
const randomIndex = Math.floor(
Math.random() * allBadgesWithOwnedStatus.length
);
return [allBadgesWithOwnedStatus[randomIndex]];
}
- const randomIndex = Math.floor(Math.random() * notOwnedBadges.length);
- return [notOwnedBadges[randomIndex]];
+ const randomIndex = Math.floor(Math.random() * missingBadges.length);
+ return [missingBadges[randomIndex]];
}
return allBadgesWithOwnedStatus;
},
- [badges, showRandomBadge, isMobile, showAllBadges]
+ [allBadgesWithOwnedStatus, missingBadges, randomItemToShow]
+ );
+
+ const feedbackItemsToShow: FeedbackInfo[] = React.useMemo(
+ () => {
+ if (!!randomItemToShow && randomItemToShow !== 'feedback') {
+ return [];
+ }
+
+ return [
+ {
+ id: 'random-game-feedback',
+ type: 'feedback',
+ },
+ ];
+ },
+ [randomItemToShow]
+ );
+
+ const allItemsToShow: CreditItem[] = React.useMemo(
+ () => [...badgesToShow, ...feedbackItemsToShow],
+ [badgesToShow, feedbackItemsToShow]
);
- // Slice badges in arrays of two to display them in a responsive way.
- const badgesSlicedInArraysOfTwo = React.useMemo(
+ // Slice items in arrays of two to display them in a responsive way.
+ const itemsSlicedInArraysOfTwo: CreditItem[][] = React.useMemo(
() => {
- const slicedBadges = [];
- for (let i = 0; i < badgesToShow.length; i += 2) {
- slicedBadges.push(badgesToShow.slice(i, i + 2));
+ const slicedItems: CreditItem[][] = [];
+ for (let i = 0; i < allItemsToShow.length; i += 2) {
+ slicedItems.push(allItemsToShow.slice(i, i + 2));
}
- return slicedBadges;
+ return slicedItems;
},
- [badgesToShow]
+ [allItemsToShow]
);
- const onlyOneBadgeDisplayed = badgesToShow.length === 1;
+ const onlyOneItemDisplayed = allItemsToShow.length === 1;
return (
@@ -223,26 +335,36 @@ export const EarnBadges = ({
expand
forceMobileLayout={isMobileOrMediumWidth}
>
- {badgesSlicedInArraysOfTwo.map((badges, index) => (
+ {itemsSlicedInArraysOfTwo.map((items, index) => (
- {badges.map(badge => (
-
- ))}
- {badges.length === 1 &&
- !onlyOneBadgeDisplayed &&
- isMobileOrMediumWidth && (
-
- )}
+ {items
+ .map(item => {
+ if (item.type === 'feedback') {
+ return ;
+ }
+
+ if (item.type === 'badge') {
+ return (
+
+ );
+ }
+
+ return null;
+ })
+ .filter(Boolean)}
+ {items.length === 1 &&
+ !onlyOneItemDisplayed &&
+ isMobileOrMediumWidth && }
))}
diff --git a/newIDE/app/src/MainFrame/index.js b/newIDE/app/src/MainFrame/index.js
index 7787843ee31b..df965d1c3eb0 100644
--- a/newIDE/app/src/MainFrame/index.js
+++ b/newIDE/app/src/MainFrame/index.js
@@ -1142,6 +1142,20 @@ const MainFrame = (props: Props) => {
oldProjectId,
options,
}) => {
+ // Update the currentFileMetadata based on the updated project, as
+ // it can have been updated in the meantime (gameId, project name, etc...).
+ // Use the ref here to be sure to have the latest file metadata.
+ if (currentFileMetadataRef.current) {
+ const newFileMetadata: FileMetadata = {
+ ...currentFileMetadataRef.current,
+ name: project.getName(),
+ gameId: project.getProjectUuid(),
+ };
+ setState(state => ({
+ ...state,
+ currentFileMetadata: newFileMetadata,
+ }));
+ }
setNewProjectSetupDialogOpen(false);
if (options.openQuickCustomizationDialog) {
setQuickCustomizationDialogOpenedFromGameId(oldProjectId);
From b239676a91467dfb5a22c64ddc84b3ce265a5753 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Cl=C3=A9ment=20Pasteau?=
<4895034+ClementPasteau@users.noreply.github.com>
Date: Fri, 13 Dec 2024 17:27:10 +0100
Subject: [PATCH 23/26] Fix AlertDialog properly handling Markdown
---
newIDE/app/src/GameDashboard/GameDashboardCard.js | 2 +-
newIDE/app/src/UI/Alert/AlertDialog.js | 5 ++++-
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/newIDE/app/src/GameDashboard/GameDashboardCard.js b/newIDE/app/src/GameDashboard/GameDashboardCard.js
index cb9f2d6cb021..650ee5ebbaf1 100644
--- a/newIDE/app/src/GameDashboard/GameDashboardCard.js
+++ b/newIDE/app/src/GameDashboard/GameDashboardCard.js
@@ -571,7 +571,7 @@ const GameDashboardCard = ({
: () => {
showAlert({
title: t`No project to open`,
- message: t`Looks like your project isn't there!\n\nYou may be using a different computer or opening GDevelop on the web and your project is saved locally.`,
+ message: t`Looks like your project isn't there!${'\n\n'}You may be using a different computer or opening GDevelop on the web and your project is saved locally.`,
});
};
diff --git a/newIDE/app/src/UI/Alert/AlertDialog.js b/newIDE/app/src/UI/Alert/AlertDialog.js
index ca2f80fc537f..590ac0292c1a 100644
--- a/newIDE/app/src/UI/Alert/AlertDialog.js
+++ b/newIDE/app/src/UI/Alert/AlertDialog.js
@@ -7,6 +7,7 @@ import { type MessageDescriptor } from '../../Utils/i18n/MessageDescriptor.flow'
import Dialog from '../Dialog';
import FlatButton from '../FlatButton';
import Text from '../Text';
+import { MarkdownText } from '../MarkdownText';
type Props = {|
open: boolean,
@@ -42,7 +43,9 @@ function AlertDialog(props: Props) {
onRequestClose={props.onDismiss}
onApply={props.onDismiss}
>
- {i18n._(props.message)}
+
+
+
)}
From da46b17ef212c41fa6b7311e0c1ac52bc09998ad Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Cl=C3=A9ment=20Pasteau?=
<4895034+ClementPasteau@users.noreply.github.com>
Date: Fri, 13 Dec 2024 18:10:40 +0100
Subject: [PATCH 24/26] Assume quick customization game is saved
---
newIDE/app/src/GameDashboard/GamesList.js | 20 ++++++++++---------
.../src/QuickCustomization/QuickPublish.js | 3 +++
2 files changed, 14 insertions(+), 9 deletions(-)
diff --git a/newIDE/app/src/GameDashboard/GamesList.js b/newIDE/app/src/GameDashboard/GamesList.js
index b07b247d429f..a1cdee0994ac 100644
--- a/newIDE/app/src/GameDashboard/GamesList.js
+++ b/newIDE/app/src/GameDashboard/GamesList.js
@@ -146,9 +146,16 @@ const getDashboardItemsToDisplay = ({
orderBy: GamesDashboardOrderBy,
|}): ?Array => {
if (!allDashboardItems) return null;
- let itemsToDisplay: DashboardItem[] = allDashboardItems;
+ let itemsToDisplay: DashboardItem[] = allDashboardItems.filter(
+ item =>
+ // First, filter out unsaved games, unless they are the opened project.
+ !item.game ||
+ item.game.savedStatus !== 'draft' ||
+ (project && item.game.id === project.getProjectUuid())
+ );
if (searchText) {
+ // If there is a search, just return those items, ordered by the search relevance.
const searchResults = searchClient.search(
getFuseSearchQueryForMultipleKeys(searchText, [
'game.gameName',
@@ -157,7 +164,7 @@ const getDashboardItemsToDisplay = ({
);
itemsToDisplay = searchResults.map(result => result.item);
} else {
- // Order items first.
+ // If there is no search, sort the items by the selected order.
itemsToDisplay =
orderBy === 'totalSessions'
? itemsToDisplay.sort(totalSessionsSort)
@@ -218,19 +225,14 @@ const getDashboardItemsToDisplay = ({
}
}
+ // Finally, if there is no search, paginate the results.
itemsToDisplay = itemsToDisplay.slice(
(currentPage - 1) * pageSize,
currentPage * pageSize
);
}
- return itemsToDisplay.filter(
- item =>
- // Filter out unsaved games, unless they are the opened project.
- !item.game ||
- item.game.savedStatus !== 'draft' ||
- (project && item.game.id === project.getProjectUuid())
- );
+ return itemsToDisplay;
};
type Props = {|
diff --git a/newIDE/app/src/QuickCustomization/QuickPublish.js b/newIDE/app/src/QuickCustomization/QuickPublish.js
index 5a7a946fd7e3..12b4b3ee1419 100644
--- a/newIDE/app/src/QuickCustomization/QuickPublish.js
+++ b/newIDE/app/src/QuickCustomization/QuickPublish.js
@@ -148,6 +148,9 @@ export const QuickPublish = ({
{
publicWebBuildId: build.id,
screenshotUrls: newGameScreenshotUrls,
+ // Here we assume the game is saved, as it just got exported properly,
+ // And the same is happening in the background.
+ savedStatus: 'saved',
}
);
setGame(updatedGame);
From 55cb9f500e2138b5333219cdf278a66b3267bff8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Cl=C3=A9ment=20Pasteau?=
<4895034+ClementPasteau@users.noreply.github.com>
Date: Mon, 16 Dec 2024 10:24:35 +0100
Subject: [PATCH 25/26] Fix padding
---
.../MainFrame/EditorContainers/HomePage/HomePageMenuBar.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/newIDE/app/src/MainFrame/EditorContainers/HomePage/HomePageMenuBar.js b/newIDE/app/src/MainFrame/EditorContainers/HomePage/HomePageMenuBar.js
index dd94859b73d8..f92edcbcf3c8 100644
--- a/newIDE/app/src/MainFrame/EditorContainers/HomePage/HomePageMenuBar.js
+++ b/newIDE/app/src/MainFrame/EditorContainers/HomePage/HomePageMenuBar.js
@@ -19,7 +19,7 @@ import {
} from './HomePageMenu';
import { Toolbar, ToolbarGroup } from '../../../UI/Toolbar';
import AuthenticatedUserContext from '../../../Profile/AuthenticatedUserContext';
-import { SECTION_PADDING } from './SectionContainer';
+import { SECTION_DESKTOP_SPACING } from './SectionContainer';
const iconSize = 20;
const iconButtonPaddingTop = 8;
@@ -41,7 +41,7 @@ export const homepageMediumMenuBarWidth =
export const styles = {
desktopMenu: {
- paddingTop: SECTION_PADDING, // To align with the top of the sections
+ paddingTop: SECTION_DESKTOP_SPACING, // To align with the top of the sections
paddingBottom: 10,
minWidth: homepageDesktopMenuBarWidth,
display: 'flex',
From f66f573db4b2673f85705017a9bf23ab2ca5e91a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Cl=C3=A9ment=20Pasteau?=
<4895034+ClementPasteau@users.noreply.github.com>
Date: Mon, 16 Dec 2024 10:29:05 +0100
Subject: [PATCH 26/26] Fix learn ratio
---
newIDE/app/src/Course/CoursePreviewBanner.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/newIDE/app/src/Course/CoursePreviewBanner.js b/newIDE/app/src/Course/CoursePreviewBanner.js
index e66f3d0b39b3..2e7130a9f3ae 100644
--- a/newIDE/app/src/Course/CoursePreviewBanner.js
+++ b/newIDE/app/src/Course/CoursePreviewBanner.js
@@ -45,7 +45,7 @@ const styles = {
progress: { borderRadius: 4, height: 5 },
chip: { height: 24 },
gdevelopAvatar: { width: 20, height: 20 },
- thumbnail: { borderRadius: 4 },
+ thumbnail: { borderRadius: 4, aspectRatio: '16 / 9' },
statusContainer: {
display: 'flex',
alignItems: 'center',