mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Compare commits
25 Commits
v5.0.0-bet
...
v5.0.0-bet
Author | SHA1 | Date | |
---|---|---|---|
![]() |
0e3f70627b | ||
![]() |
a814a07105 | ||
![]() |
c49af90a9c | ||
![]() |
24afa155c8 | ||
![]() |
9e5a431516 | ||
![]() |
182a94285c | ||
![]() |
442c2c8dd9 | ||
![]() |
f8fd0dd353 | ||
![]() |
faad9e23ac | ||
![]() |
d2af0da1b1 | ||
![]() |
c65e5c3e49 | ||
![]() |
24a8dfc5f0 | ||
![]() |
9c6790ac37 | ||
![]() |
18ef7460ba | ||
![]() |
63cd0e76c3 | ||
![]() |
bdbf7fd9fc | ||
![]() |
6bbedbd8f9 | ||
![]() |
648bd1ff2e | ||
![]() |
d4288caedb | ||
![]() |
0a35bc3272 | ||
![]() |
d8b737a31f | ||
![]() |
cfd2655f6c | ||
![]() |
1896241b9d | ||
![]() |
0ed22a6ee1 | ||
![]() |
2bfcb99c3e |
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -70,7 +70,8 @@
|
||||
"ratio": "cpp",
|
||||
"atomic": "cpp",
|
||||
"locale": "cpp",
|
||||
"string_view": "cpp"
|
||||
"string_view": "cpp",
|
||||
"__string": "cpp"
|
||||
},
|
||||
"files.exclude": {
|
||||
"Binaries/*build*": true,
|
||||
|
@@ -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]);
|
||||
|
@@ -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() )
|
||||
|
@@ -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")
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
||||
}
|
||||
|
@@ -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.
|
||||
*/
|
||||
|
@@ -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
|
||||
|
@@ -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();
|
||||
|
@@ -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;
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
||||
|
@@ -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");
|
||||
|
@@ -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;
|
||||
|
@@ -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>() )
|
||||
{
|
||||
|
@@ -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>() )
|
||||
{
|
||||
|
@@ -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);
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# Rename this file as .env.local and complete the following values.
|
||||
# Copy this file as ".env.local" and complete the following values.
|
||||
# If values are not completed, some features will be unavailable.
|
||||
|
||||
REACT_APP_PREVIEW_S3_ACCESS_KEY_ID=
|
||||
|
1
newIDE/app/flow-typed/libGD.js
vendored
1
newIDE/app/flow-typed/libGD.js
vendored
@@ -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;
|
||||
|
@@ -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>
|
||||
|
@@ -5,6 +5,7 @@ import BrowserPreviewLinkDialog from './BrowserPreviewLinkDialog';
|
||||
import { findGDJS } from './BrowserS3GDJSFinder';
|
||||
import assignIn from 'lodash/assignIn';
|
||||
import { GDevelopGamesPreview } from '../../Utils/GDevelopServices/ApiConfigs';
|
||||
import { makeTimestampedId } from '../../Utils/TimestampedId';
|
||||
const awsS3 = require('aws-sdk/clients/s3');
|
||||
const gd = global.gd;
|
||||
|
||||
@@ -41,8 +42,7 @@ export default class BrowserS3PreviewLauncher {
|
||||
}
|
||||
console.info('GDJS found in ', gdjsRoot);
|
||||
|
||||
const prefix =
|
||||
'' + Date.now() + '-' + Math.floor(Math.random() * 1000000);
|
||||
const prefix = makeTimestampedId();
|
||||
|
||||
const outputDir = destinationBucketBaseUrl + prefix;
|
||||
const browserS3FileSystem = new BrowserS3FileSystem({
|
||||
|
@@ -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);
|
||||
|
@@ -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
|
||||
);
|
||||
));
|
||||
|
@@ -8,6 +8,7 @@ import optionalRequire from '../../Utils/OptionalRequire';
|
||||
import { Column, Line, Spacer } from '../../UI/Grid';
|
||||
import LinearProgress from 'material-ui/LinearProgress';
|
||||
import { GDevelopHostingApi } from '../../Utils/GDevelopServices/ApiConfigs';
|
||||
import { makeTimestampedId } from '../../Utils/TimestampedId';
|
||||
import TextField from 'material-ui/TextField';
|
||||
const os = optionalRequire('os');
|
||||
const electron = optionalRequire('electron');
|
||||
@@ -31,11 +32,13 @@ export default class LocalS3Export extends Component {
|
||||
ipcRenderer.removeAllListeners('s3-folder-upload-done');
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
ipcRenderer.on('s3-folder-upload-progress', (event, uploadProgress, uploadMax) =>
|
||||
this.setState({
|
||||
uploadProgress,
|
||||
uploadMax,
|
||||
})
|
||||
ipcRenderer.on(
|
||||
's3-folder-upload-progress',
|
||||
(event, uploadProgress, uploadMax) =>
|
||||
this.setState({
|
||||
uploadProgress,
|
||||
uploadMax,
|
||||
})
|
||||
);
|
||||
ipcRenderer.on('s3-folder-upload-done', (event, err, prefix) => {
|
||||
if (err) return reject(err);
|
||||
@@ -51,7 +54,8 @@ export default class LocalS3Export extends Component {
|
||||
|
||||
_deploy = prefix => {
|
||||
return sleep(200)
|
||||
.then(() => //TODO: Move this to a GDevelopServices/Hosting.js file
|
||||
.then(() =>
|
||||
//TODO: Move this to a GDevelopServices/Hosting.js file
|
||||
axios(GDevelopHostingApi.deployEndpoint, {
|
||||
method: 'post',
|
||||
params: {
|
||||
@@ -79,7 +83,7 @@ export default class LocalS3Export extends Component {
|
||||
deployDone: false,
|
||||
});
|
||||
|
||||
const outputDir = os.tmpdir() + '/GDS3Export';
|
||||
const outputDir = os.tmpdir() + '/GDS3Export-' + makeTimestampedId();
|
||||
LocalExport.prepareExporter()
|
||||
.then(({ exporter }) => {
|
||||
const exportForCordova = false;
|
||||
|
30
newIDE/app/src/Export/SanityChecker/CordovaSanityChecker.js
Normal file
30
newIDE/app/src/Export/SanityChecker/CordovaSanityChecker.js
Normal 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,
|
||||
};
|
||||
};
|
25
newIDE/app/src/Export/SanityChecker/index.js
Normal file
25
newIDE/app/src/Export/SanityChecker/index.js
Normal 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;
|
||||
};
|
21
newIDE/app/src/MainFrame/Editors/ResourcesEditor.js
Normal file
21
newIDE/app/src/MainFrame/Editors/ResourcesEditor.js
Normal 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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
@@ -47,6 +47,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,
|
||||
@@ -241,7 +242,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 +546,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(
|
||||
{
|
||||
@@ -810,6 +845,7 @@ export default class MainFrame extends Component<*, State> {
|
||||
onCloseProject={this.askToCloseProject}
|
||||
onExportProject={this.openExportDialog}
|
||||
onOpenPreferences={() => this.openPreferences(true)}
|
||||
onOpenResources={() => this.openResources()}
|
||||
/>
|
||||
)}
|
||||
</Drawer>
|
||||
@@ -908,7 +944,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={() => {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import ResourcesLoader from '../ObjectsRendering/ResourcesLoader';
|
||||
import ResourceSelector from '../ResourcesEditor/ResourceSelector';
|
||||
import ResourceSelector from '../ResourcesList/ResourceSelector';
|
||||
import ImageThumbnail from './ImageThumbnail';
|
||||
|
||||
export default ({
|
||||
|
@@ -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) => {
|
||||
|
@@ -22,7 +22,7 @@ export default class PixiResourcesLoader {
|
||||
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);
|
||||
|
@@ -17,6 +17,7 @@ import Authentification, {
|
||||
import LoginDialog from './LoginDialog';
|
||||
import { watchPromiseInState } from '../Utils/WatchPromiseInState';
|
||||
import { showWarningBox } from '../UI/Messages/MessageBox';
|
||||
import { sendSignupDone } from '../Utils/Analytics/EventSender';
|
||||
|
||||
type Props = {
|
||||
open: boolean,
|
||||
@@ -181,6 +182,7 @@ export const withUserProfile = (
|
||||
() => {
|
||||
this._fetchUserProfile();
|
||||
this.openLogin(false);
|
||||
sendSignupDone(form.email);
|
||||
},
|
||||
(loginError: LoginError) => {
|
||||
this.setState({
|
||||
|
@@ -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;
|
||||
|
@@ -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,64 @@ 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,
|
||||
};
|
||||
|
||||
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 +237,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 +279,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 +363,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 +385,12 @@ export default class ProjectManager extends React.Component {
|
||||
leftIcon={<ListIcon src="res/ribbon_default/editname32.png" />}
|
||||
onClick={() => this.setState({ variablesEditorOpen: true })}
|
||||
/>,
|
||||
<ListItem
|
||||
key="resources"
|
||||
primaryText="Resources"
|
||||
leftIcon={<ListIcon src="res/ribbon_default/image32.png" />}
|
||||
onClick={() => this.props.onOpenResources()}
|
||||
/>,
|
||||
]}
|
||||
/>
|
||||
<ProjectStructureItem
|
||||
@@ -366,7 +404,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
|
||||
|
52
newIDE/app/src/PropertiesEditor/SemiControlledTextField.js
Normal file
52
newIDE/app/src/PropertiesEditor/SemiControlledTextField.js
Normal 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,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
@@ -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 || '')
|
||||
|
101
newIDE/app/src/ResourcesEditor/ResourcePropertiesEditor/index.js
Normal file
101
newIDE/app/src/ResourcesEditor/ResourcePropertiesEditor/index.js
Normal 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 ResourceLoader from '../../ObjectsRendering/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: ResourceLoader,
|
||||
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>
|
||||
);
|
||||
}
|
||||
}
|
39
newIDE/app/src/ResourcesEditor/Toolbar.js
Normal file
39
newIDE/app/src/ResourcesEditor/Toolbar.js
Normal 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);
|
156
newIDE/app/src/ResourcesEditor/index.js
Normal file
156
newIDE/app/src/ResourcesEditor/index.js
Normal file
@@ -0,0 +1,156 @@
|
||||
// @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 '../ObjectsRendering/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;
|
||||
|
||||
console.log(selectedResource);
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
}
|
19
newIDE/app/src/ResourcesList/EnumerateResources.js
Normal file
19
newIDE/app/src/ResourcesList/EnumerateResources.js
Normal 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
|
||||
);
|
||||
});
|
||||
};
|
@@ -40,7 +40,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 (
|
187
newIDE/app/src/ResourcesList/index.js
Normal file
187
newIDE/app/src/ResourcesList/index.js
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
@@ -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"
|
||||
|
@@ -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)}
|
||||
|
116
newIDE/app/src/UI/SortableVirtualizedItemList/ItemRow.js
Normal file
116
newIDE/app/src/UI/SortableVirtualizedItemList/ItemRow.js
Normal 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;
|
108
newIDE/app/src/UI/SortableVirtualizedItemList/index.js
Normal file
108
newIDE/app/src/UI/SortableVirtualizedItemList/index.js
Normal 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;
|
@@ -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
|
||||
},
|
||||
|
@@ -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,
|
||||
@@ -117,3 +141,11 @@ export const sendErrorMessage = (errorMessage, type, rawError) => {
|
||||
rawError,
|
||||
});
|
||||
};
|
||||
|
||||
export const sendSignupDone = (email: string) => {
|
||||
if (isDev || !client) return;
|
||||
|
||||
client.recordEvent('signup', {
|
||||
email,
|
||||
});
|
||||
};
|
||||
|
@@ -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',
|
||||
});
|
||||
|
23
newIDE/app/src/Utils/Analytics/LocalStats.js
Normal file
23
newIDE/app/src/Utils/Analytics/LocalStats.js
Normal 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);
|
||||
}
|
||||
};
|
@@ -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()
|
||||
|
4
newIDE/app/src/Utils/TimestampedId.js
Normal file
4
newIDE/app/src/Utils/TimestampedId.js
Normal file
@@ -0,0 +1,4 @@
|
||||
// @flow
|
||||
|
||||
export const makeTimestampedId = () =>
|
||||
'' + Date.now() + '-' + Math.floor(Math.random() * 1000000);
|
@@ -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,
|
||||
};
|
||||
|
@@ -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();
|
||||
|
@@ -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',
|
||||
}}
|
||||
/>
|
||||
))
|
||||
|
1
newIDE/electron-app/.gitignore
vendored
1
newIDE/electron-app/.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
dist/
|
||||
app/www/
|
||||
.env
|
||||
|
5
newIDE/electron-app/app/.env.dist
Normal file
5
newIDE/electron-app/app/.env.dist
Normal file
@@ -0,0 +1,5 @@
|
||||
# Copy this file as ".env" and complete the following values.
|
||||
# If values are not completed, some features will be unavailable.
|
||||
|
||||
UPLOAD_S3_ACCESS_KEY_ID=
|
||||
UPLOAD_S3_SECRET_ACCESS_KEY=
|
119
newIDE/electron-app/app/S3Upload.js
Normal file
119
newIDE/electron-app/app/S3Upload.js
Normal file
@@ -0,0 +1,119 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const async = require('async');
|
||||
const awsS3 = require('aws-sdk/clients/s3');
|
||||
const { makeTimestampedId } = require('./Utils/TimestampedId');
|
||||
const recursive = require('recursive-readdir');
|
||||
|
||||
const accessKeyId = process.env.UPLOAD_S3_ACCESS_KEY_ID;
|
||||
const secretAccessKey = process.env.UPLOAD_S3_SECRET_ACCESS_KEY;
|
||||
const destinationBucket = `gd-games-in`;
|
||||
const region = 'eu-west-1';
|
||||
const mime = {
|
||||
'.js': 'text/javascript',
|
||||
'.html': 'text/html',
|
||||
};
|
||||
|
||||
if (!accessKeyId || !secretAccessKey) {
|
||||
console.warn(
|
||||
"⚠️ Either UPLOAD_S3_ACCESS_KEY_ID or UPLOAD_S3_SECRET_ACCESS_KEY are not defined. Upload won't be working."
|
||||
);
|
||||
console.info(
|
||||
'ℹ️ Copy .env.dist file to .env and fill the values to fix this warning.'
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* Upload the specified directory to GDevelop
|
||||
* Amazon S3 inbound bucket.
|
||||
*/
|
||||
uploadGameFolderToBucket: (localDir, onProgress, onDone) => {
|
||||
if (!accessKeyId || !secretAccessKey) {
|
||||
onDone('Missing S3 configuration');
|
||||
return;
|
||||
}
|
||||
|
||||
const awsS3Client = new awsS3({
|
||||
accessKeyId: accessKeyId,
|
||||
secretAccessKey: secretAccessKey,
|
||||
region: region,
|
||||
correctClockSkew: true,
|
||||
});
|
||||
|
||||
const prefix = 'game-' + makeTimestampedId();
|
||||
recursive(localDir)
|
||||
.then(allFiles => {
|
||||
const updateProgress = (fileIndex, loaded, total) => {
|
||||
onProgress(fileIndex + (total ? loaded / total : 0), allFiles.length);
|
||||
};
|
||||
|
||||
async.series(
|
||||
allFiles.map((localFile, fileIndex) => callback => {
|
||||
const body = fs.createReadStream(localFile);
|
||||
const filename = path.relative(localDir, localFile);
|
||||
const fileExtension = path.extname(filename);
|
||||
awsS3Client
|
||||
.upload({
|
||||
Body: body,
|
||||
Bucket: destinationBucket,
|
||||
Key: prefix + '/' + filename,
|
||||
ContentType: mime[fileExtension],
|
||||
})
|
||||
.on('httpUploadProgress', function(progress) {
|
||||
updateProgress(fileIndex, progress.loaded, progress.total || 0);
|
||||
})
|
||||
.send(function(err, data) {
|
||||
callback(err, prefix + '/' + filename);
|
||||
});
|
||||
}),
|
||||
(err, results) => {
|
||||
if (err) {
|
||||
onDone(err);
|
||||
}
|
||||
|
||||
onDone(null, prefix);
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Unable to recursively read directory ' + localDir);
|
||||
onDone(err);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Upload the specified file to GDevelop
|
||||
* Amazon S3 inbound bucket.
|
||||
*/
|
||||
uploadArchiveToBucket: (localFile, onProgress, onDone) => {
|
||||
if (!accessKeyId || !secretAccessKey) {
|
||||
onDone('Missing S3 configuration');
|
||||
return;
|
||||
}
|
||||
|
||||
const awsS3Client = new awsS3({
|
||||
accessKeyId: accessKeyId,
|
||||
secretAccessKey: secretAccessKey,
|
||||
region: region,
|
||||
correctClockSkew: true,
|
||||
});
|
||||
|
||||
const prefix = 'game-archive-' + makeTimestampedId();
|
||||
const filename = 'game-archive.zip';
|
||||
const body = fs.createReadStream(localFile);
|
||||
|
||||
awsS3Client
|
||||
.upload({
|
||||
Body: body,
|
||||
Bucket: destinationBucket,
|
||||
Key: prefix + '/' + filename,
|
||||
})
|
||||
.on('httpUploadProgress', function(progress) {
|
||||
onProgress(progress.loaded, progress.total || 0);
|
||||
})
|
||||
.send(function(err, data) {
|
||||
onDone(err, prefix + '/' + filename);
|
||||
});
|
||||
},
|
||||
};
|
8
newIDE/electron-app/app/Utils/TimestampedId.js
Normal file
8
newIDE/electron-app/app/Utils/TimestampedId.js
Normal file
@@ -0,0 +1,8 @@
|
||||
// @flow
|
||||
|
||||
const makeTimestampedId = () =>
|
||||
'' + Date.now() + '-' + Math.floor(Math.random() * 1000000);
|
||||
|
||||
module.exports = {
|
||||
makeTimestampedId,
|
||||
};
|
@@ -1,3 +1,4 @@
|
||||
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.
|
||||
@@ -10,8 +11,8 @@ const log = require('electron-log');
|
||||
const {
|
||||
uploadGameFolderToBucket,
|
||||
uploadArchiveToBucket,
|
||||
} = require('./s3upload');
|
||||
const { buildMainMenuFor } = require('./main-menu');
|
||||
} = require('./S3Upload');
|
||||
const { buildMainMenuFor } = require('./MainMenu');
|
||||
const throttle = require('lodash.throttle');
|
||||
|
||||
// Logs made with electron-logs can be found
|
||||
@@ -102,9 +103,9 @@ app.on('ready', function() {
|
||||
|
||||
uploadGameFolderToBucket(
|
||||
localDir,
|
||||
(current, max) => {
|
||||
throttle((current, max) => {
|
||||
event.sender.send('s3-folder-upload-progress', current, max);
|
||||
},
|
||||
}, 200),
|
||||
(err, prefix) => {
|
||||
event.sender.send('s3-folder-upload-done', err, prefix);
|
||||
}
|
||||
|
133
newIDE/electron-app/app/package-lock.json
generated
133
newIDE/electron-app/app/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gdevelop-electron-app",
|
||||
"version": "5.0.0-beta13",
|
||||
"name": "gdevelop",
|
||||
"version": "5.0.0-beta21",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -215,6 +215,11 @@
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"dotenv": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-4.0.0.tgz",
|
||||
"integrity": "sha1-hk7xN5rO1Vzm+V3r7NzhefegzR0="
|
||||
},
|
||||
"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",
|
||||
@@ -287,19 +292,6 @@
|
||||
"resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
|
||||
"integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ="
|
||||
},
|
||||
"fd-slicer": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz",
|
||||
"integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=",
|
||||
"requires": {
|
||||
"pend": "1.2.0"
|
||||
}
|
||||
},
|
||||
"findit2": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/findit2/-/findit2-2.2.3.tgz",
|
||||
"integrity": "sha1-WKRmaX34piBc39vzlVNri9d3pfY="
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-3.0.1.tgz",
|
||||
@@ -469,11 +461,6 @@
|
||||
"resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
|
||||
"integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ="
|
||||
},
|
||||
"mime": {
|
||||
"version": "1.2.11",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz",
|
||||
"integrity": "sha1-WCA+7Ybjpe8XrtK32evUfwpg3RA="
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
@@ -487,31 +474,11 @@
|
||||
"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",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
},
|
||||
"natives": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/natives/-/natives-1.1.1.tgz",
|
||||
"integrity": "sha512-8eRaxn8u/4wN8tGkhlc2cgwwvOLMLUMUn4IYTexMgWd+LyUDfeXVkk2ygQR0hvIHbJQXgHujia3ieUUDwNGkEA=="
|
||||
},
|
||||
"normalize-path": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
|
||||
@@ -533,11 +500,6 @@
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
|
||||
},
|
||||
"pend": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
|
||||
"integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA="
|
||||
},
|
||||
"process-nextick-args": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
|
||||
@@ -567,16 +529,29 @@
|
||||
"util-deprecate": "1.0.2"
|
||||
}
|
||||
},
|
||||
"recursive-readdir": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.1.tgz",
|
||||
"integrity": "sha1-kO8jHQd4xc4JPJpI105cVCLROpk=",
|
||||
"requires": {
|
||||
"minimatch": "3.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"minimatch": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz",
|
||||
"integrity": "sha1-Kk5AkLlrLbBqnX3wEFWmKnfJt3Q=",
|
||||
"requires": {
|
||||
"brace-expansion": "1.1.8"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"remove-trailing-separator": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
|
||||
"integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8="
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "2.2.8",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz",
|
||||
"integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI="
|
||||
},
|
||||
"ripemd160": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz",
|
||||
@@ -586,59 +561,6 @@
|
||||
"inherits": "2.0.3"
|
||||
}
|
||||
},
|
||||
"s3": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/s3/-/s3-4.4.0.tgz",
|
||||
"integrity": "sha1-VqT3dVFae2ucjlxrGrUfkDdmnx8=",
|
||||
"requires": {
|
||||
"aws-sdk": "2.0.31",
|
||||
"fd-slicer": "1.0.1",
|
||||
"findit2": "2.2.3",
|
||||
"graceful-fs": "3.0.11",
|
||||
"mime": "1.2.11",
|
||||
"mkdirp": "0.5.1",
|
||||
"pend": "1.2.0",
|
||||
"rimraf": "2.2.8",
|
||||
"streamsink": "1.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"aws-sdk": {
|
||||
"version": "2.0.31",
|
||||
"resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.0.31.tgz",
|
||||
"integrity": "sha1-5yzx/caQFb2f0r3z07iMFlB9Jo4=",
|
||||
"requires": {
|
||||
"xml2js": "0.2.6",
|
||||
"xmlbuilder": "0.4.2"
|
||||
}
|
||||
},
|
||||
"graceful-fs": {
|
||||
"version": "3.0.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.11.tgz",
|
||||
"integrity": "sha1-dhPHeKGv6mLyXGMKCG1/Osu92Bg=",
|
||||
"requires": {
|
||||
"natives": "1.1.1"
|
||||
}
|
||||
},
|
||||
"sax": {
|
||||
"version": "0.4.2",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-0.4.2.tgz",
|
||||
"integrity": "sha1-OfO2AXM9a+yXEFskKipA/Wl4rDw="
|
||||
},
|
||||
"xml2js": {
|
||||
"version": "0.2.6",
|
||||
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.2.6.tgz",
|
||||
"integrity": "sha1-0gnE5N2h/JxFIUHvQcB39a399sQ=",
|
||||
"requires": {
|
||||
"sax": "0.4.2"
|
||||
}
|
||||
},
|
||||
"xmlbuilder": {
|
||||
"version": "0.4.2",
|
||||
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-0.4.2.tgz",
|
||||
"integrity": "sha1-F3bWXz/brUcKCNhgTN6xxOVA/4M="
|
||||
}
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
|
||||
@@ -681,11 +603,6 @@
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
|
||||
},
|
||||
"streamsink": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/streamsink/-/streamsink-1.2.0.tgz",
|
||||
"integrity": "sha1-76/unx4i01ke1949yqlcP1559zw="
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
|
||||
|
@@ -2,13 +2,14 @@
|
||||
"name": "gdevelop",
|
||||
"productName": "GDevelop 5",
|
||||
"description": "GDevelop 5 IDE running on the Electron runtime",
|
||||
"version": "5.0.0-beta19",
|
||||
"version": "5.0.0-beta24",
|
||||
"author": "Florian Rival",
|
||||
"license": "MIT",
|
||||
"homepage": "http://compilgames.net",
|
||||
"main": "main.js",
|
||||
"dependencies": {
|
||||
"archiver": "^2.1.1",
|
||||
"async": "^2.6.0",
|
||||
"aws-sdk": "^2.58.0",
|
||||
"electron-editor-context-menu": "^1.1.1",
|
||||
"electron-is": "^2.4.0",
|
||||
@@ -17,6 +18,7 @@
|
||||
"fs-extra": "^3.0.1",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"minimist": "^1.2.0",
|
||||
"s3": "^4.4.0"
|
||||
"recursive-readdir": "^2.2.1",
|
||||
"dotenv": "^4.0.0"
|
||||
}
|
||||
}
|
||||
|
@@ -1,77 +0,0 @@
|
||||
const fs = require('fs');
|
||||
const s3 = require('s3');
|
||||
const awsS3 = require('aws-sdk/clients/s3');
|
||||
|
||||
const destinationBucket = `gd-games-in`;
|
||||
const accessKeyId = 'AKIAJPLGZ22GBISUYFJQ';
|
||||
const secretAccessKey = 'PS6+WyMe8blAxx0CrwQagONdvQWBD3m5o9ZVC5LF';
|
||||
const region = 'eu-west-1';
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* Upload the specified directory to GDevelop
|
||||
* Amazon S3 inbound bucket.
|
||||
*/
|
||||
uploadGameFolderToBucket: (localDir, onProgress, onDone) => {
|
||||
const awsS3Client = new awsS3({
|
||||
accessKeyId: accessKeyId,
|
||||
secretAccessKey: secretAccessKey,
|
||||
region: region,
|
||||
correctClockSkew: true,
|
||||
});
|
||||
|
||||
// TODO: Switch to a more robust module for uploading files
|
||||
const s3Client = s3.createClient({
|
||||
s3Client: awsS3Client,
|
||||
});
|
||||
|
||||
const timestamp = '' + Date.now();
|
||||
const prefix = 'game-' + timestamp;
|
||||
|
||||
var uploader = s3Client.uploadDir({
|
||||
localDir,
|
||||
s3Params: {
|
||||
Bucket: destinationBucket,
|
||||
Prefix: prefix + '/',
|
||||
},
|
||||
});
|
||||
uploader.on('error', onDone);
|
||||
uploader.on('progress', function() {
|
||||
onProgress(uploader.progressAmount, uploader.progressTotal);
|
||||
});
|
||||
uploader.on('end', function() {
|
||||
onDone(null, prefix);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Upload the specified file to GDevelop
|
||||
* Amazon S3 inbound bucket.
|
||||
*/
|
||||
uploadArchiveToBucket: (localFile, onProgress, onDone) => {
|
||||
const awsS3Client = new awsS3({
|
||||
accessKeyId: accessKeyId,
|
||||
secretAccessKey: secretAccessKey,
|
||||
region: region,
|
||||
correctClockSkew: true,
|
||||
});
|
||||
|
||||
const timestamp = '' + Date.now();
|
||||
const prefix = 'game-archive-' + timestamp;
|
||||
const filename = 'game-archive.zip';
|
||||
var body = fs.createReadStream(localFile);
|
||||
|
||||
awsS3Client
|
||||
.upload({
|
||||
Body: body,
|
||||
Bucket: destinationBucket,
|
||||
Key: prefix + '/' + filename,
|
||||
})
|
||||
.on('httpUploadProgress', function(progress) {
|
||||
onProgress(progress.loaded, progress.total || 0);
|
||||
})
|
||||
.send(function(err, data) {
|
||||
onDone(err, prefix + '/' + filename);
|
||||
});
|
||||
},
|
||||
};
|
2755
newIDE/electron-app/package-lock.json
generated
Normal file
2755
newIDE/electron-app/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -28,7 +28,11 @@
|
||||
"mac": {
|
||||
"category": "public.app-category.developer-tools"
|
||||
},
|
||||
"publish": [{"provider": "github"}]
|
||||
"publish": [
|
||||
{
|
||||
"provider": "github"
|
||||
}
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"electron": "1.7.9",
|
||||
|
Reference in New Issue
Block a user