Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Piskel Resize prompt for any new animations + ability to set name and subfolder of new animations! #522

Merged
merged 20 commits into from
Jun 23, 2018

Conversation

blurymind
Copy link
Contributor

@blurymind blurymind commented Jun 15, 2018

This makes the resize pop up show up whenever the user opens a brand new piskel animation

It also enables the ability to select the folder where you want piskel to create new frames, as well as the name of the files:
piskel-enableselectingpath
Note:

  • if you select a folder outside of your project- piskel will refuse to use it
  • On animations imported from gdevelop- the folder and the name can not be edited by design- so the user doesnt make the mistake of saving half their sequence in another folder
  • If there is no animation name used in gdevelop, piskel will set it upon saving - so the animation name used in piskel is consistent with the one used in gdevelop!

This also makes sure that the user doesnt put any symbols in the animation name that would lead to an invalid filepath

This makes the resize pop up show up whenever the user opens a brand new piskel animation
- this adds ability to set name of new piskel frames
- it also paves the way to transfer the name set in piskel back to gdevelop
@blurymind blurymind changed the title Call Resize prompt on Piskel for any new animations Call Resize prompt on Piskel for any new animations + ability to set name of animation - affecting name of new files Jun 15, 2018
@blurymind
Copy link
Contributor Author

blurymind commented Jun 15, 2018

Added ability to set name of animation - It only affects the name of frames created in piskel- it will not rename any imported files from gdevelop, but should help with organizing!

I also made it so the user cant use any input characters that would lead to an invalid basepath

Now I am trying to figure out how to use the name fetched from piskel to set the changed animation's optional name - @4ian should i somehow pass it to the onchangessaved function?
Wondering how to properly get it to set the optional name

@4ian
Copy link
Owner

4ian commented Jun 17, 2018

should i somehow pass it to the onchangessaved function?

Yes I think you can pass it and use it to pass it to a function that will change the animation name.
Let me know when I can check the code :)

