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

Add preference for visualPreview on hover #12648

Merged
merged 6 commits into from
Jul 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,16 @@
- Show command shortcuts in toolbar item tooltips. [#12660](https://github.com/eclipse-theia/theia/pull/12660) - Contributed on behalf of STMicroelectronics
- [cli] added `check:theia-extensions` which checks the uniqueness of Theia extension versions [#12596](https://github.com/eclipse-theia/theia/pull/12596) - Contributed on behalf of STMicroelectronics
- [vscode] Add support for the TaskPresentationOptions close property [#12749](https://github.com/eclipse-theia/theia/pull/12749) - Contributed on behalf of STMicroelectronics
- [core] add support to render a visual preview of a tab while hovering [#12648](https://github.com/eclipse-theia/theia/pull/12648) - Contributed on behalf of STMicroelectronics

<a name="breaking_changes_1.40.0">[Breaking Changes:](#breaking_changes_1.40.0)</a>

- [workspace] split `CommonWorkspaceUtils` into `WorkspaceFileService` and `UntitledWorkspaceService` [#12420](https://github.com/eclipse-theia/theia/pull/12420)
- [preferences] changed the `window.tabbar.enhancedPreview` preference from boolean to enum: [#12648](https://github.com/eclipse-theia/theia/pull/12648) - Contributed on behalf of STMicroelectronics
- `classic`: Display a simple preview of the tab with basic information.
- `enhanced`: Display an enhanced preview of the tab with additional information. (The behavior introduced in [#12350](https://github.com/eclipse-theia/theia/pull/12350))
- `visual`: Display a visual preview of the tab. (The preview support was added with this PR)


## v1.39.0 - 06/29/2023

Expand Down
14 changes: 10 additions & 4 deletions packages/core/src/browser/core-preferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,15 @@ export const corePreferenceSchema: PreferenceSchema = {
markdownDescription: nls.localizeByDefault('Controls the dispatching logic for key presses to use either `code` (recommended) or `keyCode`.')
},
'window.tabbar.enhancedPreview': {
type: 'boolean',
default: false,
description: nls.localize('theia/core/enhancedPreview', 'Controls whether more information about the tab should be displayed in horizontal tab bars.')
type: 'string',
sgraband marked this conversation as resolved.
Show resolved Hide resolved
enum: ['classic', 'enhanced', 'visual'],
markdownEnumDescriptions: [
nls.localize('theia/core/enhancedPreview/classic', 'Display a simple preview of the tab with basic information.'),
nls.localize('theia/core/enhancedPreview/enhanced', 'Display an enhanced preview of the tab with additional information.'),
nls.localize('theia/core/enhancedPreview/visual', 'Display a visual preview of the tab.'),
],
default: 'classic',
description: nls.localize('theia/core/enhancedPreview', 'Controls what information about the tab should be displayed in horizontal tab bars, when hovering.')
},
'window.menuBarVisibility': {
type: 'string',
Expand Down Expand Up @@ -263,7 +269,7 @@ export interface CoreConfiguration {
'breadcrumbs.enabled': boolean;
'files.encoding': string;
'keyboard.dispatch': 'code' | 'keyCode';
'window.tabbar.enhancedPreview': boolean;
'window.tabbar.enhancedPreview': 'classic' | 'enhanced' | 'visual';
'window.menuBarVisibility': 'classic' | 'visible' | 'hidden' | 'compact';
'window.title': string;
'window.titleSeparator': string;
Expand Down
18 changes: 18 additions & 0 deletions packages/core/src/browser/hover-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ export interface HoverRequest {
* Used to style certain boxes different e.g. for the extended tab preview.
*/
cssClasses?: string[]
/**
* A function to render a visual preview on the hover.
* Function that takes the desired width and returns a HTMLElement to be rendered.
*/
visualPreview?: (width: number) => HTMLElement | undefined;
}

@injectable()
Expand Down Expand Up @@ -106,25 +111,38 @@ export class HoverService {

protected async renderHover(request: HoverRequest): Promise<void> {
const host = this.hoverHost;
let firstChild: HTMLElement | undefined;
const { target, content, position, cssClasses } = request;
if (cssClasses) {
host.classList.add(...cssClasses);
}
this.hoverTarget = target;
if (content instanceof HTMLElement) {
host.appendChild(content);
firstChild = content;
} else if (typeof content === 'string') {
host.textContent = content;
} else {
const renderedContent = this.markdownRenderer.render(content);
this.disposeOnHide.push(renderedContent);
host.appendChild(renderedContent.element);
firstChild = renderedContent.element;
}
// browsers might insert linebreaks when the hover appears at the edge of the window
// resetting the position prevents that
host.style.left = '0px';
host.style.top = '0px';
document.body.append(host);

if (request.visualPreview) {
// If just a string is being rendered use the size of the outer box
const width = firstChild ? firstChild.offsetWidth : this.hoverHost.offsetWidth;
const visualPreview = request.visualPreview(width);
if (visualPreview) {
host.appendChild(visualPreview);
}
}

await animationFrame(); // Allow the browser to size the host
const updatedPosition = this.setHostPosition(target, host, position);

Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/browser/shell/application-shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import { nls } from '../../common/nls';
import { SecondaryWindowHandler } from '../secondary-window-handler';
import URI from '../../common/uri';
import { OpenerService } from '../opener-service';
import { PreviewableWidget } from '../widgets/previewable-widget';

/** The class name added to ApplicationShell instances. */
const APPLICATION_SHELL_CLASS = 'theia-ApplicationShell';
Expand Down Expand Up @@ -1186,6 +1187,9 @@ export class ApplicationShell extends Widget {
newValue['onCloseRequest'](msg);
};
this.toDisposeOnActiveChanged.push(Disposable.create(() => newValue['onCloseRequest'] = onCloseRequest));
if (PreviewableWidget.is(newValue)) {
newValue.loaded = true;
}
}
this.onDidChangeActiveWidgetEmitter.fire(args);
}
Expand Down
60 changes: 58 additions & 2 deletions packages/core/src/browser/shell/tab-bars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { HoverService } from '../hover-service';
import { Root, createRoot } from 'react-dom/client';
import { SelectComponent } from '../widgets/select-component';
import { createElement } from 'react';
import { PreviewableWidget } from '../widgets/previewable-widget';

/** The class name added to hidden content nodes, which are required to render vertical side bars. */
const HIDDEN_CONTENT_CLASS = 'theia-TabBar-hidden-content';
Expand Down Expand Up @@ -165,7 +166,9 @@ export class TabBarRenderer extends TabBar.Renderer {
? nls.localizeByDefault('Unpin')
: nls.localizeByDefault('Close');

const hover = this.tabBar && (this.tabBar.orientation === 'horizontal' && !this.corePreferences?.['window.tabbar.enhancedPreview']) ? { title: title.caption } : {
const hover = this.tabBar && (this.tabBar.orientation === 'horizontal' && this.corePreferences?.['window.tabbar.enhancedPreview'] === 'classic')
? { title: title.caption }
: {
onmouseenter: this.handleMouseEnterEvent
};

Expand Down Expand Up @@ -510,6 +513,58 @@ export class TabBarRenderer extends TabBar.Renderer {
return hoverBox;
};

protected renderVisualPreview(desiredWidth: number, title: Title<Widget>): HTMLElement | undefined {
const widget = title.owner;
// Check that the widget is not currently shown, is a PreviewableWidget and it was already loaded before
if (this.tabBar && this.tabBar.currentTitle !== title && PreviewableWidget.isPreviewable(widget)) {
const html = document.getElementById(widget.id);
if (html) {
const previewNode: Node | undefined = widget.getPreviewNode();
if (previewNode) {
const clonedNode = previewNode.cloneNode(true);
const visualPreviewDiv = document.createElement('div');
visualPreviewDiv.classList.add('enhanced-preview-container');
// Add the clonedNode and get it from the children to have a HTMLElement instead of a Node
visualPreviewDiv.append(clonedNode);
const visualPreview = visualPreviewDiv.children.item(visualPreviewDiv.children.length - 1);
if (visualPreview instanceof HTMLElement) {
visualPreview.classList.remove('p-mod-hidden');
visualPreview.classList.add('enhanced-preview');
visualPreview.id = `preview:${widget.id}`;

// Use the current visible editor as a fallback if not available
const height: number = visualPreview.style.height === '' ? this.tabBar.currentTitle!.owner.node.offsetHeight : parseFloat(visualPreview.style.height);
const width: number = visualPreview.style.width === '' ? this.tabBar.currentTitle!.owner.node.offsetWidth : parseFloat(visualPreview.style.width);
const desiredRatio = 9 / 16;
const desiredHeight = desiredWidth * desiredRatio;
const ratio = height / width;
visualPreviewDiv.style.width = `${desiredWidth}px`;
visualPreviewDiv.style.height = `${desiredHeight}px`;

// If the view is wider than the desiredRatio scale the width and crop the height. If the view is longer its the other way around.
const scale = ratio < desiredRatio ? (desiredHeight / height) : (desiredWidth / width);
visualPreview.style.transform = `scale(${scale},${scale})`;
visualPreview.style.removeProperty('top');
visualPreview.style.removeProperty('left');

// Copy canvases (They are cloned empty)
const originalCanvases = html.getElementsByTagName('canvas');
const previewCanvases = visualPreview.getElementsByTagName('canvas');
// If this is not given, something went wrong during the cloning
if (originalCanvases.length === previewCanvases.length) {
for (let i = 0; i < originalCanvases.length; i++) {
previewCanvases[i].getContext('2d')?.drawImage(originalCanvases[i], 0, 0);
}
}

return visualPreviewDiv;
}
}
}
}
return undefined;
}

protected handleMouseEnterEvent = (event: MouseEvent) => {
if (this.tabBar && this.hoverService && event.currentTarget instanceof HTMLElement) {
const id = event.currentTarget.id;
Expand All @@ -520,7 +575,8 @@ export class TabBarRenderer extends TabBar.Renderer {
content: this.renderEnhancedPreview(title),
target: event.currentTarget,
position: 'bottom',
cssClasses: ['extended-tab-preview']
cssClasses: ['extended-tab-preview'],
visualPreview: this.corePreferences?.['window.tabbar.enhancedPreview'] === 'visual' ? width => this.renderVisualPreview(width, title) : undefined
});
} else {
this.hoverService.requestHover({
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/browser/style/hover-service.css
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

:root {
--theia-hover-max-width: 500px;
--theia-hover-preview-width: 300px;
}

.theia-hover {
Expand Down Expand Up @@ -96,4 +97,5 @@

.theia-hover.extended-tab-preview {
border-radius: 10px;
width: var(--theia-hover-preview-width);
}
Loading
Loading