mirror of
https://github.com/4ian/GDevelop.git
synced 2026-10-12 02:01:17 +00:00
Merge branch 'master' of https://github.com/4ian/GDevelop
This commit is contained in:
+1
-4
@@ -1,8 +1,5 @@
|
||||
/Core/GDCore/Tools/VersionPriv.h
|
||||
/docs/GDJS Runtime Documentation
|
||||
/docs/GDJS Documentation
|
||||
/docs/GDCpp Documentation
|
||||
/docs/GDCore Documentation
|
||||
/docs
|
||||
/ExtLibs/SFML
|
||||
/ExtLibs/*.7z
|
||||
/scripts/logs/*.txt
|
||||
|
||||
+9
-576
@@ -1,592 +1,25 @@
|
||||
/**
|
||||
* \mainpage GDevelop Core
|
||||
* \image html gdlogo.png
|
||||
* \section welcome Welcome
|
||||
* \section welcome GDevelop Core documentation
|
||||
*
|
||||
* The **GDevelop Core** library contains the main concepts, classes and tools that are used by the *platforms* and the *GDevelop IDE*.<br>
|
||||
* This ensures that the IDE, or any tool based on GDevelop Core, is able to work with projects based on any arbitrary platform.
|
||||
*
|
||||
* Two official platforms are available for GDevelop:
|
||||
* - The *C++ Platform* (GDCpp) to create native games.
|
||||
* - The *JS Platform* (GDJS) to create HTML5 games.
|
||||
* The **GDevelop Core** library contains the structure of a GDevelop game, classes and tools that are used by the *platforms* and the *GDevelop IDE*.
|
||||
*
|
||||
* \section gettingstarted Getting started
|
||||
* First, please refer to these pages to install the required tools and to get help about setting up a basic extension:<br>
|
||||
*
|
||||
* -# \subpage setupDevEnv
|
||||
* -# \ref overview
|
||||
* -# \ref writeANewExtension
|
||||
* In most cases, you should start by <a href="https://github.com/4ian/GDevelop/blob/master/newIDE/README.md">installing and launching the development version of GDevelop</a>.
|
||||
*
|
||||
* You can also read \subpage recommendedToolsAndConventions.
|
||||
* - If you're interested in writing extensions for GDevelop, read <a href="https://github.com/4ian/GDevelop/blob/master/newIDE/README-extensions.md">the documentation about extensions</a>.
|
||||
* - If you want to dig more into how GDevelop is architectured and work on the core, read <a href="https://github.com/4ian/GDevelop/blob/master/Core/GDevelop-Architecture-Overview.md">GDevelop Architecture Overview</a>. Then, you can browse this reference to get more information about a class or function.
|
||||
*
|
||||
* \section aboutdoc About this documentation
|
||||
* \section other Other documentations
|
||||
*
|
||||
* If you never used GDevelop Core before, take a look at \ref overview.
|
||||
*
|
||||
* As everything that is developed around GDevelop is based on this library, you should take a look at it quite often: platforms, extensions
|
||||
* and the IDE are intensively using the classes and tools offered by GDCore.
|
||||
* When developing an extension for the C++ or JS platform, read these documentations:
|
||||
* GDevelop is architectured around a `Core` (this library), platforms (`GDJS`, `GDCpp`) and extensions (`Extensions` folder). The editor (`newIDE` folder) is using all of these libraries.
|
||||
*
|
||||
* - [Open GDevelop C++ Platform documentation](../GDCpp Documentation/index.html)
|
||||
* - [Open GDevelop JS Platform documentation](../GDJS Documentation/index.html)
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* \page setupDevEnv Setting up the development environment
|
||||
*
|
||||
* If you didn't already downloaded GDevelop, get and extract the source from [GitHub](https://github.com/4ian/GD).
|
||||
*
|
||||
* Follow these steps to be able to compile GDevelop, it's super easy:
|
||||
*
|
||||
* <b>Windows</b>
|
||||
*
|
||||
* -# \subpage installWinCompiler
|
||||
* -# \ref installAndUseCMake
|
||||
* <br>
|
||||
*
|
||||
* <b>GNU/Linux</b>
|
||||
* -# \subpage installLinuxLib
|
||||
* -# \subpage installAndUseCMake
|
||||
*
|
||||
* <b>OS X</b>
|
||||
* -# \subpage installMacOSTools
|
||||
* -# \ref installAndUseCMake
|
||||
*
|
||||
* See the recommended tools and conventions for working on GDevelop on this page:
|
||||
* \subpage recommendedToolsAndConventions
|
||||
*/
|
||||
|
||||
/**
|
||||
* \page installWinCompiler (Windows) Install TDM-GCC compiler
|
||||
*
|
||||
* GDevelop is compiled with TDM-GCC under Windows.<br>
|
||||
* So as to prevent incompatibilities between the compiler (and the standard C++ library provided with) used by GDevelop and
|
||||
* the compiler used by the extensions, GDevelop require the extensions and the platforms to use the same version of TDM-GCC.
|
||||
*
|
||||
* While a recent GCC version should work, if you compile GDevelop for an "official" distribution it's better
|
||||
* to use the specific version provided here.
|
||||
*
|
||||
* \section installWinCompiler_download Download
|
||||
*
|
||||
* Download the current version of the compiler used by GDevelop on Windows here:
|
||||
*
|
||||
* https://sourceforge.net/projects/tdm-gcc/files/TDM-GCC%20Installer/Previous/1.1309.0/tdm-gcc-4.9.2.exe/download
|
||||
*
|
||||
* \section installWinCompiler_install Installation
|
||||
*
|
||||
* The installation is fairly simple :<br>
|
||||
* <br>
|
||||
* - Launch the installer.<br>
|
||||
* - Choose Create.<br>
|
||||
|
||||
\image html compilerInstall1.png
|
||||
|
||||
* - Choose an installation directory.<br>
|
||||
|
||||
\image html compilerInstall2.png
|
||||
|
||||
* - Choose the components to be installed. You don't have to change anything, the default options are good enough.<br>
|
||||
|
||||
\image html compilerInstall3.png
|
||||
|
||||
* - Click on install so as to launch the installation process.<br>
|
||||
*/
|
||||
|
||||
/**
|
||||
* \page installAndUseCMake (All) Install CMake & launch the build
|
||||
*
|
||||
* Building is done using CMake: it is an open-source build system that can generate build files for lots of IDE and build tools (Makefiles...).
|
||||
*
|
||||
* \section installAndUseCMake_download Download and install CMake
|
||||
*
|
||||
* First, install CMake:
|
||||
* Download it [here](http://www.cmake.org/cmake/resources/software.html) for Windows, get it using your package manager if you're
|
||||
* using a Linux distribution or using Homebrew for Mac OS X.
|
||||
*
|
||||
* \section installAndUseCMake_use Using CMake to generate the build files
|
||||
* Using CMake is not difficult and require only a few clicks/commands to enter. Windows users may use
|
||||
* the GUI as shown in the next section. Linux and Mac OS X users may prefer to use the command line as shown
|
||||
* as the end of this page.
|
||||
*
|
||||
* \subsection installAndUseCMake_use_gui Using the GUI
|
||||
*
|
||||
* - Start the CMake user interface (_cmake-gui_). Choose the GD root directory as the source directory, and Binaries/build as the directory where to build the binaries:
|
||||
|
||||
\image html usecmake1.png
|
||||
|
||||
* - Click on *Configure*. If asked to create the build directory, answer yes. Choose then your favorite generator: *MinGW Makefiles* (on Windows) or *Unix Makefiles* (on Linux/OS X) generate a traditional Makefile that can be built using the
|
||||
* *mingw32-make* (on Windows) or *make* (on Linux/OS X) command. You can also choose the *Ninja* generator to use the [Ninja build system](http://martine.github.io/ninja/).
|
||||
|
||||
\image html usecmake2.png
|
||||
|
||||
* - When you click on Finish, CMake do a first configuration. If **errors occurred*, make sure that you have download all required development libraries.
|
||||
* - Adjust any variable if necessary (no changes is needed by default), then click on Generate.
|
||||
|
||||
\image html usecmake3.png
|
||||
|
||||
* - You can then launch a terminal/command prompt, go to the build folder (`cd path/to/GD/Binaries/build`) and launch the build
|
||||
* using the generator you've choosen: `mingw32-make`, or `make` on Linux/OS X.
|
||||
*
|
||||
* \subsection installAndUseCMake_use_cmd Using the command line
|
||||
*
|
||||
* Using the commandline with CMake is also easy:
|
||||
*
|
||||
* ~~~~~~~~~~~~~~~~~~~~~
|
||||
* cd /path/to/GD/Binaries
|
||||
* mkdir build
|
||||
* cd build
|
||||
* cmake ../..
|
||||
* make
|
||||
* ~~~~~~~~~~~~~~~~~~~~~
|
||||
*
|
||||
* For Windows, replace `cmake ../..` by `cmake ../.. -G "MinGW Makefiles"` and `make` by `mingw32-make`.
|
||||
*
|
||||
* or using the fast [Ninja build system](http://martine.github.io/ninja/) :
|
||||
* ~~~~~~~~~~~~~~~~~~~~~
|
||||
* cd /path/to/GD/Binaries
|
||||
* mkdir build
|
||||
* cd build
|
||||
* cmake ../.. -G "Ninja"
|
||||
* ninja
|
||||
* ~~~~~~~~~~~~~~~~~~~~~
|
||||
*
|
||||
* \section installAndUseCMake_launch Launch GDevelop
|
||||
*
|
||||
* Binaries are created into *Binaries/Output/Release_{OS}* folder.
|
||||
*
|
||||
* To launch GDevelop in Windows, double click on **GDIDE**. For Linux, launch **StartGDevelop.sh**.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \page installLinuxLib (Linux) Install development files
|
||||
*
|
||||
* \section installLibs Install development libraries
|
||||
*
|
||||
* GDevelop is compiled with gcc under Linux.<br>
|
||||
* You need to have some packages to be installed before starting to build GD. These packages can vary according to the distribution you use.
|
||||
* On Ubuntu, you may want to install these packages:
|
||||
\code
|
||||
sudo apt-get install libopenal-dev
|
||||
sudo apt-get install libjpeg-dev
|
||||
sudo apt-get install libglew-dev
|
||||
sudo apt-get install libudev-dev
|
||||
sudo apt-get install libxrandr-dev
|
||||
sudo apt-get install libsndfile1-dev
|
||||
sudo apt-get install libglu1-mesa-dev
|
||||
sudo apt-get install libfreetype6-dev
|
||||
\endcode
|
||||
* Make sure you also have some basic tools installed:
|
||||
\code
|
||||
sudo apt-get install build-essential
|
||||
sudo apt-get install p7zip-full
|
||||
sudo apt-get install curl
|
||||
\endcode
|
||||
*
|
||||
* If you want to package the app, you can also install:
|
||||
\code
|
||||
sudo apt-get install devscripts
|
||||
\endcode
|
||||
*
|
||||
*
|
||||
* \subsection installcmake Install CMake
|
||||
* You'll need CMake to build GDevelop: See more on \subpage installAndUseCMake.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \page installMacOSTools (Mac OS X) Install development tools
|
||||
*
|
||||
* Make sure that you have Apple Developer Tools installed (if you have Xcode and git, that should be the case).
|
||||
*
|
||||
* \section installTools Install development tools
|
||||
*
|
||||
* The simplest way of installing dependencies required by GDevelop is to use [Homebrew](http://brew.sh/). Install it
|
||||
* and install these packages, using the terminal:
|
||||
\code
|
||||
brew install cmake
|
||||
brew install p7zip
|
||||
brew install pkgconfig
|
||||
brew install freetype
|
||||
\endcode
|
||||
* If you want to generate the documentation and translations, install Doxygen and Gettext:
|
||||
\code
|
||||
brew install doxygen
|
||||
brew install gettext
|
||||
\endcode
|
||||
*
|
||||
* \section launchCompilation Launch compilation
|
||||
*
|
||||
* You should be able to compile GD using CMake. Go with a terminal to the GD source folder:
|
||||
\code
|
||||
cd /path/to/GD
|
||||
mkdir build && cd build
|
||||
cmake ../..
|
||||
make -j4
|
||||
\endcode
|
||||
*
|
||||
* More information about compilation here: \ref installAndUseCMake
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* \page recommendedToolsAndConventions Recommended tools and advices to work with GD
|
||||
*
|
||||
* \section git Git and GitHub
|
||||
*
|
||||
* Git is an amazing *version control system*. If you never used it before, take a look at some tutorials, there
|
||||
* are plenty of them on the internet.<br>
|
||||
* Windows users could be interested in using [TortoiseGit](code.google.com/p/tortoisegit) or the official
|
||||
* [GitHub client](https://windows.github.com/).
|
||||
*
|
||||
* \subsection pullrequest Submitting code thanks to Pull Request.
|
||||
*
|
||||
* Using *Pull request*, you can easily submit your changes so that they are integrated into the official
|
||||
* GDevelop repository (http://github.com/4ian/gd).
|
||||
*
|
||||
* See this article on *GitHub help* about pull requests: https://help.github.com/articles/using-pull-requests.<br>
|
||||
* Pull requests are extremely easy to use and the best way to contribute to GD!
|
||||
*
|
||||
* ------
|
||||
*
|
||||
* \section codingstyle Coding style
|
||||
*
|
||||
* As a rule of thumb, try to retain the original coding style used in a file when editing it, or look at other
|
||||
* files when creating a new extension/dialog/feature/class.
|
||||
*
|
||||
* For both C++ and Javascript, *code indentation* should be 4 spaces (or tab set to a width of 4 spaces).<br>
|
||||
* Lines should be cutted when reaching column 110 so that two files can be displayed side-by-side on a same screen.
|
||||
* When cutting a line, indent the new lines with an additional 4 spaces.
|
||||
*
|
||||
* \subsection cpp C++
|
||||
*
|
||||
* *Naming* conventions:
|
||||
* - Classes should be *CamelCase* (starting with a capital).
|
||||
* - All variables (including member variables) should be *camelCase* (no capital for the first letter).
|
||||
* - All functions (including class methods) should be *CamelCase* (starting with a capital).
|
||||
*
|
||||
* *Comments*:
|
||||
* - Comment your classes and functions using *Doxygen* comments.
|
||||
*
|
||||
* \subsection js Javascript
|
||||
*
|
||||
* *Naming* conventions:
|
||||
* - "Classes" should be *CamelCase* (starting with a capital).
|
||||
* - All variables (including member variables) should be *camelCase* (no capital for the first letter).
|
||||
* - All functions (including class methods) should be *camelCase* (no capital for the first letter).
|
||||
*
|
||||
* *Comments*:
|
||||
* - Comment your classes and functions using *yuidoc* comments.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \page overview Overview of GDCore
|
||||
*
|
||||
* \section platformstructure Structure of a platform
|
||||
*
|
||||
* A platform for GDevelop Core is a class inheriting from gd::Platform.<br>
|
||||
* They contains the extensions of the platform (see below) and offer various methods, like gd::Platform::GetProjectExporters which
|
||||
* is called by the IDE to export a gd::Project to a stand-alone game.
|
||||
*
|
||||
* \subsection platformloading Platforms loading
|
||||
* A platform is stored in memory and managed by gd::PlatformManager. It loaded from a dynamic library file (.dll on windows, .so on Linux)
|
||||
* thanks to gd::PlatformLoader.<br>
|
||||
* It is responsibility of the IDE, or any other application using GDCore,
|
||||
* to call the appropriate method of gd::PlatformLoader to trigger the loading of the platforms when needed.
|
||||
*
|
||||
* gd::PlatformLoader search for two symbols in the dynamic library file: *CreateGDPlatform* and
|
||||
* *DestroyGDPlatform*. These symbols must exists and must create (or destroy) the platform class. For example:
|
||||
*
|
||||
* ~~~~~~~~~~~~~~~~~~~~~
|
||||
* extern "C" gd::Platform * GD_API CreateGDPlatform() {
|
||||
* return &JsPlatform::Get(); //Return the singleton object representing the JS Platform
|
||||
* }
|
||||
*
|
||||
* extern "C" void GD_API DestroyGDPlatform() {
|
||||
* JsPlatform::DestroySingleton(); //Destroy the singleton.
|
||||
* }
|
||||
* ~~~~~~~~~~~~~~~~~~~~~
|
||||
*
|
||||
* The platform dynamic library file is often located inside <i>GDevelop directory</i>/xxxPlatform (*xxx* being replaced by the platform acronym).
|
||||
*
|
||||
* In this folder, the platform can store basically anything it needs. For example, both GDJS and GDCpp are storing a folder called *Runtime* containing
|
||||
* the game engine.
|
||||
* If there is a sub directory called *Extensions*, the gd::PlatformLoader tries to load the extensions contained inside (see below).
|
||||
*
|
||||
* \section extensionsstructure Structure of an extension
|
||||
*
|
||||
* **Extensions** are seen by GDevelop Core as classes inheriting from gd::PlatformExtension.<br>
|
||||
* They are stored inside the platform they belong to, and they are also loaded from a dynamic library file thanks to gd::ExtensionsLoader. The main
|
||||
* job of an extension is to <b>declare</b> everything it provides: objects, actions and conditions, behaviors, expressions.<br>
|
||||
* This is done directly using the standard method provided by gd::PlatformExtension, notably:
|
||||
* - gd::PlatformExtension::AddCondition and gd::PlatformExtension::AddAction,
|
||||
* - gd::PlatformExtension::AddExpression (and gd::PlatformExtension::AddStrExpression),
|
||||
* - gd::PlatformExtension::AddObject and gd::PlatformExtension::AddBehavior
|
||||
*
|
||||
*
|
||||
* Some platforms (like the C++ Platform) offer another base class which must be used instead of gd::PlatformExtension when declaring a platorm: as this base class
|
||||
* inherits from gd::PlatformExtension, standard methods still work, but you may be able to declare some others features (the C++ Platform offers
|
||||
* the possibility of declaring debugger related functions).
|
||||
*
|
||||
* \subsection extensionloading Extensions loading
|
||||
*
|
||||
* A single dynamic library file can contains an extension for more than one platform:<br>
|
||||
* You just have to declare a class deriving from gd::PlatformExtension for each platform supported, and a creation function for each platform
|
||||
* (the C++ platform expects a function called *CreateGDExtension* while JS Platform search for a function called *CreateGDJSExtension*).
|
||||
*
|
||||
* \subsection extensionexample Edit or write a new extension
|
||||
*
|
||||
* Refer to these pages for more information about extensions:
|
||||
* - \subpage AboutExtensionCpp
|
||||
* - \subpage writeANewExtension
|
||||
*
|
||||
* \section utf8section UTF8 strings
|
||||
*
|
||||
* Most parts of the codebase support UTF8 strings thanks to gd::String class. gd::String is a wrapper around std::string, exposing a similar
|
||||
* interface as well as a few tool member functions and operators that are all UTF8 aware.
|
||||
*
|
||||
* Its usage is easy, especially if you're familiar with std::string. Some extra functions can be really useful, in particular
|
||||
* the ones to convert the string from/to a number.
|
||||
*
|
||||
\code
|
||||
gd::String str = "Hello";
|
||||
str += " world";
|
||||
str += " " + gd::String::From(2);
|
||||
//str now contains "Hello world 2";
|
||||
|
||||
gd::String twopointfiveStr = "2.5";
|
||||
double twopointfive = twopointfive.To<double>();
|
||||
//twopointfive == 2.5
|
||||
\endcode
|
||||
*
|
||||
*
|
||||
* For more information, see the complete reference of the class. Tests cases have also been made for most functions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* \page writeANewExtension Write a new extension
|
||||
*
|
||||
* \section writeANewExtension_createNewExtension Create a new extension
|
||||
*
|
||||
* Creation of a new extension can be made by following these steps:<br>
|
||||
*
|
||||
* - Copy the directory of an extension and rename it:
|
||||
* \image html createnew1.png
|
||||
* - Rename then the sources files :
|
||||
* \image html createnew2.png
|
||||
* - Open the *CMakeLists.txt* file and replace every occurrence of the extension old name with the new name.<br>
|
||||
* - Open all the source files and again, replace every occurrence of the extension old name with the new name.<br>
|
||||
* - In the *Extensions* directory, open the *CMakeLists.txt* file and add a line such as <code>ADD_SUBDIRECTORY(MyExtension)</code>.
|
||||
* You can then start to modify the extension.<br>
|
||||
* If your extension is fairly simple, you can create it from the AES Extension. <br>
|
||||
* If your extension need an object, you can use for instance the TextObject Extension as a starting point.<br>
|
||||
* <br>
|
||||
* - You can compile your extension by relaunching CMake like described [here](\ref installAndUseCMake). After doing that, just compile as usual.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* \page AboutExtensionCpp About Extension.cpp
|
||||
*
|
||||
* An extension has to define (usually in a file called *Extension.cpp* for the C++ Platform or *JsExtension.cpp* for the JS Platform)
|
||||
* a class that is derived from the gd::PlatformExtension class. This class contains, in its constructor, the declarations
|
||||
* of everything that is provided by the extension.
|
||||
|
||||
* \section extensionDeclaration Declare the extension information
|
||||
|
||||
* The declarations are made using the methods provided by gd::PlatformExtension.
|
||||
*
|
||||
* The first declaration if often the information about the extension:
|
||||
|
||||
* \code
|
||||
Extension()
|
||||
{
|
||||
SetExtensionInformation("TextObject",
|
||||
_("Text object"),
|
||||
_("Extension allowing to use an object displaying a text."),
|
||||
"Florian Rival",
|
||||
"Open Source (MIT License)");
|
||||
* \endcode
|
||||
|
||||
The first parameter is the name of the extension. Choose carefully the name of the extension, as projects are directly referring to it.
|
||||
|
||||
* \section instructionsDeclaration Declare actions, conditions and expressions
|
||||
|
||||
Actions are declared like this :
|
||||
|
||||
* \code
|
||||
AddAction("ActionName",
|
||||
_("Name displayed to users"),
|
||||
_("Description"),
|
||||
_("Sentence displayed in event editor"),
|
||||
_("Category"),
|
||||
"path-to-an-24-by-24-icon-file.png",
|
||||
"path-to-an-16-by-16-icon-file.png")
|
||||
.AddParameter("theTypeOfTheParameter", _("Parameter1"))
|
||||
.AddParameter("theTypeOfTheParameter", _("Parameter2"))
|
||||
.SetFunctionName("MyFunctionName").SetIncludeFile("MyExtension/MyIncludeFile.h");
|
||||
|
||||
* \endcode
|
||||
* Declare conditions and expressions in a similar way.<br>
|
||||
* Parameters are added using gd::InstructionMetadata::AddParameter.
|
||||
*
|
||||
* The last line set the function name that will be called when generating the code of an event using the action:<br>
|
||||
* You can either do it after declaring the function, or later using this syntax:
|
||||
*
|
||||
* \code
|
||||
GetAllActions()["ExtensionName::ActionName"].SetFunctionName("MyFunctionName");
|
||||
* \endcode
|
||||
*
|
||||
* Both methods are ok, but the latest allows to use the same code to declare an extension for the C++ and JS platform,
|
||||
* then customize the names of the functions to call.
|
||||
*
|
||||
* \section objectsDeclaration Declare objects
|
||||
*
|
||||
* Adding an object is made using gd::PlatformExtension::AddObject method.
|
||||
*
|
||||
* \code
|
||||
gd::ObjectMetadata & obj = AddObject<MyObject>(
|
||||
"Name",
|
||||
_("Name displayed to users"),
|
||||
_("Description"),
|
||||
"path-to-a-32-by-32-icon.png");
|
||||
* \endcode
|
||||
*
|
||||
* The *C++ platform* also requires that you call *AddRuntimeObject* to declare the RuntimeObject class associated to the object being declared:<br>
|
||||
* It has two template parameters: the first one is the corresponding object class declared with *AddObject* (class inheriting from *gd::Object*) and the
|
||||
* second one is the *RuntimeObject* class.
|
||||
* You must pass as parameter the metadata from the object previously declared and the name of the class inheriting from RuntimeObject.
|
||||
*
|
||||
* You will also want to specify the .h file associated to the object using gd::ObjectMetadata::SetIncludeFile. For example:
|
||||
* \code
|
||||
//obj is the gd::ObjectMetadata returned when you called AddObject.
|
||||
AddRuntimeObject<TextObject, RuntimeTextObject>(obj, "RuntimeTextObject");
|
||||
obj.SetIncludeFile("TextObject/TextObject.h");
|
||||
* \endcode
|
||||
*
|
||||
* You can then declare the actions, conditions, and expressions related to the objects, using the AddAction/AddCondition/AddExpression methods provided
|
||||
* by <i>obj</i>.
|
||||
|
||||
* \section eventsDeclaration Declaring events
|
||||
*
|
||||
* Events are declared like this :
|
||||
* \code
|
||||
AddEvent("Name",
|
||||
_("Name displayed to users"),
|
||||
"Description",
|
||||
"Group",
|
||||
"path-to-a-16-by-16-icon.png",
|
||||
std::make_shared<EventClassName>())
|
||||
* \endcode
|
||||
*
|
||||
* The event must be able to generate its code when events are being translated to C++ or Javascript:<br>
|
||||
* This is done by calling SetCodeGenerator. For example:
|
||||
*
|
||||
* \code
|
||||
AddEvent("Standard",
|
||||
_("Standard event"),
|
||||
_("Standard event: Actions are run if conditions are fulfilled."),
|
||||
"",
|
||||
"res/eventaddicon.png",
|
||||
std::make_shared<gd::StandardEvent>())
|
||||
.SetCodeGenerator(std::shared_ptr<gd::EventMetadata::CodeGenerator>(codeGen));
|
||||
* \endcode
|
||||
|
||||
* \section behaviorsDeclaration Declaring the behaviors
|
||||
|
||||
Behaviors are declared like objects:
|
||||
|
||||
|
||||
* \code
|
||||
gd::BehaviorMetadata & aut = AddBehavior("Name",
|
||||
_("Name displayed to users"),
|
||||
_("DefaultNameUsedInEditor"),
|
||||
_("Description."),
|
||||
"Group",
|
||||
"path-to-a-32-by-32-icon.png",
|
||||
"BehaviorClassName",
|
||||
std::make_shared<BehaviorClassName>(),
|
||||
std::make_shared<BehaviorSharedDataClassName>());
|
||||
* \endcode
|
||||
* The last line can be replaced by <code>std::shared_ptr<gd::BehaviorsSharedData>()</code> if no shared data are being used.
|
||||
*
|
||||
* You can then declare the actions, conditions, and expressions related to the behavior like objects:<br>
|
||||
* Call AddAction/AddCondition/AddExpression on the <i>aut</i> object.
|
||||
|
||||
* \section excludingNonRuntimeDeclaration (C++ platform) Excluding elements declaration from runtime
|
||||
* When your extension is compiled for the C++ platform Runtime, GDevelop does not known anything about action/condition or even events classes.<br>
|
||||
* You have then to exclude all actions/conditions/expressions/events declaration from extension at runtime (only Extension/Object/Behaviors declarations have to be kept).
|
||||
|
||||
* Use the *<code>GD_IDE_ONLY</code> define* to achieve this goal, as demonstrated in this skeleton of a complete extension declaration:
|
||||
* \code
|
||||
class Extension : public ExtensionBase //For C++ platform, extensions must derive from ExtensionBase
|
||||
{
|
||||
public:
|
||||
Extension()
|
||||
{
|
||||
SetExtensionInformation("MyExtension",
|
||||
_("Extension name"),
|
||||
_("Extension declaration"),
|
||||
"Author",
|
||||
"license");
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
AddAction(...);
|
||||
AddCondition(...);
|
||||
AddExpression(...);
|
||||
#endif
|
||||
|
||||
{
|
||||
gd::ObjectMetadata & obj = AddObject("ObjectName",
|
||||
_("Object name"),
|
||||
_("Description"),
|
||||
"CppPlatform/Extensions/myicon.png",
|
||||
&CreateMyObject,
|
||||
&DestroyMyObject);
|
||||
|
||||
AddRuntimeObject(obj, "RuntimeObjectName", CreateRuntimeObjectName);
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
obj.SetIncludeFile("MyExtension/MyIncludeFile.h");
|
||||
|
||||
obj.AddAction(...);
|
||||
obj.AddCondition(...);
|
||||
obj.AddExpression(...);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
{
|
||||
gd::BehaviorMetadata & aut = AddBehavior("BehaviorName",
|
||||
_("Behavior name"),
|
||||
"defaultGDname",
|
||||
_("Description"),
|
||||
"",
|
||||
"CppPlatform/Extensions/myicon.png",
|
||||
"PhysicsBehavior",
|
||||
std::make_shared<BehaviorClassName>(),
|
||||
std::make_shared<BehaviorSharedDataClassName>());
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
behaviorInfo.SetIncludeFile("MyExtension/MyIncludeFile.h");
|
||||
|
||||
aut.AddAction(...);
|
||||
aut.AddCondition(...);
|
||||
aut.AddExpression(...);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
GD_COMPLETE_EXTENSION_COMPILATION_INFORMATION();
|
||||
};
|
||||
virtual ~Extension() {};
|
||||
};
|
||||
|
||||
// Used by GDevelop to create the extension class
|
||||
// -- Do not need to be modified. --
|
||||
extern "C" ExtensionBase * GD_EXTENSION_API CreateGDExtension() {
|
||||
return new Extension;
|
||||
}
|
||||
* \endcode
|
||||
* - <a href="https://github.com/4ian/GDevelop/blob/master/newIDE/README.md">Getting started with the editor</a>
|
||||
* - <a href="https://github.com/4ian/GDevelop/blob/master/newIDE/README-extensions.md">Getting started with the extensions</a>
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
@@ -93,6 +93,9 @@ class GD_CORE_API EventsFunctionsContainer
|
||||
void MoveEventsFunction(std::size_t oldIndex, std::size_t newIndex) {
|
||||
return Move(oldIndex, newIndex);
|
||||
};
|
||||
std::size_t GetEventsFunctionPosition(const gd::EventsFunction& eventsFunction) {
|
||||
return GetPosition(eventsFunction);
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Provide a raw access to the vector containing the functions.
|
||||
@@ -141,4 +144,4 @@ class GD_CORE_API EventsFunctionsContainer
|
||||
} // namespace gd
|
||||
|
||||
#endif // GDCORE_EVENTSFUNCTIONSCONTAINER_H
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -425,7 +425,7 @@ class GD_CORE_API ResourcesManager {
|
||||
bool MoveResourceDownInList(const gd::String& name);
|
||||
|
||||
/**
|
||||
* Change the position of the specified resource.
|
||||
* \brief Change the position of the specified resource.
|
||||
*/
|
||||
void MoveResource(std::size_t oldIndex, std::size_t newIndex);
|
||||
|
||||
|
||||
@@ -129,6 +129,11 @@ class SerializableWithNameList {
|
||||
*/
|
||||
bool Has(const gd::String& name) const;
|
||||
|
||||
/**
|
||||
* \brief Get the position of an element in the list
|
||||
*/
|
||||
std::size_t GetPosition(const T& element) const;
|
||||
|
||||
/** \name std::vector-like API
|
||||
* These functions ensure that the class can be used just like a std::vector
|
||||
* for iterations.
|
||||
|
||||
@@ -109,6 +109,15 @@ void SerializableWithNameList<T>::Move(std::size_t oldIndex,
|
||||
elements.insert(elements.begin() + newIndex, std::move(object));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::size_t SerializableWithNameList<T>::GetPosition(const T& element) const {
|
||||
for(std::size_t index = 0;index<elements.size();++index) {
|
||||
if (&element == elements[index].get()) return index;
|
||||
}
|
||||
|
||||
return (size_t)-1;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void SerializableWithNameList<T>::SerializeElementsTo(
|
||||
const gd::String& elementName, SerializerElement& serializerElement) const {
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
# GDevelop Architecture Overview
|
||||
|
||||
GDevelop is architectured around a `Core` library, platforms (`GDJS`, `GDCpp`) and extensions (`Extensions` folder). The editor (`newIDE` folder) is using all of these libraries. This is a recap table of the main folders:
|
||||
|
||||
| Directory | ℹ️ Description |
|
||||
| --- | --- |
|
||||
| `Core` | GDevelop core library, containing common tools to implement the IDE and work with GDevelop games. |
|
||||
| `GDCpp` | GDevelop C++ game engine, used to **build native games**. |
|
||||
| `GDJS` | GDevelop JS game engine, used to build **HTML5 games**. |
|
||||
| `GDevelop.js` | Bindings of Core/GDCpp/GDJS and Extensions to JavaScript (used by the IDE). |
|
||||
| `newIDE` | The game editor, written in JavaScript with React, Electron and Pixi.js. |
|
||||
| `Extensions` | Extensions for C++ or JS game engines, providing objects, events and new features. |
|
||||
|
||||
The rest of this page is an introduction to the main concepts of GDevelop architecture.
|
||||
|
||||
## Some vocabulary: "Runtime" and "IDE"
|
||||
|
||||
**IDE** stands for "Integrated Development Environment". A synonym for it is also simply "editor". In GDevelop, the software itself, that is used to create games and running as an app or a web-app, is called the "GDevelop Editor" or the "GDevelop IDE"
|
||||
|
||||
> This term "IDE" is also used in some folders. When you browse `Core`, `GDCpp` or `GDJS` subfolders, some folders are called `IDE`. They contain classes and tools that are **only useful for the editor** (they are not per se mandatory to describe the structure of a game).
|
||||
|
||||
**Runtime** is a word used to describe classes, tools and source files being used during a game. This could also be called "in game" or a "game engine".
|
||||
|
||||
> When you browse `GDCpp` or `GDJS` subfolders, you can find folders called `Runtime`. They contain the **game engine** of GDevelop.
|
||||
|
||||
Extensions do have the same distinction between the "**IDE**" part and the "**Runtime**" part. For example, most extensions have:
|
||||
|
||||
* A file called [`JsExtension.js`(https://github.com/4ian/GDevelop/blob/master/Extensions/ExampleJsExtension/JsExtension.js)], which contains the *declaration* of the extension for the **IDE**
|
||||
* One or more files implementing the feature for the game, in other words for **Runtime**. This can be a [Runtime Object](https://github.com/4ian/GDevelop/blob/master/Extensions/ExampleJsExtension/dummyruntimeobject.js) or a [Runtime Behavior](https://github.com/4ian/GDevelop/blob/master/Extensions/ExampleJsExtension/dummyruntimebehavior.js), [functions called by actions or conditions](https://github.com/4ian/GDevelop/blob/master/Extensions/ExampleJsExtension/examplejsextensiontools.js) or by the game engine.
|
||||
|
||||
### "Runtime" and "IDE" difference using an example: the `gd::Variable` class
|
||||
|
||||
In GDevelop, developers can associate and manipulate variables in their games. To represent them, we have two things:
|
||||
|
||||
* The **editor** `gd::Variable` that is part of the structure of the game, so living in [GDCore in Variable.h](https://4ian.github.io/GD-Documentation/GDCore%20Documentation/classgd_1_1_variable.html). This is what is shown in the editor, saved on disk in the project file.
|
||||
* The **game engine** variable, called `gdjs.Variable` in GDJS. [Documentation is in the GDJS **game engine**](http://4ian.github.io/GD-Documentation/GDJS%20Runtime%20Documentation/gdjs.Variable.html). This JavaScript class is what is used during a game.
|
||||
|
||||
The editor `gd::Variable` **know nothing** about the game engine class `gdjs.Variable`. And the `gdjs.Variable` class in the game engine know almost nothing from `gd::Variable` (apart from how it's written in JSON, to be able to load a game default variables).
|
||||
|
||||
> Note that the name `gdjs.Variable` is maybe a *bad decision*: it should have been called a `gdjs.RuntimeVariable`, like `gdjs.RuntimeObject` and like most other classes of the game engine.
|
||||
|
||||
## What's inside "Core" (`GDCore` folder)?
|
||||
|
||||
GDevelop "Core" is basically containing everything that is used to **describe and manipulate the structure of a game** (called a `Project` internally). This includes events, scenes, objects, behaviors, events etc... All of these are implemented using C++ classes, in [this folder named `Project`](https://github.com/4ian/GDevelop/tree/master/Core/GDCore/Project).
|
||||
|
||||
GDevelop "Core" also contains **tools** to manipulate these `Project`. In particular, `Core/GDCore/IDE` folder is containing C++ classes allowing to do operations on the structure of a game. For example, [WholeProjectRefactorer](https://github.com/4ian/GDevelop/blob/master/Core/GDCore/IDE/WholeProjectRefactorer.cpp) is a very powerful tool, used to rename all objects in a game, update events after an object is deleted and more generally do Project wide refactoring. The directory contains other "tool" functions to manipulate [resources of projects](https://github.com/4ian/GDevelop/tree/master/Core/GDCore/IDE/Project) or do [search in events](https://github.com/4ian/GDevelop/tree/master/Core/GDCore/IDE/Events).
|
||||
|
||||
## What's inside GDJS? Why do I see some C++ file there?
|
||||
|
||||
While `GDJS/Runtime` folder is the game engine that is executed inside a game, `GDJS/GDJS` is the "IDE" part, which are the C++ classes describing to the editor various things like:
|
||||
|
||||
* How [do you do an export](https://github.com/4ian/GDevelop/tree/master/GDJS/GDJS/IDE),
|
||||
* What are [the default extensions](https://github.com/4ian/GDevelop/tree/master/GDJS/GDJS/Extensions/Builtin),
|
||||
* How do you [generate JS code from events](https://github.com/4ian/GDevelop/tree/master/GDJS/GDJS/Events/CodeGeneration),
|
||||
|
||||
The game engine is in GDJS/Runtime and is all JavaScript.
|
||||
|
||||
## What about events?
|
||||
|
||||
An "**event**" is by default something that [is mostly empty](http://4ian.github.io/GD-Documentation/GDCore%20Documentation/classgd_1_1_base_event.html). In a more traditional programming language, an event can be seen as a scope or block (example: `{ some code here }` in a C style language like JavaScript, Java or C++).
|
||||
|
||||
[Default events are defined](https://github.com/4ian/GDevelop/tree/master/Core/GDCore/Events/Builtin) in GDevelop Core.
|
||||
In particular, there is StandardEvent, which has conditions and actions. Conditions and actions are both a list of [`gd::Instruction`](https://4ian.github.io/GD-Documentation/GDCore%20Documentation/classgd_1_1_instruction.html). A `gd::Instruction` is either a condition or an action (it depends only on the context where they are used).
|
||||
|
||||
A `gd::Instruction` is "just" a type (the name of the action or condition), and some parameters. You can think of it as a function in a programming language (`add(2, 3)` is calling a function called "add" with parameter "2" and "3"). Conditions have the special thing that they are functions returning true or false (and potentially being used to do filter on the objects being picked for next conditions and actions).
|
||||
|
||||
### Why can't I see any "RuntimeEvent" or events during the game?
|
||||
|
||||
They do not exist anymore! ✨
|
||||
|
||||
Events are translated (we also say "transpiled" or "generated") into "real" code in a programming language. This process is called "Code Generation", and is [done here for the JavaScript game engine](https://github.com/4ian/GDevelop/tree/master/GDJS/GDJS/Events/CodeGeneration).
|
||||
|
||||
### Can I extract Events classes and code generator to make a development environment based on GDevelop events?
|
||||
|
||||
You're welcome to do so! Please get in touch :)
|
||||
|
||||
## I can see more than one Extensions folder, why?
|
||||
|
||||
The idea of GDevelop editor and game engine is to have a lean game engine, supporting almost nothing. Then, one can add "mods", "plugins", "modules" for GDevelop. We chose to call them "**Extensions**" in GDevelop.
|
||||
|
||||
* `GDevelop/Core/GDCore/Extensions` is the **declaration** of default (we say "builtin") extensions, that are available for any game and are "mandatory". They are called Extensions but they could be named "Extensions that will always be in your game". In programming languages, this is called a "[Standard Library](https://en.wikipedia.org/wiki/Standard_library)" (but don't get too distracted by this naming).
|
||||
* `GDevelop/GDJS/GDJS/Extensions/` and `GDevelop/GDCpp/GDCpp/Extensions/` are reusing these **declarations** and **adding** their own declarations. Mainly, they are setting the name of the functions to be called (either in JS or in C++) for each action, condition or expression.
|
||||
* `GDevelop/Extensions/` is the folder for the "mods"/"plugins" for GDevelop - the one that are not mandatory. They are not part of GDCore - they work on their own.
|
||||
|
||||
> In theory, all extensions could be moved to `GDevelop/Extensions/`. In practice, it's more pragmatic to have a set of "builtin" extensions with basic features.
|
||||
|
||||
## What's GDevelop.js? Do we care about this?
|
||||
|
||||
Everything in GDevelop.js is meant to create a "bridge" allowing to run and use C++ from JavaScript for the **IDE**, so that [we can write an editor entirely in JavaScript](https://github.com/4ian/GDevelop/tree/master/newIDE) and have it working in a web browser.
|
||||
|
||||
* We're using Emscripten which is compiling C++, but instead of writing a "native binary", it's writing a file that works in a browser (basically, JavaScript!).
|
||||
|
||||
* The most useful file is [Bindings.idl](https://github.com/4ian/GDevelop/blob/master/GDevelop.js/Bindings/Bindings.idl) that is describing everything in C++ that must be exposed to JavaScript (in the editor, not in game. Remember that during the game, we're at **Runtime**, so all of this does not exist anymore)
|
||||
|
||||
* Rest of the files are mostly bridges doing "weird stuff" to translate from JS to C++ or vice versa. It requires a bit of knowledge about how the ["bridge", made by Emscripten, called WebIDL](https://emscripten.org/docs/porting/connecting_cpp_and_javascript/WebIDL-Binder.html) is working.
|
||||
|
||||
You don't need to work with these unless you want to expose something that is written in C++ to the editor, and writing the interface in Bindings.idl is not sufficient.
|
||||
|
||||
90% of the time, you can just read or write about a class in [Bindings.idl](https://github.com/4ian/GDevelop/blob/master/GDevelop.js/Bindings/Bindings.idl). If you work in C++ classes, you may have sometime to add a header file in Wrapper.cpp and your C++ class is "automagically" compiled and available in JavaScript after writing the corresponding interface in Bindings.idl.
|
||||
|
||||
### I want all the gory details about GDevelop.js 🧐
|
||||
|
||||
All the required C++ files are imported into this huge list: https://github.com/4ian/GDevelop/blob/master/GDevelop.js/Bindings/Wrapper.cpp#L1-L79. C++ compilation is done with a "build system" called CMake, which is using [this file](https://github.com/4ian/GDevelop/blob/master/GDevelop.js/CMakeLists.txt#L82-L101) to see what to compile.
|
||||
|
||||
> In an ideal world, there would be something to do this automatically, so GDevelop.js folder would not even exist 😉
|
||||
> If you're interested and want to know more about GDevelop.js bridging between C++ and JavaScript, look at [this talk from GDevelop original author](https://www.youtube.com/watch?v=6La7jSCnYyk).
|
||||
|
||||
## Misc questions
|
||||
|
||||
### What's the deal with C++? Why so much of it?
|
||||
|
||||
GDevelop was originally written in C++. It's a scary language at first but is portable across almost any existing machine in this universe, can be pretty good, safe and readable with the latest C++ features.
|
||||
|
||||
### What's the deal with JavaScript? Why so much of it?
|
||||
|
||||
JavaScript, with the latest language proposals, is actually a very capable language, fast to write and safe with typing:
|
||||
|
||||
* Performance is getting pretty good with recent browsers JIT features.
|
||||
* Frontend frameworks like React, used for GDevelop IDE, allow for very fast and modular interface development.
|
||||
* The web is an incredible and unmatched target when it comes to cross platform (and cross form factor) apps.
|
||||
|
||||
More generally, the web is a great target for games with the rise of WebGL and WebAssembly - though GDevelop should stay modular to adapt to newer platforms for the exported games in the future.
|
||||
@@ -401,7 +401,9 @@ module.exports = {
|
||||
registerEditorConfigurations: function(objectsEditorService) {
|
||||
objectsEditorService.registerEditorConfiguration(
|
||||
'MyDummyExtension::DummyObject',
|
||||
objectsEditorService.getDefaultObjectJsImplementationPropertiesEditor()
|
||||
objectsEditorService.getDefaultObjectJsImplementationPropertiesEditor({
|
||||
helpPagePath: '/extensions/extend-gdevelop',
|
||||
})
|
||||
);
|
||||
},
|
||||
/**
|
||||
|
||||
@@ -204,7 +204,7 @@ void ExtensionSubDeclaration2(gd::ObjectMetadata& obj) {
|
||||
.SetManipulatedType("number");
|
||||
|
||||
obj.AddCondition("ParticleSize1",
|
||||
_("SIze, parameter 1"),
|
||||
_("Size, parameter 1"),
|
||||
_("Test parameter 1 of the size of particles"),
|
||||
_("Parameter 1 of the size of _PARAM0_ is _PARAM1__PARAM2_"),
|
||||
_("Common"),
|
||||
|
||||
@@ -84,8 +84,8 @@ void DeclarePrimitiveDrawingExtension(gd::PlatformExtension& extension) {
|
||||
_("Change the color used when filling"),
|
||||
_("Change fill color of _PARAM0_ to _PARAM1_"),
|
||||
_("Setup"),
|
||||
"res/actions/text24.png",
|
||||
"res/actions/text.png")
|
||||
"res/actions/color24.png",
|
||||
"res/actions/color.png")
|
||||
|
||||
.AddParameter("object", _("Shape Painter object"), "Drawer")
|
||||
.AddParameter("color", _("Fill color"))
|
||||
|
||||
@@ -500,7 +500,9 @@ module.exports = {
|
||||
registerEditorConfigurations: function(objectsEditorService) {
|
||||
objectsEditorService.registerEditorConfiguration(
|
||||
"Video::VideoObject",
|
||||
objectsEditorService.getDefaultObjectJsImplementationPropertiesEditor()
|
||||
objectsEditorService.getDefaultObjectJsImplementationPropertiesEditor({
|
||||
helpPagePath: "/objects/video"
|
||||
})
|
||||
);
|
||||
},
|
||||
/**
|
||||
|
||||
@@ -57,7 +57,6 @@ gd::PlatformExtension *CreateGDJSShopifyExtension();
|
||||
gd::PlatformExtension *CreateGDJSPathfindingBehaviorExtension();
|
||||
gd::PlatformExtension *CreateGDJSPhysicsBehaviorExtension();
|
||||
gd::PlatformExtension *CreateGDJSParticleSystemExtension();
|
||||
gd::PlatformExtension *CreateGDJSSkeletonObjectExtension();
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -164,8 +163,6 @@ void JsPlatform::ReloadBuiltinExtensions() {
|
||||
std::cout.flush();
|
||||
AddExtension(std::shared_ptr<gd::PlatformExtension>(
|
||||
CreateGDJSPhysicsBehaviorExtension()));
|
||||
AddExtension(std::shared_ptr<gd::PlatformExtension>(
|
||||
CreateGDJSSkeletonObjectExtension()));
|
||||
std::cout.flush();
|
||||
#endif
|
||||
std::cout << "done." << std::endl;
|
||||
|
||||
@@ -1683,6 +1683,7 @@ interface EventsFunctionsContainer {
|
||||
void RemoveEventsFunction([Const] DOMString name);
|
||||
void MoveEventsFunction(unsigned long oldIndex, unsigned long newIndex);
|
||||
unsigned long GetEventsFunctionsCount();
|
||||
unsigned long GetEventsFunctionPosition([Const, Ref] EventsFunction eventsFunction);
|
||||
};
|
||||
|
||||
interface EventsBasedBehavior {
|
||||
@@ -1717,6 +1718,7 @@ interface EventsBasedBehaviorsList {
|
||||
void Remove([Const] DOMString name);
|
||||
void Move(unsigned long oldIndex, unsigned long newIndex);
|
||||
unsigned long GetCount();
|
||||
unsigned long GetPosition([Const, Ref] EventsBasedBehavior item);
|
||||
|
||||
unsigned long size();
|
||||
[Ref] EventsBasedBehavior at(unsigned long index);
|
||||
@@ -1772,6 +1774,7 @@ interface EventsFunctionsExtension {
|
||||
void RemoveEventsFunction([Const] DOMString name);
|
||||
void MoveEventsFunction(unsigned long oldIndex, unsigned long newIndex);
|
||||
unsigned long GetEventsFunctionsCount();
|
||||
unsigned long GetEventsFunctionPosition([Const, Ref] EventsFunction eventsFunction);
|
||||
};
|
||||
|
||||
interface AbstractFileSystem {
|
||||
|
||||
@@ -99,4 +99,3 @@ target_link_libraries(GD PathfindingBehavior)
|
||||
target_link_libraries(GD PhysicsBehavior)
|
||||
# target_link_libraries(GD FacebookInstantGames) - Pure JS extension
|
||||
target_link_libraries(GD ParticleSystem)
|
||||
target_link_libraries(GD SkeletonObject)
|
||||
|
||||
@@ -23,10 +23,10 @@ Overview of the architecture
|
||||
| `GDCpp` | GDevelop C++ game engine, used to **build native games**. |
|
||||
| `GDJS` | GDevelop JS game engine, used to build **HTML5 games**. |
|
||||
| `GDevelop.js` | Bindings of Core/GDCpp/GDJS and Extensions to JavaScript (used by the IDE). |
|
||||
| `newIDE` | The game editor, written in Javascript with React, Electron and Pixi.js. |
|
||||
| `newIDE` | The game editor, written in JavaScript with React, Electron and Pixi.js. |
|
||||
| `Extensions` | Extensions for C++ or JS game engines, providing objects, events and new features. |
|
||||
|
||||
### Documentation
|
||||
To learn more about GDevelop Architecture, read the [architecture overview here](Core/GDevelop-Architecture-Overview.md).
|
||||
|
||||
A pre-generated documentation of the Core library, C++ and JS game engines is [available here](http://4ian.github.io/GD-Documentation).
|
||||
|
||||
|
||||
@@ -126,7 +126,9 @@ To add an editor to your object, implement the function `registerEditorConfigura
|
||||
registerEditorConfigurations: function(objectsEditorService) {
|
||||
objectsEditorService.registerEditorConfiguration(
|
||||
"MyDummyExtension::DummyObject", // Replace by your extension and object type names.
|
||||
objectsEditorService.getDefaultObjectJsImplementationPropertiesEditor()
|
||||
objectsEditorService.getDefaultObjectJsImplementationPropertiesEditor({
|
||||
helpPagePath : "/extensions/extend-gdevelop" // The link to the help page for your object, in GDevelop wiki.
|
||||
})
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Generated
+3893
-4079
File diff suppressed because it is too large
Load Diff
@@ -53,6 +53,8 @@
|
||||
"react-color": "2.13.8",
|
||||
"react-dnd": "7.7.0",
|
||||
"react-dnd-html5-backend": "7.7.0",
|
||||
"react-dnd-multi-backend": "3.2.2",
|
||||
"react-dnd-touch-backend": "0.8.3",
|
||||
"react-dom": "16.8.6",
|
||||
"react-error-boundary": "^1.2.0",
|
||||
"react-json-view": "^1.16.1",
|
||||
|
||||
@@ -30,6 +30,10 @@
|
||||
<!-- Root div used for React `App` component rendering-->
|
||||
<div id="root"></div>
|
||||
|
||||
<script>
|
||||
window["GD_STARTUP_TIMES"] = [["indexHtmlFirstScriptStarted", performance.now()]];
|
||||
</script>
|
||||
|
||||
<!-- GDevelop.js core -->
|
||||
<script>
|
||||
//Ensure module is not defined to avoid GDevelop.js to think
|
||||
@@ -40,6 +44,11 @@
|
||||
</script>
|
||||
<script src="%PUBLIC_URL%/libGD.js"></script>
|
||||
|
||||
<script>
|
||||
window["GD_STARTUP_TIMES"] = window["GD_STARTUP_TIMES"] || [];
|
||||
window["GD_STARTUP_TIMES"].push(["libGDLoadedTime", performance.now()]);
|
||||
</script>
|
||||
|
||||
<!-- Monaco Editor support -->
|
||||
<script>
|
||||
// Monaco editor is using amd require. Save the Node.js require if it exists (Electron)
|
||||
@@ -65,5 +74,10 @@
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<script>
|
||||
window["GD_STARTUP_TIMES"] = window["GD_STARTUP_TIMES"] || [];
|
||||
window["GD_STARTUP_TIMES"].push(["monacoEditorLoaderLoaded", performance.now()]);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -36,6 +36,42 @@ const updateResources = (project, baseUrl) => {
|
||||
project.exposeResources(worker);
|
||||
};
|
||||
|
||||
const writeBrowserExampleFilesJsFile = exampleNames => {
|
||||
let importsCode = [];
|
||||
let internalFilesObjectCode = [];
|
||||
exampleNames.forEach((exampleName, index) => {
|
||||
importsCode.push(`import exampleFile${index} from '../fixtures/${exampleName}/${exampleName}.json';`);
|
||||
internalFilesObjectCode.push(` 'example://${exampleName}': exampleFile${index},`);
|
||||
});
|
||||
|
||||
const content = [
|
||||
`// This file is generated by update-fixtures-from-resources-examples.js script`,
|
||||
``,
|
||||
importsCode.join('\n'),
|
||||
``,
|
||||
`// prettier-ignore`,
|
||||
`export default {`,
|
||||
internalFilesObjectCode.join('\n'),
|
||||
`};`,
|
||||
``,
|
||||
].join('\n');
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.writeFile(
|
||||
'../src/ProjectsStorage/BrowserExampleFiles.js',
|
||||
content,
|
||||
'utf8',
|
||||
err => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
|
||||
resolve();
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const noopTranslationFunction = str => str;
|
||||
const extensionsLoader = makeExtensionsLoader({ gd, filterExamples: false });
|
||||
extensionsLoader
|
||||
@@ -55,18 +91,35 @@ extensionsLoader
|
||||
console.log(`Example "${exampleName}" loaded.`);
|
||||
const project = loadSerializedProject(gd, projectObject);
|
||||
updateResources(project, baseUrl + '/' + exampleName);
|
||||
fs.mkdir(`../src/fixtures/${exampleName}`, () => {
|
||||
writeProjectJSONFile(
|
||||
gd,
|
||||
project,
|
||||
`../src/fixtures/${exampleName}/${exampleName}.json`
|
||||
);
|
||||
console.log(`Update of "${exampleName}" done.`);
|
||||
|
||||
return new Promise(resolve => {
|
||||
fs.mkdir(`../src/fixtures/${exampleName}`, () => {
|
||||
writeProjectJSONFile(
|
||||
gd,
|
||||
project,
|
||||
`../src/fixtures/${exampleName}/${exampleName}.json`
|
||||
);
|
||||
|
||||
console.log(`Update of "${exampleName}" done.`);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error caught:', error);
|
||||
console.error(`❌ Error caught for ${exampleName}:`, error);
|
||||
});
|
||||
})
|
||||
)
|
||||
.then(() => writeBrowserExampleFilesJsFile(exampleNames))
|
||||
.then(
|
||||
() => {
|
||||
console.error(`✅ BrowserExampleFiles.js written.`);
|
||||
},
|
||||
error => {
|
||||
console.error(
|
||||
`❌ Error caught while writing BrowserExampleFiles.js:`,
|
||||
error
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// @flow
|
||||
import { t } from '@lingui/macro';
|
||||
import { Trans } from '@lingui/macro';
|
||||
|
||||
import * as React from 'react';
|
||||
import { Line, Column } from '../../../UI/Grid';
|
||||
@@ -15,6 +16,7 @@ import ShapePreview from './ShapePreview.js';
|
||||
import PolygonEditor from './PolygonEditor.js';
|
||||
import { type BehaviorEditorProps } from '../BehaviorEditorProps.flow';
|
||||
import Text from '../../../UI/Text';
|
||||
import DismissableAlertMessage from '../../../UI/DismissableAlertMessage';
|
||||
|
||||
type Props = BehaviorEditorProps;
|
||||
|
||||
@@ -181,6 +183,20 @@ export default class Physics2Editor extends React.Component<Props, State> {
|
||||
/>
|
||||
</Column>
|
||||
</Line>
|
||||
<Line>
|
||||
<DismissableAlertMessage
|
||||
identifier="physics2-shape-collisions"
|
||||
kind="info"
|
||||
>
|
||||
<Trans>
|
||||
The shape used in the Physics behavior is independent from the
|
||||
collision mask of the object. Be sure to use the "Collision"
|
||||
condition provided by the Physics behavior in the events. The
|
||||
usual "Collision" condition won't take into account the shape that
|
||||
you've set up here.
|
||||
</Trans>
|
||||
</DismissableAlertMessage>
|
||||
</Line>
|
||||
<Line>
|
||||
<SelectField
|
||||
floatingLabelText={properties.get('shape').getLabel()}
|
||||
@@ -386,7 +402,7 @@ export default class Physics2Editor extends React.Component<Props, State> {
|
||||
/>
|
||||
</Column>
|
||||
<BackgroundText>
|
||||
An temporary image to help you visualize the shape/polygon
|
||||
A temporary image to help you visualize the shape/polygon
|
||||
</BackgroundText>
|
||||
</Line>
|
||||
{shape === 'Polygon' && (
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { Trans } from '@lingui/macro';
|
||||
|
||||
import * as React from 'react';
|
||||
import EditorMosaic, { MosaicWindow } from '../UI/EditorMosaic';
|
||||
import EditorMosaic from '../UI/EditorMosaic';
|
||||
import Background from '../UI/Background';
|
||||
import get from 'lodash/get';
|
||||
import RaisedButton from '../UI/RaisedButton';
|
||||
@@ -56,7 +56,7 @@ export default class DebuggerContent extends React.Component<Props, State> {
|
||||
_editors: ?EditorMosaic = null;
|
||||
|
||||
openProfiler = () => {
|
||||
if (this._editors) this._editors.openEditor('profiler', 'bottom');
|
||||
if (this._editors) this._editors.openEditor('profiler', 'end', 75);
|
||||
};
|
||||
|
||||
render() {
|
||||
@@ -80,12 +80,11 @@ export default class DebuggerContent extends React.Component<Props, State> {
|
||||
<EditorMosaic
|
||||
ref={editors => (this._editors = editors)}
|
||||
editors={{
|
||||
inspectors: (
|
||||
<MosaicWindow
|
||||
title={<Trans>Inspectors</Trans>}
|
||||
toolbarControls={[]}
|
||||
gameData={gameData}
|
||||
>
|
||||
inspectors: {
|
||||
type: 'primary',
|
||||
title: <Trans>Inspectors</Trans>,
|
||||
toolbarControls: [],
|
||||
renderEditor: () => (
|
||||
<Background>
|
||||
<Column expand noMargin>
|
||||
<Line justifyContent="center">
|
||||
@@ -112,81 +111,81 @@ export default class DebuggerContent extends React.Component<Props, State> {
|
||||
</Line>
|
||||
</Column>
|
||||
</Background>
|
||||
</MosaicWindow>
|
||||
),
|
||||
'selected-inspector': (
|
||||
<Column expand noMargin>
|
||||
{' '}
|
||||
{selectedInspector ? (
|
||||
rawMode ? (
|
||||
<RawContentInspector
|
||||
gameData={get(gameData, selectedInspectorFullPath, null)}
|
||||
onEdit={(path, newValue) =>
|
||||
onEdit(selectedInspectorFullPath.concat(path), newValue)
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
selectedInspector.renderInspector(
|
||||
get(gameData, selectedInspectorFullPath, null),
|
||||
{
|
||||
onCall: (path, args) =>
|
||||
onCall(selectedInspectorFullPath.concat(path), args),
|
||||
onEdit: (path, newValue) =>
|
||||
onEdit(
|
||||
selectedInspectorFullPath.concat(path),
|
||||
newValue
|
||||
),
|
||||
}
|
||||
) || (
|
||||
<EmptyMessage>
|
||||
<Trans>
|
||||
No inspector, choose another element in the list or
|
||||
toggle the raw data view.
|
||||
</Trans>
|
||||
</EmptyMessage>
|
||||
)
|
||||
)
|
||||
) : (
|
||||
<EmptyMessage>
|
||||
{gameData
|
||||
? 'Choose an element to inspect in the list'
|
||||
: 'Pause the game (from the toolbar) or hit refresh (on the left) to inspect the game'}
|
||||
</EmptyMessage>
|
||||
)}
|
||||
<Column>
|
||||
<Line justifyContent="space-between" alignItems="center">
|
||||
<HelpButton helpPagePath="/interface/debugger" />
|
||||
<div>
|
||||
<Checkbox
|
||||
checkedIcon={<Flash />}
|
||||
uncheckedIcon={<FlashOff />}
|
||||
checked={rawMode}
|
||||
onCheck={(e, enabled) =>
|
||||
this.setState({
|
||||
rawMode: enabled,
|
||||
})
|
||||
),
|
||||
},
|
||||
'selected-inspector': {
|
||||
type: 'primary',
|
||||
noTitleBar: true,
|
||||
renderEditor: () => (
|
||||
<Column expand noMargin>
|
||||
{selectedInspector ? (
|
||||
rawMode ? (
|
||||
<RawContentInspector
|
||||
gameData={get(gameData, selectedInspectorFullPath, null)}
|
||||
onEdit={(path, newValue) =>
|
||||
onEdit(selectedInspectorFullPath.concat(path), newValue)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</Line>
|
||||
) : (
|
||||
selectedInspector.renderInspector(
|
||||
get(gameData, selectedInspectorFullPath, null),
|
||||
{
|
||||
onCall: (path, args) =>
|
||||
onCall(selectedInspectorFullPath.concat(path), args),
|
||||
onEdit: (path, newValue) =>
|
||||
onEdit(
|
||||
selectedInspectorFullPath.concat(path),
|
||||
newValue
|
||||
),
|
||||
}
|
||||
) || (
|
||||
<EmptyMessage>
|
||||
<Trans>
|
||||
No inspector, choose another element in the list or
|
||||
toggle the raw data view.
|
||||
</Trans>
|
||||
</EmptyMessage>
|
||||
)
|
||||
)
|
||||
) : (
|
||||
<EmptyMessage>
|
||||
{gameData
|
||||
? 'Choose an element to inspect in the list'
|
||||
: 'Pause the game (from the toolbar) or hit refresh (on the left) to inspect the game'}
|
||||
</EmptyMessage>
|
||||
)}
|
||||
<Column>
|
||||
<Line justifyContent="space-between" alignItems="center">
|
||||
<HelpButton helpPagePath="/interface/debugger" />
|
||||
<div>
|
||||
<Checkbox
|
||||
checkedIcon={<Flash />}
|
||||
uncheckedIcon={<FlashOff />}
|
||||
checked={rawMode}
|
||||
onCheck={(e, enabled) =>
|
||||
this.setState({
|
||||
rawMode: enabled,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</Line>
|
||||
</Column>
|
||||
</Column>
|
||||
</Column>
|
||||
),
|
||||
profiler: (
|
||||
<MosaicWindow
|
||||
title={<Trans>Profiler</Trans>}
|
||||
// Pass profilerOutput to force MosaicWindow update when profilerOutput is changed
|
||||
profilerOutput={profilerOutput}
|
||||
profilingInProgress={profilingInProgress}
|
||||
>
|
||||
),
|
||||
},
|
||||
profiler: {
|
||||
type: 'secondary',
|
||||
title: <Trans>Profiler</Trans>,
|
||||
renderEditor: () => (
|
||||
<Profiler
|
||||
onStart={onStartProfiler}
|
||||
onStop={onStopProfiler}
|
||||
profilerOutput={profilerOutput}
|
||||
profilingInProgress={profilingInProgress}
|
||||
/>
|
||||
</MosaicWindow>
|
||||
),
|
||||
),
|
||||
},
|
||||
}}
|
||||
initialNodes={{
|
||||
direction: 'column',
|
||||
|
||||
@@ -31,6 +31,10 @@ type State = {|
|
||||
searchText: string,
|
||||
|};
|
||||
|
||||
const getEventsBasedBehaviorName = (
|
||||
eventsBasedBehavior: gdEventsBasedBehavior
|
||||
) => eventsBasedBehavior.getName();
|
||||
|
||||
type Props = {|
|
||||
project: gdProject,
|
||||
eventsBasedBehaviorsList: gdEventsBasedBehaviorsList,
|
||||
@@ -69,7 +73,7 @@ export default class EventsBasedBehaviorsList extends React.Component<
|
||||
) => cb(true),
|
||||
};
|
||||
|
||||
sortableList: any;
|
||||
sortableList: ?SortableVirtualizedItemList<gdEventsFunction>;
|
||||
state: State = {
|
||||
renamedEventsBasedBehavior: null,
|
||||
searchText: '',
|
||||
@@ -102,7 +106,9 @@ export default class EventsBasedBehaviorsList extends React.Component<
|
||||
{
|
||||
renamedEventsBasedBehavior,
|
||||
},
|
||||
() => this.sortableList.getWrappedInstance().forceUpdateGrid()
|
||||
() => {
|
||||
if (this.sortableList) this.sortableList.forceUpdateGrid();
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -131,16 +137,26 @@ export default class EventsBasedBehaviorsList extends React.Component<
|
||||
);
|
||||
};
|
||||
|
||||
_move = (oldIndex: number, newIndex: number) => {
|
||||
const { eventsBasedBehaviorsList } = this.props;
|
||||
eventsBasedBehaviorsList.move(oldIndex, newIndex);
|
||||
_moveSelectionTo = (
|
||||
destinationEventsBasedBehavior: gdEventsBasedBehavior
|
||||
) => {
|
||||
const {
|
||||
eventsBasedBehaviorsList,
|
||||
selectedEventsBasedBehavior,
|
||||
} = this.props;
|
||||
if (!selectedEventsBasedBehavior) return;
|
||||
|
||||
eventsBasedBehaviorsList.move(
|
||||
eventsBasedBehaviorsList.getPosition(selectedEventsBasedBehavior),
|
||||
eventsBasedBehaviorsList.getPosition(destinationEventsBasedBehavior)
|
||||
);
|
||||
|
||||
this.forceUpdateList();
|
||||
};
|
||||
|
||||
forceUpdateList = () => {
|
||||
this.forceUpdate();
|
||||
this.sortableList.getWrappedInstance().forceUpdateGrid();
|
||||
if (this.sortableList) this.sortableList.forceUpdateGrid();
|
||||
};
|
||||
|
||||
_copyEventsBasedBehavior = (eventsBasedBehavior: gdEventsBasedBehavior) => {
|
||||
@@ -250,15 +266,10 @@ export default class EventsBasedBehaviorsList extends React.Component<
|
||||
} = this.props;
|
||||
const { searchText } = this.state;
|
||||
|
||||
const list = [
|
||||
...filterEventsBasedBehaviorsList(
|
||||
enumerateEventsBasedBehaviors(eventsBasedBehaviorsList),
|
||||
searchText
|
||||
),
|
||||
{
|
||||
key: 'add-item-row',
|
||||
},
|
||||
];
|
||||
const list = filterEventsBasedBehaviorsList(
|
||||
enumerateEventsBasedBehaviors(eventsBasedBehaviorsList),
|
||||
searchText
|
||||
);
|
||||
|
||||
// Force List component to be mounted again if project or eventsBasedBehaviorsList
|
||||
// has been changed. Avoid accessing to invalid objects that could
|
||||
@@ -278,16 +289,18 @@ export default class EventsBasedBehaviorsList extends React.Component<
|
||||
height={height}
|
||||
onAddNewItem={this._addNewEventsBasedBehavior}
|
||||
addNewItemLabel={<Trans>Add a new behavior</Trans>}
|
||||
selectedItem={selectedEventsBasedBehavior}
|
||||
getItemName={getEventsBasedBehaviorName}
|
||||
selectedItems={
|
||||
selectedEventsBasedBehavior
|
||||
? [selectedEventsBasedBehavior]
|
||||
: []
|
||||
}
|
||||
onItemSelected={onSelectEventsBasedBehavior}
|
||||
renamedItem={this.state.renamedEventsBasedBehavior}
|
||||
onRename={this._rename}
|
||||
onSortEnd={({ oldIndex, newIndex }) =>
|
||||
this._move(oldIndex, newIndex)
|
||||
}
|
||||
onMoveSelectionToItem={this._moveSelectionTo}
|
||||
buildMenuTemplate={this._renderEventsBasedBehaviorMenuTemplate}
|
||||
helperClass="sortable-helper"
|
||||
distance={20}
|
||||
reactDndType="GD_EVENTS_BASED_BEHAVIOR"
|
||||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
// @flow
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { t } from '@lingui/macro';
|
||||
import { I18n } from '@lingui/react';
|
||||
import * as React from 'react';
|
||||
import Background from '../UI/Background';
|
||||
import { Column, Spacer } from '../UI/Grid';
|
||||
import Text from '../UI/Text';
|
||||
import RaisedButton from '../UI/RaisedButton';
|
||||
import FlatButton from '../UI/FlatButton';
|
||||
|
||||
type Props = {|
|
||||
eventsFunctionsExtension: gdEventsFunctionsExtension,
|
||||
onEditFreeFunctions: () => void,
|
||||
onEditBehaviors: () => void,
|
||||
onEditExtensionOptions: () => void,
|
||||
|};
|
||||
|
||||
export default (props: Props) => {
|
||||
const eventsFunctionsCount = props.eventsFunctionsExtension.getEventsFunctionsCount();
|
||||
const eventsBasedBehaviorsCount = props.eventsFunctionsExtension
|
||||
.getEventsBasedBehaviors()
|
||||
.getCount();
|
||||
|
||||
return (
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<Background>
|
||||
<Column>
|
||||
<Text>
|
||||
<Trans>
|
||||
Extensions can provide functions (which can be actions,
|
||||
conditions or expressions) or new behaviors.
|
||||
</Trans>
|
||||
</Text>
|
||||
<RaisedButton
|
||||
label={i18n._(
|
||||
t`Edit functions (not attached to behaviors) (${eventsFunctionsCount})`
|
||||
)}
|
||||
onClick={props.onEditFreeFunctions}
|
||||
primary
|
||||
/>
|
||||
<Spacer />
|
||||
<RaisedButton
|
||||
label={i18n._(t`Edit behaviors (${eventsBasedBehaviorsCount})`)}
|
||||
onClick={props.onEditBehaviors}
|
||||
primary
|
||||
/>
|
||||
<Spacer />
|
||||
<FlatButton
|
||||
label={<Trans>Edit extension options</Trans>}
|
||||
onClick={props.onEditExtensionOptions}
|
||||
/>
|
||||
</Column>
|
||||
</Background>
|
||||
)}
|
||||
</I18n>
|
||||
);
|
||||
};
|
||||
@@ -6,7 +6,7 @@ import { type I18n as I18nType } from '@lingui/core';
|
||||
|
||||
import * as React from 'react';
|
||||
import EventsSheet from '../EventsSheet';
|
||||
import EditorMosaic, { MosaicWindow } from '../UI/EditorMosaic';
|
||||
import EditorMosaic from '../UI/EditorMosaic';
|
||||
import EmptyMessage from '../UI/EmptyMessage';
|
||||
import EventsFunctionConfigurationEditor from './EventsFunctionConfigurationEditor';
|
||||
import EventsFunctionsList from '../EventsFunctionsList';
|
||||
@@ -25,6 +25,11 @@ import { isBehaviorLifecycleFunction } from '../EventsFunctionsExtensionsLoader/
|
||||
import FlatButton from '../UI/FlatButton';
|
||||
import { Line } from '../UI/Grid';
|
||||
import Divider from '@material-ui/core/Divider';
|
||||
import { ResponsiveWindowMeasurer } from '../UI/Reponsive/ResponsiveWindowMeasurer';
|
||||
import EditorNavigator from '../UI/EditorMosaic/EditorNavigator';
|
||||
import ChooseEventsFunctionsExtensionEditor from './ChooseEventsFunctionsExtensionEditor';
|
||||
import Check from '@material-ui/icons/Check';
|
||||
import Tune from '@material-ui/icons/Tune';
|
||||
const gd = global.gd;
|
||||
|
||||
type Props = {|
|
||||
@@ -69,7 +74,9 @@ export default class EventsFunctionsExtensionEditor extends React.Component<
|
||||
onAddEventsFunctionCb: null,
|
||||
};
|
||||
editor: ?EventsSheet;
|
||||
_editors: ?EditorMosaic;
|
||||
_editorMosaic: ?EditorMosaic;
|
||||
// $FlowFixMe
|
||||
_editorNavigator: ?EditorNavigator;
|
||||
_globalObjectsContainer: ?gdObjectsContainer;
|
||||
_objectsContainer: ?gdObjectsContainer;
|
||||
|
||||
@@ -113,13 +120,13 @@ export default class EventsFunctionsExtensionEditor extends React.Component<
|
||||
);
|
||||
};
|
||||
|
||||
updateToolbar() {
|
||||
updateToolbar = () => {
|
||||
if (this.editor) {
|
||||
this.editor.updateToolbar();
|
||||
} else {
|
||||
this.props.setToolbar(<div />);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
selectEventsFunctionByName = (
|
||||
functionName: string,
|
||||
@@ -172,7 +179,21 @@ export default class EventsFunctionsExtensionEditor extends React.Component<
|
||||
selectedEventsFunction,
|
||||
selectedEventsBasedBehavior,
|
||||
},
|
||||
() => this.updateToolbar()
|
||||
() => {
|
||||
this.updateToolbar();
|
||||
|
||||
if (this._editorNavigator) {
|
||||
// Open the parameters of the function if it's a new, empty function.
|
||||
if (
|
||||
selectedEventsFunction &&
|
||||
!selectedEventsFunction.getEvents().getEventsCount()
|
||||
) {
|
||||
this._editorNavigator.openEditor('parameters');
|
||||
} else {
|
||||
this._editorNavigator.openEditor('events-sheet');
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -261,8 +282,10 @@ export default class EventsFunctionsExtensionEditor extends React.Component<
|
||||
() => {
|
||||
this.updateToolbar();
|
||||
if (selectedEventsBasedBehavior) {
|
||||
if (this._editors)
|
||||
this._editors.openEditor('behavior-functions-list');
|
||||
if (this._editorMosaic)
|
||||
this._editorMosaic.openEditor('behavior-functions-list', 'end', 75);
|
||||
if (this._editorNavigator)
|
||||
this._editorNavigator.openEditor('behavior-functions-list');
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -421,6 +444,16 @@ export default class EventsFunctionsExtensionEditor extends React.Component<
|
||||
);
|
||||
};
|
||||
|
||||
_openFreeFunctionsListEditor = () => {
|
||||
if (this._editorNavigator)
|
||||
this._editorNavigator.openEditor('free-functions-list');
|
||||
};
|
||||
|
||||
_openBehaviorsListEditor = () => {
|
||||
if (this._editorNavigator)
|
||||
this._editorNavigator.openEditor('behaviors-list');
|
||||
};
|
||||
|
||||
render() {
|
||||
const { project, eventsFunctionsExtension } = this.props;
|
||||
const {
|
||||
@@ -431,281 +464,351 @@ export default class EventsFunctionsExtensionEditor extends React.Component<
|
||||
editedEventsBasedBehavior,
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<React.Fragment>
|
||||
<EditorMosaic
|
||||
ref={editors => (this._editors = editors)}
|
||||
editors={{
|
||||
parameters: (
|
||||
<MosaicWindow
|
||||
title={<Trans>Function Configuration</Trans>}
|
||||
toolbarControls={[]}
|
||||
>
|
||||
<Background>
|
||||
{selectedEventsFunction &&
|
||||
this._globalObjectsContainer &&
|
||||
this._objectsContainer ? (
|
||||
<EventsFunctionConfigurationEditor
|
||||
project={project}
|
||||
eventsFunction={selectedEventsFunction}
|
||||
eventsBasedBehavior={selectedEventsBasedBehavior}
|
||||
globalObjectsContainer={this._globalObjectsContainer}
|
||||
objectsContainer={this._objectsContainer}
|
||||
helpPagePath={
|
||||
!!selectedEventsBasedBehavior
|
||||
? '/behaviors/events-based-behaviors'
|
||||
: '/events/functions'
|
||||
}
|
||||
onParametersOrGroupsUpdated={() => {
|
||||
this._loadEventsFunctionFrom(
|
||||
project,
|
||||
selectedEventsFunction
|
||||
);
|
||||
this.forceUpdate();
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<EmptyMessage>
|
||||
<Trans>
|
||||
Choose a function, or a function of a behavior, to
|
||||
set the parameters that it accepts.
|
||||
</Trans>
|
||||
</EmptyMessage>
|
||||
)}
|
||||
</Background>
|
||||
</MosaicWindow>
|
||||
),
|
||||
'events-sheet':
|
||||
selectedEventsFunction &&
|
||||
this._globalObjectsContainer &&
|
||||
this._objectsContainer ? (
|
||||
<EventsSheet
|
||||
key={selectedEventsFunction.ptr}
|
||||
ref={editor => (this.editor = editor)}
|
||||
project={project}
|
||||
scope={{
|
||||
layout: null,
|
||||
eventsFunctionsExtension,
|
||||
eventsBasedBehavior: selectedEventsBasedBehavior,
|
||||
eventsFunction: selectedEventsFunction,
|
||||
}}
|
||||
globalObjectsContainer={this._globalObjectsContainer}
|
||||
objectsContainer={this._objectsContainer}
|
||||
events={selectedEventsFunction.getEvents()}
|
||||
showPreviewButton={false}
|
||||
onPreview={options => {}}
|
||||
showNetworkPreviewButton={false}
|
||||
onOpenExternalEvents={() => {}}
|
||||
onOpenLayout={() => {}}
|
||||
resourceSources={this.props.resourceSources}
|
||||
onChooseResource={this.props.onChooseResource}
|
||||
resourceExternalEditors={
|
||||
this.props.resourceExternalEditors
|
||||
}
|
||||
openInstructionOrExpression={
|
||||
this.props.openInstructionOrExpression
|
||||
}
|
||||
setToolbar={this.props.setToolbar}
|
||||
onOpenDebugger={() => {}}
|
||||
onCreateEventsFunction={this.props.onCreateEventsFunction}
|
||||
onOpenSettings={this._editOptions} //TODO: Move this extra toolbar outside of EventsSheet toolbar
|
||||
/>
|
||||
) : (
|
||||
<Background>
|
||||
<EmptyMessage>
|
||||
<Trans>
|
||||
Choose a function, or a function of a behavior, to
|
||||
edit its events.
|
||||
</Trans>
|
||||
</EmptyMessage>
|
||||
</Background>
|
||||
),
|
||||
'free-functions-list': (
|
||||
<MosaicWindow
|
||||
title={<Trans>Functions</Trans>}
|
||||
toolbarControls={[]}
|
||||
selectedEventsFunction={selectedEventsFunction}
|
||||
>
|
||||
<EventsFunctionsList
|
||||
project={project}
|
||||
eventsFunctionsContainer={eventsFunctionsExtension}
|
||||
selectedEventsFunction={selectedEventsFunction}
|
||||
onSelectEventsFunction={selectedEventsFunction =>
|
||||
this._selectEventsFunction(selectedEventsFunction, null)
|
||||
}
|
||||
onDeleteEventsFunction={this._onDeleteEventsFunction}
|
||||
canRename={() => true}
|
||||
onRenameEventsFunction={this._makeRenameFreeEventsFunction(
|
||||
i18n
|
||||
)}
|
||||
onAddEventsFunction={this._onAddFreeEventsFunction}
|
||||
onEventsFunctionAdded={() => {}}
|
||||
renderHeader={() => (
|
||||
<React.Fragment>
|
||||
<Line justifyContent="center">
|
||||
<FlatButton
|
||||
label={<Trans>Edit extension options</Trans>}
|
||||
primary
|
||||
onClick={() => this._editOptions()}
|
||||
/>
|
||||
</Line>
|
||||
<Divider />
|
||||
</React.Fragment>
|
||||
)}
|
||||
/>
|
||||
</MosaicWindow>
|
||||
),
|
||||
'behavior-functions-list': selectedEventsBasedBehavior ? (
|
||||
<MosaicWindow
|
||||
title={<Trans>Behavior functions</Trans>}
|
||||
selectedEventsBasedBehavior={selectedEventsBasedBehavior}
|
||||
selectedEventsFunction={selectedEventsFunction}
|
||||
>
|
||||
<EventsFunctionsList
|
||||
project={project}
|
||||
eventsFunctionsContainer={selectedEventsBasedBehavior.getEventsFunctions()}
|
||||
selectedEventsFunction={selectedEventsFunction}
|
||||
onSelectEventsFunction={selectedEventsFunction =>
|
||||
this._selectEventsFunction(
|
||||
selectedEventsFunction,
|
||||
selectedEventsBasedBehavior
|
||||
)
|
||||
}
|
||||
onDeleteEventsFunction={this._onDeleteEventsFunction}
|
||||
canRename={(eventsFunction: gdEventsFunction) => {
|
||||
return !isBehaviorLifecycleFunction(
|
||||
eventsFunction.getName()
|
||||
);
|
||||
}}
|
||||
onRenameEventsFunction={(
|
||||
eventsFunction: gdEventsFunction,
|
||||
newName: string,
|
||||
done: boolean => void
|
||||
) =>
|
||||
this._makeRenameBehaviorEventsFunction(i18n)(
|
||||
selectedEventsBasedBehavior,
|
||||
eventsFunction,
|
||||
newName,
|
||||
done
|
||||
)
|
||||
}
|
||||
onAddEventsFunction={this._onAddBehaviorEventsFunction}
|
||||
onEventsFunctionAdded={eventsFunction =>
|
||||
this._onBehaviorEventsFunctionAdded(
|
||||
selectedEventsBasedBehavior,
|
||||
eventsFunction
|
||||
)
|
||||
}
|
||||
renderHeader={() => (
|
||||
<React.Fragment>
|
||||
<Line justifyContent="center">
|
||||
<FlatButton
|
||||
label={<Trans>Edit behavior properties</Trans>}
|
||||
primary
|
||||
onClick={() =>
|
||||
this._editBehavior(selectedEventsBasedBehavior)
|
||||
}
|
||||
/>
|
||||
</Line>
|
||||
<Divider />
|
||||
</React.Fragment>
|
||||
)}
|
||||
/>
|
||||
</MosaicWindow>
|
||||
) : (
|
||||
<Background>
|
||||
<EmptyMessage>
|
||||
<Trans>
|
||||
Select a behavior to display the functions inside this
|
||||
behavior.
|
||||
</Trans>
|
||||
</EmptyMessage>
|
||||
</Background>
|
||||
),
|
||||
|
||||
'behaviors-list': (
|
||||
<MosaicWindow
|
||||
title={<Trans>Behaviors</Trans>}
|
||||
toolbarControls={[]}
|
||||
selectedEventsBasedBehavior={selectedEventsBasedBehavior}
|
||||
>
|
||||
<EventsBasedBehaviorsList
|
||||
project={project}
|
||||
eventsBasedBehaviorsList={eventsFunctionsExtension.getEventsBasedBehaviors()}
|
||||
selectedEventsBasedBehavior={selectedEventsBasedBehavior}
|
||||
onSelectEventsBasedBehavior={
|
||||
this._selectEventsBasedBehavior
|
||||
}
|
||||
onDeleteEventsBasedBehavior={
|
||||
this._onDeleteEventsBasedBehavior
|
||||
}
|
||||
onRenameEventsBasedBehavior={this._makeRenameEventsBasedBehavior(
|
||||
i18n
|
||||
)}
|
||||
onEventsBasedBehaviorRenamed={
|
||||
this._onEventsBasedBehaviorRenamed
|
||||
}
|
||||
onEditProperties={this._editBehavior}
|
||||
/>
|
||||
</MosaicWindow>
|
||||
),
|
||||
}}
|
||||
initialNodes={{
|
||||
direction: 'row',
|
||||
first: {
|
||||
direction: 'column',
|
||||
first: 'free-functions-list',
|
||||
second: 'behaviors-list',
|
||||
splitPercentage: 50,
|
||||
},
|
||||
second: {
|
||||
direction: 'column',
|
||||
first: 'parameters',
|
||||
second: 'events-sheet',
|
||||
splitPercentage: 25,
|
||||
},
|
||||
splitPercentage: 25,
|
||||
}}
|
||||
/>
|
||||
{editOptionsDialogOpen && (
|
||||
<OptionsEditorDialog
|
||||
eventsFunctionsExtension={eventsFunctionsExtension}
|
||||
open
|
||||
onClose={() => this._editOptions(false)}
|
||||
const editors = {
|
||||
'choose-editor': {
|
||||
type: 'primary',
|
||||
noTitleBar: true,
|
||||
renderEditor: () => (
|
||||
<ChooseEventsFunctionsExtensionEditor
|
||||
eventsFunctionsExtension={eventsFunctionsExtension}
|
||||
onEditFreeFunctions={this._openFreeFunctionsListEditor}
|
||||
onEditBehaviors={this._openBehaviorsListEditor}
|
||||
onEditExtensionOptions={this._editOptions}
|
||||
/>
|
||||
),
|
||||
},
|
||||
parameters: {
|
||||
type: 'primary',
|
||||
title: <Trans>Function Configuration</Trans>,
|
||||
toolbarControls: [],
|
||||
renderEditor: () => (
|
||||
<Background>
|
||||
{selectedEventsFunction &&
|
||||
this._globalObjectsContainer &&
|
||||
this._objectsContainer ? (
|
||||
<EventsFunctionConfigurationEditor
|
||||
project={project}
|
||||
eventsFunction={selectedEventsFunction}
|
||||
eventsBasedBehavior={selectedEventsBasedBehavior}
|
||||
globalObjectsContainer={this._globalObjectsContainer}
|
||||
objectsContainer={this._objectsContainer}
|
||||
helpPagePath={
|
||||
!!selectedEventsBasedBehavior
|
||||
? '/behaviors/events-based-behaviors'
|
||||
: '/events/functions'
|
||||
}
|
||||
onParametersOrGroupsUpdated={() => {
|
||||
this._loadEventsFunctionFrom(project, selectedEventsFunction);
|
||||
this.forceUpdate();
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<EmptyMessage>
|
||||
<Trans>
|
||||
Choose a function, or a function of a behavior, to set the
|
||||
parameters that it accepts.
|
||||
</Trans>
|
||||
</EmptyMessage>
|
||||
)}
|
||||
</Background>
|
||||
),
|
||||
},
|
||||
'events-sheet': {
|
||||
type: 'primary',
|
||||
noTitleBar: true,
|
||||
renderEditor: () =>
|
||||
selectedEventsFunction &&
|
||||
this._globalObjectsContainer &&
|
||||
this._objectsContainer ? (
|
||||
<Background>
|
||||
<EventsSheet
|
||||
key={selectedEventsFunction.ptr}
|
||||
ref={editor => (this.editor = editor)}
|
||||
project={project}
|
||||
scope={{
|
||||
layout: null,
|
||||
eventsFunctionsExtension,
|
||||
eventsBasedBehavior: selectedEventsBasedBehavior,
|
||||
eventsFunction: selectedEventsFunction,
|
||||
}}
|
||||
globalObjectsContainer={this._globalObjectsContainer}
|
||||
objectsContainer={this._objectsContainer}
|
||||
events={selectedEventsFunction.getEvents()}
|
||||
showPreviewButton={false}
|
||||
onPreview={options => {}}
|
||||
showNetworkPreviewButton={false}
|
||||
onOpenExternalEvents={() => {}}
|
||||
onOpenLayout={() => {}}
|
||||
resourceSources={this.props.resourceSources}
|
||||
onChooseResource={this.props.onChooseResource}
|
||||
resourceExternalEditors={this.props.resourceExternalEditors}
|
||||
openInstructionOrExpression={
|
||||
this.props.openInstructionOrExpression
|
||||
}
|
||||
setToolbar={this.props.setToolbar}
|
||||
onOpenDebugger={() => {}}
|
||||
onCreateEventsFunction={this.props.onCreateEventsFunction}
|
||||
onOpenSettings={this._editOptions} //TODO: Move this extra toolbar outside of EventsSheet toolbar
|
||||
/>
|
||||
</Background>
|
||||
) : (
|
||||
<Background>
|
||||
<EmptyMessage>
|
||||
<Trans>
|
||||
Choose a function, or a function of a behavior, to edit its
|
||||
events.
|
||||
</Trans>
|
||||
</EmptyMessage>
|
||||
</Background>
|
||||
),
|
||||
},
|
||||
'free-functions-list': {
|
||||
type: 'primary',
|
||||
title: <Trans>Functions</Trans>,
|
||||
toolbarControls: [],
|
||||
renderEditor: () => (
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<EventsFunctionsList
|
||||
project={project}
|
||||
eventsFunctionsContainer={eventsFunctionsExtension}
|
||||
selectedEventsFunction={selectedEventsFunction}
|
||||
onSelectEventsFunction={selectedEventsFunction =>
|
||||
this._selectEventsFunction(selectedEventsFunction, null)
|
||||
}
|
||||
onDeleteEventsFunction={this._onDeleteEventsFunction}
|
||||
canRename={() => true}
|
||||
onRenameEventsFunction={this._makeRenameFreeEventsFunction(
|
||||
i18n
|
||||
)}
|
||||
onAddEventsFunction={this._onAddFreeEventsFunction}
|
||||
onEventsFunctionAdded={() => {}}
|
||||
renderHeader={() => (
|
||||
<React.Fragment>
|
||||
<Line justifyContent="center">
|
||||
<FlatButton
|
||||
label={<Trans>Edit extension options</Trans>}
|
||||
primary
|
||||
onClick={() => this._editOptions()}
|
||||
/>
|
||||
</Line>
|
||||
<Divider />
|
||||
</React.Fragment>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{behaviorMethodSelectorDialogOpen &&
|
||||
selectedEventsBasedBehavior && (
|
||||
<BehaviorMethodSelectorDialog
|
||||
eventsBasedBehavior={selectedEventsBasedBehavior}
|
||||
onCancel={() =>
|
||||
this._onCloseBehaviorMethodSelectorDialog(false, null)
|
||||
</I18n>
|
||||
),
|
||||
},
|
||||
'behavior-functions-list': {
|
||||
type: 'primary',
|
||||
title: <Trans>Behavior functions</Trans>,
|
||||
renderEditor: () =>
|
||||
selectedEventsBasedBehavior ? (
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<EventsFunctionsList
|
||||
project={project}
|
||||
eventsFunctionsContainer={selectedEventsBasedBehavior.getEventsFunctions()}
|
||||
selectedEventsFunction={selectedEventsFunction}
|
||||
onSelectEventsFunction={selectedEventsFunction =>
|
||||
this._selectEventsFunction(
|
||||
selectedEventsFunction,
|
||||
selectedEventsBasedBehavior
|
||||
)
|
||||
}
|
||||
onChoose={name =>
|
||||
this._onCloseBehaviorMethodSelectorDialog(true, name)
|
||||
onDeleteEventsFunction={this._onDeleteEventsFunction}
|
||||
canRename={(eventsFunction: gdEventsFunction) => {
|
||||
return !isBehaviorLifecycleFunction(
|
||||
eventsFunction.getName()
|
||||
);
|
||||
}}
|
||||
onRenameEventsFunction={(
|
||||
eventsFunction: gdEventsFunction,
|
||||
newName: string,
|
||||
done: boolean => void
|
||||
) =>
|
||||
this._makeRenameBehaviorEventsFunction(i18n)(
|
||||
selectedEventsBasedBehavior,
|
||||
eventsFunction,
|
||||
newName,
|
||||
done
|
||||
)
|
||||
}
|
||||
onAddEventsFunction={this._onAddBehaviorEventsFunction}
|
||||
onEventsFunctionAdded={eventsFunction =>
|
||||
this._onBehaviorEventsFunctionAdded(
|
||||
selectedEventsBasedBehavior,
|
||||
eventsFunction
|
||||
)
|
||||
}
|
||||
renderHeader={() => (
|
||||
<React.Fragment>
|
||||
<Line justifyContent="center">
|
||||
<FlatButton
|
||||
label={<Trans>Edit behavior properties</Trans>}
|
||||
primary
|
||||
onClick={() =>
|
||||
this._editBehavior(selectedEventsBasedBehavior)
|
||||
}
|
||||
/>
|
||||
</Line>
|
||||
<Divider />
|
||||
</React.Fragment>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{editedEventsBasedBehavior && (
|
||||
<EventsBasedBehaviorEditorDialog
|
||||
</I18n>
|
||||
) : (
|
||||
<Background>
|
||||
<EmptyMessage>
|
||||
<Trans>
|
||||
Select a behavior to display the functions inside this
|
||||
behavior.
|
||||
</Trans>
|
||||
</EmptyMessage>
|
||||
</Background>
|
||||
),
|
||||
},
|
||||
|
||||
'behaviors-list': {
|
||||
type: 'secondary',
|
||||
title: <Trans>Behaviors</Trans>,
|
||||
toolbarControls: [],
|
||||
renderEditor: () => (
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<EventsBasedBehaviorsList
|
||||
project={project}
|
||||
eventsFunctionsExtension={eventsFunctionsExtension}
|
||||
eventsBasedBehavior={editedEventsBasedBehavior}
|
||||
onApply={() => this._editBehavior(null)}
|
||||
onRenameProperty={(oldName, newName) =>
|
||||
this._onBehaviorPropertyRenamed(
|
||||
editedEventsBasedBehavior,
|
||||
oldName,
|
||||
newName
|
||||
)
|
||||
eventsBasedBehaviorsList={eventsFunctionsExtension.getEventsBasedBehaviors()}
|
||||
selectedEventsBasedBehavior={selectedEventsBasedBehavior}
|
||||
onSelectEventsBasedBehavior={this._selectEventsBasedBehavior}
|
||||
onDeleteEventsBasedBehavior={this._onDeleteEventsBasedBehavior}
|
||||
onRenameEventsBasedBehavior={this._makeRenameEventsBasedBehavior(
|
||||
i18n
|
||||
)}
|
||||
onEventsBasedBehaviorRenamed={
|
||||
this._onEventsBasedBehaviorRenamed
|
||||
}
|
||||
onEditProperties={this._editBehavior}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
</I18n>
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<ResponsiveWindowMeasurer>
|
||||
{windowWidth =>
|
||||
windowWidth === 'small' ? (
|
||||
<EditorNavigator
|
||||
ref={editorNavigator =>
|
||||
(this._editorNavigator = editorNavigator)
|
||||
}
|
||||
editors={editors}
|
||||
initialEditorName={'choose-editor'}
|
||||
transitions={{
|
||||
'behaviors-list': {
|
||||
previousEditor: 'choose-editor',
|
||||
},
|
||||
'behavior-functions-list': {
|
||||
previousEditor: 'behaviors-list',
|
||||
},
|
||||
'free-functions-list': {
|
||||
previousEditor: 'choose-editor',
|
||||
},
|
||||
'events-sheet': {
|
||||
nextIcon: <Tune />,
|
||||
nextLabel: <Trans>Parameters</Trans>,
|
||||
nextEditor: 'parameters',
|
||||
previousEditor: () => {
|
||||
if (selectedEventsBasedBehavior)
|
||||
return 'behavior-functions-list';
|
||||
return 'free-functions-list';
|
||||
},
|
||||
},
|
||||
parameters: {
|
||||
nextIcon: <Check />,
|
||||
nextLabel: <Trans>Validate these parameters</Trans>,
|
||||
nextEditor: 'events-sheet',
|
||||
},
|
||||
}}
|
||||
onEditorChanged={editorName => {
|
||||
this.updateToolbar();
|
||||
|
||||
if (editorName === 'behaviors-list') {
|
||||
this._selectEventsBasedBehavior(null);
|
||||
} else if (
|
||||
editorName === 'free-functions-list' ||
|
||||
editorName === 'behavior-functions-list'
|
||||
) {
|
||||
this._selectEventsFunction(
|
||||
null,
|
||||
selectedEventsBasedBehavior
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<EditorMosaic
|
||||
ref={editorMosaic => (this._editorMosaic = editorMosaic)}
|
||||
editors={editors}
|
||||
initialNodes={{
|
||||
direction: 'row',
|
||||
first: {
|
||||
direction: 'column',
|
||||
first: 'free-functions-list',
|
||||
second: 'behaviors-list',
|
||||
splitPercentage: 50,
|
||||
},
|
||||
second: {
|
||||
direction: 'column',
|
||||
first: 'parameters',
|
||||
second: 'events-sheet',
|
||||
splitPercentage: 25,
|
||||
},
|
||||
splitPercentage: 25,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</ResponsiveWindowMeasurer>
|
||||
{editOptionsDialogOpen && (
|
||||
<OptionsEditorDialog
|
||||
eventsFunctionsExtension={eventsFunctionsExtension}
|
||||
open
|
||||
onClose={() => this._editOptions(false)}
|
||||
/>
|
||||
)}
|
||||
</I18n>
|
||||
{behaviorMethodSelectorDialogOpen && selectedEventsBasedBehavior && (
|
||||
<BehaviorMethodSelectorDialog
|
||||
eventsBasedBehavior={selectedEventsBasedBehavior}
|
||||
onCancel={() =>
|
||||
this._onCloseBehaviorMethodSelectorDialog(false, null)
|
||||
}
|
||||
onChoose={name =>
|
||||
this._onCloseBehaviorMethodSelectorDialog(true, name)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{editedEventsBasedBehavior && (
|
||||
<EventsBasedBehaviorEditorDialog
|
||||
project={project}
|
||||
eventsFunctionsExtension={eventsFunctionsExtension}
|
||||
eventsBasedBehavior={editedEventsBasedBehavior}
|
||||
onApply={() => this._editBehavior(null)}
|
||||
onRenameProperty={(oldName, newName) =>
|
||||
this._onBehaviorPropertyRenamed(
|
||||
editedEventsBasedBehavior,
|
||||
oldName,
|
||||
newName
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,9 @@ const styles = {
|
||||
},
|
||||
};
|
||||
|
||||
const getEventsFunctionName = (eventsFunction: gdEventsFunction) =>
|
||||
eventsFunction.getName();
|
||||
|
||||
type State = {|
|
||||
renamedEventsFunction: ?gdEventsFunction,
|
||||
searchText: string,
|
||||
@@ -97,7 +100,9 @@ export default class EventsFunctionsList extends React.Component<Props, State> {
|
||||
{
|
||||
renamedEventsFunction: eventsFunction,
|
||||
},
|
||||
() => this.sortableList.getWrappedInstance().forceUpdateGrid()
|
||||
() => {
|
||||
if (this.sortableList) this.sortableList.forceUpdateGrid();
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -121,16 +126,25 @@ export default class EventsFunctionsList extends React.Component<Props, State> {
|
||||
});
|
||||
};
|
||||
|
||||
_move = (oldIndex: number, newIndex: number) => {
|
||||
const { eventsFunctionsContainer } = this.props;
|
||||
eventsFunctionsContainer.moveEventsFunction(oldIndex, newIndex);
|
||||
_moveSelectionTo = (destinationEventsFunction: gdEventsFunction) => {
|
||||
const { eventsFunctionsContainer, selectedEventsFunction } = this.props;
|
||||
if (!selectedEventsFunction) return;
|
||||
|
||||
eventsFunctionsContainer.moveEventsFunction(
|
||||
eventsFunctionsContainer.getEventsFunctionPosition(
|
||||
selectedEventsFunction
|
||||
),
|
||||
eventsFunctionsContainer.getEventsFunctionPosition(
|
||||
destinationEventsFunction
|
||||
)
|
||||
);
|
||||
|
||||
this.forceUpdateList();
|
||||
};
|
||||
|
||||
forceUpdateList = () => {
|
||||
this.forceUpdate();
|
||||
this.sortableList.getWrappedInstance().forceUpdateGrid();
|
||||
if (this.sortableList) this.sortableList.forceUpdateGrid();
|
||||
};
|
||||
|
||||
_copyEventsFunction = (eventsFunction: gdEventsFunction) => {
|
||||
@@ -243,15 +257,10 @@ export default class EventsFunctionsList extends React.Component<Props, State> {
|
||||
} = this.props;
|
||||
const { searchText } = this.state;
|
||||
|
||||
const list = [
|
||||
...filterEventFunctionsList(
|
||||
enumerateEventsFunctions(eventsFunctionsContainer),
|
||||
searchText
|
||||
),
|
||||
{
|
||||
key: 'add-item-row',
|
||||
},
|
||||
];
|
||||
const list = filterEventFunctionsList(
|
||||
enumerateEventsFunctions(eventsFunctionsContainer),
|
||||
searchText
|
||||
);
|
||||
|
||||
// Force List component to be mounted again if project or objectsContainer
|
||||
// has been changed. Avoid accessing to invalid objects that could
|
||||
@@ -272,16 +281,16 @@ export default class EventsFunctionsList extends React.Component<Props, State> {
|
||||
height={height}
|
||||
onAddNewItem={this._addNewEventsFunction}
|
||||
addNewItemLabel={<Trans>Add a new function</Trans>}
|
||||
selectedItem={selectedEventsFunction}
|
||||
getItemName={getEventsFunctionName}
|
||||
selectedItems={
|
||||
selectedEventsFunction ? [selectedEventsFunction] : []
|
||||
}
|
||||
onItemSelected={onSelectEventsFunction}
|
||||
renamedItem={this.state.renamedEventsFunction}
|
||||
onRename={this._rename}
|
||||
onSortEnd={({ oldIndex, newIndex }) =>
|
||||
this._move(oldIndex, newIndex)
|
||||
}
|
||||
onMoveSelectionToItem={this._moveSelectionTo}
|
||||
buildMenuTemplate={this._renderEventsFunctionMenuTemplate}
|
||||
helperClass="sortable-helper"
|
||||
distance={20}
|
||||
reactDndType="GD_EVENTS_FUNCTION"
|
||||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
|
||||
@@ -10,6 +10,7 @@ export const executableEventContainer = 'executable-event-container';
|
||||
export const actionsContainer = 'actions-container';
|
||||
export const conditionsContainer = 'conditions-container';
|
||||
export const subInstructionsContainer = 'sub-instructions-container';
|
||||
export const smallWidthContainer = 'small-width-container';
|
||||
export const instructionParameter = 'instruction-parameter';
|
||||
export const instructionInvalidParameter = 'instruction-invalid-parameter';
|
||||
export const disabledText = 'disabled-text';
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import { type WidthType } from '../../UI/Reponsive/ResponsiveWindowMeasurer';
|
||||
import {
|
||||
actionsContainer,
|
||||
conditionsContainer,
|
||||
smallWidthContainer,
|
||||
} from './ClassNames';
|
||||
|
||||
type Props = {|
|
||||
renderConditionsList: ({ style: Object, className: string }) => React.Node,
|
||||
renderActionsList: ({ className: string }) => React.Node,
|
||||
windowWidth: WidthType,
|
||||
className?: string,
|
||||
leftIndentWidth: number,
|
||||
|};
|
||||
|
||||
const styles = {
|
||||
oneColumnContainer: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
},
|
||||
twoColumnsContainer: {
|
||||
display: 'flex',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Display the lists of instructions (conditions and actions),
|
||||
* next to each other on a big screen (with proper sizing for the conditions column),
|
||||
* and stacked on a small one.
|
||||
*/
|
||||
const ConditionsActionsColumns = (props: Props) => {
|
||||
if (props.windowWidth === 'small') {
|
||||
return (
|
||||
<div style={styles.oneColumnContainer} className={props.className}>
|
||||
{props.renderConditionsList({
|
||||
style: {},
|
||||
className: `${conditionsContainer} ${smallWidthContainer}`,
|
||||
})}
|
||||
{props.renderActionsList({
|
||||
style: {},
|
||||
className: `${actionsContainer} ${smallWidthContainer}`,
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={styles.twoColumnsContainer} className={props.className}>
|
||||
{props.renderConditionsList({
|
||||
style: {
|
||||
width: `calc(35vw - ${props.leftIndentWidth}px)`,
|
||||
},
|
||||
className: conditionsContainer,
|
||||
})}
|
||||
{props.renderActionsList({ className: actionsContainer })}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConditionsActionsColumns;
|
||||
@@ -2,6 +2,9 @@
|
||||
import * as React from 'react';
|
||||
import { dropIndicator, cantDropIndicator } from './ClassNames';
|
||||
|
||||
/**
|
||||
* A Drop indicator line for the events sheet
|
||||
*/
|
||||
export default function DropIndicator({ canDrop }: {| canDrop: boolean |}) {
|
||||
return <div className={canDrop ? dropIndicator : cantDropIndicator} />;
|
||||
}
|
||||
|
||||
@@ -17,19 +17,12 @@ import {
|
||||
type InstructionContext,
|
||||
} from '../SelectionHandler';
|
||||
import InstructionsList from './InstructionsList';
|
||||
import {
|
||||
DragSource,
|
||||
type DragSourceMonitor,
|
||||
type DragSourceConnector,
|
||||
type ConnectDragSource,
|
||||
DropTarget,
|
||||
type DropTargetMonitor,
|
||||
type DropTargetConnector,
|
||||
type ConnectDropTarget,
|
||||
} from 'react-dnd';
|
||||
import DropIndicator from './DropIndicator';
|
||||
import ParameterRenderingService from '../ParameterRenderingService';
|
||||
import InvalidParameterValue from './InvalidParameterValue';
|
||||
import { makeDragSourceAndDropTarget } from '../../UI/DragAndDrop/DragSourceAndDropTarget';
|
||||
import { type ScreenType } from '../../UI/Reponsive/ScreenTypeMeasurer';
|
||||
import { type WidthType } from '../../UI/Reponsive/ResponsiveWindowMeasurer';
|
||||
const gd = global.gd;
|
||||
const instrFormatter = gd.InstructionSentenceFormatter.get();
|
||||
instrFormatter.loadTypesFormattingFromConfig();
|
||||
@@ -43,15 +36,11 @@ const styles = {
|
||||
},
|
||||
};
|
||||
|
||||
type DragSourceProps = {|
|
||||
connectDragSource: ConnectDragSource,
|
||||
|};
|
||||
export const reactDndInstructionType = 'GD_DRAGGED_INSTRUCTION';
|
||||
|
||||
type DropTargetProps = {|
|
||||
connectDropTarget: ConnectDropTarget,
|
||||
isOver: boolean,
|
||||
canDrop: boolean,
|
||||
|};
|
||||
const DragSourceAndDropTarget = makeDragSourceAndDropTarget<{
|
||||
isCondition: boolean,
|
||||
}>(reactDndInstructionType);
|
||||
|
||||
type Props = {|
|
||||
instruction: gdInstruction,
|
||||
@@ -63,9 +52,6 @@ type Props = {|
|
||||
onContextMenu: (x: number, y: number) => void,
|
||||
onMoveToInstruction: () => void,
|
||||
|
||||
...DragSourceProps,
|
||||
...DropTargetProps,
|
||||
|
||||
// For potential sub-instructions list:
|
||||
selection: Object,
|
||||
onAddNewSubInstruction: InstructionsListContext => void,
|
||||
@@ -89,9 +75,12 @@ type Props = {|
|
||||
) => void,
|
||||
onParameterClick: (event: any, parameterIndex: number) => void,
|
||||
renderObjectThumbnail: string => React.Node,
|
||||
|
||||
screenType: ScreenType,
|
||||
windowWidth: WidthType,
|
||||
|};
|
||||
|
||||
class Instruction extends React.Component<Props, *> {
|
||||
export default class Instruction extends React.Component<Props> {
|
||||
/**
|
||||
* Render the different parts of the text of the instruction.
|
||||
* Parameter can have formatting, be hovered and clicked. The rest
|
||||
@@ -130,9 +119,15 @@ class Instruction extends React.Component<Props, *> {
|
||||
[instructionParameter]: true,
|
||||
[parameterType]: true,
|
||||
})}
|
||||
onClick={domEvent =>
|
||||
this.props.onParameterClick(domEvent, parameterIndex)
|
||||
}
|
||||
onClick={domEvent => {
|
||||
this.props.onParameterClick(domEvent, parameterIndex);
|
||||
|
||||
// On touchscreen, don't propagate the click to the instruction div,
|
||||
// as it's listening for taps to simulate double "clicks".
|
||||
if (this.props.screenType === 'touch') {
|
||||
domEvent.stopPropagation();
|
||||
}
|
||||
}}
|
||||
onKeyPress={event => {
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
this.props.onParameterClick(event, parameterIndex);
|
||||
@@ -156,12 +151,11 @@ class Instruction extends React.Component<Props, *> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { instruction, isCondition } = this.props;
|
||||
const {
|
||||
connectDragSource,
|
||||
connectDropTarget,
|
||||
isOver,
|
||||
canDrop,
|
||||
instruction,
|
||||
isCondition,
|
||||
onClick,
|
||||
onMoveToInstruction,
|
||||
} = this.props;
|
||||
|
||||
//TODO: Metadata could be cached for performance boost.
|
||||
@@ -175,158 +169,132 @@ class Instruction extends React.Component<Props, *> {
|
||||
instruction.getType()
|
||||
);
|
||||
|
||||
const instructionDiv = connectDropTarget(
|
||||
// $FlowFixMe
|
||||
connectDragSource(
|
||||
<div
|
||||
style={styles.container}
|
||||
className={classNames({
|
||||
[selectableArea]: true,
|
||||
[selectedArea]: this.props.selected,
|
||||
})}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
this.props.onClick();
|
||||
}}
|
||||
onDoubleClick={e => {
|
||||
e.stopPropagation();
|
||||
this.props.onDoubleClick();
|
||||
}}
|
||||
onContextMenu={e => {
|
||||
e.stopPropagation();
|
||||
this.props.onContextMenu(e.clientX, e.clientY);
|
||||
}}
|
||||
onKeyPress={event => {
|
||||
if (event.key === 'Enter') {
|
||||
this.props.onDoubleClick();
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
} else if (event.key === ' ') {
|
||||
this.props.onClick();
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
}}
|
||||
tabIndex={0}
|
||||
>
|
||||
{instruction.isInverted() && (
|
||||
<img
|
||||
className={classNames({
|
||||
[icon]: true,
|
||||
})}
|
||||
src="res/contraire.png"
|
||||
alt="Condition is negated"
|
||||
/>
|
||||
)}
|
||||
<img
|
||||
className={classNames({
|
||||
[icon]: true,
|
||||
})}
|
||||
src={metadata.getSmallIconFilename()}
|
||||
alt=""
|
||||
/>
|
||||
{this._renderInstructionText(metadata)}
|
||||
{metadata.canHaveSubInstructions() && (
|
||||
<InstructionsList
|
||||
style={
|
||||
{} /* TODO: Use a new object to force update - somehow updates are not always propagated otherwise */
|
||||
}
|
||||
extraClassName={subInstructionsContainer}
|
||||
instrsList={instruction.getSubInstructions()}
|
||||
areConditions={this.props.isCondition}
|
||||
selection={this.props.selection}
|
||||
onAddNewInstruction={this.props.onAddNewSubInstruction}
|
||||
onPasteInstructions={this.props.onPasteSubInstructions}
|
||||
onMoveToInstruction={this.props.onMoveToSubInstruction}
|
||||
onMoveToInstructionsList={this.props.onMoveToSubInstructionsList}
|
||||
onInstructionClick={this.props.onSubInstructionClick}
|
||||
onInstructionDoubleClick={this.props.onSubInstructionDoubleClick}
|
||||
onInstructionContextMenu={this.props.onSubInstructionContextMenu}
|
||||
onInstructionsListContextMenu={
|
||||
this.props.onSubInstructionsListContextMenu
|
||||
}
|
||||
onParameterClick={this.props.onSubParameterClick}
|
||||
addButtonLabel={<Trans>Add a sub-condition</Trans>}
|
||||
disabled={this.props.disabled}
|
||||
renderObjectThumbnail={this.props.renderObjectThumbnail}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
return (
|
||||
<DragSourceAndDropTarget
|
||||
beginDrag={() => {
|
||||
onClick(); // Select the dragged instruction
|
||||
|
||||
return isOver ? (
|
||||
<React.Fragment>
|
||||
<DropIndicator canDrop={canDrop} />
|
||||
{instructionDiv}
|
||||
</React.Fragment>
|
||||
) : (
|
||||
instructionDiv || null
|
||||
// No need to save here what is being dragged,
|
||||
// as its the entire selection that is considered to be dragged.
|
||||
return {
|
||||
isCondition,
|
||||
};
|
||||
}}
|
||||
canDrop={draggedItem => draggedItem.isCondition === isCondition}
|
||||
drop={() => {
|
||||
onMoveToInstruction();
|
||||
}}
|
||||
>
|
||||
{({ connectDragSource, connectDropTarget, isOver, canDrop }) => {
|
||||
// The instruction itself can be dragged and is a target for
|
||||
// another instruction to be dropped. It's IMPORTANT NOT to have
|
||||
// the subinstructions list inside the connectDropTarget/connectDragSource
|
||||
// as otherwise this can confuse react-dnd ("Expected to find a valid target")
|
||||
// (surely due to components re-mounting/rerendering ?).
|
||||
const instructionElement = connectDropTarget(
|
||||
connectDragSource(
|
||||
<div
|
||||
style={styles.container}
|
||||
className={classNames({
|
||||
[selectableArea]: true,
|
||||
[selectedArea]: this.props.selected,
|
||||
})}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
|
||||
if (
|
||||
this.props.screenType === 'touch' &&
|
||||
this.props.selected
|
||||
) {
|
||||
// On touch screens, tapping again a selected instruction should edit it.
|
||||
this.props.onDoubleClick();
|
||||
} else {
|
||||
this.props.onClick();
|
||||
}
|
||||
}}
|
||||
onDoubleClick={e => {
|
||||
e.stopPropagation();
|
||||
this.props.onDoubleClick();
|
||||
}}
|
||||
onContextMenu={e => {
|
||||
e.stopPropagation();
|
||||
this.props.onContextMenu(e.clientX, e.clientY);
|
||||
}}
|
||||
onKeyPress={event => {
|
||||
if (event.key === 'Enter') {
|
||||
this.props.onDoubleClick();
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
} else if (event.key === ' ') {
|
||||
this.props.onClick();
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
}}
|
||||
tabIndex={0}
|
||||
>
|
||||
{instruction.isInverted() && (
|
||||
<img
|
||||
className={classNames({
|
||||
[icon]: true,
|
||||
})}
|
||||
src="res/contraire.png"
|
||||
alt="Condition is negated"
|
||||
/>
|
||||
)}
|
||||
<img
|
||||
className={classNames({
|
||||
[icon]: true,
|
||||
})}
|
||||
src={metadata.getSmallIconFilename()}
|
||||
alt=""
|
||||
/>
|
||||
{this._renderInstructionText(metadata)}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{isOver && <DropIndicator canDrop={canDrop} />}
|
||||
{instructionElement}
|
||||
{metadata.canHaveSubInstructions() && (
|
||||
<InstructionsList
|
||||
style={
|
||||
{} /* TODO: Use a new object to force update - somehow updates are not always propagated otherwise */
|
||||
}
|
||||
className={subInstructionsContainer}
|
||||
instrsList={instruction.getSubInstructions()}
|
||||
areConditions={this.props.isCondition}
|
||||
selection={this.props.selection}
|
||||
onAddNewInstruction={this.props.onAddNewSubInstruction}
|
||||
onPasteInstructions={this.props.onPasteSubInstructions}
|
||||
onMoveToInstruction={this.props.onMoveToSubInstruction}
|
||||
onMoveToInstructionsList={
|
||||
this.props.onMoveToSubInstructionsList
|
||||
}
|
||||
onInstructionClick={this.props.onSubInstructionClick}
|
||||
onInstructionDoubleClick={
|
||||
this.props.onSubInstructionDoubleClick
|
||||
}
|
||||
onInstructionContextMenu={
|
||||
this.props.onSubInstructionContextMenu
|
||||
}
|
||||
onInstructionsListContextMenu={
|
||||
this.props.onSubInstructionsListContextMenu
|
||||
}
|
||||
onParameterClick={this.props.onSubParameterClick}
|
||||
addButtonLabel={<Trans>Add a sub-condition</Trans>}
|
||||
disabled={this.props.disabled}
|
||||
renderObjectThumbnail={this.props.renderObjectThumbnail}
|
||||
screenType={this.props.screenType}
|
||||
windowWidth={this.props.windowWidth}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
}}
|
||||
</DragSourceAndDropTarget>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Drag'n'drop support:
|
||||
|
||||
export const reactDndInstructionType = 'GD_DRAGGED_INSTRUCTION';
|
||||
|
||||
type InstructionSourceProps = {
|
||||
onClick: () => void,
|
||||
isCondition: boolean,
|
||||
};
|
||||
|
||||
const instructionSource = {
|
||||
beginDrag(props: InstructionSourceProps) {
|
||||
props.onClick(); // Select the dragged instruction
|
||||
return {
|
||||
// No need to save here what is being dragged,
|
||||
// as its the entire selection that is considered to be dragged.
|
||||
isCondition: props.isCondition,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
function sourceCollect(
|
||||
connect: DragSourceConnector,
|
||||
monitor: DragSourceMonitor
|
||||
): DragSourceProps {
|
||||
return {
|
||||
connectDragSource: connect.dragSource(),
|
||||
};
|
||||
}
|
||||
|
||||
const instructionTarget = {
|
||||
canDrop(props: Props, monitor: DropTargetMonitor) {
|
||||
return (
|
||||
monitor.getItem() && monitor.getItem().isCondition === props.isCondition
|
||||
);
|
||||
},
|
||||
drop(props: Props, monitor: DropTargetMonitor) {
|
||||
if (monitor.didDrop()) {
|
||||
return; // Drop already handled by a subinstruction
|
||||
}
|
||||
props.onMoveToInstruction();
|
||||
},
|
||||
};
|
||||
|
||||
function targetCollect(
|
||||
connect: DropTargetConnector,
|
||||
monitor: DropTargetMonitor
|
||||
): DropTargetProps {
|
||||
return {
|
||||
connectDropTarget: connect.dropTarget(),
|
||||
isOver: monitor.isOver({ shallow: true }),
|
||||
canDrop: monitor.canDrop(),
|
||||
};
|
||||
}
|
||||
|
||||
// $FlowFixMe - Typing of DragSource/DropTarget is a pain to get correctly
|
||||
export default DragSource(
|
||||
reactDndInstructionType,
|
||||
instructionSource,
|
||||
sourceCollect
|
||||
)(
|
||||
DropTarget(reactDndInstructionType, instructionTarget, targetCollect)(
|
||||
Instruction
|
||||
)
|
||||
);
|
||||
|
||||
@@ -8,16 +8,12 @@ import {
|
||||
type InstructionContext,
|
||||
type ParameterContext,
|
||||
} from '../SelectionHandler';
|
||||
import { actionsContainer, conditionsContainer } from './ClassNames';
|
||||
import {
|
||||
DropTarget,
|
||||
type DropTargetMonitor,
|
||||
type DropTargetConnector,
|
||||
type ConnectDropTarget,
|
||||
} from 'react-dnd';
|
||||
import DropIndicator from './DropIndicator';
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { hasClipboardConditions, hasClipboardActions } from '../ClipboardKind';
|
||||
import { makeDropTarget } from '../../UI/DragAndDrop/DropTarget';
|
||||
import { type ScreenType } from '../../UI/Reponsive/ScreenTypeMeasurer';
|
||||
import { type WidthType } from '../../UI/Reponsive/ResponsiveWindowMeasurer';
|
||||
|
||||
const styles = {
|
||||
addButton: {
|
||||
@@ -25,12 +21,6 @@ const styles = {
|
||||
},
|
||||
};
|
||||
|
||||
type DropTargetProps = {|
|
||||
connectDropTarget: ConnectDropTarget,
|
||||
isOver: boolean,
|
||||
canDrop: boolean,
|
||||
|};
|
||||
|
||||
type Props = {
|
||||
instrsList: gdInstructionsList,
|
||||
areConditions: boolean,
|
||||
@@ -51,18 +41,24 @@ type Props = {
|
||||
onParameterClick: ParameterContext => void,
|
||||
selection: any,
|
||||
addButtonLabel?: React.Node,
|
||||
extraClassName?: string,
|
||||
className?: string,
|
||||
style?: Object,
|
||||
disabled: boolean,
|
||||
renderObjectThumbnail: string => React.Node,
|
||||
...DropTargetProps,
|
||||
|
||||
screenType: ScreenType,
|
||||
windowWidth: WidthType,
|
||||
};
|
||||
|
||||
type State = {|
|
||||
canPaste: boolean,
|
||||
|};
|
||||
|
||||
class InstructionsList extends React.Component<Props, State> {
|
||||
const DropTarget = makeDropTarget<{
|
||||
isCondition: boolean,
|
||||
}>(reactDndInstructionType);
|
||||
|
||||
export default class InstructionsList extends React.Component<Props, State> {
|
||||
state = { canPaste: false };
|
||||
|
||||
onAddNewInstruction = () => {
|
||||
@@ -85,7 +81,7 @@ class InstructionsList extends React.Component<Props, State> {
|
||||
const {
|
||||
addButtonLabel,
|
||||
areConditions,
|
||||
extraClassName,
|
||||
className,
|
||||
instrsList,
|
||||
onAddNewInstruction,
|
||||
onPasteInstructions,
|
||||
@@ -101,8 +97,6 @@ class InstructionsList extends React.Component<Props, State> {
|
||||
disabled,
|
||||
} = this.props;
|
||||
|
||||
const { connectDropTarget, isOver, canDrop } = this.props;
|
||||
|
||||
const instructions = mapFor(0, instrsList.size(), i => {
|
||||
const instruction = instrsList.get(i);
|
||||
const instructionContext = {
|
||||
@@ -113,7 +107,6 @@ class InstructionsList extends React.Component<Props, State> {
|
||||
};
|
||||
|
||||
return (
|
||||
// $FlowFixMe - Flow don't see that DropTarget hoc is being used in instructions?
|
||||
<Instruction
|
||||
instruction={instruction}
|
||||
isCondition={areConditions}
|
||||
@@ -147,6 +140,8 @@ class InstructionsList extends React.Component<Props, State> {
|
||||
onSubParameterClick={onParameterClick}
|
||||
disabled={disabled}
|
||||
renderObjectThumbnail={this.props.renderObjectThumbnail}
|
||||
screenType={this.props.screenType}
|
||||
windowWidth={this.props.windowWidth}
|
||||
/>
|
||||
);
|
||||
});
|
||||
@@ -160,96 +155,67 @@ class InstructionsList extends React.Component<Props, State> {
|
||||
) : (
|
||||
<Trans>Add action</Trans>
|
||||
);
|
||||
const instructionsList = connectDropTarget(
|
||||
<div
|
||||
className={`${
|
||||
areConditions ? conditionsContainer : actionsContainer
|
||||
} ${extraClassName || ''}`}
|
||||
style={style}
|
||||
>
|
||||
{instructions}
|
||||
{isOver && <DropIndicator canDrop={canDrop} />}
|
||||
<span
|
||||
onPointerEnter={() => {
|
||||
const canPaste =
|
||||
(areConditions && hasClipboardConditions()) ||
|
||||
(!areConditions && hasClipboardActions());
|
||||
this.setState({ canPaste });
|
||||
}}
|
||||
onPointerLeave={() => this.setState({ canPaste: false })}
|
||||
>
|
||||
<button
|
||||
style={styles.addButton}
|
||||
className="add-link"
|
||||
onClick={this.onAddNewInstruction}
|
||||
onContextMenu={e => {
|
||||
e.stopPropagation();
|
||||
onInstructionsListContextMenu(
|
||||
e.clientX,
|
||||
e.clientY,
|
||||
instructionsListContext
|
||||
);
|
||||
}}
|
||||
>
|
||||
{addButtonLabel || addButtonDefaultLabel}
|
||||
</button>
|
||||
{canPaste && (
|
||||
<span>
|
||||
{' '}
|
||||
<button
|
||||
style={styles.addButton}
|
||||
className="add-link"
|
||||
onClick={this._onPasteInstructions}
|
||||
>
|
||||
{areConditions ? (
|
||||
<Trans>(or paste conditions)</Trans>
|
||||
) : (
|
||||
<Trans>(or paste actions)</Trans>
|
||||
)}
|
||||
</button>
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
return instructionsList || null;
|
||||
return (
|
||||
<DropTarget
|
||||
canDrop={draggedItem => draggedItem.isCondition === areConditions}
|
||||
drop={() => {
|
||||
onMoveToInstructionsList({
|
||||
isCondition: areConditions,
|
||||
instrsList: instrsList,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{({ connectDropTarget, isOver, canDrop }) =>
|
||||
connectDropTarget(
|
||||
<div className={className} style={style}>
|
||||
{instructions}
|
||||
{isOver && <DropIndicator canDrop={canDrop} />}
|
||||
<span
|
||||
onPointerEnter={() => {
|
||||
const canPaste =
|
||||
(areConditions && hasClipboardConditions()) ||
|
||||
(!areConditions && hasClipboardActions());
|
||||
this.setState({ canPaste });
|
||||
}}
|
||||
onPointerLeave={() => this.setState({ canPaste: false })}
|
||||
>
|
||||
<button
|
||||
style={styles.addButton}
|
||||
className="add-link"
|
||||
onClick={this.onAddNewInstruction}
|
||||
onContextMenu={e => {
|
||||
e.stopPropagation();
|
||||
onInstructionsListContextMenu(
|
||||
e.clientX,
|
||||
e.clientY,
|
||||
instructionsListContext
|
||||
);
|
||||
}}
|
||||
>
|
||||
{addButtonLabel || addButtonDefaultLabel}
|
||||
</button>
|
||||
{canPaste && (
|
||||
<span>
|
||||
{' '}
|
||||
<button
|
||||
style={styles.addButton}
|
||||
className="add-link"
|
||||
onClick={this._onPasteInstructions}
|
||||
>
|
||||
{areConditions ? (
|
||||
<Trans>(or paste conditions)</Trans>
|
||||
) : (
|
||||
<Trans>(or paste actions)</Trans>
|
||||
)}
|
||||
</button>
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</DropTarget>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Drag'n'drop support:
|
||||
|
||||
const instructionsListTarget = {
|
||||
canDrop(props: Props, monitor: DropTargetMonitor) {
|
||||
return (
|
||||
monitor.getItem() && monitor.getItem().isCondition === props.areConditions
|
||||
);
|
||||
},
|
||||
drop(props: Props, monitor: DropTargetMonitor) {
|
||||
if (monitor.didDrop()) {
|
||||
return; // Drop already handled by an instruction
|
||||
}
|
||||
props.onMoveToInstructionsList({
|
||||
isCondition: props.areConditions,
|
||||
instrsList: props.instrsList,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
function targetCollect(
|
||||
connect: DropTargetConnector,
|
||||
monitor: DropTargetMonitor
|
||||
): DropTargetProps {
|
||||
return {
|
||||
connectDropTarget: connect.dropTarget(),
|
||||
isOver: monitor.isOver({ shallow: true }),
|
||||
canDrop: monitor.canDrop(),
|
||||
};
|
||||
}
|
||||
|
||||
// $FlowFixMe - Typing of DragSource/DropTarget is a pain to get correctly
|
||||
export default DropTarget(
|
||||
reactDndInstructionType,
|
||||
instructionsListTarget,
|
||||
targetCollect
|
||||
)(InstructionsList);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import * as React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import classNames from 'classnames';
|
||||
import { type EventRendererProps } from './EventRenderer.flow';
|
||||
import { type EventRendererProps } from './EventRenderer';
|
||||
import { rgbToHex } from '../../../Utils/ColorTransformer';
|
||||
import {
|
||||
largeSelectedArea,
|
||||
|
||||
+5
-1
@@ -1,12 +1,13 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
|
||||
import {
|
||||
type InstructionsListContext,
|
||||
type InstructionContext,
|
||||
type ParameterContext,
|
||||
} from '../../SelectionHandler';
|
||||
import { type EventsScope } from '../../EventsScope.flow';
|
||||
import { type ScreenType } from '../../../UI/Reponsive/ScreenTypeMeasurer';
|
||||
import { type WidthType } from '../../../UI/Reponsive/ResponsiveWindowMeasurer';
|
||||
|
||||
export type EventRendererProps = {
|
||||
project: gdProject,
|
||||
@@ -40,4 +41,7 @@ export type EventRendererProps = {
|
||||
|
||||
leftIndentWidth: number,
|
||||
renderObjectThumbnail: string => React.Node,
|
||||
|
||||
screenType: ScreenType,
|
||||
windowWidth: WidthType,
|
||||
};
|
||||
@@ -11,7 +11,8 @@ import {
|
||||
} from '../ClassNames';
|
||||
import InlinePopover from '../../InlinePopover';
|
||||
import ObjectField from '../../ParameterFields/ObjectField';
|
||||
import { type EventRendererProps } from './EventRenderer.flow';
|
||||
import { type EventRendererProps } from './EventRenderer';
|
||||
import ConditionsActionsColumns from '../ConditionsActionsColumns';
|
||||
const gd = global.gd;
|
||||
|
||||
const styles = {
|
||||
@@ -61,10 +62,6 @@ export default class ForEachEvent extends React.Component<
|
||||
render() {
|
||||
var forEachEvent = gd.asForEachEvent(this.props.event);
|
||||
|
||||
const conditionsListSyle = {
|
||||
width: `calc(35vw - ${this.props.leftIndentWidth}px)`,
|
||||
};
|
||||
|
||||
const objectName = forEachEvent.getObjectToPick();
|
||||
return (
|
||||
<div
|
||||
@@ -92,50 +89,62 @@ export default class ForEachEvent extends React.Component<
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div style={styles.instructionsContainer}>
|
||||
<InstructionsList
|
||||
instrsList={forEachEvent.getConditions()}
|
||||
style={conditionsListSyle}
|
||||
selection={this.props.selection}
|
||||
areConditions
|
||||
onAddNewInstruction={this.props.onAddNewInstruction}
|
||||
onPasteInstructions={this.props.onPasteInstructions}
|
||||
onMoveToInstruction={this.props.onMoveToInstruction}
|
||||
onMoveToInstructionsList={this.props.onMoveToInstructionsList}
|
||||
onInstructionClick={this.props.onInstructionClick}
|
||||
onInstructionDoubleClick={this.props.onInstructionDoubleClick}
|
||||
onInstructionContextMenu={this.props.onInstructionContextMenu}
|
||||
onInstructionsListContextMenu={
|
||||
this.props.onInstructionsListContextMenu
|
||||
}
|
||||
onParameterClick={this.props.onParameterClick}
|
||||
disabled={this.props.disabled}
|
||||
renderObjectThumbnail={this.props.renderObjectThumbnail}
|
||||
/>
|
||||
<InstructionsList
|
||||
instrsList={forEachEvent.getActions()}
|
||||
style={
|
||||
{
|
||||
...styles.actionsList,
|
||||
} /* TODO: Use a new object to force update - somehow updates are not always propagated otherwise */
|
||||
}
|
||||
selection={this.props.selection}
|
||||
areConditions={false}
|
||||
onAddNewInstruction={this.props.onAddNewInstruction}
|
||||
onPasteInstructions={this.props.onPasteInstructions}
|
||||
onMoveToInstruction={this.props.onMoveToInstruction}
|
||||
onMoveToInstructionsList={this.props.onMoveToInstructionsList}
|
||||
onInstructionClick={this.props.onInstructionClick}
|
||||
onInstructionDoubleClick={this.props.onInstructionDoubleClick}
|
||||
onInstructionContextMenu={this.props.onInstructionContextMenu}
|
||||
onInstructionsListContextMenu={
|
||||
this.props.onInstructionsListContextMenu
|
||||
}
|
||||
onParameterClick={this.props.onParameterClick}
|
||||
disabled={this.props.disabled}
|
||||
renderObjectThumbnail={this.props.renderObjectThumbnail}
|
||||
/>
|
||||
</div>
|
||||
<ConditionsActionsColumns
|
||||
leftIndentWidth={this.props.leftIndentWidth}
|
||||
windowWidth={this.props.windowWidth}
|
||||
renderConditionsList={({ style, className }) => (
|
||||
<InstructionsList
|
||||
instrsList={forEachEvent.getConditions()}
|
||||
style={style}
|
||||
className={className}
|
||||
selection={this.props.selection}
|
||||
areConditions
|
||||
onAddNewInstruction={this.props.onAddNewInstruction}
|
||||
onPasteInstructions={this.props.onPasteInstructions}
|
||||
onMoveToInstruction={this.props.onMoveToInstruction}
|
||||
onMoveToInstructionsList={this.props.onMoveToInstructionsList}
|
||||
onInstructionClick={this.props.onInstructionClick}
|
||||
onInstructionDoubleClick={this.props.onInstructionDoubleClick}
|
||||
onInstructionContextMenu={this.props.onInstructionContextMenu}
|
||||
onInstructionsListContextMenu={
|
||||
this.props.onInstructionsListContextMenu
|
||||
}
|
||||
onParameterClick={this.props.onParameterClick}
|
||||
disabled={this.props.disabled}
|
||||
renderObjectThumbnail={this.props.renderObjectThumbnail}
|
||||
screenType={this.props.screenType}
|
||||
windowWidth={this.props.windowWidth}
|
||||
/>
|
||||
)}
|
||||
renderActionsList={({ className }) => (
|
||||
<InstructionsList
|
||||
instrsList={forEachEvent.getActions()}
|
||||
style={
|
||||
{
|
||||
...styles.actionsList,
|
||||
} /* TODO: Use a new object to force update - somehow updates are not always propagated otherwise */
|
||||
}
|
||||
className={className}
|
||||
selection={this.props.selection}
|
||||
areConditions={false}
|
||||
onAddNewInstruction={this.props.onAddNewInstruction}
|
||||
onPasteInstructions={this.props.onPasteInstructions}
|
||||
onMoveToInstruction={this.props.onMoveToInstruction}
|
||||
onMoveToInstructionsList={this.props.onMoveToInstructionsList}
|
||||
onInstructionClick={this.props.onInstructionClick}
|
||||
onInstructionDoubleClick={this.props.onInstructionDoubleClick}
|
||||
onInstructionContextMenu={this.props.onInstructionContextMenu}
|
||||
onInstructionsListContextMenu={
|
||||
this.props.onInstructionsListContextMenu
|
||||
}
|
||||
onParameterClick={this.props.onParameterClick}
|
||||
disabled={this.props.disabled}
|
||||
renderObjectThumbnail={this.props.renderObjectThumbnail}
|
||||
screenType={this.props.screenType}
|
||||
windowWidth={this.props.windowWidth}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<InlinePopover
|
||||
open={this.state.editing}
|
||||
anchorEl={this.state.anchorEl}
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
selectableArea,
|
||||
disabledText,
|
||||
} from '../ClassNames';
|
||||
import { type EventRendererProps } from './EventRenderer.flow';
|
||||
import { type EventRendererProps } from './EventRenderer';
|
||||
const gd = global.gd;
|
||||
|
||||
const styles = {
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
selectableArea,
|
||||
} from '../ClassNames';
|
||||
import { getHelpLink } from '../../../Utils/HelpLink';
|
||||
import { type EventRendererProps } from './EventRenderer.flow';
|
||||
import { type EventRendererProps } from './EventRenderer';
|
||||
import Measure from 'react-measure';
|
||||
import { CodeEditor } from '../../../CodeEditor';
|
||||
const gd = global.gd;
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
import InlinePopover from '../../InlinePopover';
|
||||
import ExternalEventsField from '../../ParameterFields/ExternalEventsField';
|
||||
import { showWarningBox } from '../../../UI/Messages/MessageBox';
|
||||
import { type EventRendererProps } from './EventRenderer.flow';
|
||||
import { type EventRendererProps } from './EventRenderer';
|
||||
const gd = global.gd;
|
||||
|
||||
const styles = {
|
||||
|
||||
@@ -11,7 +11,8 @@ import {
|
||||
} from '../ClassNames';
|
||||
import InlinePopover from '../../InlinePopover';
|
||||
import DefaultField from '../../ParameterFields/DefaultField';
|
||||
import { type EventRendererProps } from './EventRenderer.flow';
|
||||
import { type EventRendererProps } from './EventRenderer';
|
||||
import ConditionsActionsColumns from '../ConditionsActionsColumns';
|
||||
const gd = global.gd;
|
||||
|
||||
const styles = {
|
||||
@@ -61,10 +62,6 @@ export default class RepeatEvent extends React.Component<
|
||||
render() {
|
||||
var repeatEvent = gd.asRepeatEvent(this.props.event);
|
||||
|
||||
const conditionsListSyle = {
|
||||
width: `calc(35vw - ${this.props.leftIndentWidth}px)`,
|
||||
};
|
||||
|
||||
const expression = repeatEvent.getRepeatExpression();
|
||||
return (
|
||||
<div
|
||||
@@ -90,50 +87,62 @@ export default class RepeatEvent extends React.Component<
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div style={styles.instructionsContainer}>
|
||||
<InstructionsList
|
||||
instrsList={repeatEvent.getConditions()}
|
||||
style={conditionsListSyle}
|
||||
selection={this.props.selection}
|
||||
areConditions
|
||||
onAddNewInstruction={this.props.onAddNewInstruction}
|
||||
onPasteInstructions={this.props.onPasteInstructions}
|
||||
onMoveToInstruction={this.props.onMoveToInstruction}
|
||||
onMoveToInstructionsList={this.props.onMoveToInstructionsList}
|
||||
onInstructionClick={this.props.onInstructionClick}
|
||||
onInstructionDoubleClick={this.props.onInstructionDoubleClick}
|
||||
onInstructionContextMenu={this.props.onInstructionContextMenu}
|
||||
onInstructionsListContextMenu={
|
||||
this.props.onInstructionsListContextMenu
|
||||
}
|
||||
onParameterClick={this.props.onParameterClick}
|
||||
disabled={this.props.disabled}
|
||||
renderObjectThumbnail={this.props.renderObjectThumbnail}
|
||||
/>
|
||||
<InstructionsList
|
||||
instrsList={repeatEvent.getActions()}
|
||||
style={
|
||||
{
|
||||
...styles.actionsList,
|
||||
} /* TODO: Use a new object to force update - somehow updates are not always propagated otherwise */
|
||||
}
|
||||
selection={this.props.selection}
|
||||
areConditions={false}
|
||||
onAddNewInstruction={this.props.onAddNewInstruction}
|
||||
onPasteInstructions={this.props.onPasteInstructions}
|
||||
onMoveToInstruction={this.props.onMoveToInstruction}
|
||||
onMoveToInstructionsList={this.props.onMoveToInstructionsList}
|
||||
onInstructionClick={this.props.onInstructionClick}
|
||||
onInstructionDoubleClick={this.props.onInstructionDoubleClick}
|
||||
onInstructionContextMenu={this.props.onInstructionContextMenu}
|
||||
onInstructionsListContextMenu={
|
||||
this.props.onInstructionsListContextMenu
|
||||
}
|
||||
onParameterClick={this.props.onParameterClick}
|
||||
disabled={this.props.disabled}
|
||||
renderObjectThumbnail={this.props.renderObjectThumbnail}
|
||||
/>
|
||||
</div>
|
||||
<ConditionsActionsColumns
|
||||
leftIndentWidth={this.props.leftIndentWidth}
|
||||
windowWidth={this.props.windowWidth}
|
||||
renderConditionsList={({ style, className }) => (
|
||||
<InstructionsList
|
||||
instrsList={repeatEvent.getConditions()}
|
||||
style={style}
|
||||
className={className}
|
||||
selection={this.props.selection}
|
||||
areConditions
|
||||
onAddNewInstruction={this.props.onAddNewInstruction}
|
||||
onPasteInstructions={this.props.onPasteInstructions}
|
||||
onMoveToInstruction={this.props.onMoveToInstruction}
|
||||
onMoveToInstructionsList={this.props.onMoveToInstructionsList}
|
||||
onInstructionClick={this.props.onInstructionClick}
|
||||
onInstructionDoubleClick={this.props.onInstructionDoubleClick}
|
||||
onInstructionContextMenu={this.props.onInstructionContextMenu}
|
||||
onInstructionsListContextMenu={
|
||||
this.props.onInstructionsListContextMenu
|
||||
}
|
||||
onParameterClick={this.props.onParameterClick}
|
||||
disabled={this.props.disabled}
|
||||
renderObjectThumbnail={this.props.renderObjectThumbnail}
|
||||
screenType={this.props.screenType}
|
||||
windowWidth={this.props.windowWidth}
|
||||
/>
|
||||
)}
|
||||
renderActionsList={({ className }) => (
|
||||
<InstructionsList
|
||||
instrsList={repeatEvent.getActions()}
|
||||
style={
|
||||
{
|
||||
...styles.actionsList,
|
||||
} /* TODO: Use a new object to force update - somehow updates are not always propagated otherwise */
|
||||
}
|
||||
className={className}
|
||||
selection={this.props.selection}
|
||||
areConditions={false}
|
||||
onAddNewInstruction={this.props.onAddNewInstruction}
|
||||
onPasteInstructions={this.props.onPasteInstructions}
|
||||
onMoveToInstruction={this.props.onMoveToInstruction}
|
||||
onMoveToInstructionsList={this.props.onMoveToInstructionsList}
|
||||
onInstructionClick={this.props.onInstructionClick}
|
||||
onInstructionDoubleClick={this.props.onInstructionDoubleClick}
|
||||
onInstructionContextMenu={this.props.onInstructionContextMenu}
|
||||
onInstructionsListContextMenu={
|
||||
this.props.onInstructionsListContextMenu
|
||||
}
|
||||
onParameterClick={this.props.onParameterClick}
|
||||
disabled={this.props.disabled}
|
||||
renderObjectThumbnail={this.props.renderObjectThumbnail}
|
||||
screenType={this.props.screenType}
|
||||
windowWidth={this.props.windowWidth}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<InlinePopover
|
||||
open={this.state.editing}
|
||||
anchorEl={this.state.anchorEl}
|
||||
|
||||
@@ -7,13 +7,11 @@ import {
|
||||
largeSelectableArea,
|
||||
executableEventContainer,
|
||||
} from '../ClassNames';
|
||||
import { type EventRendererProps } from './EventRenderer.flow';
|
||||
import { type EventRendererProps } from './EventRenderer';
|
||||
import ConditionsActionsColumns from '../ConditionsActionsColumns';
|
||||
const gd = global.gd;
|
||||
|
||||
const styles = {
|
||||
container: {
|
||||
display: 'flex',
|
||||
},
|
||||
actionsList: {
|
||||
flex: 1,
|
||||
},
|
||||
@@ -26,62 +24,68 @@ export default class StandardEvent extends React.Component<
|
||||
render() {
|
||||
var standardEvent = gd.asStandardEvent(this.props.event);
|
||||
|
||||
const conditionsListSyle = {
|
||||
width: `calc(35vw - ${this.props.leftIndentWidth}px)`,
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
style={styles.container}
|
||||
<ConditionsActionsColumns
|
||||
leftIndentWidth={this.props.leftIndentWidth}
|
||||
windowWidth={this.props.windowWidth}
|
||||
className={classNames({
|
||||
[largeSelectableArea]: true,
|
||||
[largeSelectedArea]: this.props.selected,
|
||||
[executableEventContainer]: true,
|
||||
})}
|
||||
>
|
||||
<InstructionsList
|
||||
instrsList={standardEvent.getConditions()}
|
||||
style={conditionsListSyle}
|
||||
selection={this.props.selection}
|
||||
areConditions
|
||||
onAddNewInstruction={this.props.onAddNewInstruction}
|
||||
onPasteInstructions={this.props.onPasteInstructions}
|
||||
onMoveToInstruction={this.props.onMoveToInstruction}
|
||||
onMoveToInstructionsList={this.props.onMoveToInstructionsList}
|
||||
onInstructionClick={this.props.onInstructionClick}
|
||||
onInstructionDoubleClick={this.props.onInstructionDoubleClick}
|
||||
onInstructionContextMenu={this.props.onInstructionContextMenu}
|
||||
onInstructionsListContextMenu={
|
||||
this.props.onInstructionsListContextMenu
|
||||
}
|
||||
onParameterClick={this.props.onParameterClick}
|
||||
disabled={this.props.disabled}
|
||||
renderObjectThumbnail={this.props.renderObjectThumbnail}
|
||||
/>
|
||||
<InstructionsList
|
||||
instrsList={standardEvent.getActions()}
|
||||
style={
|
||||
{
|
||||
...styles.actionsList,
|
||||
} /* TODO: Use a new object to force update - somehow updates are not always propagated otherwise */
|
||||
}
|
||||
selection={this.props.selection}
|
||||
areConditions={false}
|
||||
onAddNewInstruction={this.props.onAddNewInstruction}
|
||||
onPasteInstructions={this.props.onPasteInstructions}
|
||||
onMoveToInstruction={this.props.onMoveToInstruction}
|
||||
onMoveToInstructionsList={this.props.onMoveToInstructionsList}
|
||||
onInstructionClick={this.props.onInstructionClick}
|
||||
onInstructionDoubleClick={this.props.onInstructionDoubleClick}
|
||||
onInstructionContextMenu={this.props.onInstructionContextMenu}
|
||||
onInstructionsListContextMenu={
|
||||
this.props.onInstructionsListContextMenu
|
||||
}
|
||||
onParameterClick={this.props.onParameterClick}
|
||||
disabled={this.props.disabled}
|
||||
renderObjectThumbnail={this.props.renderObjectThumbnail}
|
||||
/>
|
||||
</div>
|
||||
renderConditionsList={({ style, className }) => (
|
||||
<InstructionsList
|
||||
instrsList={standardEvent.getConditions()}
|
||||
style={style}
|
||||
className={className}
|
||||
selection={this.props.selection}
|
||||
areConditions
|
||||
onAddNewInstruction={this.props.onAddNewInstruction}
|
||||
onPasteInstructions={this.props.onPasteInstructions}
|
||||
onMoveToInstruction={this.props.onMoveToInstruction}
|
||||
onMoveToInstructionsList={this.props.onMoveToInstructionsList}
|
||||
onInstructionClick={this.props.onInstructionClick}
|
||||
onInstructionDoubleClick={this.props.onInstructionDoubleClick}
|
||||
onInstructionContextMenu={this.props.onInstructionContextMenu}
|
||||
onInstructionsListContextMenu={
|
||||
this.props.onInstructionsListContextMenu
|
||||
}
|
||||
onParameterClick={this.props.onParameterClick}
|
||||
disabled={this.props.disabled}
|
||||
renderObjectThumbnail={this.props.renderObjectThumbnail}
|
||||
screenType={this.props.screenType}
|
||||
windowWidth={this.props.windowWidth}
|
||||
/>
|
||||
)}
|
||||
renderActionsList={({ className }) => (
|
||||
<InstructionsList
|
||||
instrsList={standardEvent.getActions()}
|
||||
style={
|
||||
{
|
||||
...styles.actionsList,
|
||||
} /* TODO: Use a new object to force update - somehow updates are not always propagated otherwise */
|
||||
}
|
||||
className={className}
|
||||
selection={this.props.selection}
|
||||
areConditions={false}
|
||||
onAddNewInstruction={this.props.onAddNewInstruction}
|
||||
onPasteInstructions={this.props.onPasteInstructions}
|
||||
onMoveToInstruction={this.props.onMoveToInstruction}
|
||||
onMoveToInstructionsList={this.props.onMoveToInstructionsList}
|
||||
onInstructionClick={this.props.onInstructionClick}
|
||||
onInstructionDoubleClick={this.props.onInstructionDoubleClick}
|
||||
onInstructionContextMenu={this.props.onInstructionContextMenu}
|
||||
onInstructionsListContextMenu={
|
||||
this.props.onInstructionsListContextMenu
|
||||
}
|
||||
onParameterClick={this.props.onParameterClick}
|
||||
disabled={this.props.disabled}
|
||||
renderObjectThumbnail={this.props.renderObjectThumbnail}
|
||||
screenType={this.props.screenType}
|
||||
windowWidth={this.props.windowWidth}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import * as React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { largeSelectedArea, largeSelectableArea } from '../ClassNames';
|
||||
import { type EventRendererProps } from './EventRenderer.flow';
|
||||
import { type EventRendererProps } from './EventRenderer';
|
||||
|
||||
export default class UnknownEvent extends React.Component<
|
||||
EventRendererProps,
|
||||
|
||||
@@ -7,8 +7,10 @@ import {
|
||||
largeSelectableArea,
|
||||
executableEventContainer,
|
||||
disabledText,
|
||||
conditionsContainer,
|
||||
} from '../ClassNames';
|
||||
import { type EventRendererProps } from './EventRenderer.flow';
|
||||
import { type EventRendererProps } from './EventRenderer';
|
||||
import ConditionsActionsColumns from '../ConditionsActionsColumns';
|
||||
const gd = global.gd;
|
||||
|
||||
const styles = {
|
||||
@@ -31,10 +33,6 @@ export default class ForEachEvent extends React.Component<
|
||||
render() {
|
||||
var whileEvent = gd.asWhileEvent(this.props.event);
|
||||
|
||||
const conditionsListSyle = {
|
||||
width: `calc(35vw - ${this.props.leftIndentWidth}px)`,
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
style={styles.container}
|
||||
@@ -56,6 +54,7 @@ export default class ForEachEvent extends React.Component<
|
||||
style={
|
||||
{} /* TODO: Use a new object to force update - somehow updates are not always propagated otherwise */
|
||||
}
|
||||
className={conditionsContainer}
|
||||
selection={this.props.selection}
|
||||
areConditions
|
||||
onAddNewInstruction={this.props.onAddNewInstruction}
|
||||
@@ -71,6 +70,8 @@ export default class ForEachEvent extends React.Component<
|
||||
onParameterClick={this.props.onParameterClick}
|
||||
disabled={this.props.disabled}
|
||||
renderObjectThumbnail={this.props.renderObjectThumbnail}
|
||||
screenType={this.props.screenType}
|
||||
windowWidth={this.props.windowWidth}
|
||||
/>
|
||||
<div
|
||||
className={classNames({
|
||||
@@ -79,50 +80,62 @@ export default class ForEachEvent extends React.Component<
|
||||
>
|
||||
Repeat these:
|
||||
</div>
|
||||
<div style={styles.instructionsContainer}>
|
||||
<InstructionsList
|
||||
instrsList={whileEvent.getConditions()}
|
||||
style={conditionsListSyle}
|
||||
selection={this.props.selection}
|
||||
areConditions
|
||||
onAddNewInstruction={this.props.onAddNewInstruction}
|
||||
onPasteInstructions={this.props.onPasteInstructions}
|
||||
onMoveToInstruction={this.props.onMoveToInstruction}
|
||||
onMoveToInstructionsList={this.props.onMoveToInstructionsList}
|
||||
onInstructionClick={this.props.onInstructionClick}
|
||||
onInstructionDoubleClick={this.props.onInstructionDoubleClick}
|
||||
onInstructionContextMenu={this.props.onInstructionContextMenu}
|
||||
onInstructionsListContextMenu={
|
||||
this.props.onInstructionsListContextMenu
|
||||
}
|
||||
onParameterClick={this.props.onParameterClick}
|
||||
disabled={this.props.disabled}
|
||||
renderObjectThumbnail={this.props.renderObjectThumbnail}
|
||||
/>
|
||||
<InstructionsList
|
||||
instrsList={whileEvent.getActions()}
|
||||
style={
|
||||
{
|
||||
...styles.actionsList,
|
||||
} /* TODO: Use a new object to force update - somehow updates are not always propagated otherwise */
|
||||
}
|
||||
selection={this.props.selection}
|
||||
areConditions={false}
|
||||
onAddNewInstruction={this.props.onAddNewInstruction}
|
||||
onPasteInstructions={this.props.onPasteInstructions}
|
||||
onMoveToInstruction={this.props.onMoveToInstruction}
|
||||
onMoveToInstructionsList={this.props.onMoveToInstructionsList}
|
||||
onInstructionClick={this.props.onInstructionClick}
|
||||
onInstructionDoubleClick={this.props.onInstructionDoubleClick}
|
||||
onInstructionContextMenu={this.props.onInstructionContextMenu}
|
||||
onInstructionsListContextMenu={
|
||||
this.props.onInstructionsListContextMenu
|
||||
}
|
||||
onParameterClick={this.props.onParameterClick}
|
||||
disabled={this.props.disabled}
|
||||
renderObjectThumbnail={this.props.renderObjectThumbnail}
|
||||
/>
|
||||
</div>
|
||||
<ConditionsActionsColumns
|
||||
leftIndentWidth={this.props.leftIndentWidth}
|
||||
windowWidth={this.props.windowWidth}
|
||||
renderConditionsList={({ style, className }) => (
|
||||
<InstructionsList
|
||||
instrsList={whileEvent.getConditions()}
|
||||
style={style}
|
||||
className={className}
|
||||
selection={this.props.selection}
|
||||
areConditions
|
||||
onAddNewInstruction={this.props.onAddNewInstruction}
|
||||
onPasteInstructions={this.props.onPasteInstructions}
|
||||
onMoveToInstruction={this.props.onMoveToInstruction}
|
||||
onMoveToInstructionsList={this.props.onMoveToInstructionsList}
|
||||
onInstructionClick={this.props.onInstructionClick}
|
||||
onInstructionDoubleClick={this.props.onInstructionDoubleClick}
|
||||
onInstructionContextMenu={this.props.onInstructionContextMenu}
|
||||
onInstructionsListContextMenu={
|
||||
this.props.onInstructionsListContextMenu
|
||||
}
|
||||
onParameterClick={this.props.onParameterClick}
|
||||
disabled={this.props.disabled}
|
||||
renderObjectThumbnail={this.props.renderObjectThumbnail}
|
||||
screenType={this.props.screenType}
|
||||
windowWidth={this.props.windowWidth}
|
||||
/>
|
||||
)}
|
||||
renderActionsList={({ className }) => (
|
||||
<InstructionsList
|
||||
instrsList={whileEvent.getActions()}
|
||||
style={
|
||||
{
|
||||
...styles.actionsList,
|
||||
} /* TODO: Use a new object to force update - somehow updates are not always propagated otherwise */
|
||||
}
|
||||
className={className}
|
||||
selection={this.props.selection}
|
||||
areConditions={false}
|
||||
onAddNewInstruction={this.props.onAddNewInstruction}
|
||||
onPasteInstructions={this.props.onPasteInstructions}
|
||||
onMoveToInstruction={this.props.onMoveToInstruction}
|
||||
onMoveToInstructionsList={this.props.onMoveToInstructionsList}
|
||||
onInstructionClick={this.props.onInstructionClick}
|
||||
onInstructionDoubleClick={this.props.onInstructionDoubleClick}
|
||||
onInstructionContextMenu={this.props.onInstructionContextMenu}
|
||||
onInstructionsListContextMenu={
|
||||
this.props.onInstructionsListContextMenu
|
||||
}
|
||||
onParameterClick={this.props.onParameterClick}
|
||||
disabled={this.props.disabled}
|
||||
renderObjectThumbnail={this.props.renderObjectThumbnail}
|
||||
screenType={this.props.screenType}
|
||||
windowWidth={this.props.windowWidth}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ import {
|
||||
import { type EventsScope } from '../EventsScope.flow';
|
||||
import getObjectByName from '../../Utils/GetObjectByName';
|
||||
import ObjectsRenderingService from '../../ObjectsRendering/ObjectsRenderingService';
|
||||
import { type ScreenType } from '../../UI/Reponsive/ScreenTypeMeasurer';
|
||||
import { type WidthType } from '../../UI/Reponsive/ResponsiveWindowMeasurer';
|
||||
|
||||
// Import default style of react-sortable-tree and the override made for EventsSheet.
|
||||
import 'react-sortable-tree/style.css';
|
||||
@@ -70,6 +72,9 @@ type EventsContainerProps = {|
|
||||
onOpenExternalEvents: string => void,
|
||||
onOpenLayout: string => void,
|
||||
renderObjectThumbnail: string => Node,
|
||||
|
||||
screenType: ScreenType,
|
||||
windowWidth: WidthType,
|
||||
|};
|
||||
|
||||
/**
|
||||
@@ -136,6 +141,8 @@ class EventContainer extends Component<EventsContainerProps, {||}> {
|
||||
disabled /* Use disabled (not event.disabled) as it is true if a parent event is disabled*/
|
||||
}
|
||||
renderObjectThumbnail={this.props.renderObjectThumbnail}
|
||||
screenType={this.props.screenType}
|
||||
windowWidth={this.props.windowWidth}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -198,6 +205,9 @@ type EventsTreeProps = {|
|
||||
searchFocusOffset: ?number,
|
||||
|
||||
onEventMoved: () => void,
|
||||
|
||||
screenType: ScreenType,
|
||||
windowWidth: WidthType,
|
||||
|};
|
||||
|
||||
type SortableTreeNode = {
|
||||
@@ -435,6 +445,8 @@ export default class ThemableEventsTree extends Component<EventsTreeProps, *> {
|
||||
disabled /* Use node.disabled (not event.disabled) as it is true if a parent event is disabled*/
|
||||
}
|
||||
renderObjectThumbnail={this._renderObjectThumbnail}
|
||||
screenType={this.props.screenType}
|
||||
windowWidth={this.props.windowWidth}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -389,7 +389,7 @@ export default class NewInstructionEditorDialog extends React.Component<
|
||||
helpPagePath={instructionHelpPage || '/events'}
|
||||
label={
|
||||
!instructionHelpPage ||
|
||||
(width === 'small' &&
|
||||
(width === 'small' ||
|
||||
step === 'object-or-free-instructions') ? (
|
||||
<Trans>Help</Trans>
|
||||
) : this.props.isCondition ? (
|
||||
|
||||
+400
-301
@@ -1,4 +1,5 @@
|
||||
// @flow
|
||||
import { Trans } from '@lingui/macro';
|
||||
import * as React from 'react';
|
||||
import EventsTree from './EventsTree';
|
||||
import NewInstructionEditorDialog from './InstructionEditor/NewInstructionEditorDialog';
|
||||
@@ -73,6 +74,9 @@ import {
|
||||
hasClipboardConditions,
|
||||
pasteInstructionsFromClipboardInInstructionsList,
|
||||
} from './ClipboardKind';
|
||||
import InfoBar from '../UI/Messages/InfoBar';
|
||||
import { ScreenTypeMeasurer } from '../UI/Reponsive/ScreenTypeMeasurer';
|
||||
import { ResponsiveWindowMeasurer } from '../UI/Reponsive/ResponsiveWindowMeasurer';
|
||||
const gd = global.gd;
|
||||
|
||||
type Props = {|
|
||||
@@ -141,6 +145,8 @@ const styles = {
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
flexDirection: 'column',
|
||||
flex: 1,
|
||||
position: 'relative', // To be sure that absolutely positioned PlaceholderMessage won't go outside of the EventsSheet
|
||||
},
|
||||
};
|
||||
|
||||
@@ -744,6 +750,17 @@ export default class EventsSheet extends React.Component<Props, State> {
|
||||
eventsList.delete();
|
||||
};
|
||||
|
||||
moveEventsIntoNewGroup = () => {
|
||||
const eventsList = new gd.EventsList();
|
||||
|
||||
getSelectedEvents(this.state.selection).forEach(event =>
|
||||
eventsList.insertEvent(event, eventsList.getEventsCount())
|
||||
);
|
||||
|
||||
this._replaceSelectionByGroupOfEvents(eventsList);
|
||||
eventsList.delete();
|
||||
};
|
||||
|
||||
_replaceSelectionByEventsFunction = (
|
||||
extensionName: string,
|
||||
eventsFunction: gdEventsFunction
|
||||
@@ -771,6 +788,30 @@ export default class EventsSheet extends React.Component<Props, State> {
|
||||
this.deleteSelection({ deleteInstructions: false });
|
||||
};
|
||||
|
||||
_replaceSelectionByGroupOfEvents = (eventsList: gdEventsList) => {
|
||||
const contexts = getSelectedEventContexts(this.state.selection);
|
||||
if (!contexts.length) return;
|
||||
|
||||
const newEvents = this.addNewEvent(
|
||||
'BuiltinCommonInstructions::Group',
|
||||
contexts[0]
|
||||
);
|
||||
if (!newEvents.length) {
|
||||
console.error('A new event should have been created');
|
||||
return;
|
||||
}
|
||||
|
||||
const groupEvent = gd.asGroupEvent(newEvents[0]);
|
||||
|
||||
groupEvent.setName('Grouped events');
|
||||
groupEvent.setFolded(true);
|
||||
groupEvent
|
||||
.getSubEvents()
|
||||
.insertEvents(eventsList, 0, eventsList.getEventsCount(), 0);
|
||||
|
||||
this.deleteSelection({ deleteInstructions: false });
|
||||
};
|
||||
|
||||
_ensureEventUnfolded = (cb: () => ?gdBaseEvent) => {
|
||||
const event = cb();
|
||||
if (event && this._eventsTree) {
|
||||
@@ -877,312 +918,370 @@ export default class EventsSheet extends React.Component<Props, State> {
|
||||
if (!project) return null;
|
||||
|
||||
return (
|
||||
<PreferencesContext.Consumer>
|
||||
{({ values }) => (
|
||||
<EventsSearcher
|
||||
key={events.ptr}
|
||||
ref={eventSearcher => (this._eventSearcher = eventSearcher)}
|
||||
events={events}
|
||||
globalObjectsContainer={globalObjectsContainer}
|
||||
objectsContainer={objectsContainer}
|
||||
selection={this.state.selection}
|
||||
>
|
||||
{({
|
||||
eventsSearchResultEvents,
|
||||
searchFocusOffset,
|
||||
searchInEvents,
|
||||
replaceInEvents,
|
||||
goToPreviousSearchResult,
|
||||
goToNextSearchResult,
|
||||
}) => (
|
||||
<div
|
||||
className="gd-events-sheet"
|
||||
style={styles.container}
|
||||
onFocus={() => this._keyboardShortcuts.focus()}
|
||||
onBlur={() => this._keyboardShortcuts.blur()}
|
||||
tabIndex={1}
|
||||
>
|
||||
<EventsTree
|
||||
ref={eventsTree => (this._eventsTree = eventsTree)}
|
||||
key={events.ptr}
|
||||
events={events}
|
||||
project={project}
|
||||
scope={scope}
|
||||
globalObjectsContainer={globalObjectsContainer}
|
||||
objectsContainer={objectsContainer}
|
||||
selection={this.state.selection}
|
||||
onInstructionClick={this.selectInstruction}
|
||||
onInstructionDoubleClick={this.openInstructionEditor}
|
||||
onInstructionContextMenu={this.openInstructionContextMenu}
|
||||
onInstructionsListContextMenu={
|
||||
this.openInstructionsListContextMenu
|
||||
}
|
||||
onAddNewInstruction={this.openInstructionEditor}
|
||||
onPasteInstructions={this.pasteInstructionsInInstructionsList}
|
||||
onMoveToInstruction={this.moveSelectionToInstruction}
|
||||
onMoveToInstructionsList={
|
||||
this.moveSelectionToInstructionsList
|
||||
}
|
||||
onParameterClick={this.openParameterEditor}
|
||||
onEventClick={this.selectEvent}
|
||||
onEventContextMenu={this.openEventContextMenu}
|
||||
onAddNewEvent={context => {
|
||||
this.addNewEvent(
|
||||
'BuiltinCommonInstructions::Standard',
|
||||
context
|
||||
);
|
||||
}}
|
||||
onOpenExternalEvents={onOpenExternalEvents}
|
||||
onOpenLayout={onOpenLayout}
|
||||
searchResults={eventsSearchResultEvents}
|
||||
searchFocusOffset={searchFocusOffset}
|
||||
onEventMoved={this._onEventMoved}
|
||||
showObjectThumbnails={values.eventsSheetShowObjectThumbnails}
|
||||
/>
|
||||
{this.state.showSearchPanel && (
|
||||
<SearchPanel
|
||||
ref={searchPanel => (this._searchPanel = searchPanel)}
|
||||
onSearchInEvents={inputs =>
|
||||
this._searchInEvents(searchInEvents, inputs)
|
||||
}
|
||||
onReplaceInEvents={inputs =>
|
||||
this._replaceInEvents(replaceInEvents, inputs)
|
||||
}
|
||||
resultsCount={
|
||||
eventsSearchResultEvents
|
||||
? eventsSearchResultEvents.length
|
||||
: null
|
||||
}
|
||||
hasEventSelected={hasEventSelected(this.state.selection)}
|
||||
onGoToPreviousSearchResult={() =>
|
||||
this._ensureEventUnfolded(goToPreviousSearchResult)
|
||||
}
|
||||
onGoToNextSearchResult={() =>
|
||||
this._ensureEventUnfolded(goToNextSearchResult)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{events && events.getEventsCount() === 0 && (
|
||||
<EmptyEventsPlaceholder />
|
||||
)}
|
||||
<InlineParameterEditor
|
||||
open={this.state.inlineEditing}
|
||||
anchorEl={this.state.inlineEditingAnchorEl}
|
||||
onRequestClose={this.closeParameterEditor}
|
||||
project={project}
|
||||
scope={scope}
|
||||
globalObjectsContainer={globalObjectsContainer}
|
||||
objectsContainer={objectsContainer}
|
||||
isCondition={this.state.editedParameter.isCondition}
|
||||
instruction={this.state.editedParameter.instruction}
|
||||
parameterIndex={this.state.editedParameter.parameterIndex}
|
||||
onChange={value => {
|
||||
const {
|
||||
instruction,
|
||||
parameterIndex,
|
||||
} = this.state.editedParameter;
|
||||
if (!instruction) return;
|
||||
instruction.setParameter(parameterIndex, value);
|
||||
this.setState({
|
||||
inlineEditingChangesMade: true,
|
||||
});
|
||||
}}
|
||||
resourceSources={this.props.resourceSources}
|
||||
onChooseResource={this.props.onChooseResource}
|
||||
resourceExternalEditors={this.props.resourceExternalEditors}
|
||||
/>
|
||||
<ContextMenu
|
||||
ref={eventContextMenu =>
|
||||
(this.eventContextMenu = eventContextMenu)
|
||||
}
|
||||
buildMenuTemplate={() => [
|
||||
{
|
||||
label: 'Copy',
|
||||
click: () => this.copySelection(),
|
||||
accelerator: 'CmdOrCtrl+C',
|
||||
},
|
||||
{
|
||||
label: 'Cut',
|
||||
click: () => this.cutSelection(),
|
||||
accelerator: 'CmdOrCtrl+X',
|
||||
},
|
||||
{
|
||||
label: 'Paste',
|
||||
click: () => this.pasteEvents(),
|
||||
enabled: hasClipboardEvents(),
|
||||
accelerator: 'CmdOrCtrl+V',
|
||||
},
|
||||
{
|
||||
label: 'Delete',
|
||||
click: () => this.deleteSelection(),
|
||||
accelerator: 'Delete',
|
||||
},
|
||||
{
|
||||
label: 'Toggle disabled',
|
||||
click: () => this.toggleDisabled(),
|
||||
enabled: this._selectionCanToggleDisabled(),
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Add New Event Below',
|
||||
click: () =>
|
||||
this.addNewEvent('BuiltinCommonInstructions::Standard'),
|
||||
},
|
||||
{
|
||||
label: 'Add Sub Event',
|
||||
click: () => this.addSubEvents(),
|
||||
enabled: this._selectionCanHaveSubEvents(),
|
||||
},
|
||||
{
|
||||
label: 'Add Other',
|
||||
submenu: this.state.allEventsMetadata.map(metadata => {
|
||||
return {
|
||||
label: metadata.fullName,
|
||||
click: () => this.addNewEvent(metadata.type),
|
||||
};
|
||||
}),
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Undo',
|
||||
click: this.undo,
|
||||
enabled: canUndo(this.state.history),
|
||||
accelerator: 'CmdOrCtrl+Z',
|
||||
},
|
||||
{
|
||||
label: 'Redo',
|
||||
click: this.redo,
|
||||
enabled: canRedo(this.state.history),
|
||||
accelerator: 'CmdOrCtrl+Shift+Z',
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Extract Events to a Function',
|
||||
click: () => this.extractEventsToFunction(),
|
||||
},
|
||||
{
|
||||
label: 'Analyze Objects Used in this Event',
|
||||
click: this._openEventsContextAnalyzer,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<ContextMenu
|
||||
ref={instructionContextMenu =>
|
||||
(this.instructionContextMenu = instructionContextMenu)
|
||||
}
|
||||
buildMenuTemplate={() => [
|
||||
{
|
||||
label: 'Copy',
|
||||
click: () => this.copySelection(),
|
||||
accelerator: 'CmdOrCtrl+C',
|
||||
},
|
||||
{
|
||||
label: 'Cut',
|
||||
click: () => this.cutSelection(),
|
||||
accelerator: 'CmdOrCtrl+X',
|
||||
},
|
||||
{
|
||||
label: 'Paste',
|
||||
click: () => this.pasteInstructions(),
|
||||
enabled:
|
||||
hasClipboardConditions() || hasClipboardActions(),
|
||||
accelerator: 'CmdOrCtrl+V',
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Delete',
|
||||
click: () => this.deleteSelection(),
|
||||
accelerator: 'Delete',
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Undo',
|
||||
click: this.undo,
|
||||
enabled: canUndo(this.state.history),
|
||||
accelerator: 'CmdOrCtrl+Z',
|
||||
},
|
||||
{
|
||||
label: 'Redo',
|
||||
click: this.redo,
|
||||
enabled: canRedo(this.state.history),
|
||||
accelerator: 'CmdOrCtrl+Shift+Z',
|
||||
},
|
||||
{
|
||||
label: 'Invert Condition',
|
||||
click: () => this._invertSelectedConditions(),
|
||||
visible: hasSelectedAtLeastOneCondition(
|
||||
this.state.selection
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<ContextMenu
|
||||
ref={instructionsListContextMenu =>
|
||||
(this.instructionsListContextMenu = instructionsListContextMenu)
|
||||
}
|
||||
buildMenuTemplate={() => [
|
||||
{
|
||||
label: 'Paste',
|
||||
click: () => this.pasteInstructions(),
|
||||
enabled:
|
||||
hasClipboardConditions() || hasClipboardActions(),
|
||||
accelerator: 'CmdOrCtrl+V',
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Undo',
|
||||
click: this.undo,
|
||||
enabled: canUndo(this.state.history),
|
||||
accelerator: 'CmdOrCtrl+Z',
|
||||
},
|
||||
{
|
||||
label: 'Redo',
|
||||
click: this.redo,
|
||||
enabled: canRedo(this.state.history),
|
||||
accelerator: 'CmdOrCtrl+Shift+Z',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
{this._renderInstructionEditorDialog(
|
||||
values.useNewInstructionEditorDialog
|
||||
)}
|
||||
{this.state.analyzedEventsContextResult && (
|
||||
<EventsContextAnalyzerDialog
|
||||
onClose={this._closeEventsContextAnalyzer}
|
||||
eventsContextResult={this.state.analyzedEventsContextResult}
|
||||
/>
|
||||
)}
|
||||
{this.state.serializedEventsToExtract && (
|
||||
<EventsFunctionExtractorDialog
|
||||
project={project}
|
||||
<ScreenTypeMeasurer>
|
||||
{screenType => (
|
||||
<ResponsiveWindowMeasurer>
|
||||
{windowWidth => (
|
||||
<PreferencesContext.Consumer>
|
||||
{({ values }) => (
|
||||
<EventsSearcher
|
||||
key={events.ptr}
|
||||
ref={eventSearcher => (this._eventSearcher = eventSearcher)}
|
||||
events={events}
|
||||
globalObjectsContainer={globalObjectsContainer}
|
||||
objectsContainer={objectsContainer}
|
||||
onClose={() =>
|
||||
this.setState({
|
||||
serializedEventsToExtract: null,
|
||||
})
|
||||
}
|
||||
serializedEvents={this.state.serializedEventsToExtract}
|
||||
onCreate={(extensionName, eventsFunction) => {
|
||||
this.props.onCreateEventsFunction(
|
||||
extensionName,
|
||||
eventsFunction
|
||||
);
|
||||
this._replaceSelectionByEventsFunction(
|
||||
extensionName,
|
||||
eventsFunction
|
||||
);
|
||||
this.setState({
|
||||
serializedEventsToExtract: null,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
selection={this.state.selection}
|
||||
>
|
||||
{({
|
||||
eventsSearchResultEvents,
|
||||
searchFocusOffset,
|
||||
searchInEvents,
|
||||
replaceInEvents,
|
||||
goToPreviousSearchResult,
|
||||
goToNextSearchResult,
|
||||
}) => (
|
||||
<div
|
||||
className="gd-events-sheet"
|
||||
style={styles.container}
|
||||
onFocus={() => this._keyboardShortcuts.focus()}
|
||||
onBlur={() => this._keyboardShortcuts.blur()}
|
||||
tabIndex={1}
|
||||
>
|
||||
<EventsTree
|
||||
ref={eventsTree => (this._eventsTree = eventsTree)}
|
||||
key={events.ptr}
|
||||
events={events}
|
||||
project={project}
|
||||
scope={scope}
|
||||
globalObjectsContainer={globalObjectsContainer}
|
||||
objectsContainer={objectsContainer}
|
||||
selection={this.state.selection}
|
||||
onInstructionClick={this.selectInstruction}
|
||||
onInstructionDoubleClick={this.openInstructionEditor}
|
||||
onInstructionContextMenu={
|
||||
this.openInstructionContextMenu
|
||||
}
|
||||
onInstructionsListContextMenu={
|
||||
this.openInstructionsListContextMenu
|
||||
}
|
||||
onAddNewInstruction={this.openInstructionEditor}
|
||||
onPasteInstructions={
|
||||
this.pasteInstructionsInInstructionsList
|
||||
}
|
||||
onMoveToInstruction={this.moveSelectionToInstruction}
|
||||
onMoveToInstructionsList={
|
||||
this.moveSelectionToInstructionsList
|
||||
}
|
||||
onParameterClick={this.openParameterEditor}
|
||||
onEventClick={this.selectEvent}
|
||||
onEventContextMenu={this.openEventContextMenu}
|
||||
onAddNewEvent={context => {
|
||||
this.addNewEvent(
|
||||
'BuiltinCommonInstructions::Standard',
|
||||
context
|
||||
);
|
||||
}}
|
||||
onOpenExternalEvents={onOpenExternalEvents}
|
||||
onOpenLayout={onOpenLayout}
|
||||
searchResults={eventsSearchResultEvents}
|
||||
searchFocusOffset={searchFocusOffset}
|
||||
onEventMoved={this._onEventMoved}
|
||||
showObjectThumbnails={
|
||||
values.eventsSheetShowObjectThumbnails
|
||||
}
|
||||
screenType={screenType}
|
||||
windowWidth={windowWidth}
|
||||
/>
|
||||
{this.state.showSearchPanel && (
|
||||
<SearchPanel
|
||||
ref={searchPanel =>
|
||||
(this._searchPanel = searchPanel)
|
||||
}
|
||||
onSearchInEvents={inputs =>
|
||||
this._searchInEvents(searchInEvents, inputs)
|
||||
}
|
||||
onReplaceInEvents={inputs =>
|
||||
this._replaceInEvents(replaceInEvents, inputs)
|
||||
}
|
||||
resultsCount={
|
||||
eventsSearchResultEvents
|
||||
? eventsSearchResultEvents.length
|
||||
: null
|
||||
}
|
||||
hasEventSelected={hasEventSelected(
|
||||
this.state.selection
|
||||
)}
|
||||
onGoToPreviousSearchResult={() =>
|
||||
this._ensureEventUnfolded(
|
||||
goToPreviousSearchResult
|
||||
)
|
||||
}
|
||||
onGoToNextSearchResult={() =>
|
||||
this._ensureEventUnfolded(goToNextSearchResult)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{events && events.getEventsCount() === 0 && (
|
||||
<EmptyEventsPlaceholder />
|
||||
)}
|
||||
<InlineParameterEditor
|
||||
open={this.state.inlineEditing}
|
||||
anchorEl={this.state.inlineEditingAnchorEl}
|
||||
onRequestClose={this.closeParameterEditor}
|
||||
project={project}
|
||||
scope={scope}
|
||||
globalObjectsContainer={globalObjectsContainer}
|
||||
objectsContainer={objectsContainer}
|
||||
isCondition={this.state.editedParameter.isCondition}
|
||||
instruction={this.state.editedParameter.instruction}
|
||||
parameterIndex={
|
||||
this.state.editedParameter.parameterIndex
|
||||
}
|
||||
onChange={value => {
|
||||
const {
|
||||
instruction,
|
||||
parameterIndex,
|
||||
} = this.state.editedParameter;
|
||||
if (!instruction) return;
|
||||
instruction.setParameter(parameterIndex, value);
|
||||
this.setState({
|
||||
inlineEditingChangesMade: true,
|
||||
});
|
||||
}}
|
||||
resourceSources={this.props.resourceSources}
|
||||
onChooseResource={this.props.onChooseResource}
|
||||
resourceExternalEditors={
|
||||
this.props.resourceExternalEditors
|
||||
}
|
||||
/>
|
||||
<ContextMenu
|
||||
ref={eventContextMenu =>
|
||||
(this.eventContextMenu = eventContextMenu)
|
||||
}
|
||||
buildMenuTemplate={() => [
|
||||
{
|
||||
label: 'Copy',
|
||||
click: () => this.copySelection(),
|
||||
accelerator: 'CmdOrCtrl+C',
|
||||
},
|
||||
{
|
||||
label: 'Cut',
|
||||
click: () => this.cutSelection(),
|
||||
accelerator: 'CmdOrCtrl+X',
|
||||
},
|
||||
{
|
||||
label: 'Paste',
|
||||
click: () => this.pasteEvents(),
|
||||
enabled: hasClipboardEvents(),
|
||||
accelerator: 'CmdOrCtrl+V',
|
||||
},
|
||||
{
|
||||
label: 'Delete',
|
||||
click: () => this.deleteSelection(),
|
||||
accelerator: 'Delete',
|
||||
},
|
||||
{
|
||||
label: 'Toggle disabled',
|
||||
click: () => this.toggleDisabled(),
|
||||
enabled: this._selectionCanToggleDisabled(),
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Add New Event Below',
|
||||
click: () =>
|
||||
this.addNewEvent(
|
||||
'BuiltinCommonInstructions::Standard'
|
||||
),
|
||||
},
|
||||
{
|
||||
label: 'Add Sub Event',
|
||||
click: () => this.addSubEvents(),
|
||||
enabled: this._selectionCanHaveSubEvents(),
|
||||
},
|
||||
{
|
||||
label: 'Add Other',
|
||||
submenu: this.state.allEventsMetadata.map(
|
||||
metadata => {
|
||||
return {
|
||||
label: metadata.fullName,
|
||||
click: () =>
|
||||
this.addNewEvent(metadata.type),
|
||||
};
|
||||
}
|
||||
),
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Undo',
|
||||
click: this.undo,
|
||||
enabled: canUndo(this.state.history),
|
||||
accelerator: 'CmdOrCtrl+Z',
|
||||
},
|
||||
{
|
||||
label: 'Redo',
|
||||
click: this.redo,
|
||||
enabled: canRedo(this.state.history),
|
||||
accelerator: 'CmdOrCtrl+Shift+Z',
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Extract Events to a Function',
|
||||
click: () => this.extractEventsToFunction(),
|
||||
},
|
||||
{
|
||||
label: 'Move Events into a Group',
|
||||
click: () => this.moveEventsIntoNewGroup(),
|
||||
},
|
||||
{
|
||||
label: 'Analyze Objects Used in this Event',
|
||||
click: this._openEventsContextAnalyzer,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<ContextMenu
|
||||
ref={instructionContextMenu =>
|
||||
(this.instructionContextMenu = instructionContextMenu)
|
||||
}
|
||||
buildMenuTemplate={() => [
|
||||
{
|
||||
label: 'Copy',
|
||||
click: () => this.copySelection(),
|
||||
accelerator: 'CmdOrCtrl+C',
|
||||
},
|
||||
{
|
||||
label: 'Cut',
|
||||
click: () => this.cutSelection(),
|
||||
accelerator: 'CmdOrCtrl+X',
|
||||
},
|
||||
{
|
||||
label: 'Paste',
|
||||
click: () => this.pasteInstructions(),
|
||||
enabled:
|
||||
hasClipboardConditions() ||
|
||||
hasClipboardActions(),
|
||||
accelerator: 'CmdOrCtrl+V',
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Delete',
|
||||
click: () => this.deleteSelection(),
|
||||
accelerator: 'Delete',
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Undo',
|
||||
click: this.undo,
|
||||
enabled: canUndo(this.state.history),
|
||||
accelerator: 'CmdOrCtrl+Z',
|
||||
},
|
||||
{
|
||||
label: 'Redo',
|
||||
click: this.redo,
|
||||
enabled: canRedo(this.state.history),
|
||||
accelerator: 'CmdOrCtrl+Shift+Z',
|
||||
},
|
||||
{
|
||||
label: 'Invert Condition',
|
||||
click: () => this._invertSelectedConditions(),
|
||||
visible: hasSelectedAtLeastOneCondition(
|
||||
this.state.selection
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<ContextMenu
|
||||
ref={instructionsListContextMenu =>
|
||||
(this.instructionsListContextMenu = instructionsListContextMenu)
|
||||
}
|
||||
buildMenuTemplate={() => [
|
||||
{
|
||||
label: 'Paste',
|
||||
click: () => this.pasteInstructions(),
|
||||
enabled:
|
||||
hasClipboardConditions() ||
|
||||
hasClipboardActions(),
|
||||
accelerator: 'CmdOrCtrl+V',
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Undo',
|
||||
click: this.undo,
|
||||
enabled: canUndo(this.state.history),
|
||||
accelerator: 'CmdOrCtrl+Z',
|
||||
},
|
||||
{
|
||||
label: 'Redo',
|
||||
click: this.redo,
|
||||
enabled: canRedo(this.state.history),
|
||||
accelerator: 'CmdOrCtrl+Shift+Z',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
{this._renderInstructionEditorDialog(
|
||||
// Force using the new instruction editor on touch screens.
|
||||
values.useNewInstructionEditorDialog ||
|
||||
screenType === 'touch'
|
||||
)}
|
||||
{this.state.analyzedEventsContextResult && (
|
||||
<EventsContextAnalyzerDialog
|
||||
onClose={this._closeEventsContextAnalyzer}
|
||||
eventsContextResult={
|
||||
this.state.analyzedEventsContextResult
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{this.state.serializedEventsToExtract && (
|
||||
<EventsFunctionExtractorDialog
|
||||
project={project}
|
||||
globalObjectsContainer={globalObjectsContainer}
|
||||
objectsContainer={objectsContainer}
|
||||
onClose={() =>
|
||||
this.setState({
|
||||
serializedEventsToExtract: null,
|
||||
})
|
||||
}
|
||||
serializedEvents={
|
||||
this.state.serializedEventsToExtract
|
||||
}
|
||||
onCreate={(extensionName, eventsFunction) => {
|
||||
this.props.onCreateEventsFunction(
|
||||
extensionName,
|
||||
eventsFunction
|
||||
);
|
||||
this._replaceSelectionByEventsFunction(
|
||||
extensionName,
|
||||
eventsFunction
|
||||
);
|
||||
this.setState({
|
||||
serializedEventsToExtract: null,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<InfoBar
|
||||
identifier="edit-instruction-explanation"
|
||||
message={
|
||||
<Trans>
|
||||
Double click on a condition or action to edit it.
|
||||
</Trans>
|
||||
}
|
||||
touchScreenMessage={
|
||||
<Trans>
|
||||
Double tap a condition or action to edit it. Long
|
||||
press to show more options.
|
||||
</Trans>
|
||||
}
|
||||
show={hasInstructionSelected(this.state.selection)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</EventsSearcher>
|
||||
)}
|
||||
</div>
|
||||
</PreferencesContext.Consumer>
|
||||
)}
|
||||
</EventsSearcher>
|
||||
</ResponsiveWindowMeasurer>
|
||||
)}
|
||||
</PreferencesContext.Consumer>
|
||||
</ScreenTypeMeasurer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import Dialog from '../../UI/Dialog';
|
||||
import FlatButton from '../../UI/FlatButton';
|
||||
import { showErrorBox } from '../../UI/Messages/MessageBox';
|
||||
import { Column, Line } from '../../UI/Grid';
|
||||
import Text from '../../UI/Text';
|
||||
|
||||
type Props = {|
|
||||
url: ?string,
|
||||
@@ -50,21 +51,23 @@ export default class BrowserPreviewLinkDialog extends Component<Props> {
|
||||
>
|
||||
<Line>
|
||||
<Column>
|
||||
<Trans>
|
||||
Your preview is ready! Click on the button to launch the
|
||||
preview.
|
||||
</Trans>
|
||||
<Text>
|
||||
<Trans>
|
||||
Your preview is ready! Click on the button to launch the
|
||||
preview.
|
||||
</Trans>
|
||||
</Text>
|
||||
</Column>
|
||||
</Line>
|
||||
<Line>
|
||||
<Column>
|
||||
<span>
|
||||
<Text>
|
||||
<Trans>
|
||||
To skip this dialog and{' '}
|
||||
<b>directly open the preview next time</b>, please allow
|
||||
popups to be opened for this website in your browser.
|
||||
</Trans>
|
||||
</span>
|
||||
</Text>
|
||||
</Column>
|
||||
</Line>
|
||||
</Dialog>
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
export default class DropHandler {
|
||||
constructor({ canvas, onDrop }) {
|
||||
canvas.ondragover = canvas.ondrop = ev => {
|
||||
ev.dataTransfer.dropEffect = 'copy';
|
||||
ev.preventDefault();
|
||||
};
|
||||
|
||||
canvas.ondrop = ev => {
|
||||
const canvasRect = canvas.getBoundingClientRect();
|
||||
const name = ev.dataTransfer.getData('text');
|
||||
if (name)
|
||||
onDrop(ev.clientX - canvasRect.left, ev.clientY - canvasRect.top, name);
|
||||
|
||||
ev.preventDefault();
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
// @flow
|
||||
import { roundPosition } from '../Utils/GridHelpers';
|
||||
const gd = global.gd;
|
||||
|
||||
type Props = {|
|
||||
instances: gdInitialInstancesContainer,
|
||||
options: Object,
|
||||
|};
|
||||
|
||||
const roundPositionsToGrid = (
|
||||
pos: [number, number],
|
||||
options: Object
|
||||
): [number, number] => {
|
||||
const newPos = pos;
|
||||
|
||||
if (options.grid && options.snap) {
|
||||
newPos[0] = roundPosition(
|
||||
newPos[0],
|
||||
options.gridWidth,
|
||||
options.gridOffsetX
|
||||
);
|
||||
newPos[1] = roundPosition(
|
||||
newPos[1],
|
||||
options.gridHeight,
|
||||
options.gridOffsetY
|
||||
);
|
||||
} else {
|
||||
newPos[0] = Math.round(newPos[0]);
|
||||
newPos[1] = Math.round(newPos[1]);
|
||||
}
|
||||
|
||||
return newPos;
|
||||
};
|
||||
|
||||
/**
|
||||
* Allow to add instances on the scene. Supports "temporary" instances,
|
||||
* which are real instances but can be deleted as long as they are not "committed".
|
||||
*/
|
||||
export default class InstancesAdder {
|
||||
_instances: gdInitialInstancesContainer;
|
||||
_temporaryInstances: Array<gdInitialInstance>;
|
||||
_options: Object;
|
||||
_zOrderFinder = new gd.HighestZOrderFinder();
|
||||
|
||||
constructor({ instances, options }: Props) {
|
||||
this._instances = instances;
|
||||
this._options = options;
|
||||
this._temporaryInstances = [];
|
||||
}
|
||||
|
||||
setOptions(options: Object) {
|
||||
this._options = options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Immediately create new instance at the specified position
|
||||
* (specified in scene coordinates).
|
||||
*/
|
||||
addInstances = (pos: [number, number], objectNames: Array<string>) => {
|
||||
this._instances.iterateOverInstances(this._zOrderFinder);
|
||||
const zOrder = this._zOrderFinder.getHighestZOrder() + 1;
|
||||
|
||||
const newPos = roundPositionsToGrid(pos, this._options);
|
||||
objectNames.map(objectName => {
|
||||
const instance: gdInitialInstance = this._instances.insertNewInitialInstance();
|
||||
instance.setObjectName(objectName);
|
||||
instance.setX(newPos[0]);
|
||||
instance.setY(newPos[1]);
|
||||
instance.setZOrder(zOrder);
|
||||
|
||||
return instance;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Create temporary instances at the specified position
|
||||
* (specified in scene coordinates).
|
||||
*/
|
||||
createOrUpdateTemporaryInstancesFromObjectNames = (
|
||||
pos: [number, number],
|
||||
objectNames: Array<string>
|
||||
) => {
|
||||
if (!objectNames.length) return;
|
||||
|
||||
if (!this._temporaryInstances.length) {
|
||||
this._createTemporaryInstancesFromObjectNames(pos, objectNames);
|
||||
} else {
|
||||
this.updateTemporaryInstancePositions(pos);
|
||||
}
|
||||
};
|
||||
|
||||
_createTemporaryInstancesFromObjectNames = (
|
||||
pos: [number, number],
|
||||
objectNames: Array<string>
|
||||
) => {
|
||||
this.deleteTemporaryInstances();
|
||||
|
||||
this._instances.iterateOverInstances(this._zOrderFinder);
|
||||
const zOrder = this._zOrderFinder.getHighestZOrder() + 1;
|
||||
|
||||
const newPos = roundPositionsToGrid(pos, this._options);
|
||||
this._temporaryInstances = objectNames.map(objectName => {
|
||||
const instance: gdInitialInstance = this._instances.insertNewInitialInstance();
|
||||
instance.setObjectName(objectName);
|
||||
instance.setX(newPos[0]);
|
||||
instance.setY(newPos[1]);
|
||||
instance.setZOrder(zOrder);
|
||||
|
||||
return instance;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the temporary instances positions
|
||||
* (specified in scene coordinates). Useful when dragging these instances.
|
||||
*/
|
||||
updateTemporaryInstancePositions = (pos: [number, number]) => {
|
||||
const newPos = roundPositionsToGrid(pos, this._options);
|
||||
this._temporaryInstances.forEach(instance => {
|
||||
instance.setX(Math.round(newPos[0]));
|
||||
instance.setY(Math.round(newPos[1]));
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete the temporary instances.
|
||||
*/
|
||||
deleteTemporaryInstances() {
|
||||
this._temporaryInstances.forEach(instance => {
|
||||
this._instances.removeInstance(instance);
|
||||
});
|
||||
this._temporaryInstances = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Consider the temporary instances as not temporary anymore.
|
||||
*/
|
||||
commitTemporaryInstances() {
|
||||
this._temporaryInstances = [];
|
||||
}
|
||||
|
||||
unmount() {
|
||||
this._zOrderFinder.delete();
|
||||
|
||||
// Nothing to do for temporaries instances, that should have been deleted/commited by this moment.
|
||||
// Don't take the risk to delete them now as this._instances might have been deleted/invalidated
|
||||
// already.
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import ObjectsRenderingService from '../../ObjectsRendering/ObjectsRenderingServ
|
||||
import getObjectByName from '../../Utils/GetObjectByName';
|
||||
|
||||
import * as PIXI from 'pixi.js';
|
||||
import { shouldBeHandledByPinch } from '../PinchHandler';
|
||||
const gd = global.gd;
|
||||
|
||||
export default class LayerRenderer {
|
||||
@@ -132,13 +133,21 @@ export default class LayerRenderer {
|
||||
renderedInstance._pixiObject.on('mousedown', () => {
|
||||
this.onDownInstance(instance);
|
||||
});
|
||||
renderedInstance._pixiObject.on('touchstart', () => {
|
||||
renderedInstance._pixiObject.on('touchstart', event => {
|
||||
if (shouldBeHandledByPinch(event.data && event.data.originalEvent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.onDownInstance(instance);
|
||||
});
|
||||
renderedInstance._pixiObject.on('mouseout', () => {
|
||||
this.onOutInstance(instance);
|
||||
});
|
||||
renderedInstance._pixiObject.on('panmove', event => {
|
||||
if (shouldBeHandledByPinch(event.data && event.data.originalEvent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.onMoveInstance(instance, event.deltaX, event.deltaY);
|
||||
});
|
||||
renderedInstance._pixiObject.on('panend', event => {
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
// @flow
|
||||
import ViewPosition from './ViewPosition';
|
||||
|
||||
export const shouldBeHandledByPinch = (event: ?TouchEvent) => {
|
||||
if (!event) return false;
|
||||
|
||||
const { targetTouches } = event;
|
||||
return targetTouches && targetTouches.length >= 2;
|
||||
};
|
||||
|
||||
type PinchDetectorArgs = {|
|
||||
canvas: HTMLCanvasElement,
|
||||
onPinchStart: () => void,
|
||||
onPinchMove: (x: number, y: number, scale: number) => void,
|
||||
onPinchEnd: () => void,
|
||||
|};
|
||||
|
||||
/**
|
||||
* Attach events to the canvas that will notify of a pinch at some position
|
||||
* and scaling associated with it.
|
||||
* Returns a function that can be called to remove these events.
|
||||
*/
|
||||
const registerCanvasPinchDetector = ({
|
||||
canvas,
|
||||
onPinchStart,
|
||||
onPinchMove,
|
||||
onPinchEnd,
|
||||
}: PinchDetectorArgs) => {
|
||||
let lastPinch = null;
|
||||
|
||||
function start() {
|
||||
canvas.addEventListener('touchmove', move);
|
||||
}
|
||||
|
||||
function move(e: TouchEvent) {
|
||||
if (!shouldBeHandledByPinch(e)) {
|
||||
return;
|
||||
}
|
||||
var t = e.targetTouches;
|
||||
|
||||
var dx = t[0].clientX - t[1].clientX;
|
||||
var dy = t[0].clientY - t[1].clientY;
|
||||
var distance = Math.sqrt(dx * dx + dy * dy);
|
||||
if (!lastPinch) {
|
||||
lastPinch = {
|
||||
p: {
|
||||
distance: distance,
|
||||
date: new Date(),
|
||||
},
|
||||
};
|
||||
onPinchStart();
|
||||
return;
|
||||
}
|
||||
var now = new Date();
|
||||
var interval = now - lastPinch.p.date;
|
||||
if (interval < 12) {
|
||||
return;
|
||||
}
|
||||
const newCenter = {
|
||||
x: (t[0].clientX + t[1].clientX) / 2,
|
||||
y: (t[0].clientY + t[1].clientY) / 2,
|
||||
};
|
||||
const scaleChange = distance / lastPinch.p.distance;
|
||||
|
||||
onPinchMove(newCenter.x, newCenter.y, scaleChange);
|
||||
lastPinch.p = {
|
||||
distance: distance,
|
||||
date: now,
|
||||
};
|
||||
}
|
||||
|
||||
function end(e: TouchEvent) {
|
||||
canvas.removeEventListener('touchmove', move);
|
||||
if (!lastPinch) {
|
||||
return;
|
||||
}
|
||||
|
||||
onPinchEnd();
|
||||
lastPinch = null;
|
||||
}
|
||||
|
||||
canvas.addEventListener('touchstart', start);
|
||||
canvas.addEventListener('touchend', end);
|
||||
|
||||
function unregisterCanvasPinchDetector() {
|
||||
canvas.removeEventListener('touchstart', start);
|
||||
canvas.removeEventListener('touchmove', move);
|
||||
canvas.removeEventListener('touchend', end);
|
||||
}
|
||||
|
||||
return unregisterCanvasPinchDetector;
|
||||
};
|
||||
|
||||
type Props = {|
|
||||
canvas: HTMLCanvasElement,
|
||||
setZoomFactor: number => void,
|
||||
getZoomFactor: () => number,
|
||||
viewPosition: ViewPosition,
|
||||
|};
|
||||
|
||||
/**
|
||||
* Handle pinch to zoom/move on the canvas, changing the zoom factor or view
|
||||
* position accordingly.
|
||||
*/
|
||||
export default class PinchHandler {
|
||||
_lastPinchCenterX: number | null = null;
|
||||
_lastPinchCenterY: number | null = null;
|
||||
_setZoomFactor: number => void;
|
||||
_getZoomFactor: () => number;
|
||||
_viewPosition: ViewPosition;
|
||||
_unregisterCanvasPinchDetector: () => void;
|
||||
|
||||
constructor({ canvas, setZoomFactor, getZoomFactor, viewPosition }: Props) {
|
||||
this._setZoomFactor = setZoomFactor;
|
||||
this._getZoomFactor = getZoomFactor;
|
||||
this._viewPosition = viewPosition;
|
||||
this._unregisterCanvasPinchDetector = registerCanvasPinchDetector({
|
||||
canvas,
|
||||
onPinchStart: this._startPinch,
|
||||
onPinchMove: this._onPinchMove,
|
||||
onPinchEnd: this._endPinch,
|
||||
});
|
||||
}
|
||||
|
||||
_startPinch = () => {
|
||||
// Nothing to do
|
||||
};
|
||||
|
||||
_onPinchMove = (centerX: number, centerY: number, scale: number) => {
|
||||
if (this._lastPinchCenterX === null || this._lastPinchCenterY === null) {
|
||||
this._lastPinchCenterX = centerX;
|
||||
this._lastPinchCenterY = centerY;
|
||||
return;
|
||||
}
|
||||
|
||||
const deltaX = centerX - this._lastPinchCenterX;
|
||||
const deltaY = centerY - this._lastPinchCenterY;
|
||||
const sceneDeltaX = deltaX / this._getZoomFactor();
|
||||
const sceneDeltaY = deltaY / this._getZoomFactor();
|
||||
this._viewPosition.scrollBy(-sceneDeltaX, -sceneDeltaY);
|
||||
this._lastPinchCenterX = centerX;
|
||||
this._lastPinchCenterY = centerY;
|
||||
|
||||
this._setZoomFactor(this._getZoomFactor() * scale);
|
||||
};
|
||||
|
||||
_endPinch = () => {
|
||||
this._lastPinchCenterX = null;
|
||||
this._lastPinchCenterY = null;
|
||||
};
|
||||
|
||||
unmount = () => {
|
||||
this._unregisterCanvasPinchDetector();
|
||||
};
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import React, { Component } from 'react';
|
||||
import Slider from '@material-ui/core/Slider';
|
||||
import ViewPosition from './ViewPosition';
|
||||
import throttle from 'lodash/throttle';
|
||||
import { ScreenTypeMeasurer } from '../UI/Reponsive/ScreenTypeMeasurer';
|
||||
|
||||
const styles = {
|
||||
container: {
|
||||
@@ -41,6 +42,8 @@ type State = {
|
||||
yMax: number,
|
||||
};
|
||||
|
||||
const noop = () => {};
|
||||
|
||||
/**
|
||||
* Add scrollbars that track the viewPosition of the wrapped component
|
||||
* @param {*} WrappedComponent
|
||||
@@ -134,36 +137,46 @@ export const addScrollbars = (WrappedComponent: any) => {
|
||||
const { wrappedEditorRef, ...otherProps } = this.props;
|
||||
|
||||
return (
|
||||
<div style={styles.container}>
|
||||
<WrappedComponent
|
||||
onViewPositionChanged={this._handleViewPositionChange}
|
||||
ref={(editorRef: ?ScrollableComponent) => {
|
||||
wrappedEditorRef && wrappedEditorRef(editorRef);
|
||||
this._editor = editorRef;
|
||||
}}
|
||||
{...otherProps}
|
||||
/>
|
||||
<Slider
|
||||
value={
|
||||
((this.state.xValue - this.state.xMin) /
|
||||
(this.state.xMax - this.state.xMin)) *
|
||||
100
|
||||
}
|
||||
onChange={this._handleXChange}
|
||||
style={styles.xScrollbar}
|
||||
orientation="horizontal"
|
||||
/>
|
||||
<Slider
|
||||
value={
|
||||
((this.state.yValue - this.state.yMin) /
|
||||
(this.state.yMax - this.state.yMin)) *
|
||||
100
|
||||
}
|
||||
onChange={this._handleYChange}
|
||||
style={styles.yScrollbar}
|
||||
orientation="vertical"
|
||||
/>
|
||||
</div>
|
||||
<ScreenTypeMeasurer>
|
||||
{screenType => (
|
||||
<div style={styles.container}>
|
||||
<WrappedComponent
|
||||
onViewPositionChanged={
|
||||
screenType !== 'touch' ? this._handleViewPositionChange : noop
|
||||
}
|
||||
ref={(editorRef: ?ScrollableComponent) => {
|
||||
wrappedEditorRef && wrappedEditorRef(editorRef);
|
||||
this._editor = editorRef;
|
||||
}}
|
||||
{...otherProps}
|
||||
/>
|
||||
{screenType !== 'touch' && (
|
||||
<Slider
|
||||
value={
|
||||
((this.state.xValue - this.state.xMin) /
|
||||
(this.state.xMax - this.state.xMin)) *
|
||||
100
|
||||
}
|
||||
onChange={this._handleXChange}
|
||||
style={styles.xScrollbar}
|
||||
orientation="horizontal"
|
||||
/>
|
||||
)}
|
||||
{screenType !== 'touch' && (
|
||||
<Slider
|
||||
value={
|
||||
((this.state.yValue - this.state.yMin) /
|
||||
(this.state.yMax - this.state.yMin)) *
|
||||
100
|
||||
}
|
||||
onChange={this._handleYChange}
|
||||
style={styles.yScrollbar}
|
||||
orientation="vertical"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</ScreenTypeMeasurer>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,15 +1,59 @@
|
||||
// @flow
|
||||
import gesture from 'pixi-simple-gesture';
|
||||
import transformRect from '../Utils/TransformRect';
|
||||
import * as PIXI from 'pixi.js';
|
||||
import { type ScreenType } from '../UI/Reponsive/ScreenTypeMeasurer';
|
||||
import InstancesSelection from '../SceneEditor/InstancesSelection';
|
||||
|
||||
const buttonSize = 10;
|
||||
const smallButtonSize = 8;
|
||||
const buttonPadding = 5;
|
||||
type Props = {|
|
||||
instancesSelection: InstancesSelection,
|
||||
instanceMeasurer: Object, // To be typed in InstancesRenderer
|
||||
onResize: (number, number) => void,
|
||||
onResizeEnd: () => void,
|
||||
onRotate: (number, number) => void,
|
||||
onRotateEnd: () => void,
|
||||
toCanvasCoordinates: (x: number, y: number) => [number, number],
|
||||
screenType: ScreenType,
|
||||
|};
|
||||
|
||||
const getButtonSizes = (screenType: ScreenType) => {
|
||||
if (screenType === 'touch') {
|
||||
return {
|
||||
buttonSize: 18,
|
||||
smallButtonSize: 13,
|
||||
buttonPadding: 5,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
buttonSize: 10,
|
||||
smallButtonSize: 8,
|
||||
buttonPadding: 5,
|
||||
};
|
||||
};
|
||||
|
||||
const RECTANGLE_BUTTON_SHAPE = 0;
|
||||
const CIRCLE_BUTTON_SHAPE = 1;
|
||||
|
||||
export default class InstancesSelection {
|
||||
export default class SelectedInstances {
|
||||
instancesSelection: InstancesSelection;
|
||||
instanceMeasurer: Object; // To be typed in InstancesRenderer
|
||||
onResize: (number, number) => void;
|
||||
onResizeEnd: () => void;
|
||||
onRotate: (number, number) => void;
|
||||
onRotateEnd: () => void;
|
||||
toCanvasCoordinates: (x: number, y: number) => [number, number];
|
||||
_screenType: ScreenType;
|
||||
|
||||
pixiContainer = new PIXI.Container();
|
||||
rectanglesContainer = new PIXI.Container();
|
||||
selectedRectangles = [];
|
||||
resizeButton = new PIXI.Graphics();
|
||||
resizeIcon = new PIXI.Sprite.fromImage('res/actions/direction.png');
|
||||
rightResizeButton = new PIXI.Graphics();
|
||||
bottomResizeButton = new PIXI.Graphics();
|
||||
rotateButton = new PIXI.Graphics();
|
||||
|
||||
constructor({
|
||||
instancesSelection,
|
||||
instanceMeasurer,
|
||||
@@ -18,7 +62,8 @@ export default class InstancesSelection {
|
||||
onRotate,
|
||||
onRotateEnd,
|
||||
toCanvasCoordinates,
|
||||
}) {
|
||||
screenType,
|
||||
}: Props) {
|
||||
this.instanceMeasurer = instanceMeasurer;
|
||||
this.onResize = onResize;
|
||||
this.onResizeEnd = onResizeEnd;
|
||||
@@ -26,17 +71,10 @@ export default class InstancesSelection {
|
||||
this.onRotateEnd = onRotateEnd;
|
||||
this.toCanvasCoordinates = toCanvasCoordinates;
|
||||
this.instancesSelection = instancesSelection;
|
||||
this._screenType = screenType;
|
||||
|
||||
this.pixiContainer = new PIXI.Container();
|
||||
this.rectanglesContainer = new PIXI.Container();
|
||||
this.selectedRectangles = [];
|
||||
this.pixiContainer.addChild(this.rectanglesContainer);
|
||||
|
||||
this.resizeButton = new PIXI.Graphics();
|
||||
this.resizeIcon = new PIXI.Sprite.fromImage('res/actions/direction.png');
|
||||
this.rightResizeButton = new PIXI.Graphics();
|
||||
this.bottomResizeButton = new PIXI.Graphics();
|
||||
this.rotateButton = new PIXI.Graphics();
|
||||
this._makeButton(
|
||||
this.resizeButton,
|
||||
event => {
|
||||
@@ -79,7 +117,16 @@ export default class InstancesSelection {
|
||||
);
|
||||
}
|
||||
|
||||
_makeButton(objectButton, onMove, onEnd, cursor) {
|
||||
setScreenType(screenType: ScreenType) {
|
||||
this._screenType = screenType;
|
||||
}
|
||||
|
||||
_makeButton(
|
||||
objectButton: PIXI.Graphics,
|
||||
onMove: (event: any) => void,
|
||||
onEnd: () => void,
|
||||
cursor: string
|
||||
) {
|
||||
objectButton.interactive = true;
|
||||
objectButton.buttonMode = true;
|
||||
objectButton.cursor = cursor;
|
||||
@@ -94,11 +141,11 @@ export default class InstancesSelection {
|
||||
}
|
||||
|
||||
_renderButton(
|
||||
show,
|
||||
buttonObject,
|
||||
canvasPosition,
|
||||
size,
|
||||
shape = RECTANGLE_BUTTON_SHAPE
|
||||
show: boolean,
|
||||
buttonObject: PIXI.Graphics,
|
||||
canvasPosition: [number, number],
|
||||
size: number,
|
||||
shape: 0 | 1 = RECTANGLE_BUTTON_SHAPE
|
||||
) {
|
||||
buttonObject.clear();
|
||||
if (!show) {
|
||||
@@ -129,11 +176,14 @@ export default class InstancesSelection {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { buttonSize, smallButtonSize, buttonPadding } = getButtonSizes(
|
||||
this._screenType
|
||||
);
|
||||
const selection = this.instancesSelection.getSelectedInstances();
|
||||
let x1;
|
||||
let y1;
|
||||
let x2;
|
||||
let y2;
|
||||
let x1 = 0;
|
||||
let y1 = 0;
|
||||
let x2 = 0;
|
||||
let y2 = 0;
|
||||
|
||||
//Update the selection rectangle of each instance
|
||||
for (var i = 0; i < selection.length; i++) {
|
||||
@@ -164,12 +214,19 @@ export default class InstancesSelection {
|
||||
);
|
||||
this.selectedRectangles[i].endFill();
|
||||
|
||||
if (x1 === undefined || instanceRect.x < x1) x1 = instanceRect.x;
|
||||
if (y1 === undefined || instanceRect.y < y1) y1 = instanceRect.y;
|
||||
if (x2 === undefined || instanceRect.x + instanceRect.width > x2)
|
||||
if (i === 0) {
|
||||
x1 = instanceRect.x;
|
||||
y1 = instanceRect.y;
|
||||
x2 = instanceRect.x + instanceRect.width;
|
||||
if (y2 === undefined || instanceRect.y + instanceRect.height > y2)
|
||||
y2 = instanceRect.y + instanceRect.height;
|
||||
} else {
|
||||
if (instanceRect.x < x1) x1 = instanceRect.x;
|
||||
if (instanceRect.y < y1) y1 = instanceRect.y;
|
||||
if (instanceRect.x + instanceRect.width > x2)
|
||||
x2 = instanceRect.x + instanceRect.width;
|
||||
if (instanceRect.y + instanceRect.height > y2)
|
||||
y2 = instanceRect.y + instanceRect.height;
|
||||
}
|
||||
}
|
||||
|
||||
while (this.selectedRectangles.length > selection.length) {
|
||||
@@ -187,17 +244,17 @@ export default class InstancesSelection {
|
||||
y1 + (y2 - y1) / 2
|
||||
);
|
||||
rightResizeButtonPos[0] += buttonPadding;
|
||||
rightResizeButtonPos[1] -= -smallButtonSize / 2;
|
||||
rightResizeButtonPos[1] -= smallButtonSize / 2;
|
||||
|
||||
const bottomResizeButtonPos = this.toCanvasCoordinates(
|
||||
x1 + (x2 - x1) / 2,
|
||||
y2
|
||||
);
|
||||
bottomResizeButtonPos[0] -= -smallButtonSize / 2;
|
||||
bottomResizeButtonPos[0] -= smallButtonSize / 2;
|
||||
bottomResizeButtonPos[1] += buttonPadding;
|
||||
|
||||
const rotateButtonPos = this.toCanvasCoordinates(x1 + (x2 - x1) / 2, y1);
|
||||
rotateButtonPos[0] -= -smallButtonSize / 2;
|
||||
rotateButtonPos[0] -= smallButtonSize / 2;
|
||||
rotateButtonPos[1] -= buttonPadding * 4;
|
||||
|
||||
this._renderButton(show, this.resizeButton, resizeButtonPos, buttonSize);
|
||||
|
||||
@@ -52,7 +52,12 @@ export default class SelectionRectangle {
|
||||
return this.selectionRectangleStart;
|
||||
}
|
||||
|
||||
makeSelectionRectangle = (lastX, lastY) => {
|
||||
startSelectionRectangle = (x, y) => {
|
||||
this.selectionRectangleStart = { x, y };
|
||||
this.selectionRectangleEnd = { x, y };
|
||||
};
|
||||
|
||||
updateSelectionRectangle = (lastX, lastY) => {
|
||||
if (!this.selectionRectangleStart)
|
||||
this.selectionRectangleStart = { x: lastX, y: lastY };
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import * as PIXI from 'pixi.js';
|
||||
|
||||
type Props = {
|
||||
getLastCursorPosition: () => [number, number],
|
||||
getLastCursorSceneCoordinates: () => [number, number],
|
||||
width: number,
|
||||
height: number,
|
||||
};
|
||||
@@ -10,13 +10,13 @@ type Props = {
|
||||
export default class StatusBar {
|
||||
_width: number;
|
||||
_height: number;
|
||||
_getLastCursorPosition: () => [number, number];
|
||||
_getLastCursorSceneCoordinates: () => [number, number];
|
||||
_statusBarContainer: PIXI.Container;
|
||||
_statusBarBackground: PIXI.Graphics;
|
||||
_statusBarText: PIXI.Text;
|
||||
|
||||
constructor({ getLastCursorPosition, width, height }: Props) {
|
||||
this._getLastCursorPosition = getLastCursorPosition;
|
||||
constructor({ getLastCursorSceneCoordinates, width, height }: Props) {
|
||||
this._getLastCursorSceneCoordinates = getLastCursorSceneCoordinates;
|
||||
this._statusBarContainer = new PIXI.Container();
|
||||
this._statusBarContainer.alpha = 0.8;
|
||||
this._statusBarContainer.hitArea = new PIXI.Rectangle(0, 0, 0, 0);
|
||||
@@ -43,7 +43,7 @@ export default class StatusBar {
|
||||
render() {
|
||||
const padding = 5;
|
||||
const borderRadius = 4;
|
||||
const [x, y] = this._getLastCursorPosition();
|
||||
const [x, y] = this._getLastCursorSceneCoordinates();
|
||||
this._statusBarText.text = `${x.toFixed(0)};${y.toFixed(0)}`;
|
||||
this._statusBarText.position.x = 0 + padding;
|
||||
this._statusBarText.position.y = Math.round(
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React, { Component } from 'react';
|
||||
import gesture from 'pixi-simple-gesture';
|
||||
import KeyboardShortcuts from '../UI/KeyboardShortcuts';
|
||||
import SimpleDropTarget from '../Utils/DragDropHelpers/SimpleDropTarget';
|
||||
import InstancesRenderer from './InstancesRenderer';
|
||||
import ViewPosition from './ViewPosition';
|
||||
import SelectedInstances from './SelectedInstances';
|
||||
@@ -13,30 +12,30 @@ import InstancesMover from './InstancesMover';
|
||||
import Grid from './Grid';
|
||||
import WindowBorder from './WindowBorder';
|
||||
import WindowMask from './WindowMask';
|
||||
import DropHandler from './DropHandler';
|
||||
import BackgroundColor from './BackgroundColor';
|
||||
import * as PIXI from 'pixi.js';
|
||||
import FpsLimiter from './FpsLimiter';
|
||||
import { startPIXITicker, stopPIXITicker } from '../Utils/PIXITicker';
|
||||
import StatusBar from './StatusBar';
|
||||
import CanvasCursor from './CanvasCursor';
|
||||
import InstancesAdder from './InstancesAdder';
|
||||
import { makeDropTarget } from '../UI/DragAndDrop/DropTarget';
|
||||
import { objectWithContextReactDndType } from '../ObjectsList';
|
||||
import PinchHandler, { shouldBeHandledByPinch } from './PinchHandler';
|
||||
|
||||
const styles = {
|
||||
canvasArea: { flex: 1, position: 'absolute', overflow: 'hidden' },
|
||||
dropCursor: { cursor: 'copy' },
|
||||
};
|
||||
|
||||
const DropTarget = makeDropTarget(objectWithContextReactDndType);
|
||||
|
||||
export default class InstancesEditorContainer extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.lastContextMenuX = 0;
|
||||
this.lastContextMenuY = 0;
|
||||
this.lastCursorX = 0;
|
||||
this.lastCursorY = 0;
|
||||
|
||||
this.fpsLimiter = new FpsLimiter(28);
|
||||
}
|
||||
lastContextMenuX = 0;
|
||||
lastContextMenuY = 0;
|
||||
lastCursorX = 0;
|
||||
lastCursorY = 0;
|
||||
fpsLimiter = new FpsLimiter(28);
|
||||
|
||||
componentDidMount() {
|
||||
// Initialize the PIXI renderer, if possible
|
||||
@@ -76,15 +75,6 @@ export default class InstancesEditorContainer extends Component {
|
||||
|
||||
return false;
|
||||
});
|
||||
this.pixiRenderer.view.addEventListener('pointerup', event => {
|
||||
this.props.onPointerUp();
|
||||
});
|
||||
this.pixiRenderer.view.addEventListener('pointerover', event => {
|
||||
this.props.onPointerOver();
|
||||
});
|
||||
this.pixiRenderer.view.addEventListener('pointerout', event => {
|
||||
this.props.onPointerOut();
|
||||
});
|
||||
this.pixiRenderer.view.onmousewheel = event => {
|
||||
if (this.keyboardShortcuts.shouldZoom()) {
|
||||
this.zoomBy(event.wheelDelta / 5000);
|
||||
@@ -126,9 +116,13 @@ export default class InstancesEditorContainer extends Component {
|
||||
this.backgroundArea.on('mousedown', event =>
|
||||
this._onBackgroundClicked(event.data.global.x, event.data.global.y)
|
||||
);
|
||||
this.backgroundArea.on('touchstart', event =>
|
||||
this._onBackgroundClicked(event.data.global.x, event.data.global.y)
|
||||
);
|
||||
this.backgroundArea.on('touchstart', event => {
|
||||
if (shouldBeHandledByPinch(event.data && event.data.originalEvent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._onBackgroundClicked(event.data.global.x, event.data.global.y);
|
||||
});
|
||||
this.backgroundArea.on('mousemove', event =>
|
||||
this._onMouseMove(event.data.global.x, event.data.global.y)
|
||||
);
|
||||
@@ -170,9 +164,11 @@ export default class InstancesEditorContainer extends Component {
|
||||
onZoomIn: this.props.onZoomIn,
|
||||
});
|
||||
|
||||
this.dropHandler = new DropHandler({
|
||||
canvas: this.canvasArea,
|
||||
onDrop: this._onDrop,
|
||||
this.pinchHandler = new PinchHandler({
|
||||
canvas: this.pixiRenderer.view,
|
||||
setZoomFactor: this.setZoomFactor,
|
||||
getZoomFactor: this.getZoomFactor,
|
||||
viewPosition: this.viewPosition,
|
||||
});
|
||||
|
||||
this.canvasCursor = new CanvasCursor({
|
||||
@@ -180,6 +176,11 @@ export default class InstancesEditorContainer extends Component {
|
||||
shouldMoveView: () => this.keyboardShortcuts.shouldMoveView(),
|
||||
});
|
||||
|
||||
this._instancesAdder = new InstancesAdder({
|
||||
instances: this.props.initialInstances,
|
||||
options: this.props.options,
|
||||
});
|
||||
|
||||
this._mountEditorComponents(this.props);
|
||||
this._renderScene();
|
||||
}
|
||||
@@ -251,6 +252,7 @@ export default class InstancesEditorContainer extends Component {
|
||||
onRotateEnd: this._onRotateEnd,
|
||||
instanceMeasurer: this.instancesRenderer.getInstanceMeasurer(),
|
||||
toCanvasCoordinates: this.viewPosition.toCanvasCoordinates,
|
||||
screenType: this.props.screenType,
|
||||
});
|
||||
this.highlightedInstance = new HighlightedInstance({
|
||||
instanceMeasurer: this.instancesRenderer.getInstanceMeasurer(),
|
||||
@@ -278,7 +280,7 @@ export default class InstancesEditorContainer extends Component {
|
||||
this.statusBar = new StatusBar({
|
||||
width: this.props.width,
|
||||
height: this.props.height,
|
||||
getLastCursorPosition: this.getLastCursorPosition,
|
||||
getLastCursorSceneCoordinates: this.getLastCursorSceneCoordinates,
|
||||
});
|
||||
|
||||
this.pixiContainer.addChild(this.selectionRectangle.getPixiObject());
|
||||
@@ -300,6 +302,8 @@ export default class InstancesEditorContainer extends Component {
|
||||
this.keyboardShortcuts.unmount();
|
||||
this.selectionRectangle.delete();
|
||||
this.instancesRenderer.delete();
|
||||
this._instancesAdder.unmount();
|
||||
this.pinchHandler.unmount();
|
||||
if (this.nextFrame) cancelAnimationFrame(this.nextFrame);
|
||||
stopPIXITicker();
|
||||
}
|
||||
@@ -330,6 +334,11 @@ export default class InstancesEditorContainer extends Component {
|
||||
this.instancesResizer.setOptions(nextProps.options);
|
||||
this.windowMask.setOptions(nextProps.options);
|
||||
this.viewPosition.setOptions(nextProps.options);
|
||||
this._instancesAdder.setOptions(nextProps.options);
|
||||
}
|
||||
|
||||
if (nextProps.screenType !== this.props.screenType) {
|
||||
this.selectedInstances.setScreenType(this.props.screenType);
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -365,15 +374,26 @@ export default class InstancesEditorContainer extends Component {
|
||||
this.setZoomFactor(this.getZoomFactor() + value);
|
||||
}
|
||||
|
||||
getZoomFactor() {
|
||||
getZoomFactor = () => {
|
||||
return this.props.options.zoomFactor;
|
||||
}
|
||||
};
|
||||
|
||||
setZoomFactor(zoomFactor) {
|
||||
setZoomFactor = zoomFactor => {
|
||||
this.props.onChangeOptions({
|
||||
zoomFactor: Math.max(Math.min(zoomFactor, 10), 0.01),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Immediately add instances for the specified objects at the given
|
||||
* position (in scene coordinates).
|
||||
*/
|
||||
addInstances = (
|
||||
pos /*: [number, number] */,
|
||||
objectNames /*: Array<string> */
|
||||
) => {
|
||||
this._instancesAdder.addInstances(pos, objectNames);
|
||||
};
|
||||
|
||||
_onMouseMove = (x, y) => {
|
||||
this.lastCursorX = x;
|
||||
@@ -385,6 +405,13 @@ export default class InstancesEditorContainer extends Component {
|
||||
this.lastCursorY = y;
|
||||
this.pixiRenderer.view.focus();
|
||||
|
||||
// Selection rectangle is only drawn in _onPanMove,
|
||||
// which can happen a few milliseconds after a background
|
||||
// click/touch - enough to have the selection rectangle being
|
||||
// offset from the first click - which looks laggy. Set
|
||||
// the start position now.
|
||||
this.selectionRectangle.startSelectionRectangle(x, y);
|
||||
|
||||
if (
|
||||
!this.keyboardShortcuts.shouldMultiSelect() &&
|
||||
!this.keyboardShortcuts.shouldMoveView()
|
||||
@@ -405,7 +432,7 @@ export default class InstancesEditorContainer extends Component {
|
||||
this.props.onViewPositionChanged(this.viewPosition);
|
||||
}
|
||||
} else {
|
||||
this.selectionRectangle.makeSelectionRectangle(x, y);
|
||||
this.selectionRectangle.updateSelectionRectangle(x, y);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -554,13 +581,6 @@ export default class InstancesEditorContainer extends Component {
|
||||
this.props.onInstancesRotated(selectedInstances);
|
||||
};
|
||||
|
||||
_onDrop = (x, y, objectName) => {
|
||||
const newPos = this.viewPosition.toSceneCoordinates(x, y);
|
||||
if (this.props.onAddInstance) {
|
||||
this.props.onAddInstance(newPos[0], newPos[1], objectName);
|
||||
}
|
||||
};
|
||||
|
||||
clearHighlightedInstance = () => {
|
||||
this.highlightedInstance.setInstance(null);
|
||||
};
|
||||
@@ -593,14 +613,14 @@ export default class InstancesEditorContainer extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
getLastContextMenuPosition = () => {
|
||||
getLastContextMenuSceneCoordinates = () => {
|
||||
return this.viewPosition.toSceneCoordinates(
|
||||
this.lastContextMenuX,
|
||||
this.lastContextMenuY
|
||||
);
|
||||
};
|
||||
|
||||
getLastCursorPosition = () => {
|
||||
getLastCursorSceneCoordinates = () => {
|
||||
return this.viewPosition.toSceneCoordinates(
|
||||
this.lastCursorX,
|
||||
this.lastCursorY
|
||||
@@ -652,15 +672,64 @@ export default class InstancesEditorContainer extends Component {
|
||||
if (!this.props.project) return null;
|
||||
|
||||
return (
|
||||
<SimpleDropTarget>
|
||||
<div
|
||||
ref={canvasArea => (this.canvasArea = canvasArea)}
|
||||
style={{
|
||||
...styles.canvasArea,
|
||||
...(this.props.showDropCursor ? styles.dropCursor : undefined),
|
||||
}}
|
||||
/>
|
||||
</SimpleDropTarget>
|
||||
<DropTarget
|
||||
canDrop={() => true}
|
||||
hover={monitor => {
|
||||
const { _instancesAdder, viewPosition, canvasArea } = this;
|
||||
if (!_instancesAdder || !canvasArea || !viewPosition) return;
|
||||
|
||||
const { x, y } = monitor.getClientOffset();
|
||||
const canvasRect = canvasArea.getBoundingClientRect();
|
||||
const pos = viewPosition.toSceneCoordinates(
|
||||
x - canvasRect.left,
|
||||
y - canvasRect.top
|
||||
);
|
||||
_instancesAdder.createOrUpdateTemporaryInstancesFromObjectNames(
|
||||
pos,
|
||||
this.props.selectedObjectNames
|
||||
);
|
||||
}}
|
||||
drop={monitor => {
|
||||
const { _instancesAdder, viewPosition, canvasArea } = this;
|
||||
if (!_instancesAdder || !canvasArea || !viewPosition) return;
|
||||
|
||||
if (monitor.didDrop()) {
|
||||
// Drop was done somewhere else (in a child of the canvas:
|
||||
// should not happen, but still handling this case).
|
||||
_instancesAdder.deleteTemporaryInstances();
|
||||
return;
|
||||
}
|
||||
|
||||
const { x, y } = monitor.getClientOffset();
|
||||
const canvasRect = canvasArea.getBoundingClientRect();
|
||||
const pos = viewPosition.toSceneCoordinates(
|
||||
x - canvasRect.left,
|
||||
y - canvasRect.top
|
||||
);
|
||||
_instancesAdder.updateTemporaryInstancePositions(pos);
|
||||
_instancesAdder.commitTemporaryInstances();
|
||||
this.props.onInstancesAdded();
|
||||
}}
|
||||
>
|
||||
{({ connectDropTarget, isOver }) => {
|
||||
// The children are re-rendered when isOver change:
|
||||
// take this opportunity to delete any temporary instances
|
||||
// if the dragging is not done anymore over the canvas.
|
||||
if (this._instancesAdder && !isOver) {
|
||||
this._instancesAdder.deleteTemporaryInstances();
|
||||
}
|
||||
|
||||
return connectDropTarget(
|
||||
<div
|
||||
ref={canvasArea => (this.canvasArea = canvasArea)}
|
||||
style={{
|
||||
...styles.canvasArea,
|
||||
...(this.props.showDropCursor ? styles.dropCursor : undefined),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</DropTarget>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,10 @@ import Dialog from '../UI/Dialog';
|
||||
import Window from '../Utils/Window';
|
||||
import FlatButton from '../UI/FlatButton';
|
||||
import Text from '../UI/Text';
|
||||
import { ResponsiveWindowMeasurer } from '../UI/Reponsive/ResponsiveWindowMeasurer';
|
||||
import Fullscreen from '@material-ui/icons/Fullscreen';
|
||||
import RaisedButton from '../UI/RaisedButton';
|
||||
import { Spacer, Line } from '../UI/Grid';
|
||||
|
||||
export default class BetaIntroDialog extends Component {
|
||||
_onOpenWebsite() {
|
||||
@@ -12,42 +16,85 @@ export default class BetaIntroDialog extends Component {
|
||||
|
||||
render() {
|
||||
const { open, onClose } = this.props;
|
||||
const actions = [
|
||||
<FlatButton
|
||||
key="download"
|
||||
label={<Trans>Download full GDevelop desktop version</Trans>}
|
||||
primary={false}
|
||||
onClick={this._onOpenWebsite}
|
||||
/>,
|
||||
<FlatButton
|
||||
label={<Trans>Ok</Trans>}
|
||||
primary={true}
|
||||
onClick={onClose}
|
||||
key="close"
|
||||
/>,
|
||||
];
|
||||
|
||||
return (
|
||||
<Dialog actions={actions} open={open} onRequestClose={onClose}>
|
||||
<div>
|
||||
<Text>
|
||||
<Trans>
|
||||
This is a version of GDevelop 5 that you can try online.
|
||||
</Trans>
|
||||
</Text>
|
||||
<Text>
|
||||
Choose a <b>new project to create</b> and then <b>open the scene</b>{' '}
|
||||
to make changes to the game. You can{' '}
|
||||
<b>launch a preview of your game</b> at any time!
|
||||
</Text>
|
||||
<Text>
|
||||
<Trans>
|
||||
Download the full version of GDevelop to create your own game
|
||||
without limits!
|
||||
</Trans>
|
||||
</Text>
|
||||
</div>
|
||||
</Dialog>
|
||||
<ResponsiveWindowMeasurer>
|
||||
{windowWidth => (
|
||||
<Dialog
|
||||
title={<Trans>Welcome to GDevelop</Trans>}
|
||||
actions={[
|
||||
<FlatButton
|
||||
key="download"
|
||||
label={<Trans>Download GDevelop desktop app</Trans>}
|
||||
primary={false}
|
||||
onClick={this._onOpenWebsite}
|
||||
/>,
|
||||
<FlatButton
|
||||
label={<Trans>Close</Trans>}
|
||||
primary={false}
|
||||
onClick={onClose}
|
||||
key="close"
|
||||
/>,
|
||||
]}
|
||||
open={open}
|
||||
onRequestClose={onClose}
|
||||
>
|
||||
<div>
|
||||
<Text>
|
||||
<Trans>
|
||||
This is a version of GDevelop 5 that you can try online.
|
||||
</Trans>
|
||||
</Text>
|
||||
<Text>
|
||||
Choose a <b>new project to create</b>, then edit the scene or
|
||||
the events ruling the game. You can{' '}
|
||||
<b>launch a preview of your game</b> at any time.
|
||||
</Text>
|
||||
<Text>
|
||||
<Trans>
|
||||
Download the full version of GDevelop on your desktop computer
|
||||
to create your own game without limits!
|
||||
</Trans>
|
||||
</Text>
|
||||
{windowWidth === 'small' &&
|
||||
(!Window.isFullscreen() ? (
|
||||
<React.Fragment>
|
||||
<Spacer />
|
||||
<Text>
|
||||
<Trans>
|
||||
You're working on a small screen. It's recommended to
|
||||
activate Fullscreen Mode for using GDevelop.
|
||||
</Trans>
|
||||
</Text>
|
||||
<Line justifyContent="center">
|
||||
<RaisedButton
|
||||
label={<Trans>Activate Fullscreen</Trans>}
|
||||
primary
|
||||
onClick={() => {
|
||||
Window.requestFullscreen();
|
||||
setTimeout(() => {
|
||||
this.forceUpdate();
|
||||
}, 250 /* Let a bit of time for the fullscreen to kick in */);
|
||||
}}
|
||||
labelPosition="before"
|
||||
icon={<Fullscreen />}
|
||||
/>
|
||||
</Line>
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<Line justifyContent="center">
|
||||
<RaisedButton
|
||||
label={<Trans>Start using GDevelop</Trans>}
|
||||
primary
|
||||
onClick={onClose}
|
||||
labelPosition="before"
|
||||
/>
|
||||
</Line>
|
||||
))}
|
||||
</div>
|
||||
</Dialog>
|
||||
)}
|
||||
</ResponsiveWindowMeasurer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
html {
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
#root {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
|
||||
@@ -14,7 +14,9 @@ export type AlertMessageIdentifier =
|
||||
| 'resource-properties-panel-explanation'
|
||||
| 'instance-drag-n-drop-explanation'
|
||||
| 'objects-panel-explanation'
|
||||
| 'instance-properties-panel-explanation';
|
||||
| 'instance-properties-panel-explanation'
|
||||
| 'physics2-shape-collisions'
|
||||
| 'edit-instruction-explanation';
|
||||
|
||||
export type PreferencesValues = {|
|
||||
language: string,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import DragDropContextProvider from '../Utils/DragDropHelpers/DragDropContextProvider';
|
||||
import DragAndDropContextProvider from '../UI/DragAndDrop/DragAndDropContextProvider';
|
||||
import { ThemeProvider } from '@material-ui/styles';
|
||||
import { getTheme } from '../UI/Theme';
|
||||
import UserProfileProvider from '../Profile/UserProfileProvider';
|
||||
@@ -48,7 +48,7 @@ export default class Providers extends React.Component<Props, {||}> {
|
||||
eventsFunctionsExtensionOpener,
|
||||
} = this.props;
|
||||
return (
|
||||
<DragDropContextProvider>
|
||||
<DragAndDropContextProvider>
|
||||
<PreferencesProvider disableCheckForUpdates={disableCheckForUpdates}>
|
||||
<PreferencesContext.Consumer>
|
||||
{({ values }) => {
|
||||
@@ -91,7 +91,7 @@ export default class Providers extends React.Component<Props, {||}> {
|
||||
}}
|
||||
</PreferencesContext.Consumer>
|
||||
</PreferencesProvider>
|
||||
</DragDropContextProvider>
|
||||
</DragAndDropContextProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import ToolbarSeparator from '../UI/ToolbarSeparator';
|
||||
import ElementWithMenu from '../UI/Menu/ElementWithMenu';
|
||||
import Window from '../Utils/Window';
|
||||
import { I18n } from '@lingui/react';
|
||||
import { ResponsiveWindowMeasurer } from '../UI/Reponsive/ResponsiveWindowMeasurer';
|
||||
|
||||
type Props = {
|
||||
showProjectIcons: boolean,
|
||||
@@ -37,59 +38,66 @@ export class MainFrameToolbar extends PureComponent<Props, State> {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<Toolbar>
|
||||
<ToolbarGroup firstChild>
|
||||
{this.props.showProjectIcons && (
|
||||
<ToolbarIcon
|
||||
onClick={this.props.toggleProjectManager}
|
||||
src="res/ribbon_default/projectManager32.png"
|
||||
disabled={!this.props.hasProject}
|
||||
tooltip={t`Project manager`}
|
||||
/>
|
||||
)}
|
||||
{this.props.showProjectIcons && (
|
||||
<ToolbarIcon
|
||||
onClick={this.props.exportProject}
|
||||
src="res/ribbon_default/export32.png"
|
||||
disabled={!this.props.hasProject}
|
||||
tooltip={t`Export the game (Web, Android, iOS...)`}
|
||||
/>
|
||||
)}
|
||||
{this.isDev && (
|
||||
<ElementWithMenu
|
||||
element={<ToolbarIcon src="res/ribbon_default/bug32.png" />}
|
||||
buildMenuTemplate={() => [
|
||||
{
|
||||
label: 'Request update from external editor',
|
||||
disabled: !this.props.requestUpdate,
|
||||
click: () =>
|
||||
this.props.requestUpdate && this.props.requestUpdate(),
|
||||
},
|
||||
{
|
||||
label: 'Simulate update downloaded',
|
||||
disabled: !this.props.simulateUpdateDownloaded,
|
||||
click: () =>
|
||||
this.props.simulateUpdateDownloaded &&
|
||||
this.props.simulateUpdateDownloaded(),
|
||||
},
|
||||
{
|
||||
label: 'Simulate update available',
|
||||
disabled: !this.props.simulateUpdateAvailable,
|
||||
click: () =>
|
||||
this.props.simulateUpdateAvailable &&
|
||||
this.props.simulateUpdateAvailable(),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
<ToolbarSeparator />
|
||||
</ToolbarGroup>
|
||||
{this.state.editorToolbar || <ToolbarGroup />}
|
||||
</Toolbar>
|
||||
<ResponsiveWindowMeasurer>
|
||||
{windowWidth => (
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<Toolbar>
|
||||
<ToolbarGroup firstChild>
|
||||
{this.props.showProjectIcons && (
|
||||
<ToolbarIcon
|
||||
onClick={this.props.toggleProjectManager}
|
||||
src="res/ribbon_default/projectManager32.png"
|
||||
disabled={!this.props.hasProject}
|
||||
tooltip={t`Project manager`}
|
||||
/>
|
||||
)}
|
||||
{this.props.showProjectIcons && windowWidth !== 'small' && (
|
||||
<ToolbarIcon
|
||||
onClick={this.props.exportProject}
|
||||
src="res/ribbon_default/export32.png"
|
||||
disabled={!this.props.hasProject}
|
||||
tooltip={t`Export the game (Web, Android, iOS...)`}
|
||||
/>
|
||||
)}
|
||||
{this.isDev && (
|
||||
<ElementWithMenu
|
||||
element={
|
||||
<ToolbarIcon src="res/ribbon_default/bug32.png" />
|
||||
}
|
||||
buildMenuTemplate={() => [
|
||||
{
|
||||
label: 'Request update from external editor',
|
||||
disabled: !this.props.requestUpdate,
|
||||
click: () =>
|
||||
this.props.requestUpdate &&
|
||||
this.props.requestUpdate(),
|
||||
},
|
||||
{
|
||||
label: 'Simulate update downloaded',
|
||||
disabled: !this.props.simulateUpdateDownloaded,
|
||||
click: () =>
|
||||
this.props.simulateUpdateDownloaded &&
|
||||
this.props.simulateUpdateDownloaded(),
|
||||
},
|
||||
{
|
||||
label: 'Simulate update available',
|
||||
disabled: !this.props.simulateUpdateAvailable,
|
||||
click: () =>
|
||||
this.props.simulateUpdateAvailable &&
|
||||
this.props.simulateUpdateAvailable(),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
<ToolbarSeparator />
|
||||
</ToolbarGroup>
|
||||
{this.state.editorToolbar || <ToolbarGroup />}
|
||||
</Toolbar>
|
||||
)}
|
||||
</I18n>
|
||||
)}
|
||||
</I18n>
|
||||
</ResponsiveWindowMeasurer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +83,8 @@ import LanguageDialog from './Preferences/LanguageDialog';
|
||||
import PreferencesContext from './Preferences/PreferencesContext';
|
||||
import { getFunctionNameFromType } from '../EventsFunctionsExtensionsLoader';
|
||||
import { type ExportDialogWithoutExportsProps } from '../Export/ExportDialog';
|
||||
import { getStartupTimesSummary } from '../Utils/StartupTimes';
|
||||
const GD_STARTUP_TIMES = global.GD_STARTUP_TIMES || [];
|
||||
|
||||
const gd = global.gd;
|
||||
|
||||
@@ -184,6 +186,8 @@ class MainFrame extends React.Component<Props, State> {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
GD_STARTUP_TIMES.push(['MainFrameComponentDidMount', performance.now()]);
|
||||
|
||||
const { initialPathsOrURLsToOpen } = this.props;
|
||||
|
||||
this._loadExtensions();
|
||||
@@ -193,6 +197,12 @@ class MainFrame extends React.Component<Props, State> {
|
||||
);
|
||||
} else if (this.props.introDialog && !Window.isDev())
|
||||
this._openIntroDialog(true);
|
||||
|
||||
GD_STARTUP_TIMES.push([
|
||||
'MainFrameComponentDidMountFinished',
|
||||
performance.now(),
|
||||
]);
|
||||
console.info('Startup times:', getStartupTimesSummary());
|
||||
}
|
||||
|
||||
_languageDidChange() {
|
||||
@@ -824,7 +834,6 @@ class MainFrame extends React.Component<Props, State> {
|
||||
}
|
||||
onOpenDebugger={this.openDebugger}
|
||||
onEditObject={this.props.onEditObject}
|
||||
showObjectsList={!this.props.integratedEditor}
|
||||
resourceSources={this.props.resourceSources}
|
||||
onChooseResource={this._onChooseResource}
|
||||
resourceExternalEditors={this.props.resourceExternalEditors}
|
||||
@@ -959,7 +968,6 @@ class MainFrame extends React.Component<Props, State> {
|
||||
}
|
||||
onOpenDebugger={this.openDebugger}
|
||||
onEditObject={this.props.onEditObject}
|
||||
showObjectsList={!this.props.integratedEditor}
|
||||
resourceSources={this.props.resourceSources}
|
||||
onChooseResource={this._onChooseResource}
|
||||
resourceExternalEditors={this.props.resourceExternalEditors}
|
||||
|
||||
@@ -53,7 +53,7 @@ export default {
|
||||
);
|
||||
this.editorConfigurations[objectType] = editorConfiguration;
|
||||
},
|
||||
getDefaultObjectJsImplementationPropertiesEditor() {
|
||||
getDefaultObjectJsImplementationPropertiesEditor(options) {
|
||||
return {
|
||||
component: ObjectPropertiesEditor,
|
||||
createNewObject: object =>
|
||||
@@ -62,6 +62,7 @@ export default {
|
||||
.clone()
|
||||
.release(),
|
||||
castToObjectType: object => gd.asObjectJsImplementation(object),
|
||||
helpPagePath: options.helpPagePath,
|
||||
};
|
||||
},
|
||||
editorConfigurations: {
|
||||
|
||||
@@ -20,6 +20,10 @@ import {
|
||||
} from '../ObjectsList/EnumerateObjects';
|
||||
import { listItemWithoutIconHeight } from '../UI/List';
|
||||
|
||||
// TODO: This component should be updated to be implemented using SortableVirtualizedItemList,
|
||||
// so that drag'n'drop is identical to other lists (and to benefit from better typing + future improvements
|
||||
// in SortableVirtualizedItemList).
|
||||
|
||||
const styles = {
|
||||
listContainer: {
|
||||
flex: 1,
|
||||
|
||||
@@ -26,6 +26,17 @@ export type GroupWithContext = {|
|
||||
export type ObjectWithContextList = Array<ObjectWithContext>;
|
||||
export type GroupWithContextList = Array<GroupWithContext>;
|
||||
|
||||
export const isSameObjectWithContext = (
|
||||
objectWithContext: ?ObjectWithContext
|
||||
) => (other: ?ObjectWithContext) => {
|
||||
return (
|
||||
objectWithContext &&
|
||||
other &&
|
||||
objectWithContext.global === other.global &&
|
||||
objectWithContext.object === other.object
|
||||
);
|
||||
};
|
||||
|
||||
export const enumerateObjects = (
|
||||
project: gdProject,
|
||||
objectsContainer: gdObjectsContainer,
|
||||
|
||||
+220
-202
@@ -1,11 +1,11 @@
|
||||
// @flow
|
||||
import { Trans } from '@lingui/macro';
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { AutoSizer, List } from 'react-virtualized';
|
||||
import React from 'react';
|
||||
import { AutoSizer } from 'react-virtualized';
|
||||
import SortableVirtualizedItemList from '../UI/SortableVirtualizedItemList';
|
||||
import Background from '../UI/Background';
|
||||
import SearchBar from '../UI/SearchBar';
|
||||
import ObjectRow from './ObjectRow';
|
||||
import NewObjectDialog from './NewObjectDialog';
|
||||
import VariablesEditorDialog from '../VariablesList/VariablesEditorDialog';
|
||||
import newNameGenerator from '../Utils/NewNameGenerator';
|
||||
@@ -15,9 +15,11 @@ import {
|
||||
unserializeFromJSObject,
|
||||
} from '../Utils/Serializer';
|
||||
import { showWarningBox } from '../UI/Messages/MessageBox';
|
||||
import { AddListItem } from '../UI/ListCommonItem';
|
||||
import { SortableContainer, SortableElement } from 'react-sortable-hoc';
|
||||
import { enumerateObjects, filterObjectsList } from './EnumerateObjects';
|
||||
import {
|
||||
enumerateObjects,
|
||||
filterObjectsList,
|
||||
isSameObjectWithContext,
|
||||
} from './EnumerateObjects';
|
||||
import type {
|
||||
ObjectWithContextList,
|
||||
ObjectWithContext,
|
||||
@@ -25,8 +27,13 @@ import type {
|
||||
import { CLIPBOARD_KIND } from './ClipboardKind';
|
||||
import TagChips from '../UI/TagChips';
|
||||
import EditTagsDialog from '../UI/EditTagsDialog';
|
||||
import { type Tags, getStringFromTags } from '../Utils/TagsHelper';
|
||||
import { listItemWith32PxIconHeight } from '../UI/List';
|
||||
import {
|
||||
type Tags,
|
||||
type SelectedTags,
|
||||
getStringFromTags,
|
||||
buildTagsMenuTemplate,
|
||||
getTagsFromString,
|
||||
} from '../Utils/TagsHelper';
|
||||
|
||||
const styles = {
|
||||
listContainer: {
|
||||
@@ -34,120 +41,29 @@ const styles = {
|
||||
},
|
||||
};
|
||||
|
||||
const SortableObjectRow = SortableElement(props => {
|
||||
const { style, ...otherProps } = props;
|
||||
return (
|
||||
<div style={style}>
|
||||
<ObjectRow {...otherProps} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
export const objectWithContextReactDndType = 'GD_OBJECT_WITH_CONTEXT';
|
||||
|
||||
const SortableAddObjectRow = SortableElement(props => {
|
||||
const { style, ...otherProps } = props;
|
||||
return (
|
||||
<div style={style}>
|
||||
<AddListItem {...otherProps} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
const getObjectWithContextName = (objectWithContext: ObjectWithContext) =>
|
||||
objectWithContext.object.getName();
|
||||
|
||||
class ObjectsList extends Component<*, *> {
|
||||
list: any;
|
||||
const isObjectWithContextGlobal = (objectWithContext: ObjectWithContext) =>
|
||||
objectWithContext.global;
|
||||
|
||||
forceUpdateGrid() {
|
||||
if (this.list) this.list.forceUpdateGrid();
|
||||
const getPasteLabel = isGlobalObject => {
|
||||
let clipboardObjectName = '';
|
||||
if (Clipboard.has(CLIPBOARD_KIND)) {
|
||||
const clipboardContent = Clipboard.get(CLIPBOARD_KIND);
|
||||
if (clipboardContent) {
|
||||
clipboardObjectName = clipboardContent.name;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let { height, width, fullList, project, selectedObjectNames } = this.props;
|
||||
return isGlobalObject
|
||||
? 'Paste ' + clipboardObjectName + ' as a Global Object'
|
||||
: 'Paste ' + clipboardObjectName;
|
||||
};
|
||||
|
||||
return (
|
||||
<List
|
||||
ref={list => (this.list = list)}
|
||||
height={height}
|
||||
rowCount={fullList.length}
|
||||
rowHeight={listItemWith32PxIconHeight}
|
||||
rowRenderer={({ index, key, style }) => {
|
||||
const objectWithContext = fullList[index];
|
||||
if (objectWithContext.key === 'add-objects-row') {
|
||||
return (
|
||||
<SortableAddObjectRow
|
||||
index={fullList.length}
|
||||
key={key}
|
||||
style={style}
|
||||
disabled
|
||||
onClick={this.props.onAddNewObject}
|
||||
primaryText={<Trans>Click to add an object</Trans>}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const nameBeingEdited =
|
||||
this.props.renamedObjectWithContext &&
|
||||
this.props.renamedObjectWithContext.object ===
|
||||
objectWithContext.object &&
|
||||
this.props.renamedObjectWithContext.global ===
|
||||
objectWithContext.global;
|
||||
|
||||
return (
|
||||
<SortableObjectRow
|
||||
index={index}
|
||||
key={objectWithContext.object.ptr}
|
||||
project={project}
|
||||
object={objectWithContext.object}
|
||||
isGlobalObject={objectWithContext.global}
|
||||
style={style}
|
||||
onEdit={
|
||||
this.props.onEditObject
|
||||
? () => this.props.onEditObject(objectWithContext.object)
|
||||
: undefined
|
||||
}
|
||||
onEditVariables={() =>
|
||||
this.props.onEditVariables(objectWithContext.object)
|
||||
}
|
||||
onEditName={() => this.props.onEditName(objectWithContext)}
|
||||
onDelete={() => this.props.onDelete(objectWithContext)}
|
||||
onCopyObject={() => this.props.onCopyObject(objectWithContext)}
|
||||
onCutObject={() => this.props.onCutObject(objectWithContext)}
|
||||
onDuplicateObject={() =>
|
||||
this.props.onDuplicateObject(objectWithContext)
|
||||
}
|
||||
onPasteObject={() => this.props.onPasteObject(objectWithContext)}
|
||||
onRename={newName =>
|
||||
this.props.onRename(objectWithContext, newName)
|
||||
}
|
||||
onSetAsGlobalObject={
|
||||
objectWithContext.global
|
||||
? undefined
|
||||
: () => this.props.onSetAsGlobalObject(objectWithContext)
|
||||
}
|
||||
onAddNewObject={this.props.onAddNewObject}
|
||||
editingName={nameBeingEdited}
|
||||
getThumbnail={this.props.getThumbnail}
|
||||
getAllObjectTags={this.props.getAllObjectTags}
|
||||
onObjectSelected={this.props.onObjectSelected}
|
||||
onEditTags={() => this.props.onEditTags(objectWithContext.object)}
|
||||
onChangeTags={objectTags =>
|
||||
this.props.onChangeTags(objectWithContext.object, objectTags)
|
||||
}
|
||||
selected={
|
||||
selectedObjectNames.indexOf(
|
||||
objectWithContext.object.getName()
|
||||
) !== -1
|
||||
}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
width={width}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const SortableObjectsList = SortableContainer(ObjectsList, { withRef: true });
|
||||
|
||||
type StateType = {|
|
||||
type State = {|
|
||||
newObjectDialogOpen: boolean,
|
||||
renamedObjectWithContext: ?ObjectWithContext,
|
||||
variablesEditedObject: ?gdObject,
|
||||
@@ -155,23 +71,37 @@ type StateType = {|
|
||||
tagEditedObject: ?gdObject,
|
||||
|};
|
||||
|
||||
export default class ObjectsListContainer extends React.Component<
|
||||
*,
|
||||
StateType
|
||||
> {
|
||||
static defaultProps = {
|
||||
onDeleteObject: (objectWithContext: ObjectWithContext, cb: Function) =>
|
||||
cb(true),
|
||||
onRenameObject: (
|
||||
objectWithContext: ObjectWithContext,
|
||||
newName: string,
|
||||
cb: Function
|
||||
) => cb(true),
|
||||
};
|
||||
type Props = {|
|
||||
project: gdProject,
|
||||
objectsContainer: gdObjectsContainer,
|
||||
onDeleteObject: (
|
||||
objectWithContext: ObjectWithContext,
|
||||
cb: (boolean) => void
|
||||
) => void,
|
||||
onRenameObject: (
|
||||
objectWithContext: ObjectWithContext,
|
||||
newName: string,
|
||||
cb: (boolean) => void
|
||||
) => void,
|
||||
selectedObjectNames: Array<string>,
|
||||
|
||||
sortableList: any;
|
||||
_displayedObjectsList: ObjectWithContextList = [];
|
||||
state: StateType = {
|
||||
selectedObjectTags: SelectedTags,
|
||||
getAllObjectTags: () => Tags,
|
||||
onChangeSelectedObjectTags: SelectedTags => void,
|
||||
|
||||
onEditObject: gdObject => void,
|
||||
onObjectCreated: string => void,
|
||||
onObjectSelected: string => void,
|
||||
onObjectPasted?: gdObject => void,
|
||||
canRenameObject: (newName: string) => boolean,
|
||||
|
||||
getThumbnail: (project: gdProject, object: Object) => string,
|
||||
|};
|
||||
|
||||
export default class ObjectsList extends React.Component<Props, State> {
|
||||
sortableList: ?SortableVirtualizedItemList<ObjectWithContext>;
|
||||
_displayedObjectWithContextsList: ObjectWithContextList = [];
|
||||
state = {
|
||||
newObjectDialogOpen: false,
|
||||
renamedObjectWithContext: null,
|
||||
variablesEditedObject: null,
|
||||
@@ -179,7 +109,7 @@ export default class ObjectsListContainer extends React.Component<
|
||||
tagEditedObject: null,
|
||||
};
|
||||
|
||||
shouldComponentUpdate(nextProps: *, nextState: StateType) {
|
||||
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 objects list is made outside
|
||||
@@ -351,7 +281,9 @@ export default class ObjectsListContainer extends React.Component<
|
||||
{
|
||||
renamedObjectWithContext: objectWithContext,
|
||||
},
|
||||
() => this.sortableList.getWrappedInstance().forceUpdateGrid()
|
||||
() => {
|
||||
if (this.sortableList) this.sortableList.forceUpdateGrid();
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -378,48 +310,61 @@ export default class ObjectsListContainer extends React.Component<
|
||||
}
|
||||
};
|
||||
|
||||
_move = (oldIndex: number, newIndex: number) => {
|
||||
// Moving objects can be discarded by the parent (this is used to allow
|
||||
// dropping objects on the scene editor).
|
||||
if (!this.props.canMoveObjects) return;
|
||||
|
||||
const { project, objectsContainer } = this.props;
|
||||
|
||||
const movedObjectWithContext = this._displayedObjectsList[oldIndex];
|
||||
const destinationObjectWithContext = this._displayedObjectsList[newIndex];
|
||||
if (!movedObjectWithContext || !destinationObjectWithContext) return;
|
||||
|
||||
if (movedObjectWithContext.global !== destinationObjectWithContext.global) {
|
||||
// Can't move an object from the objects container to the global objects
|
||||
// or vice-versa.
|
||||
return;
|
||||
}
|
||||
|
||||
const container: gdObjectsContainer = movedObjectWithContext.global
|
||||
? project
|
||||
: objectsContainer;
|
||||
container.moveObject(
|
||||
container.getObjectPosition(movedObjectWithContext.object.getName()),
|
||||
container.getObjectPosition(destinationObjectWithContext.object.getName())
|
||||
_canMoveSelectionTo = (destinationObjectWithContext: ObjectWithContext) => {
|
||||
// Check if at least one element in the selection can be moved.
|
||||
const selectedObjects = this._displayedObjectWithContextsList.filter(
|
||||
objectWithContext =>
|
||||
this.props.selectedObjectNames.indexOf(
|
||||
objectWithContext.object.getName()
|
||||
) !== -1
|
||||
);
|
||||
return (
|
||||
selectedObjects.filter(movedObjectWithContext => {
|
||||
return (
|
||||
movedObjectWithContext.global === destinationObjectWithContext.global
|
||||
);
|
||||
}).length > 0
|
||||
);
|
||||
|
||||
this.forceUpdateList();
|
||||
};
|
||||
|
||||
_onStartDraggingObject = ({ index }: { index: number }) => {
|
||||
const draggedObjectWithContext = this._displayedObjectsList[index];
|
||||
if (!draggedObjectWithContext) {
|
||||
return;
|
||||
}
|
||||
_moveSelectionTo = (destinationObjectWithContext: ObjectWithContext) => {
|
||||
const { project, objectsContainer } = this.props;
|
||||
|
||||
this.props.onStartDraggingObject(draggedObjectWithContext.object);
|
||||
const container: gdObjectsContainer = destinationObjectWithContext.global
|
||||
? project
|
||||
: objectsContainer;
|
||||
|
||||
const selectedObjects = this._displayedObjectWithContextsList.filter(
|
||||
objectWithContext =>
|
||||
this.props.selectedObjectNames.indexOf(
|
||||
objectWithContext.object.getName()
|
||||
) !== -1
|
||||
);
|
||||
selectedObjects.forEach(movedObjectWithContext => {
|
||||
if (
|
||||
movedObjectWithContext.global !== destinationObjectWithContext.global
|
||||
) {
|
||||
// Can't move an object from the objects container to the global objects
|
||||
// or vice-versa.
|
||||
return;
|
||||
}
|
||||
|
||||
container.moveObject(
|
||||
container.getObjectPosition(movedObjectWithContext.object.getName()),
|
||||
container.getObjectPosition(
|
||||
destinationObjectWithContext.object.getName()
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
this.forceUpdateList();
|
||||
};
|
||||
|
||||
_setAsGlobalObject = (objectWithContext: ObjectWithContext) => {
|
||||
const { object } = objectWithContext;
|
||||
const { project, objectsContainer } = this.props;
|
||||
|
||||
const objectName = object.getName();
|
||||
const objectName: string = object.getName();
|
||||
if (!objectsContainer.hasObjectNamed(objectName)) return;
|
||||
|
||||
if (project.hasObjectNamed(objectName)) {
|
||||
@@ -449,7 +394,7 @@ export default class ObjectsListContainer extends React.Component<
|
||||
|
||||
forceUpdateList = () => {
|
||||
this.forceUpdate();
|
||||
this.sortableList.getWrappedInstance().forceUpdateGrid();
|
||||
if (this.sortableList) this.sortableList.forceUpdateGrid();
|
||||
};
|
||||
|
||||
_openEditTagDialog = (tagEditedObject: ?gdObject) => {
|
||||
@@ -466,19 +411,102 @@ export default class ObjectsListContainer extends React.Component<
|
||||
this.forceUpdateList();
|
||||
};
|
||||
|
||||
_selectObject = (objectWithContext: ?ObjectWithContext) => {
|
||||
this.props.onObjectSelected(
|
||||
objectWithContext ? objectWithContext.object.getName() : ''
|
||||
);
|
||||
};
|
||||
|
||||
_getObjectThumbnail = (objectWithContext: ObjectWithContext) =>
|
||||
this.props.getThumbnail(this.props.project, objectWithContext.object);
|
||||
|
||||
_renderObjectMenuTemplate = (
|
||||
objectWithContext: ObjectWithContext,
|
||||
index: number
|
||||
) => {
|
||||
const { object } = objectWithContext;
|
||||
return [
|
||||
{
|
||||
label: 'Edit object',
|
||||
click: () => this.props.onEditObject(object),
|
||||
},
|
||||
{
|
||||
label: 'Edit object variables',
|
||||
click: () => this._editVariables(object),
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Tags',
|
||||
submenu: buildTagsMenuTemplate({
|
||||
noTagLabel: 'No tags',
|
||||
getAllTags: this.props.getAllObjectTags,
|
||||
selectedTags: getTagsFromString(object.getTags()),
|
||||
onChange: objectTags => {
|
||||
this._changeObjectTags(object, objectTags);
|
||||
},
|
||||
editTagsLabel: 'Add/edit tags...',
|
||||
onEditTags: () => this._openEditTagDialog(object),
|
||||
}),
|
||||
},
|
||||
{
|
||||
label: 'Rename',
|
||||
click: () => this._editName(objectWithContext),
|
||||
},
|
||||
{
|
||||
label: 'Set as a global object',
|
||||
click: () => this._setAsGlobalObject(objectWithContext),
|
||||
},
|
||||
{
|
||||
label: 'Delete',
|
||||
click: () => this._deleteObject(objectWithContext),
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Add a new object...',
|
||||
click: () => this.onAddNewObject(),
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Copy',
|
||||
click: () => this._copyObject(objectWithContext),
|
||||
},
|
||||
{
|
||||
label: 'Cut',
|
||||
click: () => this._cutObject(objectWithContext),
|
||||
},
|
||||
{
|
||||
label: getPasteLabel(objectWithContext.global),
|
||||
enabled: Clipboard.has(CLIPBOARD_KIND),
|
||||
click: () => this._paste(objectWithContext),
|
||||
},
|
||||
{
|
||||
label: 'Duplicate',
|
||||
click: () => this._duplicateObject(objectWithContext),
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
render() {
|
||||
const { project, objectsContainer, selectedObjectTags } = this.props;
|
||||
const { searchText, tagEditedObject } = this.state;
|
||||
|
||||
const lists = enumerateObjects(project, objectsContainer);
|
||||
this._displayedObjectsList = filterObjectsList(lists.allObjectsList, {
|
||||
searchText,
|
||||
selectedTags: selectedObjectTags,
|
||||
});
|
||||
const fullList = this._displayedObjectsList.concat({
|
||||
key: 'add-objects-row',
|
||||
object: null,
|
||||
});
|
||||
this._displayedObjectWithContextsList = filterObjectsList(
|
||||
lists.allObjectsList,
|
||||
{
|
||||
searchText,
|
||||
selectedTags: selectedObjectTags,
|
||||
}
|
||||
);
|
||||
const selectedObjects = this._displayedObjectWithContextsList.filter(
|
||||
objectWithContext =>
|
||||
this.props.selectedObjectNames.indexOf(
|
||||
objectWithContext.object.getName()
|
||||
) !== -1
|
||||
);
|
||||
const renamedObjectWithContext = this._displayedObjectWithContextsList.find(
|
||||
isSameObjectWithContext(this.state.renamedObjectWithContext)
|
||||
);
|
||||
|
||||
// Force List component to be mounted again if project or objectsContainer
|
||||
// has been changed. Avoid accessing to invalid objects that could
|
||||
@@ -494,38 +522,28 @@ export default class ObjectsListContainer extends React.Component<
|
||||
<div style={styles.listContainer}>
|
||||
<AutoSizer>
|
||||
{({ height, width }) => (
|
||||
<SortableObjectsList
|
||||
<SortableVirtualizedItemList
|
||||
key={listKey}
|
||||
ref={sortableList => (this.sortableList = sortableList)}
|
||||
fullList={fullList}
|
||||
project={project}
|
||||
fullList={this._displayedObjectWithContextsList}
|
||||
width={width}
|
||||
height={height}
|
||||
renamedObjectWithContext={this.state.renamedObjectWithContext}
|
||||
getThumbnail={this.props.getThumbnail}
|
||||
getAllObjectTags={this.props.getAllObjectTags}
|
||||
onEditTags={this._openEditTagDialog}
|
||||
onChangeTags={this._changeObjectTags}
|
||||
selectedObjectNames={this.props.selectedObjectNames}
|
||||
onObjectSelected={this.props.onObjectSelected}
|
||||
onEditObject={this.props.onEditObject}
|
||||
onCopyObject={this._copyObject}
|
||||
onCutObject={this._cutObject}
|
||||
onDuplicateObject={this._duplicateObject}
|
||||
onSetAsGlobalObject={this._setAsGlobalObject}
|
||||
onPasteObject={this._pasteAndRename}
|
||||
onAddNewObject={this.onAddNewObject}
|
||||
onEditName={this._editName}
|
||||
onEditVariables={this._editVariables}
|
||||
onDelete={this._deleteObject}
|
||||
getItemName={getObjectWithContextName}
|
||||
getItemThumbnail={this._getObjectThumbnail}
|
||||
isItemBold={isObjectWithContextGlobal}
|
||||
onEditItem={objectWithContext =>
|
||||
this.props.onEditObject(objectWithContext.object)
|
||||
}
|
||||
onAddNewItem={this.onAddNewObject}
|
||||
addNewItemLabel={<Trans>Add a new object</Trans>}
|
||||
selectedItems={selectedObjects}
|
||||
onItemSelected={this._selectObject}
|
||||
renamedItem={renamedObjectWithContext}
|
||||
onRename={this._rename}
|
||||
onSortStart={this._onStartDraggingObject}
|
||||
onSortEnd={({ oldIndex, newIndex }) => {
|
||||
this.props.onEndDraggingObject();
|
||||
this._move(oldIndex, newIndex);
|
||||
}}
|
||||
helperClass="sortable-helper"
|
||||
distance={20}
|
||||
buildMenuTemplate={this._renderObjectMenuTemplate}
|
||||
onMoveSelectionToItem={this._moveSelectionTo}
|
||||
canMoveSelectionToItem={this._canMoveSelectionTo}
|
||||
reactDndType={objectWithContextReactDndType}
|
||||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
|
||||
@@ -28,6 +28,15 @@ import {
|
||||
} from '../Utils/Serializer';
|
||||
import ThemeConsumer from '../UI/Theme/ThemeConsumer';
|
||||
import ExtensionsSearchDialog from '../ExtensionsSearch/ExtensionsSearchDialog';
|
||||
import Close from '@material-ui/icons/Close';
|
||||
import SettingsApplications from '@material-ui/icons/SettingsApplications';
|
||||
import PhotoLibrary from '@material-ui/icons/PhotoLibrary';
|
||||
import Settings from '@material-ui/icons/Settings';
|
||||
import Save from '@material-ui/icons/Save';
|
||||
import VariableTree from '../UI/CustomSvgIcons/VariableTree';
|
||||
import ArtTrack from '@material-ui/icons/ArtTrack';
|
||||
import AddToHomeScreen from '@material-ui/icons/AddToHomeScreen';
|
||||
import Fullscreen from '@material-ui/icons/Fullscreen';
|
||||
|
||||
const LAYOUT_CLIPBOARD_KIND = 'Layout';
|
||||
const EXTERNAL_LAYOUT_CLIPBOARD_KIND = 'External layout';
|
||||
@@ -59,9 +68,9 @@ const styles = {
|
||||
};
|
||||
|
||||
type ProjectStructureItemProps = {|
|
||||
autoGenerateNestedIndicator: boolean,
|
||||
initiallyOpen: boolean,
|
||||
leftIcon: React$Element<any>,
|
||||
autoGenerateNestedIndicator?: boolean,
|
||||
initiallyOpen?: boolean,
|
||||
leftIcon?: React$Element<any>,
|
||||
indentNestedItems?: boolean,
|
||||
renderNestedItems: () => Array<React$Element<any> | null>,
|
||||
primaryText: React.Node,
|
||||
@@ -560,65 +569,43 @@ export default class ProjectManager extends React.Component<Props, State> {
|
||||
return (
|
||||
<ProjectStructureItem
|
||||
primaryText={<Trans>Menu</Trans>}
|
||||
leftIcon={
|
||||
<ListIcon
|
||||
iconSize={32}
|
||||
isGDevelopIcon
|
||||
src="res/ribbon_default/new32.png"
|
||||
/>
|
||||
}
|
||||
initiallyOpen={true}
|
||||
autoGenerateNestedIndicator={true}
|
||||
open
|
||||
indentNestedItems
|
||||
renderNestedItems={() => [
|
||||
<ListItem
|
||||
key="save"
|
||||
primaryText={<Trans>Save</Trans>}
|
||||
leftIcon={
|
||||
<ListIcon
|
||||
iconSize={32}
|
||||
isGDevelopIcon
|
||||
src="res/ribbon_default/save32.png"
|
||||
/>
|
||||
}
|
||||
leftIcon={<Save />}
|
||||
onClick={() => this.props.onSaveProject()}
|
||||
/>,
|
||||
<ListItem
|
||||
key="close"
|
||||
primaryText={<Trans>Close</Trans>}
|
||||
leftIcon={
|
||||
<ListIcon
|
||||
iconSize={32}
|
||||
isGDevelopIcon
|
||||
src="res/ribbon_default/close32.png"
|
||||
/>
|
||||
}
|
||||
onClick={() => this.props.onCloseProject()}
|
||||
/>,
|
||||
<ListItem
|
||||
key="export"
|
||||
primaryText={<Trans>Export</Trans>}
|
||||
leftIcon={
|
||||
<ListIcon
|
||||
iconSize={32}
|
||||
isGDevelopIcon
|
||||
src="res/ribbon_default/export32.png"
|
||||
/>
|
||||
}
|
||||
leftIcon={<AddToHomeScreen />}
|
||||
onClick={() => this.props.onExportProject()}
|
||||
/>,
|
||||
<ListItem
|
||||
key="close"
|
||||
primaryText={<Trans>Close</Trans>}
|
||||
leftIcon={<Close />}
|
||||
onClick={() => this.props.onCloseProject()}
|
||||
/>,
|
||||
<ListItem
|
||||
key="preferences"
|
||||
primaryText={<Trans>Preferences</Trans>}
|
||||
leftIcon={
|
||||
<ListIcon
|
||||
iconSize={32}
|
||||
isGDevelopIcon
|
||||
src="res/ribbon_default/pref32.png"
|
||||
/>
|
||||
}
|
||||
leftIcon={<Settings />}
|
||||
onClick={() => this.props.onOpenPreferences()}
|
||||
/>,
|
||||
...(!Window.isFullscreen()
|
||||
? [
|
||||
<ListItem
|
||||
key="fullscreen"
|
||||
primaryText={<Trans>Turn on Fullscreen</Trans>}
|
||||
leftIcon={<Fullscreen />}
|
||||
onClick={() => Window.requestFullscreen()}
|
||||
/>,
|
||||
]
|
||||
: []),
|
||||
]}
|
||||
/>
|
||||
);
|
||||
@@ -663,13 +650,7 @@ export default class ProjectManager extends React.Component<Props, State> {
|
||||
<ListItem
|
||||
key="properties"
|
||||
primaryText={<Trans>Properties</Trans>}
|
||||
leftIcon={
|
||||
<ListIcon
|
||||
iconSize={32}
|
||||
isGDevelopIcon
|
||||
src="res/ribbon_default/editprop32.png"
|
||||
/>
|
||||
}
|
||||
leftIcon={<SettingsApplications />}
|
||||
onClick={() =>
|
||||
this.setState({ projectPropertiesDialogOpen: true })
|
||||
}
|
||||
@@ -677,37 +658,19 @@ export default class ProjectManager extends React.Component<Props, State> {
|
||||
<ListItem
|
||||
key="global-variables"
|
||||
primaryText={<Trans>Global variables</Trans>}
|
||||
leftIcon={
|
||||
<ListIcon
|
||||
iconSize={32}
|
||||
isGDevelopIcon
|
||||
src="res/ribbon_default/editname32.png"
|
||||
/>
|
||||
}
|
||||
leftIcon={<VariableTree />}
|
||||
onClick={() => this.setState({ variablesEditorOpen: true })}
|
||||
/>,
|
||||
<ListItem
|
||||
key="icons"
|
||||
primaryText={<Trans>Icons</Trans>}
|
||||
leftIcon={
|
||||
<ListIcon
|
||||
iconSize={32}
|
||||
isGDevelopIcon
|
||||
src="res/ribbon_default/image32.png"
|
||||
/>
|
||||
}
|
||||
leftIcon={<PhotoLibrary />}
|
||||
onClick={() => this.props.onOpenPlatformSpecificAssets()}
|
||||
/>,
|
||||
<ListItem
|
||||
key="resources"
|
||||
primaryText={<Trans>Resources</Trans>}
|
||||
leftIcon={
|
||||
<ListIcon
|
||||
iconSize={32}
|
||||
isGDevelopIcon
|
||||
src="res/ribbon_default/image32.png"
|
||||
/>
|
||||
}
|
||||
leftIcon={<ArtTrack />}
|
||||
onClick={() => {
|
||||
this.props.onOpenResources();
|
||||
}}
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
// This file is generated by update-fixtures-from-resources-examples.js script
|
||||
|
||||
import exampleFile0 from '../fixtures/admob/admob.json';
|
||||
import exampleFile1 from '../fixtures/advanced-shape-based-painter/advanced-shape-based-painter.json';
|
||||
import exampleFile2 from '../fixtures/animation-speed-scale/animation-speed-scale.json';
|
||||
import exampleFile3 from '../fixtures/asteroids/asteroids.json';
|
||||
import exampleFile4 from '../fixtures/basic-ai-with-pathfinding/basic-ai-with-pathfinding.json';
|
||||
import exampleFile5 from '../fixtures/basic-artificial-intelligence/basic-artificial-intelligence.json';
|
||||
import exampleFile6 from '../fixtures/basic-topdown-car-driving/basic-topdown-car-driving.json';
|
||||
import exampleFile7 from '../fixtures/betabox-basics-learning-experience/betabox-basics-learning-experience.json';
|
||||
import exampleFile8 from '../fixtures/bomb-the-crate/bomb-the-crate.json';
|
||||
import exampleFile9 from '../fixtures/bouncing-ball-and-rope/bouncing-ball-and-rope.json';
|
||||
import exampleFile10 from '../fixtures/breakout/breakout.json';
|
||||
import exampleFile11 from '../fixtures/buttons/buttons.json';
|
||||
import exampleFile12 from '../fixtures/car-physics/car-physics.json';
|
||||
import exampleFile13 from '../fixtures/center-object-within-another/center-object-within-another.json';
|
||||
import exampleFile14 from '../fixtures/change-position-of-object/change-position-of-object.json';
|
||||
import exampleFile15 from '../fixtures/change-scale-of-sprites/change-scale-of-sprites.json';
|
||||
import exampleFile16 from '../fixtures/change-sprite-animation/change-sprite-animation.json';
|
||||
import exampleFile17 from '../fixtures/change-sprite-color/change-sprite-color.json';
|
||||
import exampleFile18 from '../fixtures/character-selection/character-selection.json';
|
||||
import exampleFile19 from '../fixtures/create-object-with-mouseclick/create-object-with-mouseclick.json';
|
||||
import exampleFile20 from '../fixtures/custom-font/custom-font.json';
|
||||
import exampleFile21 from '../fixtures/custom-mouse-pointer/custom-mouse-pointer.json';
|
||||
import exampleFile22 from '../fixtures/customize-keys-with-lastpressedkey/customize-keys-with-lastpressedkey.json';
|
||||
import exampleFile23 from '../fixtures/device-orientation-ballgame/device-orientation-ballgame.json';
|
||||
import exampleFile24 from '../fixtures/device-orientation-compass/device-orientation-compass.json';
|
||||
import exampleFile25 from '../fixtures/device-vibration/device-vibration.json';
|
||||
import exampleFile26 from '../fixtures/dialogue-tree-with-yarn/dialogue-tree-with-yarn.json';
|
||||
import exampleFile27 from '../fixtures/downhill-bike-physics-demo/downhill-bike-physics-demo.json';
|
||||
import exampleFile28 from '../fixtures/drag-camera-with-mouse/drag-camera-with-mouse.json';
|
||||
import exampleFile29 from '../fixtures/drop-collect-items-from-storage/drop-collect-items-from-storage.json';
|
||||
import exampleFile30 from '../fixtures/endless-up-runner/endless-up-runner.json';
|
||||
import exampleFile31 from '../fixtures/exit-app/exit-app.json';
|
||||
import exampleFile32 from '../fixtures/facebook-instant-game/facebook-instant-game.json';
|
||||
import exampleFile33 from '../fixtures/filesystem-create-directory/filesystem-create-directory.json';
|
||||
import exampleFile34 from '../fixtures/find-diagonals/find-diagonals.json';
|
||||
import exampleFile35 from '../fixtures/geodash/geodash.json';
|
||||
import exampleFile36 from '../fixtures/health-bar/health-bar.json';
|
||||
import exampleFile37 from '../fixtures/infinite-scrolling-background/infinite-scrolling-background.json';
|
||||
import exampleFile38 from '../fixtures/inventory-system/inventory-system.json';
|
||||
import exampleFile39 from '../fixtures/isometric-game/isometric-game.json';
|
||||
import exampleFile40 from '../fixtures/javascript-blocks-in-platformer/javascript-blocks-in-platformer.json';
|
||||
import exampleFile41 from '../fixtures/keyboard-practice/keyboard-practice.json';
|
||||
import exampleFile42 from '../fixtures/level-editor/level-editor.json';
|
||||
import exampleFile43 from '../fixtures/load-image-from-url/load-image-from-url.json';
|
||||
import exampleFile44 from '../fixtures/magnet/magnet.json';
|
||||
import exampleFile45 from '../fixtures/manipulate-text-object/manipulate-text-object.json';
|
||||
import exampleFile46 from '../fixtures/menu-with-functions-and-text-effects/menu-with-functions-and-text-effects.json';
|
||||
import exampleFile47 from '../fixtures/move-camera-to-position/move-camera-to-position.json';
|
||||
import exampleFile48 from '../fixtures/move-object-back-and-forth/move-object-back-and-forth.json';
|
||||
import exampleFile49 from '../fixtures/move-object-in-circle/move-object-in-circle.json';
|
||||
import exampleFile50 from '../fixtures/move-object-toward-position/move-object-toward-position.json';
|
||||
import exampleFile51 from '../fixtures/move-object-with-mouse-joint/move-object-with-mouse-joint.json';
|
||||
import exampleFile52 from '../fixtures/move-object-with-physics/move-object-with-physics.json';
|
||||
import exampleFile53 from '../fixtures/multiplayer-platformer-with-gamepads/multiplayer-platformer-with-gamepads.json';
|
||||
import exampleFile54 from '../fixtures/multitouch/multitouch.json';
|
||||
import exampleFile55 from '../fixtures/object-gravity/object-gravity.json';
|
||||
import exampleFile56 from '../fixtures/object-selection/object-selection.json';
|
||||
import exampleFile57 from '../fixtures/objects-timers/objects-timers.json';
|
||||
import exampleFile58 from '../fixtures/open-url-in-browser/open-url-in-browser.json';
|
||||
import exampleFile59 from '../fixtures/pairs/pairs.json';
|
||||
import exampleFile60 from '../fixtures/parallax/parallax.json';
|
||||
import exampleFile61 from '../fixtures/parallax-scrolling/parallax-scrolling.json';
|
||||
import exampleFile62 from '../fixtures/parse-json-from-api/parse-json-from-api.json';
|
||||
import exampleFile63 from '../fixtures/parse-json-string/parse-json-string.json';
|
||||
import exampleFile64 from '../fixtures/particles-explosions/particles-explosions.json';
|
||||
import exampleFile65 from '../fixtures/particles-various-effects/particles-various-effects.json';
|
||||
import exampleFile66 from '../fixtures/pathfinding/pathfinding.json';
|
||||
import exampleFile67 from '../fixtures/pathfinding-basics/pathfinding-basics.json';
|
||||
import exampleFile68 from '../fixtures/physics/physics.json';
|
||||
import exampleFile69 from '../fixtures/physics-joints-demo/physics-joints-demo.json';
|
||||
import exampleFile70 from '../fixtures/physics-joints-settings-demo/physics-joints-settings-demo.json';
|
||||
import exampleFile71 from '../fixtures/pin-object-to-another/pin-object-to-another.json';
|
||||
import exampleFile72 from '../fixtures/pin-object-to-another-multiple-parents/pin-object-to-another-multiple-parents.json';
|
||||
import exampleFile73 from '../fixtures/pixel-perfect-platform-game/pixel-perfect-platform-game.json';
|
||||
import exampleFile74 from '../fixtures/plane-and-clouds/plane-and-clouds.json';
|
||||
import exampleFile75 from '../fixtures/platformer/platformer.json';
|
||||
import exampleFile76 from '../fixtures/platformer-double-jump/platformer-double-jump.json';
|
||||
import exampleFile77 from '../fixtures/play-music-on-mobile/play-music-on-mobile.json';
|
||||
import exampleFile78 from '../fixtures/play-stop-sprite-animation/play-stop-sprite-animation.json';
|
||||
import exampleFile79 from '../fixtures/racing-game/racing-game.json';
|
||||
import exampleFile80 from '../fixtures/ragdoll/ragdoll.json';
|
||||
import exampleFile81 from '../fixtures/rain/rain.json';
|
||||
import exampleFile82 from '../fixtures/random-color-picker/random-color-picker.json';
|
||||
import exampleFile83 from '../fixtures/rotate-toward-mouse/rotate-toward-mouse.json';
|
||||
import exampleFile84 from '../fixtures/rotate-toward-position/rotate-toward-position.json';
|
||||
import exampleFile85 from '../fixtures/rotate-with-keypress/rotate-with-keypress.json';
|
||||
import exampleFile86 from '../fixtures/save-load/save-load.json';
|
||||
import exampleFile87 from '../fixtures/screen-shake/screen-shake.json';
|
||||
import exampleFile88 from '../fixtures/shoot-bullet-in-parabola/shoot-bullet-in-parabola.json';
|
||||
import exampleFile89 from '../fixtures/shoot-bullets/shoot-bullets.json';
|
||||
import exampleFile90 from '../fixtures/shooting-bullets-explanation/shooting-bullets-explanation.json';
|
||||
import exampleFile91 from '../fixtures/simple-space-shooter/simple-space-shooter.json';
|
||||
import exampleFile92 from '../fixtures/skeletal-animation-demo/skeletal-animation-demo.json';
|
||||
import exampleFile93 from '../fixtures/snap-object-to-grid/snap-object-to-grid.json';
|
||||
import exampleFile94 from '../fixtures/space-invaders/space-invaders.json';
|
||||
import exampleFile95 from '../fixtures/space-shooter/space-shooter.json';
|
||||
import exampleFile96 from '../fixtures/splash-screen/splash-screen.json';
|
||||
import exampleFile97 from '../fixtures/sprite-fade-in-out/sprite-fade-in-out.json';
|
||||
import exampleFile98 from '../fixtures/take-screenshot/take-screenshot.json';
|
||||
import exampleFile99 from '../fixtures/text-entry-object/text-entry-object.json';
|
||||
import exampleFile100 from '../fixtures/text-fade-in-out/text-fade-in-out.json';
|
||||
import exampleFile101 from '../fixtures/text-to-speech/text-to-speech.json';
|
||||
import exampleFile102 from '../fixtures/toggle-music-play-sound/toggle-music-play-sound.json';
|
||||
import exampleFile103 from '../fixtures/type-on-text-effect/type-on-text-effect.json';
|
||||
import exampleFile104 from '../fixtures/video-player/video-player.json';
|
||||
import exampleFile105 from '../fixtures/z-depth/z-depth.json';
|
||||
import exampleFile106 from '../fixtures/zombie-laser/zombie-laser.json';
|
||||
|
||||
// prettier-ignore
|
||||
export default {
|
||||
'example://admob': exampleFile0,
|
||||
'example://advanced-shape-based-painter': exampleFile1,
|
||||
'example://animation-speed-scale': exampleFile2,
|
||||
'example://asteroids': exampleFile3,
|
||||
'example://basic-ai-with-pathfinding': exampleFile4,
|
||||
'example://basic-artificial-intelligence': exampleFile5,
|
||||
'example://basic-topdown-car-driving': exampleFile6,
|
||||
'example://betabox-basics-learning-experience': exampleFile7,
|
||||
'example://bomb-the-crate': exampleFile8,
|
||||
'example://bouncing-ball-and-rope': exampleFile9,
|
||||
'example://breakout': exampleFile10,
|
||||
'example://buttons': exampleFile11,
|
||||
'example://car-physics': exampleFile12,
|
||||
'example://center-object-within-another': exampleFile13,
|
||||
'example://change-position-of-object': exampleFile14,
|
||||
'example://change-scale-of-sprites': exampleFile15,
|
||||
'example://change-sprite-animation': exampleFile16,
|
||||
'example://change-sprite-color': exampleFile17,
|
||||
'example://character-selection': exampleFile18,
|
||||
'example://create-object-with-mouseclick': exampleFile19,
|
||||
'example://custom-font': exampleFile20,
|
||||
'example://custom-mouse-pointer': exampleFile21,
|
||||
'example://customize-keys-with-lastpressedkey': exampleFile22,
|
||||
'example://device-orientation-ballgame': exampleFile23,
|
||||
'example://device-orientation-compass': exampleFile24,
|
||||
'example://device-vibration': exampleFile25,
|
||||
'example://dialogue-tree-with-yarn': exampleFile26,
|
||||
'example://downhill-bike-physics-demo': exampleFile27,
|
||||
'example://drag-camera-with-mouse': exampleFile28,
|
||||
'example://drop-collect-items-from-storage': exampleFile29,
|
||||
'example://endless-up-runner': exampleFile30,
|
||||
'example://exit-app': exampleFile31,
|
||||
'example://facebook-instant-game': exampleFile32,
|
||||
'example://filesystem-create-directory': exampleFile33,
|
||||
'example://find-diagonals': exampleFile34,
|
||||
'example://geodash': exampleFile35,
|
||||
'example://health-bar': exampleFile36,
|
||||
'example://infinite-scrolling-background': exampleFile37,
|
||||
'example://inventory-system': exampleFile38,
|
||||
'example://isometric-game': exampleFile39,
|
||||
'example://javascript-blocks-in-platformer': exampleFile40,
|
||||
'example://keyboard-practice': exampleFile41,
|
||||
'example://level-editor': exampleFile42,
|
||||
'example://load-image-from-url': exampleFile43,
|
||||
'example://magnet': exampleFile44,
|
||||
'example://manipulate-text-object': exampleFile45,
|
||||
'example://menu-with-functions-and-text-effects': exampleFile46,
|
||||
'example://move-camera-to-position': exampleFile47,
|
||||
'example://move-object-back-and-forth': exampleFile48,
|
||||
'example://move-object-in-circle': exampleFile49,
|
||||
'example://move-object-toward-position': exampleFile50,
|
||||
'example://move-object-with-mouse-joint': exampleFile51,
|
||||
'example://move-object-with-physics': exampleFile52,
|
||||
'example://multiplayer-platformer-with-gamepads': exampleFile53,
|
||||
'example://multitouch': exampleFile54,
|
||||
'example://object-gravity': exampleFile55,
|
||||
'example://object-selection': exampleFile56,
|
||||
'example://objects-timers': exampleFile57,
|
||||
'example://open-url-in-browser': exampleFile58,
|
||||
'example://pairs': exampleFile59,
|
||||
'example://parallax': exampleFile60,
|
||||
'example://parallax-scrolling': exampleFile61,
|
||||
'example://parse-json-from-api': exampleFile62,
|
||||
'example://parse-json-string': exampleFile63,
|
||||
'example://particles-explosions': exampleFile64,
|
||||
'example://particles-various-effects': exampleFile65,
|
||||
'example://pathfinding': exampleFile66,
|
||||
'example://pathfinding-basics': exampleFile67,
|
||||
'example://physics': exampleFile68,
|
||||
'example://physics-joints-demo': exampleFile69,
|
||||
'example://physics-joints-settings-demo': exampleFile70,
|
||||
'example://pin-object-to-another': exampleFile71,
|
||||
'example://pin-object-to-another-multiple-parents': exampleFile72,
|
||||
'example://pixel-perfect-platform-game': exampleFile73,
|
||||
'example://plane-and-clouds': exampleFile74,
|
||||
'example://platformer': exampleFile75,
|
||||
'example://platformer-double-jump': exampleFile76,
|
||||
'example://play-music-on-mobile': exampleFile77,
|
||||
'example://play-stop-sprite-animation': exampleFile78,
|
||||
'example://racing-game': exampleFile79,
|
||||
'example://ragdoll': exampleFile80,
|
||||
'example://rain': exampleFile81,
|
||||
'example://random-color-picker': exampleFile82,
|
||||
'example://rotate-toward-mouse': exampleFile83,
|
||||
'example://rotate-toward-position': exampleFile84,
|
||||
'example://rotate-with-keypress': exampleFile85,
|
||||
'example://save-load': exampleFile86,
|
||||
'example://screen-shake': exampleFile87,
|
||||
'example://shoot-bullet-in-parabola': exampleFile88,
|
||||
'example://shoot-bullets': exampleFile89,
|
||||
'example://shooting-bullets-explanation': exampleFile90,
|
||||
'example://simple-space-shooter': exampleFile91,
|
||||
'example://skeletal-animation-demo': exampleFile92,
|
||||
'example://snap-object-to-grid': exampleFile93,
|
||||
'example://space-invaders': exampleFile94,
|
||||
'example://space-shooter': exampleFile95,
|
||||
'example://splash-screen': exampleFile96,
|
||||
'example://sprite-fade-in-out': exampleFile97,
|
||||
'example://take-screenshot': exampleFile98,
|
||||
'example://text-entry-object': exampleFile99,
|
||||
'example://text-fade-in-out': exampleFile100,
|
||||
'example://text-to-speech': exampleFile101,
|
||||
'example://toggle-music-play-sound': exampleFile102,
|
||||
'example://type-on-text-effect': exampleFile103,
|
||||
'example://video-player': exampleFile104,
|
||||
'example://z-depth': exampleFile105,
|
||||
'example://zombie-laser': exampleFile106,
|
||||
};
|
||||
@@ -1,325 +1,9 @@
|
||||
import admob from '../fixtures/admob/admob.json';
|
||||
import advancedShapeBasedPainter from '../fixtures/advanced-shape-based-painter/advanced-shape-based-painter.json';
|
||||
import animationSpeedScale from '../fixtures/animation-speed-scale/animation-speed-scale.json';
|
||||
import asteroids from '../fixtures/asteroids/asteroids.json';
|
||||
import basicAiWithPathfinding from '../fixtures/basic-ai-with-pathfinding/basic-ai-with-pathfinding.json';
|
||||
import basicArtificialIntelligence from '../fixtures/basic-artificial-intelligence/basic-artificial-intelligence.json';
|
||||
import basicTopdownCarDriving from '../fixtures/basic-topdown-car-driving/basic-topdown-car-driving.json';
|
||||
import betaboxBasicsLearningExperience from '../fixtures/betabox-basics-learning-experience/betabox-basics-learning-experience.json';
|
||||
import bombTheCrate from '../fixtures/bomb-the-crate/bomb-the-crate.json';
|
||||
import bouncingBallAndRope from '../fixtures/bouncing-ball-and-rope/bouncing-ball-and-rope.json';
|
||||
import breakout from '../fixtures/breakout/breakout.json';
|
||||
import buttons from '../fixtures/buttons/buttons.json';
|
||||
import carPhysics from '../fixtures/car-physics/car-physics.json';
|
||||
import centerObjectWithinAnother from '../fixtures/center-object-within-another/center-object-within-another.json';
|
||||
import changePositionOfObject from '../fixtures/change-position-of-object/change-position-of-object.json';
|
||||
import changeScaleOfSprites from '../fixtures/change-scale-of-sprites/change-scale-of-sprites.json';
|
||||
import changeSpriteAnimation from '../fixtures/change-sprite-animation/change-sprite-animation.json';
|
||||
import changeSpriteColor from '../fixtures/change-sprite-color/change-sprite-color.json';
|
||||
import characterSelection from '../fixtures/character-selection/character-selection.json';
|
||||
import createObjectWithMouseclick from '../fixtures/create-object-with-mouseclick/create-object-with-mouseclick.json';
|
||||
import customFont from '../fixtures/custom-font/custom-font.json';
|
||||
import customizeKeysWithLastpressedkey from '../fixtures/customize-keys-with-lastpressedkey/customize-keys-with-lastpressedkey.json';
|
||||
import customMousePointer from '../fixtures/custom-mouse-pointer/custom-mouse-pointer.json';
|
||||
import deviceOrientationBallgame from '../fixtures/device-orientation-ballgame/device-orientation-ballgame.json';
|
||||
import deviceOrientationCompass from '../fixtures/device-orientation-compass/device-orientation-compass.json';
|
||||
import deviceVibration from '../fixtures/device-vibration/device-vibration.json';
|
||||
import dialogueTreeWithYarn from '../fixtures/dialogue-tree-with-yarn/dialogue-tree-with-yarn.json';
|
||||
import downhillBikePhysicsDemo from '../fixtures/downhill-bike-physics-demo/downhill-bike-physics-demo.json';
|
||||
import dragCameraWithMouse from '../fixtures/drag-camera-with-mouse/drag-camera-with-mouse.json';
|
||||
import dropCollectItemsFromStorage from '../fixtures/drop-collect-items-from-storage/drop-collect-items-from-storage.json';
|
||||
import endlessUpRunner from '../fixtures/endless-up-runner/endless-up-runner.json';
|
||||
import exitApp from '../fixtures/exit-app/exit-app.json';
|
||||
import facebookInstantGame from '../fixtures/facebook-instant-game/facebook-instant-game.json';
|
||||
import filesystemCreateDirectory from '../fixtures/filesystem-create-directory/filesystem-create-directory.json';
|
||||
import findDiagonals from '../fixtures/find-diagonals/find-diagonals.json';
|
||||
import geodash from '../fixtures/geodash/geodash.json';
|
||||
import healthBar from '../fixtures/health-bar/health-bar.json';
|
||||
import infiniteScrollingBackground from '../fixtures/infinite-scrolling-background/infinite-scrolling-background.json';
|
||||
import inventorySystem from '../fixtures/inventory-system/inventory-system.json';
|
||||
import isometricGame from '../fixtures/isometric-game/isometric-game.json';
|
||||
import javascriptBlocksInPlatformer from '../fixtures/javascript-blocks-in-platformer/javascript-blocks-in-platformer.json';
|
||||
import keyboardPractice from '../fixtures/keyboard-practice/keyboard-practice.json';
|
||||
import levelEditor from '../fixtures/level-editor/level-editor.json';
|
||||
import loadImageFromUrl from '../fixtures/load-image-from-url/load-image-from-url.json';
|
||||
import magnet from '../fixtures/magnet/magnet.json';
|
||||
import manipulateTextObject from '../fixtures/manipulate-text-object/manipulate-text-object.json';
|
||||
import menuWithFunctionsAndTextEffects from '../fixtures/menu-with-functions-and-text-effects/menu-with-functions-and-text-effects.json';
|
||||
import moveCameraToPosition from '../fixtures/move-camera-to-position/move-camera-to-position.json';
|
||||
import moveObjectBackAndForth from '../fixtures/move-object-back-and-forth/move-object-back-and-forth.json';
|
||||
import moveObjectInCircle from '../fixtures/move-object-in-circle/move-object-in-circle.json';
|
||||
import moveObjectTowardPosition from '../fixtures/move-object-toward-position/move-object-toward-position.json';
|
||||
import moveObjectWithMouseJoint from '../fixtures/move-object-with-mouse-joint/move-object-with-mouse-joint.json';
|
||||
import moveObjectWithPhysics from '../fixtures/move-object-with-physics/move-object-with-physics.json';
|
||||
import multiplayerPlatformerWithGamepads from '../fixtures/multiplayer-platformer-with-gamepads/multiplayer-platformer-with-gamepads.json';
|
||||
import multitouch from '../fixtures/multitouch/multitouch.json';
|
||||
import objectGravity from '../fixtures/object-gravity/object-gravity.json';
|
||||
import objectSelection from '../fixtures/object-selection/object-selection.json';
|
||||
import objectsTimers from '../fixtures/objects-timers/objects-timers.json';
|
||||
import openUrlInBrowser from '../fixtures/open-url-in-browser/open-url-in-browser.json';
|
||||
import pairs from '../fixtures/pairs/pairs.json';
|
||||
import parallax from '../fixtures/parallax/parallax.json';
|
||||
import parallaxScrolling from '../fixtures/parallax-scrolling/parallax-scrolling.json';
|
||||
import parseJsonFromApi from '../fixtures/parse-json-from-api/parse-json-from-api.json';
|
||||
import parseJsonString from '../fixtures/parse-json-string/parse-json-string.json';
|
||||
import particlesExplosions from '../fixtures/particles-explosions/particles-explosions.json';
|
||||
import particlesVariousEffects from '../fixtures/particles-various-effects/particles-various-effects.json';
|
||||
import pathfinding from '../fixtures/pathfinding/pathfinding.json';
|
||||
import pathfindingBasics from '../fixtures/pathfinding-basics/pathfinding-basics.json';
|
||||
import physics from '../fixtures/physics/physics.json';
|
||||
import physicsJointsDemo from '../fixtures/physics-joints-demo/physics-joints-demo.json';
|
||||
import physicsJointsSettingsDemo from '../fixtures/physics-joints-settings-demo/physics-joints-settings-demo.json';
|
||||
import pinObjectToAnother from '../fixtures/pin-object-to-another/pin-object-to-another.json';
|
||||
import pinObjectToAnotherMultipleParents from '../fixtures/pin-object-to-another-multiple-parents/pin-object-to-another-multiple-parents.json';
|
||||
import pixelPerfectPlatformGame from '../fixtures/pixel-perfect-platform-game/pixel-perfect-platform-game.json';
|
||||
import planeAndClouds from '../fixtures/plane-and-clouds/plane-and-clouds.json';
|
||||
import platformer from '../fixtures/platformer/platformer.json';
|
||||
import platformerDoubleJump from '../fixtures/platformer-double-jump/platformer-double-jump.json';
|
||||
import playMusicOnMobile from '../fixtures/play-music-on-mobile/play-music-on-mobile.json';
|
||||
import playStopSpriteAnimation from '../fixtures/play-stop-sprite-animation/play-stop-sprite-animation.json';
|
||||
import racingGame from '../fixtures/racing-game/racing-game.json';
|
||||
import rain from '../fixtures/rain/rain.json';
|
||||
import ragdoll from '../fixtures/ragdoll/ragdoll.json';
|
||||
import randomColorPicker from '../fixtures/random-color-picker/random-color-picker.json';
|
||||
import rotateTowardMouse from '../fixtures/rotate-toward-mouse/rotate-toward-mouse.json';
|
||||
import rotateTowardPosition from '../fixtures/rotate-toward-position/rotate-toward-position.json';
|
||||
import rotateWithKeypress from '../fixtures/rotate-with-keypress/rotate-with-keypress.json';
|
||||
import saveLoad from '../fixtures/save-load/save-load.json';
|
||||
import screenShake from '../fixtures/screen-shake/screen-shake.json';
|
||||
import shootBulletInParabola from '../fixtures/shoot-bullet-in-parabola/shoot-bullet-in-parabola.json';
|
||||
import shootBullets from '../fixtures/shoot-bullets/shoot-bullets.json';
|
||||
import shootingBulletsExplanation from '../fixtures/shooting-bullets-explanation/shooting-bullets-explanation.json';
|
||||
import simpleSpaceShooter from '../fixtures/simple-space-shooter/simple-space-shooter.json';
|
||||
import skeletalAnimationDemo from '../fixtures/skeletal-animation-demo/skeletal-animation-demo.json';
|
||||
import snapObjectToGrid from '../fixtures/snap-object-to-grid/snap-object-to-grid.json';
|
||||
import spaceInvaders from '../fixtures/space-invaders/space-invaders.json';
|
||||
import spaceShooter from '../fixtures/space-shooter/space-shooter.json';
|
||||
import splashScreen from '../fixtures/splash-screen/splash-screen.json';
|
||||
import spriteFadeInOut from '../fixtures/sprite-fade-in-out/sprite-fade-in-out.json';
|
||||
import textEntryObject from '../fixtures/text-entry-object/text-entry-object.json';
|
||||
import textFadeInOut from '../fixtures/text-fade-in-out/text-fade-in-out.json';
|
||||
import textToSpeech from '../fixtures/text-to-speech/text-to-speech.json';
|
||||
import toggleMusicPlaySound from '../fixtures/toggle-music-play-sound/toggle-music-play-sound.json';
|
||||
import typeOnTextEffect from '../fixtures/type-on-text-effect/type-on-text-effect.json';
|
||||
import videoPlayer from '../fixtures/video-player/video-player.json';
|
||||
import zDepth from '../fixtures/z-depth/z-depth.json';
|
||||
import zombieLaser from '../fixtures/zombie-laser/zombie-laser.json';
|
||||
import browserExampleFiles from './BrowserExampleFiles';
|
||||
|
||||
export default class BrowserProjectOpener {
|
||||
static readInternalFile(url) {
|
||||
if (url === 'example://admob') {
|
||||
return Promise.resolve(admob);
|
||||
} else if (url === 'example://advanced-shape-based-painter') {
|
||||
return Promise.resolve(advancedShapeBasedPainter);
|
||||
} else if (url === 'example://animation-speed-scale') {
|
||||
return Promise.resolve(animationSpeedScale);
|
||||
} else if (url === 'example://asteroids') {
|
||||
return Promise.resolve(asteroids);
|
||||
} else if (url === 'example://basic-ai-with-pathfinding') {
|
||||
return Promise.resolve(basicAiWithPathfinding);
|
||||
} else if (url === 'example://basic-artificial-intelligence') {
|
||||
return Promise.resolve(basicArtificialIntelligence);
|
||||
} else if (url === 'example://basic-topdown-car-driving') {
|
||||
return Promise.resolve(basicTopdownCarDriving);
|
||||
} else if (url === 'example://betabox-basics-learning-experience') {
|
||||
return Promise.resolve(betaboxBasicsLearningExperience);
|
||||
} else if (url === 'example://bomb-the-crate') {
|
||||
return Promise.resolve(bombTheCrate);
|
||||
} else if (url === 'example://bouncing-ball-and-rope') {
|
||||
return Promise.resolve(bouncingBallAndRope);
|
||||
} else if (url === 'example://breakout') {
|
||||
return Promise.resolve(breakout);
|
||||
} else if (url === 'example://buttons') {
|
||||
return Promise.resolve(buttons);
|
||||
} else if (url === 'example://car-physics') {
|
||||
return Promise.resolve(carPhysics);
|
||||
} else if (url === 'example://pin-object-to-another') {
|
||||
return Promise.resolve(pinObjectToAnother);
|
||||
} else if (url === 'example://pin-object-to-another-multiple-parents') {
|
||||
return Promise.resolve(pinObjectToAnotherMultipleParents);
|
||||
} else if (url === 'example://pixel-perfect-platform-game') {
|
||||
return Promise.resolve(pixelPerfectPlatformGame);
|
||||
} else if (url === 'example://plane-and-clouds') {
|
||||
return Promise.resolve(planeAndClouds);
|
||||
} else if (url === 'example://center-object-within-another') {
|
||||
return Promise.resolve(centerObjectWithinAnother);
|
||||
} else if (url === 'example://change-position-of-object') {
|
||||
return Promise.resolve(changePositionOfObject);
|
||||
} else if (url === 'example://change-scale-of-sprites') {
|
||||
return Promise.resolve(changeScaleOfSprites);
|
||||
} else if (url === 'example://change-sprite-animation') {
|
||||
return Promise.resolve(changeSpriteAnimation);
|
||||
} else if (url === 'example://change-sprite-color') {
|
||||
return Promise.resolve(changeSpriteColor);
|
||||
} else if (url === 'example://character-selection') {
|
||||
return Promise.resolve(characterSelection);
|
||||
} else if (url === 'example://device-orientation-ballgame') {
|
||||
return Promise.resolve(deviceOrientationBallgame);
|
||||
} else if (url === 'example://device-orientation-compass') {
|
||||
return Promise.resolve(deviceOrientationCompass);
|
||||
} else if (url === 'example://device-vibration') {
|
||||
return Promise.resolve(deviceVibration);
|
||||
} else if (url === 'example://dialogue-tree-with-yarn') {
|
||||
return Promise.resolve(dialogueTreeWithYarn);
|
||||
} else if (url === 'example://downhill-bike-physics-demo') {
|
||||
return Promise.resolve(downhillBikePhysicsDemo);
|
||||
} else if (url === 'example://create-object-with-mouseclick') {
|
||||
return Promise.resolve(createObjectWithMouseclick);
|
||||
} else if (url === 'example://custom-font') {
|
||||
return Promise.resolve(customFont);
|
||||
} else if (url === 'example://customize-keys-with-lastpressedkey') {
|
||||
return Promise.resolve(customizeKeysWithLastpressedkey);
|
||||
} else if (url === 'example://custom-mouse-pointer') {
|
||||
return Promise.resolve(customMousePointer);
|
||||
} else if (url === 'example://drag-camera-with-mouse') {
|
||||
return Promise.resolve(dragCameraWithMouse);
|
||||
} else if (url === 'example://drop-collect-items-from-storage') {
|
||||
return Promise.resolve(dropCollectItemsFromStorage);
|
||||
} else if (url === 'example://endless-up-runner') {
|
||||
return Promise.resolve(endlessUpRunner);
|
||||
} else if (url === 'example://exit-app') {
|
||||
return Promise.resolve(exitApp);
|
||||
} else if (url === 'example://facebook-instant-game') {
|
||||
return Promise.resolve(facebookInstantGame);
|
||||
} else if (url === 'example://filesystem-create-directory') {
|
||||
return Promise.resolve(filesystemCreateDirectory);
|
||||
} else if (url === 'example://find-diagonals') {
|
||||
return Promise.resolve(findDiagonals);
|
||||
} else if (url === 'example://geodash') {
|
||||
return Promise.resolve(geodash);
|
||||
} else if (url === 'example://health-bar') {
|
||||
return Promise.resolve(healthBar);
|
||||
} else if (url === 'example://infinite-scrolling-background') {
|
||||
return Promise.resolve(infiniteScrollingBackground);
|
||||
} else if (url === 'example://inventory-system') {
|
||||
return Promise.resolve(inventorySystem);
|
||||
} else if (url === 'example://isometric-game') {
|
||||
return Promise.resolve(isometricGame);
|
||||
} else if (url === 'example://javascript-blocks-in-platformer') {
|
||||
return Promise.resolve(javascriptBlocksInPlatformer);
|
||||
} else if (url === 'example://keyboard-practice') {
|
||||
return Promise.resolve(keyboardPractice);
|
||||
} else if (url === 'example://level-editor') {
|
||||
return Promise.resolve(levelEditor);
|
||||
} else if (url === 'example://load-image-from-url') {
|
||||
return Promise.resolve(loadImageFromUrl);
|
||||
} else if (url === 'example://magnet') {
|
||||
return Promise.resolve(magnet);
|
||||
} else if (url === 'example://manipulate-text-object') {
|
||||
return Promise.resolve(manipulateTextObject);
|
||||
} else if (url === 'example://menu-with-functions-and-text-effects') {
|
||||
return Promise.resolve(menuWithFunctionsAndTextEffects);
|
||||
} else if (url === 'example://move-camera-to-position') {
|
||||
return Promise.resolve(moveCameraToPosition);
|
||||
} else if (url === 'example://move-object-back-and-forth') {
|
||||
return Promise.resolve(moveObjectBackAndForth);
|
||||
} else if (url === 'example://move-object-in-circle') {
|
||||
return Promise.resolve(moveObjectInCircle);
|
||||
} else if (url === 'example://move-object-toward-position') {
|
||||
return Promise.resolve(moveObjectTowardPosition);
|
||||
} else if (url === 'example://move-object-with-mouse-joint') {
|
||||
return Promise.resolve(moveObjectWithMouseJoint);
|
||||
} else if (url === 'example://move-object-with-physics') {
|
||||
return Promise.resolve(moveObjectWithPhysics);
|
||||
} else if (url === 'example://multiplayer-platformer-with-gamepads') {
|
||||
return Promise.resolve(multiplayerPlatformerWithGamepads);
|
||||
} else if (url === 'example://multitouch') {
|
||||
return Promise.resolve(multitouch);
|
||||
} else if (url === 'example://object-gravity') {
|
||||
return Promise.resolve(objectGravity);
|
||||
} else if (url === 'example://object-selection') {
|
||||
return Promise.resolve(objectSelection);
|
||||
} else if (url === 'example://objects-timers') {
|
||||
return Promise.resolve(objectsTimers);
|
||||
} else if (url === 'example://open-url-in-browser') {
|
||||
return Promise.resolve(openUrlInBrowser);
|
||||
} else if (url === 'example://pairs') {
|
||||
return Promise.resolve(pairs);
|
||||
} else if (url === 'example://parallax') {
|
||||
return Promise.resolve(parallax);
|
||||
} else if (url === 'example://parallax-scrolling') {
|
||||
return Promise.resolve(parallaxScrolling);
|
||||
} else if (url === 'example://parse-json-from-api') {
|
||||
return Promise.resolve(parseJsonFromApi);
|
||||
} else if (url === 'example://parse-json-string') {
|
||||
return Promise.resolve(parseJsonString);
|
||||
} else if (url === 'example://particles-explosions') {
|
||||
return Promise.resolve(particlesExplosions);
|
||||
} else if (url === 'example://particles-various-effects') {
|
||||
return Promise.resolve(particlesVariousEffects);
|
||||
} else if (url === 'example://pathfinding') {
|
||||
return Promise.resolve(pathfinding);
|
||||
} else if (url === 'example://pathfinding-basics') {
|
||||
return Promise.resolve(pathfindingBasics);
|
||||
} else if (url === 'example://physics') {
|
||||
return Promise.resolve(physics);
|
||||
} else if (url === 'example://physics-joints-demo') {
|
||||
return Promise.resolve(physicsJointsDemo);
|
||||
} else if (url === 'example://physics-joints-settings-demo') {
|
||||
return Promise.resolve(physicsJointsSettingsDemo);
|
||||
} else if (url === 'example://platformer') {
|
||||
return Promise.resolve(platformer);
|
||||
} else if (url === 'example://platformer-double-jump') {
|
||||
return Promise.resolve(platformerDoubleJump);
|
||||
} else if (url === 'example://play-music-on-mobile') {
|
||||
return Promise.resolve(playMusicOnMobile);
|
||||
} else if (url === 'example://play-stop-sprite-animation') {
|
||||
return Promise.resolve(playStopSpriteAnimation);
|
||||
} else if (url === 'example://racing-game') {
|
||||
return Promise.resolve(racingGame);
|
||||
} else if (url === 'example://rain') {
|
||||
return Promise.resolve(rain);
|
||||
} else if (url === 'example://ragdoll') {
|
||||
return Promise.resolve(ragdoll);
|
||||
} else if (url === 'example://random-color-picker') {
|
||||
return Promise.resolve(randomColorPicker);
|
||||
} else if (url === 'example://rotate-toward-mouse') {
|
||||
return Promise.resolve(rotateTowardMouse);
|
||||
} else if (url === 'example://rotate-toward-position') {
|
||||
return Promise.resolve(rotateTowardPosition);
|
||||
} else if (url === 'example://rotate-with-keypress') {
|
||||
return Promise.resolve(rotateWithKeypress);
|
||||
} else if (url === 'example://save-load') {
|
||||
return Promise.resolve(saveLoad);
|
||||
} else if (url === 'example://screen-shake') {
|
||||
return Promise.resolve(screenShake);
|
||||
} else if (url === 'example://shoot-bullet-in-parabola') {
|
||||
return Promise.resolve(shootBulletInParabola);
|
||||
} else if (url === 'example://shoot-bullets') {
|
||||
return Promise.resolve(shootBullets);
|
||||
} else if (url === 'example://shooting-bullets-explanation') {
|
||||
return Promise.resolve(shootingBulletsExplanation);
|
||||
} else if (url === 'example://simple-space-shooter') {
|
||||
return Promise.resolve(simpleSpaceShooter);
|
||||
} else if (url === 'example://skeletal-animation-demo') {
|
||||
return Promise.resolve(skeletalAnimationDemo);
|
||||
} else if (url === 'example://snap-object-to-grid') {
|
||||
return Promise.resolve(snapObjectToGrid);
|
||||
} else if (url === 'example://space-invaders') {
|
||||
return Promise.resolve(spaceInvaders);
|
||||
} else if (url === 'example://space-shooter') {
|
||||
return Promise.resolve(spaceShooter);
|
||||
} else if (url === 'example://splash-screen') {
|
||||
return Promise.resolve(splashScreen);
|
||||
} else if (url === 'example://sprite-fade-in-out') {
|
||||
return Promise.resolve(spriteFadeInOut);
|
||||
} else if (url === 'example://text-entry-object') {
|
||||
return Promise.resolve(textEntryObject);
|
||||
} else if (url === 'example://text-fade-in-out') {
|
||||
return Promise.resolve(textFadeInOut);
|
||||
} else if (url === 'example://text-to-speech') {
|
||||
return Promise.resolve(textToSpeech);
|
||||
} else if (url === 'example://toggle-music-play-sound') {
|
||||
return Promise.resolve(toggleMusicPlaySound);
|
||||
} else if (url === 'example://type-on-text-effect') {
|
||||
return Promise.resolve(typeOnTextEffect);
|
||||
} else if (url === 'example://video-player') {
|
||||
return Promise.resolve(videoPlayer);
|
||||
} else if (url === 'example://z-depth') {
|
||||
return Promise.resolve(zDepth);
|
||||
} else if (url === 'example://zombie-laser') {
|
||||
return Promise.resolve(zombieLaser);
|
||||
}
|
||||
if (browserExampleFiles[url])
|
||||
return Promise.resolve(browserExampleFiles[url]);
|
||||
|
||||
return Promise.reject(`Unknown built-in game with URL ${url}`);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Column, Line } from '../UI/Grid';
|
||||
import Window from '../Utils/Window';
|
||||
import { serializeToJSObject } from '../Utils/Serializer';
|
||||
import { showErrorBox } from '../UI/Messages/MessageBox';
|
||||
import Text from '../UI/Text';
|
||||
|
||||
export default class BrowserSaveDialog extends Component {
|
||||
_download = () => {
|
||||
@@ -51,10 +52,12 @@ export default class BrowserSaveDialog extends Component {
|
||||
<Dialog actions={actions} open={open} onRequestClose={onClose}>
|
||||
<Column noMargin>
|
||||
<Line>
|
||||
<Trans>
|
||||
You can download the file of your game to continue working on it
|
||||
using the full GDevelop version:
|
||||
</Trans>
|
||||
<Text>
|
||||
<Trans>
|
||||
You can download the file of your game to continue working on it
|
||||
using the full GDevelop version:
|
||||
</Trans>
|
||||
</Text>
|
||||
</Line>
|
||||
<Line>
|
||||
<Column expand>
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import {
|
||||
ResponsiveWindowMeasurer,
|
||||
type WidthType,
|
||||
} from '../UI/Reponsive/ResponsiveWindowMeasurer';
|
||||
import SemiControlledTextField from '../UI/SemiControlledTextField';
|
||||
import InlineCheckbox from '../UI/InlineCheckbox';
|
||||
import ResourceSelector from '../ResourcesList/ResourceSelector';
|
||||
@@ -82,6 +86,7 @@ type MandatoryProps = {|
|
||||
onInstancesModified?: Instances => void,
|
||||
instances: Instances,
|
||||
schema: Schema,
|
||||
windowWidth?: WidthType,
|
||||
mode?: 'column' | 'row',
|
||||
|};
|
||||
|
||||
@@ -342,11 +347,15 @@ export default class PropertiesEditor extends React.Component<Props, {||}> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { mode } = this.props;
|
||||
const { mode, windowWidth } = this.props;
|
||||
|
||||
return (
|
||||
const renderFields = (windowWidth: WidthType) => (
|
||||
<div
|
||||
style={mode === 'row' ? styles.rowContainer : styles.columnContainer}
|
||||
style={
|
||||
mode === 'row' && windowWidth !== 'small'
|
||||
? styles.rowContainer
|
||||
: styles.columnContainer
|
||||
}
|
||||
>
|
||||
{this.props.schema.map(field => {
|
||||
if (field.children) {
|
||||
@@ -357,6 +366,7 @@ export default class PropertiesEditor extends React.Component<Props, {||}> {
|
||||
schema={field.children}
|
||||
instances={this.props.instances}
|
||||
mode="row"
|
||||
windowWidth={windowWidth}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -369,6 +379,7 @@ export default class PropertiesEditor extends React.Component<Props, {||}> {
|
||||
schema={field.children}
|
||||
instances={this.props.instances}
|
||||
mode="column"
|
||||
windowWidth={windowWidth}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -386,5 +397,15 @@ export default class PropertiesEditor extends React.Component<Props, {||}> {
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
|
||||
if (windowWidth) {
|
||||
return renderFields(windowWidth);
|
||||
}
|
||||
|
||||
return (
|
||||
<ResponsiveWindowMeasurer>
|
||||
{windowWidth => renderFields(windowWidth)}
|
||||
</ResponsiveWindowMeasurer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import * as React from 'react';
|
||||
import ResourcesList from '../ResourcesList';
|
||||
import ResourcePropertiesEditor from './ResourcePropertiesEditor';
|
||||
import Toolbar from './Toolbar';
|
||||
import EditorMosaic, { MosaicWindow } from '../UI/EditorMosaic';
|
||||
import EditorMosaic from '../UI/EditorMosaic';
|
||||
import InfoBar from '../UI/Messages/InfoBar';
|
||||
import ResourcesLoader from '../ResourcesLoader';
|
||||
import optionalRequire from '../Utils/OptionalRequire';
|
||||
@@ -105,7 +105,7 @@ export default class ResourcesEditor extends React.Component<Props, State> {
|
||||
|
||||
openProperties = () => {
|
||||
if (!this.editorMosaic) return;
|
||||
if (!this.editorMosaic.openEditor('properties')) {
|
||||
if (!this.editorMosaic.openEditor('properties', 'start', 66)) {
|
||||
this.setState({
|
||||
showPropertiesInfoBar: true,
|
||||
});
|
||||
@@ -134,12 +134,10 @@ export default class ResourcesEditor extends React.Component<Props, State> {
|
||||
const { selectedResource } = this.state;
|
||||
|
||||
const editors = {
|
||||
properties: (
|
||||
<MosaicWindow
|
||||
title={<Trans>Properties</Trans>}
|
||||
// Pass resources to force MosaicWindow update when selectedResource is changed
|
||||
resources={selectedResource ? [selectedResource] : []}
|
||||
>
|
||||
properties: {
|
||||
type: 'secondary',
|
||||
title: <Trans>Properties</Trans>,
|
||||
renderEditor: () => (
|
||||
<ResourcePropertiesEditor
|
||||
key={selectedResource ? selectedResource.ptr : undefined}
|
||||
resources={selectedResource ? [selectedResource] : []}
|
||||
@@ -156,18 +154,22 @@ export default class ResourcesEditor extends React.Component<Props, State> {
|
||||
onChooseResource={onChooseResource}
|
||||
resourceSources={resourceSources}
|
||||
/>
|
||||
</MosaicWindow>
|
||||
),
|
||||
'resources-list': (
|
||||
<ResourcesList
|
||||
project={project}
|
||||
onDeleteResource={this.deleteResource}
|
||||
onRenameResource={onRenameResource}
|
||||
onSelectResource={this._onResourceSelected}
|
||||
selectedResource={selectedResource}
|
||||
ref={resourcesList => (this._resourcesList = resourcesList)}
|
||||
/>
|
||||
),
|
||||
),
|
||||
},
|
||||
'resources-list': {
|
||||
type: 'primary',
|
||||
noTitleBar: true,
|
||||
renderEditor: () => (
|
||||
<ResourcesList
|
||||
project={project}
|
||||
onDeleteResource={this.deleteResource}
|
||||
onRenameResource={onRenameResource}
|
||||
onSelectResource={this._onResourceSelected}
|
||||
selectedResource={selectedResource}
|
||||
ref={resourcesList => (this._resourcesList = resourcesList)}
|
||||
/>
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -179,7 +181,7 @@ export default class ResourcesEditor extends React.Component<Props, State> {
|
||||
direction: 'row',
|
||||
first: 'properties',
|
||||
second: 'resources-list',
|
||||
splitPercentage: 75,
|
||||
splitPercentage: 66,
|
||||
}}
|
||||
/>
|
||||
<InfoBar
|
||||
|
||||
@@ -28,6 +28,8 @@ const styles = {
|
||||
},
|
||||
};
|
||||
|
||||
const getResourceName = (resource: gdResource) => resource.getName();
|
||||
|
||||
type State = {|
|
||||
renamedResource: ?gdResource,
|
||||
searchText: string,
|
||||
@@ -37,7 +39,7 @@ type State = {|
|
||||
type Props = {|
|
||||
project: gdProject,
|
||||
selectedResource: ?gdResource,
|
||||
onSelectResource: (resource: gdResource) => void,
|
||||
onSelectResource: (resource: ?gdResource) => void,
|
||||
onDeleteResource: (resource: gdResource) => void,
|
||||
onRenameResource: (
|
||||
resource: gdResource,
|
||||
@@ -164,7 +166,9 @@ export default class ResourcesList extends React.Component<Props, State> {
|
||||
{
|
||||
renamedResource: resource,
|
||||
},
|
||||
() => this.sortableList.getWrappedInstance().forceUpdateGrid()
|
||||
() => {
|
||||
if (this.sortableList) this.sortableList.forceUpdateGrid();
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -194,16 +198,21 @@ export default class ResourcesList extends React.Component<Props, State> {
|
||||
});
|
||||
};
|
||||
|
||||
_move = (oldIndex: number, newIndex: number) => {
|
||||
const { project } = this.props;
|
||||
_moveSelectionTo = (destinationResource: gdResource) => {
|
||||
const { project, selectedResource } = this.props;
|
||||
if (!selectedResource) return;
|
||||
|
||||
project.getResourcesManager().moveResource(oldIndex, newIndex);
|
||||
const resourcesManager = project.getResourcesManager();
|
||||
resourcesManager.moveResource(
|
||||
resourcesManager.getResourcePosition(selectedResource.getName()),
|
||||
resourcesManager.getResourcePosition(destinationResource.getName())
|
||||
);
|
||||
this.forceUpdateList();
|
||||
};
|
||||
|
||||
forceUpdateList = () => {
|
||||
this.forceUpdate();
|
||||
this.sortableList.getWrappedInstance().forceUpdateGrid();
|
||||
if (this.sortableList) this.sortableList.forceUpdateGrid();
|
||||
};
|
||||
|
||||
_renderResourceMenuTemplate = (resource: gdResource, _index: number) => {
|
||||
@@ -348,17 +357,15 @@ export default class ResourcesList extends React.Component<Props, State> {
|
||||
fullList={filteredList}
|
||||
width={width}
|
||||
height={height}
|
||||
selectedItem={selectedResource}
|
||||
getItemName={getResourceName}
|
||||
selectedItems={selectedResource ? [selectedResource] : []}
|
||||
onItemSelected={onSelectResource}
|
||||
renamedItem={this.state.renamedResource}
|
||||
onRename={this._rename}
|
||||
onSortEnd={({ oldIndex, newIndex }) =>
|
||||
this._move(oldIndex, newIndex)
|
||||
}
|
||||
onMoveSelectionToItem={this._moveSelectionTo}
|
||||
buildMenuTemplate={this._renderResourceMenuTemplate}
|
||||
helperClass="sortable-helper"
|
||||
distance={20}
|
||||
erroredItems={this.state.resourcesWithErrors}
|
||||
reactDndType="GD_RESOURCE"
|
||||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
|
||||
@@ -14,7 +14,6 @@ type Props = {|
|
||||
onNetworkPreview: () => void,
|
||||
onOpenDebugger: () => void,
|
||||
showPreviewButton: boolean,
|
||||
showObjectsList: boolean,
|
||||
openObjectsList: () => void,
|
||||
openObjectGroupsList: () => void,
|
||||
openProperties: () => void,
|
||||
@@ -71,20 +70,16 @@ export class Toolbar extends PureComponent<Props> {
|
||||
/>
|
||||
)}
|
||||
{this.props.showPreviewButton && <ToolbarSeparator />}
|
||||
{this.props.showObjectsList && (
|
||||
<ToolbarIcon
|
||||
onClick={this.props.openObjectsList}
|
||||
src="res/ribbon_default/objects64.png"
|
||||
tooltip={t`Open the objects editor`}
|
||||
/>
|
||||
)}
|
||||
{this.props.showObjectsList && (
|
||||
<ToolbarIcon
|
||||
onClick={this.props.openObjectGroupsList}
|
||||
src={'res/ribbon_default/objectsgroups64.png'}
|
||||
tooltip={t`Open the objects groups editor`}
|
||||
/>
|
||||
)}
|
||||
<ToolbarIcon
|
||||
onClick={this.props.openObjectsList}
|
||||
src="res/ribbon_default/objects64.png"
|
||||
tooltip={t`Open the objects editor`}
|
||||
/>
|
||||
<ToolbarIcon
|
||||
onClick={this.props.openObjectGroupsList}
|
||||
src={'res/ribbon_default/objectsgroups64.png'}
|
||||
tooltip={t`Open the objects groups editor`}
|
||||
/>
|
||||
<ToolbarIcon
|
||||
onClick={this.props.openProperties}
|
||||
src="res/ribbon_default/editprop32.png"
|
||||
|
||||
+147
-210
@@ -30,13 +30,12 @@ import { passFullSize } from '../UI/FullSizeMeasurer';
|
||||
import { addScrollbars } from '../InstancesEditor/ScrollbarContainer';
|
||||
import { type PreviewOptions } from '../Export/PreviewLauncher.flow';
|
||||
import Drawer from '@material-ui/core/Drawer';
|
||||
import EditorMosaic, { MosaicWindow } from '../UI/EditorMosaic';
|
||||
import EditorMosaic from '../UI/EditorMosaic';
|
||||
import EditorBar, { editorBarHeight } from '../UI/EditorBar';
|
||||
import InfoBar from '../UI/Messages/InfoBar';
|
||||
import ContextMenu from '../UI/Menu/ContextMenu';
|
||||
import { showWarningBox } from '../UI/Messages/MessageBox';
|
||||
import { shortenString } from '../Utils/StringHelpers';
|
||||
import { roundPosition } from '../Utils/GridHelpers';
|
||||
import getObjectByName from '../Utils/GetObjectByName';
|
||||
|
||||
import {
|
||||
@@ -61,7 +60,13 @@ import {
|
||||
} from '../ObjectsList/EnumerateObjects';
|
||||
import TagsButton from '../UI/EditorMosaic/TagsButton';
|
||||
import CloseButton from '../UI/EditorMosaic/CloseButton';
|
||||
import { buildTagsMenuTemplate, getTagsFromString } from '../Utils/TagsHelper';
|
||||
import {
|
||||
type SelectedTags,
|
||||
buildTagsMenuTemplate,
|
||||
getTagsFromString,
|
||||
} from '../Utils/TagsHelper';
|
||||
import { ScreenTypeMeasurer } from '../UI/Reponsive/ScreenTypeMeasurer';
|
||||
import { ResponsiveWindowMeasurer } from '../UI/Reponsive/ResponsiveWindowMeasurer';
|
||||
const gd = global.gd;
|
||||
|
||||
const INSTANCES_CLIPBOARD_KIND = 'Instances';
|
||||
@@ -92,6 +97,25 @@ const instancesDrawerPaperProps = {
|
||||
},
|
||||
};
|
||||
|
||||
const initialEditors = {
|
||||
direction: 'row',
|
||||
first: 'properties',
|
||||
splitPercentage: 23,
|
||||
second: {
|
||||
direction: 'row',
|
||||
first: 'instances-editor',
|
||||
second: 'objects-list',
|
||||
splitPercentage: 77,
|
||||
},
|
||||
};
|
||||
|
||||
const initialEditorsSmallWindow = {
|
||||
direction: 'row',
|
||||
first: 'instances-editor',
|
||||
second: 'objects-list',
|
||||
splitPercentage: 70,
|
||||
};
|
||||
|
||||
type Props = {|
|
||||
initialInstances: gdInitialInstancesContainer,
|
||||
initialUiSettings: Object,
|
||||
@@ -103,7 +127,6 @@ type Props = {|
|
||||
project: gdProject,
|
||||
setToolbar: (?React.Node) => void,
|
||||
showNetworkPreviewButton: boolean,
|
||||
showObjectsList: boolean,
|
||||
showPreviewButton: boolean,
|
||||
resourceSources: Array<ResourceSource>,
|
||||
onChooseResource: ChooseResourceFunction,
|
||||
@@ -124,14 +147,10 @@ type State = {|
|
||||
variablesEditedInstance: ?gdInitialInstance,
|
||||
variablesEditedObject: ?gdObject,
|
||||
selectedObjectNames: Array<string>,
|
||||
newObjectInstancePosition: ?[number, number],
|
||||
newObjectInstanceSceneCoordinates: ?[number, number],
|
||||
|
||||
editedGroup: ?gdObjectGroup,
|
||||
|
||||
// State for "drag'n'dropping" from the objects list to the instances editor:
|
||||
objectDraggedFromList: ?gdObject,
|
||||
canDropDraggedObject: boolean,
|
||||
|
||||
uiSettings: Object,
|
||||
history: HistoryState,
|
||||
|
||||
@@ -140,14 +159,13 @@ type State = {|
|
||||
showPropertiesInfoBar: boolean,
|
||||
|
||||
// State for tags of objects:
|
||||
selectedObjectTags: Array<string>,
|
||||
selectedObjectTags: SelectedTags,
|
||||
|};
|
||||
|
||||
type CopyCutPasteOptions = { useLastCursorPosition?: boolean };
|
||||
|
||||
export default class SceneEditor extends React.Component<Props, State> {
|
||||
static defaultProps = {
|
||||
showObjectsList: true,
|
||||
setToolbar: () => {},
|
||||
};
|
||||
|
||||
@@ -176,13 +194,9 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
variablesEditedInstance: null,
|
||||
variablesEditedObject: null,
|
||||
selectedObjectNames: [],
|
||||
newObjectInstancePosition: null,
|
||||
newObjectInstanceSceneCoordinates: null,
|
||||
editedGroup: null,
|
||||
|
||||
// State for "drag'n'dropping" from the objects list to the instances editor:
|
||||
objectDraggedFromList: null,
|
||||
canDropDraggedObject: false,
|
||||
|
||||
uiSettings: props.initialUiSettings,
|
||||
history: getHistoryInitialState(props.initialInstances, {
|
||||
historyMaxSize: 50,
|
||||
@@ -215,7 +229,6 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
this.props.onOpenDebugger();
|
||||
this.props.onPreview({});
|
||||
}}
|
||||
showObjectsList={this.props.showObjectsList}
|
||||
instancesSelection={this.instancesSelection}
|
||||
openObjectsList={this.openObjectsList}
|
||||
openObjectGroupsList={this.openObjectGroupsList}
|
||||
@@ -256,7 +269,7 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
|
||||
openObjectsList = () => {
|
||||
if (!this.editorMosaic) return;
|
||||
if (!this.editorMosaic.openEditor('objects-list')) {
|
||||
if (!this.editorMosaic.openEditor('objects-list', 'end', 75)) {
|
||||
this.setState({
|
||||
showObjectsListInfoBar: true,
|
||||
});
|
||||
@@ -265,7 +278,7 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
|
||||
openProperties = () => {
|
||||
if (!this.editorMosaic) return;
|
||||
if (!this.editorMosaic.openEditor('properties')) {
|
||||
if (!this.editorMosaic.openEditor('properties', 'start', 25)) {
|
||||
this.setState({
|
||||
showPropertiesInfoBar: true,
|
||||
});
|
||||
@@ -274,7 +287,7 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
|
||||
openObjectGroupsList = () => {
|
||||
if (!this.editorMosaic) return;
|
||||
this.editorMosaic.openEditor('object-groups-list');
|
||||
this.editorMosaic.openEditor('object-groups-list', 'end', 75);
|
||||
};
|
||||
|
||||
toggleInstancesList = () => {
|
||||
@@ -358,77 +371,6 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
this.editObject(project.getObject(objectName));
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when an object is started to be dragged from the object list.
|
||||
* See `_onPointerOverInstancesEditor`, `_onPointerOutInstancesEditor` and
|
||||
* `_onPointerUpInstancesEditor` for the drag'n'drop workflow.
|
||||
*/
|
||||
_onStartDraggingObjectFromList = (object: gdObject) => {
|
||||
// "Hijack" the name of the object that is dragged in the objects list.
|
||||
// We'll then listen to "pointer over" events to see if the object
|
||||
// is the dragged on the instances editor.
|
||||
this.setState({
|
||||
objectDraggedFromList: object,
|
||||
});
|
||||
};
|
||||
|
||||
_onEndDraggingObjectFromList = () => {
|
||||
// If the dragged object is not being dropped on the instances editor,
|
||||
// clear the dragged object so that we don't keep it the state (otherwise
|
||||
// we could think later that a dragging is still occuring when cursor
|
||||
// is over the instances editor).
|
||||
if (!this.state.canDropDraggedObject) {
|
||||
this.setState({
|
||||
objectDraggedFromList: null,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
_onPointerOverInstancesEditor = () => {
|
||||
// If an object is dragged, and cursor is over the instances editor,
|
||||
// mark in the state that we can drop an instance of this object.
|
||||
if (this.state.objectDraggedFromList && !this.state.canDropDraggedObject) {
|
||||
this.setState({
|
||||
canDropDraggedObject: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
_onPointerOutInstancesEditor = () => {
|
||||
// If cursor is going out of the instances editor,
|
||||
// mark in the state that we cannot drop an instance anymore.
|
||||
if (this.state.canDropDraggedObject) {
|
||||
this.setState({
|
||||
canDropDraggedObject: false,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
_onPointerUpInstancesEditor = () => {
|
||||
if (this.state.canDropDraggedObject) {
|
||||
if (this.editor) {
|
||||
const cursorPosition = this.editor.getLastCursorPosition();
|
||||
|
||||
if (this.state.objectDraggedFromList)
|
||||
this._addInstance(
|
||||
cursorPosition[0],
|
||||
cursorPosition[1],
|
||||
this.state.objectDraggedFromList.getName()
|
||||
);
|
||||
}
|
||||
// Wait 30ms after dropping the object before reseting the canDropDraggedObject state boolean
|
||||
// to ensure ObjectsList will be prevented to actually move object in the list.
|
||||
setTimeout(
|
||||
() =>
|
||||
this.setState({
|
||||
canDropDraggedObject: false,
|
||||
objectDraggedFromList: null,
|
||||
}),
|
||||
30 // This value is very conservative, and timeout may not be needed at all
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
editGroup = (group: ?gdObjectGroup) => {
|
||||
this.setState({ editedGroup: group });
|
||||
};
|
||||
@@ -491,7 +433,7 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
|
||||
// Remember where to create the instance, when the object will be created
|
||||
this.setState({
|
||||
newObjectInstancePosition: this.editor.getLastCursorPosition(),
|
||||
newObjectInstanceSceneCoordinates: this.editor.getLastCursorSceneCoordinates(),
|
||||
});
|
||||
|
||||
if (this._objectsList)
|
||||
@@ -504,36 +446,17 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
}
|
||||
|
||||
const objectSelected = this.state.selectedObjectNames[0];
|
||||
const cursorPosition = this.editor.getLastCursorPosition();
|
||||
this._addInstance(cursorPosition[0], cursorPosition[1], objectSelected);
|
||||
const cursorPosition = this.editor.getLastCursorSceneCoordinates();
|
||||
this._addInstance(cursorPosition, objectSelected);
|
||||
this.setState({
|
||||
selectedObjectNames: [objectSelected],
|
||||
});
|
||||
};
|
||||
|
||||
_addInstance = (x: number, y: number, objectName: string) => {
|
||||
if (!objectName) return;
|
||||
_addInstance = (pos: [number, number], objectName: string) => {
|
||||
if (!objectName || !this.editor) return;
|
||||
|
||||
const instance = this.props.initialInstances.insertNewInitialInstance();
|
||||
instance.setObjectName(objectName);
|
||||
if (this.state.uiSettings.grid) {
|
||||
x = roundPosition(
|
||||
x,
|
||||
this.state.uiSettings.gridWidth,
|
||||
this.state.uiSettings.gridOffsetX
|
||||
);
|
||||
y = roundPosition(
|
||||
y,
|
||||
this.state.uiSettings.gridHeight,
|
||||
this.state.uiSettings.gridOffsetY
|
||||
);
|
||||
}
|
||||
instance.setX(x);
|
||||
instance.setY(y);
|
||||
this.props.initialInstances.iterateOverInstances(this.zOrderFinder);
|
||||
if (this.zOrderFinder) {
|
||||
instance.setZOrder(this.zOrderFinder.getHighestZOrder() + 1);
|
||||
}
|
||||
this.editor.addInstances(pos, [objectName]);
|
||||
this.setState(
|
||||
{
|
||||
selectedObjectNames: [],
|
||||
@@ -543,6 +466,15 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
);
|
||||
};
|
||||
|
||||
_onInstancesAdded = () => {
|
||||
this.setState(
|
||||
{
|
||||
history: saveToHistory(this.state.history, this.props.initialInstances),
|
||||
},
|
||||
() => this.updateToolbar()
|
||||
);
|
||||
};
|
||||
|
||||
_onInstancesSelected = (instances: Array<gdInitialInstance>) => {
|
||||
this.setState({
|
||||
selectedObjectNames: uniq(
|
||||
@@ -600,20 +532,16 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
|
||||
/**
|
||||
* Create an instance of the given object, at the position
|
||||
* previously chosen (see `newObjectInstancePosition`).
|
||||
* previously chosen (see `newObjectInstanceSceneCoordinates`).
|
||||
*/
|
||||
_addNewObjectInstance = (newObjectName: string) => {
|
||||
const { newObjectInstancePosition } = this.state;
|
||||
if (!newObjectInstancePosition) {
|
||||
_addInstanceForNewObject = (newObjectName: string) => {
|
||||
const { newObjectInstanceSceneCoordinates } = this.state;
|
||||
if (!newObjectInstanceSceneCoordinates) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._addInstance(
|
||||
newObjectInstancePosition[0],
|
||||
newObjectInstancePosition[1],
|
||||
newObjectName
|
||||
);
|
||||
this.setState({ newObjectInstancePosition: null });
|
||||
this._addInstance(newObjectInstanceSceneCoordinates, newObjectName);
|
||||
this.setState({ newObjectInstanceSceneCoordinates: null });
|
||||
};
|
||||
|
||||
_onRemoveLayer = (layerName: string, done: boolean => void) => {
|
||||
@@ -784,11 +712,6 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
done(true);
|
||||
};
|
||||
|
||||
_canGroupUseNewName = (groupWithScope: GroupWithContext, newName: string) => {
|
||||
//TODO: implement and launch refactoring (using gd.WholeProjectRefactorer but only on some events)
|
||||
return true;
|
||||
};
|
||||
|
||||
_onRenameGroup = (
|
||||
groupWithContext: GroupWithContext,
|
||||
newName: string,
|
||||
@@ -870,8 +793,8 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
|
||||
if (this.editor) {
|
||||
const position = useLastCursorPosition
|
||||
? this.editor.getLastCursorPosition()
|
||||
: this.editor.getLastContextMenuPosition();
|
||||
? this.editor.getLastCursorSceneCoordinates()
|
||||
: this.editor.getLastContextMenuSceneCoordinates();
|
||||
Clipboard.set(INSTANCES_CLIPBOARD_KIND, {
|
||||
x: position[0],
|
||||
y: position[1],
|
||||
@@ -890,8 +813,8 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
if (!clipboardContent || !this.editor) return;
|
||||
|
||||
const position = useLastCursorPosition
|
||||
? this.editor.getLastCursorPosition()
|
||||
: this.editor.getLastContextMenuPosition();
|
||||
? this.editor.getLastCursorSceneCoordinates()
|
||||
: this.editor.getLastContextMenuSceneCoordinates();
|
||||
const { x, y } = clipboardContent;
|
||||
clipboardContent.instances
|
||||
.map(serializedInstance => {
|
||||
@@ -977,8 +900,10 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
const selectedInstances = this.instancesSelection.getSelectedInstances();
|
||||
|
||||
const editors = {
|
||||
properties: (
|
||||
<MosaicWindow title={<Trans>Properties</Trans>}>
|
||||
properties: {
|
||||
type: 'secondary',
|
||||
title: <Trans>Properties</Trans>,
|
||||
renderEditor: () => (
|
||||
<InstancePropertiesEditor
|
||||
project={project}
|
||||
layout={layout}
|
||||
@@ -990,54 +915,62 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
(this._propertiesEditor = propertiesEditor)
|
||||
}
|
||||
/>
|
||||
</MosaicWindow>
|
||||
),
|
||||
'instances-editor': (
|
||||
<FullSizeInstancesEditor
|
||||
project={project}
|
||||
layout={layout}
|
||||
initialInstances={initialInstances}
|
||||
onAddInstance={this._addInstance}
|
||||
options={this.state.uiSettings}
|
||||
onChangeOptions={this.setUiSettings}
|
||||
instancesSelection={this.instancesSelection}
|
||||
onDeleteSelection={this.deleteSelection}
|
||||
onInstancesSelected={this._onInstancesSelected}
|
||||
onInstancesMoved={this._onInstancesMoved}
|
||||
onInstancesResized={this._onInstancesResized}
|
||||
onInstancesRotated={this._onInstancesRotated}
|
||||
onPointerUp={this._onPointerUpInstancesEditor}
|
||||
onPointerOver={this._onPointerOverInstancesEditor}
|
||||
onPointerOut={this._onPointerOutInstancesEditor}
|
||||
onContextMenu={this._onContextMenu}
|
||||
onCopy={() => this.copySelection({ useLastCursorPosition: true })}
|
||||
onCut={() => this.cutSelection({ useLastCursorPosition: true })}
|
||||
onPaste={() => this.paste({ useLastCursorPosition: true })}
|
||||
onUndo={this.undo}
|
||||
onRedo={this.redo}
|
||||
onZoomOut={this.zoomOut}
|
||||
onZoomIn={this.zoomIn}
|
||||
showDropCursor={this.state.canDropDraggedObject}
|
||||
wrappedEditorRef={editor => (this.editor = editor)}
|
||||
pauseRendering={!isActive}
|
||||
/>
|
||||
),
|
||||
'objects-list': (
|
||||
<MosaicWindow
|
||||
title={<Trans>Objects</Trans>}
|
||||
toolbarControls={[
|
||||
<I18n key="tags">
|
||||
{({ i18n }) => (
|
||||
<TagsButton
|
||||
buildMenuTemplate={() =>
|
||||
this._buildObjectTagsMenuTemplate(i18n)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</I18n>,
|
||||
<CloseButton key="close" />,
|
||||
]}
|
||||
>
|
||||
),
|
||||
},
|
||||
'instances-editor': {
|
||||
type: 'primary',
|
||||
noTitleBar: true,
|
||||
renderEditor: () => (
|
||||
<ScreenTypeMeasurer>
|
||||
{screenType => (
|
||||
<FullSizeInstancesEditor
|
||||
project={project}
|
||||
layout={layout}
|
||||
initialInstances={initialInstances}
|
||||
options={this.state.uiSettings}
|
||||
onChangeOptions={this.setUiSettings}
|
||||
instancesSelection={this.instancesSelection}
|
||||
onDeleteSelection={this.deleteSelection}
|
||||
onInstancesAdded={this._onInstancesAdded}
|
||||
onInstancesSelected={this._onInstancesSelected}
|
||||
onInstancesMoved={this._onInstancesMoved}
|
||||
onInstancesResized={this._onInstancesResized}
|
||||
onInstancesRotated={this._onInstancesRotated}
|
||||
selectedObjectNames={this.state.selectedObjectNames}
|
||||
onContextMenu={this._onContextMenu}
|
||||
onCopy={() =>
|
||||
this.copySelection({ useLastCursorPosition: true })
|
||||
}
|
||||
onCut={() => this.cutSelection({ useLastCursorPosition: true })}
|
||||
onPaste={() => this.paste({ useLastCursorPosition: true })}
|
||||
onUndo={this.undo}
|
||||
onRedo={this.redo}
|
||||
onZoomOut={this.zoomOut}
|
||||
onZoomIn={this.zoomIn}
|
||||
wrappedEditorRef={editor => (this.editor = editor)}
|
||||
pauseRendering={!isActive}
|
||||
screenType={screenType}
|
||||
/>
|
||||
)}
|
||||
</ScreenTypeMeasurer>
|
||||
),
|
||||
},
|
||||
'objects-list': {
|
||||
type: 'secondary',
|
||||
title: <Trans>Objects</Trans>,
|
||||
toolbarControls: [
|
||||
<I18n key="tags">
|
||||
{({ i18n }) => (
|
||||
<TagsButton
|
||||
buildMenuTemplate={() =>
|
||||
this._buildObjectTagsMenuTemplate(i18n)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</I18n>,
|
||||
<CloseButton key="close" />,
|
||||
],
|
||||
renderEditor: () => (
|
||||
<ObjectsList
|
||||
getThumbnail={ObjectsRenderingService.getThumbnail.bind(
|
||||
ObjectsRenderingService
|
||||
@@ -1048,13 +981,10 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
onEditObject={this.props.onEditObject || this.editObject}
|
||||
onDeleteObject={this._onDeleteObject}
|
||||
canRenameObject={this._canObjectOrGroupUseNewName}
|
||||
onObjectCreated={this._addNewObjectInstance}
|
||||
onObjectCreated={this._addInstanceForNewObject}
|
||||
onObjectSelected={this._onObjectSelected}
|
||||
onRenameObject={this._onRenameObject}
|
||||
onObjectPasted={() => this.updateBehaviorsSharedData()}
|
||||
onStartDraggingObject={this._onStartDraggingObjectFromList}
|
||||
onEndDraggingObject={this._onEndDraggingObjectFromList}
|
||||
canMoveObjects={!this.state.canDropDraggedObject}
|
||||
selectedObjectTags={this.state.selectedObjectTags}
|
||||
onChangeSelectedObjectTags={selectedObjectTags =>
|
||||
this.setState({
|
||||
@@ -1064,10 +994,12 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
getAllObjectTags={this._getAllObjectTags}
|
||||
ref={objectsList => (this._objectsList = objectsList)}
|
||||
/>
|
||||
</MosaicWindow>
|
||||
),
|
||||
'object-groups-list': (
|
||||
<MosaicWindow title={<Trans>Object Groups</Trans>}>
|
||||
),
|
||||
},
|
||||
'object-groups-list': {
|
||||
type: 'secondary',
|
||||
title: <Trans>Object Groups</Trans>,
|
||||
renderEditor: () => (
|
||||
<ObjectGroupsList
|
||||
globalObjectGroups={project.getObjectGroups()}
|
||||
objectGroups={layout.getObjectGroups()}
|
||||
@@ -1076,26 +1008,25 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
onRenameGroup={this._onRenameGroup}
|
||||
canRenameGroup={this._canObjectOrGroupUseNewName}
|
||||
/>
|
||||
</MosaicWindow>
|
||||
),
|
||||
),
|
||||
},
|
||||
};
|
||||
return (
|
||||
<div style={styles.container}>
|
||||
<EditorMosaic
|
||||
editors={editors}
|
||||
ref={editorMosaic => (this.editorMosaic = editorMosaic)}
|
||||
initialNodes={{
|
||||
direction: 'row',
|
||||
first: 'properties',
|
||||
splitPercentage: 23,
|
||||
second: {
|
||||
direction: 'row',
|
||||
first: 'instances-editor',
|
||||
second: this.props.showObjectsList ? 'objects-list' : null,
|
||||
splitPercentage: 77,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<ResponsiveWindowMeasurer>
|
||||
{windowWidth => (
|
||||
<EditorMosaic
|
||||
editors={editors}
|
||||
limitToOneSecondaryEditor={windowWidth === 'small'}
|
||||
initialNodes={
|
||||
windowWidth === 'small'
|
||||
? initialEditorsSmallWindow
|
||||
: initialEditors
|
||||
}
|
||||
ref={editorMosaic => (this.editorMosaic = editorMosaic)}
|
||||
/>
|
||||
)}
|
||||
</ResponsiveWindowMeasurer>
|
||||
{this.state.editedObjectWithContext && (
|
||||
<ObjectEditorDialog
|
||||
open
|
||||
@@ -1186,6 +1117,12 @@ export default class SceneEditor extends React.Component<Props, State> {
|
||||
to add an instance of it.
|
||||
</Trans>
|
||||
}
|
||||
touchScreenMessage={
|
||||
<Trans>
|
||||
Drag and Drop the object icon to the scene or long press to show
|
||||
options to edit the object.
|
||||
</Trans>
|
||||
}
|
||||
show={!!this.state.selectedObjectNames.length}
|
||||
/>
|
||||
<InfoBar
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import React from 'react';
|
||||
import SvgIcon from '@material-ui/core/SvgIcon';
|
||||
|
||||
// This is "AccountTree" material icon.
|
||||
export default React.memo(props => (
|
||||
<SvgIcon {...props}>
|
||||
<path d="M22 11V3h-7v3H9V3H2v8h7V8h2v10h4v3h7v-8h-7v3h-2V8h2v3z" />
|
||||
</SvgIcon>
|
||||
));
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Component } from 'react';
|
||||
import MultiBackend from 'react-dnd-multi-backend';
|
||||
import HTML5toTouch from 'react-dnd-multi-backend/lib/HTML5toTouch';
|
||||
import { DragDropContext } from 'react-dnd';
|
||||
|
||||
class DragAndDropContextProvider extends Component {
|
||||
render() {
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A react-dnd provider that automatically switch to react-dnd-touch-backend
|
||||
* when a touch event is recognized (react-dnd-html5-backend won't work on
|
||||
* touch devices like phones).
|
||||
*
|
||||
* When doing the switch from HTML5 backend to Touch backend, the existing events
|
||||
* are passed to the new backend. Unsure if this is necessary in GDevelop case.
|
||||
*/
|
||||
export default DragDropContext(MultiBackend(HTML5toTouch))(
|
||||
DragAndDropContextProvider
|
||||
);
|
||||
@@ -0,0 +1,104 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import {
|
||||
DragSource,
|
||||
type DragSourceMonitor,
|
||||
type DragSourceConnector,
|
||||
type ConnectDragSource,
|
||||
DropTarget,
|
||||
type DropTargetMonitor,
|
||||
type DropTargetConnector,
|
||||
type ConnectDropTarget,
|
||||
} from 'react-dnd';
|
||||
|
||||
type Props<DraggedItemType> = {|
|
||||
children: ({
|
||||
connectDragSource: ConnectDragSource,
|
||||
connectDropTarget: ConnectDropTarget,
|
||||
isOver: boolean,
|
||||
canDrop: boolean,
|
||||
}) => React.Node,
|
||||
beginDrag: () => DraggedItemType,
|
||||
canDrop: (item: DraggedItemType) => boolean,
|
||||
drop: () => void,
|
||||
|};
|
||||
|
||||
type DragSourceProps = {|
|
||||
connectDragSource: ConnectDragSource,
|
||||
|};
|
||||
|
||||
type DropTargetProps = {|
|
||||
connectDropTarget: ConnectDropTarget,
|
||||
isOver: boolean,
|
||||
canDrop: boolean,
|
||||
|};
|
||||
|
||||
type InnerDragSourceAndDropTargetProps<DraggedItemType> = {|
|
||||
...Props<DraggedItemType>,
|
||||
...DragSourceProps,
|
||||
...DropTargetProps,
|
||||
|};
|
||||
|
||||
export const makeDragSourceAndDropTarget = <DraggedItemType>(
|
||||
reactDndType: string
|
||||
): ((Props<DraggedItemType>) => React.Node) => {
|
||||
const sourceSpec = {
|
||||
beginDrag(props: InnerDragSourceAndDropTargetProps<DraggedItemType>) {
|
||||
return props.beginDrag();
|
||||
},
|
||||
};
|
||||
|
||||
function sourceCollect(
|
||||
connect: DragSourceConnector,
|
||||
monitor: DragSourceMonitor
|
||||
): DragSourceProps {
|
||||
return {
|
||||
connectDragSource: connect.dragSource(),
|
||||
};
|
||||
}
|
||||
|
||||
const targetSpec = {
|
||||
canDrop(props: Props<DraggedItemType>, monitor: DropTargetMonitor) {
|
||||
const item = monitor.getItem();
|
||||
return item && props.canDrop(item);
|
||||
},
|
||||
drop(props: Props<DraggedItemType>, monitor: DropTargetMonitor) {
|
||||
if (monitor.didDrop()) {
|
||||
return; // Drop already handled by another target
|
||||
}
|
||||
props.drop();
|
||||
},
|
||||
};
|
||||
|
||||
function targetCollect(
|
||||
connect: DropTargetConnector,
|
||||
monitor: DropTargetMonitor
|
||||
): DropTargetProps {
|
||||
return {
|
||||
connectDropTarget: connect.dropTarget(),
|
||||
isOver: monitor.isOver({ shallow: true }),
|
||||
canDrop: monitor.canDrop(),
|
||||
};
|
||||
}
|
||||
|
||||
const InnerDragSourceAndDropTarget = DragSource(
|
||||
reactDndType,
|
||||
sourceSpec,
|
||||
sourceCollect
|
||||
)(
|
||||
DropTarget(reactDndType, targetSpec, targetCollect)(
|
||||
({ children, connectDragSource, connectDropTarget, isOver, canDrop }) => {
|
||||
return children({
|
||||
connectDragSource,
|
||||
connectDropTarget,
|
||||
isOver,
|
||||
canDrop,
|
||||
});
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
return (props: Props<DraggedItemType>) => (
|
||||
<InnerDragSourceAndDropTarget {...props} />
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,68 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import {
|
||||
DropTarget,
|
||||
type DropTargetMonitor,
|
||||
type DropTargetConnector,
|
||||
type ConnectDropTarget,
|
||||
} from 'react-dnd';
|
||||
|
||||
type Props<DraggedItemType> = {|
|
||||
children: ({
|
||||
connectDropTarget: ConnectDropTarget,
|
||||
isOver: boolean,
|
||||
canDrop: boolean,
|
||||
}) => React.Node,
|
||||
canDrop: (item: DraggedItemType) => boolean,
|
||||
hover?: (monitor: DropTargetMonitor) => void,
|
||||
drop: (monitor: DropTargetMonitor) => void,
|
||||
|};
|
||||
|
||||
type DropTargetProps = {|
|
||||
connectDropTarget: ConnectDropTarget,
|
||||
isOver: boolean,
|
||||
canDrop: boolean,
|
||||
|};
|
||||
|
||||
export const makeDropTarget = <DraggedItemType>(
|
||||
reactDndType: string
|
||||
): ((Props<DraggedItemType>) => React.Node) => {
|
||||
const targetSpec = {
|
||||
canDrop(props: Props<DraggedItemType>, monitor: DropTargetMonitor) {
|
||||
const item = monitor.getItem();
|
||||
return item && props.canDrop(item);
|
||||
},
|
||||
hover(props: Props<DraggedItemType>, monitor: DropTargetMonitor) {
|
||||
if (props.hover) props.hover(monitor);
|
||||
},
|
||||
drop(props: Props<DraggedItemType>, monitor: DropTargetMonitor) {
|
||||
if (monitor.didDrop()) {
|
||||
return; // Drop already handled by another target
|
||||
}
|
||||
props.drop(monitor);
|
||||
},
|
||||
};
|
||||
|
||||
function targetCollect(
|
||||
connect: DropTargetConnector,
|
||||
monitor: DropTargetMonitor
|
||||
): DropTargetProps {
|
||||
return {
|
||||
connectDropTarget: connect.dropTarget(),
|
||||
isOver: monitor.isOver({ shallow: true }),
|
||||
canDrop: monitor.canDrop(),
|
||||
};
|
||||
}
|
||||
|
||||
const InnerDropTarget = DropTarget(reactDndType, targetSpec, targetCollect)(
|
||||
({ children, connectDropTarget, isOver, canDrop }) => {
|
||||
return children({
|
||||
connectDropTarget,
|
||||
isOver,
|
||||
canDrop,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
return (props: Props<DraggedItemType>) => <InnerDropTarget {...props} />;
|
||||
};
|
||||
@@ -0,0 +1,98 @@
|
||||
// @flow
|
||||
import { Trans } from '@lingui/macro';
|
||||
import * as React from 'react';
|
||||
import { type Editor } from '.';
|
||||
import { Column, Line } from '../Grid';
|
||||
import FlatButton from '../FlatButton';
|
||||
import ArrowBack from '@material-ui/icons/ArrowBack';
|
||||
import Background from '../Background';
|
||||
|
||||
type Props = {|
|
||||
initialEditorName: string,
|
||||
editors: {
|
||||
[string]: Editor,
|
||||
},
|
||||
transitions: {
|
||||
[string]: {|
|
||||
nextEditor?: string | (() => string),
|
||||
nextLabel?: React.Node,
|
||||
nextIcon?: React.Node,
|
||||
previousEditor?: string | (() => string),
|
||||
|},
|
||||
},
|
||||
onEditorChanged: (editorName: string) => void,
|
||||
|};
|
||||
|
||||
type Interface = {| openEditor: (editorName: string) => void |};
|
||||
|
||||
// Flow types might have to be changed/removed if upgrading Flow
|
||||
// (see example at https://github.com/wgao19/flow-notes/blob/master/react/react-memo.md)
|
||||
|
||||
export default React.forwardRef<Props, Interface>(
|
||||
(
|
||||
{ initialEditorName, editors, transitions, onEditorChanged }: Props,
|
||||
ref
|
||||
) => {
|
||||
const [currentEditorName, setCurrentEditorName] = React.useState(
|
||||
initialEditorName
|
||||
);
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
openEditor: editorName => {
|
||||
setCurrentEditorName(editorName);
|
||||
},
|
||||
}));
|
||||
React.useEffect(
|
||||
() => {
|
||||
onEditorChanged(currentEditorName);
|
||||
},
|
||||
[currentEditorName]
|
||||
);
|
||||
|
||||
const transition = transitions[currentEditorName];
|
||||
let buttonLineJustifyContent = 'space-between';
|
||||
if (transition) {
|
||||
if (transition.previousEditor && !transition.nextEditor) {
|
||||
buttonLineJustifyContent = 'flex-start';
|
||||
}
|
||||
if (!transition.previousEditor && transition.nextEditor) {
|
||||
buttonLineJustifyContent = 'flex-end';
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Column noMargin expand>
|
||||
{transition && (
|
||||
<Background maxWidth noExpand noFullHeight>
|
||||
<Column>
|
||||
<Line justifyContent={buttonLineJustifyContent}>
|
||||
{transition.previousEditor && (
|
||||
<FlatButton
|
||||
label={<Trans>Back</Trans>}
|
||||
onClick={() => {
|
||||
if (transition.previousEditor)
|
||||
setCurrentEditorName(transition.previousEditor);
|
||||
}}
|
||||
icon={<ArrowBack />}
|
||||
/>
|
||||
)}
|
||||
{transition.nextLabel && transition.nextEditor && (
|
||||
<FlatButton
|
||||
label={transition.nextLabel}
|
||||
onClick={() => {
|
||||
if (transition.nextEditor)
|
||||
setCurrentEditorName(transition.nextEditor);
|
||||
}}
|
||||
icon={transition.nextIcon}
|
||||
/>
|
||||
)}
|
||||
</Line>
|
||||
</Column>
|
||||
</Background>
|
||||
)}
|
||||
{editors[currentEditorName]
|
||||
? editors[currentEditorName].renderEditor()
|
||||
: null}
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
);
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { Component } from 'react';
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import {
|
||||
MosaicWindow as RMMosaicWindow,
|
||||
MosaicWithoutDragDropContext,
|
||||
@@ -11,27 +12,100 @@ import ThemeConsumer from '../Theme/ThemeConsumer';
|
||||
import 'react-mosaic-component/react-mosaic-component.css';
|
||||
import './style.css';
|
||||
|
||||
const addRightNode = (
|
||||
currentNode,
|
||||
newNode,
|
||||
splitPercentage,
|
||||
direction = 'row'
|
||||
) => {
|
||||
if (currentNode.second && typeof currentNode.second !== 'string') {
|
||||
return {
|
||||
...currentNode,
|
||||
second: addRightNode(currentNode.second, newNode, splitPercentage),
|
||||
};
|
||||
export type Editor = {|
|
||||
type: 'primary' | 'secondary',
|
||||
renderEditor: () => React.Node,
|
||||
noTitleBar?: boolean,
|
||||
title?: React.Node,
|
||||
toolbarControls?: Array<React.Node>,
|
||||
|};
|
||||
|
||||
type MosaicNode =
|
||||
| {|
|
||||
direction: 'row' | 'column',
|
||||
splitPercentage: number,
|
||||
first: ?MosaicNode,
|
||||
second: ?MosaicNode,
|
||||
|}
|
||||
| string;
|
||||
|
||||
// Add a node (an editor) in the mosaic.
|
||||
const addNode = (
|
||||
currentNode: ?MosaicNode,
|
||||
newNode: MosaicNode | string,
|
||||
position: 'start' | 'end',
|
||||
splitPercentage: number,
|
||||
direction: 'row' | 'column' = 'row'
|
||||
): MosaicNode => {
|
||||
if (!currentNode) return newNode;
|
||||
|
||||
// Add the new node inside the current node...
|
||||
if (typeof currentNode !== 'string') {
|
||||
if (
|
||||
position === 'end' &&
|
||||
currentNode.second &&
|
||||
typeof currentNode.second !== 'string'
|
||||
) {
|
||||
return {
|
||||
...currentNode,
|
||||
second: addNode(
|
||||
currentNode.second,
|
||||
newNode,
|
||||
position,
|
||||
splitPercentage,
|
||||
direction
|
||||
),
|
||||
};
|
||||
} else if (
|
||||
position === 'start' &&
|
||||
currentNode.first &&
|
||||
typeof currentNode.first !== 'string'
|
||||
) {
|
||||
return {
|
||||
...currentNode,
|
||||
first: addNode(
|
||||
currentNode.first,
|
||||
newNode,
|
||||
position,
|
||||
splitPercentage,
|
||||
direction
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Or add the node here.
|
||||
return {
|
||||
direction,
|
||||
first: currentNode,
|
||||
second: newNode,
|
||||
first: position === 'end' ? currentNode : newNode,
|
||||
second: position === 'end' ? newNode : currentNode,
|
||||
splitPercentage,
|
||||
};
|
||||
};
|
||||
|
||||
// Replace a node (an editor) by another.
|
||||
const replaceNode = (
|
||||
currentNode: ?MosaicNode,
|
||||
oldNode: ?MosaicNode,
|
||||
newNode: ?MosaicNode
|
||||
): ?MosaicNode => {
|
||||
if (!currentNode) {
|
||||
return currentNode;
|
||||
} else if (typeof currentNode === 'string') {
|
||||
if (currentNode === oldNode) return newNode;
|
||||
|
||||
return currentNode;
|
||||
} else {
|
||||
if (currentNode === oldNode) return newNode;
|
||||
|
||||
return {
|
||||
...currentNode,
|
||||
first: replaceNode(currentNode.first, oldNode, newNode),
|
||||
second: replaceNode(currentNode.second, oldNode, newNode),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const defaultToolbarControls = [<CloseButton key="close" />];
|
||||
|
||||
const renderMosaicWindowPreview = props => (
|
||||
@@ -48,7 +122,7 @@ const renderMosaicWindowPreview = props => (
|
||||
* by default in the toolbarControls and a preview when the window is
|
||||
* dragged.
|
||||
*/
|
||||
export const MosaicWindow = props => (
|
||||
const MosaicWindow = (props: any) => (
|
||||
<RMMosaicWindow
|
||||
{...props}
|
||||
toolbarControls={props.toolbarControls || defaultToolbarControls}
|
||||
@@ -56,33 +130,75 @@ export const MosaicWindow = props => (
|
||||
/>
|
||||
);
|
||||
|
||||
type Props = {|
|
||||
initialNodes: MosaicNode,
|
||||
editors: {
|
||||
[string]: Editor,
|
||||
},
|
||||
limitToOneSecondaryEditor?: boolean,
|
||||
|};
|
||||
|
||||
type State = {|
|
||||
mosaicNode: ?MosaicNode,
|
||||
|};
|
||||
|
||||
/**
|
||||
* @class EditorMosaic
|
||||
*
|
||||
* Can be used to create a mosaic of resizable editors.
|
||||
* Must be used inside a component wrapped in a DragDropContext.
|
||||
*/
|
||||
export default class EditorMosaic extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
export default class EditorMosaic extends React.Component<Props, State> {
|
||||
state = {
|
||||
mosaicNode: this.props.initialNodes,
|
||||
};
|
||||
|
||||
this.state = {
|
||||
mosaicNode: props.initialNodes,
|
||||
};
|
||||
}
|
||||
openEditor = (
|
||||
editorName: string,
|
||||
position: 'start' | 'end',
|
||||
splitPercentage: number
|
||||
) => {
|
||||
const { editors, limitToOneSecondaryEditor } = this.props;
|
||||
|
||||
openEditor = editorName => {
|
||||
if (getLeaves(this.state.mosaicNode).indexOf(editorName) !== -1) {
|
||||
const editor = this.props.editors[editorName];
|
||||
if (!editor) return false;
|
||||
|
||||
const openedEditorNames = getLeaves(this.state.mosaicNode);
|
||||
if (openedEditorNames.indexOf(editorName) !== -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (limitToOneSecondaryEditor && editor.type === 'secondary') {
|
||||
// Replace the existing secondary editor, if any.
|
||||
const secondaryEditorName = openedEditorNames.find(
|
||||
editorName => editors[editorName].type === 'secondary'
|
||||
);
|
||||
if (secondaryEditorName) {
|
||||
this.setState({
|
||||
mosaicNode: replaceNode(
|
||||
this.state.mosaicNode,
|
||||
secondaryEditorName,
|
||||
editorName
|
||||
),
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Open a new editor at the indicated position
|
||||
this.setState({
|
||||
mosaicNode: addRightNode(this.state.mosaicNode, editorName, 75),
|
||||
mosaicNode: addNode(
|
||||
this.state.mosaicNode,
|
||||
editorName,
|
||||
position,
|
||||
splitPercentage
|
||||
),
|
||||
});
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
_onChange = mosaicNode => this.setState({ mosaicNode });
|
||||
_onChange = (mosaicNode: MosaicNode) => this.setState({ mosaicNode });
|
||||
|
||||
render() {
|
||||
const { editors } = this.props;
|
||||
@@ -93,9 +209,29 @@ export default class EditorMosaic extends Component {
|
||||
className={`${
|
||||
muiTheme.mosaicRootClassName
|
||||
} mosaic-blueprint-theme mosaic-gd-theme`}
|
||||
renderTile={(editorName, path) =>
|
||||
React.cloneElement(editors[editorName], { path })
|
||||
}
|
||||
renderTile={(editorName: string, path: string) => {
|
||||
const editor = editors[editorName];
|
||||
if (!editor) {
|
||||
console.error(
|
||||
'Trying to render un unknown editor: ' + editorName
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (editor.noTitleBar) {
|
||||
return editor.renderEditor();
|
||||
}
|
||||
|
||||
return (
|
||||
<MosaicWindow
|
||||
path={path}
|
||||
title={editor.title}
|
||||
toolbarControls={editor.toolbarControls}
|
||||
>
|
||||
{editor.renderEditor()}
|
||||
</MosaicWindow>
|
||||
);
|
||||
}}
|
||||
value={this.state.mosaicNode}
|
||||
onChange={this._onChange}
|
||||
/>
|
||||
|
||||
@@ -31,7 +31,7 @@ type ListItemRightButtonProps =
|
||||
onReload: () => void,
|
||||
|}
|
||||
| {|
|
||||
displayMenuButton: true,
|
||||
displayMenuButton: boolean,
|
||||
buildMenuTemplate: () => Array<any>,
|
||||
|}
|
||||
| {|
|
||||
@@ -69,6 +69,7 @@ type ListItemProps = {|
|
||||
|},
|
||||
|
||||
style?: {|
|
||||
color?: string,
|
||||
backgroundColor?: string,
|
||||
borderBottom?: string,
|
||||
opacity?: number,
|
||||
@@ -108,8 +109,8 @@ export class ListItem extends React.Component<ListItemProps, ListItemState> {
|
||||
</MUIListItemSecondaryAction>
|
||||
);
|
||||
}
|
||||
if (props.displayMenuButton) {
|
||||
return (
|
||||
if (props.buildMenuTemplate) {
|
||||
return props.displayMenuButton ? (
|
||||
<MUIListItemSecondaryAction>
|
||||
<ElementWithMenu
|
||||
ref={elementWithMenu => (this._elementWithMenu = elementWithMenu)}
|
||||
@@ -121,6 +122,14 @@ export class ListItem extends React.Component<ListItemProps, ListItemState> {
|
||||
buildMenuTemplate={props.buildMenuTemplate}
|
||||
/>
|
||||
</MUIListItemSecondaryAction>
|
||||
) : (
|
||||
<ElementWithMenu
|
||||
ref={elementWithMenu => (this._elementWithMenu = elementWithMenu)}
|
||||
element={
|
||||
<div /> /* We still need a dummy div for context menu placement */
|
||||
}
|
||||
buildMenuTemplate={props.buildMenuTemplate}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (props.displayLinkButton) {
|
||||
@@ -174,7 +183,7 @@ export class ListItem extends React.Component<ListItemProps, ListItemState> {
|
||||
selected={props.selected}
|
||||
style={props.style}
|
||||
onContextMenu={
|
||||
props.displayMenuButton ? this._openContextMenu : undefined
|
||||
props.buildMenuTemplate ? this._openContextMenu : undefined
|
||||
}
|
||||
alignItems={props.secondaryTextLines === 2 ? 'flex-start' : undefined}
|
||||
>
|
||||
|
||||
@@ -6,10 +6,12 @@ import Button from '@material-ui/core/Button';
|
||||
import PreferencesContext, {
|
||||
type AlertMessageIdentifier,
|
||||
} from '../../MainFrame/Preferences/PreferencesContext';
|
||||
import { ScreenTypeMeasurer } from '../Reponsive/ScreenTypeMeasurer';
|
||||
|
||||
type Props = {|
|
||||
identifier: AlertMessageIdentifier,
|
||||
message: React.Node,
|
||||
touchScreenMessage?: React.Node,
|
||||
show: boolean,
|
||||
|};
|
||||
|
||||
@@ -18,26 +20,36 @@ export default class InfoBar extends React.PureComponent<Props> {
|
||||
const { identifier } = this.props;
|
||||
|
||||
return (
|
||||
<PreferencesContext.Consumer>
|
||||
{({ values, showAlertMessage }) => (
|
||||
<Snackbar
|
||||
open={this.props.show && !values.hiddenAlertMessages[identifier]}
|
||||
message={this.props.message}
|
||||
action={
|
||||
<Button
|
||||
key="undo"
|
||||
color="primary"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
showAlertMessage(identifier, false);
|
||||
}}
|
||||
>
|
||||
<Trans>Got it</Trans>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<ScreenTypeMeasurer>
|
||||
{screenType => (
|
||||
<PreferencesContext.Consumer>
|
||||
{({ values, showAlertMessage }) => (
|
||||
<Snackbar
|
||||
open={
|
||||
this.props.show && !values.hiddenAlertMessages[identifier]
|
||||
}
|
||||
message={
|
||||
screenType === 'touch' && this.props.touchScreenMessage
|
||||
? this.props.touchScreenMessage
|
||||
: this.props.message
|
||||
}
|
||||
action={
|
||||
<Button
|
||||
key="undo"
|
||||
color="primary"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
showAlertMessage(identifier, false);
|
||||
}}
|
||||
>
|
||||
<Trans>Got it</Trans>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</PreferencesContext.Consumer>
|
||||
)}
|
||||
</PreferencesContext.Consumer>
|
||||
</ScreenTypeMeasurer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ export const ResponsiveWindowMeasurer = ({ children }: Props) => {
|
||||
}
|
||||
|
||||
return children(
|
||||
window.innerWidth < 750 || window.innerHeight < 750
|
||||
window.innerWidth < 750 || window.innerHeight < 475
|
||||
? 'small'
|
||||
: window.innerWidth < 1150
|
||||
? 'medium'
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
|
||||
export type ScreenType = 'normal' | 'touch';
|
||||
|
||||
let userHasTouchedScreen = false;
|
||||
if (typeof window !== 'undefined') {
|
||||
window.addEventListener(
|
||||
'touchstart',
|
||||
function onFirstTouch() {
|
||||
console.info('Touch detected, considering the screen as touch enabled.');
|
||||
userHasTouchedScreen = true;
|
||||
window.removeEventListener('touchstart', onFirstTouch, false);
|
||||
},
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
type Props = {|
|
||||
children: (screenType: ScreenType) => React.Node,
|
||||
|};
|
||||
|
||||
/**
|
||||
* Check if the screen is a touchscreen or not.
|
||||
*/
|
||||
export const ScreenTypeMeasurer = ({ children }: Props) => {
|
||||
if (typeof window === 'undefined') {
|
||||
return children('normal');
|
||||
}
|
||||
|
||||
return children(userHasTouchedScreen ? 'touch' : 'normal');
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import ThemeConsumer from '../Theme/ThemeConsumer';
|
||||
|
||||
const styles = {
|
||||
dropIndicator: {
|
||||
borderTop: '2px solid #18dcf2',
|
||||
height: 0,
|
||||
marginTop: '-1px',
|
||||
marginBottom: '-1px',
|
||||
width: '100%',
|
||||
boxSizing: 'border-box',
|
||||
},
|
||||
};
|
||||
|
||||
export default function DropIndicator({ canDrop }: {| canDrop: boolean |}) {
|
||||
return (
|
||||
<ThemeConsumer>
|
||||
{gdevelopTheme => (
|
||||
<div
|
||||
style={{
|
||||
...styles.dropIndicator,
|
||||
borderColor: canDrop
|
||||
? gdevelopTheme.listItem.selectedBackgroundColor
|
||||
: gdevelopTheme.listItem.selectedErrorBackgroundColor,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</ThemeConsumer>
|
||||
);
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
// @flow
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import { ListItem } from '../List';
|
||||
import ListIcon from '../ListIcon';
|
||||
import TextField, { noMarginTextFieldInListItemTopOffset } from '../TextField';
|
||||
import { type Item } from '.';
|
||||
import ThemeConsumer from '../Theme/ThemeConsumer';
|
||||
|
||||
const styles = {
|
||||
@@ -17,23 +16,28 @@ const styles = {
|
||||
},
|
||||
};
|
||||
|
||||
type Props = {
|
||||
index: number,
|
||||
const LEFT_MOUSE_BUTTON = 0;
|
||||
|
||||
type Props<Item> = {
|
||||
item: Item,
|
||||
itemName: string,
|
||||
isBold: boolean,
|
||||
onRename: string => void,
|
||||
editingName: boolean,
|
||||
getThumbnail?: () => string,
|
||||
selected: true,
|
||||
selected: boolean,
|
||||
onItemSelected: (?Item) => void,
|
||||
errorStatus: '' | 'error' | 'warning',
|
||||
buildMenuTemplate: () => Array<any>,
|
||||
style: Object,
|
||||
onEdit?: ?(Item) => void,
|
||||
hideMenuButton: boolean,
|
||||
connectIconDragSource?: ?(React.Node) => React.Node,
|
||||
};
|
||||
|
||||
class ItemRow extends React.Component<Props, *> {
|
||||
class ItemRow<Item> extends React.Component<Props<Item>> {
|
||||
textField: ?TextField;
|
||||
|
||||
componentDidUpdate(prevProps: Props) {
|
||||
componentDidUpdate(prevProps: Props<Item>) {
|
||||
if (!prevProps.editingName && this.props.editingName) {
|
||||
setTimeout(() => {
|
||||
if (this.textField) this.textField.focus();
|
||||
@@ -42,12 +46,22 @@ class ItemRow extends React.Component<Props, *> {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { item, selected, style, getThumbnail, errorStatus } = this.props;
|
||||
const {
|
||||
item,
|
||||
itemName,
|
||||
isBold,
|
||||
selected,
|
||||
getThumbnail,
|
||||
errorStatus,
|
||||
onEdit,
|
||||
onItemSelected,
|
||||
hideMenuButton,
|
||||
connectIconDragSource,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<ThemeConsumer>
|
||||
{muiTheme => {
|
||||
const itemName = item.getName();
|
||||
const label = this.props.editingName ? (
|
||||
<TextField
|
||||
id="rename-item-field"
|
||||
@@ -71,6 +85,8 @@ class ItemRow extends React.Component<Props, *> {
|
||||
color: selected
|
||||
? muiTheme.listItem.selectedTextColor
|
||||
: undefined,
|
||||
fontStyle: isBold ? 'italic' : undefined,
|
||||
fontWeight: isBold ? 'bold' : 'normal',
|
||||
}}
|
||||
>
|
||||
{itemName}
|
||||
@@ -94,20 +110,34 @@ class ItemRow extends React.Component<Props, *> {
|
||||
: muiTheme.listItem.warningTextColor,
|
||||
};
|
||||
|
||||
const leftIcon = getThumbnail ? (
|
||||
<ListIcon iconSize={32} src={getThumbnail()} />
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
style={{ ...itemStyle, ...style }}
|
||||
style={{ ...itemStyle }}
|
||||
primaryText={label}
|
||||
leftIcon={
|
||||
getThumbnail && <ListIcon iconSize={32} src={getThumbnail()} />
|
||||
connectIconDragSource && leftIcon
|
||||
? connectIconDragSource(<div>{leftIcon}</div>)
|
||||
: leftIcon
|
||||
}
|
||||
displayMenuButton
|
||||
displayMenuButton={!hideMenuButton}
|
||||
buildMenuTemplate={this.props.buildMenuTemplate}
|
||||
onClick={() => {
|
||||
if (!this.props.onItemSelected) return;
|
||||
if (!onItemSelected) return;
|
||||
if (this.props.editingName) return;
|
||||
|
||||
this.props.onItemSelected(selected ? null : item);
|
||||
onItemSelected(selected ? null : item);
|
||||
}}
|
||||
onDoubleClick={event => {
|
||||
if (event.button !== LEFT_MOUSE_BUTTON) return;
|
||||
if (!onEdit) return;
|
||||
if (this.props.editingName) return;
|
||||
|
||||
onItemSelected(null);
|
||||
onEdit(item);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -3,52 +3,43 @@ import * as React from 'react';
|
||||
import { List } from 'react-virtualized';
|
||||
import ItemRow from './ItemRow';
|
||||
import { AddListItem } from '../ListCommonItem';
|
||||
import { SortableContainer, SortableElement } from 'react-sortable-hoc';
|
||||
import { listItemWith32PxIconHeight, listItemWithoutIconHeight } from '../List';
|
||||
import { makeDragSourceAndDropTarget } from '../DragAndDrop/DragSourceAndDropTarget';
|
||||
import DropIndicator from './DropIndicator';
|
||||
import { ResponsiveWindowMeasurer } from '../Reponsive/ResponsiveWindowMeasurer';
|
||||
import { ScreenTypeMeasurer } from '../Reponsive/ScreenTypeMeasurer';
|
||||
|
||||
const SortableItemRow = SortableElement(props => {
|
||||
const { style, ...otherProps } = props;
|
||||
return (
|
||||
<div style={style}>
|
||||
<ItemRow {...otherProps} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const SortableAddItemRow = SortableElement(props => {
|
||||
const { style, ...otherProps } = props;
|
||||
return (
|
||||
<div style={style}>
|
||||
<AddListItem {...otherProps} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export type Item = {
|
||||
key: string | number,
|
||||
getName: () => string,
|
||||
};
|
||||
|
||||
type ItemsListProps = {
|
||||
type Props<Item> = {|
|
||||
height: number,
|
||||
width: number,
|
||||
fullList: Array<Item>,
|
||||
selectedItem: ?Item,
|
||||
selectedItems: Array<Item>,
|
||||
onAddNewItem?: () => void,
|
||||
addNewItemLabel?: React.Node | string,
|
||||
onRename: (Item, string) => void,
|
||||
getThumbnail?: Item => string,
|
||||
getItemName: Item => string,
|
||||
getItemThumbnail?: Item => string,
|
||||
isItemBold?: Item => boolean,
|
||||
onItemSelected: (?Item) => void,
|
||||
onEditItem?: Item => void,
|
||||
renamedItem: ?Item,
|
||||
addNewItemLabel: React.Node | string,
|
||||
erroredItems?: { [string]: '' | 'error' | 'warning' },
|
||||
buildMenuTemplate: (Item, index: number) => any,
|
||||
};
|
||||
onMoveSelectionToItem: (destinationItem: Item) => void,
|
||||
canMoveSelectionToItem?: ?(destinationItem: Item) => boolean,
|
||||
reactDndType: string,
|
||||
|};
|
||||
|
||||
class ItemsList extends React.Component<ItemsListProps, *> {
|
||||
list: any;
|
||||
export default class SortableVirtualizedItemList<Item> extends React.Component<
|
||||
Props<Item>
|
||||
> {
|
||||
_list: ?List;
|
||||
DragSourceAndDropTarget = makeDragSourceAndDropTarget<Item>(
|
||||
this.props.reactDndType
|
||||
);
|
||||
|
||||
forceUpdateGrid() {
|
||||
if (this.list) this.list.forceUpdateGrid();
|
||||
if (this._list) this._list.forceUpdateGrid();
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -56,63 +47,135 @@ class ItemsList extends React.Component<ItemsListProps, *> {
|
||||
height,
|
||||
width,
|
||||
fullList,
|
||||
selectedItem,
|
||||
selectedItems,
|
||||
addNewItemLabel,
|
||||
renamedItem,
|
||||
getThumbnail,
|
||||
getItemThumbnail,
|
||||
getItemName,
|
||||
erroredItems,
|
||||
onAddNewItem,
|
||||
isItemBold,
|
||||
onEditItem,
|
||||
onMoveSelectionToItem,
|
||||
canMoveSelectionToItem,
|
||||
} = this.props;
|
||||
const { DragSourceAndDropTarget } = this;
|
||||
|
||||
return (
|
||||
<List
|
||||
ref={list => (this.list = list)}
|
||||
height={height}
|
||||
rowCount={fullList.length}
|
||||
rowHeight={
|
||||
getThumbnail ? listItemWith32PxIconHeight : listItemWithoutIconHeight
|
||||
}
|
||||
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}
|
||||
<ResponsiveWindowMeasurer>
|
||||
{windowWidth => (
|
||||
<ScreenTypeMeasurer>
|
||||
{screenType => (
|
||||
<List
|
||||
ref={list => (this._list = list)}
|
||||
height={height}
|
||||
rowCount={fullList.length + (onAddNewItem ? 1 : 0)}
|
||||
rowHeight={
|
||||
getItemThumbnail
|
||||
? listItemWith32PxIconHeight
|
||||
: listItemWithoutIconHeight
|
||||
}
|
||||
rowRenderer={({
|
||||
index,
|
||||
key,
|
||||
style,
|
||||
}: {|
|
||||
index: number,
|
||||
key: string,
|
||||
style: Object,
|
||||
|}) => {
|
||||
if (index >= fullList.length) {
|
||||
return (
|
||||
<div style={style} key={key}>
|
||||
<AddListItem
|
||||
disabled
|
||||
onClick={onAddNewItem}
|
||||
primaryText={addNewItemLabel}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const item = fullList[index];
|
||||
const nameBeingEdited = renamedItem === item;
|
||||
const itemName = getItemName(item);
|
||||
|
||||
return (
|
||||
<div style={style} key={key}>
|
||||
<DragSourceAndDropTarget
|
||||
beginDrag={() => {
|
||||
this.props.onItemSelected(item);
|
||||
return {};
|
||||
}}
|
||||
canDrop={() =>
|
||||
canMoveSelectionToItem
|
||||
? canMoveSelectionToItem(item)
|
||||
: true
|
||||
}
|
||||
drop={() => {
|
||||
onMoveSelectionToItem(item);
|
||||
}}
|
||||
>
|
||||
{({
|
||||
connectDragSource,
|
||||
connectDropTarget,
|
||||
isOver,
|
||||
canDrop,
|
||||
}) => {
|
||||
// Add an extra div because connectDropTarget/connectDragSource can
|
||||
// only be used on native elements
|
||||
const dropTarget = connectDropTarget(
|
||||
<div>
|
||||
{isOver && <DropIndicator canDrop={canDrop} />}
|
||||
<ItemRow
|
||||
item={item}
|
||||
itemName={itemName}
|
||||
isBold={isItemBold ? isItemBold(item) : false}
|
||||
onRename={newName =>
|
||||
this.props.onRename(item, newName)
|
||||
}
|
||||
editingName={nameBeingEdited}
|
||||
getThumbnail={
|
||||
getItemThumbnail
|
||||
? () => getItemThumbnail(item)
|
||||
: undefined
|
||||
}
|
||||
selected={selectedItems.indexOf(item) !== -1}
|
||||
onItemSelected={this.props.onItemSelected}
|
||||
errorStatus={
|
||||
erroredItems
|
||||
? erroredItems[itemName] || ''
|
||||
: ''
|
||||
}
|
||||
buildMenuTemplate={() =>
|
||||
this.props.buildMenuTemplate(item, index)
|
||||
}
|
||||
onEdit={onEditItem}
|
||||
hideMenuButton={windowWidth === 'small'}
|
||||
connectIconDragSource={
|
||||
// If on a touch screen, only set the icon to be draggable.
|
||||
screenType === 'touch'
|
||||
? connectDragSource
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
return screenType === 'touch'
|
||||
? dropTarget
|
||||
: connectDragSource(dropTarget);
|
||||
}}
|
||||
</DragSourceAndDropTarget>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
width={width}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
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}
|
||||
errorStatus={
|
||||
erroredItems ? erroredItems[item.getName()] || '' : ''
|
||||
}
|
||||
buildMenuTemplate={() =>
|
||||
this.props.buildMenuTemplate(item, index)
|
||||
}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
width={width}
|
||||
/>
|
||||
)}
|
||||
</ScreenTypeMeasurer>
|
||||
)}
|
||||
</ResponsiveWindowMeasurer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const SortableItemsList = SortableContainer(ItemsList, { withRef: true });
|
||||
export default SortableItemsList;
|
||||
|
||||
@@ -77,6 +77,10 @@
|
||||
padding-right: 5px;
|
||||
min-width: 0; /* Prevent container to growing outside its parent */
|
||||
}
|
||||
.gd-events-sheet-dark-theme .conditions-container.small-width-container {
|
||||
border-right: none;
|
||||
border-bottom: 1px solid black;
|
||||
}
|
||||
|
||||
.gd-events-sheet-dark-theme .actions-container {
|
||||
background: #303030;
|
||||
@@ -85,6 +89,9 @@
|
||||
padding-right: 5px;
|
||||
min-width: 0; /* Prevent container to growing outside its parent */
|
||||
}
|
||||
.gd-events-sheet-dark-theme .actions-container.small-width-container {
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
.gd-events-sheet-dark-theme .sub-instructions-container {
|
||||
margin-left: 9px;
|
||||
|
||||
@@ -71,6 +71,10 @@
|
||||
padding-right: 5px;
|
||||
min-width: 0; /* Prevent container to growing outside its parent */
|
||||
}
|
||||
.gd-events-sheet-default-theme .conditions-container.small-width-container {
|
||||
border-right: none;
|
||||
border-bottom: 1px solid #d3d3d3;
|
||||
}
|
||||
|
||||
.gd-events-sheet-default-theme .actions-container {
|
||||
background: white;
|
||||
@@ -78,6 +82,9 @@
|
||||
padding-right: 5px;
|
||||
min-width: 0; /* Prevent container to growing outside its parent */
|
||||
}
|
||||
.gd-events-sheet-default-theme .actions-container.small-width-container {
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
.gd-events-sheet-default-theme .sub-instructions-container {
|
||||
margin-left: 9px;
|
||||
|
||||
@@ -7,9 +7,11 @@ import {
|
||||
getProgramOpeningCount,
|
||||
incrementProgramOpeningCount,
|
||||
} from './LocalStats';
|
||||
import { getStartupTimesSummary } from '../StartupTimes';
|
||||
|
||||
const isDev = Window.isDev();
|
||||
let client = null;
|
||||
let startupTimesSummary = null;
|
||||
|
||||
export const installAnalyticsEvents = (authentification: Authentification) => {
|
||||
if (isDev) {
|
||||
@@ -28,8 +30,12 @@ export const installAnalyticsEvents = (authentification: Authentification) => {
|
||||
});
|
||||
|
||||
client.extendEvents(function() {
|
||||
// Include the user public profile.
|
||||
const userProfile = authentification.getUserProfileSync();
|
||||
|
||||
// Compute the startup times (only once to avoid doing this for every event).
|
||||
startupTimesSummary = startupTimesSummary || getStartupTimesSummary();
|
||||
|
||||
return {
|
||||
user: {
|
||||
uuid: getUserUUID(),
|
||||
@@ -41,6 +47,7 @@ export const installAnalyticsEvents = (authentification: Authentification) => {
|
||||
localStats: {
|
||||
programOpeningCount: getProgramOpeningCount(),
|
||||
},
|
||||
startupTimesSummary,
|
||||
page: {
|
||||
title: document.title,
|
||||
url: document.location.href,
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import { Component } from 'react';
|
||||
import HTML5Backend from 'react-dnd-html5-backend';
|
||||
import { DragDropContext } from 'react-dnd';
|
||||
|
||||
class DragDropContextProvider extends Component {
|
||||
render() {
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
export default DragDropContext(HTML5Backend)(DragDropContextProvider);
|
||||
@@ -1,37 +0,0 @@
|
||||
import { Component } from 'react';
|
||||
import { NativeTypes } from 'react-dnd-html5-backend';
|
||||
import { DropTarget } from 'react-dnd';
|
||||
|
||||
class SimpleDropTarget extends Component {
|
||||
render() {
|
||||
const { connectDropTarget } = this.props;
|
||||
return connectDropTarget(this.props.children);
|
||||
}
|
||||
}
|
||||
|
||||
function collect(connect, monitor) {
|
||||
return {
|
||||
// Call this function inside render()
|
||||
// to let React DnD handle the drag events:
|
||||
connectDropTarget: connect.dropTarget(),
|
||||
// You can ask the monitor about the current drag state:
|
||||
isOver: monitor.isOver(),
|
||||
isOverCurrent: monitor.isOver({ shallow: true }),
|
||||
canDrop: monitor.canDrop(),
|
||||
itemType: monitor.getItemType(),
|
||||
};
|
||||
}
|
||||
const spec = {
|
||||
drop(props, monitor, component) {
|
||||
if (monitor.didDrop()) {
|
||||
// If you want, you can check whether some nested
|
||||
// target already handled drop
|
||||
return;
|
||||
}
|
||||
|
||||
const item = monitor.getItem();
|
||||
return { item };
|
||||
},
|
||||
};
|
||||
|
||||
export default DropTarget(NativeTypes.TEXT, spec, collect)(SimpleDropTarget);
|
||||
@@ -0,0 +1,39 @@
|
||||
// @flow
|
||||
type RawStepMeasure = [string, number];
|
||||
type SummarizedStep = {|
|
||||
stepName: string,
|
||||
time: number,
|
||||
elapsedTime: number,
|
||||
|};
|
||||
type Summary = {|
|
||||
totalStartupTime: number,
|
||||
steps: Array<SummarizedStep>,
|
||||
|};
|
||||
|
||||
export const GD_STARTUP_TIMES: Array<RawStepMeasure> = global.GD_STARTUP_TIMES;
|
||||
|
||||
if (!GD_STARTUP_TIMES) {
|
||||
console.error(
|
||||
'Could not find GD_STARTUP_TIMES array. Have you declared it in index.html, in a synchronous script?'
|
||||
);
|
||||
}
|
||||
|
||||
export const getStartupTimesSummary = (): Summary => {
|
||||
let previousStep = ['<init>', 0];
|
||||
|
||||
let steps = GD_STARTUP_TIMES.map(step => {
|
||||
const stepSummary = {
|
||||
stepName: step[0],
|
||||
time: step[1],
|
||||
elapsedTime: step[1] - previousStep[1],
|
||||
};
|
||||
|
||||
previousStep = step;
|
||||
return stepSummary;
|
||||
});
|
||||
|
||||
return {
|
||||
steps,
|
||||
totalStartupTime: previousStep[1],
|
||||
};
|
||||
};
|
||||
@@ -174,6 +174,35 @@ export default class Window {
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
|
||||
static isFullscreen() {
|
||||
// $FlowFixMe
|
||||
return !!document.fullscreenElement;
|
||||
}
|
||||
|
||||
static requestFullscreen() {
|
||||
const { documentElement } = document;
|
||||
if (!documentElement) return;
|
||||
|
||||
if (documentElement.requestFullscreen) {
|
||||
documentElement.requestFullscreen();
|
||||
// $FlowFixMe
|
||||
} else if (documentElement.mozRequestFullScreen) {
|
||||
/* Firefox */
|
||||
// $FlowFixMe
|
||||
documentElement.mozRequestFullScreen();
|
||||
// $FlowFixMe
|
||||
} else if (documentElement.webkitRequestFullscreen) {
|
||||
/* Chrome, Safari and Opera */
|
||||
// $FlowFixMe
|
||||
documentElement.webkitRequestFullscreen();
|
||||
// $FlowFixMe
|
||||
} else if (documentElement.msRequestFullscreen) {
|
||||
/* IE/Edge */
|
||||
// $FlowFixMe
|
||||
documentElement.msRequestFullscreen();
|
||||
}
|
||||
}
|
||||
|
||||
static hasMainMenu() {
|
||||
return !!electron;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import Text from '../UI/Text';
|
||||
//TODO: Refactor into TreeTable?
|
||||
const Indent = ({ width }) => (
|
||||
<div style={{ ...styles.indent, width }}>
|
||||
<SubdirectoryArrowRight color={styles.indentIconColor} />
|
||||
<SubdirectoryArrowRight htmlColor={styles.indentIconColor} />
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import { unregister } from './registerServiceWorker';
|
||||
import './UI/iconmoon-font.css'; // Styles for Iconmoon font.
|
||||
import optionalRequire from './Utils/OptionalRequire.js';
|
||||
import { showErrorBox } from './UI/Messages/MessageBox';
|
||||
const GD_STARTUP_TIMES = global.GD_STARTUP_TIMES || [];
|
||||
|
||||
// No i18n in this file
|
||||
|
||||
@@ -32,6 +33,7 @@ class Bootstrapper extends Component<{}, State> {
|
||||
installAnalyticsEvents(this.authentification);
|
||||
installRaven();
|
||||
installFullstory();
|
||||
GD_STARTUP_TIMES.push(["bootstrapperComponentDidMount", performance.now()]);
|
||||
|
||||
if (electron) {
|
||||
import(/* webpackChunkName: "local-app" */ './LocalApp')
|
||||
@@ -67,8 +69,13 @@ class Bootstrapper extends Component<{}, State> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
const rootElement = document.getElementById('root');
|
||||
if (rootElement) ReactDOM.render(<Bootstrapper />, rootElement);
|
||||
if (rootElement) {
|
||||
GD_STARTUP_TIMES.push(['reactDOMRenderCall', performance.now()]);
|
||||
ReactDOM.render(<Bootstrapper />, rootElement);
|
||||
}
|
||||
else console.error('No root element defined in index.html');
|
||||
|
||||
// registerServiceWorker();
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import { makeDragSourceAndDropTarget } from '../UI/DragAndDrop/DragSourceAndDropTarget';
|
||||
import { makeDropTarget } from '../UI/DragAndDrop/DropTarget';
|
||||
|
||||
type Props = {||};
|
||||
|
||||
const DragSourceAndDropTarget = makeDragSourceAndDropTarget<{
|
||||
someData: string,
|
||||
}>('dnd-type1');
|
||||
|
||||
const DragSourceAndDropTargetBox = ({ name }: {| name: string |}) => (
|
||||
<DragSourceAndDropTarget
|
||||
beginDrag={() => {
|
||||
console.log(
|
||||
'Begin dragging' + name + ', which should be added to the selection'
|
||||
);
|
||||
|
||||
return { someData: name };
|
||||
}}
|
||||
canDrop={() => name.indexOf('cant-drop-here') === -1}
|
||||
drop={() => {
|
||||
console.log('Selection to be dropped on' + name);
|
||||
}}
|
||||
>
|
||||
{({ connectDragSource, connectDropTarget, isOver, canDrop }) =>
|
||||
connectDropTarget(
|
||||
connectDragSource(
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: 'blue',
|
||||
color: 'white',
|
||||
height: 100,
|
||||
width: 100,
|
||||
margin: 20,
|
||||
}}
|
||||
>
|
||||
This is a box called {name}.{isOver && <div>Hovered</div>}
|
||||
{canDrop && <div>Can drop here</div>}
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
</DragSourceAndDropTarget>
|
||||
);
|
||||
|
||||
const DropTarget = makeDropTarget<{
|
||||
someData: string,
|
||||
}>('dnd-type1');
|
||||
|
||||
const DropTargetBox = ({ name }: {| name: string |}) => (
|
||||
<DropTarget
|
||||
canDrop={() => name.indexOf('cant-drop-here') === -1}
|
||||
drop={() => {
|
||||
console.log('Selection to be dropped on' + name);
|
||||
}}
|
||||
>
|
||||
{({ connectDropTarget, isOver, canDrop }) =>
|
||||
connectDropTarget(
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: 'green',
|
||||
color: 'white',
|
||||
height: 100,
|
||||
width: 100,
|
||||
margin: 20,
|
||||
}}
|
||||
>
|
||||
This is a box called {name}.{isOver && <div>Hovered</div>}
|
||||
{canDrop && <div>Can drop here</div>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</DropTarget>
|
||||
);
|
||||
|
||||
export default (props: Props) => (
|
||||
<div>
|
||||
<DragSourceAndDropTargetBox name="box1" />
|
||||
<DragSourceAndDropTargetBox name="box2, cant-drop-here" />
|
||||
<DragSourceAndDropTargetBox name="box3" />
|
||||
<DropTargetBox name="box4, drop target only" />
|
||||
<DropTargetBox name="box5, drop target but cant-drop-here" />
|
||||
</div>
|
||||
);
|
||||
@@ -0,0 +1,45 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import DragAndDropContextProvider from '../UI/DragAndDrop/DragAndDropContextProvider';
|
||||
import EditorMosaic from '../UI/EditorMosaic';
|
||||
import FixedHeightFlexContainer from './FixedHeightFlexContainer';
|
||||
import { Line, Column } from '../UI/Grid';
|
||||
import EditorNavigator from '../UI/EditorMosaic/EditorNavigator';
|
||||
|
||||
type OpenEditorFunction = (
|
||||
editorName: string,
|
||||
position: 'start' | 'end',
|
||||
slipPercentage: number
|
||||
) => void;
|
||||
|
||||
type Props = {|
|
||||
renderButtons: ({| openEditor: OpenEditorFunction |}) => React.Node,
|
||||
renderEditorMosaic: ({
|
||||
// $FlowFixMe
|
||||
editorRef: { current: EditorMosaic | EditorNavigator | null },
|
||||
}) => React.Node,
|
||||
|};
|
||||
|
||||
export default ({ renderButtons, renderEditorMosaic }: Props) => {
|
||||
// $FlowFixMe
|
||||
const editorRef = React.useRef((null: ?EditorMosaic | ?EditorNavigator));
|
||||
const openEditor = (
|
||||
editorName: string,
|
||||
position: 'start' | 'end',
|
||||
slipPercentage: number
|
||||
) => {
|
||||
if (editorRef.current)
|
||||
editorRef.current.openEditor(editorName, position, slipPercentage);
|
||||
};
|
||||
|
||||
return (
|
||||
<DragAndDropContextProvider>
|
||||
<FixedHeightFlexContainer height={400}>
|
||||
<Column expand>
|
||||
{renderButtons({ openEditor })}
|
||||
<Line expand>{renderEditorMosaic({ editorRef })}</Line>
|
||||
</Column>
|
||||
</FixedHeightFlexContainer>
|
||||
</DragAndDropContextProvider>
|
||||
);
|
||||
};
|
||||
+511
-123
@@ -65,7 +65,7 @@ import muiDecorator from './ThemeDecorator';
|
||||
import paperDecorator from './PaperDecorator';
|
||||
import ValueStateHolder from './ValueStateHolder';
|
||||
import RefGetter from './RefGetter';
|
||||
import DragDropContextProvider from '../Utils/DragDropHelpers/DragDropContextProvider';
|
||||
import DragAndDropContextProvider from '../UI/DragAndDrop/DragAndDropContextProvider';
|
||||
import ResourcesLoader from '../ResourcesLoader';
|
||||
import VariablesList from '../VariablesList';
|
||||
import ExpressionSelector from '../EventsSheet/InstructionEditor/InstructionOrExpressionSelector/ExpressionSelector';
|
||||
@@ -146,6 +146,13 @@ import Dialog from '../UI/Dialog';
|
||||
import MiniToolbar, { MiniToolbarText } from '../UI/MiniToolbar';
|
||||
import NewObjectDialog from '../ObjectsList/NewObjectDialog';
|
||||
import { Column } from '../UI/Grid';
|
||||
import DragAndDropTestBed from './DragAndDropTestBed';
|
||||
import EditorMosaic from '../UI/EditorMosaic';
|
||||
import FlatButton from '../UI/FlatButton';
|
||||
import EditorMosaicPlayground from './EditorMosaicPlayground';
|
||||
import EditorNavigator from '../UI/EditorMosaic/EditorNavigator';
|
||||
import ChooseEventsFunctionsExtensionEditor from '../EventsFunctionsExtensionEditor/ChooseEventsFunctionsExtensionEditor';
|
||||
import PropertiesEditor from '../PropertiesEditor';
|
||||
|
||||
// No i18n in this file
|
||||
|
||||
@@ -244,6 +251,12 @@ storiesOf('UI Building Blocks/SemiControlledTextField', module)
|
||||
/>
|
||||
));
|
||||
|
||||
storiesOf('UI Building Blocks/DragAndDrop', module).add('test bed', () => (
|
||||
<DragAndDropContextProvider>
|
||||
<DragAndDropTestBed />
|
||||
</DragAndDropContextProvider>
|
||||
));
|
||||
|
||||
storiesOf('UI Building Blocks/SemiControlledAutoComplete', module)
|
||||
.addDecorator(muiDecorator)
|
||||
.add('default, with text', () => (
|
||||
@@ -551,6 +564,185 @@ storiesOf('UI Building Blocks/ColorField', module)
|
||||
</div>
|
||||
));
|
||||
|
||||
storiesOf('UI Building Blocks/EditorMosaic', module)
|
||||
.addDecorator(muiDecorator)
|
||||
.add('default', () => (
|
||||
<EditorMosaicPlayground
|
||||
renderButtons={({ openEditor }) => (
|
||||
<FlatButton
|
||||
onClick={() => openEditor('thirdEditor', 'end', 65)}
|
||||
label="Open the third editor"
|
||||
/>
|
||||
)}
|
||||
renderEditorMosaic={({ editorRef }) => (
|
||||
<EditorMosaic
|
||||
ref={editorRef}
|
||||
editors={{
|
||||
firstEditor: {
|
||||
type: 'primary',
|
||||
title: 'First editor',
|
||||
toolbarControls: [],
|
||||
renderEditor: () => (
|
||||
<div>
|
||||
This is the first editor (left), with title bar but no
|
||||
controls to close the window.
|
||||
</div>
|
||||
),
|
||||
},
|
||||
secondEditor: {
|
||||
type: 'primary',
|
||||
noTitleBar: true,
|
||||
renderEditor: () => (
|
||||
<div>
|
||||
This is the second editor ("central"), without title bar.
|
||||
</div>
|
||||
),
|
||||
},
|
||||
thirdEditor: {
|
||||
type: 'secondary',
|
||||
title: 'Third editor',
|
||||
renderEditor: () => <div>This is the third editor (bottom).</div>,
|
||||
},
|
||||
}}
|
||||
initialNodes={{
|
||||
direction: 'column',
|
||||
first: {
|
||||
direction: 'row',
|
||||
first: 'firstEditor',
|
||||
second: 'secondEditor',
|
||||
splitPercentage: 25,
|
||||
},
|
||||
second: 'thirdEditor',
|
||||
splitPercentage: 65,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
))
|
||||
.add('limit to one secondary editor', () => (
|
||||
<EditorMosaicPlayground
|
||||
renderButtons={({ openEditor }) => (
|
||||
<React.Fragment>
|
||||
<FlatButton
|
||||
onClick={() => openEditor('firstEditor', 'end', 65)}
|
||||
label="Open the 1st secondary editor"
|
||||
/>
|
||||
<FlatButton
|
||||
onClick={() => openEditor('secondEditor', 'end', 65)}
|
||||
label="Open the 2nd secondary editor"
|
||||
/>
|
||||
<FlatButton
|
||||
onClick={() => openEditor('thirdEditor', 'end', 65)}
|
||||
label="Open the 3rd secondary editor"
|
||||
/>
|
||||
</React.Fragment>
|
||||
)}
|
||||
renderEditorMosaic={({ editorRef }) => (
|
||||
<EditorMosaic
|
||||
limitToOneSecondaryEditor
|
||||
ref={editorRef}
|
||||
editors={{
|
||||
firstEditor: {
|
||||
type: 'secondary',
|
||||
title: '1st secondary editor',
|
||||
renderEditor: () => <div>This is a secondary editor.</div>,
|
||||
},
|
||||
secondEditor: {
|
||||
type: 'secondary',
|
||||
title: '2nd secondary editor',
|
||||
renderEditor: () => <div>This is another secondary editor.</div>,
|
||||
},
|
||||
thirdEditor: {
|
||||
type: 'secondary',
|
||||
title: '3rd secondary editor',
|
||||
renderEditor: () => (
|
||||
<div>This is yet another secondary editor.</div>
|
||||
),
|
||||
},
|
||||
mainEditor: {
|
||||
type: 'primary',
|
||||
noTitleBar: true,
|
||||
renderEditor: () => (
|
||||
<div>This is the main editor, always shown</div>
|
||||
),
|
||||
},
|
||||
}}
|
||||
initialNodes={{
|
||||
direction: 'row',
|
||||
first: 'mainEditor',
|
||||
second: 'firstEditor',
|
||||
splitPercentage: 65,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
));
|
||||
|
||||
storiesOf('UI Building Blocks/EditorNavigator', module)
|
||||
.addDecorator(muiDecorator)
|
||||
.add('default', () => (
|
||||
<EditorMosaicPlayground
|
||||
renderButtons={({ openEditor }) => (
|
||||
<React.Fragment>
|
||||
<FlatButton
|
||||
onClick={() => openEditor('thirdEditor', 'end', 65)}
|
||||
label="Open the third editor"
|
||||
/>
|
||||
<FlatButton
|
||||
onClick={() => openEditor('noTransitionsEditor', 'end', 65)}
|
||||
label="Open the editor without transitions"
|
||||
/>
|
||||
</React.Fragment>
|
||||
)}
|
||||
renderEditorMosaic={({ editorRef }) => (
|
||||
<EditorNavigator
|
||||
ref={editorRef}
|
||||
initialEditorName="firstEditor"
|
||||
transitions={{
|
||||
firstEditor: {
|
||||
nextLabel: 'Second Editor',
|
||||
nextEditor: 'secondEditor',
|
||||
},
|
||||
secondEditor: {
|
||||
previousEditor: 'firstEditor',
|
||||
nextLabel: 'Third Editor',
|
||||
nextEditor: 'thirdEditor',
|
||||
},
|
||||
thirdEditor: {
|
||||
previousEditor: 'secondEditor',
|
||||
},
|
||||
}}
|
||||
editors={{
|
||||
firstEditor: {
|
||||
type: 'primary',
|
||||
title: 'First editor',
|
||||
toolbarControls: [],
|
||||
renderEditor: () => <div>This is the first editor.</div>,
|
||||
},
|
||||
secondEditor: {
|
||||
type: 'primary',
|
||||
noTitleBar: true,
|
||||
renderEditor: () => <div>This is the second editor.</div>,
|
||||
},
|
||||
thirdEditor: {
|
||||
type: 'secondary',
|
||||
title: 'Third editor',
|
||||
renderEditor: () => <div>This is the third editor.</div>,
|
||||
},
|
||||
noTransitionsEditor: {
|
||||
type: 'secondary',
|
||||
title: 'Editor without transitions',
|
||||
renderEditor: () => (
|
||||
<div>This is an editor without transitions.</div>
|
||||
),
|
||||
},
|
||||
}}
|
||||
onEditorChanged={action('Editor was changed')}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
));
|
||||
|
||||
storiesOf('UI Building Blocks/ClosableTabs', module)
|
||||
.addDecorator(muiDecorator)
|
||||
.add('3 tabs', () => (
|
||||
@@ -716,75 +908,93 @@ storiesOf('UI Building Blocks/ClosableTabs', module)
|
||||
<ValueStateHolder
|
||||
initialValue={0}
|
||||
render={(value, onChange) => (
|
||||
<FixedHeightFlexContainer height={400}>
|
||||
<Column expand>
|
||||
<ClosableTabs>
|
||||
<ClosableTab
|
||||
onActivated={action('Tab 1 activated')}
|
||||
closable
|
||||
active={value === 0}
|
||||
label="Tab 1"
|
||||
onClick={() => onChange(0)}
|
||||
onClose={action('Close tab 1')}
|
||||
onCloseAll={action('Close all')}
|
||||
onCloseOthers={action('Close others')}
|
||||
/>
|
||||
<ClosableTab
|
||||
onActivated={action('Tab 2 activated')}
|
||||
closable
|
||||
active={value === 1}
|
||||
label="Tab 2"
|
||||
onClick={() => onChange(1)}
|
||||
onClose={action('Close tab 2')}
|
||||
onCloseAll={action('Close all')}
|
||||
onCloseOthers={action('Close others')}
|
||||
/>
|
||||
<ClosableTab
|
||||
onActivated={action('Tab 3 activated')}
|
||||
closable
|
||||
active={value === 2}
|
||||
label="Tab 3"
|
||||
onClick={() => onChange(2)}
|
||||
onClose={action('Close tab 3')}
|
||||
onCloseAll={action('Close all')}
|
||||
onCloseOthers={action('Close others')}
|
||||
/>
|
||||
</ClosableTabs>
|
||||
{
|
||||
<TabContentContainer active={value === 0}>
|
||||
<div
|
||||
style={{ backgroundColor: 'green', height: '100%', flex: 1 }}
|
||||
>
|
||||
The second tab has a list of objects. Check that the scrolling
|
||||
position is maintained while navigating between tabs.
|
||||
</div>
|
||||
</TabContentContainer>
|
||||
}
|
||||
{
|
||||
<TabContentContainer active={value === 1}>
|
||||
<ObjectsList
|
||||
getThumbnail={() => 'res/unknown32.png'}
|
||||
project={project}
|
||||
objectsContainer={testLayout}
|
||||
onEditObject={action('On edit object')}
|
||||
selectedObjectNames={[]}
|
||||
selectedObjectTags={[]}
|
||||
onChangeSelectedObjectTags={() => {}}
|
||||
getAllObjectTags={() => []}
|
||||
<DragAndDropContextProvider>
|
||||
<FixedHeightFlexContainer height={400}>
|
||||
<Column expand>
|
||||
<ClosableTabs>
|
||||
<ClosableTab
|
||||
onActivated={action('Tab 1 activated')}
|
||||
closable
|
||||
active={value === 0}
|
||||
label="Tab 1"
|
||||
onClick={() => onChange(0)}
|
||||
onClose={action('Close tab 1')}
|
||||
onCloseAll={action('Close all')}
|
||||
onCloseOthers={action('Close others')}
|
||||
/>
|
||||
</TabContentContainer>
|
||||
}
|
||||
{
|
||||
<TabContentContainer active={value === 2}>
|
||||
<div
|
||||
style={{ backgroundColor: 'green', height: '100%', flex: 1 }}
|
||||
>
|
||||
Tab 3 content
|
||||
</div>
|
||||
</TabContentContainer>
|
||||
}
|
||||
</Column>
|
||||
</FixedHeightFlexContainer>
|
||||
<ClosableTab
|
||||
onActivated={action('Tab 2 activated')}
|
||||
closable
|
||||
active={value === 1}
|
||||
label="Tab 2"
|
||||
onClick={() => onChange(1)}
|
||||
onClose={action('Close tab 2')}
|
||||
onCloseAll={action('Close all')}
|
||||
onCloseOthers={action('Close others')}
|
||||
/>
|
||||
<ClosableTab
|
||||
onActivated={action('Tab 3 activated')}
|
||||
closable
|
||||
active={value === 2}
|
||||
label="Tab 3"
|
||||
onClick={() => onChange(2)}
|
||||
onClose={action('Close tab 3')}
|
||||
onCloseAll={action('Close all')}
|
||||
onCloseOthers={action('Close others')}
|
||||
/>
|
||||
</ClosableTabs>
|
||||
{
|
||||
<TabContentContainer active={value === 0}>
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: 'green',
|
||||
height: '100%',
|
||||
flex: 1,
|
||||
}}
|
||||
>
|
||||
The second tab has a list of objects. Check that the
|
||||
scrolling position is maintained while navigating between
|
||||
tabs.
|
||||
</div>
|
||||
</TabContentContainer>
|
||||
}
|
||||
{
|
||||
<TabContentContainer active={value === 1}>
|
||||
<ObjectsList
|
||||
getThumbnail={() => 'res/unknown32.png'}
|
||||
project={project}
|
||||
objectsContainer={testLayout}
|
||||
onEditObject={action('On edit object')}
|
||||
selectedObjectNames={[]}
|
||||
selectedObjectTags={[]}
|
||||
onChangeSelectedObjectTags={() => {}}
|
||||
getAllObjectTags={() => []}
|
||||
canRenameObject={() => true}
|
||||
onDeleteObject={(objectWithContext, cb) => cb(true)}
|
||||
onRenameObject={(objectWithContext, newName, cb) =>
|
||||
cb(true)
|
||||
}
|
||||
onObjectCreated={() => {}}
|
||||
onObjectSelected={() => {}}
|
||||
/>
|
||||
</TabContentContainer>
|
||||
}
|
||||
{
|
||||
<TabContentContainer active={value === 2}>
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: 'green',
|
||||
height: '100%',
|
||||
flex: 1,
|
||||
}}
|
||||
>
|
||||
Tab 3 content
|
||||
</div>
|
||||
</TabContentContainer>
|
||||
}
|
||||
</Column>
|
||||
</FixedHeightFlexContainer>
|
||||
</DragAndDropContextProvider>
|
||||
)}
|
||||
/>
|
||||
));
|
||||
@@ -801,6 +1011,112 @@ storiesOf('HelpFinder', module)
|
||||
.addDecorator(muiDecorator)
|
||||
.add('default', () => <HelpFinder open onClose={action('close')} />);
|
||||
|
||||
storiesOf('PropertiesEditor', module)
|
||||
.addDecorator(muiDecorator)
|
||||
.add('default', () => (
|
||||
<PropertiesEditor
|
||||
schema={[
|
||||
{
|
||||
name: 'Object name',
|
||||
valueType: 'string',
|
||||
disabled: true,
|
||||
getValue: instance => 'Disabled field',
|
||||
setValue: (instance, newValue) => {},
|
||||
onEditButtonClick: instance => action('edit button clicked'),
|
||||
},
|
||||
{
|
||||
name: 'Position',
|
||||
type: 'row',
|
||||
children: [
|
||||
{
|
||||
name: 'X',
|
||||
valueType: 'number',
|
||||
getValue: instance => 10,
|
||||
setValue: (instance, newValue) => {},
|
||||
},
|
||||
{
|
||||
name: 'Y',
|
||||
valueType: 'number',
|
||||
getValue: instance => 20.1234,
|
||||
setValue: (instance, newValue) => {},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Angle',
|
||||
valueType: 'number',
|
||||
getValue: instance => 90.123456,
|
||||
setValue: (instance, newValue) => {},
|
||||
},
|
||||
{
|
||||
name: 'Checked checkbox',
|
||||
valueType: 'boolean',
|
||||
getValue: instance => true,
|
||||
setValue: (instance, newValue) => {},
|
||||
},
|
||||
{
|
||||
name: 'Unchecked checkbox',
|
||||
valueType: 'boolean',
|
||||
getValue: instance => false,
|
||||
setValue: (instance, newValue) => {},
|
||||
},
|
||||
]}
|
||||
instances={[{ name: 'instance1' }, { name: 'instance2' }]}
|
||||
/>
|
||||
))
|
||||
.add('row (window width = medium)', () => (
|
||||
<PropertiesEditor
|
||||
schema={[
|
||||
{
|
||||
name: 'Position',
|
||||
type: 'row',
|
||||
children: [
|
||||
{
|
||||
name: 'X',
|
||||
valueType: 'number',
|
||||
getValue: instance => 10,
|
||||
setValue: (instance, newValue) => {},
|
||||
},
|
||||
{
|
||||
name: 'Y',
|
||||
valueType: 'number',
|
||||
getValue: instance => 20.1234,
|
||||
setValue: (instance, newValue) => {},
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
instances={[{ name: 'instance1' }, { name: 'instance2' }]}
|
||||
windowWidth="medium"
|
||||
/>
|
||||
))
|
||||
.add('row (window width = small)', () => (
|
||||
<PropertiesEditor
|
||||
schema={[
|
||||
{
|
||||
name: 'Position',
|
||||
type: 'row',
|
||||
children: [
|
||||
{
|
||||
name: 'X',
|
||||
valueType: 'number',
|
||||
getValue: instance => 10,
|
||||
setValue: (instance, newValue) => {},
|
||||
},
|
||||
{
|
||||
name: 'Y',
|
||||
valueType: 'number',
|
||||
getValue: instance => 20.1234,
|
||||
setValue: (instance, newValue) => {},
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
instances={[{ name: 'instance1' }, { name: 'instance2' }]}
|
||||
windowWidth="small"
|
||||
/>
|
||||
));
|
||||
|
||||
storiesOf('ParameterFields', module)
|
||||
.addDecorator(paperDecorator)
|
||||
.addDecorator(muiDecorator)
|
||||
@@ -1410,7 +1726,7 @@ storiesOf('StartPage', module)
|
||||
storiesOf('DebuggerContent', module)
|
||||
.addDecorator(muiDecorator)
|
||||
.add('with data', () => (
|
||||
<DragDropContextProvider>
|
||||
<DragAndDropContextProvider>
|
||||
<FixedHeightFlexContainer height={550}>
|
||||
<DebuggerContent
|
||||
gameData={debuggerGameDataDump}
|
||||
@@ -1425,10 +1741,10 @@ storiesOf('DebuggerContent', module)
|
||||
profilingInProgress={false}
|
||||
/>
|
||||
</FixedHeightFlexContainer>
|
||||
</DragDropContextProvider>
|
||||
</DragAndDropContextProvider>
|
||||
))
|
||||
.add('without data', () => (
|
||||
<DragDropContextProvider>
|
||||
<DragAndDropContextProvider>
|
||||
<FixedHeightFlexContainer height={550}>
|
||||
<DebuggerContent
|
||||
gameData={null}
|
||||
@@ -1443,13 +1759,13 @@ storiesOf('DebuggerContent', module)
|
||||
profilingInProgress={true}
|
||||
/>
|
||||
</FixedHeightFlexContainer>
|
||||
</DragDropContextProvider>
|
||||
</DragAndDropContextProvider>
|
||||
));
|
||||
|
||||
storiesOf('Profiler', module)
|
||||
.addDecorator(muiDecorator)
|
||||
.add('without profiler output', () => (
|
||||
<DragDropContextProvider>
|
||||
<DragAndDropContextProvider>
|
||||
<FixedHeightFlexContainer height={550}>
|
||||
<Profiler
|
||||
onStart={action('start profiler')}
|
||||
@@ -1458,10 +1774,10 @@ storiesOf('Profiler', module)
|
||||
profilingInProgress={false}
|
||||
/>
|
||||
</FixedHeightFlexContainer>
|
||||
</DragDropContextProvider>
|
||||
</DragAndDropContextProvider>
|
||||
))
|
||||
.add('without profiler output, while profiling', () => (
|
||||
<DragDropContextProvider>
|
||||
<DragAndDropContextProvider>
|
||||
<FixedHeightFlexContainer height={550}>
|
||||
<Profiler
|
||||
onStart={action('start profiler')}
|
||||
@@ -1470,10 +1786,10 @@ storiesOf('Profiler', module)
|
||||
profilingInProgress={true}
|
||||
/>
|
||||
</FixedHeightFlexContainer>
|
||||
</DragDropContextProvider>
|
||||
</DragAndDropContextProvider>
|
||||
))
|
||||
.add('with profiler output', () => (
|
||||
<DragDropContextProvider>
|
||||
<DragAndDropContextProvider>
|
||||
<FixedHeightFlexContainer height={550}>
|
||||
<Profiler
|
||||
onStart={action('start profiler')}
|
||||
@@ -1482,10 +1798,10 @@ storiesOf('Profiler', module)
|
||||
profilingInProgress={false}
|
||||
/>
|
||||
</FixedHeightFlexContainer>
|
||||
</DragDropContextProvider>
|
||||
</DragAndDropContextProvider>
|
||||
))
|
||||
.add('with profiler output, while profiling', () => (
|
||||
<DragDropContextProvider>
|
||||
<DragAndDropContextProvider>
|
||||
<FixedHeightFlexContainer height={550}>
|
||||
<Profiler
|
||||
onStart={action('start profiler')}
|
||||
@@ -1494,7 +1810,7 @@ storiesOf('Profiler', module)
|
||||
profilingInProgress={true}
|
||||
/>
|
||||
</FixedHeightFlexContainer>
|
||||
</DragDropContextProvider>
|
||||
</DragAndDropContextProvider>
|
||||
));
|
||||
|
||||
storiesOf('MeasuresTable', module)
|
||||
@@ -1531,8 +1847,8 @@ storiesOf('LayoutChooserDialog', module)
|
||||
|
||||
storiesOf('EventsTree', module)
|
||||
.addDecorator(muiDecorator)
|
||||
.add('default (no scope)', () => (
|
||||
<DragDropContextProvider>
|
||||
.add('default, medium screen (no scope)', () => (
|
||||
<DragAndDropContextProvider>
|
||||
<div className="gd-events-sheet">
|
||||
<FixedHeightFlexContainer height={500}>
|
||||
<EventsTree
|
||||
@@ -1562,16 +1878,56 @@ storiesOf('EventsTree', module)
|
||||
searchFocusOffset={null}
|
||||
onEventMoved={() => {}}
|
||||
showObjectThumbnails={true}
|
||||
screenType={'normal'}
|
||||
windowWidth={'medium'}
|
||||
/>
|
||||
</FixedHeightFlexContainer>
|
||||
</div>
|
||||
</DragDropContextProvider>
|
||||
</DragAndDropContextProvider>
|
||||
))
|
||||
.add('default, small screen (no scope)', () => (
|
||||
<DragAndDropContextProvider>
|
||||
<div className="gd-events-sheet">
|
||||
<FixedHeightFlexContainer height={500}>
|
||||
<EventsTree
|
||||
events={testLayout.getEvents()}
|
||||
project={project}
|
||||
scope={{ layout: testLayout }}
|
||||
globalObjectsContainer={project}
|
||||
objectsContainer={testLayout}
|
||||
selection={getInitialSelection()}
|
||||
onAddNewInstruction={action('add new instruction')}
|
||||
onPasteInstructions={action('paste instructions')}
|
||||
onMoveToInstruction={action('move to instruction')}
|
||||
onMoveToInstructionsList={action('move instruction to list')}
|
||||
onInstructionClick={action('instruction click')}
|
||||
onInstructionDoubleClick={action('instruction double click')}
|
||||
onInstructionContextMenu={action('instruction context menu')}
|
||||
onInstructionsListContextMenu={action(
|
||||
'instruction list context menu'
|
||||
)}
|
||||
onParameterClick={action('parameter click')}
|
||||
onEventClick={action('event click')}
|
||||
onEventContextMenu={action('event context menu')}
|
||||
onAddNewEvent={action('add new event')}
|
||||
onOpenExternalEvents={action('open external events')}
|
||||
onOpenLayout={action('open layout')}
|
||||
searchResults={null}
|
||||
searchFocusOffset={null}
|
||||
onEventMoved={() => {}}
|
||||
showObjectThumbnails={true}
|
||||
screenType={'normal'}
|
||||
windowWidth={'small'}
|
||||
/>
|
||||
</FixedHeightFlexContainer>
|
||||
</div>
|
||||
</DragAndDropContextProvider>
|
||||
));
|
||||
|
||||
storiesOf('EventsSheet', module)
|
||||
.addDecorator(muiDecorator)
|
||||
.add('default (no scope)', () => (
|
||||
<DragDropContextProvider>
|
||||
<DragAndDropContextProvider>
|
||||
<FixedHeightFlexContainer height={500}>
|
||||
<EventsSheet
|
||||
project={project}
|
||||
@@ -1596,10 +1952,10 @@ storiesOf('EventsSheet', module)
|
||||
onCreateEventsFunction={action('create events function')}
|
||||
/>
|
||||
</FixedHeightFlexContainer>
|
||||
</DragDropContextProvider>
|
||||
</DragAndDropContextProvider>
|
||||
))
|
||||
.add('empty (no events) (no scope)', () => (
|
||||
<DragDropContextProvider>
|
||||
<DragAndDropContextProvider>
|
||||
<FixedHeightFlexContainer height={500}>
|
||||
<EventsSheet
|
||||
project={project}
|
||||
@@ -1624,7 +1980,7 @@ storiesOf('EventsSheet', module)
|
||||
onCreateEventsFunction={action('create events function')}
|
||||
/>
|
||||
</FixedHeightFlexContainer>
|
||||
</DragDropContextProvider>
|
||||
</DragAndDropContextProvider>
|
||||
));
|
||||
|
||||
storiesOf('EventsSheet/EventsFunctionExtractorDialog', module)
|
||||
@@ -2015,41 +2371,57 @@ storiesOf('ObjectsList', module)
|
||||
.addDecorator(paperDecorator)
|
||||
.addDecorator(muiDecorator)
|
||||
.add('default', () => (
|
||||
<SerializedObjectDisplay object={testLayout}>
|
||||
<div style={{ height: 250 }}>
|
||||
<ObjectsList
|
||||
getThumbnail={() => 'res/unknown32.png'}
|
||||
project={project}
|
||||
objectsContainer={testLayout}
|
||||
onEditObject={action('On edit object')}
|
||||
selectedObjectNames={[]}
|
||||
selectedObjectTags={[]}
|
||||
onChangeSelectedObjectTags={selectedObjectTags => {}}
|
||||
getAllObjectTags={() => []}
|
||||
/>
|
||||
</div>
|
||||
</SerializedObjectDisplay>
|
||||
<DragAndDropContextProvider>
|
||||
<SerializedObjectDisplay object={testLayout}>
|
||||
<div style={{ height: 250 }}>
|
||||
<ObjectsList
|
||||
getThumbnail={() => 'res/unknown32.png'}
|
||||
project={project}
|
||||
objectsContainer={testLayout}
|
||||
onEditObject={action('On edit object')}
|
||||
onObjectCreated={action('On object created')}
|
||||
selectedObjectNames={[]}
|
||||
selectedObjectTags={[]}
|
||||
onChangeSelectedObjectTags={selectedObjectTags => {}}
|
||||
getAllObjectTags={() => []}
|
||||
canRenameObject={() => true}
|
||||
onDeleteObject={(objectWithContext, cb) => cb(true)}
|
||||
onRenameObject={(objectWithContext, newName, cb) => cb(true)}
|
||||
onObjectSelected={() => {}}
|
||||
/>
|
||||
</div>
|
||||
</SerializedObjectDisplay>
|
||||
</DragAndDropContextProvider>
|
||||
))
|
||||
.add('with tags', () => (
|
||||
<SerializedObjectDisplay object={testLayout}>
|
||||
<div style={{ height: 250 }}>
|
||||
<ObjectsList
|
||||
getThumbnail={() => 'res/unknown32.png'}
|
||||
project={project}
|
||||
objectsContainer={testLayout}
|
||||
onEditObject={action('On edit object')}
|
||||
selectedObjectNames={[]}
|
||||
selectedObjectTags={['Tag1', 'Tag2']}
|
||||
onChangeSelectedObjectTags={action('on change selected object tags')}
|
||||
getAllObjectTags={() => [
|
||||
'Tag1',
|
||||
'Tag2',
|
||||
'Looooooooooong Tag 3',
|
||||
'Unselected Tag 4',
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</SerializedObjectDisplay>
|
||||
<DragAndDropContextProvider>
|
||||
<SerializedObjectDisplay object={testLayout}>
|
||||
<div style={{ height: 250 }}>
|
||||
<ObjectsList
|
||||
getThumbnail={() => 'res/unknown32.png'}
|
||||
project={project}
|
||||
objectsContainer={testLayout}
|
||||
onEditObject={action('On edit object')}
|
||||
onObjectCreated={action('On object created')}
|
||||
selectedObjectNames={[]}
|
||||
selectedObjectTags={['Tag1', 'Tag2']}
|
||||
onChangeSelectedObjectTags={action(
|
||||
'on change selected object tags'
|
||||
)}
|
||||
getAllObjectTags={() => [
|
||||
'Tag1',
|
||||
'Tag2',
|
||||
'Looooooooooong Tag 3',
|
||||
'Unselected Tag 4',
|
||||
]}
|
||||
canRenameObject={() => true}
|
||||
onDeleteObject={(objectWithContext, cb) => cb(true)}
|
||||
onRenameObject={(objectWithContext, newName, cb) => cb(true)}
|
||||
onObjectSelected={() => {}}
|
||||
/>
|
||||
</div>
|
||||
</SerializedObjectDisplay>
|
||||
</DragAndDropContextProvider>
|
||||
));
|
||||
|
||||
storiesOf('ObjectSelector', module)
|
||||
@@ -2624,7 +2996,7 @@ storiesOf('EventsFunctionsList', module)
|
||||
storiesOf('EventsFunctionsExtensionEditor/index', module)
|
||||
.addDecorator(muiDecorator)
|
||||
.add('default', () => (
|
||||
<DragDropContextProvider>
|
||||
<DragAndDropContextProvider>
|
||||
<FixedHeightFlexContainer height={500}>
|
||||
<EventsFunctionsExtensionEditor
|
||||
project={project}
|
||||
@@ -2641,7 +3013,23 @@ storiesOf('EventsFunctionsExtensionEditor/index', module)
|
||||
onCreateEventsFunction={action('on create events function')}
|
||||
/>
|
||||
</FixedHeightFlexContainer>
|
||||
</DragDropContextProvider>
|
||||
</DragAndDropContextProvider>
|
||||
));
|
||||
|
||||
storiesOf(
|
||||
'EventsFunctionsExtensionEditor/ChooseEventsFunctionsExtensionEditor',
|
||||
module
|
||||
)
|
||||
.addDecorator(muiDecorator)
|
||||
.add('default', () => (
|
||||
<FixedHeightFlexContainer height={500}>
|
||||
<ChooseEventsFunctionsExtensionEditor
|
||||
eventsFunctionsExtension={testEventsFunctionsExtension}
|
||||
onEditBehaviors={action('edit behaviors')}
|
||||
onEditFreeFunctions={action('edit free functions')}
|
||||
onEditExtensionOptions={action('edit extension options')}
|
||||
/>
|
||||
</FixedHeightFlexContainer>
|
||||
));
|
||||
|
||||
storiesOf('EventsFunctionsExtensionEditor/OptionsEditorDialog', module)
|
||||
|
||||
+20
-4
@@ -10340,9 +10340,9 @@ pixi-gl-core@^1.1.4:
|
||||
resolved "https://registry.yarnpkg.com/pixi-gl-core/-/pixi-gl-core-1.1.4.tgz#8b4b5c433b31e419bc379dc565ce1b835a91b372"
|
||||
integrity sha1-i0tcQzsx5Bm8N53FZc4bg1qRs3I=
|
||||
|
||||
"pixi-simple-gesture@git://github.com/4ian/pixi-simple-gesture#v0.3.2":
|
||||
version "0.3.2"
|
||||
resolved "git://github.com/4ian/pixi-simple-gesture#0e42e78b55c63ebd157f52f42da65b0cc36f6360"
|
||||
"pixi-simple-gesture@git://github.com/4ian/pixi-simple-gesture#v0.3.3":
|
||||
version "0.3.3"
|
||||
resolved "git://github.com/4ian/pixi-simple-gesture#c84e0cc3c62edeca019e708d9897ef6b97a0d18a"
|
||||
|
||||
[email protected]:
|
||||
version "4.8.6"
|
||||
@@ -11637,6 +11637,15 @@ [email protected], react-dnd-html5-backend@^7.0.1:
|
||||
dependencies:
|
||||
dnd-core "^7.7.0"
|
||||
|
||||
[email protected]:
|
||||
version "3.2.2"
|
||||
resolved "https://registry.yarnpkg.com/react-dnd-multi-backend/-/react-dnd-multi-backend-3.2.2.tgz#4ecb20ccdfc753fa8a1e457a4df04606251d8f8f"
|
||||
integrity sha512-94lBSZ8acVLeV2nyBLNwILcWcEGAcGrMzNRLIVeUI1yC0Uq1SaRM+5rdEyrElR9GMDjn0U41jVBlfS+usccZTA==
|
||||
dependencies:
|
||||
dnd-multi-backend "^3.2.1"
|
||||
prop-types "^15.6.2"
|
||||
react-dnd-preview "^3.2.2"
|
||||
|
||||
react-dnd-multi-backend@^3.2.1:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/react-dnd-multi-backend/-/react-dnd-multi-backend-3.2.1.tgz#bfda3b72b738704d638c56fd5e992c3fac556652"
|
||||
@@ -11653,7 +11662,14 @@ react-dnd-preview@^3.2.1:
|
||||
dependencies:
|
||||
prop-types "^15.6.2"
|
||||
|
||||
react-dnd-touch-backend@^0.8.3:
|
||||
react-dnd-preview@^3.2.2:
|
||||
version "3.2.2"
|
||||
resolved "https://registry.yarnpkg.com/react-dnd-preview/-/react-dnd-preview-3.2.2.tgz#194d620f7bebaec9bd2cd38672abc7b24fa1474a"
|
||||
integrity sha512-IuuU9X4cX7b0xahnbOcj+Ec2VGBlPJHogZXUCtfp/JfVdvoW8M1M/jA8rx28BlIJ7BAc0YY24TxHk5Rm4faExQ==
|
||||
dependencies:
|
||||
prop-types "^15.6.2"
|
||||
|
||||
[email protected], react-dnd-touch-backend@^0.8.3:
|
||||
version "0.8.3"
|
||||
resolved "https://registry.yarnpkg.com/react-dnd-touch-backend/-/react-dnd-touch-backend-0.8.3.tgz#85d961ec8578dc9efa5b78000ea904d37e01e60f"
|
||||
integrity sha512-LLJlpeJftRreP9ixm3yfd1JEXf+6BeKo5dkfJFHvNSOEOu+FDSwNl5l8uAG0FeD4z2ZVi7RDQ/jUxdjkBV3zBg==
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#Script launching the generation of the documentations of all modules
|
||||
#Install globally doxgen and jsdoc before starting it.
|
||||
echo Generating all docs...
|
||||
mkdir -p ../docs
|
||||
mkdir -p logs
|
||||
cd ..
|
||||
cd Core/docs
|
||||
|
||||
Reference in New Issue
Block a user