Skip to content

Commit

Permalink
Automatically use a screenshot from the latest preview as the game th…
Browse files Browse the repository at this point in the history
…umbnail if none are set (#7156)
  • Loading branch information
ClementPasteau authored Nov 14, 2024
1 parent a06138b commit 272766c
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 9 deletions.
4 changes: 4 additions & 0 deletions newIDE/app/src/MainFrame/Preferences/PreferencesContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ export type PreferencesValues = {|
fetchPlayerTokenForPreviewAutomatically: boolean,
previewCrashReportUploadLevel: string,
gamesListOrderBy: 'createdAt' | 'totalSessions' | 'weeklySessions',
takeScreenshotOnPreview: boolean,
|};

/**
Expand Down Expand Up @@ -326,6 +327,7 @@ export type Preferences = {|
setGamesListOrderBy: (
orderBy: 'createdAt' | 'totalSessions' | 'weeklySessions'
) => void,
setTakeScreenshotOnPreview: (enabled: boolean) => void,
|};

export const initialPreferences = {
Expand Down Expand Up @@ -382,6 +384,7 @@ export const initialPreferences = {
fetchPlayerTokenForPreviewAutomatically: true,
previewCrashReportUploadLevel: 'exclude-javascript-code-events',
gamesListOrderBy: 'createdAt',
takeScreenshotOnPreview: true,
},
setLanguage: () => {},
setThemeName: () => {},
Expand Down Expand Up @@ -455,6 +458,7 @@ export const initialPreferences = {
setGamesListOrderBy: (
orderBy: 'createdAt' | 'totalSessions' | 'weeklySessions'
) => {},
setTakeScreenshotOnPreview: (enabled: boolean) => {},
};

const PreferencesContext = React.createContext<Preferences>(initialPreferences);
Expand Down
9 changes: 9 additions & 0 deletions newIDE/app/src/MainFrame/Preferences/PreferencesDialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ const PreferencesDialog = ({
setDisplaySaveReminder,
setFetchPlayerTokenForPreviewAutomatically,
setPreviewCrashReportUploadLevel,
setTakeScreenshotOnPreview,
} = React.useContext(PreferencesContext);

const initialUse3DEditor = React.useRef<boolean>(values.use3DEditor);
Expand Down Expand Up @@ -441,6 +442,14 @@ const PreferencesDialog = ({
<Trans>Send crash reports during previews to GDevelop</Trans>
}
/>
<Toggle
onToggle={(e, check) => setTakeScreenshotOnPreview(check)}
toggled={values.takeScreenshotOnPreview}
labelPosition="right"
label={
<Trans>Automatically take a screenshot in game previews</Trans>
}
/>
<Toggle
onToggle={(e, check) => setShowDeprecatedInstructionWarning(check)}
toggled={values.showDeprecatedInstructionWarning}
Expand Down
13 changes: 13 additions & 0 deletions newIDE/app/src/MainFrame/Preferences/PreferencesProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ export default class PreferencesProvider extends React.Component<Props, State> {
this
),
setGamesListOrderBy: this._setGamesListOrderBy.bind(this),
setTakeScreenshotOnPreview: this._setTakeScreenshotOnPreview.bind(this),
};

componentDidMount() {
Expand Down Expand Up @@ -1021,6 +1022,18 @@ export default class PreferencesProvider extends React.Component<Props, State> {
);
}

_setTakeScreenshotOnPreview(newValue: boolean) {
this.setState(
state => ({
values: {
...state.values,
takeScreenshotOnPreview: newValue,
},
}),
() => this._persistValuesToLocalStorage(this.state)
);
}

render() {
return (
<PreferencesContext.Provider value={this.state}>
Expand Down
84 changes: 79 additions & 5 deletions newIDE/app/src/MainFrame/UseCapturesManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,23 @@ import {
type LaunchCaptureOptions,
type CaptureOptions,
} from '../ExportAndShare/PreviewLauncher.flow';
import { createGameResourceSignedUrls } from '../Utils/GDevelopServices/Game';

const useCapturesManager = ({ project }: { project: ?gdProject }) => {
import {
createGameResourceSignedUrls,
updateGame,
} from '../Utils/GDevelopServices/Game';
import { type GamesList } from '../GameDashboard/UseGamesList';
import AuthenticatedUserContext from '../Profile/AuthenticatedUserContext';
import PreferencesContext from './Preferences/PreferencesContext';

export const TIME_BETWEEN_PREVIEW_SCREENSHOTS = 1000 * 60 * 3; // 3 minutes

const useCapturesManager = ({
project,
gamesList,
}: {
project: ?gdProject,
gamesList: GamesList,
}) => {
const [
unverifiedGameScreenshots,
setUnverifiedGameScreenshots,
Expand All @@ -17,6 +31,14 @@ const useCapturesManager = ({ project }: { project: ?gdProject }) => {
unverifiedPublicUrl: string,
|}>
>([]);
const [
lastPreviewScreenshotsTakenAt,
setLastPreviewScreenshotsTakenAt,
] = React.useState<{ [projectUuid: string]: number }>({});
const { getAuthorizationHeader, profile } = React.useContext(
AuthenticatedUserContext
);
const preferences = React.useContext(PreferencesContext);

const createCaptureOptionsForPreview = React.useCallback(
async (
Expand Down Expand Up @@ -70,6 +92,7 @@ const useCapturesManager = ({ project }: { project: ?gdProject }) => {
const onCaptureFinished = React.useCallback(
async (captureOptions: CaptureOptions) => {
if (!project) return;
const projectId = project.getProjectUuid();

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

if (!uploadedScreenshotPublicUrls.length) return;

const game = gamesList.games
? gamesList.games.find(game => game.id === projectId)
: null;

setLastPreviewScreenshotsTakenAt(lastPreviewScreenshotsTakenAt => ({
...lastPreviewScreenshotsTakenAt,
[projectId]: Date.now(),
}));

// The game is registered, let's update it.
if (game && profile) {
try {
const currentGameScreenshotUrls = game.screenshotUrls || [];
const newGameScreenshotUrls = [
...currentGameScreenshotUrls,
...uploadedScreenshotPublicUrls,
];
const updatedGame = await updateGame(
getAuthorizationHeader,
profile.id,
game.id,
{
screenshotUrls: newGameScreenshotUrls,
}
);
gamesList.onGameUpdated(updatedGame);
} catch (error) {
console.error(
'Error while updating game with new screenshots:',
error
);
// Do not throw or save the screenshots.
}
return;
}

setUnverifiedGameScreenshots(unverifiedScreenshots => [
...unverifiedScreenshots,
...uploadedScreenshotPublicUrls.map(unverifiedPublicUrl => ({
projectUuid: project.getProjectUuid(),
projectUuid: projectId,
unverifiedPublicUrl,
})),
]);
} catch (error) {
console.error('Error while handling finished capture options:', error);
}
},
[project]
[project, gamesList, getAuthorizationHeader, profile]
);

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

const getHotReloadPreviewLaunchCaptureOptions = React.useCallback(
(gameId: string): LaunchCaptureOptions | void => {
const shouldTakeScreenshotOnPreview =
preferences.values.takeScreenshotOnPreview &&
Date.now() >
(lastPreviewScreenshotsTakenAt[gameId] || 0) +
TIME_BETWEEN_PREVIEW_SCREENSHOTS;
return shouldTakeScreenshotOnPreview
? { screenshots: [{ delayTimeInSeconds: 3000 }] }
: undefined;
},
[preferences.values.takeScreenshotOnPreview, lastPreviewScreenshotsTakenAt]
);

return {
createCaptureOptionsForPreview,
onCaptureFinished,
getGameUnverifiedScreenshotUrls,
onGameScreenshotsClaimed,
getHotReloadPreviewLaunchCaptureOptions,
};
};

Expand Down
20 changes: 16 additions & 4 deletions newIDE/app/src/MainFrame/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -541,7 +541,8 @@ const MainFrame = (props: Props) => {
onCaptureFinished,
onGameScreenshotsClaimed,
getGameUnverifiedScreenshotUrls,
} = useCapturesManager({ project: currentProject });
getHotReloadPreviewLaunchCaptureOptions,
} = useCapturesManager({ project: currentProject, gamesList });

/**
* This reference is useful to get the current opened project,
Expand Down Expand Up @@ -1723,14 +1724,25 @@ const MainFrame = (props: Props) => {
const launchNewPreview = React.useCallback(
async options => {
const numberOfWindows = options ? options.numberOfWindows : 1;
launchPreview({ networkPreview: false, numberOfWindows });
await launchPreview({ networkPreview: false, numberOfWindows });
},
[launchPreview]
);

const launchHotReloadPreview = React.useCallback(
() => launchPreview({ networkPreview: false, hotReload: true }),
[launchPreview]
async () => {
const launchCaptureOptions = currentProject
? getHotReloadPreviewLaunchCaptureOptions(
currentProject.getProjectUuid()
)
: undefined;
await launchPreview({
networkPreview: false,
hotReload: true,
launchCaptureOptions,
});
},
[currentProject, launchPreview, getHotReloadPreviewLaunchCaptureOptions]
);

const launchNetworkPreview = React.useCallback(
Expand Down

0 comments on commit 272766c

Please sign in to comment.