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

vsx-extensions-view-styling-improvements #8086

Conversation

kenneth-marut-work
Copy link
Contributor

@kenneth-marut-work kenneth-marut-work commented Jun 24, 2020

What it does

This PR introduces some styling improvements to the vsx-extensions view to be more inline with VSCodes respective view, these improvements include:

  • Sticky "header" during scroll
  • Dividing border lines under header, as well as extension title in body
  • Max-width container for body content
  • Image resize on width resize
  • Repositioned scrollbar to be underneath header

extensionsstyling

How to test

  • Pull branch
  • Open extensions view
  • Search for extensions and double click to view content
  • Observe styling changes

Review checklist

Reminder for reviewers

@kenneth-marut-work kenneth-marut-work changed the title feature/extensions-view-styling-improvements WIP: feature/extensions-view-styling-improvements Jun 24, 2020
@kenneth-marut-work kenneth-marut-work force-pushed the feature/extensions-styling-improvements branch from d4a2b84 to 005516f Compare June 24, 2020 18:41
@kenneth-marut-work kenneth-marut-work changed the title WIP: feature/extensions-view-styling-improvements vsx-extensions-view-styling-improvements Jun 24, 2020
Copy link
Member

@vince-fugnitto vince-fugnitto left a comment

Choose a reason for hiding this comment

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

The changes look much better with a sticky header 👍

My only comment would be to preserve the same width for the custom scrollbar, with these changes (compared to master) the scrollbar is quite thin.

Pull Request:

scrollbar-2

Master:

master-scroll

@vince-fugnitto vince-fugnitto added ui/ux issues related to user interface / user experience vsx-registry Issues related to Open VSX Registry Integration labels Jun 24, 2020
@kenneth-marut-work kenneth-marut-work force-pushed the feature/extensions-styling-improvements branch from 005516f to 5f2c6d4 Compare June 24, 2020 20:19
@kenneth-marut-work
Copy link
Contributor Author

@vince-fugnitto thanks for looking this over, I've just pushed up a slight change that fixes the scrollbar issue:
scrollbarfix-vsx

Copy link
Member

@vince-fugnitto vince-fugnitto left a comment

Choose a reason for hiding this comment

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

The changes look very good! 👍
The next improvement would be to start the scrollbar right after the sticky header (similarly to vscode), but I think it can be done afterwards, the changes already are a big improvement.

@kenneth-marut-work
Copy link
Contributor Author

Thanks @vince-fugnitto, I agree with you on the scrollbar improvement😎. I also noticed the quick scroll-to-top icon on the bottom right that could be added as well.
image

@vince-fugnitto
Copy link
Member

Thanks @vince-fugnitto, I agree with you on the scrollbar improvement😎. I also noticed the quick scroll-to-top icon on the bottom right that could be added as well.
image

@kenneth-marut-work do you think the scroll issue should be included in the pull-request (since it is introduced with the sticky header)? The icon to scroll-to-top would be a subsequent improvement which is out of scope at the moment.

@kenneth-marut-work
Copy link
Contributor Author

@vince-fugnitto Sounds good to me, I can certainly make that change in this PR 👍

@kenneth-marut-work kenneth-marut-work force-pushed the feature/extensions-styling-improvements branch from 5f2c6d4 to cb28fe6 Compare June 25, 2020 16:36
@kenneth-marut-work
Copy link
Contributor Author

@vince-fugnitto I've moved the location of the scrollbar to under the header, it required a couple of modifications to the widget and PS container, but it's working well on my machine in both browser and electron. Here's a screencast:
scroll-bar-below-header

@colin-grant-work thanks for the help on this!

@vince-fugnitto vince-fugnitto requested a review from lmcbout June 25, 2020 18:46
Copy link
Member

@vince-fugnitto vince-fugnitto left a comment

Choose a reason for hiding this comment

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

Functionality the changes look very good 👍
I'll give others the chance to review as well :)

@lmcbout
Copy link
Contributor

lmcbout commented Jun 25, 2020

Sticky header whne scrolling is nice.
I have two remars:
1- When re-sizing smaller, ther is no Horizontal scroll for the sticky portion
2- The bottom part is wrapping except when there is a comment in the text.
Check the text is wrapping except the comment line with "... present Microsoft..."

PrefScrollNoComment

@kenneth-marut-work
Copy link
Contributor Author

Hi @lmcbout
Thanks for the your comments. Quick question about the following:

1- When re-sizing smaller, ther is no Horizontal scroll for the sticky portion

I do not see this behavior in VSCode, is this still something we would like to implement? See VSCode behavior below:
horizontal-scroll-sticky

