Skip to content
This repository was archived by the owner on Sep 30, 2024. It is now read-only.

blob page keyboard navigation#46829

Merged
taras-yemets merged 32 commits into
mainfrom
taras-yemets/file-page-keyboard-nav
Mar 8, 2023
Merged

blob page keyboard navigation#46829
taras-yemets merged 32 commits into
mainfrom
taras-yemets/file-page-keyboard-nav

Conversation

@taras-yemets

@taras-yemets taras-yemets commented Jan 24, 2023

Copy link
Copy Markdown
Contributor

Closes https://github.com/sourcegraph/sourcegraph/issues/46375

Adds keyboard shortcuts to switch between blob view, file, and symbol trees.
Available under blob-page-switch-areas-shortcuts feature flag.

Screen.Recording.2023-03-02.at.18.47.40.mov

Adds shortcut labels to the code view and files/symbols tags. I asked for design help in this thread. I think it's not blocking as this feature is behind the feature flag and we can address the feedback as we get it.

Screenshot 2023-03-07 at 18 18 46

Test plan

Tested manually (video attached).

App preview:

Check out the client app preview documentation to learn more.

@cla-bot cla-bot Bot added the cla-signed label Jan 24, 2023
@sg-e2e-regression-test-bob

sg-e2e-regression-test-bob commented Feb 17, 2023

Copy link
Copy Markdown

Bundle size report 📦

Initial size Total size Async size Modules
0.01% (+0.24 kb) 0.01% (+2.08 kb) 0.02% (+1.83 kb) 0.00% (0)

Look at the Statoscope report for a full comparison between the commits d6810db and fa271fb or learn more.

Open explanation
  • Initial size is the size of the initial bundle (the one that is loaded when you open the page)
  • Total size is the size of the initial bundle + all the async loaded chunks
  • Async size is the size of all the async loaded chunks
  • Modules is the number of modules in the initial bundle

Comment on lines -1444 to -1461
if (event.key.length === 1) {
let currentId = getNextAccessible(data, id, expandedIds)
while (currentId !== id) {
if (currentId == null) {
currentId = data[0].children[0]
continue
}
if (data[currentId].name[0].toLowerCase() === event.key.toLowerCase()) {
dispatch({
type: treeTypes.focus,
id: currentId,
lastInteractedWith: id,
})
return
}
currentId = getNextAccessible(data, currentId, expandedIds)
}
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

a-z, A-Z keys handlers interfere with keyboard shortcuts introduced by this PR so we disable the former.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can we do this without patching the third-party library?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Seems like there's no option for that though. Let's add a comment then and explain the change.

@taras-yemets taras-yemets marked this pull request as ready for review March 3, 2023 11:49
@taras-yemets taras-yemets requested a review from a team March 3, 2023 11:49
@philipp-spiess

Copy link
Copy Markdown
Contributor

Screenshot 2023-03-03 at 13 18 49

Can we make these icons a bit (much, lol) more subtle? They're screaming for attention now 😬

Maybe ask in #ask-design for some ideas and rm for now?

@taras-yemets

Copy link
Copy Markdown
Contributor Author

@philipp-spiess philipp-spiess left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Couple of comments but it's all gated anyway so, sure. I’m sad that we have to let the a-zA-Z listeners in the treeview go for it though 😭

Adds shortcuts labels when the feature is enabled:

btw it looks like you forgot to commit the code for this? I don't see the change anywhere haha

focusCodeEditor: {
title: 'Focus editor',
keybindings: [{ ordered: ['c'] }],
hideInHelp: true,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why hide this in the help menu?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Not to show them when the feature flag is disabled.
Though we can filter them out based on the feature flag value.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not to show them when the feature flag is disabled.

Ah, of course 😅

},
focusCodeEditor: {
title: 'Focus editor',
keybindings: [{ ordered: ['c'] }],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All other file handlers in this file have a modifier to go first, are we certain we want to break this pattern here? Hm.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

These shortcuts are from RFC 345.
I have a few doubts regarding it:

  • both JetBrains and VSCode IDEs use shortcuts with modifiers (Cmd+1 and Shift+Cmd+E respectively)
  • I'll do an extra round of testing to ensure c, f, s shortcuts to not hijack commonly used browser shortcuts (copy, interacting with panels, etc.)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@taras-yemets What, for example, happens when you press c, f, or s while inside a text input?

Maybe we should discuss this with other folks as well? 🤔 summoning @sourcegraph/code-exploration-devs

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

What, for example, happens when you press c, f, or s while inside a text input?

Nothing. Input behaves as usual.
It can be another argument favoring adding modifiers to focus files/symbols/code shortcuts so that we can switch from input to any of mentioned areas.

)}