@@ -21,6 +21,7 @@
"classnames": "2.2.5",
"create-react-context": "^0.1.6",
"date-fns": "^1.29.0",
"electron": "^2.0.2",
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should not commit any changes that you made to package.json or package-lock.json. Can you try to remove these?
You may want to read about git add (if you're using command line) (and git add -p, which is super useful to choose the part to add in your commit). If you're using a graphical client you should also be able to choose the files to include in your commits.

In general, only commit the changes related to your feature - otherwise I'll have to edit your commits and remove these :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah sorry, i thought it was in the gitignore

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The gitignore contains the file that are not in the git repository at all.
It's a bit a pain at the beginning but once you know how to choose the files to commit, you can make much cleaner commits :) (by not selecting files like package.json or package-lock.json, or files with irrelevant changes)

guess the subfolder via  the path of the first loaded resource
@blurymind
Copy link
Contributor Author

blurymind commented Jun 17, 2018

@4ian can you help me in changing it (the animation name)? I am not sure how to pass it through the onchangessaved function. I got piskel to send it back to gdevelop, but can't figure out how to tell gdevelop to change it.
So if the user hasn't entered a name in gdevelop yet- make gdevelop automatically take the one set in piskel. It would be a nice touch imo :)

I also want to send the name to piskel too - so piskel uses the animation name if the user has entered a name- instead of the object name. The object name is the backup option
Edit: Oh wait- it looks like you are already passing the animation name to piskel :D

@4ian
Copy link
Owner

4ian commented Jun 17, 2018

First, let's pass the name from Piskel back to GD:

  1. (Piskel) When calling ipcRenderer.send('piskel-changes-saved', outputResources); in Piskel, add the extra argument that you want to pass: ipcRenderer.send('piskel-changes-saved', outputResources, name); (or a more complex object instead of name if necessary).

  2. (Electron "bridge") Then add the argument in ipcMain.on('piskel-changes-saved', (event, imageResources) => { and mainWindow.webContents.send('piskel-changes-saved', imageResources);. Relaunch electron as you've here modified some electron related files.

  3. (App) Then, add it to ipcRenderer.on('piskel-changes-saved', (event, outputResources) => { (in LocalPiskelBridge.js). Add it also to the call to onChangesSaved to have the name passed back to the SpritesList.

EDIT: In fact you've already done what I've said above ;) Here is the more interesting part then:

Now, you have the name (or whatever more complex object) in GDevelop SpritesList.
The responsibility of SpritesList though is only to display the list. To get the direction, you have to add a prop to SpritesList which is called for example "onChangeAnimationName". Take example on onReplaceByDirection and replaceDirection. You must do the same with a prop called onChangeAnimationName and a method called changeAnimationName (taking animationId and the new name as arguments) in SpriteEditor/index.js.
In fact this function already exists! Here it is: https://github.com/4ian/GD/blob/af4cdcd48542fcda671704fe5800f517ab02ca83/newIDE/app/src/ObjectEditor/Editors/SpriteEditor/index.js#L268

The job is mainly to be able to use React "props" to pass the function down to the SpritesList, and call it when you want to update the name.

EDIT : (If you're unfamiliar with props, maybe you can take a look at this React doc: https://reactjs.org/docs/handling-events.html)

...but not both. This will make more sense when the animation name on new animations is set in piskel and piskel sends it back to gdevelop
@blurymind
Copy link
Contributor Author

@4ian Thank you for the explanation :)
This is indeed very interesting. I will add this final part tomorrow hopefully and will be ready for review.
I think trying to tackle saving layers needs to have its own pull request - once this one is good to go.

Btw I love the way you have designed gdevelop's new editor, it's a beautiful thing 👍

@4ian
Copy link
Owner

4ian commented Jun 17, 2018

Cool, let me know how it goes!

You can see that changeAnimationName is passed as a prop to SortableAnimationsList as onChangeAnimationName. Then onChangeAnimationName is passed from SortableAnimationsList to SortableAnimation as a prop named onChangeName. Finally, onChangeName is passed to the text field containing the animation name. SpritesList is also there so you can reuse this props and pass it to SpritesList, and finally call it at the right moment in SpritesList.
Should not be too difficult in fact!

Btw I love the way you have designed gdevelop's new editor, it's a beautiful thing 👍

Thanks! It's the way how everything is architectured in the IDE with React: components are handling some part of the interface and use props to pass or get information to their children or parent components - where the real work is done. Need a bit of reading to find where to add new code, but the result is clean and really modular.

@blurymind
Copy link
Contributor Author

blurymind commented Jun 19, 2018

@4ian how would one export the function changeAnimationName from a class that is not exported in index.js?

I tried everything,including importing and using onChangeAnimationName, and it still says that it is not a function

Do I need to edit multiple files to get access to that function in spriteList.js?
I must be missing something. Do I need to also export it in the file where it is established

Can you show me just in this instance how one would import it in the best way?

@blurymind
Copy link
Contributor Author

@4ian I figured it out!!
I wasn't exporting it indeed.
I exported onchangeName in index.js and using it in SpriteList.js
Thank you @4ian , git comit coming later today :)

@4ian
Copy link
Owner

4ian commented Jun 19, 2018

So you need to call onChangeAnimationName in SpritesList. First thing to do is to check if it's present in the props of SpritesList, here: https://github.com/4ian/GD/blob/af4cdcd48542fcda671704fe5800f517ab02ca83/newIDE/app/src/ObjectEditor/Editors/SpriteEditor/index.js#L134

The answer is no, there is no such props that we can use that is passed by Animation component to SpritesList component. So let's see if we can find a prop or a function doing the job in Animation. The answer is yes:

https://github.com/4ian/GD/blob/af4cdcd48542fcda671704fe5800f517ab02ca83/newIDE/app/src/ObjectEditor/Editors/SpriteEditor/index.js#L107

You can see being passed to another component here: https://github.com/4ian/GD/blob/af4cdcd48542fcda671704fe5800f517ab02ca83/newIDE/app/src/ObjectEditor/Editors/SpriteEditor/index.js#L122

Seems that its usage is quite simple: call it why the new name of the function as argument. Typing is helping us to ensure that this is the case:

https://github.com/4ian/GD/blob/af4cdcd48542fcda671704fe5800f517ab02ca83/newIDE/app/src/ObjectEditor/Editors/SpriteEditor/index.js#L88

So let's pass it to SpritesList! At the end of SpritesList props, pass it by adding onChangeName={onChangeName} (which means that we just pass to SpritesList the function):
https://github.com/4ian/GD/blob/af4cdcd48542fcda671704fe5800f517ab02ca83/newIDE/app/src/ObjectEditor/Editors/SpriteEditor/index.js#L149

Once you've added this props, it becames available in SpritesList. You can use it anywhere in the SpritesList component by using this.props.onChangeName. For us, we'll want to use it in onChangesSaved, at the end of the function: https://github.com/4ian/GD/blob/af4cdcd48542fcda671704fe5800f517ab02ca83/newIDE/app/src/ObjectEditor/Editors/SpriteEditor/SpritesList.js#L265

Something like this: this.props.onChangeName(newAnimationName).
(You can start by trying to call yourself this.props.onChangeName("Test123") somewhere else in the function).

When you call this function in SpritesList, the original function changeAnimationName from SpriteEditor/index.js is used:

https://github.com/4ian/GD/blob/af4cdcd48542fcda671704fe5800f517ab02ca83/newIDE/app/src/ObjectEditor/Editors/SpriteEditor/index.js#L268

React will update any children component using the animation name so that the change is reflected everywhere.

@4ian
Copy link
Owner

4ian commented Jun 19, 2018

Following my previous explanation, the last thing you need to do is to "register" the new prop onChangeName by adding it to the Props "typing" after this line: https://github.com/4ian/GD/blob/af4cdcd48542fcda671704fe5800f517ab02ca83/newIDE/app/src/ObjectEditor/Editors/SpriteEditor/SpritesList.js#L153

Add your new prop: onChangeName: string => void (meaning that it's a function that takes a string and returns nothing).
This help the type checker "flowtype" to verify that you're properly using the function when calling it or passing it to the component. This avoid error when you forget to pass a function. Especially useful when you reuse your components everywhere in the codebase (which is not the case here, but still helpful to catch errors).

@blurymind
Copy link
Contributor Author

blurymind commented Jun 19, 2018

@4ian I did it by adding this line to spriteList's return function (index.js):
onChangeName={onChangeName}

That allowed me to use it like this in SpriteList.js

        if (animationName.length === 0) {
          onChangeName(newAnimationName)
        };

but I also did as you said- registering it like this

onChangeName: string => void

So if the user hasnt set any optional animation name- piskel will automatically set it in gdevelop, depending on what was set in piskel when the animation was created

@blurymind
Copy link
Contributor Author

Btw thank you for explaining this. Passing functions between modules confused me at first

@4ian
Copy link
Owner

4ian commented Jun 19, 2018

That allowed me to use it like this in SpriteList.js

Yes that looks correct! :)
Don't forget to update the flow typing. You can check for errors by running npm run flow (or yarn flow) and/or installing the Flowtype extension for VSCode if you use it.

Passing functions in "props" is the React way to "export" functions to other components. It is what makes all component reusable because you can easily plug any function to any props and all will behave properly. Much like assembling bricks and plugging them together :)

@blurymind
Copy link
Contributor Author

blurymind commented Jun 19, 2018

@4ian ready for review now :)
I updated the description of the pull too- for better documentation

We can now:

  • Use subfolders for animations made in piskel
  • Specify name for the image sequence and animation name when creating them
  • Autoresize automatically shows up on new animations

For support of saving layers -i need to do that in another pull- when we figure how to approach that

@blurymind blurymind changed the title Call Resize prompt on Piskel for any new animations + ability to set name of animation - affecting name of new files Piskel Resize prompt for any new animations + ability to set name and subfolder of new animations! Jun 19, 2018
@4ian
Copy link
Owner

4ian commented Jun 20, 2018

Great! Will try to check this today, posting comments as usual and we'll merge this when it is ready then. Thanks for your work so far 👍

Copy link
Owner

@4ian 4ian left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've left various comments about different things, that will help for clarity and maintainability. Thanks again for your work so far :)
Let me know if you have any question!

@@ -4,12 +4,52 @@ const path = require('path');
const fs = require('fs');
const async = require('async');
const remote = electron.remote;
const { dialog } = require('electron').remote;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpicking: You can do const { dialog } = remote, as remote is defined on the previous line.

if (!projectBasePath) {
projectBasePath = piskelOptions.projectPath;
}
var selectedDir = dialog.showOpenDialog(remote.getCurrentWindow(), {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpicking: can you use const instead of var? :)

return;
}
piskelOptions.projectPath = selectedDir;
alert('New frames will be saved in:\n' + piskelOptions.projectPath);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think this alert is necessary? I'm thinking it can be a bit redundant. If the user clicked on the folder and chose a new folder, the UI will be updated with the new path, I think it's clear enough.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will remove it. You are right - it might get annoying after the first time :)


