Hi Mikko,
This sounds really great! Can't wait to try it all out.
Here my responses:
> Temporary State
> There are generally two ways to edit an item based on mouse movement. The first simpler kind is that you apply the delta movement on mousedrag. The second kind is that you store the original position (state) of the item and calculate the delta all the way from mousedown and the new position is the original position plus the delta. The second kind is generally more robust, and is needed if you want to do snapping or constraining.
Instead of serializing, couldn't you simply use clone for this? Maybe we should add a Item#swap() method which would swap one item with another.
> The first problem I ran into implementing this was that Paper.js does not retain the IDs over serialization. I fixed that simply by storing the item id, then calling importJSON and then restore the id back. It works, but I'm not sure if that is a good way to handle things.
Yeah that's a tricky one... Rather than preserving IDs in serialization, which doesn't seem like such a good idea. ids in Paper.js really are unique ids, and should be treated as such. I was considering renaming them to #uid to be more clear about that.
> The reason the ids are need to be the same is that we will capture the state once, but restore it multiple times, each time the mouse moves. If the ids change, then it is not possible to know which item states to restore.
I don't understand this problem. Can you explain better why a changing id is an issue here?
> The second snag I ran into was that item.exportJSON() returns a string, so I used paper.Base.serialize(item) to get an non-strigified json.
Yes I will fix this for you. There will be a `asString` property in the options object, defaulting to true. `item.exportJSON({ asString: false })` will produce the desired result then.
> Copy & Paste
> I used the same captureSelectionState() here for copy, but the difference between restoring state and paste is that you'd actually want to create new items so this was more straight forward. It took me a while to figure out how the API should be used to create new items based on json. It seems like that I could have called importJSON() on a layer, but feels really weird API choice.
Let me explain the current design:
item#importJSON() tries to import the JSON into the actual item. There are multiple scenarios:
- Importing a JSON describing a Path into a Path: The item does not get replaced, and preserves the ID and all. It will read its new state from the JSON, replacing all its segments, style, closed state, matrix, etc.
- Importing a JSON describing a Path, Raster, CompoundPath, etc. into a Layer or Group: A new item will be deserialized from the JSON and added as a child to the Layer, using addChild (I think).
- Importing a JSON describing a Group into a Group: The same happens asa for the Path above, the Group replaces its children with the ones deserialized from the JSON. The Group keeps its ID, but the children receive new ones, since they get replaced.
I think that's a pretty clean API choice, but perhaps the docs are out of sync. They are in quite a few places, I'm simply too swamped to keep track of all the docs at the same time, and am hoping for other people to join the effort there.
> I ended up using paper.Base.importJSON(). I wish the API would be more clear in this, like paper.Item.createFromJSON().
I'm considering Base.importJSON() kind of private... Ideally you wouldn't have to use it from outside. Does what I outlined above make it more clear? Does the current API facilitate what you need to do?
> Undo/Redo
> Undo was the most involved of the all. I stumbled into the same problem with IDs again. This time I solved it by storing the IDs in the data of the items, and then restoring the ids from there. I used paper.Base.serialize() again to store the state as a js object instead of string.
>
> One thing I could not get working was selection. In order to get expected results after undo, I'd store the selection separately. After the project has been restore, I would deselect all, and apply my stored selection.
>
> No matter what I did, I always ended up in a situation where an item or few were selected after undo or redo and I could not deselect them. So I had to manually call paper.project._selectedItems = {} after paper.project.deselectAll() to make this work.
That's very strange. It most definitely sounds like a bug. Do you think you could create a simple isolated test case for me to replicate and debug this? Perhaps on
sketch.paperjs.org?
> I did not end up using project._changes array for undo, as it would require a way to store the state of an object before modification too.
That makes sense! So how do you store the state?
> Summary
> I think these are quite usual cases for serialization apart from actually storing the data to disk.
>
> It would be nice if it was possible to control if the ids were retained in serialization. I think they should be always serialized, and there should be control over if they were to be deserialized. In addition it would be nice if the API regarding serialization would be a bit more clear, especially in the context of restoring state vs. creating new item. It is quite error prone if the same function does both.
I don't agree, for the reason stated above (uids). I think ids shouldn't play a role at all except internally... Couldn't you do what you're doing without ids, but with direct variable references to these items?
Let's figure this out and find something that helps you and makes your life easier. It's great to have such a project that highlights issues in the current design, and I'm happy to reconsider choices and fix such issues. It's also a good time now since we'll hopefully soon stabilize the API and go v1.0
Best,
Jürg