@kenneth-marut-work kenneth-marut-work force-pushed the feature/extensions-styling-improvements branch from cb28fe6 to 9bc1c82 Compare June 25, 2020 20:11
@vince-fugnitto
Copy link
Member

Hi @lmcbout
Thanks for the your comments. Quick question about the following:

1- When re-sizing smaller, ther is no Horizontal scroll for the sticky portion

I do not see this behavior in VSCode, is this still something we would like to implement? See VSCode behavior below:
horizontal-scroll-sticky

@kenneth-marut-work no I wouldn’t add a scrollbar (neither horizontal or vertical) for the sticky header, it does not align with the rest of the framework nor with vscode.

@kenneth-marut-work
Copy link
Contributor Author

@vince-fugnitto thanks for the response, in that case I have pushed the adjustment regarding @lmcbout 's comment:

2- The bottom part is wrapping except when there is a comment in the text.
Check the text is wrapping except the comment line with "... present Microsoft..."

The change targets pre elements inside the HTML body and adds text wrapping:
pre-wrap

Copy link
Contributor

@lmcbout lmcbout left a comment

Choose a reason for hiding this comment

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

LGTM
Concerning item 1

no I wouldn’t add a scrollbar (neither horizontal or vertical) for the sticky header, it does not align with the rest of the framework nor with vscode.