{enableBlobPageSwitchAreasShortcuts &&
[focusFileTreeShortcut, focusSymbolsShortcut].map((shortcut, tabIndex) =>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

very minor nit feel free to ignore but I think it'd be easier to read this without combining it into an array and then mapping over it and instead rendering two components. 😅

const [enableBlobPageSwitchAreasShortcuts] = useFeatureFlag('blob-page-switch-areas-shortcuts')
const focusFileTreeShortcut = useKeyboardShortcut('focusFileTree')
const focusSymbolsShortcut = useKeyboardShortcut('focusSymbols')
const [focusKey, setFocusKey] = useState('')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hmmm I wonder if there are any issues here with reusing the same key across both components in combination with keeping the components mounted (so when we focus one, both are technically getting focused). Can you try to apply this line and see if it still works?

https://github.com/sourcegraph/sourcegraph/pull/48610/files#diff-b71212ff6b850bb481f9a4742d142fcf94b40a544f771c837aa10e24163d7d5fR115

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Seems to work fine with <Tabs {...props} behavior="memoize" />

Screen.Recording.2023-03-03.at.15.21.59.mov

But you're right, focus() on the FocusableTree ref is called twice (once per tab). Looking into it 👀

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

}

/**
* Renders {@link Tree} and focuses it when {@link focusKey} changes.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Are the @link like this even working? In JS, the Tree module is not global so i figured this is not really helpful anyway and will just render incorrectly, hence I've never bothered to use it. Happy to be proven wrong though :D

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It works for me in JetBrans IDE 🤷🏻

Screen.Recording.2023-03-03.at.14.48.08.mov

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ah because it uses the Tree that is available within the current module I guess? 😱 And what about focusKey, no way it finds that?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

And what about focusKey, no way it finds that?

See sec 6 on the video. It takes it from the props interface.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Awesome, thanks for showing.

export function FocusableTree<N extends TreeNode>(props: FocusableTreeProps & TreeProps<N>): JSX.Element {
const { focusKey, ...rest } = props

const ref = useRef<HTMLUListElement>(null)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
const ref = useRef<HTMLUListElement>(null)
const ref = useRef<TreeRef>(null)

How did this not fail? 😱

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🤷🏻 maybe because HTMLUListElement matches TreeRef interface (has focus method)?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I mean it has focus but it also has some properties that only DOM nodes have as well so this should really not work. Maybe the typescript workaround for generic refs doesn't work? :(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Maybe the typescript workaround for generic refs doesn't work?

It seems to work
Screenshot 2023-03-03 at 15 09 44
Screenshot 2023-03-03 at 15 09 34

But yes, we can be more explicit and define ref type as TreeRef 👍🏻

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@philipp-spiess philipp-spiess Mar 3, 2023

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I’m still confused here. Does this mean useImperativeHandle merges the ref with the object you define? e.g. what happens if you do ref.current.innerHTML = 'foobar'? This would be valid on a UList but not on the TreeRef 😅

Also the error message is confusing because we don't want to assign HTMLUListElement ref into TreeRef but instead it should be assigned the other way around haha. Seems like this is a covariant/contravariant issue with your workaround because it looses the nuance that this is one over the other (I don't know exactly which term is the correct one here lol)

microsoft/TypeScript#30748 (comment)

Ah the sound type system strikes again 🙃

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

what happens if you do ref.current.innerHTML = 'foobar'? This would be valid on a UList but not on the TreeRef

You're right! With TreeRef as a type we get an expected error
Screenshot 2023-03-03 at 15 31 29

Seems like this is a covariant/contravariant issue with your workaround

Maybe 🤷🏻
Do you have ideas on how we can define safer types on the Tree component with generic type and forwardRef combo?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do you have ideas on how we can define safer types on the Tree component with generic type and forwardRef combo?

no, I did a quick google session but it's not as simple as in Flowtype to define a property as contravariant so I'd say we ignore it for now 😓

@taras-yemets

Copy link
Copy Markdown
Contributor Author

btw it looks like you forgot to commit the code for this? I don't see the change anywhere haha

@philipp-spiess, exactly! https://github.com/sourcegraph/sourcegraph/pull/46829/commits/6e2dae2e9720f40ceb867086958d19453892d4d9

@taras-yemets taras-yemets marked this pull request as draft March 3, 2023 12:57
@taras-yemets

Copy link
Copy Markdown
Contributor Author
Screenshot 2023-03-03 at 13 18 49

Can we make these icons a bit (much, lol) more subtle? They're screaming for attention now 😬

Maybe ask in #ask-design for some ideas and rm for now?

Agree. Removing them for now. https://github.com/sourcegraph/sourcegraph/pull/46829/commits/fa4a6c44aecac509b9fa75cd8a5c055c4fd84774

@taras-yemets taras-yemets marked this pull request as ready for review March 7, 2023 13:43
@sourcegraph-bot

sourcegraph-bot commented Mar 7, 2023

Copy link
Copy Markdown
Contributor

Codenotify: Notifying subscribers in OWNERS files for diff fa271fb...d56b5b8.

Notify File(s)
@sourcegraph/code-exploration-devs client/wildcard/src/components/Tree/Tree.tsx
client/wildcard/src/components/Tree/react-accessible-treeview/index.tsx
client/wildcard/src/components/index.ts
@vovakulikov client/wildcard/src/components/Tree/Tree.tsx
client/wildcard/src/components/Tree/react-accessible-treeview/index.tsx
client/wildcard/src/components/index.ts

@taras-yemets taras-yemets added the needs-design Design requests - add to 'design priorities' project, add a deadline, if possible.. label Mar 7, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

cla-signed needs-design Design requests - add to 'design priorities' project, add a deadline, if possible..

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Blob view shortcuts

4 participants