const editorContentWindow = document.getElementById('piskel-frame')
.contentWindow;
let baseExportPath;
let piskelOptions; // The options received from GDevelop

let saveFolderLabel,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpicking: you're set piskelAnimationNameInput to an empty object, while later your making it a reference to a DOM input element (as the name properly implies).
It's best to then avoid using an object (which confuses the source code reader) and set it explicitly to null, null meaning that "this input is not yet created". You may have to update one or two other places to ensure that piskelAnimationNameInput is not null then before doing a change.

Objects are more to store data.

); // Don't allow the user to enter any characters that would lead to an invalid path
piskelOptions.name = piskelAnimationNameInput.value;
baseExportPath = piskelOptions.projectPath + '/' + piskelOptions.name;
saveFolderLabel.innerHTML = piskelOptions.projectPath + '\\';
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid innerHTML as much as possible, consider using .textContent. It will first avoid you to have to escape characters, and above all it avoid XSS injections attacks that are possible when using innerHTML.
(All of this is not needed with a UI framework like React - which one of the many reason why it's great :))

More interesting information available here: https://stackoverflow.com/questions/24427621/innertext-vs-innerhtml-vs-label-vs-text-vs-textcontent-vs-outertext

objectName,
animationName,
animationName,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpicking: there is a useless extra space at the end of the line ;)

