-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Fix "undo trap" by introducing "transient" attributes #11504
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need more context to understand the purpose its serving, but in the meantime I'll preemptively state my distaste for the exceptional treatment to "secret" attributes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just piling on here: without suggesting a better solution, I worry about these too. While quite explicitly not part of the public API, I can imagine third-party folks finding and using these… and I also don't really like us using these kind of "secret" attributes in our own API.
I haven't seen it elsewhere in the codebase; it seems a bit un-React-y to me as well, but that's just, like, my opinion. 🤷♂️
Just quickly replying from my phone: I don’t disagree with any of that, hopefully this is what the review is for. I’ve explored one or two other ways (see description of parent issue) to solve the problem, and none is entirely clean across the board. I still conceptually like the idea of “events comprised of multiple actions” better, but it required using Redux in an... original way. Anyway, I think a solution is very much needed for the issue. What alternative routes do you see? Silent attributes are a way to easily pass a piece of state to a block’s |
I'd like to explore another solution, but leaving this open for a while to discuss. |
@aduth, the parent issue should provide enough context — otherwise, ask me anything. Does your distaste stem from the implicit nature of it (hidden attributes, no declaration in the type, a sketchy "convention" around the use of underscores), or is it more fundamentally about the idea of treating a certain kind of attribute differently? More practically, if the solution would offer an interface like: attr1: {
type: 'number',
history: false, // doesn't persist
},
attr2: {
type: 'string',
}, would that change anything? Because I don't care about this particular interface either, I wanted to see what the smallest change could look like. But the problem stands that some sort of state is needed for blocks that:
Let me know, so I can either refactor this into something more palatable, or nix it in favour of something else. |
I think the distaste comes from a perspective of looking to the attributes as the minimal set of values necessary to describe a block in its saved state. From the parent issue, this reads as problematic in and of itself:
Temporary states should not be in the attributes. For what reason is the actual component state of the block's I'd be okay with considering a change of attribute value as not being considered for history, but it's still a consideration of the specific implementation of how the |
The issue lies in tracking the circumstances of a block’s creation: when it’s the result of a transformation — in the broader sense — it can be incomplete. If we’re loading an existing post, all blocks therein are complete. If an existing block is being manipulated by the user, it can temporarily hold state in Given this, the sane way to work with attribute resolution should be to just wait inside a transformation’s callback until all the data requests have been resolved and only then instantiate a finalised block. The problem there, of course, is that we lose the ability to indicate progress (typically an incomplete block appears and will pulsate as data is loaded, etc.). Thus, our options would be:
which is similar to my initial explorations (the more complex one using |
Finally, the other possibility is to extend not // createBlock( name, attributes, innerBlocks, initialEditState )
createBlock( 'core/image', {}, [], { blobUrl: createBlobURL( file ) } ) (where This would lead to an empty (per @aduth, any advice on the best direction, between those described? |
Yeah, one of my initial impressions might be that we add framework-level support for transformations to return a More thoughts to come... |
Could this be something on an attribute definition, i.e. |
I like this proposal in that it preserves the idea of a two-way mapping between attributes and the persisted save form, but I worry it would require duplicating many of the mechanisms we've created around attributes ( |
Is this the same as the example in #11504 (comment) ? If so, we can set out to build it.
Hadn't thought about that, sounds interesting. |
Effectively, yes, though less specific to the case of history and more to this idea of temporary states. |
Doesn't that then not do anything for us so far as resolving #5936, #11565 ? If we're saying transients are needed for cases in which the final state isn't yet known and we're contemplating a case where the resolution of that final state fails... what then do we save for the failed thing? |
* | ||
* @return {Object} "Transient" block attributes. | ||
*/ | ||
export function getTransientAttributes( blockType, attributes ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Your branch is probably stale. Most functions accept blockTypeOrName
as argument.
See: #11490
// Ignore "transient" attributes. | ||
// | ||
// @see getTransientAttributes | ||
if ( attributeSchema.transient === true ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would truthy if ( transient) {
suffice?
@@ -235,6 +235,60 @@ export const editor = flow( [ | |||
resetTypes: [ 'SETUP_EDITOR_STATE' ], | |||
ignoreTypes: [ 'RECEIVE_BLOCKS', 'RESET_POST', 'UPDATE_POST' ], | |||
shouldOverwriteState, | |||
serialize: ( present, past ) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's this all about then?
[emphasis mine in both quotes]
Well, from the issue in question:
As this branch stands, serialisation of transients won't happen. What can happen is that a block gets saved with partial information (e.g. missing an unresolved image), but at least it's not saved in a broken state of trying to fetch something.
This makes some sense, but I worry about blocking users out of saving due to unforeseen failures — essentially, this gives a buggy block type the power to sabotage saving of an entire post. Don't you think so? (Coming from Calypso, where in the past I experienced breakage of the saving action due to an intermittent network connection, I'd do my best not to mess with saving too much.) |
I don't like the idea of these polluting our block parse trees, as they are not regular attributes. I could even consider imposing that rule upon type registration.
I agree. I think also the alternative to save partial data is in its own way a broken experience. This seems one of those things which falls into the realm of the machine being unable to make a decision, and where we might even need to consider surfacing it to the user. For example, I could imagine a prompt about "The Image block has operations pending. Save anyways? [ Yes ] [ No ]", maybe highlighting the block in question. |
} | ||
|
||
// For each changed block in the present, check for any transient | ||
// attribute. If such an attribute is found, delete it from the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As DM'd, this is problematic due to how we now expect to instantiate blocks with createBlocks( { myNormalAttr, myTransientAttr } )
.
Summarizing direct message conversation:
|
My primetime brain energy was dedicated elsewhere today, and here I am fruitlessly trying to consider this one with what little remains 😄 My latest concern with the more recent ideas is that: We have a lot of ways that attributes get into state (any of the I started then considering again: Okay, make “transients” a thing. And if we need it from the creation of the block, should it be part of the Then I considered: What about some one-off approach of Ultimately I’d like some top-level state key where the transient data lives. This helps in a few ways: Ideally not needing strange heavy-handed forking / history behaviors for I'd worried that we'd need to recreate a lot of the machinery around attributes for this new transients objects, but upon some reflection I don't know that there's really as much overlap as I might have thought, aside from surface-level similarities in I'll plan to circle back around to this once I've had some time to rest and reflect. |
2 unit tests fail:
They probably should be updated to reflect changes introduced. |
To update, I'd iterated some on a separate branch toward the same end-goal here, exploring what it might look like to handle transient attribute updates from the block itself after the fact. The main hiccups involve issues around when and how to "flatten" history, mainly (a) the initial entry of the block having a temporary attribute value and (b) there being possible to be many states between the attribute value first being marked as transient and the transient state becoming completed (since an upload can take many actual seconds to complete). The branch can be found here: https://github.com/WordPress/gutenberg/compare/try/set-attributes-transient-option It does reasonably well to avoid saving when an image upload is in-progress, but the aforementioned issues around history flattening become quite obvious in bugginess apparent when trying to Undo after an image upload had taken place. It's also subject to the issue described at #12327, in that transient data is meant to live outside the history of a block, but is only relevant so long as a reference to that block exists. |
I just chatted with Andrew. We have no clear path forward yet, so I've opened and closed a PR from his branch, #12724, so that we can find it later, and I'll close this one as well. Meanwhile, the original issue, #8119, remains open. |
Fixes #8119
Edit: I'd like to explore another solution, but leaving this open for a while to discuss.
Gallery: conversion from shortcode
Image: upload new image
Embed: convert from classic
File: upload bad file
Description
How has this been tested?
Screenshots
Types of changes
Checklist: