mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Allow defining up to 4 genres for a game
This commit is contained in:
@@ -536,17 +536,23 @@ void Project::UnserializeFrom(const SerializerElement& element) {
|
||||
authorIds.push_back(authorIdsElement.GetChild(i).GetStringValue());
|
||||
}
|
||||
|
||||
categories.clear();
|
||||
auto& categoriesElement = propElement.GetChild("categories");
|
||||
categoriesElement.ConsiderAsArray();
|
||||
for (std::size_t i = 0; i < categoriesElement.GetChildrenCount(); ++i) {
|
||||
categories.push_back(categoriesElement.GetChild(i).GetStringValue());
|
||||
}
|
||||
|
||||
auto& playableDevicesElement = propElement.GetChild("playableDevices");
|
||||
playableDevicesElement.ConsiderAsArray();
|
||||
for (std::size_t i = 0; i < playableDevicesElement.GetChildrenCount(); ++i) {
|
||||
const auto& playableDevice = playableDevicesElement.GetChild(i).GetStringValue();
|
||||
const auto& playableDevice =
|
||||
playableDevicesElement.GetChild(i).GetStringValue();
|
||||
if (playableDevice == "keyboard") {
|
||||
isPlayableWithKeyboard = true;
|
||||
}
|
||||
else if (playableDevice == "gamepad") {
|
||||
} else if (playableDevice == "gamepad") {
|
||||
isPlayableWithGamepad = true;
|
||||
}
|
||||
else if (playableDevice == "mobile") {
|
||||
} else if (playableDevice == "mobile") {
|
||||
isPlayableWithMobile = true;
|
||||
}
|
||||
}
|
||||
@@ -751,6 +757,12 @@ void Project::SerializeTo(SerializerElement& element) const {
|
||||
authorIdsElement.AddChild("").SetStringValue(authorId);
|
||||
}
|
||||
|
||||
auto& categoriesElement = propElement.AddChild("categories");
|
||||
categoriesElement.ConsiderAsArray();
|
||||
for (const auto& category : categories) {
|
||||
categoriesElement.AddChild("").SetStringValue(category);
|
||||
}
|
||||
|
||||
auto& playableDevicesElement = propElement.AddChild("playableDevices");
|
||||
playableDevicesElement.ConsiderAsArray();
|
||||
if (isPlayableWithKeyboard) {
|
||||
@@ -938,6 +950,8 @@ Project& Project::operator=(const Project& other) {
|
||||
|
||||
void Project::Init(const gd::Project& game) {
|
||||
name = game.name;
|
||||
categories = game.categories;
|
||||
description = game.description;
|
||||
firstLayout = game.firstLayout;
|
||||
version = game.version;
|
||||
windowWidth = game.windowWidth;
|
||||
|
@@ -65,6 +65,16 @@ class GD_CORE_API Project : public ObjectsContainer {
|
||||
*/
|
||||
const gd::String& GetName() const { return name; }
|
||||
|
||||
/**
|
||||
* \brief Get the categories/genres of the project.
|
||||
*/
|
||||
const std::vector<gd::String>& GetCategories() const { return categories; };
|
||||
|
||||
/**
|
||||
* \brief Get the categories of the project, to modify them (non-const).
|
||||
*/
|
||||
std::vector<gd::String>& GetCategories() { return categories; };
|
||||
|
||||
/**
|
||||
* \brief Change the project description
|
||||
*/
|
||||
@@ -967,6 +977,8 @@ class GD_CORE_API Project : public ObjectsContainer {
|
||||
gd::String author; ///< Game author name, for publishing purpose.
|
||||
std::vector<gd::String>
|
||||
authorIds; ///< Game author ids, from GDevelop users DB.
|
||||
std::vector<gd::String>
|
||||
categories; ///< Game categories
|
||||
bool isPlayableWithKeyboard; ///< The project is playable with a keyboard.
|
||||
bool isPlayableWithGamepad; ///< The project is playable with a gamepad.
|
||||
bool isPlayableWithMobile; ///< The project is playable on a mobile.
|
||||
|
@@ -364,6 +364,7 @@ interface Project {
|
||||
|
||||
void SetName([Const] DOMString name);
|
||||
[Const, Ref] DOMString GetName();
|
||||
[Ref] VectorString GetCategories();
|
||||
void SetDescription([Const] DOMString description);
|
||||
[Const, Ref] DOMString GetDescription();
|
||||
void SetVersion([Const] DOMString authorName);
|
||||
|
@@ -3,6 +3,7 @@ declare class gdProject extends gdObjectsContainer {
|
||||
constructor(): void;
|
||||
setName(name: string): void;
|
||||
getName(): string;
|
||||
getCategories(): gdVectorString;
|
||||
setDescription(description: string): void;
|
||||
getDescription(): string;
|
||||
setVersion(authorName: string): void;
|
||||
|
@@ -2,7 +2,7 @@
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { t } from '@lingui/macro';
|
||||
import * as React from 'react';
|
||||
import { format, formatISO } from 'date-fns';
|
||||
import { formatISO } from 'date-fns';
|
||||
import FlatButton from '../UI/FlatButton';
|
||||
import { Line, Spacer } from '../UI/Grid';
|
||||
import {
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
getPublicGame,
|
||||
setGameUserAcls,
|
||||
getAclsFromAuthorIds,
|
||||
getCategoryName,
|
||||
} from '../Utils/GDevelopServices/Game';
|
||||
import Dialog from '../UI/Dialog';
|
||||
import { Tab, Tabs } from '../UI/Tabs';
|
||||
@@ -40,6 +41,7 @@ import TextField from '../UI/TextField';
|
||||
import KeyboardIcon from '@material-ui/icons/Keyboard';
|
||||
import SportsEsportsIcon from '@material-ui/icons/SportsEsports';
|
||||
import SmartphoneIcon from '@material-ui/icons/Smartphone';
|
||||
import { I18n } from '@lingui/react';
|
||||
|
||||
const styles = {
|
||||
tableRowStatColumn: {
|
||||
@@ -155,6 +157,7 @@ export const GameDetailsDialog = ({
|
||||
const updatedGame = await updateGame(getAuthorizationHeader, id, gameId, {
|
||||
authorName: project.getAuthor() || 'Unspecified publisher',
|
||||
gameName: project.getName() || 'Untitle game',
|
||||
categories: project.getCategories().toJSArray() || [],
|
||||
description: project.getDescription() || '',
|
||||
playWithKeyboard: project.isPlayableWithKeyboard(),
|
||||
playWithGamepad: project.isPlayableWithGamepad(),
|
||||
@@ -216,352 +219,404 @@ export const GameDetailsDialog = ({
|
||||
!!project && project.getProjectUuid() === game.id;
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
title={
|
||||
<span>
|
||||
{game.gameName}
|
||||
{' - '}
|
||||
<Trans>Dashboard</Trans>
|
||||
</span>
|
||||
}
|
||||
open
|
||||
noMargin
|
||||
onRequestClose={onClose}
|
||||
maxWidth="md"
|
||||
actions={[
|
||||
<FlatButton
|
||||
label={<Trans>Close</Trans>}
|
||||
onClick={onClose}
|
||||
key="close"
|
||||
/>,
|
||||
]}
|
||||
secondaryActions={[
|
||||
<HelpButton key="help" helpPagePath="/interface/games-dashboard" />,
|
||||
]}
|
||||
>
|
||||
<Tabs value={currentTab} onChange={setCurrentTab}>
|
||||
<Tab label={<Trans>Details</Trans>} value="details" />
|
||||
<Tab label={<Trans>Builds</Trans>} value="builds" />
|
||||
<Tab label={<Trans>Analytics</Trans>} value="analytics" />
|
||||
</Tabs>
|
||||
<Line>
|
||||
{currentTab === 'details' ? (
|
||||
publicGameError ? (
|
||||
<PlaceholderError onRetry={loadPublicGame}>
|
||||
<Trans>There was an issue getting the game details.</Trans>{' '}
|
||||
<Trans>Verify your internet connection or try again later.</Trans>
|
||||
</PlaceholderError>
|
||||
) : !publicGame ? (
|
||||
<PlaceholderLoader />
|
||||
) : (
|
||||
<ColumnStackLayout expand>
|
||||
{!isGameOpenedAsProject && (
|
||||
<AlertMessage kind="info">
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<Dialog
|
||||
title={
|
||||
<span>
|
||||
{game.gameName}
|
||||
{' - '}
|
||||
<Trans>Dashboard</Trans>
|
||||
</span>
|
||||
}
|
||||
open
|
||||
noMargin
|
||||
onRequestClose={onClose}
|
||||
maxWidth="md"
|
||||
actions={[
|
||||
<FlatButton
|
||||
label={<Trans>Close</Trans>}
|
||||
onClick={onClose}
|
||||
key="close"
|
||||
/>,
|
||||
]}
|
||||
secondaryActions={[
|
||||
<HelpButton key="help" helpPagePath="/interface/games-dashboard" />,
|
||||
]}
|
||||
>
|
||||
<Tabs value={currentTab} onChange={setCurrentTab}>
|
||||
<Tab label={<Trans>Details</Trans>} value="details" />
|
||||
<Tab label={<Trans>Builds</Trans>} value="builds" />
|
||||
<Tab label={<Trans>Analytics</Trans>} value="analytics" />
|
||||
</Tabs>
|
||||
<Line>
|
||||
{currentTab === 'details' ? (
|
||||
publicGameError ? (
|
||||
<PlaceholderError onRetry={loadPublicGame}>
|
||||
<Trans>There was an issue getting the game details.</Trans>{' '}
|
||||
<Trans>
|
||||
In order to update these details you have to open the game's
|
||||
project.
|
||||
Verify your internet connection or try again later.
|
||||
</Trans>
|
||||
</AlertMessage>
|
||||
)}
|
||||
<Line alignItems="center">
|
||||
<Line expand justifyContent="flex-start" alignItems="center">
|
||||
{authorUsernames && (
|
||||
<>
|
||||
<Text>
|
||||
<Trans>Authors:</Trans>
|
||||
</Text>
|
||||
<Line>
|
||||
{authorUsernames.map((username, index) => (
|
||||
<React.Fragment key={username}>
|
||||
<Spacer />
|
||||
<Chip
|
||||
size="small"
|
||||
label={username}
|
||||
color={index === 0 ? 'primary' : 'default'}
|
||||
/>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</Line>
|
||||
</>
|
||||
</PlaceholderError>
|
||||
) : !publicGame ? (
|
||||
<PlaceholderLoader />
|
||||
) : (
|
||||
<ColumnStackLayout expand>
|
||||
{!isGameOpenedAsProject && (
|
||||
<AlertMessage kind="info">
|
||||
<Trans>
|
||||
In order to update these details you have to open the
|
||||
game's project.
|
||||
</Trans>
|
||||
</AlertMessage>
|
||||
)}
|
||||
</Line>
|
||||
<Line expand justifyContent="flex-end">
|
||||
<Text>
|
||||
<Trans>
|
||||
Created on{' '}
|
||||
{format(game.createdAt * 1000 /* TODO */, 'yyyy-MM-dd')}
|
||||
</Trans>
|
||||
</Text>
|
||||
</Line>
|
||||
</Line>
|
||||
{(publicGame.playWithKeyboard ||
|
||||
publicGame.playWithGamepad ||
|
||||
publicGame.playWithMobile) && (
|
||||
<Line expand justifyContent="flex-start" alignItems="center">
|
||||
{publicGame.playWithKeyboard && <KeyboardIcon />}
|
||||
{publicGame.playWithGamepad && <SportsEsportsIcon />}
|
||||
{publicGame.playWithMobile && <SmartphoneIcon />}
|
||||
</Line>
|
||||
)}
|
||||
<TextField
|
||||
value={publicGame.gameName}
|
||||
readOnly
|
||||
fullWidth
|
||||
floatingLabelText={<Trans>Game name</Trans>}
|
||||
floatingLabelFixed={true}
|
||||
/>
|
||||
<TextField
|
||||
value={publicGame.description || ''}
|
||||
readOnly
|
||||
fullWidth
|
||||
floatingLabelText={<Trans>Game description</Trans>}
|
||||
floatingLabelFixed={true}
|
||||
hintText={t`No description set.`}
|
||||
multiline
|
||||
rows={5}
|
||||
/>
|
||||
<SelectField
|
||||
disabled
|
||||
fullWidth
|
||||
floatingLabelText={
|
||||
<Trans>Device orientation (for mobile)</Trans>
|
||||
}
|
||||
value={publicGame.orientation}
|
||||
>
|
||||
<SelectOption
|
||||
value="default"
|
||||
primaryText={t`Platform default`}
|
||||
/>
|
||||
<SelectOption value="landscape" primaryText={t`Landscape`} />
|
||||
<SelectOption value="portrait" primaryText={t`Portrait`} />
|
||||
</SelectField>
|
||||
<Line noMargin justifyContent="flex-end">
|
||||
<FlatButton
|
||||
onClick={() => {
|
||||
const answer = Window.showConfirmDialog(
|
||||
"Are you sure you want to unregister this game? \n\nIt will disappear from your games dashboard and you won't get access to analytics, unless you register it again."
|
||||
);
|
||||
|
||||
if (!answer) return;
|
||||
|
||||
unregisterGame();
|
||||
}}
|
||||
label={<Trans>Unregister this game</Trans>}
|
||||
/>
|
||||
<Spacer />
|
||||
{publicGame.publicWebBuildId && (
|
||||
<>
|
||||
<RaisedButton
|
||||
<Line alignItems="center" noMargin>
|
||||
<Line
|
||||
expand
|
||||
justifyContent="flex-start"
|
||||
alignItems="center"
|
||||
noMargin
|
||||
>
|
||||
{authorUsernames && (
|
||||
<>
|
||||
<Text>
|
||||
<Trans>Authors:</Trans>
|
||||
</Text>
|
||||
<Line noMargin>
|
||||
{authorUsernames.map((username, index) => (
|
||||
<React.Fragment key={username}>
|
||||
<Spacer />
|
||||
<Chip
|
||||
size="small"
|
||||
label={username}
|
||||
color={index === 0 ? 'primary' : 'default'}
|
||||
/>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</Line>
|
||||
</>
|
||||
)}
|
||||
</Line>
|
||||
<Line expand justifyContent="flex-end" noMargin>
|
||||
<Text>
|
||||
<Trans>
|
||||
Created on {i18n.date(game.createdAt * 1000)}
|
||||
</Trans>
|
||||
</Text>
|
||||
</Line>
|
||||
</Line>
|
||||
{(publicGame.playWithKeyboard ||
|
||||
publicGame.playWithGamepad ||
|
||||
publicGame.playWithMobile ||
|
||||
publicGame.categories) && (
|
||||
<Line alignItems="center" noMargin>
|
||||
<Line
|
||||
expand
|
||||
justifyContent="flex-start"
|
||||
alignItems="center"
|
||||
noMargin
|
||||
>
|
||||
{publicGame.categories &&
|
||||
!!publicGame.categories.length && (
|
||||
<>
|
||||
<Text>
|
||||
<Trans>Genres:</Trans>
|
||||
</Text>
|
||||
<Line noMargin>
|
||||
{publicGame.categories.map(
|
||||
(category, index) => (
|
||||
<React.Fragment key={category}>
|
||||
<Spacer />
|
||||
<Chip
|
||||
size="small"
|
||||
label={getCategoryName(category, i18n)}
|
||||
color={
|
||||
index === 0 ? 'primary' : 'default'
|
||||
}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)
|
||||
)}
|
||||
</Line>
|
||||
</>
|
||||
)}
|
||||
</Line>
|
||||
<Line expand justifyContent="flex-end" noMargin>
|
||||
{publicGame.playWithKeyboard && <KeyboardIcon />}
|
||||
{publicGame.playWithGamepad && <SportsEsportsIcon />}
|
||||
{publicGame.playWithMobile && <SmartphoneIcon />}
|
||||
</Line>
|
||||
</Line>
|
||||
)}
|
||||
<TextField
|
||||
value={publicGame.gameName}
|
||||
readOnly
|
||||
fullWidth
|
||||
floatingLabelText={<Trans>Game name</Trans>}
|
||||
floatingLabelFixed={true}
|
||||
/>
|
||||
<TextField
|
||||
value={publicGame.description || ''}
|
||||
readOnly
|
||||
fullWidth
|
||||
floatingLabelText={<Trans>Game description</Trans>}
|
||||
floatingLabelFixed={true}
|
||||
hintText={t`No description set.`}
|
||||
multiline
|
||||
rows={5}
|
||||
/>
|
||||
<SelectField
|
||||
disabled
|
||||
fullWidth
|
||||
floatingLabelText={
|
||||
<Trans>Device orientation (for mobile)</Trans>
|
||||
}
|
||||
value={publicGame.orientation}
|
||||
>
|
||||
<SelectOption
|
||||
value="default"
|
||||
primaryText={t`Platform default`}
|
||||
/>
|
||||
<SelectOption
|
||||
value="landscape"
|
||||
primaryText={t`Landscape`}
|
||||
/>
|
||||
<SelectOption value="portrait" primaryText={t`Portrait`} />
|
||||
</SelectField>
|
||||
<Line noMargin justifyContent="flex-end">
|
||||
<FlatButton
|
||||
onClick={() => {
|
||||
const answer = Window.showConfirmDialog(
|
||||
'Are you sure you want to unpublish this game? \n\nThis will make your Liluo unique game URL not accessible anymore. \n\nYou can decide anytime to publish it again.'
|
||||
"Are you sure you want to unregister this game? \n\nIt will disappear from your games dashboard and you won't get access to analytics, unless you register it again."
|
||||
);
|
||||
|
||||
if (!answer) return;
|
||||
|
||||
unpublishGame();
|
||||
unregisterGame();
|
||||
}}
|
||||
label={<Trans>Unpublish from Liluo</Trans>}
|
||||
label={<Trans>Unregister this game</Trans>}
|
||||
/>
|
||||
<Spacer />
|
||||
</>
|
||||
)}
|
||||
<RaisedButton
|
||||
primary
|
||||
onClick={() => setIsPublicGamePropertiesDialogOpen(true)}
|
||||
label={<Trans>Edit game details</Trans>}
|
||||
disabled={!isGameOpenedAsProject}
|
||||
/>
|
||||
</Line>
|
||||
</ColumnStackLayout>
|
||||
)
|
||||
) : null}
|
||||
{currentTab === 'builds' ? (
|
||||
<Builds
|
||||
game={game}
|
||||
authenticatedUser={authenticatedUser}
|
||||
onGameUpdated={onGameUpdated}
|
||||
/>
|
||||
) : null}
|
||||
{currentTab === 'analytics' ? (
|
||||
gameRollingMetricsError ? (
|
||||
<PlaceholderError
|
||||
onRetry={() => {
|
||||
loadGameMetrics();
|
||||
}}
|
||||
>
|
||||
<Trans>There was an issue getting the game analytics.</Trans>{' '}
|
||||
<Trans>Verify your internet connection or try again later.</Trans>
|
||||
</PlaceholderError>
|
||||
) : (
|
||||
<ColumnStackLayout expand>
|
||||
<Line noMargin alignItems="center">
|
||||
<Text size="title">
|
||||
<Trans>Consolidated metrics</Trans>
|
||||
</Text>
|
||||
<Spacer />
|
||||
{!publicGame && <CircularProgress size={20} />}
|
||||
</Line>
|
||||
<Table>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableRowColumn>
|
||||
<Trans>Last week sessions count</Trans>
|
||||
</TableRowColumn>
|
||||
<TableRowColumn style={styles.tableRowStatColumn}>
|
||||
{publicGame &&
|
||||
publicGame.metrics &&
|
||||
publicGame.metrics.lastWeekSessionsCount
|
||||
? publicGame.metrics.lastWeekSessionsCount
|
||||
: '-'}
|
||||
</TableRowColumn>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableRowColumn>
|
||||
<Trans>Last year sessions count</Trans>
|
||||
</TableRowColumn>
|
||||
<TableRowColumn style={styles.tableRowStatColumn}>
|
||||
{publicGame &&
|
||||
publicGame.metrics &&
|
||||
publicGame.metrics.lastYearSessionsCount
|
||||
? publicGame.metrics.lastYearSessionsCount
|
||||
: '-'}
|
||||
</TableRowColumn>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
<Line noMargin alignItems="center">
|
||||
<Text size="title">
|
||||
<Trans>Daily metrics</Trans>
|
||||
</Text>
|
||||
<Spacer />
|
||||
{isGameMetricsLoading && <CircularProgress size={20} />}
|
||||
</Line>
|
||||
<Line noMargin>
|
||||
<SelectField
|
||||
fullWidth
|
||||
floatingLabelText={<Trans>Day</Trans>}
|
||||
value={analyticsDate}
|
||||
onChange={(_, _index, newIsoDate) => {
|
||||
setAnalyticsDate(newIsoDate);
|
||||
{publicGame.publicWebBuildId && (
|
||||
<>
|
||||
<RaisedButton
|
||||
onClick={() => {
|
||||
const answer = Window.showConfirmDialog(
|
||||
'Are you sure you want to unpublish this game? \n\nThis will make your Liluo unique game URL not accessible anymore. \n\nYou can decide anytime to publish it again.'
|
||||
);
|
||||
|
||||
if (!answer) return;
|
||||
|
||||
unpublishGame();
|
||||
}}
|
||||
label={<Trans>Unpublish from Liluo</Trans>}
|
||||
/>
|
||||
<Spacer />
|
||||
</>
|
||||
)}
|
||||
<RaisedButton
|
||||
primary
|
||||
onClick={() => setIsPublicGamePropertiesDialogOpen(true)}
|
||||
label={<Trans>Edit game details</Trans>}
|
||||
disabled={!isGameOpenedAsProject}
|
||||
/>
|
||||
</Line>
|
||||
</ColumnStackLayout>
|
||||
)
|
||||
) : null}
|
||||
{currentTab === 'builds' ? (
|
||||
<Builds
|
||||
game={game}
|
||||
authenticatedUser={authenticatedUser}
|
||||
onGameUpdated={onGameUpdated}
|
||||
/>
|
||||
) : null}
|
||||
{currentTab === 'analytics' ? (
|
||||
gameRollingMetricsError ? (
|
||||
<PlaceholderError
|
||||
onRetry={() => {
|
||||
loadGameMetrics();
|
||||
}}
|
||||
>
|
||||
{Array(5)
|
||||
.fill('')
|
||||
.map((_, index) => {
|
||||
const isoDate = formatISO(
|
||||
subDays(new Date(), index + 2),
|
||||
{
|
||||
representation: 'date',
|
||||
}
|
||||
);
|
||||
return (
|
||||
<SelectOption
|
||||
key={isoDate}
|
||||
value={isoDate}
|
||||
primaryText={isoDate}
|
||||
/>
|
||||
);
|
||||
})
|
||||
.reverse()}
|
||||
<SelectOption
|
||||
value={yesterdayIsoDate}
|
||||
primaryText={t`Yesterday`}
|
||||
/>
|
||||
<SelectOption
|
||||
value={formatISO(new Date(), { representation: 'date' })}
|
||||
primaryText={t`Today (so far, in real time)`}
|
||||
/>
|
||||
</SelectField>
|
||||
</Line>
|
||||
{!isGameMetricsLoading && !gameRollingMetrics ? (
|
||||
<AlertMessage kind="warning">
|
||||
<Trans>There was an issue getting the game analytics.</Trans>{' '}
|
||||
<Trans>
|
||||
There were no players or stored metrics for this day. Be
|
||||
sure to publish your game and get players to try it to see
|
||||
the collected anonymous analytics.
|
||||
Verify your internet connection or try again later.
|
||||
</Trans>
|
||||
</AlertMessage>
|
||||
) : null}
|
||||
<Table>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableRowColumn>
|
||||
<Trans>Players count</Trans>
|
||||
</TableRowColumn>
|
||||
<TableRowColumn style={styles.tableRowStatColumn}>
|
||||
{gameRollingMetrics && gameRollingMetrics.players
|
||||
? gameRollingMetrics.players.d0Players
|
||||
: '-'}
|
||||
</TableRowColumn>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableRowColumn>
|
||||
<Trans>Sessions count</Trans>
|
||||
</TableRowColumn>
|
||||
<TableRowColumn style={styles.tableRowStatColumn}>
|
||||
{gameRollingMetrics && gameRollingMetrics.sessions
|
||||
? gameRollingMetrics.sessions.d0Sessions
|
||||
: '-'}
|
||||
</TableRowColumn>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableRowColumn>
|
||||
<Trans>New players count</Trans>
|
||||
</TableRowColumn>
|
||||
<TableRowColumn style={styles.tableRowStatColumn}>
|
||||
{gameRollingMetrics && gameRollingMetrics.players
|
||||
? gameRollingMetrics.players.d0NewPlayers
|
||||
: '-'}
|
||||
</TableRowColumn>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
{gameRollingMetrics &&
|
||||
(!gameRollingMetrics.retention || !gameRollingMetrics.players) ? (
|
||||
<AlertMessage kind="info">
|
||||
Upgrade your account with a subscription to unlock all the
|
||||
metrics for your game.
|
||||
</AlertMessage>
|
||||
) : null}
|
||||
<Table>
|
||||
<TableBody>
|
||||
{[1, 2, 3, 4, 5, 6, 7].map(dayIndex => (
|
||||
<TableRow key={dayIndex}>
|
||||
<TableRowColumn>
|
||||
<Trans>Day {dayIndex} retained players</Trans>
|
||||
</TableRowColumn>
|
||||
<TableRowColumn style={styles.tableRowStatColumn}>
|
||||
{gameRollingMetrics &&
|
||||
gameRollingMetrics.retention &&
|
||||
gameRollingMetrics.retention[
|
||||
`d${dayIndex}RetainedPlayers`
|
||||
] != null
|
||||
? gameRollingMetrics.retention[
|
||||
</PlaceholderError>
|
||||
) : (
|
||||
<ColumnStackLayout expand>
|
||||
<Line noMargin alignItems="center">
|
||||
<Text size="title">
|
||||
<Trans>Consolidated metrics</Trans>
|
||||
</Text>
|
||||
<Spacer />
|
||||
{!publicGame && <CircularProgress size={20} />}
|
||||
</Line>
|
||||
<Table>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableRowColumn>
|
||||
<Trans>Last week sessions count</Trans>
|
||||
</TableRowColumn>
|
||||
<TableRowColumn style={styles.tableRowStatColumn}>
|
||||
{publicGame &&
|
||||
publicGame.metrics &&
|
||||
publicGame.metrics.lastWeekSessionsCount
|
||||
? publicGame.metrics.lastWeekSessionsCount
|
||||
: '-'}
|
||||
</TableRowColumn>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableRowColumn>
|
||||
<Trans>Last year sessions count</Trans>
|
||||
</TableRowColumn>
|
||||
<TableRowColumn style={styles.tableRowStatColumn}>
|
||||
{publicGame &&
|
||||
publicGame.metrics &&
|
||||
publicGame.metrics.lastYearSessionsCount
|
||||
? publicGame.metrics.lastYearSessionsCount
|
||||
: '-'}
|
||||
</TableRowColumn>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
<Line noMargin alignItems="center">
|
||||
<Text size="title">
|
||||
<Trans>Daily metrics</Trans>
|
||||
</Text>
|
||||
<Spacer />
|
||||
{isGameMetricsLoading && <CircularProgress size={20} />}
|
||||
</Line>
|
||||
<Line noMargin>
|
||||
<SelectField
|
||||
fullWidth
|
||||
floatingLabelText={<Trans>Day</Trans>}
|
||||
value={analyticsDate}
|
||||
onChange={(_, _index, newIsoDate) => {
|
||||
setAnalyticsDate(newIsoDate);
|
||||
}}
|
||||
>
|
||||
{Array(5)
|
||||
.fill('')
|
||||
.map((_, index) => {
|
||||
const isoDate = formatISO(
|
||||
subDays(new Date(), index + 2),
|
||||
{
|
||||
representation: 'date',
|
||||
}
|
||||
);
|
||||
return (
|
||||
<SelectOption
|
||||
key={isoDate}
|
||||
value={isoDate}
|
||||
primaryText={isoDate}
|
||||
/>
|
||||
);
|
||||
})
|
||||
.reverse()}
|
||||
<SelectOption
|
||||
value={yesterdayIsoDate}
|
||||
primaryText={t`Yesterday`}
|
||||
/>
|
||||
<SelectOption
|
||||
value={formatISO(new Date(), {
|
||||
representation: 'date',
|
||||
})}
|
||||
primaryText={t`Today (so far, in real time)`}
|
||||
/>
|
||||
</SelectField>
|
||||
</Line>
|
||||
{!isGameMetricsLoading && !gameRollingMetrics ? (
|
||||
<AlertMessage kind="warning">
|
||||
<Trans>
|
||||
There were no players or stored metrics for this day. Be
|
||||
sure to publish your game and get players to try it to
|
||||
see the collected anonymous analytics.
|
||||
</Trans>
|
||||
</AlertMessage>
|
||||
) : null}
|
||||
<Table>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableRowColumn>
|
||||
<Trans>Players count</Trans>
|
||||
</TableRowColumn>
|
||||
<TableRowColumn style={styles.tableRowStatColumn}>
|
||||
{gameRollingMetrics && gameRollingMetrics.players
|
||||
? gameRollingMetrics.players.d0Players
|
||||
: '-'}
|
||||
</TableRowColumn>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableRowColumn>
|
||||
<Trans>Sessions count</Trans>
|
||||
</TableRowColumn>
|
||||
<TableRowColumn style={styles.tableRowStatColumn}>
|
||||
{gameRollingMetrics && gameRollingMetrics.sessions
|
||||
? gameRollingMetrics.sessions.d0Sessions
|
||||
: '-'}
|
||||
</TableRowColumn>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableRowColumn>
|
||||
<Trans>New players count</Trans>
|
||||
</TableRowColumn>
|
||||
<TableRowColumn style={styles.tableRowStatColumn}>
|
||||
{gameRollingMetrics && gameRollingMetrics.players
|
||||
? gameRollingMetrics.players.d0NewPlayers
|
||||
: '-'}
|
||||
</TableRowColumn>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
{gameRollingMetrics &&
|
||||
(!gameRollingMetrics.retention ||
|
||||
!gameRollingMetrics.players) ? (
|
||||
<AlertMessage kind="info">
|
||||
Upgrade your account with a subscription to unlock all the
|
||||
metrics for your game.
|
||||
</AlertMessage>
|
||||
) : null}
|
||||
<Table>
|
||||
<TableBody>
|
||||
{[1, 2, 3, 4, 5, 6, 7].map(dayIndex => (
|
||||
<TableRow key={dayIndex}>
|
||||
<TableRowColumn>
|
||||
<Trans>Day {dayIndex} retained players</Trans>
|
||||
</TableRowColumn>
|
||||
<TableRowColumn style={styles.tableRowStatColumn}>
|
||||
{gameRollingMetrics &&
|
||||
gameRollingMetrics.retention &&
|
||||
gameRollingMetrics.retention[
|
||||
`d${dayIndex}RetainedPlayers`
|
||||
]
|
||||
: '-'}
|
||||
</TableRowColumn>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</ColumnStackLayout>
|
||||
)
|
||||
) : null}
|
||||
</Line>
|
||||
{publicGame && project && (
|
||||
<PublicGamePropertiesDialog
|
||||
open={isPublicGamePropertiesDialogOpen}
|
||||
project={project}
|
||||
game={publicGame}
|
||||
onApply={() => {
|
||||
setIsPublicGamePropertiesDialogOpen(false);
|
||||
updateGameFromProject();
|
||||
}}
|
||||
onClose={() => setIsPublicGamePropertiesDialogOpen(false)}
|
||||
/>
|
||||
] != null
|
||||
? gameRollingMetrics.retention[
|
||||
`d${dayIndex}RetainedPlayers`
|
||||
]
|
||||
: '-'}
|
||||
</TableRowColumn>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</ColumnStackLayout>
|
||||
)
|
||||
) : null}
|
||||
</Line>
|
||||
{publicGame && project && (
|
||||
<PublicGamePropertiesDialog
|
||||
open={isPublicGamePropertiesDialogOpen}
|
||||
project={project}
|
||||
publicGame={publicGame}
|
||||
onApply={() => {
|
||||
setIsPublicGamePropertiesDialogOpen(false);
|
||||
updateGameFromProject();
|
||||
}}
|
||||
onClose={() => setIsPublicGamePropertiesDialogOpen(false)}
|
||||
/>
|
||||
)}
|
||||
</Dialog>
|
||||
)}
|
||||
</Dialog>
|
||||
</I18n>
|
||||
);
|
||||
};
|
||||
|
@@ -8,11 +8,19 @@ import Checkbox from '../UI/Checkbox';
|
||||
import SelectField from '../UI/SelectField';
|
||||
import SelectOption from '../UI/SelectOption';
|
||||
import { t } from '@lingui/macro';
|
||||
import SemiControlledMultiAutoComplete from '../UI/SemiControlledMultiAutoComplete';
|
||||
import {
|
||||
allGameCategories,
|
||||
getCategoryName,
|
||||
} from '../Utils/GDevelopServices/Game';
|
||||
import { I18n } from '@lingui/react';
|
||||
|
||||
type Props = {|
|
||||
project: gdProject,
|
||||
setName: string => void,
|
||||
name: string,
|
||||
setCategories?: (string[]) => void,
|
||||
categories?: string[],
|
||||
setDescription: string => void,
|
||||
description: ?string,
|
||||
setAuthorIds: (string[]) => void,
|
||||
@@ -31,6 +39,8 @@ function PublicGameProperties({
|
||||
project,
|
||||
setName,
|
||||
name,
|
||||
categories,
|
||||
setCategories,
|
||||
setDescription,
|
||||
description,
|
||||
setAuthorIds,
|
||||
@@ -44,72 +54,113 @@ function PublicGameProperties({
|
||||
setOrientation,
|
||||
orientation,
|
||||
}: Props) {
|
||||
const [categoryInput, setCategoryInput] = React.useState('');
|
||||
|
||||
return (
|
||||
<ColumnStackLayout noMargin>
|
||||
<SemiControlledTextField
|
||||
floatingLabelText={<Trans>Game name</Trans>}
|
||||
fullWidth
|
||||
type="text"
|
||||
value={name}
|
||||
onChange={setName}
|
||||
autoFocus
|
||||
/>
|
||||
<SemiControlledTextField
|
||||
floatingLabelText={<Trans>Game description</Trans>}
|
||||
fullWidth
|
||||
type="text"
|
||||
value={description || ''}
|
||||
onChange={setDescription}
|
||||
autoFocus
|
||||
multiline
|
||||
rows={5}
|
||||
/>
|
||||
<UsersAutocomplete
|
||||
userIds={authorIds}
|
||||
onChange={setAuthorIds}
|
||||
floatingLabelText={<Trans>Authors</Trans>}
|
||||
helperText={
|
||||
<Trans>
|
||||
Select the usernames of the authors of this project. They will be
|
||||
displayed in the selected order, if you publish this game as an
|
||||
example or in the community.
|
||||
</Trans>
|
||||
}
|
||||
/>
|
||||
<SelectField
|
||||
fullWidth
|
||||
floatingLabelText={<Trans>Device orientation (for mobile)</Trans>}
|
||||
value={orientation}
|
||||
onChange={(e, i, value: string) => setOrientation(value)}
|
||||
>
|
||||
<SelectOption value="default" primaryText={t`Platform default`} />
|
||||
<SelectOption value="landscape" primaryText={t`Landscape`} />
|
||||
<SelectOption value="portrait" primaryText={t`Portrait`} />
|
||||
</SelectField>
|
||||
{// This view is used for public game properties as well as project properties.
|
||||
// The following properties are not shown in project properties.
|
||||
setPlayableWithKeyboard &&
|
||||
setPlayableWithGamepad &&
|
||||
setPlayableWithMobile && (
|
||||
<React.Fragment>
|
||||
<Checkbox
|
||||
label={<Trans>Playable with a keyboard</Trans>}
|
||||
checked={!!playWithKeyboard}
|
||||
onCheck={(e, checked) => setPlayableWithKeyboard(checked)}
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<ColumnStackLayout noMargin>
|
||||
<SemiControlledTextField
|
||||
floatingLabelText={<Trans>Game name</Trans>}
|
||||
fullWidth
|
||||
type="text"
|
||||
value={name}
|
||||
onChange={setName}
|
||||
autoFocus
|
||||
/>
|
||||
{setCategories && (
|
||||
<SemiControlledMultiAutoComplete
|
||||
hintText={t`Select a genre`}
|
||||
floatingLabelText={<Trans>Genres</Trans>}
|
||||
helperText={
|
||||
<Trans>
|
||||
Select up to 4 genres, the first one will define the game's
|
||||
main genre
|
||||
</Trans>
|
||||
}
|
||||
value={
|
||||
categories
|
||||
? categories.map(category => ({
|
||||
value: category,
|
||||
text: getCategoryName(category, i18n),
|
||||
}))
|
||||
: []
|
||||
}
|
||||
onChange={(event, values) => {
|
||||
setCategories(
|
||||
values ? values.map(category => category.value) : []
|
||||
);
|
||||
}}
|
||||
inputValue={categoryInput}
|
||||
onInputChange={(event, value) => {
|
||||
setCategoryInput(value);
|
||||
}}
|
||||
dataSource={allGameCategories.map(category => ({
|
||||
value: category,
|
||||
text: getCategoryName(category, i18n),
|
||||
}))}
|
||||
fullWidth
|
||||
optionsLimit={4}
|
||||
/>
|
||||
<Checkbox
|
||||
label={<Trans>Playable with a gamepad</Trans>}
|
||||
checked={!!playWithGamepad}
|
||||
onCheck={(e, checked) => setPlayableWithGamepad(checked)}
|
||||
/>
|
||||
<Checkbox
|
||||
label={<Trans>Playable on mobile</Trans>}
|
||||
checked={!!playWithMobile}
|
||||
onCheck={(e, checked) => setPlayableWithMobile(checked)}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</ColumnStackLayout>
|
||||
)}
|
||||
<SemiControlledTextField
|
||||
floatingLabelText={<Trans>Game description</Trans>}
|
||||
fullWidth
|
||||
type="text"
|
||||
value={description || ''}
|
||||
onChange={setDescription}
|
||||
autoFocus
|
||||
multiline
|
||||
rows={5}
|
||||
/>
|
||||
<UsersAutocomplete
|
||||
userIds={authorIds}
|
||||
onChange={setAuthorIds}
|
||||
floatingLabelText={<Trans>Authors</Trans>}
|
||||
helperText={
|
||||
<Trans>
|
||||
Select the usernames of the authors of this project. They will
|
||||
be displayed in the selected order, if you publish this game as
|
||||
an example or in the community.
|
||||
</Trans>
|
||||
}
|
||||
/>
|
||||
<SelectField
|
||||
fullWidth
|
||||
floatingLabelText={<Trans>Device orientation (for mobile)</Trans>}
|
||||
value={orientation}
|
||||
onChange={(e, i, value: string) => setOrientation(value)}
|
||||
>
|
||||
<SelectOption value="default" primaryText={t`Platform default`} />
|
||||
<SelectOption value="landscape" primaryText={t`Landscape`} />
|
||||
<SelectOption value="portrait" primaryText={t`Portrait`} />
|
||||
</SelectField>
|
||||
{// This view is used for public game properties as well as project properties.
|
||||
// The following properties are not shown in project properties.
|
||||
setPlayableWithKeyboard &&
|
||||
setPlayableWithGamepad &&
|
||||
setPlayableWithMobile && (
|
||||
<React.Fragment>
|
||||
<Checkbox
|
||||
label={<Trans>Playable with a keyboard</Trans>}
|
||||
checked={!!playWithKeyboard}
|
||||
onCheck={(e, checked) => setPlayableWithKeyboard(checked)}
|
||||
/>
|
||||
<Checkbox
|
||||
label={<Trans>Playable with a gamepad</Trans>}
|
||||
checked={!!playWithGamepad}
|
||||
onCheck={(e, checked) => setPlayableWithGamepad(checked)}
|
||||
/>
|
||||
<Checkbox
|
||||
label={<Trans>Playable on mobile</Trans>}
|
||||
checked={!!playWithMobile}
|
||||
onCheck={(e, checked) => setPlayableWithMobile(checked)}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</ColumnStackLayout>
|
||||
)}
|
||||
</I18n>
|
||||
);
|
||||
}
|
||||
|
||||
|
@@ -17,6 +17,7 @@ import { type PublicGame } from '../Utils/GDevelopServices/Game';
|
||||
*/
|
||||
type PublicProjectProperties = {|
|
||||
name: string,
|
||||
categories: string[],
|
||||
description: string,
|
||||
authorIds: string[],
|
||||
playWithKeyboard: boolean,
|
||||
@@ -30,8 +31,11 @@ function applyPublicPropertiesToProject(
|
||||
newProperties: PublicProjectProperties
|
||||
) {
|
||||
const t = str => str; //TODO
|
||||
const { name, authorIds, description } = newProperties;
|
||||
const { name, authorIds, description, categories } = newProperties;
|
||||
project.setName(name);
|
||||
const projectCategories = project.getCategories();
|
||||
projectCategories.clear();
|
||||
categories.forEach(category => projectCategories.push_back(category));
|
||||
project.setDescription(description);
|
||||
const projectAuthorIds = project.getAuthorIds();
|
||||
projectAuthorIds.clear();
|
||||
@@ -46,7 +50,7 @@ function applyPublicPropertiesToProject(
|
||||
|
||||
type Props = {|
|
||||
project: gdProject,
|
||||
game: PublicGame,
|
||||
publicGame: PublicGame,
|
||||
open: boolean,
|
||||
onClose: () => void,
|
||||
onApply: () => void,
|
||||
@@ -54,29 +58,30 @@ type Props = {|
|
||||
|
||||
const PublicGamePropertiesDialog = ({
|
||||
project,
|
||||
game,
|
||||
publicGame,
|
||||
open,
|
||||
onClose,
|
||||
onApply,
|
||||
}: Props) => {
|
||||
const publicGameAuthorIds = game.authors
|
||||
const publicGameAuthorIds = publicGame.authors
|
||||
.map(author => (author ? author.id : null))
|
||||
.filter(Boolean);
|
||||
const [name, setName] = React.useState(game.gameName);
|
||||
const [description, setDescription] = React.useState(game.description);
|
||||
const [name, setName] = React.useState(publicGame.gameName);
|
||||
const [categories, setCategories] = React.useState(publicGame.categories);
|
||||
const [description, setDescription] = React.useState(publicGame.description);
|
||||
const [authorIds, setAuthorIds] = React.useState<string[]>(
|
||||
publicGameAuthorIds
|
||||
);
|
||||
const [playWithKeyboard, setPlayableWithKeyboard] = React.useState(
|
||||
game.playWithKeyboard
|
||||
publicGame.playWithKeyboard
|
||||
);
|
||||
const [playWithGamepad, setPlayableWithGamepad] = React.useState(
|
||||
game.playWithGamepad
|
||||
publicGame.playWithGamepad
|
||||
);
|
||||
const [playWithMobile, setPlayableWithMobile] = React.useState(
|
||||
game.playWithMobile
|
||||
publicGame.playWithMobile
|
||||
);
|
||||
const [orientation, setOrientation] = React.useState(game.orientation);
|
||||
const [orientation, setOrientation] = React.useState(publicGame.orientation);
|
||||
|
||||
if (!open) return null;
|
||||
|
||||
@@ -84,6 +89,7 @@ const PublicGamePropertiesDialog = ({
|
||||
if (
|
||||
applyPublicPropertiesToProject(project, {
|
||||
name,
|
||||
categories: categories || [],
|
||||
description: description || '',
|
||||
authorIds,
|
||||
playWithKeyboard: !!playWithKeyboard,
|
||||
@@ -121,6 +127,8 @@ const PublicGamePropertiesDialog = ({
|
||||
<PublicGameProperties
|
||||
name={name}
|
||||
setName={setName}
|
||||
categories={categories}
|
||||
setCategories={setCategories}
|
||||
description={description}
|
||||
setDescription={setDescription}
|
||||
project={project}
|
||||
|
@@ -5,12 +5,12 @@ import TextField from '@material-ui/core/TextField';
|
||||
import { type MessageDescriptor } from '../Utils/i18n/MessageDescriptor.flow';
|
||||
import Autocomplete from '@material-ui/lab/Autocomplete';
|
||||
|
||||
type Option = {|
|
||||
export type AutocompleteOption = {|
|
||||
text: string, // The text displayed
|
||||
value: string, // The internal value selected
|
||||
|};
|
||||
|
||||
export type DataSource = Array<?Option>;
|
||||
export type DataSource = Array<?AutocompleteOption>;
|
||||
|
||||
const styles = {
|
||||
chip: {
|
||||
@@ -20,8 +20,8 @@ const styles = {
|
||||
};
|
||||
|
||||
type Props = {|
|
||||
value: Array<Option>,
|
||||
onChange: Option => void,
|
||||
value: Array<AutocompleteOption>,
|
||||
onChange: AutocompleteOption => void,
|
||||
dataSource: DataSource,
|
||||
inputValue: ?string,
|
||||
onInputChange: string => void,
|
||||
@@ -31,7 +31,8 @@ type Props = {|
|
||||
helperText?: React.Node,
|
||||
fullWidth?: boolean,
|
||||
error?: ?string,
|
||||
loading: boolean,
|
||||
loading?: boolean,
|
||||
optionsLimit?: number, // Allow limiting the number of options by disabling the autocomplete.
|
||||
|};
|
||||
|
||||
export default function SemiControlledMultiAutoComplete(props: Props) {
|
||||
@@ -45,11 +46,12 @@ export default function SemiControlledMultiAutoComplete(props: Props) {
|
||||
inputValue={props.inputValue}
|
||||
onInputChange={props.onInputChange}
|
||||
options={props.dataSource}
|
||||
getOptionLabel={(option: Option) => option.text}
|
||||
getOptionDisabled={(option: Option) =>
|
||||
getOptionLabel={(option: AutocompleteOption) => option.text}
|
||||
getOptionDisabled={(option: AutocompleteOption) =>
|
||||
!!props.value.find(
|
||||
element => element && element.value === option.value
|
||||
)
|
||||
) ||
|
||||
(props.optionsLimit && props.value.length >= props.optionsLimit)
|
||||
}
|
||||
loading={props.loading}
|
||||
renderInput={params => (
|
||||
|
@@ -1,8 +1,10 @@
|
||||
// @flow
|
||||
import axios from 'axios';
|
||||
import { type I18n as I18nType } from '@lingui/core';
|
||||
import { GDevelopGameApi, GDevelopGamesPlatform } from './ApiConfigs';
|
||||
import { type Filters } from './Filters';
|
||||
import { type UserPublicProfile } from './User';
|
||||
import { t } from '@lingui/macro';
|
||||
|
||||
export type PublicGame = {
|
||||
id: string,
|
||||
@@ -19,6 +21,7 @@ export type PublicGame = {
|
||||
lastWeekSessionsCount: number,
|
||||
lastYearSessionsCount: number,
|
||||
},
|
||||
categories?: string[],
|
||||
};
|
||||
|
||||
export type Game = {
|
||||
@@ -63,6 +66,64 @@ export type AllShowcasedGames = {
|
||||
filters: Filters,
|
||||
};
|
||||
|
||||
export const allGameCategories = [
|
||||
'action',
|
||||
'adventure',
|
||||
'shooter',
|
||||
'platformer',
|
||||
'rpg',
|
||||
'horror',
|
||||
'strategy',
|
||||
'puzzle',
|
||||
'story-rich',
|
||||
'survival',
|
||||
'racing',
|
||||
'building',
|
||||
'simulation',
|
||||
'sport',
|
||||
'multiplayer',
|
||||
'leaderboard',
|
||||
];
|
||||
|
||||
export const getCategoryName = (category: string, i18n: I18nType) => {
|
||||
switch (category) {
|
||||
case 'action':
|
||||
return i18n._(t`Action`);
|
||||
case 'adventure':
|
||||
return i18n._(t`Adventure`);
|
||||
case 'shooter':
|
||||
return i18n._(t`Shooter`);
|
||||
case 'platformer':
|
||||
return i18n._(t`Platformer`);
|
||||
case 'rpg':
|
||||
return i18n._(t`RPG`);
|
||||
case 'horror':
|
||||
return i18n._(t`Horror`);
|
||||
case 'strategy':
|
||||
return i18n._(t`Strategy`);
|
||||
case 'puzzle':
|
||||
return i18n._(t`Puzzle`);
|
||||
case 'racing':
|
||||
return i18n._(t`Racing`);
|
||||
case 'simulation':
|
||||
return i18n._(t`Simulation`);
|
||||
case 'sport':
|
||||
return i18n._(t`Sport`);
|
||||
case 'story-rich':
|
||||
return i18n._(t`Story-Rich`);
|
||||
case 'survival':
|
||||
return i18n._(t`Survival`);
|
||||
case 'building':
|
||||
return i18n._(t`Building`);
|
||||
case 'multiplayer':
|
||||
return i18n._(t`Multiplayer`);
|
||||
case 'leaderboard':
|
||||
return i18n._(t`Leaderboard`);
|
||||
default:
|
||||
return category;
|
||||
}
|
||||
};
|
||||
|
||||
export const getGameUrl = (game: ?Game) => {
|
||||
if (!game) return null;
|
||||
return GDevelopGamesPlatform.getGameUrl(game.id);
|
||||
@@ -135,6 +196,7 @@ export const updateGame = (
|
||||
gameId: string,
|
||||
{
|
||||
gameName,
|
||||
categories,
|
||||
authorName,
|
||||
publicWebBuildId,
|
||||
description,
|
||||
@@ -144,6 +206,7 @@ export const updateGame = (
|
||||
orientation,
|
||||
}: {|
|
||||
gameName?: string,
|
||||
categories?: string[],
|
||||
authorName?: string,
|
||||
publicWebBuildId?: ?string,
|
||||
description?: string,
|
||||
@@ -159,6 +222,7 @@ export const updateGame = (
|
||||
`${GDevelopGameApi.baseUrl}/game/${gameId}`,
|
||||
{
|
||||
gameName,
|
||||
categories,
|
||||
authorName,
|
||||
publicWebBuildId,
|
||||
description,
|
||||
|
@@ -10,6 +10,7 @@ import {
|
||||
type UserPublicProfileSearch,
|
||||
getUserPublicProfilesByIds,
|
||||
} from './GDevelopServices/User';
|
||||
import { type AutocompleteOption } from '../UI/SemiControlledMultiAutoComplete';
|
||||
|
||||
import useForceUpdate from './UseForceUpdate';
|
||||
|
||||
@@ -20,11 +21,6 @@ type Props = {|
|
||||
helperText: React.Node,
|
||||
|};
|
||||
|
||||
export type AutocompleteOption = {|
|
||||
text: string,
|
||||
value: string,
|
||||
|};
|
||||
|
||||
const getErrorMessage = (error: ?Error) => {
|
||||
if (error) return 'Error while loading users';
|
||||
};
|
||||
|
Reference in New Issue
Block a user