Add version in properties of a project

This commit is contained in:
Florian Rival
2018-06-11 22:36:44 +01:00
parent daa683f492
commit 06b54e277a
12 changed files with 182 additions and 122 deletions
+4
View File
@@ -61,6 +61,7 @@ Project::Project()
:
#if defined(GD_IDE_ONLY)
name(_("Project")),
version("1.0.0"),
packageName("com.example.gamename"),
orientation("landscape"),
folderProject(false),
@@ -524,6 +525,7 @@ void Project::UnserializeFrom(const SerializerElement& element) {
const SerializerElement& propElement =
element.GetChild("properties", 0, "Info");
SetName(propElement.GetChild("name", 0, "Nom").GetValue().GetString());
SetVersion(propElement.GetStringAttribute("version", "1.0.0"));
SetDefaultWidth(
propElement.GetChild("windowWidth", 0, "WindowW").GetValue().GetInt());
SetDefaultHeight(
@@ -782,6 +784,7 @@ void Project::SerializeTo(SerializerElement& element) const {
SerializerElement& propElement = element.AddChild("properties");
propElement.AddChild("name").SetValue(GetName());
propElement.SetAttribute("version", GetVersion());
propElement.AddChild("author").SetValue(GetAuthor());
propElement.AddChild("windowWidth").SetValue(GetMainWindowDefaultWidth());
propElement.AddChild("windowHeight").SetValue(GetMainWindowDefaultHeight());
@@ -1100,6 +1103,7 @@ Project& Project::operator=(const Project& other) {
void Project::Init(const gd::Project& game) {
// Some properties
name = game.name;
version = game.version;
windowWidth = game.windowWidth;
windowHeight = game.windowHeight;
maxFPS = game.maxFPS;
+12 -26
View File
@@ -21,44 +21,18 @@ class TiXmlElement;
#include "GDCore/Project/VariablesContainer.h"
namespace gd {
class Platform;
}
namespace gd {
class Layout;
}
namespace gd {
class ExternalEvents;
}
namespace gd {
class ResourcesManager;
}
namespace gd {
class ExternalLayout;
}
namespace gd {
class Object;
}
namespace gd {
class VariablesContainer;
}
namespace gd {
class ArbitraryResourceWorker;
}
namespace gd {
class SourceFile;
}
namespace gd {
class ImageManager;
}
namespace gd {
class Behavior;
}
namespace gd {
class BehaviorsSharedData;
}
namespace gd {
class BaseEvent;
}
namespace gd {
class SerializerElement;
}
#undef GetObject // Disable an annoying macro
@@ -92,6 +66,17 @@ class GD_CORE_API Project : public ClassWithObjects {
*/
const gd::String& GetName() const { return name; }
/**
* \brief Change the version of the project.
* This can be freely set, but should follow "X.Y.Z" format for compatibility with some exporters.
*/
void SetVersion(const gd::String& version_) { version = version_; };
/**
* \brief Get project version.
*/
const gd::String& GetVersion() const { return version; }
#if defined(GD_IDE_ONLY)
/**
* \brief Change the author of the project.
@@ -877,6 +862,7 @@ class GD_CORE_API Project : public ClassWithObjects {
void LoadProjectInformationFromXml(const TiXmlElement* elem);
gd::String name; ///< Game name
gd::String version; ///< Game version number (used for some exports)
unsigned int windowWidth; ///< Window default width
unsigned int windowHeight; ///< Window default height
int maxFPS; ///< Maximum Frame Per Seconds, -1 for unlimited
+1
View File
@@ -315,6 +315,7 @@ bool ExporterHelper::ExportElectronFiles(const gd::Project &project,
project.GetName()) // TODO: JSON encode string
.FindAndReplace("GDJS_GAME_AUTHOR",
project.GetAuthor()) // TODO: JSON encode string
.FindAndReplace("GDJS_GAME_VERSION", project.GetVersion())
.FindAndReplace(
"GDJS_GAME_MANGLED_NAME",
project.GetName().LowerCase()); // TODO: JSON encode string
+1 -1
View File
@@ -4,7 +4,7 @@
"productName": "GDJS_GAME_NAME",
"description": "GDJS_GAME_NAME",
"author": "GDJS_GAME_AUTHOR",
"version": "1.0.0",
"version": "GDJS_GAME_VERSION",
"dependencies": {}
}
@@ -1,3 +1,4 @@
// @flow
import React, { Component } from 'react';
import Dialog from '../../UI/Dialog';
import FlatButton from 'material-ui/FlatButton';
@@ -9,9 +10,11 @@ import { findGDJS } from './LocalGDJSFinder';
import localFileSystem from './LocalFileSystem';
import LocalFolderPicker from '../../UI/LocalFolderPicker';
import HelpButton from '../../UI/HelpButton';
import { displaySanityCheck } from '../SanityChecker';
import { getSanityMessages } from '../SanityChecker/ProjectSanityChecker';
import { translate } from 'react-i18next';
import {
displayProjectErrorsBox,
getErrors,
} from '../../ProjectManager/ProjectErrorsChecker';
import { translate, type TranslatorProps } from 'react-i18next';
import assignIn from 'lodash/assignIn';
import optionalRequire from '../../Utils/OptionalRequire';
import Window from '../../Utils/Window';
@@ -20,7 +23,16 @@ const shell = electron ? electron.shell : null;
const gd = global.gd;
class LocalCordovaExport extends Component {
type Props = TranslatorProps & {|
project: gdProject,
|};
type State = {|
outputDir: string,
exportFinishedDialogOpen: boolean,
|};
class LocalCordovaExport extends Component<Props, State> {
state = {
exportFinishedDialogOpen: false,
outputDir: '',
@@ -61,7 +73,7 @@ class LocalCordovaExport extends Component {
sendExportLaunched('local-cordova');
if (!displaySanityCheck(t, getSanityMessages(t, project))) return;
if (!displayProjectErrorsBox(t, getErrors(t, project))) return;
const outputDir = this.state.outputDir;
project.setLastCompilationDirectory(outputDir);
@@ -70,14 +82,10 @@ class LocalCordovaExport extends Component {
.then(({ exporter }) => {
const exportOptions = new gd.MapStringBoolean();
exportOptions.set('exportForCordova', true);
exporter.exportWholePixiProject(
project,
outputDir,
exportOptions
);
exporter.exportWholePixiProject(project, outputDir, exportOptions);
exportOptions.delete();
exporter.delete();
this.setState({
exportFinishedDialogOpen: true,
});
@@ -88,7 +96,7 @@ class LocalCordovaExport extends Component {
};
openExportFolder = () => {
shell.openItem(this.state.outputDir);
if (shell) shell.openItem(this.state.outputDir);
};
openPhoneGapBuild = () => {
@@ -1,3 +1,4 @@
// @flow
import React, { Component } from 'react';
import Dialog from '../../UI/Dialog';
import FlatButton from 'material-ui/FlatButton';
@@ -9,9 +10,8 @@ import { findGDJS } from './LocalGDJSFinder';
import localFileSystem from './LocalFileSystem';
import LocalFolderPicker from '../../UI/LocalFolderPicker';
import HelpButton from '../../UI/HelpButton';
import { displaySanityCheck } from '../SanityChecker';
import { getSanityMessages } from '../SanityChecker/ProjectSanityChecker';
import { translate } from 'react-i18next';
import { displayProjectErrorsBox, getErrors } from '../../ProjectManager/ProjectErrorsChecker';
import { translate, type TranslatorProps } from 'react-i18next';
import assignIn from 'lodash/assignIn';
import optionalRequire from '../../Utils/OptionalRequire';
const electron = optionalRequire('electron');
@@ -19,7 +19,16 @@ const shell = electron ? electron.shell : null;
const gd = global.gd;
class LocalElectronExport extends Component {
type Props = TranslatorProps & {|
project: gdProject,
|};
type State = {|
outputDir: string,
exportFinishedDialogOpen: boolean,
|};
class LocalElectronExport extends Component<Props, State> {
state = {
exportFinishedDialogOpen: false,
outputDir: '',
@@ -60,7 +69,7 @@ class LocalElectronExport extends Component {
sendExportLaunched('local-electron');
if (!displaySanityCheck(t, getSanityMessages(t, project))) return;
if (!displayProjectErrorsBox(t, getErrors(t, project))) return;
const outputDir = this.state.outputDir;
project.setLastCompilationDirectory(outputDir);
@@ -87,7 +96,7 @@ class LocalElectronExport extends Component {
};
openExportFolder = () => {
shell.openItem(this.state.outputDir);
if (shell) shell.openItem(this.state.outputDir);
};
render() {
@@ -24,8 +24,7 @@ import Window from '../../../Utils/Window';
import { delay } from '../../../Utils/Delay';
import CreateProfile from '../../../Profile/CreateProfile';
import LimitDisplayer from '../../../Profile/LimitDisplayer';
import { displaySanityCheck } from '../../SanityChecker';
import { getSanityMessages } from '../../SanityChecker/ProjectSanityChecker';
import { displayProjectErrorsBox, getErrors } from '../../../ProjectManager/ProjectErrorsChecker';
import { translate, type TranslatorProps } from 'react-i18next';
import { type Limit } from '../../../Utils/GDevelopServices/Usage';
const path = optionalRequire('path');
@@ -209,7 +208,7 @@ class LocalOnlineCordovaExport extends Component<Props, State> {
const { t, project } = this.props;
sendExportLaunched('local-online-cordova');
if (!displaySanityCheck(t, getSanityMessages(t, project))) return;
if (!displayProjectErrorsBox(t, getErrors(t, project))) return;
const handleError = (message: string) => err => {
if (!this.state.errored) {
@@ -24,8 +24,7 @@ import Window from '../../../Utils/Window';
import { delay } from '../../../Utils/Delay';
import CreateProfile from '../../../Profile/CreateProfile';
import LimitDisplayer from '../../../Profile/LimitDisplayer';
import { displaySanityCheck } from '../../SanityChecker';
import { getSanityMessages } from '../../SanityChecker/ProjectSanityChecker';
import { displayProjectErrorsBox, getErrors } from '../../../ProjectManager/ProjectErrorsChecker';
import { translate, type TranslatorProps } from 'react-i18next';
import { type Limit } from '../../../Utils/GDevelopServices/Usage';
const path = optionalRequire('path');
@@ -209,7 +208,7 @@ class LocalOnlineElectronExport extends Component<Props, State> {
const { t, project } = this.props;
sendExportLaunched('local-online-electron');
if (!displaySanityCheck(t, getSanityMessages(t, project))) return;
if (!displayProjectErrorsBox(t, getErrors(t, project))) return;
const handleError = (message: string) => err => {
if (!this.state.errored) {
@@ -1,30 +0,0 @@
// @flow
import { type TFunction } from 'react-i18next';
import { type SanityMessages } from './index';
export const getSanityMessages = (t: TFunction, project: gdProject): SanityMessages => {
let errors = [];
let warnings = [];
if (!project.getPackageName()) {
errors.push(
t(
'The package name is empty. Choose and enter a package name in the game properties.'
)
);
} else if (project.getPackageName().length >= 255) {
errors.push(t('The package name is too long.'));
}
if (!project.getName()) {
errors.push(
t(
'The game name is empty. Choose and enter a name in the game properties.'
)
);
}
return {
errors,
warnings,
};
};
@@ -1,25 +0,0 @@
// @flow
import { type TFunction } from 'react-i18next';
import { showErrorBox } from '../../UI/Messages/MessageBox';
export type SanityMessages = {
errors: Array<string>,
warnings: Array<string>,
};
export const displaySanityCheck = (
t: TFunction,
messages: SanityMessages
): boolean => {
if (messages.errors.length) {
showErrorBox(
t(
'Your game has some invalid elements, please fix these before continuing:'
) +
'\n\n' +
messages.errors.map(message => `- ${message}`).join('\n')
);
}
return !messages.errors.length;
};
@@ -0,0 +1,90 @@
// @flow
import { type TFunction } from 'react-i18next';
import { showErrorBox } from '../UI/Messages/MessageBox';
import values from 'lodash/values';
export type ProjectError = {
type: 'error' | 'warning',
message: string,
extraExplanation: string,
};
export type ProjectErrors = {
[string]: Array<ProjectError>,
};
export const getErrors = (t: TFunction, project: gdProject): ProjectErrors => {
const errors: ProjectErrors = {};
const addError = (
propertyName: string,
type: 'error' | 'warning',
message: string,
extraExplanation: string = ''
) => {
if (!errors[propertyName]) errors[propertyName] = [];
errors[propertyName].push({
type,
message,
extraExplanation,
});
};
if (!project.getPackageName()) {
addError(
'packageName',
'error',
t('The package name is empty.'),
t('Choose and enter a package name in the game properties.')
);
} else if (project.getPackageName().length >= 255) {
addError(
'packageName',
'error',
t('The package name is too long.'),
t('Change the package name in the game properties.')
);
}
if (!project.getName()) {
addError(
'name',
'error',
t('The name of your game is too long.'),
t('Change the name in the game properties.')
);
}
if (!project.getVersion() || project.getVersion().split('.').length < 3) {
addError(
'packageName',
'error',
t("The version that you've set for the game is invalid."),
t('Enter a version in the game properties.')
);
}
return errors;
};
export const displayProjectErrorsBox = (
t: TFunction,
errors: ProjectErrors
): boolean => {
if (!Object.keys(errors).length) return true;
showErrorBox(
t(
'Your game has some invalid elements, please fix these before continuing:'
) +
'\n\n' +
values(errors)
.map(errors =>
errors.map((error: ProjectError) => `- ${error.message}`).join('\n')
)
.join('\n')
);
return false;
};
@@ -1,14 +1,16 @@
// @flow
import * as React from 'react';
import FlatButton from 'material-ui/FlatButton';
import TextField from 'material-ui/TextField';
import Checkbox from 'material-ui/Checkbox';
import SelectField from 'material-ui/SelectField';
import MenuItem from 'material-ui/MenuItem';
import Dialog from '../UI/Dialog';
import SemiControlledTextField from '../UI/SemiControlledTextField';
import SubscriptionChecker from '../Profile/SubscriptionChecker';
import { translate, type TranslatorProps } from 'react-i18next';
import { getErrors, displayProjectErrorsBox } from './ProjectErrorsChecker';
type Props = {|
type Props = TranslatorProps & {|
project: gdProject,
open: boolean,
onClose: Function,
@@ -21,13 +23,14 @@ type State = {|
windowDefaultHeight: number,
name: string,
author: string,
version: string,
packageName: string,
orientation: string,
sizeOnStartupMode: string,
showGDevelopSplash: boolean,
|};
export default class ProjectPropertiesDialog extends React.Component<
class ProjectPropertiesDialog extends React.Component<
Props,
State
> {
@@ -44,6 +47,7 @@ export default class ProjectPropertiesDialog extends React.Component<
windowDefaultHeight: project.getMainWindowDefaultHeight(),
name: project.getName(),
author: project.getAuthor(),
version: project.getVersion(),
packageName: project.getPackageName(),
orientation: project.getOrientation(),
sizeOnStartupMode: project.getSizeOnStartupMode(),
@@ -61,12 +65,13 @@ export default class ProjectPropertiesDialog extends React.Component<
}
_onApply = () => {
const { project } = this.props;
const { project, t } = this.props;
const {
windowDefaultWidth,
windowDefaultHeight,
name,
author,
version,
packageName,
orientation,
sizeOnStartupMode,
@@ -76,11 +81,14 @@ export default class ProjectPropertiesDialog extends React.Component<
project.setDefaultHeight(windowDefaultHeight);
project.setName(name);
project.setAuthor(author);
project.setVersion(version);
project.setPackageName(packageName);
project.setOrientation(orientation);
project.setSizeOnStartupMode(sizeOnStartupMode);
project.getLoadingScreen().showGDevelopSplash(showGDevelopSplash);
if (!displayProjectErrorsBox(t, getErrors(t, project))) return;
this.props.onApply();
};
@@ -103,6 +111,7 @@ export default class ProjectPropertiesDialog extends React.Component<
windowDefaultWidth,
windowDefaultHeight,
author,
version,
packageName,
orientation,
sizeOnStartupMode,
@@ -117,48 +126,56 @@ export default class ProjectPropertiesDialog extends React.Component<
onRequestClose={this.props.onClose}
autoScrollBodyContent={true}
>
<TextField
<SemiControlledTextField
floatingLabelText="Game name"
fullWidth
type="text"
value={name}
onChange={(e, value) => this.setState({ name: value })}
onChange={value => this.setState({ name: value })}
/>
<TextField
<SemiControlledTextField
floatingLabelText="Game's window width"
fullWidth
type="number"
value={windowDefaultWidth}
onChange={(e, value) =>
value={'' + windowDefaultWidth}
onChange={value =>
this.setState({
windowDefaultWidth: Math.max(0, parseInt(value, 10)),
})}
/>
<TextField
<SemiControlledTextField
floatingLabelText="Game's window height"
fullWidth
type="number"
value={windowDefaultHeight}
onChange={(e, value) =>
value={'' + windowDefaultHeight}
onChange={value =>
this.setState({
windowDefaultHeight: Math.max(0, parseInt(value, 10)),
})}
/>
<TextField
<SemiControlledTextField
floatingLabelText="Author name"
fullWidth
hintText="Your name"
type="text"
value={author}
onChange={(e, value) => this.setState({ author: value })}
onChange={value => this.setState({ author: value })}
/>
<TextField
<SemiControlledTextField
floatingLabelText="Version number (X.Y.Z)"
fullWidth
hintText="1.0.0"
type="text"
value={version}
onChange={value => this.setState({ version: value })}
/>
<SemiControlledTextField
floatingLabelText="Package name (for iOS and Android)"
fullWidth
hintText="com.example.mygame"
type="text"
value={packageName}
onChange={(e, value) => this.setState({ packageName: value })}
onChange={value => this.setState({ packageName: value })}
/>
<SelectField
fullWidth
@@ -219,3 +236,5 @@ export default class ProjectPropertiesDialog extends React.Component<
);
}
}
export default translate()(ProjectPropertiesDialog);