Compare commits

..

1 Commits

Author SHA1 Message Date
Florian Rival
f368660cf2 Fix newIDE yarn.lock 2017-12-09 17:25:59 +01:00
228 changed files with 3013 additions and 7864 deletions

View File

@@ -62,12 +62,7 @@
"__tuple": "cpp",
"hash_map": "cpp",
"hash_set": "cpp",
"system_error": "cpp",
"__nullptr": "cpp",
"__functional_base": "cpp",
"__functional_base_03": "cpp",
"chrono": "cpp",
"ratio": "cpp"
"system_error": "cpp"
},
"files.exclude": {
"Binaries/*build*": true,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 324 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 382 B

View File

@@ -621,18 +621,6 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(gd:
.AddParameter("objectList", _("Objects"))
.MarkAsSimple();
obj.AddCondition("CollisionPoint",
_("Point inside object"),
_("Test if a point is inside the object collision masks."),
_("_PARAM1_;_PARAM2_ is inside _PARAM0_"),
_("Collision"),
"res/conditions/collisionPoint24.png",
"res/conditions/collisionPoint.png")
.AddParameter("object", _("Object"))
.AddParameter("expression", _("X position of the point"))
.AddParameter("expression", _("Y position of the point"))
.MarkAsSimple();
obj.AddExpression("X", _("X position"), _("X position of the object"), _("Position"), "res/actions/position.png")
.AddParameter("object", _("Object"));

View File

@@ -143,13 +143,13 @@ void InstructionSentenceFormatter::LoadTypesFormattingFromConfig()
{
//Load default configuration
typesFormatting.clear();
typesFormatting["expression"].SetColor(27, 143, 1).SetBold();
typesFormatting["object"].SetColor(182, 97, 10).SetBold();
typesFormatting["behavior"].SetColor(119, 119, 119).SetBold();
typesFormatting["operator"].SetColor(55, 131, 211).SetBold();
typesFormatting["objectvar"].SetColor(131, 55, 162).SetBold();
typesFormatting["scenevar"].SetColor(131, 55, 162).SetBold();
typesFormatting["globalvar"].SetColor(131, 55, 162).SetBold();
typesFormatting["expression"].SetColor(99, 0, 0).SetBold();
typesFormatting["object"].SetColor(19, 81, 0).SetBold();
typesFormatting["behavior"].SetColor(19, 81, 0).SetBold();
typesFormatting["operator"].SetColor(64, 81, 79).SetBold();
typesFormatting["objectvar"].SetColor(44, 69, 99).SetBold();
typesFormatting["scenevar"].SetColor(44, 69, 99).SetBold();
typesFormatting["globalvar"].SetColor(44, 69, 99).SetBold();
//Load any existing custom configuration
#if !defined(GD_NO_WX_GUI)

View File

@@ -29,21 +29,13 @@ gdjs.TextRuntimeObjectPixiRenderer.prototype.ensureUpToDate = function() {
gdjs.TextRuntimeObjectPixiRenderer.prototype.updateStyle = function() {
var fontName = "\"gdjs_font_" + this._object._fontName + "\"";
var style = this._text.style;
style.fontStyle = this._object._italic ? 'italic' : 'normal';
style.fontWeight = this._object._bold ? 'bold' : 'normal';
style.fontSize = this._object._characterSize;
style.fontFamily = fontName;
style.fill = gdjs.rgbToHexNumber(
this._object._color[0],
this._object._color[1],
this._object._color[2]
);
// Manually ask the PIXI object to re-render as we changed a style property
// see http://www.html5gamedevs.com/topic/16924-change-text-style-post-render/
this._text.dirty = true;
var style = { align:"left" };
style.font = "";
if ( this._object._italic ) style.font += "italic ";
if ( this._object._bold ) style.font += "bold ";
style.font += this._object._characterSize + "px " + fontName;
style.fill = "rgb("+this._object._color[0]+","+this._object._color[1]+","+this._object._color[2]+")";
this._text.style = style;
};
gdjs.TextRuntimeObjectPixiRenderer.prototype.updatePosition = function() {

View File

@@ -69,7 +69,6 @@ BaseObjectExtension::BaseObjectExtension()
objectActions["Rebondir"].SetFunctionName("SeparateObjectsWithForces").SetIncludeFile("GDCpp/Extensions/Builtin/ObjectTools.h");
objectActions["Ecarter"].SetFunctionName("SeparateObjectsWithoutForces").SetIncludeFile("GDCpp/Extensions/Builtin/ObjectTools.h");
objectActions["SeparateFromObjects"].SetFunctionName("SeparateFromObjects").SetIncludeFile("GDCpp/Extensions/Builtin/ObjectTools.h");
objectConditions["CollisionPoint"].SetFunctionName("IsCollidingWithPoint");
objectExpressions["X"].SetFunctionName("GetX");

View File

@@ -128,19 +128,3 @@ CollisionResult GD_API PolygonCollisionTest(Polygon2d & p1, Polygon2d & p2)
return result;
}
bool GD_API IsPointInsidePolygon(Polygon2d & poly, float x, float y)
{
bool inside = false;
sf::Vector2f vi, vj;
for (std::size_t i = 0, j = poly.vertices.size()-1; i < poly.vertices.size(); j = i++)
{
vi = poly.vertices[i];
vj = poly.vertices[j];
if ( ((vi.y>y) != (vj.y>y)) && (x < (vj.x-vi.x) * (y-vi.y) / (vj.y-vi.y) + vi.x) )
inside = !inside;
}
return inside;
}

View File

@@ -33,16 +33,5 @@ struct CollisionResult
*/
CollisionResult GD_API PolygonCollisionTest(Polygon2d & p1, Polygon2d & p2);
/**
* Check if a point is inside a polygon.
*
* Uses PNPOLY by W. Randolph Franklin (https://wrf.ecse.rpi.edu//Research/Short_Notes/pnpoly.html)
*
* \return true if the point is inside the polygon
*
* \ingroup GameEngine
*/
bool GD_API IsPointInsidePolygon(Polygon2d & poly, float x, float y);
#endif // POLYGONCOLLISION_H

View File

@@ -379,17 +379,6 @@ bool RuntimeObject::IsCollidingWith(RuntimeObject * obj2)
return false;
}
bool RuntimeObject::IsCollidingWithPoint(float pointX, float pointY){
vector<Polygon2d> hitBoxes = GetHitBoxes();
for (std::size_t i = 0; i < hitBoxes.size(); ++i)
{
if ( IsPointInsidePolygon(hitBoxes[i], pointX, pointY) )
return true;
}
return false;
}
void RuntimeObject::SeparateObjectsWithoutForces( std::map <gd::String, std::vector<RuntimeObject*> *> pickedObjectLists)
{
vector<RuntimeObject*> objects2;

View File

@@ -223,14 +223,6 @@ public:
*/
bool IsCollidingWith(RuntimeObject * other);
/**
* \brief Check if a point is inside the object collision hitboxes.
* \param pointX The point x coordinate.
* \param pointY The point y coordinate.
* \return true if the point is inside the object collision hitboxes.
*/
bool IsCollidingWithPoint(float pointX, float pointY);
/**
* \brief Check collision with each object of the list using their hitboxes, and move the object
* according to the sum of the move vector returned by each collision test.

View File

@@ -103,7 +103,7 @@ ELSE()
ELSE()
add_custom_target(GDJS_Runtime
ALL
COMMAND bash "CopyRuntimeToGD.sh" ${GD_base_dir}/Binaries/Output/${CMAKE_BUILD_TYPE}_${CMAKE_SYSTEM_NAME}/JsPlatform/Runtime
COMMAND sh "CopyRuntimeToGD.sh" ${GD_base_dir}/Binaries/Output/${CMAKE_BUILD_TYPE}_${CMAKE_SYSTEM_NAME}/JsPlatform/Runtime
WORKING_DIRECTORY ${GD_base_dir}/GDJS/scripts)
ENDIF()
ENDIF()

View File

@@ -71,7 +71,6 @@ BaseObjectExtension::BaseObjectExtension()
objectConditions["ObjectVariableChildExists"].SetFunctionName("variableChildExists").SetIncludeFile("runtimeobject.js");
objectActions["ObjectVariableRemoveChild"].SetFunctionName("variableRemoveChild").SetIncludeFile("runtimeobject.js");
objectActions["ObjectVariableClearChildren"].SetFunctionName("variableClearChildren").SetIncludeFile("runtimeobject.js");
objectConditions["CollisionPoint"].SetFunctionName("isCollidingWithPoint").SetIncludeFile("runtimeobject.js");
objectExpressions["X"].SetFunctionName("getX");
objectExpressions["Y"].SetFunctionName("getY");

View File

@@ -100,17 +100,6 @@ gdjs.evtTools.input.keysNameToCode = {
"x": 88,
"y": 89,
"z": 90,
"Num0": 48,
"Num1": 49,
"Num2": 50,
"Num3": 51,
"Num4": 52,
"Num5": 53,
"Num6": 54,
"Num7": 55,
"Num8": 56,
"Num9": 57,
"Numpad0": 96,
"Numpad1": 97,

View File

@@ -17,7 +17,7 @@ var gdjs = gdjs || {
};
/**
* Convert a rgb color value to a hex string.
* Convert a rgb color value to a hex value.
* @note No "#" or "0x" are added.
* @static
*/
@@ -25,14 +25,6 @@ gdjs.rgbToHex = function(r, g, b) {
return "" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
};
/**
* Convert a rgb color value to a hex value.
* @static
*/
gdjs.rgbToHexNumber = function(r, g, b) {
return (r << 16) + (g << 8) + b;
}
/**
* Get a random integer between 0 and max.
* @method random

View File

@@ -75,8 +75,6 @@ gdjs.HowlerSoundManager = function(resources)
this._resources = resources;
this._availableResources = {}; //Map storing "audio" resources for faster access.
this._globalVolume = 100;
this._sounds = {};
this._musics = {};
this._freeSounds = []; //Sounds without an assigned channel.
@@ -123,7 +121,7 @@ gdjs.HowlerSoundManager = function(resources)
sound.play();
}
}
that._pausedSounds.length = 0;
that._pausedSounds = [];
that._paused = false;
}, false);
});
@@ -205,7 +203,7 @@ gdjs.HowlerSoundManager.prototype.playSound = function(soundName, loop, volume,
gdjs.HowlerSoundManager.prototype.playSoundOnChannel = function(soundName, channel, loop, volume, pitch) {
var oldSound = this._sounds[channel];
if (oldSound) {
oldSound.unload();
oldSound.stop();
}
var soundFile = this._getFileFromSoundName(soundName);
@@ -246,7 +244,7 @@ gdjs.HowlerSoundManager.prototype.playMusic = function(soundName, loop, volume,
gdjs.HowlerSoundManager.prototype.playMusicOnChannel = function(soundName, channel, loop, volume, pitch) {
var oldMusic = this._musics[channel];
if (oldMusic) {
oldMusic.unload();
oldMusic.stop();
}
var soundFile = this._getFileFromSoundName(soundName);
@@ -270,43 +268,41 @@ gdjs.HowlerSoundManager.prototype.getMusicOnChannel = function(channel) {
};
gdjs.HowlerSoundManager.prototype.setGlobalVolume = function(volume) {
this._globalVolume = volume;
if (this._globalVolume > 100) this._globalVolume = 100;
if (this._globalVolume < 0) this._globalVolume = 0;
Howler.volume(this._globalVolume/100);
Howler.volume(volume/100);
};
gdjs.HowlerSoundManager.prototype.getGlobalVolume = function() {
return this._globalVolume;
return Howler.volume()*100;
};
gdjs.HowlerSoundManager.prototype.clearAll = function() {
for (var i = 0;i<this._freeSounds.length;++i) {
if (this._freeSounds[i]) this._freeSounds[i].unload();
if (this._freeSounds[i]) this._freeSounds[i].stop();
}
for (var i = 0;i<this._freeMusics.length;++i) {
if (this._freeMusics[i]) this._freeMusics[i].unload();
if (this._freeMusics[i]) this._freeMusics[i].stop();
}
this._freeSounds.length = 0;
this._freeMusics.length = 0;
for (var p in this._sounds) {
if (this._sounds.hasOwnProperty(p) && this._sounds[p]) {
this._sounds[p].unload();
this._sounds[p].stop();
delete this._sounds[p];
}
}
for (var p in this._musics) {
if (this._musics.hasOwnProperty(p) && this._musics[p]) {
this._musics[p].unload();
this._musics[p].stop();
delete this._musics[p];
}
}
this._pausedSounds.length = 0;
this._pausedSounds = [];
}
gdjs.HowlerSoundManager.prototype.preloadAudio = function(onProgress, onComplete, resources) {
resources = resources || this._resources;
var files = [];
for(var i = 0, len = resources.length;i<len;++i) {
var res = resources[i];
@@ -323,8 +319,10 @@ gdjs.HowlerSoundManager.prototype.preloadAudio = function(onProgress, onComplete
var loaded = 0;
function onLoad(audioFile) {
console.log("loaded" + audioFile);
loaded++;
if (loaded === files.length) {
console.log("All audio loaded");
return onComplete();
}
@@ -334,12 +332,13 @@ gdjs.HowlerSoundManager.prototype.preloadAudio = function(onProgress, onComplete
var that = this;
for(var i = 0;i<files.length;++i) {
(function(audioFile) {
var sound = new XMLHttpRequest();
sound.addEventListener('load', onLoad.bind(that, audioFile));
sound.addEventListener('error', onLoad.bind(that, audioFile));
sound.addEventListener('abort', onLoad.bind(that, audioFile));
sound.open('GET', audioFile);
sound.send();
console.log("Loading" + audioFile)
var sound = new Howl({
src: [audioFile], //TODO: ogg, mp3...
preload: true,
onload: onLoad.bind(that, audioFile),
onloaderror: onLoad.bind(that, audioFile)
});
})(files[i]);
}
}

File diff suppressed because one or more lines are too long

View File

@@ -22,13 +22,13 @@ gdjs.NightPixiFilter = function() {
opacity: { type: '1f', value: 1 }
};
PIXI.Filter.call(this,
PIXI.AbstractFilter.call(this,
vertexShader,
fragmentShader,
uniforms
);
}
gdjs.NightPixiFilter.prototype = Object.create(PIXI.Filter.prototype);
gdjs.NightPixiFilter.prototype = Object.create(PIXI.AbstractFilter.prototype);
gdjs.NightPixiFilter.prototype.constructor = gdjs.NightPixiFilter;
gdjs.LightNightPixiFilter = function() {
@@ -51,13 +51,13 @@ gdjs.LightNightPixiFilter = function() {
opacity: { type: '1f', value: 1 }
};
PIXI.Filter.call(this,
PIXI.AbstractFilter.call(this,
vertexShader,
fragmentShader,
uniforms
);
}
gdjs.LightNightPixiFilter.prototype = Object.create(PIXI.Filter.prototype);
gdjs.LightNightPixiFilter.prototype = Object.create(PIXI.AbstractFilter.prototype);
gdjs.LightNightPixiFilter.prototype.constructor = gdjs.LightNightPixiFilter;
gdjs.PixiFiltersTools._filters = {
@@ -70,7 +70,7 @@ gdjs.PixiFiltersTools._filters = {
if (parameterName !== 'intensity' &&
parameterName !== 'opacity') return;
filter.uniforms[parameterName] = value;
filter.uniforms[parameterName].value = value;
},
},
LightNight: {
@@ -81,7 +81,7 @@ gdjs.PixiFiltersTools._filters = {
updateParameter: function(filter, parameterName, value) {
if (parameterName !== 'opacity') return;
filter.uniforms.opacity = value;
filter.uniforms.opacity.value = value;
},
},
Sepia: {

File diff suppressed because one or more lines are too long

View File

@@ -258,29 +258,3 @@ gdjs.Polygon.distance = function(minA, maxA, minB, maxB)
if (minA < minB) return minB - maxA;
else return minA - maxB;
}
/**
* Check if a point is inside a polygon.
*
* Uses <a href="https://wrf.ecse.rpi.edu//Research/Short_Notes/pnpoly.html">PNPOLY</a> by W. Randolph Franklin.
*
* @method isPointInside
* @static
* @param poly {Polygon} The polygon to test
* @param x {Number} The point x coordinate
* @param y {Number} The point y coordinate
* @return {Boolean} true if the point is inside the polygon
*/
gdjs.Polygon.isPointInside = function(poly, x, y)
{
var inside = false;
var vi, vj;
for (var i = 0, j = poly.vertices.length-1; i < poly.vertices.length; j = i++) {
vi = poly.vertices[i];
vj = poly.vertices[j];
if ( ((vi[1]>y) != (vj[1]>y)) && (x < (vj[0]-vi[0]) * (y-vi[1]) / (vj[1]-vi[1]) + vi[0]) )
inside = !inside;
}
return inside;
};

View File

@@ -1202,23 +1202,6 @@ gdjs.RuntimeObject.prototype.cursorOnObject = function(runtimeScene) {
return false;
};
/**
* \brief Check if a point is inside the object collision hitboxes.
* @method isCollidingWithPoint
* @param pointX The point x coordinate.
* @param pointY The point y coordinate.
* @return true if the point is inside the object collision hitboxes.
*/
gdjs.RuntimeObject.prototype.isCollidingWithPoint = function(pointX, pointY) {
var hitBoxes = this.getHitBoxes();
for(var i = 0; i < this.hitBoxes.length; ++i) {
if ( gdjs.Polygon.isPointInside(hitBoxes[i], pointX, pointY) )
return true;
}
return false;
}
/**
* Get the identifier associated to an object name :<br>

View File

@@ -146,28 +146,6 @@ gdjs.RuntimeScene.prototype.unloadScene = function() {
for(var i = 0;i < gdjs.callbacksRuntimeSceneUnloaded.length;++i) {
gdjs.callbacksRuntimeSceneUnloaded[i](this);
}
// It should not be necessary to reset these variables, but this help
// ensuring that all memory related to the RuntimeScene is released immediately.
this._layers = new Hashtable();
this._variables = new gdjs.VariablesContainer();
this._initialBehaviorSharedData = new Hashtable();
this._objects = new Hashtable();
this._instances = new Hashtable();
this._instancesCache = new Hashtable();
this._initialObjectsData = null;
this._eventsFunction = null;
this._objectsCtor = new Hashtable();
this._allInstancesList = [];
this._instancesRemoved = [];
this._renderer = new gdjs.RuntimeSceneRenderer(this, this._runtimeGame ? this._runtimeGame.getRenderer() : null);
this._lastId = 0;
this._eventsContext = null;
this._isLoaded = false;
this.onCanvasResized();
};
/**

View File

@@ -1,4 +1,3 @@
#!/bin/bash
#Get the destination, or copy by default to release directory
DESTINATION=../../Binaries/Output/Release_Linux/JsPlatform/Runtime/
if [ "$(uname)" == "Darwin" ]; then
@@ -15,4 +14,4 @@ mkdir -p "$DESTINATION"
cp -R ../Runtime/* "$DESTINATION"
rsync -r -u --include=*.js --include=*/ --exclude=* ../../Extensions/ "$DESTINATION"/Extensions/
echo "✅ Copied GDJS and extensions runtime files (*.js) to '$DESTINATION'."
echo "✅ Copied GDJS and extensions runtime files (*.js) to '$DESTINATION'."

114
GDJS/tests/games/Text.gdg Normal file
View File

@@ -0,0 +1,114 @@
<?xml version="1.0" encoding="ISO-8859-1" ?>
<Project>
<GDVersion Major="3" Minor="0" Build="11297" Revision="57008" />
<Info winExecutableFilename="" winExecutableIconFile="" linuxExecutableFilename="" macExecutableFilename="" useExternalSourceFiles="false">
<Nom value="Projet" />
<Auteur value="" />
<Extensions>
<Extension name="BuiltinObject" />
<Extension name="BuiltinAudio" />
<Extension name="BuiltinVariables" />
<Extension name="BuiltinTime" />
<Extension name="BuiltinMouse" />
<Extension name="BuiltinKeyboard" />
<Extension name="BuiltinJoystick" />
<Extension name="BuiltinCamera" />
<Extension name="BuiltinWindow" />
<Extension name="BuiltinFile" />
<Extension name="BuiltinNetwork" />
<Extension name="BuiltinScene" />
<Extension name="BuiltinAdvanced" />
<Extension name="Sprite" />
<Extension name="BuiltinCommonInstructions" />
<Extension name="BuiltinCommonConversions" />
<Extension name="BuiltinStringInstructions" />
<Extension name="BuiltinMathematicalTools" />
<Extension name="BuiltinExternalLayouts" />
<Extension name="TextObject" />
</Extensions>
<Platforms current="GDevelop JS platform">
<Platform name="GDevelop JS platform" />
<Platform name="GDevelop C++ platform" />
</Platforms>
<WindowW value="800" />
<WindowH value="600" />
<Portable />
<LatestCompilationDirectory value="" />
<FPSmax value="60" />
<FPSmin value="10" />
<verticalSync value="false" />
</Info>
<Resources>
<Resources />
<ResourceFolders />
</Resources>
<Objects />
<ObjectGroups />
<Variables />
<Scenes firstScene="">
<Scene nom="Nouvelle sc<73>ne" mangledName="Nouvelle_32sc__4524ne" r="209.000000" v="209.000000" b="209.000000" titre="" oglFOV="90.000000" oglZNear="1.000000" oglZFar="500.000000" standardSortMethod="true" stopSoundsOnStartup="true" disableInputWhenNotFocused="true">
<UISettings gridWidth="32.000000" grid="false" snap="true" gridHeight="32.000000" gridR="158.000000" gridG="180.000000" gridB="255.000000" zoomFactor="1.000000" windowMask="true" associatedLayout="" />
<GroupesObjets />
<Objets>
<Objet nom="NouvelObjet" type="TextObject::Text" smoothed="true" bold="false" italic="false" underlined="false">
<Variables />
<String value="Heeello" />
<Font value="BOD_CB.TTF" />
<CharacterSize value="50" />
<Color r="255" g="128" b="0" />
</Objet>
</Objets>
<Layers>
<Layer Name="" Visibility="true">
<Camera DefaultSize="true" Width="0.000000" Height="0.000000" DefaultViewport="true" ViewportLeft="0.000000" ViewportTop="0.000000" ViewportRight="1.000000" ViewportBottom="1.000000" />
</Layer>
</Layers>
<Variables />
<BehaviorsSharedDatas />
<Positions>
<Objet nom="NouvelObjet" x="297.000000" y="245.000000" plan="1" layer="" angle="0.000000" personalizedSize="false" width="0.000000" height="0.000000" locked="false">
<floatInfos />
<stringInfos />
<InitialVariables />
</Objet>
</Positions>
<Events>
<Event disabled="false" folded="false">
<Type value="BuiltinCommonInstructions::Standard" />
<Conditions />
<Actions>
<Action>
<Type value="MettreXY" />
<Parametre value="NouvelObjet" />
<Parametre value="=" />
<Parametre value="300" />
<Parametre value="=" />
<Parametre value="300" />
</Action>
<Action>
<Type value="TextObject::Angle" />
<Parametre value="NouvelObjet" />
<Parametre value="+" />
<Parametre value="1" />
</Action>
<Action>
<Type value="TextObject::Opacity" />
<Parametre value="NouvelObjet" />
<Parametre value="=" />
<Parametre value="128+cos(TimeFromStart())*128" />
</Action>
<Action>
<Type value="TextObject::Size" />
<Parametre value="NouvelObjet" />
<Parametre value="=" />
<Parametre value="30+cos(TimeFromStart())*30" />
</Action>
</Actions>
</Event>
</Events>
</Scene>
</Scenes>
<ExternalEvents />
<ExternalLayouts />
<ExternalSourceFiles />
</Project>

View File

@@ -1 +0,0 @@
{"firstLayout": "","gdVersion": {"build": 96,"major": 4,"minor": 0,"revision": 89},"properties": {"folderProject": false,"linuxExecutableFilename": "","macExecutableFilename": "","packageName": "","projectFile": "/Users/florian/Projects/F/GD/GDJS/tests/games/Text.json","useExternalSourceFiles": false,"winExecutableFilename": "","winExecutableIconFile": "","name": "Projet","author": "","windowWidth": 800,"windowHeight": 600,"latestCompilationDirectory": "","maxFPS": 60,"minFPS": 10,"verticalSync": false,"extensions": [{"name": "BuiltinObject"},{"name": "BuiltinAudio"},{"name": "BuiltinVariables"},{"name": "BuiltinTime"},{"name": "BuiltinMouse"},{"name": "BuiltinKeyboard"},{"name": "BuiltinJoystick"},{"name": "BuiltinCamera"},{"name": "BuiltinWindow"},{"name": "BuiltinFile"},{"name": "BuiltinNetwork"},{"name": "BuiltinScene"},{"name": "BuiltinAdvanced"},{"name": "Sprite"},{"name": "BuiltinCommonInstructions"},{"name": "BuiltinCommonConversions"},{"name": "BuiltinStringInstructions"},{"name": "BuiltinMathematicalTools"},{"name": "BuiltinExternalLayouts"},{"name": "TextObject"}],"platforms": [{"name": "GDevelop JS platform"},{"name": "GDevelop C++ platform"}],"currentPlatform": "GDevelop C++ platform"},"resources": {"resources": [],"resourceFolders": []},"objects": [],"objectsGroups": [],"variables": [],"layouts": [{"b": 209,"disableInputWhenNotFocused": true,"mangledName": "Nouvelle_32sc_232ne","name": "Nouvelle scène","oglFOV": 90,"oglZFar": 500,"oglZNear": 1,"r": 209,"standardSortMethod": true,"stopSoundsOnStartup": true,"title": "","v": 209,"uiSettings": {"grid": false,"gridB": 255,"gridG": 180,"gridHeight": 32,"gridOffsetX": 0,"gridOffsetY": 0,"gridR": 158,"gridWidth": 32,"snap": true,"windowMask": true,"zoomFactor": 1},"objectsGroups": [],"variables": [],"instances": [{"angle": 0,"customSize": false,"height": 0,"layer": "","locked": false,"name": "NouvelObjet","width": 0,"x": 297,"y": 245,"zOrder": 1,"numberProperties": [],"stringProperties": [],"initialVariables": []}],"objects": [{"bold": false,"italic": false,"name": "NouvelObjet","smoothed": true,"type": "TextObject::Text","underlined": false,"variables": [],"behaviors": [],"string": "Heeello","font": "BOD_CB.TTF","characterSize": 50,"color": {"b": 0,"g": 128,"r": 255}}],"events": [{"disabled": false,"folded": false,"type": "BuiltinCommonInstructions::Standard","conditions": [],"actions": [{"type": {"inverted": false,"value": "MettreXY"},"parameters": ["NouvelObjet","=","300","=","300"],"subInstructions": []},{"type": {"inverted": false,"value": "TextObject::Angle"},"parameters": ["NouvelObjet","+","1"],"subInstructions": []},{"type": {"inverted": false,"value": "TextObject::Opacity"},"parameters": ["NouvelObjet","=","128+cos(TimeFromStart())*128"],"subInstructions": []},{"type": {"inverted": false,"value": "TextObject::Size"},"parameters": ["NouvelObjet","=","30+cos(TimeFromStart())*30"],"subInstructions": []}],"events": []}],"layers": [{"name": "","visibility": true,"cameras": [{"defaultSize": true,"defaultViewport": true,"height": 0,"viewportBottom": 1,"viewportLeft": 0,"viewportRight": 1,"viewportTop": 0,"width": 0}],"effects": []}],"behaviorsSharedData": []}],"externalEvents": [],"externalLayouts": [],"externalSourceFiles": []}

View File

@@ -2,7 +2,6 @@ cmake_minimum_required(VERSION 2.6)
cmake_policy(SET CMP0015 NEW)
project(GDVersion)
set(GD_VERSION_STR "4.0.96")
if(FULL_VERSION_NUMBER)
set(GENERATE_VERSION_SCRIPT ${PROJECT_SOURCE_DIR}/GenerateVersionFull.cmake)
@@ -12,5 +11,5 @@ endif()
add_custom_target(GDVersion
ALL
COMMAND ${CMAKE_COMMAND} -P ${GENERATE_VERSION_SCRIPT} ${PROJECT_SOURCE_DIR}/../Core/GDCore/Tools/ ${GD_VERSION_STR}
COMMAND ${CMAKE_COMMAND} -P ${GENERATE_VERSION_SCRIPT} ${PROJECT_SOURCE_DIR}/../Core/GDCore/Tools/
)

View File

@@ -1,19 +1,13 @@
find_package(Git)
if(GIT_FOUND)
# Retrieving GDevelop version from Git tags is disabled as GDevelop 5
# is being developed and we still want the old IDE version to stay at 4.
# The old IDE has the same version number as GDCore/libGD.js
# Hence, version of GDevelop 4 is manually specified in CMakeLists.txt
# EXECUTE_PROCESS(
# COMMAND ${GIT_EXECUTABLE} describe --tags
# OUTPUT_VARIABLE GD_VERSION_STR
# RESULT_VARIABLE GIT_DESCRIBE_RESULT
# ERROR_VARIABLE GIT_DESCRIBE_ERROR
# OUTPUT_STRIP_TRAILING_WHITESPACE
# )
set(GD_VERSION_STR ${CMAKE_ARGV4})
EXECUTE_PROCESS(
COMMAND ${GIT_EXECUTABLE} describe --tags
OUTPUT_VARIABLE GD_VERSION_STR
RESULT_VARIABLE GIT_DESCRIBE_RESULT
ERROR_VARIABLE GIT_DESCRIBE_ERROR
OUTPUT_STRIP_TRAILING_WHITESPACE
)
set(VERSIONPRIV_PATH "${CMAKE_ARGV3}/VersionPriv.h")
set(ORIGINAL_CONTENT " ")

View File

@@ -3,19 +3,13 @@ find_package(Git)
message(WARNING "You're not using the full version number. It's not suitable for public releases and builds!")
if(GIT_FOUND)
# Retrieving GDevelop version from Git tags is disabled as GDevelop 5
# is being developed and we still want the old IDE version to stay at 4.
# The old IDE has the same version number as GDCore/libGD.js
# Hence, version of GDevelop 4 is manually specified in CMakeLists.txt
# EXECUTE_PROCESS(
# COMMAND ${GIT_EXECUTABLE} describe --tags --abbrev=0 # Only get the lastest tag's name
# OUTPUT_VARIABLE GD_VERSION_STR
# RESULT_VARIABLE GIT_DESCRIBE_RESULT
# ERROR_VARIABLE GIT_DESCRIBE_ERROR
# OUTPUT_STRIP_TRAILING_WHITESPACE
# )
set(GD_VERSION_STR ${CMAKE_ARGV4})
EXECUTE_PROCESS(
COMMAND ${GIT_EXECUTABLE} describe --tags --abbrev=0 # Only get the lastest tag's name
OUTPUT_VARIABLE GD_VERSION_STR
RESULT_VARIABLE GIT_DESCRIBE_RESULT
ERROR_VARIABLE GIT_DESCRIBE_ERROR
OUTPUT_STRIP_TRAILING_WHITESPACE
)
set(VERSIONPRIV_PATH "${CMAKE_ARGV3}/VersionPriv.h")
set(ORIGINAL_CONTENT " ")

View File

@@ -61,29 +61,18 @@ cd newIDE/app
yarn test #or npm run test
```
### Theming
It's possible to create new themes for the UI. See [this file](https://github.com/4ian/GD/blob/master/newIDE/app/src/UI/Theme/index.js) to declare a new theme. You can take a look at the [default theme](https://github.com/4ian/GD/blob/master/newIDE/app/src/UI/Theme/DefaultTheme/index.js), including the [styling of the Events Sheets](https://github.com/4ian/GD/blob/master/newIDE/app/src/UI/Theme/DefaultTheme/EventsSheet.css).
## Building and deploying the standalone app
### Desktop version
First, update version number which is read in `newIDE/electron-app/app/package.json`.
```bash
cd newIDE/electron-app
yarn build #or npm run build
```
This will build and package the Electron app for Windows, macOS and Linux (according to your OS). The output are stored inside `newIDE/electron-app/dist`.
To build artifacts for all platforms and publish to a draft GitHub release:
```
GH_TOKEN=xxx yarn build --mac --win --linux tar.gz --publish always
```
This will build and package the Electron app for Windows, macOS and Linux (according to your OS).
The output are stored inside `newIDE/electron-app/dist` and copied to `Binaries/Output/Release_XXX`.
Version number is read from `newIDE/electron-app/app/package.json`.
### Webapp version
@@ -97,6 +86,7 @@ yarn deploy #or npm run deploy
This new editor is still in development and is missing some features:
- [ ] Support for translations (See an [example of a component that can be translated](https://github.com/4ian/GD/blob/master/newIDE/app/src/MainFrame/Toolbar.js#L44))
- [ ] [Autocompletion of expressions and parameters in Events editor](https://trello.com/c/mAROBTR8/46-expression-editor-auto-complete-for-the-new-ide).
- [ ] [Collision mask editor](https://trello.com/c/2Kzwj61r/47-collision-masks-editors-for-sprite-objects-in-the-new-ide)
- [ ] Support for native games
- [ ] More [documentation](http://wiki.compilgames.net/doku.php/gdevelop5/start) about how to package for iOS/Android with Cordova/PhoneGap Build or Cocos2d-JS.

View File

@@ -1,7 +1,5 @@
[ignore]
<PROJECT_ROOT>/resources/.*
<PROJECT_ROOT>/node_modules/auth0-lock/node_modules/fbjs/flow/include/PromiseMap.js
<PROJECT_ROOT>/node_modules/protobufjs/src/bower.json
[include]

View File

@@ -5,7 +5,6 @@ node_modules
# testing
coverage
flow-coverage
# production
build
@@ -24,4 +23,3 @@ public/res
public/CppPlatform
public/JsPlatform
resources/GDJS

View File

@@ -1,18 +0,0 @@
// @flow
//TODO: These types could be generated from GDevelop.js instead of being
//manually written here.
type EmscriptenObject = Object & {
ptr: Number
};
declare type gdProject = EmscriptenObject;
declare type gdLayout = EmscriptenObject;
declare type gdExternalLayout = EmscriptenObject;
declare type gdExternalEvents = EmscriptenObject;
declare type gdSerializerElement = EmscriptenObject;
declare type gdInitialInstance = EmscriptenObject;
declare type gdBaseEvent = EmscriptenObject;
//Represents all objects that have serializeTo and unserializeFrom methods.
declare type gdSerializable = EmscriptenObject;

View File

@@ -1,94 +0,0 @@
// flow-typed signature: 6c6a5771bbdffe188d60637063b5f9a4
// flow-typed version: 6533cd10ce/react-i18next_v6.x.x/flow_>=v0.53.x
declare module "react-i18next" {
declare type TFunction = (key?: ?string, data?: ?Object) => string;
declare type Locales = string | Array<string>;
declare type TranslatorProps = {
t: TFunction,
i18nLoadedAt: Date,
i18n: Object
};
declare type Translator<OP, P> = (
component: React$ComponentType<P>
) => Class<React$Component<OP, *>>;
declare type TranslateOptions = $Shape<{
wait: boolean,
nsMode: "default" | "fallback",
bindi18n: false | string,
bindStore: false | string,
withRef: boolean,
translateFuncName: string,
i18n: Object
}>;
declare function translate<OP, P>(
locales?: Locales,
options?: TranslateOptions
): Translator<OP, P>;
declare type I18nProps = {
i18n?: Object,
ns?: string | Array<string>,
children: (t: TFunction, { i18n: Object, t: TFunction }) => React$Node,
initialI18nStore?: Object,
initialLanguage?: string
};
declare var I18n: React$ComponentType<I18nProps>;
declare type InterpolateProps = {
className?: string,
dangerouslySetInnerHTMLPartElement?: string,
i18n?: Object,
i18nKey?: string,
options?: Object,
parent?: string,
style?: Object,
t?: TFunction,
useDangerouslySetInnerHTML?: boolean
};
declare var Interpolate: React$ComponentType<InterpolateProps>;
declare type TransProps = {
count?: number,
parent?: string,
i18n?: Object,
i18nKey?: string,
t?: TFunction
};
declare var Trans: React$ComponentType<TransProps>;
declare type ProviderProps = { i18n: Object, children: React$Element<*> };
declare var I18nextProvider: React$ComponentType<ProviderProps>;
declare type NamespacesProps = {
components: Array<React$ComponentType<*>>,
i18n: { loadNamespaces: Function }
};
declare function loadNamespaces(props: NamespacesProps): Promise<void>;
declare var reactI18nextModule: {
type: "3rdParty",
init: (instance: Object) => void
};
declare var defaultOptions: {
wait: false,
withRef: false,
bindI18n: "languageChanged loaded",
bindStore: "added removed",
translateFuncName: "t",
nsMode: "default"
};
declare function setDefaults(options: TranslateOptions): void;
declare function getDefaults(): TranslateOptions;
declare function getI18n(): Object;
declare function setI18n(instance: Object): void;
}

View File

@@ -5,74 +5,62 @@
"license": "MIT",
"homepage": ".",
"devDependencies": {
"flow-bin": "^0.61.0",
"flow-coverage-report": "^0.4.0",
"flow-bin": "^0.58.0",
"follow-redirects": "^1.2.3",
"prettier": "1.7.0",
"react-scripts": "1.0.17",
"react-scripts": "1.0.6",
"shelljs": "^0.7.7"
},
"dependencies": {
"@storybook/react": "3.2.19",
"@storybook/react": "3.2.14",
"aws-sdk": "^2.100.0",
"axios": "^0.16.1",
"blueimp-md5": "^2.10.0",
"classnames": "2.2.5",
"date-fns": "^1.29.0",
"element-closest": "2.0.2",
"firebase": "^4.8.2",
"classnames": "^2.2.5",
"cursores": "^1.0.1",
"element-closest": "^2.0.2",
"flat": "2.0.1",
"fontfaceobserver": "2.0.13",
"fontfaceobserver": "^2.0.13",
"i18next": "^10.0.3",
"keen-tracking": "1.1.3",
"lodash": "4.17.4",
"material-ui": "0.20",
"material-ui-search-bar": "0.4.1",
"material-ui": "0.18.3",
"material-ui-search-bar": "0.4.0",
"pixi-simple-gesture": "0.2.2",
"pixi.js": "3.0.11",
"prop-types": "^15.5.10",
"raven-js": "^3.19.1",
"react": "^16.2.0",
"react-color": "2.13.8",
"react-dnd": "2.5.4",
"react-dnd-html5-backend": "2.5.4",
"react-dom": "^16.2.0",
"react-error-boundary": "^1.2.0",
"react-i18next": "6.2.0",
"react-measure": "1.4.7",
"react": "15.4.2",
"react-addons-css-transition-group": "15.4.2",
"react-addons-perf": "15.4.2",
"react-color": "2.11.7",
"react-dnd": "2.3.0",
"react-dnd-html5-backend": "2.3.0",
"react-dom": "15.4.2",
"react-i18next": "^6.0.6",
"react-measure": "1.4.6",
"react-mosaic-component": "1.0.3",
"react-sortable-hoc": "0.6.8",
"react-sortable-tree": "1.5.3",
"react-test-renderer": "16.2.0",
"react-virtualized": "9.14.1",
"slugs": "0.1.3",
"react-sortable-hoc": "^0.6.3",
"react-sortable-tree": "^0.1.21",
"react-tap-event-plugin": "2.0.1",
"react-test-renderer": "15.4.2",
"react-virtualized": "9.3.0",
"slugs": "^0.1.3",
"source-map-explorer": "^1.4.0",
"wait-promise": "0.4.1"
"wait-promise": "^0.4.1"
},
"scripts": {
"postinstall": "npm run import-resources",
"import-resources": "cd scripts && node import-libGD.js && node import-res-folder.js && node import-GDJS-Runtime.js",
"start": "npm run import-resources && react-scripts start",
"build": "npm run import-resources && react-scripts build",
"postinstall": "npm run import-resources",
"format": "prettier --write \"src/**/*.js\"",
"test": "react-scripts test --env=jsdom",
"flow": "flow",
"analyze-source-map": "source-map-explorer build/static/js/main.*",
"storybook": "start-storybook -p 9009 -s public",
"analyze-test-coverage": "react-scripts test --env=jsdom --coverage",
"analyze-flow-coverage": "flow-coverage-report",
"analyze-source-map": "source-map-explorer build/static/js/main.*"
"build-storybook": "build-storybook -s public"
},
"eslintConfig": {
"extends": "react-app"
},
"flow-coverage-report": {
"includeGlob": [
"src/**/*.js"
],
"type": [
"text",
"html",
"json"
]
}
}

View File

@@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<title>GDevelop 5</title>
@@ -43,10 +43,6 @@
';path=/;expires='+new Date(0).toUTCString();i=d.indexOf('.');if(i<0)break;d=d.slice(i+1)}}};
})(window,document,window['_fs_namespace'],'script','user');
</script>
<!-- Stripe.com Checkout -->
<script src="https://checkout.stripe.com/checkout.js"></script>
</head>
<body>
<!-- Root div used for React `App` component rendering-->

File diff suppressed because one or more lines are too long

View File

@@ -65,12 +65,7 @@ export default class NewBehaviorDialog extends Component {
if (!open || !project) return null;
const actions = [
<FlatButton
key="close"
label="Close"
primary={false}
onClick={onClose}
/>,
<FlatButton key="close" label="Close" primary={false} onClick={onClose} />,
];
return (

View File

@@ -0,0 +1,10 @@
export const selectableArea = 'selectable';
export const selectedArea = 'selected';
export const largeSelectableArea = 'large-selectable';
export const largeSelectedArea = 'large-selected';
export const background = 'background';
export const container = 'gd-events-sheet';
export const eventsTree = 'events-tree';

View File

@@ -1,16 +0,0 @@
// @flow
import * as React from 'react';
import PlaceholderMessage from '../UI/PlaceholderMessage';
import HelpButton from '../UI/HelpButton';
const EmptyEventsPlaceholder = () => (
<PlaceholderMessage>
<p>
There are no events here. Events are composed of conditions and actions.
</p>
<p>Add your first event using the first buttons of the toolbar.</p>
<HelpButton helpPagePath="/events" />
</PlaceholderMessage>
);
export default EmptyEventsPlaceholder;

View File

@@ -6,7 +6,6 @@ import ForEachEvent from './Renderers/ForEachEvent';
import RepeatEvent from './Renderers/RepeatEvent';
import WhileEvent from './Renderers/WhileEvent';
import LinkEvent from './Renderers/LinkEvent';
import JsCodeEvent from './Renderers/JsCodeEvent';
export default {
components: {
@@ -18,7 +17,6 @@ export default {
'BuiltinCommonInstructions::Repeat': RepeatEvent,
'BuiltinCommonInstructions::While': WhileEvent,
'BuiltinCommonInstructions::Link': LinkEvent,
'BuiltinCommonInstructions::JsCode': JsCodeEvent,
},
getEventComponent: function(event) {
if (this.components.hasOwnProperty(event.getType()))

View File

@@ -1,15 +0,0 @@
export const selectableArea = 'selectable';
export const selectedArea = 'selected';
export const largeSelectableArea = 'large-selectable';
export const largeSelectedArea = 'large-selected';
export const executableEventContainer = 'executable-event-container';
export const actionsContainer = 'actions-container';
export const conditionsContainer = 'conditions-container';
export const subInstructionsContainer = 'sub-instructions-container';
export const instructionParameter = 'instruction-parameter';
export const background = 'background';
export const eventsTree = 'events-tree';

View File

@@ -1,50 +0,0 @@
// @flow
type WatchedComponent = {
onHeightsChanged: (Function) => void
};
/**
* Store the height of events and notify a component whenever
* heights have changed.
* Needed for EventsTree as we need to tell it when heights have changed
* so it can recompute the internal row heights of the react-virtualized List.
*/
export default class EventHeightsCache {
eventHeights = {};
updateTimeoutId: ?number = null;
component: ?WatchedComponent = null;
constructor(component: WatchedComponent) {
this.component = component;
}
_notifyComponent() {
if (this.updateTimeoutId) {
return; // An update is already scheduled.
}
// Notify the component, on the next tick, that heights have changed
this.updateTimeoutId = setTimeout(() => {
if (this.component) {
this.component.onHeightsChanged(() => (this.updateTimeoutId = null));
} else {
this.updateTimeoutId = null;
}
}, 0);
}
setEventHeight(event: gdBaseEvent, height: number) {
const cachedHeight = this.eventHeights[event.ptr];
if (!cachedHeight || cachedHeight !== height) {
// console.log(event.ptr, 'has a new height', height, 'old:', cachedHeight);
this._notifyComponent();
}
this.eventHeights[event.ptr] = height;
}
getEventHeight(event: gdBaseEvent): number {
return this.eventHeights[event.ptr] || 60;
}
}

View File

@@ -1,192 +0,0 @@
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import classNames from 'classnames';
import InlinePopover from '../../InlinePopover';
import ObjectField from '../../InstructionEditor/ParameterFields/ObjectField';
import {
largeSelectedArea,
largeSelectableArea,
selectableArea,
} from '../ClassNames';
import { getHelpLink } from '../../../Utils/HelpLink';
import Window from '../../../Utils/Window';
const gd = global.gd;
const fontFamily = '"Lucida Console", Monaco, monospace';
const styles = {
container: {
minHeight: 30,
display: 'flex',
flexDirection: 'column',
backgroundColor: 'white',
},
wrappingText: {
fontFamily,
paddingLeft: 5,
paddingRight: 5,
margin: 0,
},
text: {
flex: 1,
whiteSpace: 'pre-line',
margin: 0,
paddingLeft: 4 * 5,
paddingRight: 5,
fontFamily,
},
textArea: {
paddingLeft: 4 * 5,
paddingRight: 5,
flex: 1,
boxSizing: 'border-box',
width: '100%',
fontSize: 14,
fontFamily,
},
comment: {
color: '#777',
},
commentLink: {
cursor: 'pointer',
color: '#777',
textDecoration: 'underline',
},
};
export default class JsCodeEvent extends Component {
state = {
editing: false,
anchorEl: null,
};
edit = () => {
this.setState(
{
editing: true,
height: this._container.offsetHeight,
},
() => {
const input = ReactDOM.findDOMNode(this._input);
input.focus();
input.value = gd.asJsCodeEvent(this.props.event).getInlineCode();
}
);
};
endEditing = () => {
const jsCodeEvent = gd.asJsCodeEvent(this.props.event);
jsCodeEvent.setInlineCode(ReactDOM.findDOMNode(this._input).value);
this.setState(
{
editing: false,
},
() => this.props.onUpdate()
);
};
editObject = domEvent => {
this.setState({
editingObject: true,
anchorEl: domEvent.currentTarget,
});
};
endObjectEditing = () => {
this.setState({
editingObject: false,
anchorEl: null,
});
};
openHelp = () => {
Window.openExternalURL(getHelpLink('/events/js-code'));
};
render() {
const jsCodeEvent = gd.asJsCodeEvent(this.props.event);
const parameterObjects = jsCodeEvent.getParameterObjects();
const objects = (
<span
className={classNames({
[selectableArea]: true,
})}
onClick={this.editObject}
>
{parameterObjects
? `, objects /*${parameterObjects}*/`
: ' /* No objects selected, only pass the scene as argument */'}
</span>
);
const functionStart = (
<p style={styles.wrappingText}>
<span>{'(function(runtimeScene'}</span>
{objects}
<span>{') {'}</span>
</p>
);
const functionEnd = (
<p style={styles.wrappingText}>
<span>{'})(runtimeScene'}</span>
{objects}
<span>{');'}</span>
<span style={styles.comment}>
{' // '}
<a onClick={this.openHelp} style={styles.commentLink}>
Read the documentation and help
</a>
</span>
</p>
);
return (
<div
style={styles.container}
className={classNames({
[largeSelectableArea]: true,
[largeSelectedArea]: this.props.selected,
})}
ref={container => (this._container = container)}
>
{functionStart}
{!this.state.editing ? (
<p
className={classNames({
[selectableArea]: true,
})}
onClick={this.edit}
key="p"
style={styles.text}
>
{jsCodeEvent.getInlineCode()}
</p>
) : (
<textarea
key="textarea"
type="text"
style={{ ...styles.textArea, height: this.state.height }}
onBlur={this.endEditing}
ref={input => (this._input = input)}
/>
)}
{functionEnd}
<InlinePopover
open={this.state.editingObject}
anchorEl={this.state.anchorEl}
onRequestClose={this.endObjectEditing}
>
<ObjectField
project={this.props.project}
layout={this.props.layout}
value={parameterObjects}
onChange={text => {
jsCodeEvent.setParameterObjects(text);
this.props.onUpdate();
}}
isInline
/>
</InlinePopover>
</div>
);
}
}

View File

@@ -1,19 +1,60 @@
import React, { Component } from 'react';
import muiThemeable from 'material-ui/styles/muiThemeable';
import findIndex from 'lodash/findIndex';
import {
SortableTreeWithoutDndContext,
SortableTreeWithoutDndContext as SortableTree,
getNodeAtPath,
} from 'react-sortable-tree';
import EventsRenderingService from '../EventsRenderingService';
import { mapFor } from '../../Utils/MapFor';
import { eventsTree } from '../ClassNames';
import findIndex from 'lodash/findIndex';
import { getInitialSelection, isEventSelected } from '../SelectionHandler';
import EventsRenderingService from './EventsRenderingService';
import EventHeightsCache from './EventHeightsCache';
import { eventsTree } from './ClassNames';
import './style.css';
const indentWidth = 22;
/**
* Store the height of events and notify a component whenever
* heights have changed.
* Needed for EventsTree as we need to tell it when heights have changed
* so it can recompute the internal row heights of the react-virtualized List.
*/
class EventHeightsCache {
eventHeights = {};
component = null;
constructor(component) {
this.component = component;
}
_notifyComponent() {
if (this.updateTimeoutId) {
return; // An update is already scheduled.
}
// Notify the component, on the next tick, that heights have changed
this.updateTimeoutId = setTimeout(() => {
if (this.component) {
this.component.onHeightsChanged(() => (this.updateTimeoutId = null));
} else {
this.updateTimeoutId = null;
}
}, 0);
}
setEventHeight(event, height) {
const cachedHeight = this.eventHeights[event.ptr];
if (!cachedHeight || cachedHeight !== height) {
// console.log(event.ptr, 'has a new height', height, 'old:', cachedHeight);
this._notifyComponent();
}
this.eventHeights[event.ptr] = height;
}
getEventHeight(event) {
return this.eventHeights[event.ptr] || 60;
}
}
/**
* The component containing an event.
* It will report the rendered event height so that the EventsTree can
@@ -66,8 +107,6 @@ class EventContainer extends Component {
this.props.onInstructionsListContextMenu
}
onParameterClick={this.props.onParameterClick}
onOpenExternalEvents={this.props.onOpenExternalEvents}
onOpenLayout={this.props.onOpenLayout}
/>
)}
</div>
@@ -77,19 +116,11 @@ class EventContainer extends Component {
const getNodeKey = ({ treeIndex }) => treeIndex;
const ThemableSortableTree = ({ muiTheme, ...otherProps }) => (
<SortableTreeWithoutDndContext
className={`${eventsTree} ${muiTheme.eventsSheetRootClassName}`}
{...otherProps}
/>
);
const SortableTree = muiThemeable()(ThemableSortableTree);
/**
* Display a tree of event. Builtin on react-sortable-tree so that event
* can be drag'n'dropped and events rows are virtualized.
*/
export default class ThemableEventsTree extends Component {
export default class EventsTree extends Component {
static defaultProps = {
selection: getInitialSelection(),
};
@@ -112,7 +143,7 @@ export default class ThemableEventsTree extends Component {
*/
onHeightsChanged(cb) {
this.forceUpdate(() => {
if (this._list) this._list.wrappedInstance.recomputeRowHeights();
this._list.wrappedInstance.recomputeRowHeights();
if (cb) cb();
});
}
@@ -123,14 +154,14 @@ export default class ThemableEventsTree extends Component {
*/
forceEventsUpdate(cb) {
this.setState(this._eventsToTreeData(this.props.events), () => {
if (this._list) this._list.wrappedInstance.recomputeRowHeights();
this._list.wrappedInstance.recomputeRowHeights();
if (cb) cb();
});
}
scrollToEvent(event) {
const row = this._getEventRow(event);
if (row !== -1 && this._list) this._list.wrappedInstance.scrollToRow(row);
if (row !== -1) this._list.wrappedInstance.scrollToRow(row);
}
_getEventRow(searchedEvent) {
@@ -231,18 +262,15 @@ export default class ThemableEventsTree extends Component {
onEventContextMenu={(x, y) => this.props.onEventContextMenu(x, y, node)}
onInstructionContextMenu={this.props.onInstructionContextMenu}
onInstructionsListContextMenu={this.props.onInstructionsListContextMenu}
onOpenExternalEvents={this.props.onOpenExternalEvents}
onOpenLayout={this.props.onOpenLayout}
/>
);
};
render() {
const { height } = this.props;
return (
<div style={{ height: height || 400 }}>
<div style={{ height: this.props.height || 400 }}>
<SortableTree
className={eventsTree}
treeData={this.state.treeData}
scaffoldBlockPxWidth={indentWidth}
onChange={() => {}}
@@ -251,8 +279,6 @@ export default class ThemableEventsTree extends Component {
canDrop={this._canDrop}
rowHeight={({ index }) => {
const event = this.state.flatData[index];
if (!event) return 0;
return this.eventsHeightsCache.getEventHeight(event);
}}
reactVirtualizedListProps={{

View File

@@ -4,10 +4,14 @@ import ParameterRenderingService from './InstructionEditor/ParameterRenderingSer
const gd = global.gd;
export default class InlineParameterEditor extends Component {
state = {
isValid: false,
parameterMetadata: null,
};
constructor(props) {
super(props);
this.state = {
isValid: false,
parameterMetadata: null,
};
}
componentWillReceiveProps(newProps) {
if (

View File

@@ -1,8 +1,8 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { mapFor } from '../../Utils/MapFor';
import { mapFor } from '../Utils/MapFor';
import classNames from 'classnames';
import { selectedArea, selectableArea, subInstructionsContainer, instructionParameter } from './ClassNames';
import { selectedArea, selectableArea } from './ClassNames';
import InstructionsList from './InstructionsList';
const gd = global.gd;
const instrFormatter = gd.InstructionSentenceFormatter.get();
@@ -20,6 +20,12 @@ const styles = {
paddingLeft: 2,
paddingRight: 2,
},
subInstructionsList: {
marginLeft: 9,
marginTop: 1,
borderRight: 'none',
borderLeft: '1px solid #d3d3d3',
},
};
export default class Instruction extends Component {
@@ -55,20 +61,29 @@ export default class Instruction extends Component {
{mapFor(0, formattedTexts.size(), i => {
const formatting = formattedTexts.getTextFormatting(i);
const parameterIndex = formatting.getUserData();
const isParameter =
parameterIndex >= 0 && parameterIndex < parametersCount;
if (!isParameter)
return <span key={i}>{formattedTexts.getString(i)}</span>;
const parameterType = metadata.getParameter(parameterIndex).getType();
return (
<span
key={i}
style={{
color:
'rgb(' +
formatting.getColorRed() +
',' +
formatting.getColorGreen() +
',' +
formatting.getColorBlue() +
')',
fontWeight: formatting.isBold() ? 'bold' : 'normal',
fontStyle: formatting.isItalic() ? 'italic' : 'normal',
}}
className={classNames({
[selectableArea]: true,
[instructionParameter]: true,
[parameterType]: true,
})}
onClick={domEvent =>
this.props.onParameterClick(domEvent, parameterIndex)}
@@ -134,7 +149,7 @@ export default class Instruction extends Component {
{this._renderInstructionText(metadata)}
{metadata.canHaveSubInstructions() && (
<InstructionsList
extraClassName={subInstructionsContainer}
style={styles.subInstructionsList}
instrsList={instruction.getSubInstructions()}
areConditions={this.props.isCondition}
selection={this.props.selection}

View File

@@ -30,13 +30,13 @@ export default class InstructionEditorDialog extends React.Component {
<FlatButton
label="Cancel"
primary={false}
onClick={this.props.onCancel}
onTouchTap={this.props.onCancel}
/>,
<FlatButton
label="Ok"
primary={true}
keyboardFocused={false}
onClick={this.props.onSubmit}
onTouchTap={this.props.onSubmit}
/>,
];

View File

@@ -15,21 +15,19 @@ export const createTree = (
allExpressions: Array<InstructionOrExpressionInformation>
): InstructionOrExpressionTreeNode => {
const tree = {};
allExpressions.forEach(
(expressionInfo: InstructionOrExpressionInformation) => {
update(
tree,
compact(expressionInfo.fullGroupName.split(GROUP_DELIMITER)),
groupInfo => {
const existingGroupInfo = groupInfo || {};
return {
...existingGroupInfo,
[expressionInfo.displayedName]: expressionInfo,
};
}
);
}
);
allExpressions.forEach((expressionInfo: InstructionOrExpressionInformation) => {
update(
tree,
compact(expressionInfo.fullGroupName.split(GROUP_DELIMITER)),
groupInfo => {
const existingGroupInfo = groupInfo || {};
return {
...existingGroupInfo,
[expressionInfo.displayedName]: expressionInfo,
};
}
);
});
return tree;
};

View File

@@ -50,7 +50,7 @@ const enumerateExtensionExpressions = (
return allExpressions;
};
export const enumerateExpressions = type => {
export const enumerateExpressions = (type) => {
const freeExpressions = [];
const objectsExpressions = [];
const behaviorsExpressions = [];
@@ -85,10 +85,7 @@ export const enumerateExpressions = type => {
//Free expressions
freeExpressions.push.apply(
freeExpressions,
enumerateExtensionExpressions(
prefix,
allFreeExpressionsGetter.call(extension)
)
enumerateExtensionExpressions(prefix, allFreeExpressionsGetter.call(extension))
);
//Objects expressions:
@@ -137,13 +134,14 @@ export const enumerateExpressions = type => {
};
export const filterExpressions = (list, searchText) => {
if (!searchText) return list;
const lowercaseSearchText = searchText.toLowerCase();
if (!searchText) return list;
const lowercaseSearchText = searchText.toLowerCase();
return list.filter(enumeratedExpression => {
return (
enumeratedExpression.type.toLowerCase().indexOf(lowercaseSearchText) !==
-1
);
});
};
return list.filter(enumeratedExpression => {
return (
enumeratedExpression.type
.toLowerCase()
.indexOf(lowercaseSearchText) !== -1
);
});
}

View File

@@ -15,5 +15,5 @@ describe('EnumerateInstructions', () => {
it('can create the tree of instructions', () => {
const instructions = enumerateInstructions('number');
expect(createTree(instructions)).toMatchSnapshot();
});
})
});

View File

@@ -1,13 +1,14 @@
// @flow
import React, { Component } from 'react';
import { enumerateExpressions } from './EnumerateExpressions';
import InstructionOrExpressionSelector from './index';
import { InstructionOrExpressionSelector } from './index';
import { createTree, type InstructionOrExpressionTreeNode } from './CreateTree';
import { type InstructionOrExpressionInformation } from './InstructionOrExpressionInformation.flow.js';
export default class ExpressionSelector extends Component<*, *> {
_selector: any = null;
instructionsInfo: Array<InstructionOrExpressionInformation> = [];
instructionsInfoTree: ?InstructionOrExpressionTreeNode = null;
instructionsInfoTree: InstructionOrExpressionTreeNode = null;
static defaultProps = {
expressionType: 'number',
@@ -19,9 +20,14 @@ export default class ExpressionSelector extends Component<*, *> {
this.instructionsInfoTree = createTree(allExpressions);
}
focus = () => {
if (this._selector) this._selector.focus();
};
render() {
return (
<InstructionOrExpressionSelector
ref={selector => (this._selector = selector)}
instructionsInfo={this.instructionsInfo}
instructionsInfoTree={this.instructionsInfoTree}
{...this.props}

View File

@@ -1,5 +1,3 @@
//@flow
export type InstructionOrExpressionInformation = {
type: string,
name?: string, //For expressions

View File

@@ -1,6 +1,6 @@
// @flow
import React, { Component } from 'react';
import InstructionOrExpressionSelector from './index';
import { InstructionOrExpressionSelector } from './index';
import { createTree, type InstructionOrExpressionTreeNode } from './CreateTree';
import { enumerateInstructions } from './EnumerateInstructions';
import { type InstructionOrExpressionInformation } from './InstructionOrExpressionInformation.flow.js';
@@ -11,8 +11,9 @@ type Props = {
};
export default class InstructionSelector extends Component<Props, *> {
_selector: any = null;
instructionsInfo: Array<InstructionOrExpressionInformation> = [];
instructionsInfoTree: ?InstructionOrExpressionTreeNode = null;
instructionsInfoTree: InstructionOrExpressionTreeNode = null;
componentWillMount() {
const allInstructions = enumerateInstructions(this.props.isCondition);
@@ -20,9 +21,14 @@ export default class InstructionSelector extends Component<Props, *> {
this.instructionsInfoTree = createTree(allInstructions);
}
focus = () => {
if (this._selector) this._selector.focus();
};
render() {
return (
<InstructionOrExpressionSelector
ref={selector => (this._selector = selector)}
instructionsInfo={this.instructionsInfo}
instructionsInfoTree={this.instructionsInfoTree}
{...this.props}

View File

@@ -133,11 +133,6 @@ Object {
"fullGroupName": "Common conditions for all objects/Collision",
"type": "CollisionNP",
},
"Point inside object": Object {
"displayedName": "Point inside object",
"fullGroupName": "Common conditions for all objects/Collision",
"type": "CollisionPoint",
},
},
"Layer": Object {
"Compare layer": Object {
@@ -1768,11 +1763,6 @@ Array [
"fullGroupName": "Common conditions for all objects/Behaviors",
"type": "BehaviorActivated",
},
Object {
"displayedName": "Point inside object",
"fullGroupName": "Common conditions for all objects/Collision",
"type": "CollisionPoint",
},
Object {
"displayedName": "Compare layer",
"fullGroupName": "Common conditions for all objects/Layer",

View File

@@ -2,7 +2,6 @@ import React, { Component } from 'react';
import { List, ListItem, makeSelectable } from 'material-ui/List';
import SearchBar from 'material-ui-search-bar';
import keys from 'lodash/keys';
import muiThemeable from 'material-ui/styles/muiThemeable';
const SelectableList = makeSelectable(List);
const styles = {
@@ -10,12 +9,18 @@ const styles = {
margin: '0 auto',
backgroundColor: 'transparent',
},
groupListItemText: {
color: 'rgba(0,0,0,0.54)',
},
groupListItem: {
borderBottom: '1px solid #f0f0f0', //TODO: Use theme color instead
},
groupListItemNestedList: {
padding: 0,
},
};
class ThemableInstructionOrExpressionSelector extends Component {
export class InstructionOrExpressionSelector extends Component {
state = {
search: '',
searchResults: [],
@@ -56,8 +61,6 @@ class ThemableInstructionOrExpressionSelector extends Component {
};
_renderTree(instructionInfoTree) {
const { muiTheme } = this.props;
return Object.keys(instructionInfoTree).map(key => {
const instructionOrGroup = instructionInfoTree[key];
@@ -76,9 +79,9 @@ class ThemableInstructionOrExpressionSelector extends Component {
return (
<ListItem
key={key}
style={{borderBottom: `1px solid ${muiTheme.listItem.separatorColor}`}}
style={styles.groupListItem}
nestedListStyle={styles.groupListItemNestedList}
primaryText={<div style={{color: muiTheme.listItem.groupTextColor}}>{key}</div>}
primaryText={<div style={styles.groupListItemText}>{key}</div>}
primaryTogglesNestedList={true}
autoGenerateNestedIndicator={true}
nestedItems={this._renderTree(instructionOrGroup)}
@@ -106,15 +109,8 @@ class ThemableInstructionOrExpressionSelector extends Component {
};
render() {
const { muiTheme, selectedType, instructionsInfoTree, style } = this.props;
return (
<div
style={{
backgroundColor: muiTheme.list.itemsBackgroundColor,
...style,
}}
>
<div style={this.props.style}>
<SearchBar
onChange={text =>
this.setState({
@@ -125,17 +121,16 @@ class ThemableInstructionOrExpressionSelector extends Component {
style={styles.searchBar}
ref={searchBar => (this._searchBar = searchBar)}
/>
<SelectableList value={selectedType}>
<SelectableList
value={this.props.selectedType}
>
{this.state.search
? this._renderSearchResults()
: this._renderTree(instructionsInfoTree)}
: this._renderTree(this.props.instructionsInfoTree)}
</SelectableList>
</div>
);
}
}
const InstructionOrExpressionSelector = muiThemeable()(
ThemableInstructionOrExpressionSelector
);
export default InstructionOrExpressionSelector;

View File

@@ -29,20 +29,15 @@ export default class ExpressionParametersEditorDialog extends Component<
};
componentWillMount() {
this.setState({
this.state = {
parameterValues: Array(
this.props.expressionMetadata.getParametersCount()
).fill(''),
});
};
}
render() {
const {
project,
layout,
expressionMetadata,
parameterRenderingService,
} = this.props;
const { project, layout, expressionMetadata, parameterRenderingService } = this.props;
return (
<Dialog

View File

@@ -20,8 +20,6 @@ export const formatExpressionCall = (
expressionInfo: InstructionOrExpressionInformation,
parameterValues: ParameterValues
): string => {
const functionName = expressionInfo.name || '';
if (expressionInfo.objectMetadata) {
const [objectName, ...otherParameters] = parameterValues;
@@ -29,7 +27,7 @@ export const formatExpressionCall = (
otherParameters,
expressionInfo.metadata
).join(', ');
return `${objectName}.${functionName}(${functionArgs})`;
return `${objectName}.${expressionInfo.name}(${functionArgs})`;
} else if (expressionInfo.behaviorMetadata) {
const [objectName, behaviorName, ...otherParameters] = parameterValues;
@@ -37,12 +35,12 @@ export const formatExpressionCall = (
otherParameters,
expressionInfo.metadata
).join(', ');
return `${objectName}.${behaviorName}::${functionName}(${functionArgs})`;
return `${objectName}.${behaviorName}::${expressionInfo.name}(${functionArgs})`;
} else {
const functionArgs = filterOutCodeOnlyParameters(
parameterValues,
expressionInfo.metadata
).join(', ');
return `${functionName}(${functionArgs})`;
return `${expressionInfo.name}(${functionArgs})`;
}
};

View File

@@ -25,13 +25,10 @@ describe('HelpButton', () => {
});
it('properly format a free function, with "code-only" parameters', () => {
const cameraHeightExpression = filterExpressions(
freeExpressions,
'CameraHeight'
)[0];
expect(
formatExpressionCall(cameraHeightExpression, ['', '"My layer"', '0'])
).toBe('CameraHeight("My layer", 0)');
const cameraHeightExpression = filterExpressions(freeExpressions, 'CameraHeight')[0];
expect(formatExpressionCall(cameraHeightExpression, ['', '"My layer"', "0"])).toBe(
'CameraHeight("My layer", 0)'
);
});
it('properly format an object function', () => {

View File

@@ -20,8 +20,9 @@ const styles = {
flex: 1,
},
expressionSelector: {
maxHeight: 350,
maxHeight: 250,
overflowY: 'scroll',
backgroundColor: 'white',
},
input: {
fontFamily: '"Lucida Console", Monaco, monospace',

View File

@@ -11,6 +11,7 @@ const styles = {
typeSelector: {
flex: 1,
overflowY: 'scroll',
backgroundColor: 'white',
},
parametersEditor: {
flex: 2,
@@ -22,6 +23,10 @@ const styles = {
};
export default class InstructionEditor extends Component {
componentDidMount() {
if (this._typeSelector) this._typeSelector.focus();
}
render() {
const { instruction, isCondition, project, layout } = this.props;
@@ -35,6 +40,7 @@ export default class InstructionEditor extends Component {
instruction.setType(type);
this.forceUpdate();
}}
ref={typeSelector => (this._typeSelector = typeSelector)}
/>
<Paper style={styles.parametersEditor} rounded={false} zDepth={2}>
<InstructionParametersEditor

View File

@@ -1,11 +1,20 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Instruction from './Instruction';
import { mapFor } from '../../Utils/MapFor';
import { isInstructionSelected } from '../SelectionHandler';
import { actionsContainer, conditionsContainer } from './ClassNames';
import Instruction from './Instruction.js';
import { mapFor } from '../Utils/MapFor';
import { isInstructionSelected } from './SelectionHandler';
const styles = {
conditionsContainer: {
paddingLeft: 5,
paddingRight: 5,
background: '#f1f2f2',
borderRight: '1px solid #d3d3d3',
},
actionsContainer: {
paddingLeft: 5,
paddingRight: 5,
},
addButton: {
cursor: 'pointer',
},
@@ -34,26 +43,16 @@ export default class InstructionsList extends Component {
};
render() {
const {
addButtonLabel,
areConditions,
extraClassName,
instrsList,
onAddNewInstruction,
onInstructionClick,
onInstructionContextMenu,
onInstructionDoubleClick,
onInstructionsListContextMenu,
onParameterClick,
selection,
style,
} = this.props;
const instructionsListContext = {
isCondition: this.props.areConditions,
instrsList: this.props.instrsList,
};
const instructions = mapFor(0, instrsList.size(), i => {
const instruction = instrsList.get(i);
const instructions = mapFor(0, this.props.instrsList.size(), i => {
const instruction = this.props.instrsList.get(i);
const instructionContext = {
isCondition: areConditions,
instrsList: instrsList,
isCondition: this.props.areConditions,
instrsList: this.props.instrsList,
instruction,
indexInList: i,
};
@@ -61,49 +60,44 @@ export default class InstructionsList extends Component {
return (
<Instruction
instruction={instruction}
isCondition={areConditions}
instrsList={instrsList}
isCondition={this.props.areConditions}
instrsList={this.props.instrsList}
index={i}
key={instruction.ptr}
selected={isInstructionSelected(selection, instruction)}
onClick={() => onInstructionClick(instructionContext)}
selected={isInstructionSelected(this.props.selection, instruction)}
onClick={() => this.props.onInstructionClick(instructionContext)}
onDoubleClick={() =>
onInstructionDoubleClick(instructionContext)}
this.props.onInstructionDoubleClick(instructionContext)}
onContextMenu={(x, y) =>
onInstructionContextMenu(x, y, instructionContext)}
this.props.onInstructionContextMenu(x, y, instructionContext)}
onParameterClick={(domEvent, parameterIndex) =>
onParameterClick({
this.props.onParameterClick({
...instructionContext,
parameterIndex,
domEvent,
})}
selection={selection}
onAddNewSubInstruction={onAddNewInstruction}
onSubInstructionClick={onInstructionClick}
onSubInstructionDoubleClick={onInstructionDoubleClick}
onSubInstructionContextMenu={onInstructionContextMenu}
selection={this.props.selection}
onAddNewSubInstruction={this.props.onAddNewInstruction}
onSubInstructionClick={this.props.onInstructionClick}
onSubInstructionDoubleClick={this.props.onInstructionDoubleClick}
onSubInstructionContextMenu={this.props.onInstructionContextMenu}
onSubInstructionsListContextMenu={
onInstructionsListContextMenu
this.props.onInstructionsListContextMenu
}
onSubParameterClick={onParameterClick}
onSubParameterClick={this.props.onParameterClick}
/>
);
});
const instructionsListContext = {
isCondition: areConditions,
instrsList: instrsList,
};
const addButtonDefaultLabel = areConditions
const containerStyle = this.props.areConditions
? styles.conditionsContainer
: styles.actionsContainer;
const addButtonLabel = this.props.areConditions
? 'Add condition'
: 'Add action';
return (
<div
className={`${areConditions
? conditionsContainer
: actionsContainer} ${extraClassName || ''}`}
style={style}
>
<div style={{ ...containerStyle, ...this.props.style }}>
{instructions}
<a
style={styles.addButton}
@@ -111,14 +105,14 @@ export default class InstructionsList extends Component {
onClick={this.onAddNewInstruction}
onContextMenu={e => {
e.stopPropagation();
onInstructionsListContextMenu(
this.props.onInstructionsListContextMenu(
e.clientX,
e.clientY,
instructionsListContext
);
}}
>
{addButtonLabel || addButtonDefaultLabel}
{this.props.addButtonLabel || addButtonLabel}
</a>
</div>
);

View File

@@ -2,7 +2,7 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import ReactDOM from 'react-dom';
import classNames from 'classnames';
import { rgbToHex } from '../../../Utils/ColorTransformer';
import { rgbToHex } from '../../Utils/ColorTransformer';
import {
largeSelectedArea,
largeSelectableArea,
@@ -37,9 +37,13 @@ export default class CommentEvent extends Component {
onUpdate: PropTypes.func.isRequired,
};
state = {
editing: false,
};
constructor(props) {
super(props);
this.state = {
editing: false,
};
}
edit = () => {
this.setState(

View File

@@ -6,16 +6,16 @@ import {
largeSelectedArea,
largeSelectableArea,
selectableArea,
executableEventContainer,
} from '../ClassNames';
import InlinePopover from '../../InlinePopover';
import ObjectField from '../../InstructionEditor/ParameterFields/ObjectField';
import InlinePopover from '../InlinePopover';
import ObjectField from '../InstructionEditor/ParameterFields/ObjectField';
const gd = global.gd;
const styles = {
container: {
display: 'flex',
flexDirection: 'column',
borderBottom: '1px solid #d3d3d3',
},
instructionsContainer: {
display: 'flex',
@@ -75,7 +75,6 @@ export default class ForEachEvent extends Component {
className={classNames({
[largeSelectableArea]: true,
[largeSelectedArea]: this.props.selected,
[executableEventContainer]: true,
})}
>
<div

View File

@@ -11,7 +11,7 @@ const gd = global.gd;
const styles = {
container: {
height: 40,
height: 60,
display: 'flex',
alignItems: 'center',
padding: 5,

View File

@@ -1,16 +1,13 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import OpenInNew from 'material-ui/svg-icons/action/open-in-new';
import IconButton from 'material-ui/IconButton';
import classNames from 'classnames';
import {
largeSelectedArea,
largeSelectableArea,
selectableArea,
} from '../ClassNames';
import InlinePopover from '../../InlinePopover';
import ExternalEventsField from '../../InstructionEditor/ParameterFields/ExternalEventsField';
import { showWarningBox } from '../../../UI/Messages/MessageBox';
import InlinePopover from '../InlinePopover';
import ExternalEventsField from '../InstructionEditor/ParameterFields/ExternalEventsField';
const gd = global.gd;
const styles = {
@@ -42,31 +39,12 @@ export default class LinkEvent extends Component {
}
edit = domEvent => {
this.setState(
{
editing: true,
anchorEl: domEvent.currentTarget,
},
() => {
if (this._externalEventsField) this._externalEventsField.focus();
}
);
};
openTarget = () => {
const { project, event, onOpenLayout, onOpenExternalEvents } = this.props;
const linkEvent = gd.asLinkEvent(event);
const target = linkEvent.getTarget();
if (project.hasExternalEventsNamed(target)) {
onOpenExternalEvents(target);
} else if (project.hasLayoutNamed(target)) {
onOpenLayout(target);
} else {
showWarningBox(
'The specified external events do not exist in the game. Be sure that the name is correctly spelled or create them using the project manager.'
);
}
this.setState({
editing: true,
anchorEl: domEvent.currentTarget,
}, () => {
if (this._externalEventsField) this._externalEventsField.focus();
});
};
endEditing = () => {
@@ -77,7 +55,7 @@ export default class LinkEvent extends Component {
};
render() {
const linkEvent = gd.asLinkEvent(this.props.event);
var linkEvent = gd.asLinkEvent(this.props.event);
const target = linkEvent.getTarget();
return (
@@ -99,9 +77,6 @@ export default class LinkEvent extends Component {
{target || '< Enter the name of external events >'}
</i>
</span>
<IconButton onClick={this.openTarget} disabled={!target}>
<OpenInNew />
</IconButton>
<InlinePopover
open={this.state.editing}
anchorEl={this.state.anchorEl}
@@ -115,8 +90,7 @@ export default class LinkEvent extends Component {
this.props.onUpdate();
}}
isInline
ref={externalEventsField =>
(this._externalEventsField = externalEventsField)}
ref={externalEventsField => this._externalEventsField = externalEventsField}
/>
</InlinePopover>
</div>

View File

@@ -6,16 +6,16 @@ import {
largeSelectedArea,
largeSelectableArea,
selectableArea,
executableEventContainer,
} from '../ClassNames';
import InlinePopover from '../../InlinePopover';
import DefaultField from '../../InstructionEditor/ParameterFields/DefaultField';
import InlinePopover from '../InlinePopover';
import DefaultField from '../InstructionEditor/ParameterFields/DefaultField';
const gd = global.gd;
const styles = {
container: {
display: 'flex',
flexDirection: 'column',
borderBottom: '1px solid #d3d3d3',
},
instructionsContainer: {
display: 'flex',
@@ -75,7 +75,6 @@ export default class RepeatEvent extends Component {
className={classNames({
[largeSelectableArea]: true,
[largeSelectedArea]: this.props.selected,
[executableEventContainer]: true,
})}
>
<div

View File

@@ -1,17 +1,14 @@
import React, { Component } from 'react';
import InstructionsList from '../InstructionsList';
import InstructionsList from '../InstructionsList.js';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import {
largeSelectedArea,
largeSelectableArea,
executableEventContainer,
} from '../ClassNames';
import { largeSelectedArea, largeSelectableArea } from '../ClassNames';
const gd = global.gd;
const styles = {
container: {
display: 'flex',
borderBottom: '1px solid #d3d3d3',
},
actionsList: {
flex: 1,
@@ -43,7 +40,6 @@ export default class StandardEvent extends Component {
className={classNames({
[largeSelectableArea]: true,
[largeSelectedArea]: this.props.selected,
[executableEventContainer]: true,
})}
>
<InstructionsList

View File

@@ -1,18 +1,15 @@
import React, { Component } from 'react';
import InstructionsList from '../InstructionsList';
import InstructionsList from '../InstructionsList.js';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import {
largeSelectedArea,
largeSelectableArea,
executableEventContainer,
} from '../ClassNames';
import { largeSelectedArea, largeSelectableArea } from '../ClassNames';
const gd = global.gd;
const styles = {
container: {
display: 'flex',
flexDirection: 'column',
borderBottom: '1px solid #d3d3d3',
},
instructionsContainer: {
display: 'flex',
@@ -48,7 +45,6 @@ export default class ForEachEvent extends Component {
className={classNames({
[largeSelectableArea]: true,
[largeSelectedArea]: this.props.selected,
[executableEventContainer]: true,
})}
>
<div>While these conditions are true:</div>

View File

@@ -70,13 +70,12 @@ export class Toolbar extends PureComponent {
tooltip={t('Choose and add an event')}
/>
}
buildMenuTemplate={() =>
this.allEventsMetadata.map(metadata => {
return {
label: metadata.fullName,
click: () => this.props.onAddEvent(metadata.type),
};
})}
menuTemplate={this.allEventsMetadata.map(metadata => {
return {
label: metadata.fullName,
click: () => this.props.onAddEvent(metadata.type),
};
})}
/>
<ToolbarSeparator />
<ToolbarIcon

View File

@@ -1,6 +1,8 @@
import React, { Component } from 'react';
import EventsTree from './EventsTree';
import InstructionEditorDialog from './InstructionEditor/InstructionEditorDialog';
import '../UI/Theme/EventsSheet.css';
import { container } from './ClassNames';
import Toolbar from './Toolbar';
import KeyboardShortcuts from '../UI/KeyboardShortcuts';
import { passFullSize } from '../UI/FullSizeMeasurer';
@@ -35,7 +37,6 @@ import {
getSelectedInstructionsListsContexts,
selectInstructionsList,
} from './SelectionHandler';
import EmptyEventsPlaceholder from './EmptyEventsPlaceholder';
const gd = global.gd;
const CLIPBOARD_KIND = 'EventsAndInstructions';
@@ -189,20 +190,17 @@ export default class EventsSheet extends Component {
this.state.editedInstruction.instruction.delete();
}
this.setState(
{
editedInstruction: {
isCondition: true,
instruction: null,
instrsList: null,
},
this.setState({
editedInstruction: {
isCondition: true,
instruction: null,
instrsList: null,
},
() => {
if (saveChanges) {
this._saveChangesToHistory();
}
}, () => {
if (saveChanges) {
this._saveChangesToHistory();
}
);
});
}
selectEvent = eventContext => {
@@ -456,18 +454,12 @@ export default class EventsSheet extends Component {
};
render() {
const {
project,
layout,
events,
onOpenExternalEvents,
onOpenLayout,
} = this.props;
const { project, layout, events } = this.props;
if (!project) return null;
return (
<div
className="gd-events-sheet"
className={container}
onFocus={() => this._keyboardShortcuts.focus()}
onBlur={() => this._keyboardShortcuts.blur()}
tabIndex={1}
@@ -489,10 +481,7 @@ export default class EventsSheet extends Component {
onEventContextMenu={this.openEventContextMenu}
onAddNewEvent={context =>
this.addNewEvent('BuiltinCommonInstructions::Standard', context)}
onOpenExternalEvents={onOpenExternalEvents}
onOpenLayout={onOpenLayout}
/>
{events && events.getEventsCount() === 0 && <EmptyEventsPlaceholder />}
<InlineParameterEditor
open={this.state.inlineEditing}
anchorEl={this.state.inlineEditingAnchorEl}
@@ -510,7 +499,7 @@ export default class EventsSheet extends Component {
/>
<ContextMenu
ref={eventContextMenu => (this.eventContextMenu = eventContextMenu)}
buildMenuTemplate={() => [
menuTemplate={[
{
label: 'Copy',
click: () => this.copySelection(),
@@ -524,7 +513,6 @@ export default class EventsSheet extends Component {
{
label: 'Paste',
click: () => this.pasteEvents(),
enabled: Clipboard.has(CLIPBOARD_KIND),
accelerator: 'CmdOrCtrl+V',
},
{ type: 'separator' },
@@ -537,13 +525,11 @@ export default class EventsSheet extends Component {
{
label: 'Undo',
click: this.undo,
enabled: canUndo(this.state.history),
accelerator: 'CmdOrCtrl+Z',
},
{
label: 'Redo',
click: this.redo,
enabled: canRedo(this.state.history),
accelerator: 'CmdOrCtrl+Shift+Z',
},
]}
@@ -551,7 +537,7 @@ export default class EventsSheet extends Component {
<ContextMenu
ref={instructionContextMenu =>
(this.instructionContextMenu = instructionContextMenu)}
buildMenuTemplate={() => [
menuTemplate={[
{
label: 'Copy',
click: () => this.copySelection(),
@@ -565,7 +551,6 @@ export default class EventsSheet extends Component {
{
label: 'Paste',
click: () => this.pasteInstructions(),
enabled: Clipboard.has(CLIPBOARD_KIND),
accelerator: 'CmdOrCtrl+V',
},
{ type: 'separator' },
@@ -578,13 +563,11 @@ export default class EventsSheet extends Component {
{
label: 'Undo',
click: this.undo,
enabled: canUndo(this.state.history),
accelerator: 'CmdOrCtrl+Z',
},
{
label: 'Redo',
click: this.redo,
enabled: canRedo(this.state.history),
accelerator: 'CmdOrCtrl+Shift+Z',
},
]}
@@ -592,24 +575,21 @@ export default class EventsSheet extends Component {
<ContextMenu
ref={instructionsListContextMenu =>
(this.instructionsListContextMenu = instructionsListContextMenu)}
buildMenuTemplate={() => [
menuTemplate={[
{
label: 'Paste',
click: () => this.pasteInstructions(),
enabled: Clipboard.has(CLIPBOARD_KIND),
accelerator: 'CmdOrCtrl+V',
},
{ type: 'separator' },
{
label: 'Undo',
click: this.undo,
enabled: canUndo(this.state.history),
accelerator: 'CmdOrCtrl+Z',
},
{
label: 'Redo',
click: this.redo,
enabled: canRedo(this.state.history),
accelerator: 'CmdOrCtrl+Shift+Z',
},
]}

View File

@@ -1,7 +1,7 @@
import React, { Component } from 'react';
import RaisedButton from 'material-ui/RaisedButton';
import { Column, Line } from '../../UI/Grid';
import Window from '../../Utils/Window';
import { Column, Line } from '../UI/Grid';
import Window from '../Utils/Window';
export default class BrowserExport extends Component {
openWebsite = () => {
@@ -19,7 +19,7 @@ export default class BrowserExport extends Component {
</Line>
<Line>
<RaisedButton
onClick={this.openWebsite}
onTouchTap={this.openWebsite}
primary
label="Download GDevelop"
/>

View File

@@ -1,60 +0,0 @@
import * as React from 'react';
import BrowserExport from './BrowserExport';
import PhoneIphone from 'material-ui/svg-icons/hardware/phone-iphone';
import LaptopMac from 'material-ui/svg-icons/hardware/laptop-mac';
import Folder from 'material-ui/svg-icons/file/folder';
import Devices from 'material-ui/svg-icons/device/devices';
export const getBrowserExporters = () => [
{
name: 'Android (& iOS coming soon)',
renderIcon: props => <PhoneIphone {...props} />,
description:
'Package your game for Android directly from GDevelop. iOS support is coming soon!',
key: 'localonlinecordovaexport',
ExportComponent: BrowserExport,
},
{
name: 'Web (upload online)',
renderIcon: props => <Devices {...props} />,
description:
'Upload your game online directly from GDevelop and share the link to players. Play to your game using your browser on computers and mobile phones.',
key: 'locals3export',
ExportComponent: BrowserExport,
},
{
name: 'Local folder',
renderIcon: props => <Folder {...props} />,
description:
'Build the game locally as a HTML5 game. You can then export it on website like Itch.io or Kongregate.',
key: 'localexport',
ExportComponent: BrowserExport,
advanced: true,
},
{
name: 'iOS & Android (manual)',
renderIcon: props => <PhoneIphone {...props} />,
description:
'Build the game locally as a Cordova project, and export it manually then to iOS or Android with Cordova developers tools.',
key: 'localcordovaexport',
ExportComponent: BrowserExport,
advanced: true,
},
{
name: 'Windows/macOS/Linux (coming soon)',
renderIcon: props => <LaptopMac {...props} />,
description: 'Package your game as an app for Windows, macOs or Linux.',
disabled: true,
key: 'localonlineelectronexport',
ExportComponent: BrowserExport,
},
{
name: 'Cocos2d-JS',
renderIcon: props => <PhoneIphone {...props} />,
description:
'Export your game using Cocos2d-JS game engine. The game can be compiled for Android or iOS if you install Cocos2d-JS developer tools.',
key: 'localcocos2dexport',
ExportComponent: BrowserExport,
experimental: true,
},
];

View File

@@ -1,8 +1,8 @@
import React, { Component } from 'react';
import Dialog from '../../UI/Dialog';
import Dialog from '../UI/Dialog';
import FlatButton from 'material-ui/FlatButton';
import { showErrorBox } from '../../UI/Messages/MessageBox';
import { Column, Line } from '../../UI/Grid';
import { showErrorBox } from '../UI/Messages/MessageBox';
import { Column, Line } from '../UI/Grid';
export default class BrowserPreviewLinkDialog extends Component {
constructor(props) {
@@ -31,7 +31,7 @@ export default class BrowserPreviewLinkDialog extends Component {
<FlatButton
label="Launch the preview"
primary
onClick={this._onOpen}
onTouchTap={this._onOpen}
/>,
];

View File

@@ -4,27 +4,32 @@ import BrowserS3FileSystem from './BrowserS3FileSystem';
import BrowserPreviewLinkDialog from './BrowserPreviewLinkDialog';
import { findGDJS } from './BrowserS3GDJSFinder';
import assignIn from 'lodash/assignIn';
import { GDevelopGamesPreview } from '../../Utils/GDevelopServices/ApiConfigs';
const awsS3 = require('aws-sdk/clients/s3');
const gd = global.gd;
const {
destinationBucket,
accessKeyId,
secretAccessKey,
region,
destinationBucketBaseUrl,
} = GDevelopGamesPreview;
const awsS3 = require('aws-sdk/clients/s3');
const destinationBucket = `gd-games-preview`;
const accessKeyId = process.env.REACT_APP_PREVIEW_S3_ACCESS_KEY_ID;
const secretAccessKey = process.env.REACT_APP_PREVIEW_S3_SECRET_ACCESS_KEY;
const region = 'eu-west-1';
const destinationBucketBaseUrl = `https://s3-${region}.amazonaws.com/${destinationBucket}/`;
if (!accessKeyId || !secretAccessKey) {
console.warn(
"Either REACT_APP_PREVIEW_S3_ACCESS_KEY_ID or REACT_APP_PREVIEW_S3_SECRET_ACCESS_KEY are not defined. Preview in browsers won't be working"
);
console.info(
'Copy .env.dist file to .env and fill the values to fix this warning.'
);
}
const awsS3Client = new awsS3({
accessKeyId: accessKeyId,
secretAccessKey: secretAccessKey,
region: region,
correctClockSkew: true,
});
export default class BrowserS3PreviewLauncher {
static _openPreviewWindow = (project: gdProject, url: string): any => {
static _openPreviewWindow = (project, url): any => {
const windowObjectReference = window.open(url, `_blank`);
return {
url,
@@ -68,10 +73,7 @@ export default class BrowserS3PreviewLauncher {
});
};
static launchLayoutPreview = (
project: gdProject,
layout: gdLayout
): Promise<any> => {
static launchLayoutPreview = (project, layout): Promise<any> => {
if (!project || !layout) return Promise.reject();
return BrowserS3PreviewLauncher._prepareExporter().then(
@@ -97,9 +99,9 @@ export default class BrowserS3PreviewLauncher {
};
static launchExternalLayoutPreview = (
project: gdProject,
layout: gdLayout,
externalLayout: gdExternalLayout
project,
layout,
externalLayout
): Promise<any> => {
return Promise.reject('Not implemented');
};

View File

@@ -2,26 +2,28 @@ import React, { Component } from 'react';
import Dialog from '../UI/Dialog';
import HelpButton from '../UI/HelpButton';
import FlatButton from 'material-ui/FlatButton';
import Subheader from 'material-ui/Subheader';
import { List, ListItem } from 'material-ui/List';
import { Tabs, Tab } from 'material-ui/Tabs';
import Visibility from 'material-ui/svg-icons/action/visibility';
import VisibilityOff from 'material-ui/svg-icons/action/visibility-off';
const styles = {
icon: { width: 40, height: 40 },
disabledItem: { opacity: 0.6 },
content: { padding: 24 },
content: {
padding: 0,
},
tabContent: {
padding: 24,
},
};
export default class ExportDialog extends Component {
state = {
chosenExporterKey: '',
value: 0,
showExperimental: false,
};
chooseExporter = key => {
_onChangeTab = value => {
this.setState({
chosenExporterKey: key,
value,
});
};
@@ -31,118 +33,53 @@ export default class ExportDialog extends Component {
});
};
_renderExporterListItem = (exporter, index) => {
return (
<ListItem
key={exporter.key}
disabled={exporter.disabled}
style={exporter.disabled ? styles.disabledItem : undefined}
leftAvatar={exporter.renderIcon({ style: styles.icon })}
primaryText={exporter.name}
secondaryText={<p>{exporter.description}</p>}
secondaryTextLines={2}
onClick={() => this.chooseExporter(exporter.key)}
/>
);
};
render() {
const {
project,
open,
onClose,
authentification,
onChangeSubscription,
exporters,
} = this.props;
const { showExperimental, chosenExporterKey } = this.state;
const { project, open, onClose } = this.props;
const { showExperimental } = this.state;
if (!open || !project) return null;
const exporter = exporters.find(
exporter => exporter.key === chosenExporterKey
);
return (
<Dialog
title="Export project to a standalone game"
onRequestClose={onClose}
actions={[
chosenExporterKey && (
actions={
<FlatButton label="Close" primary={false} onTouchTap={onClose} />
}
secondaryActions={[
<HelpButton key="help" helpPagePath="/publishing" />,
!showExperimental ? (
<FlatButton
label="Back"
key="back"
key="toggle-experimental"
icon={<Visibility />}
primary={false}
onClick={() => this.chooseExporter('')}
onTouchTap={() => this._showExperimental(true)}
label="Show experimental exports"
/>
) : (
<FlatButton
key="toggle-experimental"
icon={<VisibilityOff />}
primary={false}
onTouchTap={() => this._showExperimental(false)}
label="Hide experimental exports"
/>
),
<FlatButton
label="Close"
key="close"
primary={false}
onClick={onClose}
/>,
]}
secondaryActions={[
<HelpButton
key="help"
helpPagePath={(exporter && exporter.helpPage) || '/publishing'}
/>,
!chosenExporterKey &&
(!showExperimental ? (
<FlatButton
key="toggle-experimental"
icon={<Visibility />}
primary={false}
onClick={() => this._showExperimental(true)}
label="Show experimental exports"
/>
) : (
<FlatButton
key="toggle-experimental"
icon={<VisibilityOff />}
primary={false}
onClick={() => this._showExperimental(false)}
label="Hide experimental exports"
/>
)),
]}
open={open}
noMargin
autoScrollBodyContent
>
{!exporter && (
<List>
{exporters
.filter(exporter => !exporter.advanced && !exporter.experimental)
.map((exporter, index) =>
this._renderExporterListItem(exporter, index)
)}
<Subheader>Advanced</Subheader>
{exporters
.filter(exporter => exporter.advanced)
.map((exporter, index) =>
this._renderExporterListItem(exporter, index)
)}
{showExperimental && <Subheader>Experimental</Subheader>}
{showExperimental &&
exporters
.filter(exporter => exporter.experimental)
.map((exporter, index) =>
this._renderExporterListItem(exporter, index)
)}
</List>
)}
{exporter && (
<div style={styles.content}>
<exporter.ExportComponent
project={project}
authentification={authentification}
onChangeSubscription={onChangeSubscription}
/>
</div>
)}
<Tabs value={this.state.value} onChange={this._onChangeTab}>
{this.props.tabs.map(
({ ExportComponent, name, advanced }, index) =>
(!advanced || showExperimental) && (
<Tab label={name} value={index} key={index}>
<div style={styles.tabContent}>
<ExportComponent project={this.props.project} />
</div>
</Tab>
)
)}
</Tabs>
</Dialog>
);
}

View File

@@ -1,17 +1,17 @@
import React, { Component } from 'react';
import Dialog from '../../UI/Dialog';
import Dialog from '../UI/Dialog';
import FlatButton from 'material-ui/FlatButton';
import RaisedButton from 'material-ui/RaisedButton';
import Toggle from 'material-ui/Toggle';
import { sendExportLaunched } from '../../Utils/Analytics/EventSender';
import { Column, Line, Spacer } from '../../UI/Grid';
import HelpButton from '../../UI/HelpButton';
import { showErrorBox } from '../../UI/Messages/MessageBox';
import { sendExportLaunched } from '../Utils/Analytics/EventSender';
import { Column, Line, Spacer } from '../UI/Grid';
import HelpButton from '../UI/HelpButton';
import { showErrorBox } from '../UI/Messages/MessageBox';
import { findGDJS } from './LocalGDJSFinder';
import localFileSystem from './LocalFileSystem';
import LocalFolderPicker from '../../UI/LocalFolderPicker';
import LocalFolderPicker from '../UI/LocalFolderPicker';
import assignIn from 'lodash/assignIn';
import optionalRequire from '../../Utils/OptionalRequire';
import optionalRequire from '../Utils/OptionalRequire';
const electron = optionalRequire('electron');
const shell = electron ? electron.shell : null;
@@ -114,7 +114,7 @@ export default class LocalCocos2dExport extends Component {
<RaisedButton
label="Export"
primary={true}
onClick={this.launchExport}
onTouchTap={this.launchExport}
disabled={!this.state.outputDir}
/>
</Line>
@@ -125,13 +125,13 @@ export default class LocalCocos2dExport extends Component {
key="open"
label="Open folder"
primary={true}
onClick={this.openExportFolder}
onTouchTap={this.openExportFolder}
/>,
<FlatButton
key="close"
label="Close"
primary={false}
onClick={() =>
onTouchTap={() =>
this.setState({
exportFinishedDialogOpen: false,
})}

View File

@@ -1,17 +1,17 @@
import React, { Component } from 'react';
import Dialog from '../../UI/Dialog';
import Dialog from '../UI/Dialog';
import FlatButton from 'material-ui/FlatButton';
import RaisedButton from 'material-ui/RaisedButton';
import { sendExportLaunched } from '../../Utils/Analytics/EventSender';
import { Column, Line, Spacer } from '../../UI/Grid';
import { showErrorBox } from '../../UI/Messages/MessageBox';
import { sendExportLaunched } from '../Utils/Analytics/EventSender';
import { Column, Line, Spacer } from '../UI/Grid';
import { showErrorBox } from '../UI/Messages/MessageBox';
import { findGDJS } from './LocalGDJSFinder';
import localFileSystem from './LocalFileSystem';
import LocalFolderPicker from '../../UI/LocalFolderPicker';
import HelpButton from '../../UI/HelpButton';
import LocalFolderPicker from '../UI/LocalFolderPicker';
import HelpButton from '../UI/HelpButton';
import assignIn from 'lodash/assignIn';
import optionalRequire from '../../Utils/OptionalRequire';
import Window from '../../Utils/Window';
import optionalRequire from '../Utils/OptionalRequire';
import Window from '../Utils/Window';
const electron = optionalRequire('electron');
const shell = electron ? electron.shell : null;
@@ -120,7 +120,7 @@ export default class LocalCordovaExport extends Component {
<RaisedButton
label="Export"
primary={true}
onClick={this.launchExport}
onTouchTap={this.launchExport}
disabled={!this.state.outputDir}
/>
</Line>
@@ -131,13 +131,13 @@ export default class LocalCordovaExport extends Component {
key="open"
label="Open folder"
primary={true}
onClick={this.openExportFolder}
onTouchTap={this.openExportFolder}
/>,
<FlatButton
key="close"
label="Close"
primary={false}
onClick={() =>
onTouchTap={() =>
this.setState({
exportFinishedDialogOpen: false,
})}
@@ -161,7 +161,7 @@ export default class LocalCordovaExport extends Component {
<RaisedButton
fullWidth
primary
onClick={() => this.openPhoneGapBuild()}
onTouchTap={() => this.openPhoneGapBuild()}
label="Open PhoneGap Build"
/>
</Dialog>

View File

@@ -1,17 +1,17 @@
import React, { Component } from 'react';
import Dialog from '../../UI/Dialog';
import Dialog from '../UI/Dialog';
import FlatButton from 'material-ui/FlatButton';
import RaisedButton from 'material-ui/RaisedButton';
import { sendExportLaunched } from '../../Utils/Analytics/EventSender';
import { Column, Line, Spacer } from '../../UI/Grid';
import { showErrorBox } from '../../UI/Messages/MessageBox';
import { sendExportLaunched } from '../Utils/Analytics/EventSender';
import { Column, Line, Spacer } from '../UI/Grid';
import { showErrorBox } from '../UI/Messages/MessageBox';
import { findGDJS } from './LocalGDJSFinder';
import localFileSystem from './LocalFileSystem';
import LocalFolderPicker from '../../UI/LocalFolderPicker';
import LocalFolderPicker from '../UI/LocalFolderPicker';
import assignIn from 'lodash/assignIn';
import optionalRequire from '../../Utils/OptionalRequire';
import Window from '../../Utils/Window';
import { getHelpLink } from '../../Utils/HelpLink';
import optionalRequire from '../Utils/OptionalRequire';
import Window from '../Utils/Window';
import { getHelpLink } from '../Utils/HelpLink';
const electron = optionalRequire('electron');
const shell = electron ? electron.shell : null;
@@ -115,7 +115,7 @@ export default class LocalExport extends Component {
<RaisedButton
label="Export"
primary={true}
onClick={this.launchExport}
onTouchTap={this.launchExport}
disabled={!this.state.outputDir}
/>
</Line>
@@ -126,13 +126,13 @@ export default class LocalExport extends Component {
key="open"
label="Open folder"
primary={true}
onClick={this.openExportFolder}
onTouchTap={this.openExportFolder}
/>,
<FlatButton
key="close"
label="Close"
primary={false}
onClick={() =>
onTouchTap={() =>
this.setState({
exportFinishedDialogOpen: false,
})}
@@ -147,12 +147,12 @@ export default class LocalExport extends Component {
<RaisedButton
fullWidth
primary
onClick={() => this.openItchioHelp()}
onTouchTap={() => this.openItchioHelp()}
label="Publish your game on Itch.io"
/>
<FlatButton
fullWidth
onClick={() => this.openLearnMore()}
onTouchTap={() => this.openLearnMore()}
label="Learn more about publishing"
/>
</Dialog>

View File

@@ -1,35 +0,0 @@
// @flow
import optionalRequire from '../../../Utils/OptionalRequire.js';
const archiver = optionalRequire('archiver');
const fs = optionalRequire('fs');
export const archiveFolder = ({
path,
outputFilename,
}: {
path: string,
outputFilename: string,
}): Promise<string> => {
return new Promise((resolve, reject) => {
const output = fs.createWriteStream(outputFilename);
const archive = archiver('zip', {
zlib: { level: 9 }, // Sets the compression level.
});
output.on('close', () => {
console.log(`Archive written at ${outputFilename}, ${archive.pointer()} total bytes.`);
resolve(outputFilename);
});
archive.on('error', (err) => {
reject(err);
});
archive.pipe(output);
archive.directory(path, false);
archive.finalize();
});
};

View File

@@ -1,106 +0,0 @@
import * as React from 'react';
import RaisedButton from 'material-ui/RaisedButton';
import { Step, Stepper, StepLabel, StepContent } from 'material-ui/Stepper';
import CircularProgress from 'material-ui/CircularProgress';
import LinearProgress from 'material-ui/LinearProgress';
import FlatButton from 'material-ui/FlatButton';
import { Line, Spacer } from '../../../UI/Grid';
export default ({
exportStep,
downloadUrl,
onDownload,
onDownloadLogs,
uploadMax,
uploadProgress,
buildMax,
buildProgress,
errored,
}) => (
<Stepper
activeStep={
exportStep === 'export'
? 0
: exportStep === 'compress' || exportStep === 'upload'
? 1
: exportStep === 'waiting-for-build' || exportStep === 'build'
? 2
: exportStep === 'done' ? 3 : undefined
}
orientation="vertical"
>
<Step>
<StepLabel>Game export</StepLabel>
<StepContent>
<Line alignItems="center">
<CircularProgress size={20} />
<Spacer />
<p>Export in progress...</p>
</Line>
</StepContent>
</Step>
<Step>
<StepLabel>Upload to build service</StepLabel>
<StepContent>
{errored ? (
<p>
Can't upload your game to the build service. Please check your
internet connection or try again later
</p>
) : exportStep === 'compress' ? (
<Line alignItems="center">
<CircularProgress size={20} />
<Spacer />
<p>Compressing before upload...</p>
</Line>
) : (
<Line alignItems="center" expand>
<LinearProgress
style={{ flex: 1 }}
max={uploadMax}
value={uploadProgress}
mode="determinate"
/>
</Line>
)}
</StepContent>
</Step>
<Step>
<StepLabel>Build</StepLabel>
<StepContent>
{errored ? (
<p>
Something wrong happened :(
</p>
) : exportStep === 'waiting-for-build' ? (
<Line alignItems="center">
<CircularProgress size={20} />
<Spacer />
<p>Waiting for build to start...</p>
</Line>
) : (
<Line alignItems="center" expand>
<LinearProgress
style={{ flex: 1 }}
max={buildMax}
value={buildProgress}
mode="determinate"
/>
</Line>
)}
</StepContent>
</Step>
<Step>
<StepLabel>Download</StepLabel>
<StepContent>
<Line>
<RaisedButton label="Download" primary onClick={onDownload} />
<FlatButton label="See logs" onClick={onDownloadLogs} />
</Line>
<Line expand>
You can download it on your Android phone and install it.
</Line>
</StepContent>
</Step>
</Stepper>
);

View File

@@ -1,352 +0,0 @@
// @flow
import React, { Component } from 'react';
import assignIn from 'lodash/assignIn';
import RaisedButton from 'material-ui/RaisedButton';
import { sendExportLaunched } from '../../../Utils/Analytics/EventSender';
import {
type Build,
buildCordovaAndroid,
getUrl,
getBuild,
} from '../../../Utils/GDevelopServices/Build';
import {
withUserProfile,
type WithUserProfileProps,
} from '../../../Profile/UserProfileContainer';
import { Column, Line } from '../../../UI/Grid';
import { showErrorBox } from '../../../UI/Messages/MessageBox';
import { findGDJS } from '../LocalGDJSFinder';
import localFileSystem from '../LocalFileSystem';
import Progress from './Progress';
import { archiveFolder } from './Archiver';
import optionalRequire from '../../../Utils/OptionalRequire.js';
import Window from '../../../Utils/Window';
import { delay } from '../../../Utils/Delay';
import CreateProfile from '../../../Profile/CreateProfile';
import LimitDisplayer from '../../../Profile/LimitDisplayer';
const path = optionalRequire('path');
const os = optionalRequire('os');
const electron = optionalRequire('electron');
const ipcRenderer = electron ? electron.ipcRenderer : null;
const gd = global.gd;
export type LocalOnlineCordovaExportStep =
| ''
| 'export'
| 'compress'
| 'upload'
| 'waiting-for-build'
| 'build'
| 'done';
type State = {
exportStep: LocalOnlineCordovaExportStep,
build: ?Build,
uploadProgress: number,
uploadMax: number,
buildMax: number,
buildProgress: number,
errored: boolean,
};
type Props = WithUserProfileProps & {
project: gdProject,
onChangeSubscription: Function,
};
class LocalOnlineCordovaExport extends Component<Props, State> {
state = {
exportStep: '',
build: null,
uploadProgress: 0,
uploadMax: 0,
buildProgress: 0,
buildMax: 0,
errored: false,
};
static prepareExporter = (): Promise<any> => {
return new Promise((resolve, reject) => {
findGDJS(gdjsRoot => {
if (!gdjsRoot) {
showErrorBox('Could not find GDJS');
return reject();
}
console.info('GDJS found in ', gdjsRoot);
const fileSystem = assignIn(
new gd.AbstractFileSystemJS(),
localFileSystem
);
const exporter = new gd.Exporter(fileSystem, gdjsRoot);
const outputDir = path.join(
fileSystem.getTempDir(),
'OnlineCordovaExport'
);
fileSystem.mkDir(outputDir);
fileSystem.clearDir(outputDir);
resolve({
exporter,
outputDir,
});
});
});
};
launchExport = (): Promise<string> => {
const { project } = this.props;
if (!project) return Promise.reject();
return LocalOnlineCordovaExport.prepareExporter()
.then(({ exporter, outputDir }) => {
const exportForCordova = true;
exporter.exportWholePixiProject(
project,
outputDir,
false,
exportForCordova
);
exporter.delete();
return outputDir;
})
.catch(err => {
showErrorBox('Unable to export the game', err);
throw err;
});
};
launchCompression = (outputDir: string): Promise<string> => {
const archiveOutputDir = os.tmpdir();
return archiveFolder({
path: outputDir,
outputFilename: path.join(archiveOutputDir, 'game-archive.zip'),
});
};
launchUpload = (outputFile: string): Promise<string> => {
if (!ipcRenderer) return Promise.reject('No support for upload');
ipcRenderer.removeAllListeners('s3-file-upload-progress');
ipcRenderer.removeAllListeners('s3-file-upload-done');
return new Promise((resolve, reject) => {
ipcRenderer.on(
's3-file-upload-progress',
(event, uploadProgress, uploadMax) => {
this.setState({
uploadProgress,
uploadMax,
});
}
);
ipcRenderer.on('s3-file-upload-done', (event, err, prefix) => {
if (err) return reject(err);
resolve(prefix);
});
ipcRenderer.send('s3-file-upload', outputFile);
});
};
launchBuild = (uploadBucketKey: string): Promise<string> => {
const { authentification, profile } = this.props;
if (!profile || !authentification)
return Promise.reject(new Error('User is not authenticated'));
return buildCordovaAndroid(
authentification,
profile.uid,
uploadBucketKey
).then(build => {
return build.id;
});
};
pollBuild = async (buildId: string): Promise<Build> => {
const { authentification, profile } = this.props;
if (!profile || !authentification)
return Promise.reject(new Error('User is not authenticated'));
try {
let build = null;
let tries = 0;
const waitTime = 1000;
const maxWaitTime = 200000;
do {
await delay(waitTime);
build = await getBuild(authentification, profile.uid, buildId);
this.setState({
build,
buildMax: maxWaitTime,
buildProgress: tries * waitTime,
});
tries += 1;
} while (
build &&
build.status === 'pending' &&
tries * waitTime < maxWaitTime
);
if (build.status !== 'complete') throw build;
return build;
} catch (err) {
throw err;
}
};
launchWholeExport = () => {
sendExportLaunched('local-online-cordova');
const handleError = (message: string) => err => {
if (!this.state.errored) {
this.setState({
errored: true,
});
showErrorBox(message, {
exportStep: this.state.exportStep,
rawError: err,
});
}
throw err;
};
this.setState({
exportStep: 'export',
uploadProgress: 0,
uploadMax: 0,
errored: false,
});
this.launchExport()
.then(outputDir => {
this.setState({
exportStep: 'compress',
});
return this.launchCompression(outputDir);
}, handleError('Error while exporting the game.'))
.then(outputFile => {
this.setState({
exportStep: 'upload',
});
return this.launchUpload(outputFile);
}, handleError('Error while compressing the game.'))
.then((uploadBucketKey: string) => {
this.setState({
exportStep: 'waiting-for-build',
});
return this.launchBuild(uploadBucketKey);
}, handleError('Error while uploading the game. Check your internet connection or try again later.'))
.then(buildId => {
this.setState({
exportStep: 'build',
});
return this.pollBuild(buildId);
}, handleError('Error while lauching the build of the game.'))
.then(build => {
this.setState({
exportStep: 'done',
build,
});
this.props.onRefreshUserProfile();
}, handleError('Error while building the game.'));
};
_download = () => {
const { build } = this.state;
if (!build || !build.apkKey) return;
Window.openExternalURL(getUrl(build.apkKey));
};
_downloadLogs = () => {
const { build } = this.state;
if (!build || !build.logsKey) return;
Window.openExternalURL(getUrl(build.logsKey));
};
_onChangeSubscription = () => {
const { onChangeSubscription } = this.props;
onChangeSubscription();
};
render() {
const {
exportStep,
build,
uploadMax,
uploadProgress,
buildMax,
buildProgress,
errored,
} = this.state;
const {
project,
authenticated,
onLogin,
subscription,
limits,
} = this.props;
if (!project) return null;
const buildLimit = limits ? limits['cordova-build'] : null;
const disableBuild =
(!errored && exportStep !== '' && exportStep !== 'done') ||
(buildLimit && buildLimit.limitReached);
return (
<Column noMargin>
<Line>
Packaging your game for Android will create an APK file that can be
installed on Android phones, based on Cordova framework.
</Line>
{authenticated && (
<Line justifyContent="center">
<RaisedButton
label="Package for Android"
primary
onClick={this.launchWholeExport}
disabled={disableBuild}
/>
</Line>
)}
{authenticated && (
<LimitDisplayer
subscription={subscription}
limit={buildLimit}
onChangeSubscription={this._onChangeSubscription}
/>
)}
{!authenticated && (
<CreateProfile
message="Create an account to build your game for Android in one-click:"
onLogin={onLogin}
/>
)}
<Line>
<Progress
exportStep={exportStep}
downloadUrl={build && build.apkKey ? getUrl(build.apkKey) : null}
logsUrl={build && build.logsKey ? getUrl(build.logsKey) : null}
onDownload={this._download}
onDownloadLogs={this._downloadLogs}
uploadMax={uploadMax}
uploadProgress={uploadProgress}
buildMax={buildMax}
buildProgress={buildProgress}
errored={errored}
/>
</Line>
</Column>
);
}
}
export default withUserProfile({ fetchLimits: true, fetchSubscription: true })(
LocalOnlineCordovaExport
);

View File

@@ -1,28 +0,0 @@
// @flow
import * as React from 'react';
import FlatButton from 'material-ui/FlatButton';
import Window from '../../Utils/Window';
import PlaceholderMessage from '../../UI/PlaceholderMessage';
export default class LocalOnlineElectronExport extends React.Component<*, *> {
_openGithub() {
Window.openExternalURL('https://github.com/4ian/GD');
}
render() {
const { project } = this.props;
if (!project) return null;
return (
<div style={{ height: 300 }}>
<PlaceholderMessage>
<p>This export is not ready yet!</p>
<FlatButton
label="Help by contributing on GitHub"
onClick={this._openGithub}
/>
</PlaceholderMessage>
</div>
);
}
}

View File

@@ -1,68 +0,0 @@
import * as React from 'react';
import LocalExport from './LocalExport';
import LocalS3Export from './LocalS3Export';
import LocalOnlineCordovaExport from './LocalOnlineCordovaExport';
import LocalCordovaExport from './LocalCordovaExport';
import LocalCocos2dExport from './LocalCocos2dExport';
import LocalOnlineElectronExport from './LocalOnlineElectronExport';
import PhoneIphone from 'material-ui/svg-icons/hardware/phone-iphone';
import LaptopMac from 'material-ui/svg-icons/hardware/laptop-mac';
import Folder from 'material-ui/svg-icons/file/folder';
import Devices from 'material-ui/svg-icons/device/devices';
export const getLocalExporters = () => [
{
name: 'Android (& iOS coming soon)',
renderIcon: (props) => <PhoneIphone {...props}/>,
helpPage: '/publishing/android_and_ios',
description: 'Package your game for Android directly from GDevelop. iOS support is coming soon!',
key: 'localonlinecordovaexport',
ExportComponent: LocalOnlineCordovaExport,
},
{
name: 'Web (upload online)',
renderIcon: (props) => <Devices {...props}/>,
helpPage: '/publishing/web',
description: 'Upload your game online directly from GDevelop and share the link to players. Play to your game using your browser on computers and mobile phones.',
key: 'locals3export',
ExportComponent: LocalS3Export,
},
{
name: 'Local folder',
renderIcon: (props) => <Folder {...props}/>,
helpPage: '/publishing/html5_game_in_a_local_folder',
description:
'Build the game locally as a HTML5 game. You can then export it on website like Itch.io or Kongregate.',
key: 'localexport',
ExportComponent: LocalExport,
advanced: true,
},
{
name: 'iOS & Android (manual)',
renderIcon: (props) => <PhoneIphone {...props}/>,
helpPage: '/publishing/android_and_ios_with_cordova',
description:
'Build the game locally as a Cordova project, and export it manually then to iOS or Android with Cordova developers tools.',
key: 'localcordovaexport',
ExportComponent: LocalCordovaExport,
advanced: true,
},
{
name: 'Windows/macOS/Linux (coming soon)',
renderIcon: (props) => <LaptopMac {...props}/>,
description: 'Package your game as an app for Windows, macOs or Linux.',
disabled: true,
key: 'localonlineelectronexport',
ExportComponent: LocalOnlineElectronExport,
},
{
name: 'Cocos2d-JS',
renderIcon: (props) => <PhoneIphone {...props}/>,
helpPage: '/publishing/android_and_ios_with_cocos2d-js',
description:
'Export your game using Cocos2d-JS game engine. The game can be compiled for Android or iOS if you install Cocos2d-JS developer tools.',
key: 'localcocos2dexport',
ExportComponent: LocalCocos2dExport,
experimental: true,
},
];

View File

@@ -1,4 +1,4 @@
import optionalRequire from '../../Utils/OptionalRequire.js';
import optionalRequire from '../Utils/OptionalRequire.js';
var fs = optionalRequire('fs-extra');
var path = optionalRequire('path');
var os = optionalRequire('os');

View File

@@ -1,6 +1,6 @@
// @flow
import optionalRequire from '../../Utils/OptionalRequire.js';
import optionalRequire from '../Utils/OptionalRequire.js';
const electron = optionalRequire('electron');
const app = electron ? electron.remote.app : null;
const fs = optionalRequire('fs');

View File

@@ -1,8 +1,8 @@
// @flow
import localFileSystem from './LocalFileSystem';
import optionalRequire from '../../Utils/OptionalRequire';
import { timeFunction } from '../../Utils/TimeFunction';
import optionalRequire from '../Utils/OptionalRequire';
import { timeFunction } from '../Utils/TimeFunction';
import { findGDJS } from './LocalGDJSFinder';
import assignIn from 'lodash/assignIn';
const electron = optionalRequire('electron');
@@ -11,7 +11,7 @@ const BrowserWindow = electron ? electron.remote.BrowserWindow : null;
const gd = global.gd;
export default class LocalPreviewLauncher {
static _openPreviewWindow = (project: gdProject, gamePath: string): void => {
static _openPreviewWindow = (project, gamePath): void => {
if (!BrowserWindow) return;
const win = new BrowserWindow({
@@ -47,10 +47,7 @@ export default class LocalPreviewLauncher {
});
};
static launchLayoutPreview = (
project: gdProject,
layout: gdLayout
): Promise<any> => {
static launchLayoutPreview = (project, layout): Promise<any> => {
if (!project || !layout) return Promise.reject();
return LocalPreviewLauncher._prepareExporter().then(
@@ -68,9 +65,9 @@ export default class LocalPreviewLauncher {
};
static launchExternalLayoutPreview = (
project: gdProject,
layout: gdLayout,
externalLayout: gdExternalLayout
project,
layout,
externalLayout
): Promise<any> => {
if (!project || !externalLayout) return Promise.reject();

View File

@@ -2,18 +2,21 @@ import React, { Component } from 'react';
import axios from 'axios';
import { sleep } from 'wait-promise';
import RaisedButton from 'material-ui/RaisedButton';
import { sendExportLaunched } from '../../Utils/Analytics/EventSender';
import { sendExportLaunched } from '../Utils/Analytics/EventSender';
import LocalExport from './LocalExport';
import optionalRequire from '../../Utils/OptionalRequire';
import { Column, Line, Spacer } from '../../UI/Grid';
import optionalRequire from '../Utils/OptionalRequire';
import { Column, Line, Spacer } from '../UI/Grid';
import LinearProgress from 'material-ui/LinearProgress';
import { GDevelopHostingApi } from '../../Utils/GDevelopServices/ApiConfigs';
import TextField from 'material-ui/TextField';
const os = optionalRequire('os');
const electron = optionalRequire('electron');
const ipcRenderer = electron ? electron.ipcRenderer : null;
const shell = electron ? electron.shell : null;
const deployEndpoint =
'https://nik50aqlp6.execute-api.eu-west-1.amazonaws.com/Production/deploy';
const gamesHost = 'http://gd-games.s3-website-eu-west-1.amazonaws.com';
export default class LocalS3Export extends Component {
constructor(props) {
super(props);
@@ -27,17 +30,17 @@ export default class LocalS3Export extends Component {
}
_uploadToS3 = localDir => {
ipcRenderer.removeAllListeners('s3-folder-upload-progress');
ipcRenderer.removeAllListeners('s3-folder-upload-done');
ipcRenderer.removeAllListeners('s3-upload-progress');
ipcRenderer.removeAllListeners('s3-upload-done');
return new Promise((resolve, reject) => {
ipcRenderer.on('s3-folder-upload-progress', (event, uploadProgress, uploadMax) =>
ipcRenderer.on('s3-upload-progress', (event, uploadProgress, uploadMax) =>
this.setState({
uploadProgress,
uploadMax,
})
);
ipcRenderer.on('s3-folder-upload-done', (event, err, prefix) => {
ipcRenderer.on('s3-upload-done', (event, err, prefix) => {
if (err) return reject(err);
this.setState({
@@ -45,14 +48,14 @@ export default class LocalS3Export extends Component {
});
resolve(prefix);
});
ipcRenderer.send('s3-folder-upload', localDir);
ipcRenderer.send('s3-upload', localDir);
});
};
_deploy = prefix => {
return sleep(200)
.then(() => //TODO: Move this to a GDevelopServices/Hosting.js file
axios(GDevelopHostingApi.deployEndpoint, {
.then(() =>
axios(deployEndpoint, {
method: 'post',
params: {
name: prefix,
@@ -98,9 +101,8 @@ export default class LocalS3Export extends Component {
.then(() => this._uploadToS3(outputDir))
.then(uploadPrefix => this._deploy(uploadPrefix))
.then(deployPrefix => {
//TODO: Move this to a function getURL in a GDevelopServices/Hosting.js file.
this.setState({
url: `${GDevelopHostingApi.gamesHost}/${deployPrefix}`,
url: `${gamesHost}/${deployPrefix}`,
});
})
.catch(err => {
@@ -125,7 +127,7 @@ export default class LocalS3Export extends Component {
<Spacer />
<TextField value={this.state.url} style={{ flex: 1 }} />
<Spacer />
<RaisedButton label="Open" primary={true} onClick={this.openURL} />
<RaisedButton label="Open" primary={true} onTouchTap={this.openURL} />
</Line>
);
};
@@ -165,7 +167,7 @@ export default class LocalS3Export extends Component {
<RaisedButton
label="Export and upload my game"
primary={true}
onClick={this.launchExport}
onTouchTap={this.launchExport}
disabled={exportStarted}
/>
</Line>

View File

@@ -172,7 +172,7 @@ class ExternalEditor extends Component<Props, State> {
this.editorOpened = true;
if (this.props.editor === 'scene-editor') {
this.editor.openLayout(this.props.editedElementName, {openEventsEditor: false});
this.editor.openLayout(this.props.editedElementName, false);
}
if (this.props.editor === 'external-layout-editor') {
this.editor.openExternalLayout(this.props.editedElementName);

View File

@@ -1,35 +0,0 @@
// @flow
/**
* Provide a method shouldUpdate that can be called in a game loop or in
* a method used in renderAnimationFrame, and which indicate if the rendering/update
* of the scene should be done according to the desired framerate.
*/
export default class FpsLimiter {
_lastFrameTime: number;
_interval: number;
_forceUpdate: boolean;
constructor(maxFps: number) {
this._lastFrameTime = Date.now();
this._interval = 1000 / maxFps;
this._forceUpdate = false;
}
shouldUpdate() {
const now = Date.now();
const delta = now - this._lastFrameTime;
if (delta > this._interval || this._forceUpdate) {
this._lastFrameTime = now - delta % this._interval;
this._forceUpdate = false;
return true;
}
return false;
}
forceNextUpdate() {
this._forceUpdate = true;
}
}

View File

@@ -99,13 +99,7 @@ export const addScrollbars = (WrappedComponent: any) => {
);
};
_setAndAdjust = ({
xValue,
yValue,
}: {
xValue: number,
yValue: number,
}) => {
_setAndAdjust = ({ xValue, yValue }: {xValue: number, yValue: number}) => {
const xMax = Math.max(Math.abs(xValue) + 100, this.state.xMax);
const yMax = Math.max(Math.abs(yValue) + 100, this.state.yMax);

View File

@@ -15,7 +15,6 @@ import WindowMask from './WindowMask';
import DropHandler from './DropHandler';
import BackgroundColor from './BackgroundColor';
import PIXI from 'pixi.js';
import FpsLimiter from './FpsLimiter';
export default class InstancesEditorContainer extends Component {
constructor() {
@@ -25,8 +24,6 @@ export default class InstancesEditorContainer extends Component {
this.lastContextMenuY = 0;
this.lastCursorX = 0;
this.lastCursorY = 0;
this.fpsLimiter = new FpsLimiter(28);
}
componentDidMount() {
@@ -90,12 +87,7 @@ export default class InstancesEditorContainer extends Component {
this._onMouseMove(event.data.global.x, event.data.global.y)
);
this.backgroundArea.on('panmove', event =>
this._onPanMove(
event.deltaX,
event.deltaY,
event.data.global.x,
event.data.global.y
)
this._onPanMove(event.deltaX, event.deltaY, event.data.global.x, event.data.global.y)
);
this.backgroundArea.on('panend', event => this._onEndSelectionRectangle());
this.pixiContainer.addChild(this.backgroundArea);
@@ -254,10 +246,7 @@ export default class InstancesEditorContainer extends Component {
nextProps.width,
nextProps.height
);
// Avoid flickering that could happen while waiting for next animation frame.
this.fpsLimiter.forceNextUpdate();
this._renderScene();
this._renderScene(); //Avoid flickering that could happen while waiting for next animation frame.
}
if (nextProps.options !== this.props.options) {
@@ -471,25 +460,22 @@ export default class InstancesEditorContainer extends Component {
getViewPosition = () => {
return this.viewPosition;
};
}
_renderScene = () => {
// Protect against rendering scheduled after the component is unmounted.
if (this._unmounted) return;
// Avoid killing the CPU by limiting the rendering calls.
if (this.fpsLimiter.shouldUpdate()) {
this.backgroundColor.render();
this.viewPosition.render();
this.grid.render();
this.instancesRenderer.render();
this.highlightedInstance.render();
this.selectedInstances.render();
this.selectionRectangle.render();
this.windowBorder.render();
this.windowMask.render();
this.pixiRenderer.render(this.pixiContainer);
}
this.backgroundColor.render();
this.viewPosition.render();
this.grid.render();
this.instancesRenderer.render();
this.highlightedInstance.render();
this.selectedInstances.render();
this.selectionRectangle.render();
this.windowBorder.render();
this.windowMask.render();
this.pixiRenderer.render(this.pixiContainer);
this.nextFrame = requestAnimationFrame(this._renderScene);
};

View File

@@ -10,7 +10,7 @@ const AddLayerRow = ({ onAdd }) => (
<TableRowColumn />
<TableRowColumn style={styles.visibleColumn} />
<TableRowColumn style={styles.toolColumn}>
<IconButton onClick={onAdd}>
<IconButton onTouchTap={onAdd}>
<Add />
</IconButton>
</TableRowColumn>

View File

@@ -30,17 +30,17 @@ export default class VariablesEditorDialog extends Component {
label="Cancel"
secondary={true}
keyboardFocused={true}
onClick={() => this.props.onClose(false)}
onTouchTap={() => this.props.onClose(false)}
/>,
<FlatButton
label="Remove objects"
secondary={true}
onClick={() => this.props.onClose(true, null)}
onTouchTap={() => this.props.onClose(true, null)}
/>,
<FlatButton
label="Move objects"
primary={true}
onClick={() => this.props.onClose(true, this.state.selectedLayer)}
onTouchTap={() => this.props.onClose(true, this.state.selectedLayer)}
/>,
];

View File

@@ -6,22 +6,20 @@ import VisibilityOff from 'material-ui/svg-icons/action/visibility-off';
import IconButton from 'material-ui/IconButton';
import Delete from 'material-ui/svg-icons/action/delete';
import TextField from 'material-ui/TextField';
import muiThemeable from 'material-ui/styles/muiThemeable';
import DragHandle from '../UI/DragHandle';
import styles from './styles';
const ThemableLayerRow = ({
const LayerRow = ({
layerName,
nameError,
onBlur,
onRemove,
isVisible,
onChangeVisibility,
muiTheme,
}) => (
<TableRow
style={{
backgroundColor: muiTheme.list.itemsBackgroundColor,
backgroundColor: 'white',
}}
>
<TableRowColumn style={styles.handleColumn}>
@@ -45,14 +43,11 @@ const ThemableLayerRow = ({
/>
</TableRowColumn>
<TableRowColumn style={styles.toolColumn}>
<IconButton onClick={onRemove}>
<IconButton onTouchTap={onRemove}>
<Delete />
</IconButton>
</TableRowColumn>
</TableRow>
);
const LayerRow = muiThemeable()(
ThemableLayerRow
);
export default LayerRow;

View File

@@ -1,4 +1,3 @@
//TODO: Factor with styles.js from PointsEditor.
export default {
handleColumn: {
width: 24,

Some files were not shown because too many files have changed in this diff Show More