isLooping: direction.isLooping(),
},
onChangesSaved: resources => {
onChangesSaved: (resources,newAnimationName) => {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpicking: missing a space before the comma, be sure to run prettier to fix this

@@ -54,8 +54,7 @@ export const openPiskel = ({
resourcesManager.addResource(imageResource);
imageResource.delete();
});

onChangesSaved(outputResources);
onChangesSaved(outputResources,newAnimationName);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpicking: missing space after the comma

Copy link
Contributor Author

@blurymind blurymind Jun 20, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah sorry, I will run format document on all the files again

@@ -11,7 +11,7 @@ export type ExternalEditorOpenOptions = {|
singleFrame: boolean, // If set to true, edition should be limited to a single frame
resourceNames: Array<string>,
onChangesSaved: (
Array<{ path: string, name: string, originalIndex: ?number }>
Array<{ path: string, name: string, originalIndex: ?number }>, newName: string
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 great, thanks for updating flow!

);
updatePiskelBasePath(); //update the path label
// Disable changing path and naming convention by user - on animations imported from gdevelop
saveFolderLabel.removeEventListener('click', selectBaseFolderPath);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe move this part (all the logic to disable folder choosing) in another function? Will help to achieve clarity by avoiding mixing the loading logic with the UI updating logic. :)

@blurymind
Copy link
Contributor Author

blurymind commented Jun 20, 2018

Applied all requested changes :) Comit coming later today 👍

@blurymind
Copy link
Contributor Author

@4ian pull submited =)

@4ian
Copy link
Owner

4ian commented Jun 21, 2018

Will double check this this evening :) Thanks!

@4ian
Copy link
Owner

4ian commented Jun 22, 2018

Looks ok like this. Will merge this week-end!
Can you still try to remove the package-lock.json from this PR? (easier to track changes in the history for later)

@blurymind
Copy link
Contributor Author

blurymind commented Jun 22, 2018

@4ian 'm sorry, I tried to remove it, but gitkraken wont let me unstage it, because it is in previous commits :(
I have no idea why it wasnt in the gitignore to begin with.. should have been more careful

@Lizard-13
Copy link
Contributor

This answer says that you have to reset the file in your pull request branch and make a new commit: https://stackoverflow.com/questions/39459467/remove-a-modified-file-from-pull-request

@blurymind
Copy link
Contributor Author

blurymind commented Jun 22, 2018

@Lizard-13 @4ian I solved it by reverting the comit in which it was introduced

Thank you for the help :)

@4ian
Copy link
Owner

4ian commented Jun 23, 2018

Good job with reverting the commit :)
Merging now. Thanks for working on it! 👍

@4ian 4ian merged commit 6cdee3b into 4ian:master Jun 23, 2018
@blurymind
Copy link
Contributor Author

blurymind commented Jun 23, 2018

@4ian thank you for reviewing this and the patience! Now the piskel implementation in gdevelop is almost feature complete. Only thing missing is the ability to preserve layer data

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants