Compare commits

...

1 Commits

Author SHA1 Message Date
Clément Pasteau
7d161d7b8f wip 2024-07-05 11:15:20 +02:00
7 changed files with 248 additions and 46 deletions

View File

@@ -109,9 +109,9 @@ namespace gdjs {
// Uncomment to test the case of a failing loading:
// return 'https://gd.games.wronglink';
const baseUrl = 'https://gd.games';
// const baseUrl = 'https://gd.games';
// Uncomment to test locally:
// const baseUrl = 'http://localhost:4000';
const baseUrl = 'http://localhost:4000';
const url = new URL(
`${baseUrl}/games/${gameId}/lobbies${_lobbyId ? `/${_lobbyId}` : ''}`
@@ -627,8 +627,8 @@ namespace gdjs {
},
// Specify the origin to avoid leaking the playerToken.
// Replace with '*' to test locally.
'https://gd.games'
// '*'
// 'https://gd.games'
'*'
);
};

View File

@@ -85,6 +85,8 @@ export const localCordovaExportPipeline: ExportPipeline<
</Column>
) : null,
shouldSuggestBumpingVersionNumber: () => true,
renderExportFlow: (props: ExportFlowProps) => (
<ExportFlow {...props} exportPipelineName={exportPipelineName} />
),

View File

@@ -85,6 +85,8 @@ export const localElectronExportPipeline: ExportPipeline<
</Column>
) : null,
shouldSuggestBumpingVersionNumber: () => true,
renderExportFlow: (props: ExportFlowProps) => (
<ExportFlow {...props} exportPipelineName={exportPipelineName} />
),

View File

@@ -76,6 +76,8 @@ export const localOnlineElectronExportPipeline: ExportPipeline<
renderHeader: props => <SetupExportHeader {...props} />,
shouldSuggestBumpingVersionNumber: () => true,
renderExportFlow: (props: ExportFlowProps) => (
<ExportFlow {...props} exportPipelineName={exportPipelineName} />
),

View File

@@ -36,11 +36,10 @@ import {
} from '../../Utils/GDevelopServices/Badge';
import { extractGDevelopApiErrorStatusAndCode } from '../../Utils/GDevelopServices/Errors';
import { type EventsFunctionsExtensionsState } from '../../EventsFunctionsExtensionsLoader/EventsFunctionsExtensionsContext';
import inc from 'semver/functions/inc';
import Toggle from '../../UI/Toggle';
import PlaceholderLoader from '../../UI/PlaceholderLoader';
import AlertMessage from '../../UI/AlertMessage';
import { type GameAvailabilityError } from '../../GameDashboard/GameRegistration';
import ProjectVersionSelector from './ProjectVersionSelector';
type State = {|
exportStep: BuildStep,
@@ -52,6 +51,7 @@ type State = {|
shouldBumpVersionNumber: boolean,
exportState: any,
doneFooterOpen: boolean,
selectedVersionNumber: string,
|};
type Props = {|
@@ -70,10 +70,6 @@ type Props = {|
onRefreshBuilds: () => Promise<void>,
|};
const getIncrementedVersionNumber = (project: gdProject) => {
return inc(project.getVersion(), 'patch', { loose: true });
};
const getBuildQuota = (
authenticatedUser: AuthenticatedUser,
onlineBuildType: ?string
@@ -107,8 +103,8 @@ export default class ExportLauncher extends Component<Props, State> {
exportState: this.props.exportPipeline.getInitialExportState(
this.props.project
),
selectedVersionNumber: this.props.project.getVersion(),
};
_candidateBumpedVersionNumber = '';
buildsWatcher = new BuildsWatcher();
launchWholeExport: ({|
i18n: I18nType,
@@ -128,10 +124,6 @@ export default class ExportLauncher extends Component<Props, State> {
constructor(props: Props) {
super(props);
this._setupAchievementHook();
this._candidateBumpedVersionNumber = getIncrementedVersionNumber(
props.project
);
}
componentDidUpdate(prevProps: Props, prevState: State) {
this._setupAchievementHook();
@@ -350,13 +342,7 @@ export default class ExportLauncher extends Component<Props, State> {
exportState: this.state.exportState,
};
if (
exportPipeline.shouldSuggestBumpingVersionNumber &&
exportPipeline.shouldSuggestBumpingVersionNumber() &&
this.state.shouldBumpVersionNumber
) {
project.setVersion(this._candidateBumpedVersionNumber);
}
project.setVersion(this.state.selectedVersionNumber);
setStep('export');
this.setState({
@@ -515,8 +501,8 @@ export default class ExportLauncher extends Component<Props, State> {
(exportStep === 'done' && !isBuildRunning) || errored;
const isUsingOnlineBuildNonAuthenticated =
!!exportPipeline.onlineBuildType && !authenticatedUser.authenticated;
const isOnlineBuildIncludedInSubscription =
!!buildQuota && buildQuota.max > 0;
const isIncludedInSubscriptionIfOnline =
!exportPipeline.onlineBuildType || (!!buildQuota && buildQuota.max > 0);
const hasSomeBuildsRunning = hasBuildsCurrentlyRunning();
return (
@@ -559,7 +545,7 @@ export default class ExportLauncher extends Component<Props, State> {
exportPipeline.renderTutorial()}
</Column>
)}
<Column expand justifyContent="center">
<Column expand justifyContent="center" alignItems="center">
{!isUsingOnlineBuildNonAuthenticated && (
<Line alignItems="center" justifyContent="center">
{exportPipeline.renderHeader({
@@ -575,28 +561,16 @@ export default class ExportLauncher extends Component<Props, State> {
</Line>
)}
{!isUsingOnlineBuildNonAuthenticated &&
isOnlineBuildIncludedInSubscription &&
exportPipeline.shouldSuggestBumpingVersionNumber &&
exportPipeline.shouldSuggestBumpingVersionNumber() &&
isIncludedInSubscriptionIfOnline &&
!isExportAndBuildCompleteOrErrored && (
<Line noMargin>
<Toggle
labelPosition="right"
toggled={this.state.shouldBumpVersionNumber}
label={
<Trans>
Increase version number to{' '}
{this._candidateBumpedVersionNumber}
</Trans>
}
onToggle={(e, toggled) => {
this.setState({
shouldBumpVersionNumber: toggled,
});
}}
disabled={isExportingOrWaitingForBuild}
/>
</Line>
<ProjectVersionSelector
project={project}
versionNumber={this.state.selectedVersionNumber}
onVersionNumberChanged={versionNumber => {
this.setState({ selectedVersionNumber: versionNumber });
}}
disabled={isExportingOrWaitingForBuild}
/>
)}
{!!exportPipeline.limitedBuilds &&
authenticatedUser.authenticated &&

View File

@@ -0,0 +1,217 @@
// @flow
import * as React from 'react';
import { Trans, t } from '@lingui/macro';
import { I18n } from '@lingui/react';
import inc from 'semver/functions/inc';
import { ResponsiveLineStackLayout } from '../../UI/Layout';
import AuthenticatedUserContext from '../../Profile/AuthenticatedUserContext';
import { getBuilds } from '../../Utils/GDevelopServices/Build';
import SelectOption from '../../UI/SelectOption';
import SelectField from '../../UI/SelectField';
import TextField from '../../UI/TextField';
import { Column, Line } from '../../UI/Grid';
import FlatButton from '../../UI/FlatButton';
import HelpIcon from '../../UI/HelpIcon';
const gd: libGDevelop = global.gd;
const styles = {
buttonStyle: {
flexShrink: 0,
marginTop: 17,
},
container: {
maxWidth: 400,
},
};
const getIncrementedVersionNumber = (project: gdProject) => {
return inc(project.getVersion(), 'patch', { loose: true });
};
type Props = {|
project: gdProject,
disabled: boolean,
versionNumber: string,
onVersionNumberChanged: string => void,
|};
const ProjectVersionSelector = ({
project,
disabled,
versionNumber,
onVersionNumberChanged,
}: Props) => {
const { getAuthorizationHeader, profile } = React.useContext(
AuthenticatedUserContext
);
const [recentBuildGameVersions, setRecentBuildGameVersions] = React.useState<
string[]
>([project.getVersion()]);
const [isLoading, setIsLoading] = React.useState(true);
const fetchRecentBuildGameVersions = React.useCallback(
async () => {
try {
const gameId = project.getProjectUuid();
if (!gameId) {
return;
}
const userId = profile ? profile.id : null;
if (!userId) {
return;
}
const recentBuilds = await getBuilds(
getAuthorizationHeader,
userId,
gameId
);
const allVersions = recentBuilds
.map(build => build.gameVersion)
.filter(Boolean)
.concat([project.getVersion()]);
const sortedVersions = allVersions.sort((a, b) => {
return a.localeCompare(b);
});
const uniqueVersions = Array.from(new Set(sortedVersions));
setRecentBuildGameVersions(uniqueVersions);
} catch (error) {
console.error('Unable to fetch recent build versions:', error);
} finally {
setIsLoading(false);
}
},
[getAuthorizationHeader, profile, project]
);
const isMultiplayerExtensionUsed = React.useMemo(
() =>
gd.UsedExtensionsFinder.scanProject(project)
.getUsedExtensions()
.toNewVectorString()
.toJSArray()
.some(extensionName => extensionName === 'Multiplayer'),
[project]
);
React.useEffect(
() => {
fetchRecentBuildGameVersions();
},
[fetchRecentBuildGameVersions]
);
const isCurrentValueInGameVersionsList =
!!recentBuildGameVersions &&
recentBuildGameVersions.find(
recentGameVersion => recentGameVersion === versionNumber
);
const [isFreeTextField, setIsFreeTextField] = React.useState(false);
const switchFieldType = () => {
if (isFreeTextField && !isCurrentValueInGameVersionsList) {
// If the current value is not in the list of versions when switching to the select field,
// go back to the current project version, to ensure a value is always selected.
onVersionNumberChanged(project.getVersion());
} else {
// If switching to the free text field, increment the current project version,
// as we expect the user to enter a new version.
onVersionNumberChanged(getIncrementedVersionNumber(project));
}
setIsFreeTextField(!isFreeTextField);
};
const onChangeSelectValue = (event, value) => {
onVersionNumberChanged(event.target.value);
};
const onChangeTextValue = (e, value) => {
onVersionNumberChanged(value);
};
const isDisabled = disabled || isLoading;
const selectOptions = recentBuildGameVersions.map(buildVersion => (
<SelectOption
key={buildVersion}
value={buildVersion}
label={buildVersion}
shouldNotTranslate
disabled={isDisabled}
/>
));
return (
<I18n>
{({ i18n }) => (
<div style={styles.container}>
<Column noMargin expand>
<ResponsiveLineStackLayout
justifyContent="center"
alignItems="start"
>
{!isFreeTextField ? (
<SelectField
id="game-version-select-field"
value={versionNumber}
onChange={onChangeSelectValue}
margin="dense"
floatingLabelText={<Trans>Game version</Trans>}
floatingLabelFixed
translatableHintText={t`Choose a game version`}
fullWidth
helperMarkdownText={
isMultiplayerExtensionUsed
? i18n._(
t`Bump the version if you want your players to join different lobbies.`
)
: null
}
disabled={isDisabled}
>
{selectOptions}
</SelectField>
) : (
<TextField
id="game-version-text-field"
onChange={onChangeTextValue}
floatingLabelText={<Trans>Game version</Trans>}
value={versionNumber}
floatingLabelFixed
fullWidth
helperMarkdownText={
isMultiplayerExtensionUsed
? i18n._(
t`Bump the version if you want your players to join different lobbies.`
)
: null
}
disabled={isDisabled}
/>
)}
<FlatButton
id="switch-game-version-text-select"
label={
isFreeTextField ? (
<Trans>Select version</Trans>
) : (
<Trans>Bump version</Trans>
)
}
onClick={switchFieldType}
style={styles.buttonStyle}
disabled={isDisabled}
/>
</ResponsiveLineStackLayout>
</Column>
</div>
)}
</I18n>
);
};
export default ProjectVersionSelector;

View File

@@ -44,6 +44,7 @@ export type Build = {
targets?: Array<TargetName>,
createdAt?: number, // Not defined for old builds.
updatedAt: number,
gameVersion?: string,
};
export type BuildArtifactKeyName =
@@ -222,6 +223,7 @@ export const buildElectron = (
type: 'electron-build',
targets: targets.join(','),
gameId,
gameVersion: options.gameVersion,
filename: getBuildExtensionlessFilename(options),
payWithCredits,
},
@@ -253,6 +255,7 @@ export const buildWeb = (
type: 'web-build',
targets: 's3',
gameId,
gameVersion: options.gameVersion,
payWithCredits,
},
headers: {
@@ -293,6 +296,7 @@ export const buildCordovaAndroid = (
targets: targets.join(','),
gameId,
filename: getBuildExtensionlessFilename(options),
gameVersion: options.gameVersion,
payWithCredits,
},
headers: {
@@ -332,6 +336,7 @@ export const buildCordovaIos = (
targets: targets.join(','),
gameId,
filename: getBuildExtensionlessFilename(options),
gameVersion: options.gameVersion,
payWithCredits,
},
headers: {