Compare commits

...

35 Commits

Author SHA1 Message Date
Florian Rival
07f26027f6 Bump newIDE version 2018-02-15 22:56:13 +01:00
Florian Rival
09cf13d6e2 Add confirm before close dialog to newIDE 2018-02-15 22:50:08 +01:00
Florian Rival
42f91565fa Merge pull request #443 from 4ian/feature/cordova-icons
Add support for icons when exporting to Android/iOS
2018-02-13 19:51:04 +01:00
Florian Rival
ac6c146808 Fix ImageThumbnail error when onContextMenu is not specified 2018-02-13 19:50:21 +01:00
Florian Rival
59ad23f8ac Allow to customize game orientation (for when exported with Cordova) 2018-02-11 23:44:00 +01:00
Florian Rival
769c6fe3d5 Move ResourcesLoader in its own folder + refactor methods 2018-02-11 23:17:44 +01:00
Florian Rival
07b92911ab Add message on web-app about non support of automatic icons creation 2018-02-11 22:03:30 +01:00
Florian Rival
eb57bcfc87 Add iOS icons support for Cordova + fix image caching 2018-02-11 21:52:40 +01:00
Florian Rival
7addeba73a Add support for Android icons in Cordova export 2018-02-11 21:14:18 +01:00
Florian Rival
263902b45a Avoid caching in ImagePreview and ImageThumbnail 2018-02-11 18:14:47 +01:00
Florian Rival
d283f759fe Add PlatformSpecificAssets to Project 2018-02-10 18:36:02 +01:00
Florian Rival
fabd028a63 Update VSCode C++ properties for Intellisense 2018-02-10 18:35:05 +01:00
Florian Rival
2b18272c41 Merge branch 'master' of github.com:4ian/GD 2018-02-07 00:02:25 +01:00
Florian Rival
a1fb39da3d Fix scene unloading re-creating a RuntimeSceneRenderer leading to issues with Cocos2d-JS renderer 2018-02-05 23:12:51 +01:00
Florian Rival
09602fdf9e Merge pull request #441 from ronnystandtke/patch-1
Update gdevelop.desktop
2018-02-04 17:03:22 +01:00
ronnystandtke
d574ef17ba Update gdevelop.desktop
added German L10n
2018-02-04 17:00:34 +01:00
Florian Rival
0e3f70627b Bump newIDE version 2018-02-03 19:32:52 +01:00
Florian Rival
a814a07105 Fix ResourcesEditor selection 2018-02-03 19:24:47 +01:00
Florian Rival
c49af90a9c Add program opening count to Keen.io analytics 2018-02-03 19:09:33 +01:00
Florian Rival
24afa155c8 Improve Keen.io analytics events by adding user profile information 2018-02-03 18:30:09 +01:00
Florian Rival
9e5a431516 Only run Fullstory on newIDE web-app 2018-02-03 17:53:57 +01:00
Florian Rival
182a94285c Unregister service worker (it is not ready yet) 2018-02-03 13:05:22 +01:00
Florian Rival
442c2c8dd9 Merge pull request #439 from 4ian/feature/resources-editor
Resources editor for newIDE
2018-02-03 12:57:22 +01:00
Florian Rival
f8fd0dd353 Add a preview in ResourcesEditor 2018-02-03 12:55:30 +01:00
Florian Rival
faad9e23ac Add ResourcesEditor with ResourcePropertiesEditor and Toolbar 2018-01-30 23:12:31 +01:00
Florian Rival
d2af0da1b1 Added tools method to ResourcesManager and finish working version of ResourcesList 2018-01-30 21:56:40 +01:00
Florian Rival
c65e5c3e49 [WIP] Add ResourcesEditor based on a generic SortableVirtualizedItemList
TODO:
* Use SortableVirtualizedItemList for ObjectsList and ObjectsGroupsList
* Add missing features (sorting, delete, thumbnail for images, properties edition) to ResourcesEditor
2018-01-30 00:58:21 +01:00
Florian Rival
24a8dfc5f0 Add SemiControlledTextField to be able to type freely in PropertiesEditor 2018-01-28 18:45:44 +01:00
Florian Rival
9c6790ac37 Update default gravity in PhysicsBehavior 2018-01-27 23:50:49 +01:00
Florian Rival
18ef7460ba Bump newIDE version 2018-01-27 19:34:36 +01:00
Florian Rival
63cd0e76c3 Fix warning 2018-01-27 19:34:27 +01:00
Florian Rival
bdbf7fd9fc Add support for editing BehaviorSharedData in newIDE
TODO:
* Support for cancelling modifications made on it in ScenePropertiesDialog
2018-01-27 19:13:27 +01:00
Florian Rival
6bbedbd8f9 Fix BehaviorSharedData not created by newIDE + fix warnings 2018-01-27 17:00:44 +01:00
Florian Rival
648bd1ff2e Show warning if game name or package name is empty when exporting in newIDE 2018-01-24 23:31:02 +01:00
Florian Rival
d4288caedb Fix .env file loading with electron-app and bump newIDE version 2018-01-23 00:15:13 +01:00
88 changed files with 3013 additions and 404 deletions

View File

@@ -10,29 +10,29 @@
"${workspaceRoot}/Extensions",
"${workspaceRoot}/Core",
"${workspaceRoot}/ExtLibs/SFML/include",
"/usr/local/lib/wx/include/osx_cocoa-unicode-3.0",
"/usr/local/include/wx-3.0",
"/usr/include/machine",
"/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include",
"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1",
"/usr/local/include",
"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/8.0.0/include",
"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/9.0.0/include",
"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include",
"/usr/include"
"/usr/include",
"${workspaceRoot}"
],
"defines": [
"GD_IDE_ONLY",
"__WXMAC__",
"__WXOSX__",
"__WXOSX_COCOA__",
"GD_CORE_API=\" \"",
"GD_CORE_API=\/* Macro used to export classes on Windows, please ignore *\/",
"GD_API=\/* Macro used to export classes on Windows, please ignore *\/",
"GD_EXTENSION_API=\/* Macro used to export classes on Windows, please ignore *\/",
"WXUSINGDLL"
],
"intelliSenseMode": "clang-x64",
"browse": {
"path": [
"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1",
"/usr/local/include",
"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/8.0.0/include",
"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/9.0.0/include",
"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include",
"/usr/include",
"${workspaceRoot}"
@@ -40,7 +40,6 @@
"limitSymbolsToIncludedHeaders": true,
"databaseFilename": ""
},
"intelliSenseMode": "clang-x64",
"macFrameworkPath": [
"/System/Library/Frameworks",
"/Library/Frameworks"
@@ -50,10 +49,27 @@
"name": "Linux",
"includePath": [
"${workspaceRoot}",
"${workspaceRoot}/IDE",
"${workspaceRoot}/GDCpp",
"${workspaceRoot}/GDJS",
"${workspaceRoot}/Extensions",
"${workspaceRoot}/Core",
"${workspaceRoot}/ExtLibs/SFML/include",
"/usr/include",
"/usr/local/include"
"/usr/local/include",
"${workspaceRoot}"
],
"defines": [],
"defines": [
"GD_IDE_ONLY",
"__WXMAC__",
"__WXOSX__",
"__WXOSX_COCOA__",
"GD_CORE_API=\/* Macro used to export classes on Windows, please ignore *\/",
"GD_API=\/* Macro used to export classes on Windows, please ignore *\/",
"GD_EXTENSION_API=\/* Macro used to export classes on Windows, please ignore *\/",
"WXUSINGDLL"
],
"intelliSenseMode": "clang-x64",
"browse": {
"path": [
"/usr/include",
@@ -62,19 +78,34 @@
],
"limitSymbolsToIncludedHeaders": true,
"databaseFilename": ""
},
"intelliSenseMode": "clang-x64"
}
},
{
"name": "Win32",
"includePath": [
"${workspaceRoot}",
"C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/include"
"${workspaceRoot}/IDE",
"${workspaceRoot}/GDCpp",
"${workspaceRoot}/GDJS",
"${workspaceRoot}/Extensions",
"${workspaceRoot}/Core",
"${workspaceRoot}/ExtLibs/SFML/include",
"C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/include",
"${workspaceRoot}"
],
"defines": [
"_DEBUG",
"UNICODE"
"UNICODE",
"GD_IDE_ONLY",
"__WXMAC__",
"__WXOSX__",
"__WXOSX_COCOA__",
"GD_CORE_API=\/* Macro used to export classes on Windows, please ignore *\/",
"GD_API=\/* Macro used to export classes on Windows, please ignore *\/",
"GD_EXTENSION_API=\/* Macro used to export classes on Windows, please ignore *\/",
"WXUSINGDLL"
],
"intelliSenseMode": "msvc-x64",
"browse": {
"path": [
"C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/include/*",
@@ -82,8 +113,7 @@
],
"limitSymbolsToIncludedHeaders": true,
"databaseFilename": ""
},
"intelliSenseMode": "msvc-x64"
}
}
],
"version": 3

View File

@@ -70,7 +70,8 @@
"ratio": "cpp",
"atomic": "cpp",
"locale": "cpp",
"string_view": "cpp"
"string_view": "cpp",
"__string": "cpp"
},
"files.exclude": {
"Binaries/*build*": true,

View File

@@ -2,7 +2,9 @@
Version=1.0
Name=GDevelop
GenericName=Game creator IDE
GenericName[de]=Entwicklungsumgebung für Spiele
Comment=HTML5 and native game development software
Comment[de]=Entwicklungsumgebung für native und HTML5-Spiele
Exec=sh -c "gdevelop %F"
MimeType=application/x-gdevelop-project;
Icon=GDevelop

View File

@@ -945,7 +945,7 @@ void ResourcesEditor::Refresh()
gd::ResourceFolder & folder = project.GetResourcesManager().GetFolder(folders[i]);
wxTreeItemId folderItem = resourcesTree->AppendItem( resourcesTree->GetRootItem(), folders[i], -1, -1, new gd::TreeItemStringData("Folder", folders[i] ));
std::vector<gd::String> resources = folder.GetAllResourcesList();
std::vector<gd::String> resources = folder.GetAllResourceNames();
for (std::size_t j=0;j<resources.size();++j)
{
gd::Resource & resource = folder.GetResource(resources[j]);
@@ -959,7 +959,7 @@ void ResourcesEditor::Refresh()
//All images
allImagesItem = resourcesTree->AppendItem( resourcesTree->GetRootItem(), _("All images"), -1,-1, new gd::TreeItemStringData("BaseFolder", "" ));
std::vector<gd::String> resources = project.GetResourcesManager().GetAllResourcesList();
std::vector<gd::String> resources = project.GetResourcesManager().GetAllResourceNames();
for ( std::size_t i = 0;i <resources.size();i++ )
{
gd::Resource & resource = project.GetResourcesManager().GetResource(resources[i]);

View File

@@ -51,7 +51,7 @@ void ArbitraryResourceWorker::ExposeResources(gd::ResourcesManager * resourcesMa
resourcesManagers.push_back(resourcesManager);
std::vector<gd::String> resources = resourcesManager->GetAllResourcesList();
std::vector<gd::String> resources = resourcesManager->GetAllResourceNames();
for ( std::size_t i = 0;i < resources.size() ;i++ )
{
if ( resourcesManager->GetResource(resources[i]).UseFile() )

View File

@@ -45,7 +45,7 @@ std::vector<gd::String> ProjectResourcesAdder::GetAllUselessImages(gd::Project &
std::set<gd::String> & usedImages = inventorizer.GetAllUsedImages();
//Search all images resources not used
std::vector<gd::String> resources = project.GetResourcesManager().GetAllResourcesList();
std::vector<gd::String> resources = project.GetResourcesManager().GetAllResourceNames();
for (std::size_t i = 0;i < resources.size();i++)
{
if (project.GetResourcesManager().GetResource(resources[i]).GetKind() != "image")

View File

@@ -45,7 +45,7 @@ bool ProjectResourcesCopier::CopyAllResourcesTo(gd::Project & originalProject, A
#endif
auto projectDirectory = fs.DirNameFrom(originalProject.GetProjectFile());
std::cout << "Copying all ressources from " << projectDirectory << " to " << destinationDirectory;
std::cout << "Copying all ressources from " << projectDirectory << " to " << destinationDirectory << "..." << std::endl;
//Get the resources to be copied
gd::ResourcesMergingHelper resourcesMergingHelper(fs);

View File

@@ -68,7 +68,7 @@ public:
* Usage example:
\code
std::map<gd::String, gd::PropertyDescriptor> properties;
properties[ToString(_("Initial speed"))].SetValue("5");
properties[_("Initial speed")].SetValue(gd::String::From(initialSpeed));
return properties;
\endcode

View File

@@ -5,6 +5,10 @@
*/
#include "GDCore/Project/BehaviorsSharedData.h"
#if defined(GD_IDE_ONLY)
#include <map>
#include "GDCore/IDE/Dialogs/PropertyDescriptor.h"
#endif
namespace gd
{
@@ -13,4 +17,12 @@ BehaviorsSharedData::~BehaviorsSharedData()
{
};
#if defined(GD_IDE_ONLY)
std::map<gd::String, gd::PropertyDescriptor> BehaviorsSharedData::GetProperties(gd::Project & project) const
{
std::map<gd::String, gd::PropertyDescriptor> nothing;
return nothing;
}
#endif
}

View File

@@ -8,9 +8,13 @@
#define BEHAVIORSSHAREDDATA_H
#include <memory>
#include <map>
#include "GDCore/String.h"
class BehaviorsRuntimeSharedData;
namespace gd { class SerializerElement; }
namespace gd { class PropertyDescriptor; }
namespace gd { class Project; }
namespace gd { class Layout; }
namespace gd
{
@@ -52,6 +56,31 @@ public:
virtual void SetTypeName(const gd::String & type_) { type = type_; };
#if defined(GD_IDE_ONLY)
/**
* \brief Called when the IDE wants to know about the properties of the shared data.
*
* Usage example:
\code
std::map<gd::String, gd::PropertyDescriptor> properties;
properties[_("Initial speed")].SetValue(gd::String::From(initialSpeed));
return properties;
\endcode
*
* \return a std::map with properties names as key.
* \see gd::PropertyDescriptor
*/
virtual std::map<gd::String, gd::PropertyDescriptor> GetProperties(gd::Project & project) const;
/**
* \brief Called when the IDE wants to update a property of the shared data
*
* \return false if the new value cannot be set
* \see gd::InitialInstance
*/
virtual bool UpdateProperty(const gd::String & name, const gd::String & value, gd::Project & project) {return false;};
/**
* \brief Serialize behaviors shared data.
*/

View File

@@ -141,7 +141,7 @@ void ImageManager::LoadPermanentImages()
//so as not to unload images that could be still present.
std::map < gd::String, std::shared_ptr<SFMLTextureWrapper> > newPermanentlyLoadedImages;
std::vector<gd::String> resources = resourcesManager->GetAllResourcesList();
std::vector<gd::String> resources = resourcesManager->GetAllResourceNames();
for ( std::size_t i = 0;i <resources.size();i++ )
{
try

View File

@@ -29,6 +29,7 @@ namespace gd
{
gd::Layer Layout::badLayer;
gd::BehaviorsSharedData Layout::badBehaviorSharedData;
Layout::Layout(const Layout & other)
{
@@ -74,6 +75,53 @@ void Layout::SetName(const gd::String & name_)
mangledName = gd::SceneNameMangler::GetMangledSceneName(name);
};
bool Layout::HasBehaviorSharedData(const gd::String & behaviorName)
{
return behaviorsInitialSharedDatas.find(behaviorName) != behaviorsInitialSharedDatas.end();
}
std::vector <gd::String> Layout::GetAllBehaviorSharedDataNames() const
{
std::vector < gd::String > allNames;
for (auto & it : behaviorsInitialSharedDatas)
allNames.push_back(it.first);
return allNames;
}
const gd::BehaviorsSharedData & Layout::GetBehaviorSharedData(const gd::String & behaviorName) const
{
auto it = behaviorsInitialSharedDatas.find(behaviorName);
if (it != behaviorsInitialSharedDatas.end())
return *it->second;
return badBehaviorSharedData;
}
gd::BehaviorsSharedData & Layout::GetBehaviorSharedData(const gd::String & behaviorName)
{
auto it = behaviorsInitialSharedDatas.find(behaviorName);
if (it != behaviorsInitialSharedDatas.end())
return *it->second;
return badBehaviorSharedData;
}
std::shared_ptr<gd::BehaviorsSharedData> Layout::GetBehaviorSharedDataSmartPtr(const gd::String & behaviorName)
{
auto it = behaviorsInitialSharedDatas.find(behaviorName);
if (it != behaviorsInitialSharedDatas.end())
return it->second;
return std::shared_ptr<gd::BehaviorsSharedData>();
}
const std::map < gd::String, std::shared_ptr<gd::BehaviorsSharedData> > & Layout::GetAllBehaviorSharedData() const
{
return behaviorsInitialSharedDatas;
}
gd::Layer & Layout::GetLayer(const gd::String & name)
{
std::vector<gd::Layer>::iterator layer = find_if(initialLayers.begin(), initialLayers.end(), bind2nd(gd::LayerHasName(), name));
@@ -83,6 +131,7 @@ gd::Layer & Layout::GetLayer(const gd::String & name)
return badLayer;
}
const gd::Layer & Layout::GetLayer(const gd::String & name) const
{
std::vector<gd::Layer>::const_iterator layer = find_if(initialLayers.begin(), initialLayers.end(), bind2nd(gd::LayerHasName(), name));
@@ -92,14 +141,17 @@ const gd::Layer & Layout::GetLayer(const gd::String & name) const
return badLayer;
}
gd::Layer & Layout::GetLayer(std::size_t index)
{
return initialLayers[index];
}
const gd::Layer & Layout::GetLayer (std::size_t index) const
{
return initialLayers[index];
}
std::size_t Layout::GetLayersCount() const
{
return initialLayers.size();

View File

@@ -172,37 +172,37 @@ public:
/** \name Layout layers management
* Members functions related to layout layers management.
* TODO: This should be moved to a separate class
* TODO: This could be moved to a separate class
*/
///@{
/**
* Must return true if the layer called "name" exists.
* \brief Return true if the layer called "name" exists.
*/
bool HasLayerNamed(const gd::String & name) const;
/**
* Must return a reference to the layer called "name".
* \brief Return a reference to the layer called "name".
*/
Layer & GetLayer(const gd::String & name);
/**
* Must return a reference to the layer called "name".
* \brief Return a reference to the layer called "name".
*/
const Layer & GetLayer(const gd::String & name) const;
/**
* Must return a reference to the layer at position "index" in the layers list
* \brief Return a reference to the layer at position "index" in the layers list
*/
Layer & GetLayer(std::size_t index);
/**
* Must return a reference to the layer at position "index" in the layers list
* \brief Return a reference to the layer at position "index" in the layers list
*/
const Layer & GetLayer (std::size_t index) const;
const Layer & GetLayer(std::size_t index) const;
/**
* Must return the position of the layer called "name" in the layers list
* \brief Return the position of the layer called "name" in the layers list
*/
std::size_t GetLayerPosition(const gd::String & name) const;
@@ -253,15 +253,45 @@ public:
///@}
/**
* Make sure that the scene had an instance of shared data for
* This ensures that the scene had an instance of shared data for
* every behavior of every object that can be used on the scene
* ( i.e. the objects of the scene and the global objects )
* (i.e. the objects of the scene and the global objects)
*
* Must be called when a behavior have been added/deleted
* or when a scene have been added to a project.
*/
void UpdateBehaviorsSharedData(gd::Project & project);
/**
* \brief Get the names of all shared data stored for behaviors
*/
std::vector <gd::String> GetAllBehaviorSharedDataNames() const;
/**
* \brief Check if shared data are stored for a behavior
*/
bool HasBehaviorSharedData(const gd::String & behaviorName);
/**
* \brief Get the shared data stored for a behavior
*/
const gd::BehaviorsSharedData & GetBehaviorSharedData(const gd::String & behaviorName) const;
/**
* \brief Get the shared data stored for a behavior
*/
gd::BehaviorsSharedData & GetBehaviorSharedData(const gd::String & behaviorName);
/**
* \brief Get a map of all shared data stored for behaviors
*/
const std::map < gd::String, std::shared_ptr<gd::BehaviorsSharedData> > & GetAllBehaviorSharedData() const;
/**
* \brief Get the (smart pointer to the) shared data stored for a behavior.
*/
std::shared_ptr<gd::BehaviorsSharedData> GetBehaviorSharedDataSmartPtr(const gd::String & behaviorName);
#if defined(GD_IDE_ONLY)
/**
* Return the settings associated to the layout.
@@ -357,9 +387,6 @@ public:
void UnserializeFrom(gd::Project & project, const SerializerElement & element);
///@}
//TODO: Send this to private part.
std::map < gd::String, std::shared_ptr<gd::BehaviorsSharedData> > behaviorsInitialSharedDatas; ///< Initial shared datas of behaviors
//TODO: GD C++ Platform specific code below
#if defined(GD_IDE_ONLY)
/** \name Events compilation and bitcode management
@@ -442,6 +469,7 @@ private:
gd::InitialInstancesContainer initialInstances; ///< Initial instances
std::vector < gd::Layer > initialLayers; ///< Initial layers
ObjectGroupsContainer objectGroups; ///< Objects groups
std::map < gd::String, std::shared_ptr<gd::BehaviorsSharedData> > behaviorsInitialSharedDatas; ///< Initial shared datas of behaviors
bool stopSoundsOnStartup; ///< True to make the scene stop all sounds at startup.
bool standardSortMethod; ///< True to sort objects using standard sort.
float oglFOV; ///< OpenGL Field Of View value
@@ -449,6 +477,7 @@ private:
float oglZFar; ///< OpenGL Far Z position
bool disableInputWhenNotFocused; /// If set to true, the input must be disabled when the window do not have the focus.
static gd::Layer badLayer; ///< Null object, returned when GetLayer can not find an appropriate layer.
static gd::BehaviorsSharedData badBehaviorSharedData; ///< Null object, returned when GetBehaviorSharedData can not find the specified behavior shared data.
#if defined(GD_IDE_ONLY)
EventsList events; ///< Scene events
gd::LayoutEditorCanvasOptions associatedSettings;

View File

@@ -0,0 +1,58 @@
/*
* GDevelop Core
* Copyright 2008-2018 Florian Rival (Florian.Rival@gmail.com). All rights reserved.
* This project is released under the MIT License.
*/
#include "PlatformSpecificAssets.h"
#include "GDCore/Serialization/SerializerElement.h"
#include "GDCore/IDE/Project/ArbitraryResourceWorker.h"
namespace gd {
gd::String PlatformSpecificAssets::badStr;
bool PlatformSpecificAssets::Has(const gd::String& platform, const gd::String& name) const
{
return assets.find(platform + "-" + name) != assets.end();
}
const gd::String& PlatformSpecificAssets::Get(const gd::String& platform, const gd::String& name) const
{
const auto & it = assets.find(platform + "-" + name);
return it != assets.end() ? it->second : badStr;
}
void PlatformSpecificAssets::Remove(const gd::String& platform, const gd::String& name)
{
assets.erase(platform + "-" + name);
}
void PlatformSpecificAssets::Set(const gd::String& platform, const gd::String& name, const gd::String& resourceName)
{
assets[platform + "-" + name] = resourceName;
}
void PlatformSpecificAssets::SerializeTo(SerializerElement& element) const
{
for (auto& it : assets) {
element.AddChild(it.first).SetValue(it.second);
}
}
void PlatformSpecificAssets::UnserializeFrom(const SerializerElement& element)
{
assets.clear();
for (auto& child : element.GetAllChildren()) {
assets[child.first] = child.second->GetValue().GetString();
}
}
void PlatformSpecificAssets::ExposeResources(gd::ArbitraryResourceWorker & worker)
{
for (auto& it : assets) {
worker.ExposeImage(it.second);
}
}
}

View File

@@ -0,0 +1,76 @@
/*
* GDevelop Core
* Copyright 2008-2018 Florian Rival (Florian.Rival@gmail.com). All rights reserved.
* This project is released under the MIT License.
*/
#ifndef GDCORE_PLATFORMASSETS_H
#define GDCORE_PLATFORMASSETS_H
#include "GDCore/String.h"
#include <map>
namespace gd {
class SerializerElement;
}
namespace gd {
class ArbitraryResourceWorker;
}
namespace gd {
/**
* \brief Store the icons, splashscreens or reference to any other asset
* that can be needed when exporting the game.
*
* \see gd::Project
*
* \ingroup PlatformDefinition
*/
class GD_CORE_API PlatformSpecificAssets {
public:
PlatformSpecificAssets(){};
virtual ~PlatformSpecificAssets(){};
/**
* \brief Return true if the specified asset exists.
*/
bool Has(const gd::String& platform, const gd::String& name) const;
/**
* \brief Get the specified asset resource name.
*/
const gd::String& Get(const gd::String& platform, const gd::String& name) const;
/**
* \brief Remove the specified asset.
*/
void Remove(const gd::String& platform, const gd::String& name);
/**
* \brief Remove the specified asset.
*/
void Set(const gd::String& platform, const gd::String& name, const gd::String& resourceName);
void ExposeResources(gd::ArbitraryResourceWorker & worker);
/** \name Saving and loading
*/
///@{
/**
* \brief Serialize objects groups container.
*/
void SerializeTo(SerializerElement& element) const;
/**
* \brief Unserialize the objects groups container.
*/
void UnserializeFrom(const SerializerElement& element);
///@}
private:
std::map<gd::String, gd::String> assets;
static gd::String badStr;
};
}
#endif // GDCORE_PLATFORMASSETS_H

View File

@@ -61,6 +61,7 @@ Project::Project() :
#if defined(GD_IDE_ONLY)
name(_("Project")),
packageName("com.example.gamename"),
orientation("landscape"),
folderProject(false),
#endif
windowWidth(800),
@@ -531,9 +532,11 @@ void Project::UnserializeFrom(const SerializerElement & element)
#if defined(GD_IDE_ONLY)
SetAuthor(propElement.GetChild("author", 0, "Auteur").GetValue().GetString());
SetPackageName(propElement.GetStringAttribute("packageName"));
SetOrientation(propElement.GetStringAttribute("orientation", "default"));
SetFolderProject(propElement.GetBoolAttribute("folderProject"));
SetProjectFile(propElement.GetStringAttribute("projectFile"));
SetLastCompilationDirectory(propElement.GetChild("latestCompilationDirectory", 0, "LatestCompilationDirectory").GetValue().GetString());
platformSpecificAssets.UnserializeFrom(propElement.GetChild("platformSpecificAssets"));
winExecutableFilename = propElement.GetStringAttribute("winExecutableFilename");
winExecutableIconFile = propElement.GetStringAttribute("winExecutableIconFile");
linuxExecutableFilename = propElement.GetStringAttribute("linuxExecutableFilename");
@@ -743,6 +746,8 @@ void Project::SerializeTo(SerializerElement & element) const
propElement.SetAttribute("projectFile", gameFile);
propElement.SetAttribute("folderProject", folderProject);
propElement.SetAttribute("packageName", packageName);
propElement.SetAttribute("orientation", orientation);
platformSpecificAssets.SerializeTo(propElement.AddChild("platformSpecificAssets"));
propElement.SetAttribute("winExecutableFilename", winExecutableFilename);
propElement.SetAttribute("winExecutableIconFile", winExecutableIconFile);
propElement.SetAttribute("linuxExecutableFilename", linuxExecutableFilename);
@@ -815,6 +820,7 @@ void Project::ExposeResources(gd::ArbitraryResourceWorker & worker)
{
//Add project resources
worker.ExposeResources(&GetResourcesManager());
platformSpecificAssets.ExposeResources(worker);
#if !defined(GD_NO_WX_GUI)
gd::SafeYield::Do();
#endif
@@ -1019,8 +1025,10 @@ void Project::Init(const gd::Project & game)
#if defined(GD_IDE_ONLY)
author = game.author;
packageName = game.packageName;
orientation = game.orientation;
folderProject = game.folderProject;
latestCompilationDirectory = game.latestCompilationDirectory;
platformSpecificAssets = game.platformSpecificAssets;
objectGroups = game.objectGroups;
GDMajorVersion = game.GDMajorVersion;

View File

@@ -16,6 +16,7 @@ class TiXmlElement;
#include "GDCore/Project/ChangesNotifier.h"
#include "GDCore/Project/VariablesContainer.h"
#include "GDCore/Project/ResourcesManager.h"
#include "GDCore/Project/PlatformSpecificAssets.h"
#include "GDCore/Project/ObjectGroupsContainer.h"
namespace gd { class Platform; }
namespace gd { class Layout; }
@@ -85,6 +86,18 @@ public:
*/
const gd::String & GetPackageName() const { return packageName; }
/**
* \brief Change the project orientation (in particular when exported with Cordova).
* This has no effect on desktop and web browsers.
* \param orientation The orientation to use ("default", "landscape", "portrait").
*/
void SetOrientation(const gd::String & orientation_) { orientation = orientation_; };
/**
* \brief Get project orientation ("default", "landscape", "portrait").
*/
const gd::String & GetOrientation() const { return orientation; }
/**
* Called when project file has changed.
*/
@@ -118,6 +131,16 @@ public:
* \see gd::Project::SetLastCompilationDirectory
*/
const gd::String & GetLastCompilationDirectory() const {return latestCompilationDirectory;}
/**
* \brief Return a reference to platform assets of the project (icons, splashscreen...).
*/
gd::PlatformSpecificAssets & GetPlatformSpecificAssets() { return platformSpecificAssets; }
/**
* \brief Return a reference to platform assets of the project (icons, splashscreen...).
*/
const gd::PlatformSpecificAssets & GetPlatformSpecificAssets() const { return platformSpecificAssets; }
#endif
/**
@@ -737,10 +760,12 @@ private:
gd::ObjectGroupsContainer objectGroups; ///< Global objects groups
gd::String author; ///< Game author name
gd::String packageName; ///< Game package name
gd::String orientation; ///< Lock game orientation (on mobile devices). "default", "landscape" or "portrait".
bool folderProject; ///< True if folder project, false if single file project.
gd::String gameFile; ///< File of the game
gd::String latestCompilationDirectory; ///< File of the game
gd::Platform* currentPlatform; ///< The platform being used to edit the project.
gd::PlatformSpecificAssets platformSpecificAssets;
std::vector < std::unique_ptr<gd::ExternalEvents> > externalEvents; ///< List of all externals events
mutable unsigned int GDMajorVersion; ///< The GD major version used the last time the project was saved.
mutable unsigned int GDMinorVersion; ///< The GD minor version used the last time the project was saved.

View File

@@ -105,7 +105,7 @@ bool ResourcesManager::HasResource(const gd::String & name) const
return false;
}
std::vector<gd::String> ResourcesManager::GetAllResourcesList()
std::vector<gd::String> ResourcesManager::GetAllResourceNames()
{
std::vector<gd::String> allResources;
for (std::size_t i = 0;i<resources.size();++i)
@@ -146,7 +146,7 @@ bool ResourcesManager::AddResource(const gd::String & name, const gd::String & f
return true;
}
std::vector<gd::String> ResourceFolder::GetAllResourcesList()
std::vector<gd::String> ResourceFolder::GetAllResourceNames()
{
std::vector<gd::String> allResources;
for (std::size_t i = 0;i<resources.size();++i)
@@ -351,6 +351,25 @@ bool ResourcesManager::MoveResourceDownInList(const gd::String & name)
return gd::MoveResourceDownInList(resources, name);
}
std::size_t ResourcesManager::GetResourcePosition(const gd::String & name) const
{
for (std::size_t i = 0;i<resources.size();++i)
{
if (resources[i]->GetName() == name) return i;
}
return gd::String::npos;
}
void ResourcesManager::MoveResource(std::size_t oldIndex, std::size_t newIndex)
{
if ( oldIndex >= resources.size() || newIndex >= resources.size() )
return;
auto resource = resources[oldIndex];
resources.erase(resources.begin() + oldIndex);
resources.insert(resources.begin() + newIndex, resource);
}
bool ResourcesManager::MoveFolderUpInList(const gd::String & name)
{
for (std::size_t i =1;i<folders.size();++i)

View File

@@ -293,9 +293,9 @@ public:
std::shared_ptr<Resource> CreateResource(const gd::String & kind);
/**
* \brief Get a list containing the name of all of the resources.
* \brief Get a list containing the names of all resources.
*/
std::vector<gd::String> GetAllResourcesList();
std::vector<gd::String> GetAllResourceNames();
#if defined(GD_IDE_ONLY)
/**
@@ -324,6 +324,11 @@ public:
*/
void RenameResource(const gd::String & oldName, const gd::String & newName);
/**
* \brief Return the position of the layer called "name" in the layers list
*/
std::size_t GetResourcePosition(const gd::String & name) const;
/**
* \brief Move a resource up in the list
*/
@@ -334,6 +339,11 @@ public:
*/
bool MoveResourceDownInList(const gd::String & name);
/**
* Change the position of the specified resource.
*/
void MoveResource(std::size_t oldIndex, std::size_t newIndex);
/**
* \brief Return true if the folder exists.
*/
@@ -445,7 +455,7 @@ public:
/**
* Get a list containing the name of all of the resources.
*/
virtual std::vector<gd::String> GetAllResourcesList();
virtual std::vector<gd::String> GetAllResourceNames();
/**
* Move a resource up in the list

View File

@@ -84,7 +84,7 @@ TEST_CASE( "Resources", "[common][resources]" ) {
gd::ProjectResourcesAdder::RemoveAllUselessImages(project);
std::vector<gd::String> remainingResources =
project.GetResourcesManager().GetAllResourcesList();
project.GetResourcesManager().GetAllResourceNames();
REQUIRE(remainingResources.size() == 2);
REQUIRE(remainingResources[0] == "res1");
REQUIRE(remainingResources[1] == "res4");

View File

@@ -21,7 +21,7 @@ class GD_EXTENSION_API AdMobObject : public gd::Object
public:
AdMobObject(gd::String name_);
virtual ~AdMobObject() {};
virtual std::unique_ptr<gd::Object> Clone() const { return gd::make_unique<AdMobObject>(*this); }
virtual std::unique_ptr<gd::Object> Clone() const override { return gd::make_unique<AdMobObject>(*this); }
#if !defined(GD_NO_WX_GUI)
void DrawInitialInstance(gd::InitialInstance & instance, sf::RenderTarget & renderTarget, gd::Project & project, gd::Layout & layout) override;

View File

@@ -322,13 +322,13 @@ PathBehaviorEditor::PathBehaviorEditor(wxWindow* parent, gd::Project & game_, gd
followAngleCheck->SetValue(behavior.FollowAngle());
//Setup shared datas
if ( !scene || scene->behaviorsInitialSharedDatas.find(behavior.GetName()) == scene->behaviorsInitialSharedDatas.end())
if (!scene || !scene->HasBehaviorSharedData(behavior.GetName()))
{
gd::LogError(_("Unable to access to shared datas."));
return;
}
sharedDatas = std::dynamic_pointer_cast<ScenePathDatas>(scene->behaviorsInitialSharedDatas[behavior.GetName()]);
sharedDatas = std::dynamic_pointer_cast<ScenePathDatas>(scene->GetBehaviorSharedDataSmartPtr(behavior.GetName()));
if ( sharedDatas == std::shared_ptr<ScenePathDatas>() )
{

View File

@@ -241,13 +241,13 @@ scene(scene_)
}
//Setup shared datas
if ( !scene || scene->behaviorsInitialSharedDatas.find(behavior.GetName()) == scene->behaviorsInitialSharedDatas.end())
if (!scene || !scene->HasBehaviorSharedData(behavior.GetName()))
{
gd::LogError(_("Unable to access to shared datas."));
return;
}
sharedDatas = std::dynamic_pointer_cast<ScenePhysicsDatas>(scene->behaviorsInitialSharedDatas[behavior.GetName()]);
sharedDatas = std::dynamic_pointer_cast<ScenePhysicsDatas>(scene->GetBehaviorSharedDataSmartPtr(behavior.GetName()));
if ( sharedDatas == std::shared_ptr<ScenePhysicsDatas>() )
{

View File

@@ -6,9 +6,46 @@ This project is released under the MIT License.
*/
#include "ScenePhysicsDatas.h"
#include "GDCore/Tools/Localization.h"
#include "GDCore/Serialization/SerializerElement.h"
#if defined(GD_IDE_ONLY)
#include <map>
#include "GDCore/String.h"
#include "GDCore/CommonTools.h"
#include "GDCore/Project/Project.h"
#include "GDCore/IDE/Dialogs/PropertyDescriptor.h"
#endif
#if defined(GD_IDE_ONLY)
std::map<gd::String, gd::PropertyDescriptor> ScenePhysicsDatas::GetProperties(gd::Project & project) const
{
std::map<gd::String, gd::PropertyDescriptor> properties;
properties[_("Gravity on X axis (in m/s²)")].SetValue(gd::String::From(gravityX));
properties[_("Gravity on Y axis (in m/s²)")].SetValue(gd::String::From(gravityY));
properties[_("X Scale: number of pixels for 1 meter")].SetValue(gd::String::From(scaleX));
properties[_("Y Scale: number of pixels for 1 meter")].SetValue(gd::String::From(scaleY));
return properties;
}
bool ScenePhysicsDatas::UpdateProperty(const gd::String & name, const gd::String & value, gd::Project & project)
{
if (name == _("Gravity on X axis (in m/s²)")) {
gravityX = value.To<float>();
}
if (name == _("Gravity on Y axis (in m/s²)")) {
gravityY = value.To<float>();
}
if (name == _("X scale: number of pixels for 1 meter")) {
scaleX = value.To<float>();
}
if (name == _("Y scale: number of pixels for 1 meter")) {
scaleY = value.To<float>();
}
return true;
}
void ScenePhysicsDatas::SerializeTo(gd::SerializerElement & element) const
{
element.SetAttribute("gravityX", gravityX);

View File

@@ -17,7 +17,7 @@ This project is released under the MIT License.
class ScenePhysicsDatas : public gd::BehaviorsSharedData
{
public:
ScenePhysicsDatas() : BehaviorsSharedData(), gravityX(0), gravityY(0), scaleX(100), scaleY(100)
ScenePhysicsDatas() : BehaviorsSharedData(), gravityX(0), gravityY(9), scaleX(100), scaleY(100)
{
};
virtual ~ScenePhysicsDatas() {};
@@ -34,6 +34,8 @@ public:
}
#if defined(GD_IDE_ONLY)
virtual std::map<gd::String, gd::PropertyDescriptor> GetProperties(gd::Project & project) const;
virtual bool UpdateProperty(const gd::String & name, const gd::String & value, gd::Project & project);
virtual void SerializeTo(gd::SerializerElement & element) const;
#endif

View File

@@ -450,7 +450,7 @@ bool RuntimeScene::LoadFromSceneAndCustomInstances( const gd::Layout & scene, co
//Behaviors shared data
std::cout << ".";
behaviorsSharedDatas.LoadFrom(scene.behaviorsInitialSharedDatas);
behaviorsSharedDatas.LoadFrom(scene.GetAllBehaviorSharedData());
std::cout << ".";
//Extensions specific initialization

View File

@@ -97,8 +97,9 @@ bool Exporter::ExportWholePixiProject(gd::Project & project, gd::String exportDi
bool minify, bool exportForCordova)
{
ExporterHelper helper(fs, gdjsRoot, codeOutputDir);
gd::Project exportedProject = project;
auto exportProject = [this, &project, &minify,
auto exportProject = [this, &exportedProject, &minify,
&exportForCordova, &helper](gd::String exportDir)
{
wxProgressDialog * progressDialogPtr = NULL;
@@ -111,8 +112,6 @@ bool Exporter::ExportWholePixiProject(gd::Project & project, gd::String exportDi
fs.MkDir(exportDir);
std::vector<gd::String> includesFiles;
gd::Project exportedProject = project;
//Export the resources (before generating events as some resources filenames may be updated)
helper.ExportResources(fs, exportedProject, exportDir, progressDialogPtr);
@@ -168,11 +167,12 @@ bool Exporter::ExportWholePixiProject(gd::Project & project, gd::String exportDi
{
//Prepare the export directory
fs.MkDir(exportDir);
if (!helper.ExportCordovaConfigFile(project, exportDir))
return false;
if (!exportProject(exportDir + "/www"))
return false;
if (!helper.ExportCordovaConfigFile(exportedProject, exportDir))
return false;
} else {
if (!exportProject(exportDir))
return false;

View File

@@ -156,10 +156,42 @@ bool ExporterHelper::ExportPixiIndexFile(gd::String source, gd::String exportDir
bool ExporterHelper::ExportCordovaConfigFile(const gd::Project & project, gd::String exportDir)
{
auto & platformSpecificAssets = project.GetPlatformSpecificAssets();
auto & resourceManager = project.GetResourcesManager();
auto getIconFilename = [&resourceManager, &platformSpecificAssets](const gd::String & platform, const gd::String & name) {
const gd::String & file = resourceManager.GetResource(platformSpecificAssets.Get(platform, name)).GetFile();
return file.empty() ? "" : "www/" + file;
};
gd::String str = fs.ReadFile(gdjsRoot + "/Runtime/Cordova/config.xml")
.FindAndReplace("GDJS_PROJECTNAME", project.GetName())
.FindAndReplace("GDJS_PACKAGENAME", project.GetPackageName())
.FindAndReplace("GDJS_ORIENTATION", "default");
.FindAndReplace("GDJS_ORIENTATION", project.GetOrientation())
// Android icons
.FindAndReplace("GDJS_ICON_ANDROID_36", getIconFilename("android", "icon-36"))
.FindAndReplace("GDJS_ICON_ANDROID_48", getIconFilename("android", "icon-48"))
.FindAndReplace("GDJS_ICON_ANDROID_72", getIconFilename("android", "icon-72"))
.FindAndReplace("GDJS_ICON_ANDROID_96", getIconFilename("android", "icon-96"))
.FindAndReplace("GDJS_ICON_ANDROID_144", getIconFilename("android", "icon-144"))
.FindAndReplace("GDJS_ICON_ANDROID_192", getIconFilename("android", "icon-192"))
// iOS icons
.FindAndReplace("GDJS_ICON_IOS_180", getIconFilename("ios", "icon-180"))
.FindAndReplace("GDJS_ICON_IOS_60", getIconFilename("ios", "icon-60"))
.FindAndReplace("GDJS_ICON_IOS_120", getIconFilename("ios", "icon-120"))
.FindAndReplace("GDJS_ICON_IOS_76", getIconFilename("ios", "icon-76"))
.FindAndReplace("GDJS_ICON_IOS_152", getIconFilename("ios", "icon-152"))
.FindAndReplace("GDJS_ICON_IOS_40", getIconFilename("ios", "icon-40"))
.FindAndReplace("GDJS_ICON_IOS_80", getIconFilename("ios", "icon-80"))
.FindAndReplace("GDJS_ICON_IOS_57", getIconFilename("ios", "icon-57"))
.FindAndReplace("GDJS_ICON_IOS_114", getIconFilename("ios", "icon-114"))
.FindAndReplace("GDJS_ICON_IOS_72", getIconFilename("ios", "icon-72"))
.FindAndReplace("GDJS_ICON_IOS_144", getIconFilename("ios", "icon-144"))
.FindAndReplace("GDJS_ICON_IOS_167", getIconFilename("ios", "icon-167"))
.FindAndReplace("GDJS_ICON_IOS_29", getIconFilename("ios", "icon-29"))
.FindAndReplace("GDJS_ICON_IOS_58", getIconFilename("ios", "icon-58"))
.FindAndReplace("GDJS_ICON_IOS_50", getIconFilename("ios", "icon-50"))
.FindAndReplace("GDJS_ICON_IOS_100", getIconFilename("ios", "icon-100"))
;
if (!fs.WriteToFile(exportDir + "/config.xml", str))
{
@@ -196,14 +228,14 @@ bool ExporterHelper::ExportCocos2dFiles(const gd::Project & project, gd::String
std::vector<gd::String> noIncludesInThisFile;
if (!CompleteIndexFile(str, customCss, customHtml, exportDir, noIncludesInThisFile, ""))
{
lastError = "Unable to complete Cocos2d index.html file.";
lastError = "Unable to complete Cocos2d-JS index.html file.";
return false;
}
//Write the index.html file
if (!fs.WriteToFile(exportDir + "/index.html", str))
{
lastError = "Unable to write Cocos2d index.html file.";
lastError = "Unable to write Cocos2d-JS index.html file.";
return false;
}
}
@@ -229,7 +261,7 @@ bool ExporterHelper::ExportCocos2dFiles(const gd::Project & project, gd::String
if (!fs.WriteToFile(exportDir + "/project.json", str))
{
lastError = "Unable to write Cocos2d project.json file.";
lastError = "Unable to write Cocos2d-JS project.json file.";
return false;
}
}

View File

@@ -12,10 +12,46 @@
<allow-intent href="geo:*" />
<platform name="android">
<allow-intent href="market:*" />
<icon src="GDJS_ICON_ANDROID_36" density="ldpi" />
<icon src="GDJS_ICON_ANDROID_48" density="mdpi" />
<icon src="GDJS_ICON_ANDROID_72" density="hdpi" />
<icon src="GDJS_ICON_ANDROID_96" density="xhdpi" />
<icon src="GDJS_ICON_ANDROID_144" density="xxhdpi" />
<icon src="GDJS_ICON_ANDROID_192" density="xxxhdpi" />
</platform>
<platform name="ios">
<allow-intent href="itms:*" />
<allow-intent href="itms-apps:*" />
<!-- iOS 8.0+ -->
<!-- iPhone 6 Plus -->
<icon src="GDJS_ICON_IOS_180" width="180" height="180" />
<!-- iOS 7.0+ -->
<!-- iPhone / iPod Touch -->
<icon src="GDJS_ICON_IOS_60" width="60" height="60" />
<icon src="GDJS_ICON_IOS_120" width="120" height="120" />
<!-- iPad -->
<icon src="GDJS_ICON_IOS_76" width="76" height="76" />
<icon src="GDJS_ICON_IOS_152" width="152" height="152" />
<!-- Spotlight Icon -->
<icon src="GDJS_ICON_IOS_40" width="40" height="40" />
<icon src="GDJS_ICON_IOS_80" width="80" height="80" />
<!-- iOS 6.1 -->
<!-- iPhone / iPod Touch -->
<icon src="GDJS_ICON_IOS_57" width="57" height="57" />
<icon src="GDJS_ICON_IOS_114" width="114" height="114" />
<!-- iPad -->
<icon src="GDJS_ICON_IOS_72" width="72" height="72" />
<icon src="GDJS_ICON_IOS_144" width="144" height="144" />
<!-- iPad Pro -->
<icon src="GDJS_ICON_IOS_167" width="167" height="167" />
<!-- iPhone Spotlight and Settings Icon -->
<icon src="GDJS_ICON_IOS_29" width="29" height="29" />
<icon src="GDJS_ICON_IOS_58" width="58" height="58" />
<!-- iPad Spotlight and Settings Icon -->
<icon src="GDJS_ICON_IOS_50" width="50" height="50" />
<icon src="GDJS_ICON_IOS_100" width="100" height="100" />
<!-- iPad Pro -->
<icon src="GDJS_ICON_IOS_167" width="167" height="167" />
</platform>
<preference name="orientation" value="GDJS_ORIENTATION" />
</widget>

View File

@@ -161,7 +161,6 @@ gdjs.RuntimeScene.prototype.unloadScene = function() {
this._allInstancesList = [];
this._instancesRemoved = [];
this._renderer = new gdjs.RuntimeSceneRenderer(this, this._runtimeGame ? this._runtimeGame.getRenderer() : null);
this._lastId = 0;
this._eventsContext = null;

View File

@@ -13,6 +13,7 @@ declare type gdExternalEvents = EmscriptenObject;
declare type gdSerializerElement = EmscriptenObject;
declare type gdInitialInstance = EmscriptenObject;
declare type gdBaseEvent = EmscriptenObject;
declare type gdResource = EmscriptenObject;
//Represents all objects that have serializeTo and unserializeFrom methods.
declare type gdSerializable = EmscriptenObject;

View File

@@ -25,27 +25,8 @@
<meta name="theme-color" content="#4ab0e4">
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<!-- Fullstory user analytics -->
<script>
window['_fs_debug'] = false;
window['_fs_host'] = 'fullstory.com';
window['_fs_org'] = '8DWZ1';
window['_fs_namespace'] = 'FS';
(function(m,n,e,t,l,o,g,y){
if (e in m) {if(m.console && m.console.log) { m.console.log('FullStory namespace conflict. Please set window["_fs_namespace"].');} return;}
g=m[e]=function(a,b){g.q?g.q.push([a,b]):g._api(a,b);};g.q=[];
o=n.createElement(t);o.async=1;o.src='https://'+_fs_host+'/s/fs.js';
y=n.getElementsByTagName(t)[0];y.parentNode.insertBefore(o,y);
g.identify=function(i,v){g(l,{uid:i});if(v)g(l,v)};g.setUserVars=function(v){g(l,v)};
g.identifyAccount=function(i,v){o='account';v=v||{};v.acctId=i;g(o,v)};
g.clearUserCookie=function(c,d,i){if(!c || document.cookie.match('fs_uid=[`;`]*`[`;`]*`[`;`]*`')){
d=n.domain;while(1){n.cookie='fs_uid=;domain='+d+
';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>
<!-- Stripe.com Checkout -->
<script src="https://checkout.stripe.com/checkout.js"></script>
</head>
<body>

View File

@@ -9,6 +9,9 @@ import { findGDJS } from './LocalGDJSFinder';
import localFileSystem from './LocalFileSystem';
import LocalFolderPicker from '../../UI/LocalFolderPicker';
import HelpButton from '../../UI/HelpButton';
import { displaySanityCheck } from '../SanityChecker';
import { getSanityMessages } from '../SanityChecker/CordovaSanityChecker';
import { translate } from 'react-i18next';
import assignIn from 'lodash/assignIn';
import optionalRequire from '../../Utils/OptionalRequire';
import Window from '../../Utils/Window';
@@ -17,7 +20,7 @@ const shell = electron ? electron.shell : null;
const gd = global.gd;
export default class LocalCordovaExport extends Component {
class LocalCordovaExport extends Component {
state = {
exportFinishedDialogOpen: false,
outputDir: '',
@@ -53,11 +56,13 @@ export default class LocalCordovaExport extends Component {
};
launchExport = () => {
const { project } = this.props;
const { t, project } = this.props;
if (!project) return;
sendExportLaunched('local-cordova');
if (!displaySanityCheck(t, getSanityMessages(t, project))) return;
const outputDir = this.state.outputDir;
project.setLastCompilationDirectory(outputDir);
@@ -89,7 +94,7 @@ export default class LocalCordovaExport extends Component {
};
render() {
const { project } = this.props;
const { t, project } = this.props;
if (!project) return null;
return (
@@ -125,7 +130,7 @@ export default class LocalCordovaExport extends Component {
/>
</Line>
<Dialog
title="Export finished"
title={t('Export finished')}
actions={[
<FlatButton
key="open"
@@ -162,10 +167,12 @@ export default class LocalCordovaExport extends Component {
fullWidth
primary
onClick={() => this.openPhoneGapBuild()}
label="Open PhoneGap Build"
label={t('Open PhoneGap Build')}
/>
</Dialog>
</Column>
);
}
}
export default translate()(LocalCordovaExport);

View File

@@ -25,6 +25,9 @@ import Window from '../../../Utils/Window';
import { delay } from '../../../Utils/Delay';
import CreateProfile from '../../../Profile/CreateProfile';
import LimitDisplayer from '../../../Profile/LimitDisplayer';
import { displaySanityCheck } from '../../SanityChecker';
import { getSanityMessages } from '../../SanityChecker/CordovaSanityChecker';
import { translate, type TranslatorProps } from 'react-i18next';
const path = optionalRequire('path');
const os = optionalRequire('os');
const electron = optionalRequire('electron');
@@ -51,7 +54,7 @@ type State = {
errored: boolean,
};
type Props = WithUserProfileProps & {
type Props = WithUserProfileProps & TranslatorProps & {
project: gdProject,
onChangeSubscription: Function,
};
@@ -97,7 +100,7 @@ class LocalOnlineCordovaExport extends Component<Props, State> {
};
launchExport = (): Promise<string> => {
const { project } = this.props;
const { project, t } = this.props;
if (!project) return Promise.reject();
return LocalOnlineCordovaExport.prepareExporter()
@@ -114,7 +117,7 @@ class LocalOnlineCordovaExport extends Component<Props, State> {
return outputDir;
})
.catch(err => {
showErrorBox('Unable to export the game', err);
showErrorBox(t('Unable to export the game'), err);
throw err;
});
};
@@ -198,8 +201,12 @@ class LocalOnlineCordovaExport extends Component<Props, State> {
};
launchWholeExport = () => {
const { t, project } = this.props;
sendExportLaunched('local-online-cordova');
if (!displaySanityCheck(t, getSanityMessages(t, project)))
return;
const handleError = (message: string) => err => {
if (!this.state.errored) {
this.setState({
@@ -226,33 +233,33 @@ class LocalOnlineCordovaExport extends Component<Props, State> {
exportStep: 'compress',
});
return this.launchCompression(outputDir);
}, handleError('Error while exporting the game.'))
}, handleError(t('Error while exporting the game.')))
.then(outputFile => {
this.setState({
exportStep: 'upload',
});
return this.launchUpload(outputFile);
}, handleError('Error while compressing the game.'))
}, handleError(t('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.'))
}, handleError(t('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.'))
}, handleError(t('Error while lauching the build of the game.')))
.then(build => {
this.setState({
exportStep: 'done',
build,
});
this.props.onRefreshUserProfile();
}, handleError('Error while building the game.'));
}, handleError(t('Error while building the game.')));
};
_download = () => {
@@ -291,6 +298,7 @@ class LocalOnlineCordovaExport extends Component<Props, State> {
onLogin,
subscription,
limits,
t,
} = this.props;
if (!project) return null;
@@ -302,13 +310,12 @@ class LocalOnlineCordovaExport extends Component<Props, State> {
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.
{t("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"
label={t("Package for Android")}
primary
onClick={this.launchWholeExport}
disabled={disableBuild}
@@ -324,7 +331,7 @@ class LocalOnlineCordovaExport extends Component<Props, State> {
)}
{!authenticated && (
<CreateProfile
message="Create an account to build your game for Android in one-click:"
message={t("Create an account to build your game for Android in one-click:")}
onLogin={onLogin}
/>
)}
@@ -347,6 +354,6 @@ class LocalOnlineCordovaExport extends Component<Props, State> {
}
}
export default withUserProfile({ fetchLimits: true, fetchSubscription: true })(
export default translate()(withUserProfile({ fetchLimits: true, fetchSubscription: true })(
LocalOnlineCordovaExport
);
));

View File

@@ -0,0 +1,30 @@
// @flow
import { type TFunction } from 'react-i18next';
import { type SanityMessages } from './index';
export const getSanityMessages = (t: TFunction, project: gdProject): SanityMessages => {
let errors = [];
let warnings = [];
if (!project.getPackageName()) {
errors.push(
t(
'The package name is empty. Choose and enter a package name in the game properties.'
)
);
} else if (project.getPackageName().length >= 255) {
errors.push(t('The package name is too long.'));
}
if (!project.getName()) {
errors.push(
t(
'The game name is empty. Choose and enter a name in the game properties.'
)
);
}
return {
errors,
warnings,
};
};

View File

@@ -0,0 +1,25 @@
// @flow
import { type TFunction } from 'react-i18next';
import { showErrorBox } from '../../UI/Messages/MessageBox';
export type SanityMessages = {
errors: Array<string>,
warnings: Array<string>,
};
export const displaySanityCheck = (
t: TFunction,
messages: SanityMessages
): boolean => {
if (messages.errors.length) {
showErrorBox(
t(
'Your game has some invalid elements, please fix these before continuing:'
) +
'\n\n' +
messages.errors.map(message => `- ${message}`).join('\n')
);
}
return !messages.errors.length;
};

View File

@@ -0,0 +1,21 @@
import React from 'react';
import BaseEditor from './BaseEditor';
import ResourcesFullEditor from '../../ResourcesEditor';
export default class ResourcesEditor extends BaseEditor {
updateToolbar() {
if (this.editor) this.editor.updateToolbar();
}
render() {
const { project } = this.props;
return (
<ResourcesFullEditor
{...this.props}
ref={editor => (this.editor = editor)}
project={project}
/>
);
}
}

View File

@@ -16,8 +16,10 @@ import PreferencesDialog from './Preferences/PreferencesDialog';
import ConfirmCloseDialog from './ConfirmCloseDialog';
import AboutDialog, { type UpdateStatus } from './AboutDialog';
import ProjectManager from '../ProjectManager';
import PlatformSpecificAssetsDialog from '../PlatformSpecificAssetsEditor/PlatformSpecificAssetsDialog';
import LoaderModal from '../UI/LoaderModal';
import EditorBar from '../UI/EditorBar';
import CloseConfirmDialog from '../UI/CloseConfirmDialog';
import ProfileDialog from '../Profile/ProfileDialog';
import Window from '../Utils/Window';
import { showErrorBox } from '../UI/Messages/MessageBox';
@@ -47,6 +49,7 @@ import ExternalEventsEditor from './Editors/ExternalEventsEditor';
import SceneEditor from './Editors/SceneEditor';
import ExternalLayoutEditor from './Editors/ExternalLayoutEditor';
import StartPage from './Editors/StartPage';
import ResourcesEditor from './Editors/ResourcesEditor';
import {
type PreferencesState,
getThemeName,
@@ -55,6 +58,7 @@ import {
} from './Preferences/PreferencesHandler';
import ErrorBoundary from '../UI/ErrorBoundary';
import SubscriptionDialog from '../Profile/SubscriptionDialog';
import ResourcesLoader from '../ResourcesLoader/index';
const gd = global.gd;
@@ -87,6 +91,7 @@ type State = {|
updateStatus: UpdateStatus,
aboutDialogOpen: boolean,
onSubscriptionDialogClosed: ?Function,
platformSpecificAssetsDialogOpen: boolean,
|};
export default class MainFrame extends Component<*, State> {
@@ -111,6 +116,7 @@ export default class MainFrame extends Component<*, State> {
updateStatus: { message: '', status: 'unknown' },
aboutDialogOpen: false,
onSubscriptionDialogClosed: null,
platformSpecificAssetsDialogOpen: false,
};
toolbar = null;
confirmCloseDialog: any = null;
@@ -139,6 +145,11 @@ export default class MainFrame extends Component<*, State> {
loadFromProject = (project: gdProject, cb: Function) => {
this.closeProject(() => {
// Make sure that the ResourcesLoader cache is emptied, so that
// the URL to a resource with a name in the old project is not re-used
// for another resource with the same name in the new project.
ResourcesLoader.burstUrlsCache();
this.setState(
{
currentProject: project,
@@ -241,7 +252,11 @@ export default class MainFrame extends Component<*, State> {
const name = newNameGenerator('NewScene', name =>
currentProject.hasLayoutNamed(name)
);
currentProject.insertNewLayout(name, currentProject.getLayoutsCount());
const newLayout = currentProject.insertNewLayout(
name,
currentProject.getLayoutsCount()
);
newLayout.updateBehaviorsSharedData(currentProject);
this.forceUpdate();
};
@@ -541,6 +556,36 @@ export default class MainFrame extends Component<*, State> {
);
};
openResources = () => {
this.setState(
{
editorTabs: openEditorTab(this.state.editorTabs, {
name: 'Resources',
editorCreator: () => (
<ResourcesEditor
project={this.state.currentProject}
setToolbar={this.setEditorToolbar}
onDeleteResource={(resource: gdResource, cb: boolean => void) => {
// TODO: Project wide refactoring of objects/events using the resource
cb(true);
}}
onRenameResource={(
resource: gdResource,
newName: string,
cb: boolean => void
) => {
// TODO: Project wide refactoring of objects/events using the resource
cb(true);
}}
/>
),
key: 'resources',
}),
},
() => this.updateToolbar()
);
};
openStartPage = () => {
this.setState(
{
@@ -724,6 +769,12 @@ export default class MainFrame extends Component<*, State> {
});
};
openPlatformSpecificAssets = (open: boolean = true) => {
this.setState({
platformSpecificAssetsDialogOpen: open,
});
};
setUpdateStatus = (status: UpdateStatus) => {
this.setState({
updateStatus: status,
@@ -810,6 +861,9 @@ export default class MainFrame extends Component<*, State> {
onCloseProject={this.askToCloseProject}
onExportProject={this.openExportDialog}
onOpenPreferences={() => this.openPreferences(true)}
onOpenResources={() => this.openResources()}
onOpenPlatformSpecificAssets={() =>
this.openPlatformSpecificAssets()}
/>
)}
</Drawer>
@@ -892,6 +946,16 @@ export default class MainFrame extends Component<*, State> {
open: this.state.saveDialogOpen,
onClose: () => this._openSaveDialog(false),
})}
{!!this.state.currentProject && (
<PlatformSpecificAssetsDialog
project={this.state.currentProject}
open={this.state.platformSpecificAssetsDialogOpen}
onApply={() => this.openPlatformSpecificAssets(false)}
onClose={() => this.openPlatformSpecificAssets(false)}
resourceSources={resourceSources}
onChooseResource={this._onChooseResource}
/>
)}
{!!genericDialog &&
React.cloneElement(genericDialog, {
open: this.state.genericDialogOpen,
@@ -908,7 +972,7 @@ export default class MainFrame extends Component<*, State> {
open={profileDialogOpen}
authentification={authentification}
onClose={() => this.openProfile(false)}
onChangeSubscription={(onDone) => this.openSubscription(true, onDone)}
onChangeSubscription={onDone => this.openSubscription(true, onDone)}
/>
<SubscriptionDialog
onClose={() => {
@@ -933,6 +997,7 @@ export default class MainFrame extends Component<*, State> {
onClose={() => this.openAboutDialog(false)}
updateStatus={updateStatus}
/>
<CloseConfirmDialog shouldPrompt={!!this.state.currentProject} />
</div>
</Providers>
);

View File

@@ -2,7 +2,7 @@ import React, { Component } from 'react';
import TextField from 'material-ui/TextField';
import Checkbox from 'material-ui/Checkbox';
import { Line, Column } from '../../UI/Grid';
import ResourcesLoader from '../../ObjectsRendering/ResourcesLoader';
import ResourcesLoader from '../../ResourcesLoader';
import ResourceSelectorWithThumbnail from '../ResourceSelectorWithThumbnail';
const gd = global.gd;

View File

@@ -15,7 +15,7 @@ import MiniToolbar from '../../../UI/MiniToolbar';
import DragHandle from '../../../UI/DragHandle';
import ContextMenu from '../../../UI/Menu/ContextMenu';
import { showWarningBox } from '../../../UI/Messages/MessageBox';
import ResourcesLoader from '../../../ObjectsRendering/ResourcesLoader';
import ResourcesLoader from '../../../ResourcesLoader';
import PointsEditor from './PointsEditor';
import { deleteSpritesFromAnimation } from './Utils/SpriteObjectHelper';

View File

@@ -1,7 +1,7 @@
import React, { Component } from 'react';
import TextField from 'material-ui/TextField';
import { Line, Column } from '../../UI/Grid';
import ResourcesLoader from '../../ObjectsRendering/ResourcesLoader';
import ResourcesLoader from '../../ResourcesLoader';
import ResourceSelectorWithThumbnail from '../ResourceSelectorWithThumbnail';
const gd = global.gd;

View File

@@ -1,4 +1,6 @@
import React from 'react';
// @flow
import * as React from 'react';
import ResourcesLoader from '../ResourcesLoader';
const MARGIN = 50;
@@ -29,12 +31,50 @@ const styles = {
},
};
export default class ImagePreview extends React.Component {
state = {
errored: false,
imageWidth: null,
imageHeight: null,
};
type Props = {|
project: gdProject,
resourceName: string,
resourcesLoader: typeof ResourcesLoader,
children?: any,
style?: Object,
|};
type State = {|
errored: boolean,
imageWidth: ?number,
imageHeight: ?number,
imageSource: ?string,
|};
export default class ImagePreview extends React.Component<Props, State> {
_container: ?HTMLDivElement = null;
constructor(props: Props) {
super(props);
this.state = this._loadFrom(props);
}
componentWillReceiveProps(newProps: Props) {
if (
newProps.resourceName !== this.props.resourceName ||
newProps.project !== this.props.project ||
newProps.resourcesLoader !== this.props.resourcesLoader
) {
this.setState(this._loadFrom(newProps));
}
}
_loadFrom(props: Props): State {
const { project, resourceName, resourcesLoader } = props;
return {
errored: false,
imageWidth: null,
imageHeight: null,
imageSource:
resourcesLoader.getResourceFullUrl(project, resourceName),
};
}
componentDidMount() {
if (this._container) {
@@ -49,7 +89,7 @@ export default class ImagePreview extends React.Component {
});
};
_handleImageLoaded = (e, t) => {
_handleImageLoaded = (e: any) => {
const imgElement = e.target;
this.setState({
@@ -59,15 +99,9 @@ export default class ImagePreview extends React.Component {
};
render() {
const {
project,
resourceName,
resourcesLoader,
style,
children,
} = this.props;
const { resourceName, style, children } = this.props;
const { imageHeight, imageWidth } = this.state;
const { imageHeight, imageWidth, imageSource } = this.state;
const overlayStyle = {
...styles.overlayContainer,
@@ -86,9 +120,10 @@ export default class ImagePreview extends React.Component {
<img
style={styles.spriteThumbnailImage}
alt={resourceName}
src={resourcesLoader.getResourceFullFilename(project, resourceName)}
src={imageSource}
onError={this._handleError}
onLoad={this._handleImageLoaded}
crossOrigin="anonymous"
/>
)}
{canDisplayOverlays &&

View File

@@ -58,13 +58,14 @@ const ThemableImageThumbnail = ({
}}
onContextMenu={e => {
e.stopPropagation();
onContextMenu(e.clientX, e.clientY);
if (onContextMenu) onContextMenu(e.clientX, e.clientY);
}}
>
<img
style={styles.spriteThumbnailImage}
alt={resourceName}
src={resourcesLoader.getResourceFullFilename(project, resourceName)}
src={resourcesLoader.getResourceFullUrl(project, resourceName)}
crossOrigin="anonymous"
/>
{selectable && (
<div style={styles.checkboxContainer}>

View File

@@ -1,6 +1,6 @@
import React from 'react';
import ResourcesLoader from '../ObjectsRendering/ResourcesLoader';
import ResourceSelector from '../ResourcesEditor/ResourceSelector';
import ResourcesLoader from '../ResourcesLoader';
import ResourceSelector from '../ResourcesList/ResourceSelector';
import ImageThumbnail from './ImageThumbnail';
export default ({
@@ -10,6 +10,7 @@ export default ({
resourceKind,
resourceName,
onChange,
floatingLabelText,
}) => {
return (
<div style={{ flex: 1, display: 'flex', alignItems: 'flex-end' }}>
@@ -22,6 +23,7 @@ export default ({
fullWidth
initialResourceName={resourceName}
onChange={onChange}
floatingLabelText={floatingLabelText}
/>
</div>
<ImageThumbnail

View File

@@ -257,7 +257,7 @@ export default class ObjectsListContainer extends React.Component<
const { object: pasteObject, global } = objectWithContext;
const { object: copiedObject, type, name } = Clipboard.get(CLIPBOARD_KIND);
const { project, objectsContainer } = this.props;
const { project, objectsContainer, onObjectPasted } = this.props;
const newName = newNameGenerator(
'CopyOf' + name,
@@ -287,6 +287,7 @@ export default class ObjectsListContainer extends React.Component<
);
this.forceUpdate();
if (onObjectPasted) onObjectPasted(newObject);
};
_editName = (objectWithContext: ?ObjectWithContext) => {

View File

@@ -7,7 +7,7 @@ import RenderedTextInstance from './Renderers/RenderedTextInstance';
import RenderedShapePainterInstance from './Renderers/RenderedShapePainterInstance';
import RenderedTextEntryInstance from './Renderers/RenderedTextEntryInstance';
import PixiResourcesLoader from './PixiResourcesLoader';
import ResourcesLoader from './ResourcesLoader';
import ResourcesLoader from '../ResourcesLoader';
/**
* A service containing functions that are called to render instances

View File

@@ -1,6 +1,7 @@
// @flow
import slugs from 'slugs';
import PIXI from 'pixi.js';
import ResourcesLoader from './ResourcesLoader';
import ResourcesLoader from '../ResourcesLoader';
import { loadFontFace } from '../Utils/FontFaceLoader';
const gd = global.gd;
@@ -9,7 +10,7 @@ const loadedTextures = {};
const invalidTexture = PIXI.Texture.fromImage('res/error48.png');
export default class PixiResourcesLoader {
static _initializeTexture(resource, texture) {
static _initializeTexture(resource: gdResource, texture: any) {
if (resource.getKind() !== 'image') return;
const imageResource = gd.asImageResource(resource);
@@ -18,15 +19,19 @@ export default class PixiResourcesLoader {
}
}
static loadTextures(project, onProgress, onComplete) {
static loadTextures(
project: gdProject,
onProgress: (number, number) => void,
onComplete: () => void
) {
const resourcesManager = project.getResourcesManager();
const loader = PIXI.loader;
const resourcesList = resourcesManager.getAllResourcesList().toJSArray();
const resourcesList = resourcesManager.getAllResourceNames().toJSArray();
const allResources = {};
resourcesList.forEach(resourceName => {
const resource = resourcesManager.getResource(resourceName);
const filename = ResourcesLoader.getResourceFullFilename(
const filename = ResourcesLoader.getResourceFullUrl(
project,
resourceName
);
@@ -72,7 +77,7 @@ export default class PixiResourcesLoader {
* should listen to PIXI.Texture `update` event, and refresh your object
* if this event is triggered.
*/
static getPIXITexture(project, resourceName) {
static getPIXITexture(project: gdProject, resourceName: string) {
if (loadedTextures[resourceName]) {
return loadedTextures[resourceName];
}
@@ -84,7 +89,7 @@ export default class PixiResourcesLoader {
if (resource.getKind() !== 'image') return invalidTexture;
loadedTextures[resourceName] = PIXI.Texture.fromImage(
ResourcesLoader.getResourceFullFilename(project, resourceName),
ResourcesLoader.getResourceFullUrl(project, resourceName),
true /* Treats request as cross-origin */
);
@@ -100,14 +105,17 @@ export default class PixiResourcesLoader {
* @returns a Promise that resolves with the font-family to be used
* to render a text with the font.
*/
static loadFontFamily(project, fontFilename) {
static loadFontFamily(
project: gdProject,
fontFilename: string
): Promise<string> {
// Avoid reloading a font if it's already cached
if (loadedFontFamilies[fontFilename]) {
return Promise.resolve(loadedFontFamilies[fontFilename]);
}
const fontFamily = slugs(fontFilename);
const fullFilename = ResourcesLoader.getFullFilename(project, fontFilename);
const fullFilename = ResourcesLoader.getFullUrl(project, fontFilename);
return loadFontFace(
fontFamily,
`url("${fullFilename}")`,
@@ -124,7 +132,7 @@ export default class PixiResourcesLoader {
* The font won't be loaded.
* @returns The font-family to be used to render a text with the font.
*/
static getFontFamily(project, fontFilename) {
static getFontFamily(project: gdProject, fontFilename: string) {
if (loadedFontFamilies[fontFilename]) {
return loadedFontFamilies[fontFilename];
}

View File

@@ -376,7 +376,7 @@ RenderedPanelSpriteInstance.getThumbnail = function(
) {
const panelSprite = gd.asPanelSpriteObject(object);
return resourcesLoader.getResourceFullFilename(
return resourcesLoader.getResourceFullUrl(
project,
panelSprite.getTexture()
);

View File

@@ -69,7 +69,7 @@ RenderedSpriteInstance.getThumbnail = function(
.getDirection(0)
.getSprite(0)
.getImageName();
return resourcesLoader.getResourceFullFilename(project, imageName);
return resourcesLoader.getResourceFullUrl(project, imageName);
}
return 'res/unknown32.png';

View File

@@ -55,7 +55,7 @@ RenderedTiledSpriteInstance.getThumbnail = function(
) {
var tiledSprite = gd.asTiledSpriteObject(object);
return resourcesLoader.getResourceFullFilename(
return resourcesLoader.getResourceFullUrl(
project,
tiledSprite.getTexture()
);

View File

@@ -1,84 +0,0 @@
import optionalRequire from '../Utils/OptionalRequire.js';
const electron = optionalRequire('electron');
const path = optionalRequire('path');
class FilenamesCache {
constructor() {
this.projectCache = {};
}
_getProjectCache(project) {
const cache = this.projectCache[project.ptr];
if (!cache) {
return (this.projectCache[project.ptr] = {});
}
return cache;
}
getSystemFilename(project, filename) {
const cache = this._getProjectCache(project);
return cache[filename];
}
cacheSystemFilename(project, filename, systemFilename) {
const cache = this._getProjectCache(project);
return (cache[filename] = systemFilename);
}
}
export default class ResourceLoader {
static _cache = new FilenamesCache();
static isURL(filename) {
return (
filename.indexOf('http://') === 0 ||
filename.indexOf('https://') === 0 ||
filename.indexOf('ftp://') === 0
);
}
/**
* Get the fully qualified URL/filename for a filename relative to the project.
*/
static getFullFilename(project, filename) {
const cachedSystemFilename = ResourceLoader._cache.getSystemFilename(
project,
filename
);
if (cachedSystemFilename) return cachedSystemFilename;
if (electron && !ResourceLoader.isURL(filename)) {
// Support local filesystem with Electron
const file = project.getProjectFile();
const projectPath = path.dirname(file);
const resourceAbsolutePath = path
.resolve(projectPath, filename)
.replace(/\\/g, '/');
console.info('Loading', resourceAbsolutePath);
return this._cache.cacheSystemFilename(
project,
filename,
'file://' + resourceAbsolutePath
);
}
return this._cache.cacheSystemFilename(project, filename, filename);
}
/**
* Get the fully qualified URL/filename associated with the given resource.
*/
static getResourceFullFilename(project, resourceName) {
if (project.getResourcesManager().hasResource(resourceName)) {
const resourceRelativePath = project
.getResourcesManager()
.getResource(resourceName)
.getFile();
return ResourceLoader.getFullFilename(project, resourceRelativePath);
}
return resourceName;
}
}

View File

@@ -0,0 +1,25 @@
//@flow
import optionalRequire from '../Utils/OptionalRequire';
const Jimp = optionalRequire('jimp');
export const isResizeSupported = () => !!Jimp;
export const resizeImage = (
inputFile: string,
outputFile: string,
{ width, height }: { width: number, height: number }
): Promise<any> => {
if (!Jimp) return Promise.resolve(false);
return Jimp.read(inputFile)
.then(function(jimpImage) {
return jimpImage.contain(width, height).write(outputFile);
})
.then(() => {
return true;
})
.catch(function(err) {
console.error(err);
return false;
});
};

View File

@@ -0,0 +1,255 @@
// @flow
import * as React from 'react';
import path from 'path';
import FlatButton from 'material-ui/FlatButton';
import RaisedButton from 'material-ui/RaisedButton';
import Dialog from '../UI/Dialog';
import { Line } from '../UI/Grid';
import ResourcesLoader from '../ResourcesLoader';
import ResourceSelectorWithThumbnail from '../ObjectEditor/ResourceSelectorWithThumbnail';
import {
type ResourceSource,
type ChooseResourceFunction,
} from '../ResourcesList/ResourceSource.flow';
import { resizeImage, isResizeSupported } from './ImageResizer';
import { showErrorBox } from '../UI/Messages/MessageBox';
const gd = global.gd;
type Props = {|
project: gdProject,
open: boolean,
onClose: Function,
onApply: Function,
resourceSources: Array<ResourceSource>,
onChooseResource: ChooseResourceFunction,
|};
type State = {|
androidIconResourceNames: Array<string>,
iosIconResourceNames: Array<string>,
|};
const androidSizes = [192, 144, 96, 72, 48, 36];
const iosSizes = [
180,
167,
152,
144,
120,
114,
100,
80,
76,
72,
60,
58,
57,
50,
40,
29,
];
export default class PlatformSpecificAssetsDialog extends React.Component<
Props,
State
> {
constructor(props: Props) {
super(props);
this.state = this._loadFrom(props.project);
}
_loadFrom(project: gdProject): State {
return {
androidIconResourceNames: androidSizes.map(size =>
project.getPlatformSpecificAssets().get('android', `icon-${size}`)
),
iosIconResourceNames: iosSizes.map(size =>
project.getPlatformSpecificAssets().get('ios', `icon-${size}`)
),
};
}
componentWillReceiveProps(newProps: Props) {
if (
(!this.props.open && newProps.open) ||
(newProps.open && this.props.project !== newProps.project)
) {
this.setState(this._loadFrom(newProps.project));
}
}
_generateFromFile = () => {
const { project, resourceSources, onChooseResource } = this.props;
const sources = resourceSources.filter(source => source.kind === 'image');
if (!sources.length) return;
onChooseResource(sources[0].name, false).then(resources => {
if (!resources.length) {
return;
}
const resourcesManager = project.getResourcesManager();
const projectPath = path.dirname(project.getProjectFile());
const fullPath = path.resolve(projectPath, resources[0].getFile());
Promise.all([
...androidSizes.map(size =>
resizeImage(
fullPath,
path.join(projectPath, `android-icon-${size}.png`),
{
width: size,
height: size,
}
)
),
...iosSizes.map(size =>
resizeImage(
fullPath,
path.join(projectPath, `ios-icon-${size}.png`),
{
width: size,
height: size,
}
)
),
]).then(results => {
if (results.indexOf(false) !== -1) {
showErrorBox('Some icons could not be generated!');
return;
}
const createOrUpdateResource = name => {
if (!resourcesManager.hasResource(name)) {
const imageResource = new gd.ImageResource();
imageResource.setFile(name);
imageResource.setName(name);
resourcesManager.addResource(imageResource);
imageResource.delete();
} else {
resourcesManager.getResource(name).setFile(name);
}
};
androidSizes.forEach(size =>
createOrUpdateResource(`android-icon-${size}.png`)
);
iosSizes.forEach(size =>
createOrUpdateResource(`ios-icon-${size}.png`)
);
ResourcesLoader.burstUrlsCache();
setTimeout(() => {
this.setState({
androidIconResourceNames: androidSizes.map(
size => `android-icon-${size}.png`
),
iosIconResourceNames: iosSizes.map(size => `ios-icon-${size}.png`),
});
}, 200 /* Let a bit of time so that image files can be found */);
});
});
};
_onApply = () => {
const { project } = this.props;
const { androidIconResourceNames, iosIconResourceNames } = this.state;
androidSizes.forEach((size, index) => {
project
.getPlatformSpecificAssets()
.set('android', `icon-${size}`, androidIconResourceNames[index]);
});
iosSizes.forEach((size, index) => {
project
.getPlatformSpecificAssets()
.set('ios', `icon-${size}`, iosIconResourceNames[index]);
});
this.props.onApply();
};
render() {
const actions = [
<FlatButton
label="Cancel"
primary={false}
onClick={this.props.onClose}
/>,
<FlatButton
label="Apply"
primary={true}
keyboardFocused={true}
onClick={this._onApply}
/>,
];
const { project, resourceSources, onChooseResource } = this.props;
const { androidIconResourceNames, iosIconResourceNames } = this.state;
return (
<Dialog
actions={actions}
open={this.props.open}
onRequestClose={this.props.onClose}
autoScrollBodyContent
>
<Line justifyContent="center">
{isResizeSupported() ? (
<RaisedButton
primary
label="Generate icons from a file"
onClick={this._generateFromFile}
/>
) : (
<p>
Download GDevelop desktop version to generate the Android and iOS
icons of your game.
</p>
)}
</Line>
<p>Android icons:</p>
{androidSizes.map((size, index) => (
<ResourceSelectorWithThumbnail
key={size}
floatingLabelText={`Android icon (${size}x${size} px)`}
project={project}
resourceSources={resourceSources}
onChooseResource={onChooseResource}
resourceKind="image"
resourceName={androidIconResourceNames[index]}
resourcesLoader={ResourcesLoader}
onChange={resourceName => {
const newIcons = [...androidIconResourceNames];
newIcons[index] = resourceName;
this.setState({
androidIconResourceNames: newIcons,
});
}}
/>
))}
<p>iOS (iPhone and iPad) icons:</p>
{iosSizes.map((size, index) => (
<ResourceSelectorWithThumbnail
key={size}
floatingLabelText={`iOS icon (${size}x${size} px)`}
project={project}
resourceSources={resourceSources}
onChooseResource={onChooseResource}
resourceKind="image"
resourceName={iosIconResourceNames[index]}
resourcesLoader={ResourcesLoader}
onChange={resourceName => {
const newIcons = [...iosIconResourceNames];
newIcons[index] = resourceName;
this.setState({
iosIconResourceNames: newIcons,
});
}}
/>
))}
</Dialog>
);
}
}

View File

@@ -1,21 +1,6 @@
// @flow
import { mapFor } from '../Utils/MapFor';
//TODO: Layout, ExternalEvents and ExternalLayout should be moved to a common type definition file
//for all GDevelop.js
type Layout = {
getName: Function,
setName: Function,
};
type ExternalLayout = {
getName: Function,
setName: Function,
};
type ExternalEvents = {
getName: Function,
setName: Function,
};
export const enumerateLayouts = (project: any) =>
mapFor(0, project.getLayoutsCount(), i => project.getLayoutAt(i));
@@ -30,7 +15,7 @@ export const enumerateExternalLayouts = (project: any) =>
);
export const filterProjectItemsList = (
list: Array<Layout> | Array<ExternalLayout> | Array<ExternalEvents>,
list: Array<gdLayout> | Array<gdExternalLayout> | Array<gdExternalEvents>,
searchText: string
) => {
if (!searchText) return list;

View File

@@ -1,25 +1,48 @@
import React, { Component } from 'react';
// @flow
import * as React from 'react';
import FlatButton from 'material-ui/FlatButton';
import TextField from 'material-ui/TextField';
import SelectField from 'material-ui/SelectField';
import MenuItem from 'material-ui/MenuItem';
import Dialog from '../UI/Dialog';
export default class ProjectPropertiesDialog extends Component {
constructor(props) {
type Props = {|
project: gdProject,
open: boolean,
onClose: Function,
onApply: Function,
|};
type State = {|
windowDefaultWidth: number,
windowDefaultHeight: number,
name: string,
author: string,
packageName: string,
orientation: string,
|};
export default class ProjectPropertiesDialog extends React.Component<
Props,
State
> {
constructor(props: Props) {
super(props);
this.state = { ...this._loadFrom(props.project) };
this.state = this._loadFrom(props.project);
}
_loadFrom(project) {
_loadFrom(project: gdProject): State {
return {
windowDefaultWidth: project.getMainWindowDefaultWidth(),
windowDefaultHeight: project.getMainWindowDefaultHeight(),
name: project.getName(),
author: project.getAuthor(),
packageName: project.getPackageName(),
orientation: project.getOrientation(),
};
}
componentWillReceiveProps(newProps) {
componentWillReceiveProps(newProps: Props) {
if (
(!this.props.open && newProps.open) ||
(newProps.open && this.props.project !== newProps.project)
@@ -30,12 +53,22 @@ export default class ProjectPropertiesDialog extends Component {
_onApply = () => {
const { project } = this.props;
project.setDefaultWidth(this.state.windowDefaultWidth);
project.setDefaultHeight(this.state.windowDefaultHeight);
project.setName(this.state.name);
project.setAuthor(this.state.author);
project.setPackageName(this.state.packageName);
if (this.props.onApply) this.props.onApply();
const {
windowDefaultWidth,
windowDefaultHeight,
name,
author,
packageName,
orientation,
} = this.state;
project.setDefaultWidth(windowDefaultWidth);
project.setDefaultHeight(windowDefaultHeight);
project.setName(name);
project.setAuthor(author);
project.setPackageName(packageName);
project.setOrientation(orientation);
this.props.onApply();
};
render() {
@@ -52,6 +85,14 @@ export default class ProjectPropertiesDialog extends Component {
onClick={this._onApply}
/>,
];
const {
name,
windowDefaultWidth,
windowDefaultHeight,
author,
packageName,
orientation,
} = this.state;
return (
<Dialog
@@ -59,20 +100,19 @@ export default class ProjectPropertiesDialog extends Component {
open={this.props.open}
onRequestClose={this.props.onClose}
autoScrollBodyContent={true}
contentStyle={{ width: '350px' }}
>
<TextField
floatingLabelText="Game name"
fullWidth
type="text"
value={this.state.name}
value={name}
onChange={(e, value) => this.setState({ name: value })}
/>
<TextField
floatingLabelText="Game's window width"
fullWidth
type="number"
value={this.state.windowDefaultWidth}
value={windowDefaultWidth}
onChange={(e, value) =>
this.setState({
windowDefaultWidth: Math.max(0, parseInt(value, 10)),
@@ -82,7 +122,7 @@ export default class ProjectPropertiesDialog extends Component {
floatingLabelText="Game's window height"
fullWidth
type="number"
value={this.state.windowDefaultHeight}
value={windowDefaultHeight}
onChange={(e, value) =>
this.setState({
windowDefaultHeight: Math.max(0, parseInt(value, 10)),
@@ -93,7 +133,7 @@ export default class ProjectPropertiesDialog extends Component {
fullWidth
hintText="Your name"
type="text"
value={this.state.author}
value={author}
onChange={(e, value) => this.setState({ author: value })}
/>
<TextField
@@ -101,16 +141,19 @@ export default class ProjectPropertiesDialog extends Component {
fullWidth
hintText="com.example.mygame"
type="text"
value={this.state.packageName}
value={packageName}
onChange={(e, value) => this.setState({ packageName: value })}
/>
<TextField
floatingLabelText="Icon"
<SelectField
fullWidth
type="text"
disabled
value="Coming soon"
/>
floatingLabelText="Device orientation (for iOS and Android)"
value={orientation}
onChange={(e, i, value) => this.setState({orientation: value})}
>
<MenuItem value="default" primaryText="Platform default" />
<MenuItem value="landscape" primaryText="Landscape" />
<MenuItem value="portrait" primaryText="Portrait" />
</SelectField>
</Dialog>
);
}

View File

@@ -1,4 +1,5 @@
import React, { Component } from 'react';
// @flow
import * as React from 'react';
import { List, ListItem } from 'material-ui/List';
import TextField from 'material-ui/TextField';
import SearchBar from 'material-ui-search-bar';
@@ -65,7 +66,10 @@ const ThemableProjectStructureItem = ({ muiTheme, ...otherProps }) => (
const ProjectStructureItem = muiThemeable()(ThemableProjectStructureItem);
class ThemableItem extends Component {
class ThemableItem extends React.Component<*, *> {
textField: ?Object;
_iconMenu: ?Object;
componentDidUpdate(prevProps) {
if (!prevProps.editingName && this.props.editingName) {
setTimeout(() => {
@@ -126,7 +130,7 @@ class ThemableItem extends Component {
onKeyPress={event => {
if (event.charCode === 13) {
// enter key pressed
this.textField.blur();
if (this.textField) this.textField.blur();
this.props.onRename(event.target.value);
}
}}
@@ -157,33 +161,65 @@ const Item = muiThemeable()(ThemableItem);
const AddItem = makeAddItem(ListItem);
export default class ProjectManager extends React.Component {
type Props = {|
project: gdProject,
onDeleteLayout: gdLayout => void,
onDeleteExternalEvents: gdExternalEvents => void,
onDeleteExternalLayout: gdExternalLayout => void,
onRenameLayout: (string, string) => void,
onRenameExternalEvents: (string, string) => void,
onRenameExternalLayout: (string, string) => void,
onOpenLayout: string => void,
onOpenExternalEvents: string => void,
onOpenExternalLayout: string => void,
onSaveProject: () => void,
onCloseProject: () => void,
onExportProject: () => void,
onOpenPreferences: () => void,
onOpenResources: () => void,
onAddLayout: () => void,
onAddExternalEvents: () => void,
onAddExternalLayout: () => void,
onOpenPlatformSpecificAssets: () => void,
|};
type State = {|
renamedItemKind: ?string,
renamedItemName: string,
searchText: string,
projectPropertiesDialogOpen: boolean,
variablesEditorOpen: boolean,
|};
export default class ProjectManager extends React.Component<Props, State> {
state = {
renamedItemKind: null,
renamedItemName: '',
searchText: '',
projectPropertiesDialogOpen: false,
variablesEditorOpen: false,
};
_onEditName = (kind, name) => {
_onEditName = (kind: ?string, name: string) => {
this.setState({
renamedItemKind: kind,
renamedItemName: name,
});
};
_copyLayout = layout => {
_copyLayout = (layout: gdLayout) => {
Clipboard.set(LAYOUT_CLIPBOARD_KIND, {
layout: serializeToJSObject(layout),
name: layout.getName(),
});
};
_cutLayout = layout => {
_cutLayout = (layout: gdLayout) => {
this._copyLayout(layout);
this.props.onDeleteLayout(layout);
};
_pasteLayout = index => {
_pasteLayout = (index: number) => {
if (!Clipboard.has(LAYOUT_CLIPBOARD_KIND)) return;
const { layout: copiedLayout, name } = Clipboard.get(LAYOUT_CLIPBOARD_KIND);
@@ -202,23 +238,24 @@ export default class ProjectManager extends React.Component {
project
);
newLayout.setName(newName);
newLayout.updateBehaviorsSharedData(project);
this.forceUpdate();
};
_copyExternalEvents = externalEvents => {
_copyExternalEvents = (externalEvents: gdExternalEvents) => {
Clipboard.set(EXTERNAL_EVENTS_CLIPBOARD_KIND, {
externalEvents: serializeToJSObject(externalEvents),
name: externalEvents.getName(),
});
};
_cutExternalEvents = externalEvents => {
_cutExternalEvents = (externalEvents: gdExternalEvents) => {
this._copyExternalEvents(externalEvents);
this.props.onDeleteExternalEvents(externalEvents);
};
_pasteExternalEvents = index => {
_pasteExternalEvents = (index: number) => {
if (!Clipboard.has(EXTERNAL_EVENTS_CLIPBOARD_KIND)) return;
const { externalEvents: copiedExternalEvents, name } = Clipboard.get(
@@ -243,19 +280,19 @@ export default class ProjectManager extends React.Component {
this.forceUpdate();
};
_copyExternalLayout = externalLayout => {
_copyExternalLayout = (externalLayout: gdExternalLayout) => {
Clipboard.set(EXTERNAL_LAYOUT_CLIPBOARD_KIND, {
externalLayout: serializeToJSObject(externalLayout),
name: externalLayout.getName(),
});
};
_cutExternalLayout = externalLayout => {
_cutExternalLayout = (externalLayout: gdExternalLayout) => {
this._copyExternalLayout(externalLayout);
this.props.onDeleteExternalLayout(externalLayout);
};
_pasteExternalLayout = index => {
_pasteExternalLayout = (index: number) => {
if (!Clipboard.has(EXTERNAL_LAYOUT_CLIPBOARD_KIND)) return;
const { externalLayout: copiedExternalLayout, name } = Clipboard.get(
@@ -327,10 +364,6 @@ export default class ProjectManager extends React.Component {
<div style={styles.container}>
<List style={styles.list}>
{this._renderMenu()}
{/* <ProjectStructureItem
primaryText="Resources"
leftIcon={<ListIcon src="res/ribbon_default/image32.png" />}
/> */}
<ProjectStructureItem
primaryText="Game settings"
leftIcon={
@@ -353,6 +386,18 @@ export default class ProjectManager extends React.Component {
leftIcon={<ListIcon src="res/ribbon_default/editname32.png" />}
onClick={() => this.setState({ variablesEditorOpen: true })}
/>,
<ListItem
key="icons"
primaryText="Icons"
leftIcon={<ListIcon src="res/ribbon_default/image32.png" />}
onClick={() => this.props.onOpenPlatformSpecificAssets()}
/>,
<ListItem
key="resources"
primaryText="Resources"
leftIcon={<ListIcon src="res/ribbon_default/image32.png" />}
onClick={() => this.props.onOpenResources()}
/>,
]}
/>
<ProjectStructureItem
@@ -366,7 +411,7 @@ export default class ProjectManager extends React.Component {
enumerateLayouts(project),
searchText
)
.map((layout, i) => {
.map((layout: gdLayout, i: number) => {
const name = layout.getName();
return (
<Item

View File

@@ -0,0 +1,52 @@
// @flow
import * as React from 'react';
import TextField from 'material-ui/TextField';
type State = {
focused: boolean,
text: ?string,
};
/**
* This component works like a material-ui TextField, except that
* the value passed as props is not forced into the text field when the user
* is typing. This is useful if the parent component can do modifications on the value:
* the user won't be interrupted or have the value changed until he blurs the field.
*/
export default class SemiControlledTextField extends React.Component<*, State> {
state = {
focused: false,
text: null,
};
render() {
const { value, onChange, ...otherProps } = this.props;
return (
<TextField
{...otherProps}
value={this.state.focused ? this.state.text : value}
onFocus={() => {
this.setState({
focused: true,
text: this.props.value,
});
}}
onChange={(event, newValue) => {
this.setState({
text: newValue,
});
onChange(newValue);
}}
onBlur={event => {
onChange(event.target.value);
this.setState({
focused: false,
text: null,
});
}}
/>
);
}
}

View File

@@ -1,5 +1,5 @@
import React, { Component } from 'react';
import TextField from 'material-ui/TextField';
import SemiControlledTextField from './SemiControlledTextField';
import Checkbox from 'material-ui/Checkbox';
import Subheader from 'material-ui/Subheader';
import FlatButton from 'material-ui/FlatButton';
@@ -53,13 +53,13 @@ export default class PropertiesEditor extends Component {
);
} else if (field.valueType === 'number') {
return (
<TextField
<SemiControlledTextField
value={this._getFieldValue(this.props.instances, field)}
key={field.name}
id={field.name}
floatingLabelText={field.name}
floatingLabelFixed={true}
onChange={(event, newValue) => {
floatingLabelFixed
onChange={newValue => {
this.props.instances.forEach(i =>
field.setValue(i, parseFloat(newValue) || 0)
);
@@ -72,16 +72,17 @@ export default class PropertiesEditor extends Component {
);
} else {
return (
<TextField
<SemiControlledTextField
value={this._getFieldValue(
this.props.instances,
field,
'(Multiple values)'
)}
key={field.name}
id={field.name}
floatingLabelText={field.name}
floatingLabelFixed={true}
onChange={(event, newValue) => {
floatingLabelFixed
onChange={newValue => {
this.props.instances.forEach(i =>
field.setValue(i, newValue || '')
);
@@ -107,7 +108,7 @@ export default class PropertiesEditor extends Component {
value={this._getFieldValue(this.props.instances, field)}
key={field.name}
floatingLabelText={field.name}
floatingLabelFixed={true}
floatingLabelFixed
onChange={(event, index, newValue) => {
this.props.instances.forEach(i =>
field.setValue(i, parseFloat(newValue) || 0)
@@ -130,7 +131,7 @@ export default class PropertiesEditor extends Component {
)}
key={field.name}
floatingLabelText={field.name}
floatingLabelFixed={true}
floatingLabelFixed
onChange={(event, index, newValue) => {
this.props.instances.forEach(i =>
field.setValue(i, newValue || '')

View File

@@ -0,0 +1,101 @@
// @flow
import * as React from 'react';
import Paper from 'material-ui/Paper';
import EmptyMessage from '../../UI/EmptyMessage';
import PropertiesEditor from '../../PropertiesEditor';
import ImagePreview from '../../ObjectEditor/ImagePreview'; //TODO: Move ImagePreview out of ObjectEditor
import ResourcesLoader from '../../ResourcesLoader';
const styles = {
container: {
display: 'flex',
flexDirection: 'column',
flex: 1,
width: '100%',
},
imagePreview: { flex: 1 },
propertiesContainer: {
padding: 10,
overflowY: 'scroll',
overflowX: 'hidden',
flex: 2,
},
};
type Props = {|
project: gdProject,
resourcesLoader: typeof ResourcesLoader,
resources: Array<gdResource>,
|};
export default class ResourcePropertiesEditor extends React.Component<
Props,
{}
> {
schema = [
{
name: 'Resource name',
valueType: 'string',
disabled: true,
getValue: (resource: gdResource) => resource.getName(),
setValue: (resource: gdResource, newValue: string) =>
resource.setName(newValue),
},
{
name: 'File',
valueType: 'string',
getValue: (resource: gdResource) => resource.getFile(),
setValue: (resource: gdResource, newValue: string) =>
resource.setFile(newValue),
},
];
_renderEmpty() {
return (
<EmptyMessage>
Resources are automatically added to your project whenever you add an
image to an object. Choose a resource to display its properties.
</EmptyMessage>
);
}
_renderResourcesProperties() {
const { resources } = this.props;
return (
<div
style={styles.propertiesContainer}
key={resources.map(resource => '' + resource.ptr).join(';')}
>
<PropertiesEditor schema={this.schema} instances={resources} />
</div>
);
}
_renderPreview() {
const { resources, project, resourcesLoader } = this.props;
if (!resources || !resources.length) return;
return (
<ImagePreview
style={styles.imagePreview}
resourceName={resources[0].getName()}
resourcesLoader={resourcesLoader}
project={project}
/>
);
}
render() {
const { resources } = this.props;
return (
<Paper style={styles.container}>
{this._renderPreview()}
{!resources || !resources.length
? this._renderEmpty()
: this._renderResourcesProperties()}
</Paper>
);
}
}

View File

@@ -0,0 +1,39 @@
// @flow
import React, { PureComponent } from 'react';
import { translate, type TranslatorProps } from 'react-i18next';
import { ToolbarGroup } from 'material-ui/Toolbar';
import ToolbarIcon from '../UI/ToolbarIcon';
import ToolbarSeparator from '../UI/ToolbarSeparator';
type Props = {|
onDeleteSelection: () => void,
canDelete: boolean,
onOpenProperties: () => void,
|} & TranslatorProps;
type State = {||};
export class Toolbar extends PureComponent<Props, State> {
render() {
const { t, canDelete } = this.props;
return (
<ToolbarGroup lastChild>
<ToolbarIcon
onClick={this.props.onOpenProperties}
src="res/ribbon_default/editprop32.png"
tooltip={t('Open the properties panel')}
/>
<ToolbarSeparator />
<ToolbarIcon
onClick={this.props.onDeleteSelection}
src="res/ribbon_default/deleteselected32.png"
disabled={!canDelete}
tooltip={t('Delete the selected resource')}
/>
</ToolbarGroup>
);
}
}
export default translate()(Toolbar);

View File

@@ -0,0 +1,154 @@
// @flow
import * as React from 'react';
import ResourcesList from '../ResourcesList';
import ResourcePropertiesEditor from './ResourcePropertiesEditor';
import Toolbar from './Toolbar';
import EditorMosaic, { MosaicWindow } from '../UI/EditorMosaic';
import InfoBar from '../UI/Messages/InfoBar';
import ResourcesLoader from '../ResourcesLoader';
const styles = {
container: {
display: 'flex',
flex: 1,
position: 'relative',
overflow: 'hidden',
},
};
type State = {
showPropertiesInfoBar: boolean,
selectedResource: ?gdResource,
};
type Props = {
setToolbar: React.Node => void,
project: gdProject,
onDeleteResource: (resource: gdResource, cb: (boolean) => void) => void,
onRenameResource: (
resource: gdResource,
newName: string,
cb: (boolean) => void
) => void,
};
export default class InstancesFullEditor extends React.Component<Props, State> {
static defaultProps = {
setToolbar: () => {},
};
editorMosaic: ?EditorMosaic = null;
_propertiesEditor: ?ResourcePropertiesEditor = null;
_resourcesList: ?ResourcesList = null;
resourcesLoader = ResourcesLoader;
state = {
showPropertiesInfoBar: false,
selectedResource: null,
};
updateToolbar() {
this.props.setToolbar(
<Toolbar
onOpenProperties={this.openProperties}
canDelete={!!this.state.selectedResource}
onDeleteSelection={() =>
this.deleteResource(this.state.selectedResource)}
/>
);
}
deleteResource = (resource: ?gdResource) => {
const { project, onDeleteResource } = this.props;
if (!resource) return;
//eslint-disable-next-line
const answer = confirm(
"Are you sure you want to remove this resource? This can't be undone."
);
if (!answer) return;
onDeleteResource(resource, doRemove => {
if (!doRemove || !resource) return;
project.getResourcesManager().removeResource(resource.getName());
this.setState(
{
selectedResource: null,
},
() => {
if (this._resourcesList) this._resourcesList.forceUpdateList();
this.updateToolbar();
}
);
});
};
openProperties = () => {
if (!this.editorMosaic) return;
if (!this.editorMosaic.openEditor('properties')) {
this.setState({
showPropertiesInfoBar: true,
});
}
};
_onResourceSelected = (selectedResource: ?gdResource) => {
this.setState(
{
selectedResource,
},
() => {
if (this._propertiesEditor) this._propertiesEditor.forceUpdate();
this.updateToolbar();
}
);
};
render() {
const { project, onRenameResource } = this.props;
const { selectedResource } = this.state;
const editors = {
properties: (
<MosaicWindow
title="Properties"
// Pass resources to force MosaicWindow update when selectedResource is changed
resources={selectedResource ? [selectedResource] : []}
>
<ResourcePropertiesEditor
key={selectedResource ? selectedResource.ptr : undefined}
resources={selectedResource ? [selectedResource] : []}
project={project}
resourcesLoader={this.resourcesLoader}
ref={propertiesEditor =>
(this._propertiesEditor = propertiesEditor)}
/>
</MosaicWindow>
),
'resources-list': (
<ResourcesList
project={project}
onDeleteResource={this.deleteResource}
onRenameResource={onRenameResource}
onSelectResource={this._onResourceSelected}
selectedResource={selectedResource}
ref={resourcesList => (this._resourcesList = resourcesList)}
/>
),
};
return (
<div style={styles.container}>
<EditorMosaic
editors={editors}
ref={editorMosaic => (this.editorMosaic = editorMosaic)}
initialEditorNames={['properties', 'resources-list']}
/>
<InfoBar
message="Properties panel is already opened"
show={!!this.state.showPropertiesInfoBar}
/>
</div>
);
}
}

View File

@@ -0,0 +1,19 @@
// @flow
export const filterResourcesList = (
list: Array<gdResource>,
searchText: string
): Array<gdResource> => {
if (!searchText) return list;
const lowercaseSearchText = searchText.toLowerCase();
return list.filter((resource: gdResource) => {
return (
resource
.getName()
.toLowerCase()
.indexOf(lowercaseSearchText) !== -1
);
});
};

View File

@@ -20,6 +20,14 @@ export default class ResourceSelector extends Component {
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.initialResourceName !== this.props.initialResourceName) {
this.setState({
resourceName: nextProps.initialResourceName || '',
});
}
}
_getDefaultItems() {
const sources = this.props.resourceSources || [];
return [
@@ -40,7 +48,7 @@ export default class ResourceSelector extends Component {
}
_loadFrom(resourcesManager) {
this.allResourcesNames = resourcesManager.getAllResourcesList().toJSArray();
this.allResourcesNames = resourcesManager.getAllResourceNames().toJSArray();
if (this.props.resourceKind) {
this.allResourcesNames = this.allResourcesNames.filter(resourceName => {
return (

View File

@@ -0,0 +1,14 @@
// @flow
import * as React from 'react';
export type ResourceSource = {
name: string,
displayName: string,
kind: 'image' | 'audio',
component: React.Component<*, *>,
};
export type ChooseResourceFunction = (
sourceName: string,
multiSelection: boolean
) => Promise<Array<gdResource>>;

View File

@@ -0,0 +1,187 @@
// @flow
import * as React from 'react';
import { AutoSizer } from 'react-virtualized';
import SortableVirtualizedItemList from '../UI/SortableVirtualizedItemList';
import Paper from 'material-ui/Paper';
import SearchBar from 'material-ui-search-bar';
import { showWarningBox } from '../UI/Messages/MessageBox';
import { filterResourcesList } from './EnumerateResources';
const styles = {
container: {
flex: 1,
display: 'flex',
height: '100%',
flexDirection: 'column',
},
listContainer: {
flex: 1,
},
};
type State = {|
renamedResource: ?gdResource,
searchText: string,
|};
type Props = {|
project: gdProject,
selectedResource: ?gdResource,
onSelectResource: (resource: gdResource) => void,
onDeleteResource: (resource: gdResource) => void,
onRenameResource: (
resource: gdResource,
newName: string,
cb: (boolean) => void
) => void,
|};
export default class ResourcesList extends React.Component<Props, State> {
static defaultProps = {
onDeleteResource: (resource: gdResource, cb: boolean => void) => cb(true),
onRenameResource: (
resource: gdResource,
newName: string,
cb: boolean => void
) => cb(true),
};
sortableList: any;
state: State = {
renamedResource: null,
searchText: '',
};
shouldComponentUpdate(nextProps: Props, nextState: State) {
// The component is costly to render, so avoid any re-rendering as much
// as possible.
// We make the assumption that no changes to resources list is made outside
// from the component.
// If a change is made, the component won't notice it: you have to manually
// call forceUpdate.
if (
this.state.renamedResource !== nextState.renamedResource ||
this.state.searchText !== nextState.searchText
)
return true;
if (
this.props.project !== nextProps.project ||
this.props.selectedResource !== nextProps.selectedResource
)
return true;
return false;
}
_deleteResource = (resource: gdResource) => {
this.props.onDeleteResource(resource);
};
_editName = (resource: ?gdResource) => {
this.setState(
{
renamedResource: resource,
},
() => this.sortableList.getWrappedInstance().forceUpdateGrid()
);
};
_rename = (resource: gdResource, newName: string) => {
const { project } = this.props;
this.setState({
renamedResource: null,
});
if (resource.getName() === newName) return;
if (project.getResourcesManager().hasResource(newName)) {
showWarningBox('Another resource with this name already exists');
return;
}
this.props.onRenameResource(resource, newName, doRename => {
if (!doRename) return;
resource.setName(newName);
this.forceUpdate();
});
};
_move = (oldIndex: number, newIndex: number) => {
const { project } = this.props;
project.getResourcesManager().moveResource(oldIndex, newIndex);
this.forceUpdateList();
};
forceUpdateList = () => {
this.forceUpdate();
this.sortableList.getWrappedInstance().forceUpdateGrid();
};
_renderResourceMenuTemplate = (resource: gdResource) => {
return [
{
label: 'Rename',
click: () => this._editName(resource),
},
{
label: 'Delete',
click: () => this._deleteResource(resource),
},
];
};
render() {
const { project, selectedResource, onSelectResource } = this.props;
const { searchText } = this.state;
const resourcesManager = project.getResourcesManager();
const allResourcesList = resourcesManager
.getAllResourceNames()
.toJSArray()
.map(resourceName => resourcesManager.getResource(resourceName));
const filteredList = filterResourcesList(allResourcesList, searchText);
// Force List component to be mounted again if project or objectsContainer
// has been changed. Avoid accessing to invalid objects that could
// crash the app.
const listKey = project.ptr;
return (
<Paper style={styles.container}>
<div style={styles.listContainer}>
<AutoSizer>
{({ height, width }) => (
<SortableVirtualizedItemList
key={listKey}
ref={sortableList => (this.sortableList = sortableList)}
fullList={filteredList}
width={width}
height={height}
selectedItem={selectedResource}
onItemSelected={onSelectResource}
renamedItem={this.state.renamedResource}
onRename={this._rename}
onSortEnd={({ oldIndex, newIndex }) =>
this._move(oldIndex, newIndex)}
helperClass="sortable-helper"
distance={30}
buildMenuTemplate={this._renderResourceMenuTemplate}
/>
)}
</AutoSizer>
</div>
<SearchBar
value={searchText}
onRequestSearch={() => {}}
onChange={text =>
this.setState({
searchText: text,
})}
/>
</Paper>
);
}
}

View File

@@ -0,0 +1,107 @@
// @flow
import optionalRequire from '../Utils/OptionalRequire.js';
const electron = optionalRequire('electron');
const path = optionalRequire('path');
class UrlsCache {
projectCache = {};
_getProjectCache(project: gdProject) {
const cache = this.projectCache[project.ptr];
if (!cache) {
return (this.projectCache[project.ptr] = {});
}
return cache;
}
getCachedUrl(project: gdProject, filename: string) {
const cache = this._getProjectCache(project);
return cache[filename];
}
cacheUrl(project: gdProject, url: string) {
const cache = this._getProjectCache(project);
return (cache[url] = url);
}
cacheLocalFileUrl(
project: gdProject,
filename: string,
systemFilename: string
) {
const cache = this._getProjectCache(project);
// The URL is cached with an extra "cache-bursting" parameter.
// If the cache is emptied or changed, local files will have another
// value for this parameter, forcing the browser to reload the images.
return (cache[filename] = `${systemFilename}?cache=${Date.now()}`);
}
}
/**
* A class globally used in the whole IDE to get URLs to resources of games
* (notably images).
*/
export default class ResourcesLoader {
static _cache = new UrlsCache();
static isLocalFile(filename: string): boolean {
return (
filename.indexOf('data:') !== 0 &&
filename.indexOf('http://') !== 0 &&
filename.indexOf('https://') !== 0 &&
filename.indexOf('ftp://') !== 0
);
}
/**
* Re-create a new cache for URLs. Call this to force local
* file to be loaded again.
*/
static burstUrlsCache() {
ResourcesLoader._cache = new UrlsCache();
}
/**
* Get the fully qualified URL/filename for a filename relative to the project.
*/
static getFullUrl(project: gdProject, filename: string) {
const cachedUrl = ResourcesLoader._cache.getCachedUrl(project, filename);
if (cachedUrl) return cachedUrl;
if (electron && ResourcesLoader.isLocalFile(filename)) {
// Support local filesystem with Electron
const file = project.getProjectFile();
const projectPath = path.dirname(file);
const resourceAbsolutePath = path
.resolve(projectPath, filename)
.replace(/\\/g, '/');
console.info('Caching resolved local filename:', resourceAbsolutePath);
return this._cache.cacheLocalFileUrl(
project,
filename,
'file://' + resourceAbsolutePath
);
}
// URLs to non local files are unchanged
return this._cache.cacheUrl(project, filename);
}
/**
* Get the fully qualified URL/filename associated with the given resource.
*/
static getResourceFullUrl(project: gdProject, resourceName: string) {
if (project.getResourcesManager().hasResource(resourceName)) {
const resourceRelativePath = project
.getResourcesManager()
.getResource(resourceName)
.getFile();
return ResourcesLoader.getFullUrl(project, resourceRelativePath);
}
return resourceName;
}
}

View File

@@ -1,9 +1,13 @@
import React, { Component } from 'react';
import FlatButton from 'material-ui/FlatButton';
import TextField from 'material-ui/TextField';
import Dialog from '../../UI/Dialog';
import RaisedButton from 'material-ui/RaisedButton';
import Dialog from '../../UI/Dialog';
import ColorField from '../../UI/ColorField';
import EmptyMessage from '../../UI/EmptyMessage';
import PropertiesEditor from '../../PropertiesEditor';
import propertiesMapToSchema from '../../PropertiesEditor/PropertiesMapToSchema';
import some from 'lodash/some';
export default class ScenePropertiesDialog extends Component {
constructor(props) {
@@ -43,20 +47,50 @@ export default class ScenePropertiesDialog extends Component {
};
render() {
const { layout, project } = this.props;
const actions = [
// TODO: Add support for cancelling modifications made to BehaviorSharedData
// (either by enhancing a function like propertiesMapToSchema or using copies)
// and then re-enable cancel button.
// <FlatButton
// label="Cancel"
// primary={false}
// onClick={this.props.onClose}
// />,
<FlatButton
label="Cancel"
primary={false}
onClick={this.props.onClose}
/>,
<FlatButton
label="Apply"
label="Ok"
key="ok"
primary={true}
keyboardFocused={true}
onClick={this._onApply}
/>,
];
const allBehaviorSharedDataNames = layout
.getAllBehaviorSharedDataNames()
.toJSArray();
const propertiesEditors = allBehaviorSharedDataNames.map(name => {
const sharedData = layout.getBehaviorSharedData(name);
const properties = sharedData.getProperties(project);
const propertiesSchema = propertiesMapToSchema(
properties,
sharedData => sharedData.getProperties(project),
(sharedData, name, value) =>
sharedData.updateProperty(name, value, project)
);
return (
!!propertiesSchema.length && (
<PropertiesEditor
schema={propertiesSchema}
instances={[sharedData]}
/>
)
);
});
return (
<Dialog
actions={actions}
@@ -88,6 +122,13 @@ export default class ScenePropertiesDialog extends Component {
this.props.onClose();
}}
/>
{!some(propertiesEditors) && (
<EmptyMessage>
Any additional properties will appear here if you add behaviors to
objects, like Physics behavior.
</EmptyMessage>
)}
{propertiesEditors}
{this.props.onOpenMoreSettings && (
<RaisedButton
label="Open advanced settings"

View File

@@ -279,9 +279,12 @@ export default class InstancesFullEditor extends Component {
};
_onInstancesMoved = instances => {
this.setState({
history: saveToHistory(this.state.history, this.props.initialInstances),
}, () => this.forceUpdatePropertiesEditor());
this.setState(
{
history: saveToHistory(this.state.history, this.props.initialInstances),
},
() => this.forceUpdatePropertiesEditor()
);
};
_onInstancesModified = instances => {
@@ -476,6 +479,11 @@ export default class InstancesFullEditor extends Component {
});
};
updateBehaviorsSharedData = () => {
const { layout, project } = this.props;
layout.updateBehaviorsSharedData(project);
};
forceUpdateObjectsList = () => {
if (this._objectsList) this._objectsList.forceUpdateList();
};
@@ -544,6 +552,7 @@ export default class InstancesFullEditor extends Component {
onEditObject={this.props.onEditObject || this.editObject}
onDeleteObject={this._onDeleteObject}
onRenameObject={this._onRenameObject}
onObjectPasted={() => this.updateBehaviorsSharedData()}
ref={objectsList => (this._objectsList = objectsList)}
/>
</MosaicWindow>
@@ -581,6 +590,7 @@ export default class InstancesFullEditor extends Component {
onCancel={() => this.editObject(null)}
onApply={() => {
this.editObject(null);
this.updateBehaviorsSharedData();
this.forceUpdateObjectsList();
}}
/>
@@ -672,6 +682,7 @@ export default class InstancesFullEditor extends Component {
/>
<ScenePropertiesDialog
open={!!this.state.scenePropertiesDialogOpen}
project={project}
layout={layout}
onClose={() => this.openSceneProperties(false)}
onApply={() => this.openSceneProperties(false)}

View File

@@ -0,0 +1,61 @@
// @flow
import * as React from 'react';
import optionalRequire from '../Utils/OptionalRequire';
import Window from '../Utils/Window';
const electron = optionalRequire('electron');
type Props = {|
shouldPrompt: boolean,
|};
export default class CloseConfirmDialog extends React.Component<Props, *> {
_delayElectronClose = true;
componentDidMount() {
this._setup(this.props);
}
componentWillReceiveProps(newProps: Props) {
if (newProps.shouldPrompt !== this.props.shouldPrompt)
this._setup(newProps);
}
_setup(props: Props) {
if (Window.isDev()) return; // Don't prevent live-reload in development
const { shouldPrompt } = props;
const message =
'Are you sure you want to quit GDevelop? Any unsaved changes will be lost.';
if (electron) {
window.onbeforeunload = e => {
if (this._delayElectronClose && shouldPrompt) {
//eslint-disable-next-line
const answer = confirm(message);
setTimeout(() => {
if (answer) {
// If answer is positive, re-trigger the close
this._delayElectronClose = false;
electron.remote.getCurrentWindow().close();
}
});
// First, prevents closing the window immediately
e.returnValue = true;
} else {
// Returning undefined will let the window close
}
};
} else if (window) {
if (shouldPrompt) {
window.onbeforeunload = () => message;
} else {
window.onbeforeunload = null;
}
}
}
render() {
return null;
}
}

View File

@@ -0,0 +1,116 @@
import React from 'react';
import { ListItem } from 'material-ui/List';
import IconMenu from '../Menu/IconMenu';
import ListIcon from '../ListIcon';
import IconButton from 'material-ui/IconButton';
import TextField from 'material-ui/TextField';
import MoreVertIcon from 'material-ui/svg-icons/navigation/more-vert';
import muiThemeable from 'material-ui/styles/muiThemeable';
import { type Item } from '.';
const styles = {
itemName: {
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
},
textField: {
top: -16,
},
};
type Props = {
index: number,
item: Item,
onRename: (string) => void,
editingName: boolean,
getThumbnail?: () => string,
selected: true,
onItemSelected: () => void,
buildMenuTemplate: Item => any,
};
class ThemableItemRow extends React.Component<Props, *> {
_renderItemMenu(item) {
return (
<IconMenu
ref={iconMenu => (this._iconMenu = iconMenu)}
iconButtonElement={
<IconButton>
<MoreVertIcon />
</IconButton>
}
buildMenuTemplate={() => this.props.buildMenuTemplate(item)}
/>
);
}
componentDidUpdate(prevProps) {
if (!prevProps.editingName && this.props.editingName) {
setTimeout(() => {
if (this.textField) this.textField.focus();
}, 100);
}
}
_onContextMenu = event => {
if (this._iconMenu) this._iconMenu.open(event);
};
render() {
const { item, selected, style, getThumbnail, muiTheme } = this.props;
const itemName = item.getName();
const label = this.props.editingName ? (
<TextField
id="rename-item-field"
ref={textField => (this.textField = textField)}
defaultValue={itemName}
onBlur={e => this.props.onRename(e.target.value)}
onKeyPress={event => {
if (event.charCode === 13) {
// enter key pressed
this.textField.blur();
}
}}
fullWidth
style={styles.textField}
/>
) : (
<div
style={{
...styles.itemName,
color: selected ? muiTheme.listItem.selectedTextColor : undefined,
}}
>
{itemName}
</div>
);
const itemStyle = {
borderBottom: `1px solid ${muiTheme.listItem.separatorColor}`,
backgroundColor: selected
? muiTheme.listItem.selectedBackgroundColor
: undefined,
};
return (
<ListItem
style={{ ...itemStyle, ...style }}
onContextMenu={this._onContextMenu}
primaryText={label}
leftIcon={getThumbnail && <ListIcon src={getThumbnail()} />}
rightIconButton={this._renderItemMenu(item)}
onClick={() => {
if (!this.props.onItemSelected) return;
if (this.props.editingName) return;
this.props.onItemSelected(selected ? null : item);
}}
/>
);
}
}
const ItemRow = muiThemeable()(ThemableItemRow);
export default ItemRow;

View File

@@ -0,0 +1,108 @@
// @flow
import * as React from 'react';
import { List } from 'react-virtualized';
import { ListItem } from 'material-ui/List';
import ItemRow from './ItemRow';
import { makeAddItem } from '../ListAddItem';
import { SortableContainer, SortableElement } from 'react-sortable-hoc';
const listItemHeight = 48; // TODO: Move this into theme?
const AddItemRow = makeAddItem(ListItem);
const SortableItemRow = SortableElement(props => {
const { style, ...otherProps } = props;
return (
<div style={style}>
<ItemRow {...otherProps} />
</div>
);
});
const SortableAddItemRow = SortableElement(props => {
return <AddItemRow {...props} />;
});
export type Item = {
key: string | number,
getName: () => string,
};
type ItemsListProps = {
height: number,
width: number,
fullList: Array<Item>,
selectedItem: ?Item,
onAddNewItem?: () => void,
onRename: (Item, string) => void,
getThumbnail?: Item => string,
onItemSelected: ?Item => void,
renamedItem: ?Item,
addNewItemLabel: React.Node | string,
buildMenuTemplate: Item => any,
};
class ItemsList extends React.Component<ItemsListProps, *> {
list: any;
forceUpdateGrid() {
if (this.list) this.list.forceUpdateGrid();
}
render() {
const {
height,
width,
fullList,
selectedItem,
addNewItemLabel,
renamedItem,
getThumbnail,
} = this.props;
return (
<List
ref={list => (this.list = list)}
height={height}
rowCount={fullList.length}
rowHeight={listItemHeight}
rowRenderer={({ index, key, style }) => {
const item = fullList[index];
if (item.key === 'add-item-row') {
return (
<SortableAddItemRow
index={fullList.length}
key={key}
style={style}
disabled
onClick={this.props.onAddNewItem}
primaryText={addNewItemLabel}
/>
);
}
const nameBeingEdited = renamedItem === item;
return (
<SortableItemRow
index={index}
key={key}
item={item}
style={style}
onRename={newName => this.props.onRename(item, newName)}
editingName={nameBeingEdited}
getThumbnail={getThumbnail ? () => getThumbnail(item) : undefined}
selected={item === selectedItem}
onItemSelected={this.props.onItemSelected}
buildMenuTemplate={this.props.buildMenuTemplate}
/>
);
}}
width={width}
/>
);
}
}
const SortableItemsList = SortableContainer(ItemsList, { withRef: true });
export default SortableItemsList;

View File

@@ -3,6 +3,10 @@ import i18n from 'i18next';
i18n.init({
fallbackLng: 'en',
// allow keys to be phrases having `:`, `.`
nsSeparator: false,
keySeparator: false,
interpolation: {
escapeValue: false, // Not needed for react
},

View File

@@ -1,89 +1,109 @@
// @flow
import Keen from 'keen-tracking';
import Window from '../Window';
import { getUserUUID } from './UserUUID';
import Authentification from '../GDevelopServices/Authentification';
import {
getProgramOpeningCount,
incrementProgramOpeningCount,
} from './LocalStats';
const sessionCookie = Keen.utils.cookie('visitor-stats');
const sessionTimer = Keen.utils.timer();
sessionTimer.start();
const isDev = Window.isDev();
let client = null;
const isDev = process.env.NODE_ENV === 'development';
export const installAnalyticsEvents = (authentification: Authentification) => {
const sessionCookie = Keen.utils.cookie('visitor-stats');
const sessionTimer = Keen.utils.timer();
sessionTimer.start();
var client = new Keen({
projectId: '593d9f0595cfc907a1f8126a',
writeKey:
'B917F1DB50EE4C8949DBB374D2962845A22838B425AA43322A37138691A5270EB0358AEE45A4F61AFA7713B9765B4980517A1E276D4973A2E546EA851BF7757523706367ED430C041D2728A63BF61B5D1B2079C75E455DDDFAAC4324128AC2DB',
});
client = new Keen({
projectId: '593d9f0595cfc907a1f8126a',
writeKey:
'B917F1DB50EE4C8949DBB374D2962845A22838B425AA43322A37138691A5270EB0358AEE45A4F61AFA7713B9765B4980517A1E276D4973A2E546EA851BF7757523706367ED430C041D2728A63BF61B5D1B2079C75E455DDDFAAC4324128AC2DB',
});
client.extendEvents(function() {
return {
user: {
uuid: getUserUUID(),
},
page: {
title: document.title,
url: document.location.href,
// info: {} (add-on)
},
referrer: {
url: document.referrer,
// info: {} (add-on)
},
tech: {
browser: Keen.helpers.getBrowserProfile(),
// info: {} (add-on)
ip: '${keen.ip}', // eslint-disable-line
ua: '${keen.user_agent}', // eslint-disable-line
},
time: Keen.helpers.getDatetimeIndex(),
visitor: {
id: sessionCookie.get('user_id'),
time_on_page: sessionTimer.value(),
},
// geo: {} (add-on)
keen: {
timestamp: new Date().toISOString(),
addons: [
{
name: 'keen:ip_to_geo',
input: {
ip: 'tech.ip',
client.extendEvents(function() {
const userProfile = authentification.getUserProfileSync();
return {
user: {
uuid: getUserUUID(),
uid: userProfile ? userProfile.uid : undefined,
providerId: userProfile ? userProfile.providerId : undefined,
email: userProfile ? userProfile.email : undefined,
emailVerified: userProfile ? userProfile.emailVerified : undefined,
},
localStats: {
programOpeningCount: getProgramOpeningCount(),
},
page: {
title: document.title,
url: document.location.href,
// info: {} (add-on)
},
referrer: {
url: document.referrer,
// info: {} (add-on)
},
tech: {
browser: Keen.helpers.getBrowserProfile(),
// info: {} (add-on)
ip: '${keen.ip}', // eslint-disable-line
ua: '${keen.user_agent}', // eslint-disable-line
},
time: Keen.helpers.getDatetimeIndex(),
visitor: {
id: sessionCookie.get('user_id'),
time_on_page: sessionTimer.value(),
},
// geo: {} (add-on)
keen: {
timestamp: new Date().toISOString(),
addons: [
{
name: 'keen:ip_to_geo',
input: {
ip: 'tech.ip',
},
output: 'geo',
},
output: 'geo',
},
{
name: 'keen:ua_parser',
input: {
ua_string: 'tech.ua',
{
name: 'keen:ua_parser',
input: {
ua_string: 'tech.ua',
},
output: 'tech.info',
},
output: 'tech.info',
},
{
name: 'keen:url_parser',
input: {
url: 'page.url',
{
name: 'keen:url_parser',
input: {
url: 'page.url',
},
output: 'page.info',
},
output: 'page.info',
},
{
name: 'keen:referrer_parser',
input: {
page_url: 'page.url',
referrer_url: 'referrer.url',
{
name: 'keen:referrer_parser',
input: {
page_url: 'page.url',
referrer_url: 'referrer.url',
},
output: 'referrer.info',
},
output: 'referrer.info',
},
],
},
};
});
],
},
};
});
};
export const sendProgramOpening = () => {
if (isDev) return;
if (isDev || !client) return;
incrementProgramOpeningCount();
client.recordEvent('program_opening');
};
export const sendExportLaunched = exportKind => {
if (isDev) return;
export const sendExportLaunched = (exportKind: string) => {
if (isDev || !client) return;
client.recordEvent('export_launched', {
platform: 'GDevelop JS Platform', // Hardcoded here for now
@@ -91,8 +111,8 @@ export const sendExportLaunched = exportKind => {
});
};
export const sendNewGameCreated = templateName => {
if (isDev) return;
export const sendNewGameCreated = (templateName: string) => {
if (isDev || !client) return;
client.recordEvent('new_game_creation', {
platform: 'GDevelop JS Platform', // Hardcoded here for now
@@ -100,16 +120,20 @@ export const sendNewGameCreated = templateName => {
});
};
export const sendTutorialOpened = tutorialName => {
if (isDev) return;
export const sendTutorialOpened = (tutorialName: string) => {
if (isDev || !client) return;
client.recordEvent('tutorial_opened', {
tutorialName,
});
};
export const sendErrorMessage = (errorMessage, type, rawError) => {
if (isDev) return;
export const sendErrorMessage = (
errorMessage: string,
type: string,
rawError: any
) => {
if (isDev || !client) return;
client.recordEvent('error_message', {
message: errorMessage,
@@ -118,8 +142,8 @@ export const sendErrorMessage = (errorMessage, type, rawError) => {
});
};
export const sendSignupDone = email => {
if (isDev) return;
export const sendSignupDone = (email: string) => {
if (isDev || !client) return;
client.recordEvent('signup', {
email,

View File

@@ -1,9 +1,32 @@
import { getUserUUID } from './UserUUID';
const FS = global.FS;
import optionalRequire from '../OptionalRequire';
import Window from '../Window';
const electron = optionalRequire('electron');
export const installFullstory = () => {
if (FS) {
FS.identify(getUserUUID(), {
// prettier-ignore
if (!electron && !Window.isDev()) {
window['_fs_debug'] = false;
window['_fs_host'] = 'fullstory.com';
window['_fs_org'] = '8DWZ1';
window['_fs_namespace'] = 'FS';
(function(m,n,e,t,l,o,g,y){
if (e in m) {if(m.console && m.console.log) { m.console.log('FullStory namespace conflict. Please set window["_fs_namespace"].');} return;}
g=m[e]=function(a,b){g.q?g.q.push([a,b]):g._api(a,b);};g.q=[];
o=n.createElement(t);o.async=1;o.src='https://'+_fs_host+'/s/fs.js'; //eslint-disable-line
y=n.getElementsByTagName(t)[0];y.parentNode.insertBefore(o,y);
g.identify=function(i,v){g(l,{uid:i});if(v)g(l,v)};g.setUserVars=function(v){g(l,v)};
g.identifyAccount=function(i,v){o='account';v=v||{};v.acctId=i;g(o,v)};
g.clearUserCookie=function(c,d,i){if(!c || document.cookie.match('fs_uid=[`;`]*`[`;`]*`[`;`]*`')){
d=n.domain;while(1){n.cookie='fs_uid=;domain='+d+
';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');
} else {
console.info("Electron or development build - Fullstory disabled");
}
if (window.FS) {
window.FS.identify(getUserUUID(), {
// displayName: 'Daniel Falko',
// email: 'danielfalko@example.com',
});

View File

@@ -0,0 +1,23 @@
// @flow
const localStoragePrefix = 'gd-local-stats';
export const getProgramOpeningCount = (): number => {
try {
const count = localStorage.getItem(`${localStoragePrefix}-program-opening`);
if (count !== null) return parseInt(count, 10);
} catch (e) {
console.warn('Unable to load stored program opening count', e);
}
return 0;
};
export const incrementProgramOpeningCount = () => {
const count = getProgramOpeningCount() + 1;
try {
localStorage.setItem(`${localStoragePrefix}-program-opening`, '' + count);
} catch (e) {
console.warn('Unable to store program opening count', e);
}
};

View File

@@ -4,7 +4,7 @@ import { GDevelopFirebaseConfig } from './ApiConfigs';
export type Profile = {
uid: string, // This represents the userId
picture: string,
providerId: string,
email: string,
emailVerified: boolean,
};
@@ -76,6 +76,10 @@ export default class Authentification {
cb(null, this.user);
};
getUserProfileSync = (): ?Profile => {
return this.user;
};
logout = () => {
firebase
.auth()

View File

@@ -12,7 +12,7 @@ export type Build = {
bucket?: string,
logsKey?: string,
apkKey?: string,
status: 'pending' | 'complete' | 'errored',
status: 'pending' | 'complete' | 'error',
type: 'cordova-build',
createdAt: number,
updatedAt: number,

View File

@@ -8,7 +8,7 @@ import { type Profile } from '../Utils/GDevelopServices/Authentification';
export const profileForIndieUser: Profile = {
uid: 'indie-user',
picture: '',
providerId: 'fake-provider.com',
email: 'indie-user@example.com',
emailVerified: true,
};

View File

@@ -1,3 +1,4 @@
// @flow
import 'element-closest';
import React from 'react';
import ReactDOM from 'react-dom';
@@ -6,10 +7,10 @@ import Window from './Utils/Window';
import ExportDialog from './Export/ExportDialog';
import CreateProjectDialog from './ProjectCreation/CreateProjectDialog';
import Authentification from './Utils/GDevelopServices/Authentification';
import { sendProgramOpening } from './Utils/Analytics/EventSender';
import { sendProgramOpening, installAnalyticsEvents } from './Utils/Analytics/EventSender';
import { installRaven } from './Utils/Analytics/Raven';
import { installFullstory } from './Utils/Analytics/Fullstory';
import registerServiceWorker from './registerServiceWorker';
import { unregister } from './registerServiceWorker';
import './UI/iconmoon-font.css'; // Styles for Iconmoon font.
import 'react-virtualized/styles.css'; // Styles for react-virtualized Table
@@ -18,7 +19,7 @@ import BrowserExamples from './ProjectCreation/BrowserExamples';
import BrowserProjectOpener from './ProjectsStorage/BrowserProjectOpener';
import BrowserSaveDialog from './ProjectsStorage/BrowserSaveDialog';
import BrowserIntroDialog from './MainFrame/BrowserIntroDialog';
import browserResourceSources from './ResourcesEditor/BrowserResourceSources';
import browserResourceSources from './ResourcesList/BrowserResourceSources';
import BrowserS3PreviewLauncher from './Export/BrowserExporters/BrowserS3PreviewLauncher';
import { getBrowserExporters } from './Export/BrowserExporters';
@@ -26,7 +27,7 @@ import { getBrowserExporters } from './Export/BrowserExporters';
import ExternalEditor from './ExternalEditor';
import optionalRequire from './Utils/OptionalRequire.js';
import LocalExamples from './ProjectCreation/LocalExamples';
import localResourceSources from './ResourcesEditor/LocalResourceSources';
import localResourceSources from './ResourcesList/LocalResourceSources';
import LocalProjectWriter from './ProjectsStorage/LocalProjectWriter';
import LocalProjectOpener from './ProjectsStorage/LocalProjectOpener';
import LocalPreviewLauncher from './Export/LocalExporters/LocalPreviewLauncher';
@@ -35,12 +36,14 @@ import ElectronEventsBridge from './MainFrame/ElectronEventsBridge';
import LocalIntroDialog from './MainFrame/LocalIntroDialog';
const electron = optionalRequire('electron');
const authentification = new Authentification();
installAnalyticsEvents(authentification);
installRaven();
installFullstory();
Window.setUpContextMenu();
const authentification = new Authentification();
let app = null;
if (electron) {
@@ -97,6 +100,10 @@ if (electron) {
);
}
ReactDOM.render(app, document.getElementById('root'));
registerServiceWorker();
const rootElement = document.getElementById('root');
if (rootElement) ReactDOM.render(app, rootElement);
else console.error("No root element defined in index.html");
// registerServiceWorker();
unregister();
sendProgramOpening();

View File

@@ -43,7 +43,7 @@ import muiDecorator from './MuiDecorator';
import paperDecorator from './PaperDecorator';
import ValueStateHolder from './ValueStateHolder';
import DragDropContextProvider from '../Utils/DragDropHelpers/DragDropContextProvider';
import ResourcesLoader from '../ObjectsRendering/ResourcesLoader';
import ResourcesLoader from '../ResourcesLoader';
import VariablesList from '../VariablesList';
import ExpressionSelector from '../EventsSheet/InstructionEditor/InstructionOrExpressionSelector/ExpressionSelector';
import InstructionSelector from '../EventsSheet/InstructionEditor/InstructionOrExpressionSelector/InstructionSelector';
@@ -539,8 +539,6 @@ storiesOf('ProfileDetails', module)
<ProfileDetails
profile={{
email: 'test@example.com',
picture:
'"https://s.gravatar.com/avatar/d6fc8df7ddfe938cc379c53bfb5645fc?s=480&r=pg&d=https%3A%2F%2Fcdn.auth0.com%2Favatars%2Ffl.png',
}}
/>
))

View File

@@ -1,4 +1,4 @@
require('dotenv').config();
require('dotenv').config({ path: __dirname + '/.env' });
const electron = require('electron');
const app = electron.app; // Module to control application life.
const BrowserWindow = electron.BrowserWindow; // Module to create native browser window.

View File

@@ -1,9 +1,20 @@
{
"name": "gdevelop",
"version": "5.0.0-beta19",
"version": "5.0.0-beta24",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"ajv": {
"version": "5.5.2",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz",
"integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=",
"requires": {
"co": "4.6.0",
"fast-deep-equal": "1.0.0",
"fast-json-stable-stringify": "2.0.0",
"json-schema-traverse": "0.3.1"
}
},
"archiver": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/archiver/-/archiver-2.1.1.tgz",
@@ -40,6 +51,16 @@
"sprintf-js": "1.0.3"
}
},
"asn1": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz",
"integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y="
},
"assert-plus": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
},
"async": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz",
@@ -48,6 +69,11 @@
"lodash": "4.17.4"
}
},
"asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
},
"aws-sdk": {
"version": "2.181.0",
"resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.181.0.tgz",
@@ -66,6 +92,16 @@
"xmlbuilder": "4.2.1"
}
},
"aws-sign2": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
"integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg="
},
"aws4": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz",
"integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4="
},
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
@@ -76,6 +112,20 @@
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.1.tgz",
"integrity": "sha512-dwVUVIXsBZXwTuwnXI9RK8sBmgq09NDHzyR9SAph9eqk76gKK2JSQmZARC2zRC81JC2QTtxD0ARU5qTS25gIGw=="
},
"bcrypt-pbkdf": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz",
"integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=",
"optional": true,
"requires": {
"tweetnacl": "0.14.5"
}
},
"bignumber.js": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.4.0.tgz",
"integrity": "sha1-g4qZLan51zfg9LLbC+YrsJ3Qxeg="
},
"bl": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/bl/-/bl-1.2.1.tgz",
@@ -97,6 +147,19 @@
"bluebird": "3.5.1"
}
},
"bmp-js": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.0.3.tgz",
"integrity": "sha1-ZBE+nHzxICs3btYHvzBibr5XsYo="
},
"boom": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz",
"integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=",
"requires": {
"hoek": "4.2.0"
}
},
"brace-expansion": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz",
@@ -121,6 +184,11 @@
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
"integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI="
},
"buffer-equal": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz",
"integrity": "sha1-kbx0sR6kBbyRa8aqkI+q+ltKrEs="
},
"builder-util-runtime": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-4.0.1.tgz",
@@ -139,6 +207,11 @@
}
}
},
"caseless": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
},
"cipher-base": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
@@ -148,6 +221,19 @@
"safe-buffer": "5.1.1"
}
},
"co": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
"integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ="
},
"combined-stream": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz",
"integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=",
"requires": {
"delayed-stream": "1.0.0"
}
},
"compress-commons": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-1.2.2.tgz",
@@ -207,6 +293,32 @@
"sha.js": "2.4.9"
}
},
"cryptiles": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz",
"integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=",
"requires": {
"boom": "5.2.0"
},
"dependencies": {
"boom": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz",
"integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==",
"requires": {
"hoek": "4.2.0"
}
}
}
},
"dashdash": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
"integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
"requires": {
"assert-plus": "1.0.0"
}
},
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
@@ -215,6 +327,30 @@
"ms": "2.0.0"
}
},
"delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
},
"dom-walk": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.1.tgz",
"integrity": "sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg="
},
"dotenv": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-4.0.0.tgz",
"integrity": "sha1-hk7xN5rO1Vzm+V3r7NzhefegzR0="
},
"ecc-jsbn": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz",
"integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=",
"optional": true,
"requires": {
"jsbn": "0.1.1"
}
},
"electron-editor-context-menu": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/electron-editor-context-menu/-/electron-editor-context-menu-1.1.1.tgz",
@@ -277,6 +413,11 @@
"once": "1.4.0"
}
},
"es6-promise": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz",
"integrity": "sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM="
},
"esprima": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz",
@@ -287,6 +428,59 @@
"resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
"integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ="
},
"exif-parser": {
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz",
"integrity": "sha1-WKnS1ywCwfbwKg70qRZicrd2CSI="
},
"extend": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz",
"integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ="
},
"extsprintf": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
"integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
},
"fast-deep-equal": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz",
"integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8="
},
"fast-json-stable-stringify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
"integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I="
},
"file-type": {
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz",
"integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek="
},
"for-each": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.2.tgz",
"integrity": "sha1-LEBFC5NI6X8oEyJZO6lnBLmr1NQ=",
"requires": {
"is-function": "1.0.1"
}
},
"forever-agent": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
"integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
},
"form-data": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz",
"integrity": "sha1-b7lPvXGIUwbXPRXMSX/kzE7NRL8=",
"requires": {
"asynckit": "0.4.0",
"combined-stream": "1.0.5",
"mime-types": "2.1.17"
}
},
"fs-extra": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-3.0.1.tgz",
@@ -331,6 +525,14 @@
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
},
"getpass": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
"integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
"requires": {
"assert-plus": "1.0.0"
}
},
"glob": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
@@ -344,11 +546,34 @@
"path-is-absolute": "1.0.1"
}
},
"global": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/global/-/global-4.3.2.tgz",
"integrity": "sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8=",
"requires": {
"min-document": "2.19.0",
"process": "0.5.2"
}
},
"graceful-fs": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
"integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg="
},
"har-schema": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
"integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI="
},
"har-validator": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz",
"integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=",
"requires": {
"ajv": "5.5.2",
"har-schema": "2.0.0"
}
},
"hash-base": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hash-base/-/hash-base-2.0.2.tgz",
@@ -357,6 +582,32 @@
"inherits": "2.0.3"
}
},
"hawk": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz",
"integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==",
"requires": {
"boom": "4.3.1",
"cryptiles": "3.1.2",
"hoek": "4.2.0",
"sntp": "2.1.0"
}
},
"hoek": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz",
"integrity": "sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ=="
},
"http-signature": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
"integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
"requires": {
"assert-plus": "1.0.0",
"jsprim": "1.4.1",
"sshpk": "1.13.1"
}
},
"ieee754": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz",
@@ -376,16 +627,64 @@
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
},
"ip-regex": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-1.0.3.tgz",
"integrity": "sha1-3FiQdvZZ9BnCIgOaMzFvHHOH7/0="
},
"is-function": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.1.tgz",
"integrity": "sha1-Es+5i2W1fdPRk6MSH19uL0N2ArU="
},
"is-typedarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
},
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
},
"isstream": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
},
"jimp": {
"version": "0.2.28",
"resolved": "https://registry.npmjs.org/jimp/-/jimp-0.2.28.tgz",
"integrity": "sha1-3VKak3GQ9ClXp5N9Gsw6d2KZbqI=",
"requires": {
"bignumber.js": "2.4.0",
"bmp-js": "0.0.3",
"es6-promise": "3.3.1",
"exif-parser": "0.1.12",
"file-type": "3.9.0",
"jpeg-js": "0.2.0",
"load-bmfont": "1.3.0",
"mime": "1.6.0",
"mkdirp": "0.5.1",
"pixelmatch": "4.0.2",
"pngjs": "3.3.1",
"read-chunk": "1.0.1",
"request": "2.83.0",
"stream-to-buffer": "0.1.0",
"tinycolor2": "1.4.1",
"url-regex": "3.2.0"
}
},
"jmespath": {
"version": "0.15.0",
"resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz",
"integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc="
},
"jpeg-js": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.2.0.tgz",
"integrity": "sha1-U+RI7J0mPmgyZkZ+lELSxaLvVII="
},
"js-yaml": {
"version": "3.10.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz",
@@ -395,6 +694,27 @@
"esprima": "4.0.0"
}
},
"jsbn": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
"optional": true
},
"json-schema": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
"integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM="
},
"json-schema-traverse": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz",
"integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A="
},
"json-stringify-safe": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
"integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
},
"jsonfile": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-3.0.1.tgz",
@@ -403,6 +723,17 @@
"graceful-fs": "4.1.11"
}
},
"jsprim": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
"integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
"requires": {
"assert-plus": "1.0.0",
"extsprintf": "1.3.0",
"json-schema": "0.2.3",
"verror": "1.10.0"
}
},
"lazy-val": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.3.tgz",
@@ -416,6 +747,20 @@
"readable-stream": "2.3.3"
}
},
"load-bmfont": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.3.0.tgz",
"integrity": "sha1-u358cQ3mvK/LE8s7jIHgwBMey8k=",
"requires": {
"buffer-equal": "0.0.1",
"mime": "1.6.0",
"parse-bmfont-ascii": "1.0.6",
"parse-bmfont-binary": "1.0.6",
"parse-bmfont-xml": "1.1.3",
"xhr": "2.4.1",
"xtend": "4.0.1"
}
},
"lodash": {
"version": "4.17.4",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
@@ -456,6 +801,32 @@
"resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
"integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ="
},
"mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
},
"mime-db": {
"version": "1.30.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz",
"integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE="
},
"mime-types": {
"version": "2.1.17",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz",
"integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=",
"requires": {
"mime-db": "1.30.0"
}
},
"min-document": {
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz",
"integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=",
"requires": {
"dom-walk": "0.1.1"
}
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
@@ -469,6 +840,21 @@
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
},
"mkdirp": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"requires": {
"minimist": "0.0.8"
},
"dependencies": {
"minimist": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
}
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
@@ -482,6 +868,11 @@
"remove-trailing-separator": "1.1.0"
}
},
"oauth-sign": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz",
"integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM="
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -490,11 +881,62 @@
"wrappy": "1.0.2"
}
},
"parse-bmfont-ascii": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz",
"integrity": "sha1-Eaw8P/WPfCAgqyJ2kHkQjU36AoU="
},
"parse-bmfont-binary": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz",
"integrity": "sha1-0Di0dtPp3Z2x4RoLDlOiJ5K2kAY="
},
"parse-bmfont-xml": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.3.tgz",
"integrity": "sha1-1rZqNxr9OcUAfZ8O6yYqTyzOe3w=",
"requires": {
"xml-parse-from-string": "1.0.1",
"xml2js": "0.4.17"
}
},
"parse-headers": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.1.tgz",
"integrity": "sha1-aug6eqJanZtwCswoaYzR8e1+lTY=",
"requires": {
"for-each": "0.3.2",
"trim": "0.0.1"
}
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
},
"performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
},
"pixelmatch": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-4.0.2.tgz",
"integrity": "sha1-j0fc7FARtHe2fbA8JDvB8wheiFQ=",
"requires": {
"pngjs": "3.3.1"
}
},
"pngjs": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.3.1.tgz",
"integrity": "sha512-ggXCTsqHRIsGMkHlCEhbHhUmNTA2r1lpkE0NL4Q9S8spkXbm4vE9TVmPso2AGYn90Gltdz8W5CyzhcIGg2Gejg=="
},
"process": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/process/-/process-0.5.2.tgz",
"integrity": "sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8="
},
"process-nextick-args": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
@@ -505,11 +947,21 @@
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
"integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0="
},
"qs": {
"version": "6.5.1",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz",
"integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A=="
},
"querystring": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
"integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA="
},
"read-chunk": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/read-chunk/-/read-chunk-1.0.1.tgz",
"integrity": "sha1-X2jKswfmY/GZk1J9m1icrORmEZQ="
},
"readable-stream": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz",
@@ -547,6 +999,35 @@
"resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
"integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8="
},
"request": {
"version": "2.83.0",
"resolved": "https://registry.npmjs.org/request/-/request-2.83.0.tgz",
"integrity": "sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==",
"requires": {
"aws-sign2": "0.7.0",
"aws4": "1.6.0",
"caseless": "0.12.0",
"combined-stream": "1.0.5",
"extend": "3.0.1",
"forever-agent": "0.6.1",
"form-data": "2.3.1",
"har-validator": "5.0.3",
"hawk": "6.0.2",
"http-signature": "1.2.0",
"is-typedarray": "1.0.0",
"isstream": "0.1.2",
"json-stringify-safe": "5.0.1",
"mime-types": "2.1.17",
"oauth-sign": "0.8.2",
"performance-now": "2.1.0",
"qs": "6.5.1",
"safe-buffer": "5.1.1",
"stringstream": "0.0.5",
"tough-cookie": "2.3.3",
"tunnel-agent": "0.6.0",
"uuid": "3.1.0"
}
},
"ripemd160": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz",
@@ -580,6 +1061,14 @@
"safe-buffer": "5.1.1"
}
},
"sntp": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz",
"integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==",
"requires": {
"hoek": "4.2.0"
}
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@@ -598,6 +1087,34 @@
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
},
"sshpk": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz",
"integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=",
"requires": {
"asn1": "0.2.3",
"assert-plus": "1.0.0",
"bcrypt-pbkdf": "1.0.1",
"dashdash": "1.14.1",
"ecc-jsbn": "0.1.1",
"getpass": "0.1.7",
"jsbn": "0.1.1",
"tweetnacl": "0.14.5"
}
},
"stream-to": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/stream-to/-/stream-to-0.2.2.tgz",
"integrity": "sha1-hDBgmNhf25kLn6MAsbPM9V6O8B0="
},
"stream-to-buffer": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/stream-to-buffer/-/stream-to-buffer-0.1.0.tgz",
"integrity": "sha1-JnmdkDqyAlyb1VCsRxcbAPjdgKk=",
"requires": {
"stream-to": "0.2.2"
}
},
"string_decoder": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
@@ -606,6 +1123,11 @@
"safe-buffer": "5.1.1"
}
},
"stringstream": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",
"integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg="
},
"tar-stream": {
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.5.5.tgz",
@@ -617,6 +1139,45 @@
"xtend": "4.0.1"
}
},
"tinycolor2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz",
"integrity": "sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g="
},
"tough-cookie": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz",
"integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=",
"requires": {
"punycode": "1.4.1"
},
"dependencies": {
"punycode": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
"integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
}
}
},
"trim": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz",
"integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0="
},
"tunnel-agent": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
"integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
"requires": {
"safe-buffer": "5.1.1"
}
},
"tweetnacl": {
"version": "0.14.5",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
"optional": true
},
"universalify": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.1.tgz",
@@ -631,6 +1192,14 @@
"querystring": "0.2.0"
}
},
"url-regex": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/url-regex/-/url-regex-3.2.0.tgz",
"integrity": "sha1-260eDJ4p4QXdCx8J9oYvf9tIJyQ=",
"requires": {
"ip-regex": "1.0.3"
}
},
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -641,11 +1210,37 @@
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz",
"integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g=="
},
"verror": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
"integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
"requires": {
"assert-plus": "1.0.0",
"core-util-is": "1.0.2",
"extsprintf": "1.3.0"
}
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
},
"xhr": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/xhr/-/xhr-2.4.1.tgz",
"integrity": "sha512-pAIU5vBr9Hiy5cpFIbPnwf0C18ZF86DBsZKrlsf87N5De/JbA6RJ83UP/cv+aljl4S40iRVMqP4pr4sF9Dnj0A==",
"requires": {
"global": "4.3.2",
"is-function": "1.0.1",
"parse-headers": "2.0.1",
"xtend": "4.0.1"
}
},
"xml-parse-from-string": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz",
"integrity": "sha1-qQKekp09vN7RafPG4oI42VpdWig="
},
"xml2js": {
"version": "0.4.17",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.17.tgz",

View File

@@ -2,7 +2,7 @@
"name": "gdevelop",
"productName": "GDevelop 5",
"description": "GDevelop 5 IDE running on the Electron runtime",
"version": "5.0.0-beta21",
"version": "5.0.0-beta25",
"author": "Florian Rival",
"license": "MIT",
"homepage": "http://compilgames.net",
@@ -11,11 +11,13 @@
"archiver": "^2.1.1",
"async": "^2.6.0",
"aws-sdk": "^2.58.0",
"dotenv": "^4.0.0",
"electron-editor-context-menu": "^1.1.1",
"electron-is": "^2.4.0",
"electron-log": "^2.2.14",
"electron-updater": "^2.18.2",
"fs-extra": "^3.0.1",
"jimp": "^0.2.28",
"lodash.throttle": "^4.1.1",
"minimist": "^1.2.0",
"recursive-readdir": "^2.2.1"

View File

@@ -664,7 +664,8 @@
"dotenv": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-4.0.0.tgz",
"integrity": "sha1-hk7xN5rO1Vzm+V3r7NzhefegzR0="
"integrity": "sha1-hk7xN5rO1Vzm+V3r7NzhefegzR0=",
"dev": true
},
"dotenv-expand": {
"version": "4.0.1",

View File

@@ -41,7 +41,6 @@
"shelljs": "^0.7.7"
},
"dependencies": {
"dotenv": "^4.0.0",
"electron-is": "^2.4.0"
}
}