Skip to content

Commit 272766c

Browse files
Automatically use a screenshot from the latest preview as the game thumbnail if none are set (#7156)
1 parent a06138b commit 272766c

File tree

5 files changed

+121
-9
lines changed

5 files changed

+121
-9
lines changed

newIDE/app/src/MainFrame/Preferences/PreferencesContext.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@ export type PreferencesValues = {|
225225
fetchPlayerTokenForPreviewAutomatically: boolean,
226226
previewCrashReportUploadLevel: string,
227227
gamesListOrderBy: 'createdAt' | 'totalSessions' | 'weeklySessions',
228+
takeScreenshotOnPreview: boolean,
228229
|};
229230

230231
/**
@@ -326,6 +327,7 @@ export type Preferences = {|
326327
setGamesListOrderBy: (
327328
orderBy: 'createdAt' | 'totalSessions' | 'weeklySessions'
328329
) => void,
330+
setTakeScreenshotOnPreview: (enabled: boolean) => void,
329331
|};
330332

331333
export const initialPreferences = {
@@ -382,6 +384,7 @@ export const initialPreferences = {
382384
fetchPlayerTokenForPreviewAutomatically: true,
383385
previewCrashReportUploadLevel: 'exclude-javascript-code-events',
384386
gamesListOrderBy: 'createdAt',
387+
takeScreenshotOnPreview: true,
385388
},
386389
setLanguage: () => {},
387390
setThemeName: () => {},
@@ -455,6 +458,7 @@ export const initialPreferences = {
455458
setGamesListOrderBy: (
456459
orderBy: 'createdAt' | 'totalSessions' | 'weeklySessions'
457460
) => {},
461+
setTakeScreenshotOnPreview: (enabled: boolean) => {},
458462
};
459463

460464
const PreferencesContext = React.createContext<Preferences>(initialPreferences);

newIDE/app/src/MainFrame/Preferences/PreferencesDialog.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ const PreferencesDialog = ({
8282
setDisplaySaveReminder,
8383
setFetchPlayerTokenForPreviewAutomatically,
8484
setPreviewCrashReportUploadLevel,
85+
setTakeScreenshotOnPreview,
8586
} = React.useContext(PreferencesContext);
8687

8788
const initialUse3DEditor = React.useRef<boolean>(values.use3DEditor);
@@ -441,6 +442,14 @@ const PreferencesDialog = ({
441442
<Trans>Send crash reports during previews to GDevelop</Trans>
442443
}
443444
/>
445+
<Toggle
446+
onToggle={(e, check) => setTakeScreenshotOnPreview(check)}
447+
toggled={values.takeScreenshotOnPreview}
448+
labelPosition="right"
449+
label={
450+
<Trans>Automatically take a screenshot in game previews</Trans>
451+
}
452+
/>
444453
<Toggle
445454
onToggle={(e, check) => setShowDeprecatedInstructionWarning(check)}
446455
toggled={values.showDeprecatedInstructionWarning}

newIDE/app/src/MainFrame/Preferences/PreferencesProvider.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ export default class PreferencesProvider extends React.Component<Props, State> {
197197
this
198198
),
199199
setGamesListOrderBy: this._setGamesListOrderBy.bind(this),
200+
setTakeScreenshotOnPreview: this._setTakeScreenshotOnPreview.bind(this),
200201
};
201202

202203
componentDidMount() {
@@ -1021,6 +1022,18 @@ export default class PreferencesProvider extends React.Component<Props, State> {
10211022
);
10221023
}
10231024

1025+
_setTakeScreenshotOnPreview(newValue: boolean) {
1026+
this.setState(
1027+
state => ({
1028+
values: {
1029+
...state.values,
1030+
takeScreenshotOnPreview: newValue,
1031+
},
1032+
}),
1033+
() => this._persistValuesToLocalStorage(this.state)
1034+
);
1035+
}
1036+
10241037
render() {
10251038
return (
10261039
<PreferencesContext.Provider value={this.state}>

newIDE/app/src/MainFrame/UseCapturesManager.js

Lines changed: 79 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,23 @@ import {
55
type LaunchCaptureOptions,
66
type CaptureOptions,
77
} from '../ExportAndShare/PreviewLauncher.flow';
8-
import { createGameResourceSignedUrls } from '../Utils/GDevelopServices/Game';
9-
10-
const useCapturesManager = ({ project }: { project: ?gdProject }) => {
8+
import {
9+
createGameResourceSignedUrls,
10+
updateGame,
11+
} from '../Utils/GDevelopServices/Game';
12+
import { type GamesList } from '../GameDashboard/UseGamesList';
13+
import AuthenticatedUserContext from '../Profile/AuthenticatedUserContext';
14+
import PreferencesContext from './Preferences/PreferencesContext';
15+
16+
export const TIME_BETWEEN_PREVIEW_SCREENSHOTS = 1000 * 60 * 3; // 3 minutes
17+
18+
const useCapturesManager = ({
19+
project,
20+
gamesList,
21+
}: {
22+
project: ?gdProject,
23+
gamesList: GamesList,
24+
}) => {
1125
const [
1226
unverifiedGameScreenshots,
1327
setUnverifiedGameScreenshots,
@@ -17,6 +31,14 @@ const useCapturesManager = ({ project }: { project: ?gdProject }) => {
1731
unverifiedPublicUrl: string,
1832
|}>
1933
>([]);
34+
const [
35+
lastPreviewScreenshotsTakenAt,
36+
setLastPreviewScreenshotsTakenAt,
37+
] = React.useState<{ [projectUuid: string]: number }>({});
38+
const { getAuthorizationHeader, profile } = React.useContext(
39+
AuthenticatedUserContext
40+
);
41+
const preferences = React.useContext(PreferencesContext);
2042

2143
const createCaptureOptionsForPreview = React.useCallback(
2244
async (
@@ -70,6 +92,7 @@ const useCapturesManager = ({ project }: { project: ?gdProject }) => {
7092
const onCaptureFinished = React.useCallback(
7193
async (captureOptions: CaptureOptions) => {
7294
if (!project) return;
95+
const projectId = project.getProjectUuid();
7396

7497
try {
7598
const screenshots = captureOptions.screenshots;
@@ -101,18 +124,54 @@ const useCapturesManager = ({ project }: { project: ?gdProject }) => {
101124

102125
if (!uploadedScreenshotPublicUrls.length) return;
103126

127+
const game = gamesList.games
128+
? gamesList.games.find(game => game.id === projectId)
129+
: null;
130+
131+
setLastPreviewScreenshotsTakenAt(lastPreviewScreenshotsTakenAt => ({
132+
...lastPreviewScreenshotsTakenAt,
133+
[projectId]: Date.now(),
134+
}));
135+
136+
// The game is registered, let's update it.
137+
if (game && profile) {
138+
try {
139+
const currentGameScreenshotUrls = game.screenshotUrls || [];
140+
const newGameScreenshotUrls = [
141+
...currentGameScreenshotUrls,
142+
...uploadedScreenshotPublicUrls,
143+
];
144+
const updatedGame = await updateGame(
145+
getAuthorizationHeader,
146+
profile.id,
147+
game.id,
148+
{
149+
screenshotUrls: newGameScreenshotUrls,
150+
}
151+
);
152+
gamesList.onGameUpdated(updatedGame);
153+
} catch (error) {
154+
console.error(
155+
'Error while updating game with new screenshots:',
156+
error
157+
);
158+
// Do not throw or save the screenshots.
159+
}
160+
return;
161+
}
162+
104163
setUnverifiedGameScreenshots(unverifiedScreenshots => [
105164
...unverifiedScreenshots,
106165
...uploadedScreenshotPublicUrls.map(unverifiedPublicUrl => ({
107-
projectUuid: project.getProjectUuid(),
166+
projectUuid: projectId,
108167
unverifiedPublicUrl,
109168
})),
110169
]);
111170
} catch (error) {
112171
console.error('Error while handling finished capture options:', error);
113172
}
114173
},
115-
[project]
174+
[project, gamesList, getAuthorizationHeader, profile]
116175
);
117176

118177
const getGameUnverifiedScreenshotUrls = React.useCallback(
@@ -138,11 +197,26 @@ const useCapturesManager = ({ project }: { project: ?gdProject }) => {
138197
[project]
139198
);
140199

200+
const getHotReloadPreviewLaunchCaptureOptions = React.useCallback(
201+
(gameId: string): LaunchCaptureOptions | void => {
202+
const shouldTakeScreenshotOnPreview =
203+
preferences.values.takeScreenshotOnPreview &&
204+
Date.now() >
205+
(lastPreviewScreenshotsTakenAt[gameId] || 0) +
206+
TIME_BETWEEN_PREVIEW_SCREENSHOTS;
207+
return shouldTakeScreenshotOnPreview
208+
? { screenshots: [{ delayTimeInSeconds: 3000 }] }
209+
: undefined;
210+
},
211+
[preferences.values.takeScreenshotOnPreview, lastPreviewScreenshotsTakenAt]
212+
);
213+
141214
return {
142215
createCaptureOptionsForPreview,
143216
onCaptureFinished,
144217
getGameUnverifiedScreenshotUrls,
145218
onGameScreenshotsClaimed,
219+
getHotReloadPreviewLaunchCaptureOptions,
146220
};
147221
};
148222

newIDE/app/src/MainFrame/index.js

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -541,7 +541,8 @@ const MainFrame = (props: Props) => {
541541
onCaptureFinished,
542542
onGameScreenshotsClaimed,
543543
getGameUnverifiedScreenshotUrls,
544-
} = useCapturesManager({ project: currentProject });
544+
getHotReloadPreviewLaunchCaptureOptions,
545+
} = useCapturesManager({ project: currentProject, gamesList });
545546

546547
/**
547548
* This reference is useful to get the current opened project,
@@ -1723,14 +1724,25 @@ const MainFrame = (props: Props) => {
17231724
const launchNewPreview = React.useCallback(
17241725
async options => {
17251726
const numberOfWindows = options ? options.numberOfWindows : 1;
1726-
launchPreview({ networkPreview: false, numberOfWindows });
1727+
await launchPreview({ networkPreview: false, numberOfWindows });
17271728
},
17281729
[launchPreview]
17291730
);
17301731

17311732
const launchHotReloadPreview = React.useCallback(
1732-
() => launchPreview({ networkPreview: false, hotReload: true }),
1733-
[launchPreview]
1733+
async () => {
1734+
const launchCaptureOptions = currentProject
1735+
? getHotReloadPreviewLaunchCaptureOptions(
1736+
currentProject.getProjectUuid()
1737+
)
1738+
: undefined;
1739+
await launchPreview({
1740+
networkPreview: false,
1741+
hotReload: true,
1742+
launchCaptureOptions,
1743+
});
1744+
},
1745+
[currentProject, launchPreview, getHotReloadPreviewLaunchCaptureOptions]
17341746
);
17351747

17361748
const launchNetworkPreview = React.useCallback(

0 commit comments

Comments
 (0)