When the window width is narrow, we don't see any content on the right side. I don't think it is user friendly. I understand it is the same behavior on VSCode, but still :(
Lets keep it as it is right now and we will see if we get other comments about it later

Good jod @kenneth-marut-work

@@ -17,6 +17,7 @@
:root {
--theia-vsx-extension-icon-size: calc(var(--theia-ui-icon-font-size)*3);
--theia-vsx-extension-editor-icon-size: calc(var(--theia-vsx-extension-icon-size)*3);
--theia-vsx-extension-header-height: 150px;
Copy link
Member

@akosyakov akosyakov Jun 27, 2020

Choose a reason for hiding this comment

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

how do you get 150? Would it still work if some font size css variable is redefined? Could it be expressed as a formula?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the comment @akosyakov
I had originally used this value since VSCode seems to have a fixed header size of 166px but also includes a tabbar (which we do not have). I have removed this hard coded value and instead calculated the heights using JS from the deferred HTMLElements inside vsx-extension-editor.tsx .

this.update();
this.toDispose.push(this.model.onDidChange(() => this.update()));
}

async getScrollContainer(): Promise<HTMLElement> {
await waitForRevealed(this);
Copy link
Member

Choose a reason for hiding this comment

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

looks fragile, one should rather pass deferred promise into the rect component and resolve in ref assignment

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@akosyakov Thanks for the suggestion, I did not know about deferred technique but went ahead and implemented it according to my understanding

@kenneth-marut-work kenneth-marut-work force-pushed the feature/extensions-styling-improvements branch 2 times, most recently from 8d17032 to 6f92fff Compare June 29, 2020 17:57
} = this.props.extension;
return <React.Fragment>
<div className='header'>
<div className='header' ref={element => this.resolveDeferredOnRender(element, deferredHeaderElementRender)}>
Copy link
Member

Choose a reason for hiding this comment

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

you should just update the property of this class without any deferred the same in other places

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've updated this in my latest changes. The only remaining deferred item is the scroll container which is used for getScrollContainer inside vsx-extension-editor

@@ -99,6 +100,9 @@ export class VSXExtension implements VSXExtensionData, TreeElement {
readonly search: VSXExtensionsSearchModel;

protected readonly data: Partial<VSXExtensionData> = {};
deferredScrollContainerRender: Deferred<HTMLDivElement> | undefined;
Copy link
Member

Choose a reason for hiding this comment

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

please don't break encapsulation, clients should be able to modify rendered DOM elements

protected _header: HTMLDivElement | undefined;
get header(): HTMLDIvElement | undefined {
    return this._header;
}

the same for other properties

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the suggestions, I've reorganized the logic so that these elements remain encapsulated. I have also added a method to reset the vsx-extension.tsx deferred scrollContainer property during tab close/reopen since the previously opened instance will persist through a close/reopen

super.onResize(msg);
const bodyElement = await this.deferredBodyElementRender.promise;
const scrollContainer = await this.deferredScrollContainerRender.promise;
if (bodyElement && scrollContainer) {
Copy link
Member

Choose a reason for hiding this comment

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

it does not look good, since we modify rendered DOM than we should touch only virtual, we should propagate onResize to VSXExtension and there update new sizes as part of the state to retrigger DOM update via React

@kenneth-marut-work kenneth-marut-work force-pushed the feature/extensions-styling-improvements branch from 6f92fff to d5c3701 Compare July 1, 2020 21:18
@kenneth-marut-work kenneth-marut-work force-pushed the feature/extensions-styling-improvements branch 3 times, most recently from 99b8480 to eb9cbef Compare July 1, 2020 21:46
@@ -100,6 +104,56 @@ export class VSXExtension implements VSXExtensionData, TreeElement {

protected readonly data: Partial<VSXExtensionData> = {};

protected _body: HTMLDivElement | undefined;
Copy link
Member

Choose a reason for hiding this comment

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

It should be replaced with:

protected _editor: VSXExtensionEditorComponent | undefined;

renderEditor(): React.ReactNode {
        return <VSXExtensionEditorComponent extension={this} ref={ref = (this._editor = ref || undefined)} />;
    }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi @akosyakov, the reason I have these additional (body, header, and scrollContainer) elements is because it seems that the absence of a readme on line 475 could potentially cause the body and scroll-container to not be rendered. Additionally because there is manipulation of the virtual DOM and I need a ref to all 3 elements in setContainerHeights and updateOnResize to keep track of each individually.

Copy link
Member

Choose a reason for hiding this comment

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

My problem is not that you add them, but how, i.e. there are public APIs to allow setting them by child components. Instead proper encapsulation should be applied, i.e. they should be exposed on child components instead and here we should only capture a child component ref::

const body = this._editor?.body;

protected header?: HTMLDivElement;
protected body?: HTMLDivElement;

componentDidMount(): void {
Copy link
Member

Choose a reason for hiding this comment

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

you don't need all of 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.

Since I am keeping track of these in VSXExtension, I went ahead and removed these redundancies as well as some of the checks inside componentDidMount. The refs are now set using the setter methods passed in as props and to VSXExtensionEditorComponent, and componentDidMount will trigger the initial header/body sizing calculations if the refs exist

@kenneth-marut-work kenneth-marut-work force-pushed the feature/extensions-styling-improvements branch from eb9cbef to e5c6296 Compare July 2, 2020 14:57
// TODO replace with webview
readonly openLink = (event: React.MouseEvent) => {
if (!this.body) {
if (!this.props.extension.body) {
Copy link
Member

Choose a reason for hiding this comment

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

body ref should belong to this component, not to its parent, please don't break encapsulation

@kenneth-marut-work kenneth-marut-work force-pushed the feature/extensions-styling-improvements branch 2 times, most recently from 680deac to 0dd4d84 Compare July 7, 2020 17:01
@kenneth-marut-work
Copy link
Contributor Author

@akosyakov Thanks for the comments, I've moved all refs used for sizing calculations to VSXExtensionEditorComponent for proper encapsulation. The VSXExtensionEditor widget's onResize width is propagated to VSXExtension and passed into VSXExtensionEditorComponent as a prop, which triggers a re-render and size calculations which are methods and properties confined to VSXExtensionEditorComponent.

The VSXExtensionEditorComponent's react life-cycle methods are now responsible for resolving and resetting the deferred scrollContainer which are needed by the VSXExtensionEditor widget:

this.props.extension.resolveDeferredScrollContainer(this.scrollContainer);

Please note I am aware that this breaks encapsulation of the VSXExtensionEditorComponent's scrollContainer member, but seems to be the only way to pass the value upward to the getScrollContainer method since there is currently no way to reference an exposed getter on the instance of VSXExtensionEditorComponent as it is hardcoded inside VSXExtension:

return <VSXExtensionEditorComponent extension={this} />;

Also note build seems to be failing due to:
error /Users/travis/build/eclipse-theia/theia/node_modules/vscode-ripgrep: Command failed.

} = this.props.extension;

if (width !== undefined) {
Copy link
Member

Choose a reason for hiding this comment

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

Why is it happening on render but no on proper events like resize?

return this._width;
}
set width(width: number) {
this._width = width;
Copy link
Member

Choose a reason for hiding this comment

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

On resitze event, you should call VSXExtensionEditorComponent.setState and keep in the state everything what is required for rendering. render function should not compute any new properties but simply use state and props to render. That's how react works.

@colin-grant-work
Copy link
Contributor

colin-grant-work commented Jul 8, 2020

@akosyakov, @kenneth-marut-work, reading over a number of the comments on this PR, my impression is that a lot of the discussion has revolved around questions of encapsulation and the proper location for various member variables. It seems to me that origin of a lot of this difficulty is that the existing implementation has made the VSXExtension class serve two purposes: rendering a TreeNode in the extensions list widget and rendering the VSXExtensionEditorComponent. It isn't really fit for both purposes, and it shouldn't be asked to do both jobs.

When the VSXExtension is acting as a TreeElement, resizing etc. is handled by the TreeWidget, and because that logic is handled by the TreeWidget, the VSXExtension doesn't need to handle it. When the VSXEditor is rendering the VSXExtensionEditorComponent, analogy would dictate that the resizing and other state logic should be handled by the widget that is ultimately rendering the editor, VSXExtensionEditor, not by VSXExtension. Trying to handle events in the VSXExtension turns it into a props object that renders itself - which is definitely not how React is supposed to work. As a props object, the VSXExtension can't inform its renderer of changes to itself: it isn't set up to be a widget, able to call update on a state change. It's set up to house static data and render that data in the limited context of a TreeWidget.

Instead, I think Ken's instinct was correct to track the scroll container and other state relevant to the VSXExtensionEditorComponent in the VSXExtensionEditor widget. That isn't breaking encapsulation; it's keeping state data about a widget on that widget. To make that division of labor clearer, I'd propose removing the .renderEditor() method from VSXExtension and replacing the .render() method of VSXExtensionEditor with one that returns <VSXExtensionEditorComponent extension={this.extension} {{...andMaybeOtherProps}} />. That way the VSXExtension class retains its role as a repository of data about a particular extension, and the VSXExtensionEditor widget can handle widget-related state and events.

Does that sound like a reasonable adjustment to the current architecture?

@akosyakov
Copy link
Member

Moving VSXExtensionEditorComponent sounds fine with me. But my issues were not understanding the flow: like state is kept not in the component but somewhere else, child components methods of parent components and so on. Moving component does not fix it.

@colin-grant-work colin-grant-work force-pushed the feature/extensions-styling-improvements branch 2 times, most recently from 22f2d78 to 37d7602 Compare July 9, 2020 15:29
@colin-grant-work
Copy link
Contributor

colin-grant-work commented Jul 9, 2020

@akosyakov, I've pushed a new version that moves the call to VSXExtensionEditorComponent into the VSXExtensionEditor, and I believed it has obviated many of the problems you described. The reason that the VSXExtensionEditorComponent was (undesirably) given access to methods on the VSXExtension was because of a mismatch between the lifecycle of the VSXExtension - persistent - and the VSXExtensionEditor widget - able to be opened and closed at will. As long as the VSXExtensionEditorComponent was housed in the VSXExtension, it had to enforce the lifecycle of a widget (the VSXExtensionEditor) on something that was not a widget (the VSXExtension) by registering its mounting and unmounting events.

Moving the VSXExtensionEditorComponent into the VSXExtensionEditor widget class removes that mismatch in lifecycles and means that the VSXExtensionEditor widget can simply manage its own lifecycle and use the VSXExtensionEditorComponent as a renderer, and the VSXExtension doesn't have to worry about it at all.

I've also moved some of the styling logic - the max-width and margin of the body subcomponent - into CSS, but if you prefer that those calculations be made on the JS side, I'm happy to revert that part.

Copy link
Member

@akosyakov akosyakov left a comment

Choose a reason for hiding this comment

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

thanks, code wise it looks very clean now. Someone has to test it.

Signed-off-by: Kenneth Marut <kenneth.marut@ericsson.com>
Signed-off-by: Colin Grant <colin.grant@ericsson.com>
@colin-grant-work colin-grant-work force-pushed the feature/extensions-styling-improvements branch from 37d7602 to 1a63d2d Compare July 12, 2020 20:05
@colin-grant-work
Copy link
Contributor

@vince-fugnitto, would you be willing to give this a quick once over to make sure everything is still working after these latest changes? the CI checks are failing at the moment, but it looks like an error downloading rip-grep in one build, rather than a problem with the code.

@vince-fugnitto
Copy link
Member

@vince-fugnitto, would you be willing to give this a quick once over to make sure everything is still working after these latest changes? the CI checks are failing at the moment, but it looks like an error downloading rip-grep in one build, rather than a problem with the code.

Of course :) I'll also restart the build (ripgrep issues occur for forked repositories since they do not have the necessary tokens).

Copy link
Member

@vince-fugnitto vince-fugnitto left a comment

Choose a reason for hiding this comment

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

The changes look very good 👍
I've verified that:

  • the header is sticky
  • the scrollbar works correctly (and starts after the header)
  • the view is responsive (content wraps correctly, images are resized properly)

@vince-fugnitto
Copy link
Member

I'll merge tomorrow if there are no objections :)

@vince-fugnitto vince-fugnitto merged commit 349fd06 into eclipse-theia:master Jul 16, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ui/ux issues related to user interface / user experience vsx-registry Issues related to Open VSX Registry Integration
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants