mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Compare commits
4 Commits
update-edi
...
fix/slug-v
Author | SHA1 | Date | |
---|---|---|---|
![]() |
e93e65e7e1 | ||
![]() |
b67e7035bc | ||
![]() |
b0f824f889 | ||
![]() |
ee7065d52c |
@@ -38,7 +38,10 @@ import {
|
|||||||
getGame,
|
getGame,
|
||||||
getGameUrl,
|
getGameUrl,
|
||||||
updateGame,
|
updateGame,
|
||||||
|
setGameSlug,
|
||||||
|
getGameSlugs,
|
||||||
type Game,
|
type Game,
|
||||||
|
type GameSlug,
|
||||||
} from '../../../Utils/GDevelopServices/Game';
|
} from '../../../Utils/GDevelopServices/Game';
|
||||||
import AuthenticatedUserContext from '../../../Profile/AuthenticatedUserContext';
|
import AuthenticatedUserContext from '../../../Profile/AuthenticatedUserContext';
|
||||||
import AlertMessage from '../../../UI/AlertMessage';
|
import AlertMessage from '../../../UI/AlertMessage';
|
||||||
@@ -78,6 +81,7 @@ const OnlineGameLink = ({
|
|||||||
setIsOnlineGamePropertiesDialogOpen,
|
setIsOnlineGamePropertiesDialogOpen,
|
||||||
] = React.useState<boolean>(false);
|
] = React.useState<boolean>(false);
|
||||||
const [game, setGame] = React.useState<?Game>(null);
|
const [game, setGame] = React.useState<?Game>(null);
|
||||||
|
const [slug, setSlug] = React.useState<?GameSlug>(null);
|
||||||
const [isGameLoading, setIsGameLoading] = React.useState<boolean>(false);
|
const [isGameLoading, setIsGameLoading] = React.useState<boolean>(false);
|
||||||
const { getAuthorizationHeader, profile } = React.useContext(
|
const { getAuthorizationHeader, profile } = React.useContext(
|
||||||
AuthenticatedUserContext
|
AuthenticatedUserContext
|
||||||
@@ -86,7 +90,7 @@ const OnlineGameLink = ({
|
|||||||
const exportPending = !errored && exportStep !== '' && exportStep !== 'done';
|
const exportPending = !errored && exportStep !== '' && exportStep !== 'done';
|
||||||
const isBuildComplete = build && build.status === 'complete';
|
const isBuildComplete = build && build.status === 'complete';
|
||||||
const isBuildPublished = build && game && build.id === game.publicWebBuildId;
|
const isBuildPublished = build && game && build.id === game.publicWebBuildId;
|
||||||
const gameUrl = getGameUrl(game);
|
const gameUrl = getGameUrl(game, slug);
|
||||||
const buildUrl =
|
const buildUrl =
|
||||||
exportPending || !isBuildComplete
|
exportPending || !isBuildComplete
|
||||||
? null
|
? null
|
||||||
@@ -102,8 +106,16 @@ const OnlineGameLink = ({
|
|||||||
const { id } = profile;
|
const { id } = profile;
|
||||||
try {
|
try {
|
||||||
setIsGameLoading(true);
|
setIsGameLoading(true);
|
||||||
const game = await getGame(getAuthorizationHeader, id, gameId);
|
const [game, slugs] = await Promise.all([
|
||||||
|
getGame(getAuthorizationHeader, id, gameId),
|
||||||
|
getGameSlugs(getAuthorizationHeader, id, gameId).catch(err => {
|
||||||
|
console.error('Unable to get the game slug', err);
|
||||||
|
}),
|
||||||
|
]);
|
||||||
setGame(game);
|
setGame(game);
|
||||||
|
if (slugs && slugs.length > 0) {
|
||||||
|
setSlug(slugs[0]);
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Unable to load the game', err);
|
console.error('Unable to load the game', err);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -189,6 +201,35 @@ const OnlineGameLink = ({
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
setGame(updatedGame);
|
setGame(updatedGame);
|
||||||
|
const { userSlug, gameSlug } = partialGameChange;
|
||||||
|
if (userSlug && gameSlug && userSlug === profile.username) {
|
||||||
|
try {
|
||||||
|
await setGameSlug(
|
||||||
|
getAuthorizationHeader,
|
||||||
|
id,
|
||||||
|
game.id,
|
||||||
|
userSlug,
|
||||||
|
gameSlug
|
||||||
|
);
|
||||||
|
setSlug({ username: userSlug, gameSlug: gameSlug, createdAt: 0 });
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
'Unable to update the game slug:',
|
||||||
|
error.response || error.message
|
||||||
|
);
|
||||||
|
showErrorBox({
|
||||||
|
message:
|
||||||
|
i18n._(
|
||||||
|
t`Unable to update the game slug. A slug must be 6 to 30 characters long and only contains letters, digits or dashes.`
|
||||||
|
) +
|
||||||
|
' ' +
|
||||||
|
i18n._(t`Verify your internet connection or try again later.`),
|
||||||
|
rawError: error,
|
||||||
|
errorId: 'game-slug-update-error',
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showErrorBox({
|
showErrorBox({
|
||||||
message: i18n._(
|
message: i18n._(
|
||||||
@@ -377,6 +418,7 @@ const OnlineGameLink = ({
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
game={game}
|
game={game}
|
||||||
|
slug={slug}
|
||||||
isLoading={isGameLoading}
|
isLoading={isGameLoading}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@@ -2,22 +2,27 @@
|
|||||||
import { t, Trans } from '@lingui/macro';
|
import { t, Trans } from '@lingui/macro';
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { type Game } from '../../../Utils/GDevelopServices/Game';
|
import { type Game, type GameSlug } from '../../../Utils/GDevelopServices/Game';
|
||||||
import FlatButton from '../../../UI/FlatButton';
|
import FlatButton from '../../../UI/FlatButton';
|
||||||
import Dialog from '../../../UI/Dialog';
|
import Dialog from '../../../UI/Dialog';
|
||||||
import PublicGameProperties from '../../../GameDashboard/PublicGameProperties';
|
import {
|
||||||
|
cleanUpGameSlug,
|
||||||
|
PublicGameProperties,
|
||||||
|
} from '../../../GameDashboard/PublicGameProperties';
|
||||||
import {
|
import {
|
||||||
applyPublicPropertiesToProject,
|
applyPublicPropertiesToProject,
|
||||||
type PartialGameChange,
|
type PartialGameChange,
|
||||||
} from '../../../GameDashboard/PublicGamePropertiesDialog';
|
} from '../../../GameDashboard/PublicGamePropertiesDialog';
|
||||||
import { getWebBuildThumbnailUrl } from '../../../Utils/GDevelopServices/Build';
|
import { getWebBuildThumbnailUrl } from '../../../Utils/GDevelopServices/Build';
|
||||||
import RaisedButtonWithSplitMenu from '../../../UI/RaisedButtonWithSplitMenu';
|
import RaisedButtonWithSplitMenu from '../../../UI/RaisedButtonWithSplitMenu';
|
||||||
|
import AuthenticatedUserContext from '../../../Profile/AuthenticatedUserContext';
|
||||||
|
|
||||||
type Props = {|
|
type Props = {|
|
||||||
project: gdProject,
|
project: gdProject,
|
||||||
onSaveProject: () => Promise<void>,
|
onSaveProject: () => Promise<void>,
|
||||||
buildId: string,
|
buildId: string,
|
||||||
game: Game,
|
game: Game,
|
||||||
|
slug: ?GameSlug,
|
||||||
onClose: () => void,
|
onClose: () => void,
|
||||||
onApply: PartialGameChange => Promise<void>,
|
onApply: PartialGameChange => Promise<void>,
|
||||||
isLoading: boolean,
|
isLoading: boolean,
|
||||||
@@ -28,10 +33,13 @@ export const OnlineGamePropertiesDialog = ({
|
|||||||
onSaveProject,
|
onSaveProject,
|
||||||
buildId,
|
buildId,
|
||||||
game,
|
game,
|
||||||
|
slug,
|
||||||
onClose,
|
onClose,
|
||||||
onApply,
|
onApply,
|
||||||
isLoading,
|
isLoading,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
|
const { profile } = React.useContext(AuthenticatedUserContext);
|
||||||
|
|
||||||
const [name, setName] = React.useState<string>(project.getName());
|
const [name, setName] = React.useState<string>(project.getName());
|
||||||
const [categories, setCategories] = React.useState<string[]>(
|
const [categories, setCategories] = React.useState<string[]>(
|
||||||
project.getCategories().toJSArray()
|
project.getCategories().toJSArray()
|
||||||
@@ -51,6 +59,12 @@ export const OnlineGamePropertiesDialog = ({
|
|||||||
const [playWithMobile, setPlayableWithMobile] = React.useState<boolean>(
|
const [playWithMobile, setPlayableWithMobile] = React.useState<boolean>(
|
||||||
project.isPlayableWithMobile()
|
project.isPlayableWithMobile()
|
||||||
);
|
);
|
||||||
|
const [userSlug, setUserSlug] = React.useState<string>(
|
||||||
|
(slug && slug.username) || (profile && profile.username) || ''
|
||||||
|
);
|
||||||
|
const [gameSlug, setGameSlug] = React.useState<string>(
|
||||||
|
(slug && slug.gameSlug) || cleanUpGameSlug(project.getName())
|
||||||
|
);
|
||||||
const [orientation, setOrientation] = React.useState<string>(
|
const [orientation, setOrientation] = React.useState<string>(
|
||||||
project.getOrientation()
|
project.getOrientation()
|
||||||
);
|
);
|
||||||
@@ -78,7 +92,7 @@ export const OnlineGamePropertiesDialog = ({
|
|||||||
orientation: orientation || 'default',
|
orientation: orientation || 'default',
|
||||||
})
|
})
|
||||||
) {
|
) {
|
||||||
await onApply({ discoverable });
|
await onApply({ discoverable, userSlug, gameSlug });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -131,6 +145,10 @@ export const OnlineGamePropertiesDialog = ({
|
|||||||
playWithMobile={playWithMobile}
|
playWithMobile={playWithMobile}
|
||||||
setOrientation={setOrientation}
|
setOrientation={setOrientation}
|
||||||
orientation={orientation}
|
orientation={orientation}
|
||||||
|
userSlug={userSlug}
|
||||||
|
setUserSlug={setUserSlug}
|
||||||
|
gameSlug={gameSlug}
|
||||||
|
setGameSlug={setGameSlug}
|
||||||
discoverable={discoverable}
|
discoverable={discoverable}
|
||||||
setDiscoverable={setDiscoverable}
|
setDiscoverable={setDiscoverable}
|
||||||
displayThumbnail
|
displayThumbnail
|
||||||
|
@@ -17,6 +17,7 @@ import AuthenticatedUserContext from '../Profile/AuthenticatedUserContext';
|
|||||||
import { I18n } from '@lingui/react';
|
import { I18n } from '@lingui/react';
|
||||||
import { Column, Line, Spacer } from '../UI/Grid';
|
import { Column, Line, Spacer } from '../UI/Grid';
|
||||||
import BackgroundText from '../UI/BackgroundText';
|
import BackgroundText from '../UI/BackgroundText';
|
||||||
|
import AlertMessage from '../UI/AlertMessage';
|
||||||
import { GameThumbnail } from './GameThumbnail';
|
import { GameThumbnail } from './GameThumbnail';
|
||||||
|
|
||||||
const isCyrillic = (text: string) =>
|
const isCyrillic = (text: string) =>
|
||||||
@@ -195,42 +196,68 @@ export function PublicGameProperties({
|
|||||||
rows={5}
|
rows={5}
|
||||||
/>
|
/>
|
||||||
{setUserSlug && setGameSlug && (
|
{setUserSlug && setGameSlug && (
|
||||||
<Line>
|
<>
|
||||||
<SelectField
|
<Line>
|
||||||
fullWidth
|
<SelectField
|
||||||
floatingLabelText={<Trans>User name in the game URL</Trans>}
|
fullWidth
|
||||||
value={userSlug || ''}
|
floatingLabelText={<Trans>User name in the game URL</Trans>}
|
||||||
onChange={(e, i, value: string) => setUserSlug(value)}
|
value={userSlug || ''}
|
||||||
// It's disabled if one of the condition of SelectOption is false.
|
onChange={(e, i, value: string) => setUserSlug(value)}
|
||||||
disabled={
|
// It's disabled if one of the condition of SelectOption is false.
|
||||||
!(
|
disabled={
|
||||||
profile &&
|
!(
|
||||||
profile.username &&
|
profile &&
|
||||||
userSlug &&
|
profile.username &&
|
||||||
(!profile || userSlug !== profile.username)
|
userSlug &&
|
||||||
)
|
(!profile || userSlug !== profile.username)
|
||||||
}
|
)
|
||||||
>
|
}
|
||||||
{profile && profile.username && (
|
>
|
||||||
<SelectOption
|
{profile && profile.username && (
|
||||||
value={profile.username}
|
<SelectOption
|
||||||
primaryText={profile.username}
|
value={profile.username}
|
||||||
/>
|
primaryText={profile.username}
|
||||||
)}
|
/>
|
||||||
{userSlug && (!profile || userSlug !== profile.username) && (
|
)}
|
||||||
<SelectOption value={userSlug} primaryText={userSlug} />
|
{userSlug && (!profile || userSlug !== profile.username) && (
|
||||||
)}
|
<SelectOption value={userSlug} primaryText={userSlug} />
|
||||||
</SelectField>
|
)}
|
||||||
<Spacer />
|
</SelectField>
|
||||||
<SemiControlledTextField
|
<Spacer />
|
||||||
floatingLabelText={<Trans>Game name in the game URL</Trans>}
|
<SemiControlledTextField
|
||||||
fullWidth
|
disabled={
|
||||||
type="text"
|
!(
|
||||||
value={gameSlug || ''}
|
userSlug &&
|
||||||
onChange={gameSlug => setGameSlug(cleanUpGameSlug(gameSlug))}
|
userSlug.length &&
|
||||||
autoFocus
|
profile &&
|
||||||
/>
|
profile.username
|
||||||
</Line>
|
)
|
||||||
|
}
|
||||||
|
floatingLabelText={<Trans>Game name in the game URL</Trans>}
|
||||||
|
fullWidth
|
||||||
|
type="text"
|
||||||
|
value={
|
||||||
|
userSlug && userSlug.length && profile && profile.username
|
||||||
|
? gameSlug || ''
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
onChange={gameSlug => setGameSlug(cleanUpGameSlug(gameSlug))}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
</Line>
|
||||||
|
{!(
|
||||||
|
userSlug &&
|
||||||
|
userSlug.length &&
|
||||||
|
profile &&
|
||||||
|
profile.username
|
||||||
|
) && (
|
||||||
|
<AlertMessage kind="info">
|
||||||
|
<Trans>
|
||||||
|
Usernames are required to choose a custom game URL.
|
||||||
|
</Trans>
|
||||||
|
</AlertMessage>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
<UsersAutocomplete
|
<UsersAutocomplete
|
||||||
userIds={authorIds}
|
userIds={authorIds}
|
||||||
|
@@ -16,6 +16,10 @@ export const GDevelopGamesPlatform = {
|
|||||||
isDev
|
isDev
|
||||||
? `https://liluo.io/games/${gameId}?dev=true`
|
? `https://liluo.io/games/${gameId}?dev=true`
|
||||||
: `https://liluo.io/games/${gameId}`,
|
: `https://liluo.io/games/${gameId}`,
|
||||||
|
getGameUrlWithSlug: (userSlug: string, gameSlug: string) =>
|
||||||
|
isDev
|
||||||
|
? `https://liluo.io/${userSlug.toLowerCase()}/${gameSlug.toLowerCase()}?dev=true`
|
||||||
|
: `https://liluo.io/${userSlug.toLowerCase()}/${gameSlug.toLowerCase()}`,
|
||||||
getUserPublicProfileUrl: (userId: string, username: ?string) =>
|
getUserPublicProfileUrl: (userId: string, username: ?string) =>
|
||||||
username
|
username
|
||||||
? `https://liluo.io/${username}${isDev ? '?dev=true' : ''}`
|
? `https://liluo.io/${username}${isDev ? '?dev=true' : ''}`
|
||||||
|
@@ -38,6 +38,12 @@ export type Game = {
|
|||||||
discoverable?: boolean,
|
discoverable?: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type GameSlug = {
|
||||||
|
username: string,
|
||||||
|
gameSlug: string,
|
||||||
|
createdAt: number,
|
||||||
|
};
|
||||||
|
|
||||||
export type ShowcasedGameLink = {
|
export type ShowcasedGameLink = {
|
||||||
url: string,
|
url: string,
|
||||||
type:
|
type:
|
||||||
@@ -129,9 +135,11 @@ export const getCategoryName = (category: string, i18n: I18nType) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getGameUrl = (game: ?Game) => {
|
export const getGameUrl = (game: ?Game, slug: ?GameSlug) => {
|
||||||
if (!game) return null;
|
if (!game) return null;
|
||||||
return GDevelopGamesPlatform.getGameUrl(game.id);
|
return slug
|
||||||
|
? GDevelopGamesPlatform.getGameUrlWithSlug(slug.username, slug.gameSlug)
|
||||||
|
: GDevelopGamesPlatform.getGameUrl(game.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getAclsFromUserIds = (
|
export const getAclsFromUserIds = (
|
||||||
@@ -374,3 +382,23 @@ export const getPublicGame = (gameId: string): Promise<PublicGame> => {
|
|||||||
.get(`${GDevelopGameApi.baseUrl}/public-game/${gameId}`)
|
.get(`${GDevelopGameApi.baseUrl}/public-game/${gameId}`)
|
||||||
.then(response => response.data);
|
.then(response => response.data);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getGameSlugs = (
|
||||||
|
getAuthorizationHeader: () => Promise<string>,
|
||||||
|
userId: string,
|
||||||
|
gameId: string
|
||||||
|
): Promise<Array<GameSlug>> => {
|
||||||
|
return getAuthorizationHeader()
|
||||||
|
.then(authorizationHeader =>
|
||||||
|
axios.get(`${GDevelopGameApi.baseUrl}/game-slug`, {
|
||||||
|
params: {
|
||||||
|
userId,
|
||||||
|
gameId,
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
Authorization: authorizationHeader,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.then(response => response.data);
|
||||||
|
};
|
||||||
|
Reference in New Issue
Block a user