-
-
Notifications
You must be signed in to change notification settings - Fork 273
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 navigation through DOM views with pages #1565
Conversation
@@ -362,25 +364,49 @@ const UNDOABLE_ACTIONS = new Set<DomActionType>([ | |||
'DESELECT_NODE', | |||
]); | |||
|
|||
export const getCurrentPageDomView = (location: Location): DomView => { |
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.
Should I move this function to some other file? Not sure where to move it to.
Do we know what created this regression? |
I'm not sure about this, it feels a bit brittle. How should the undoable views interact with the browser back button? I don't believe it would currently be possible to keep the browser history in sync with the undo stack. I feel like as soon as we're in the editor flow, the undoable view changes shouldn't create new browser history entries. And if that is the case then:
@oliviertassinari This was introduced when taking the view switching and selection into account for undo/redo. I think the initial implementation gets into a chicken/egg situation between application state (undo stack) and the browser history. I believe it would be best to make one the single source of truth (application state) and have the other simply follow suit. For that we'll have to break out of |
I tried to make the source of truth be the DOM state, but yeah I did not anticipate this issue coming from the URLs and I tried out this solution but it's not the simplest... |
582a5d4
to
2c2c322
Compare
2c2c322
to
bc5dee2
Compare
@Janpot how about this? I followed the approach you suggested, seems like a good solution! |
const navigate = useNavigate(); | ||
|
||
const firstPage = pages.length > 0 ? pages[0] : null; | ||
|
||
React.useEffect(() => { |
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 believe this could be a bit DRYer if written as:
function getPathnameForView (appId, view) {
switch (view.kind) {
case 'page': return `/app/${appId}/pages/${view.nodeId}`;
// ...
}
}
React.useEffect(() => {
const desiredPath = getPathnameForView(currentView)
if (desiredPath !== location.pathname) {
navigate({ pathname: desiredPath }, { replace: true });
}
}, [appId, currentView, location.pathname]
</Route> | ||
</Routes> | ||
<AppEditorShell appId={appId}> | ||
{currentView.kind === 'page' ? ( |
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.
The intent of this isn't very clear from structuring the code this way. (The intent being "only one of these needs to be rendered"). Moreover, the last element needs to always be kept in sync with the lines above. Ideally this would be written as a switch
. Infortunately there's still no way to write switch expressions or do pattern matching in javascript, but perhaps this could be written as:
{(() => {
switch (currentView.kind) {
case 'page': return <PageEditor appId={appId} nodeId={currentView.nodeId} />;
// ...
}
})()}
It's a bit ugly with all those brackets. Alternatively this could be written in a useMemo
, or extracted as a function.
@@ -362,25 +366,54 @@ const UNDOABLE_ACTIONS = new Set<DomActionType>([ | |||
'DESELECT_NODE', | |||
]); | |||
|
|||
export const getInitialPageDomView = (location: Location, defaultPageNodeId?: NodeId): DomView => { |
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.
This looks like the inverse of the getPathnameFromView
. Perhaps we could put the two functions physically together and name them similarly?
getPathnameFromView
and getViewFromPathname
?
I like this implementation a lot, hopefully it works well in practice. Just a few stylistic comments |
Great suggestions again, there was definitely some unneeded repetition. |
case 'codeComponent': | ||
return `/app/${appId}/codeComponents/${view.nodeId}`; | ||
default: | ||
return null; |
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.
Should there be any view for which no pathame exists? Perhaps this should throw?
return null; | |
throw new Error(`Unknown view "${(view as DomView).kind}".`) |
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.
Sounds good! I've added it in a new commit to include small type adjustments since the result can't be null
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.
👌 Very nice!
Great, the DX is much better with this fix. Also the e2e test was much needed, great to see it 👌. |
Closes #1551
Preview: https://toolpad-pr-1565.onrender.com/