deploy: current vibn theia state
Made-with: Cursor
This commit is contained in:
42
packages/timeline/src/browser/style/index.css
Normal file
42
packages/timeline/src/browser/style/index.css
Normal file
@@ -0,0 +1,42 @@
|
||||
/********************************************************************************
|
||||
* Copyright (C) 2020 RedHat and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the Eclipse
|
||||
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
* with the GNU Classpath Exception which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
********************************************************************************/
|
||||
|
||||
.theia-timeline {
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.theia-timeline .timeline-outer-container {
|
||||
overflow-y: auto;
|
||||
width: 100%;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.theia-timeline .timeline-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.theia-timeline .timeline-item-icon,
|
||||
.theia-timeline .timeline-item-label {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.theia-timeline .timeline-item-description {
|
||||
font-size: var(--theia-ui-font-size0);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2020 RedHat and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable, inject, postConstruct } from '@theia/core/shared/inversify';
|
||||
import { ContextKeyService, ContextKey } from '@theia/core/lib/browser/context-key-service';
|
||||
|
||||
@injectable()
|
||||
export class TimelineContextKeyService {
|
||||
|
||||
@inject(ContextKeyService)
|
||||
protected readonly contextKeyService: ContextKeyService;
|
||||
|
||||
protected _timelineItem: ContextKey<string | undefined>;
|
||||
get timelineItem(): ContextKey<string | undefined> {
|
||||
return this._timelineItem;
|
||||
}
|
||||
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
this._timelineItem = this.contextKeyService.createKey<string | undefined>('timelineItem', undefined);
|
||||
}
|
||||
|
||||
}
|
||||
112
packages/timeline/src/browser/timeline-contribution.ts
Normal file
112
packages/timeline/src/browser/timeline-contribution.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2020 RedHat and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable, inject } from '@theia/core/shared/inversify';
|
||||
import {
|
||||
ViewContainer,
|
||||
WidgetManager,
|
||||
Widget,
|
||||
ApplicationShell,
|
||||
Navigatable,
|
||||
codicon
|
||||
} from '@theia/core/lib/browser';
|
||||
import { EXPLORER_VIEW_CONTAINER_ID } from '@theia/navigator/lib/browser';
|
||||
import { TimelineWidget } from './timeline-widget';
|
||||
import { TimelineService } from './timeline-service';
|
||||
import { CommandContribution, CommandRegistry, nls } from '@theia/core/lib/common';
|
||||
import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
||||
import { toArray } from '@theia/core/shared/@lumino/algorithm';
|
||||
import { LOAD_MORE_COMMAND } from './timeline-tree-model';
|
||||
|
||||
@injectable()
|
||||
export class TimelineContribution implements CommandContribution, TabBarToolbarContribution {
|
||||
|
||||
@inject(WidgetManager)
|
||||
protected readonly widgetManager: WidgetManager;
|
||||
@inject(TimelineService)
|
||||
protected readonly timelineService: TimelineService;
|
||||
@inject(CommandRegistry)
|
||||
protected readonly commandRegistry: CommandRegistry;
|
||||
@inject(TabBarToolbarRegistry)
|
||||
protected readonly tabBarToolbar: TabBarToolbarRegistry;
|
||||
@inject(ApplicationShell)
|
||||
protected readonly shell: ApplicationShell;
|
||||
|
||||
/** @deprecated @since 1.28.0. Import from timeline-tree-model instead */
|
||||
public static readonly LOAD_MORE_COMMAND = LOAD_MORE_COMMAND;
|
||||
private readonly toolbarItem = {
|
||||
id: 'timeline-refresh-toolbar-item',
|
||||
command: 'timeline-refresh',
|
||||
tooltip: nls.localizeByDefault('Refresh'),
|
||||
icon: codicon('refresh')
|
||||
};
|
||||
registerToolbarItems(registry: TabBarToolbarRegistry): void {
|
||||
registry.registerItem(this.toolbarItem);
|
||||
}
|
||||
registerCommands(commands: CommandRegistry): void {
|
||||
const attachTimeline = async (explorer: Widget) => {
|
||||
const timeline = await this.widgetManager.getOrCreateWidget(TimelineWidget.ID);
|
||||
if (explorer instanceof ViewContainer && explorer.getTrackableWidgets().indexOf(timeline) === -1) {
|
||||
explorer.addWidget(timeline, { initiallyCollapsed: true });
|
||||
}
|
||||
};
|
||||
this.widgetManager.onWillCreateWidget(async event => {
|
||||
if (event.widget.id === EXPLORER_VIEW_CONTAINER_ID && this.timelineService.getSources().length > 0) {
|
||||
event.waitUntil(attachTimeline(event.widget));
|
||||
}
|
||||
});
|
||||
this.timelineService.onDidChangeProviders(async event => {
|
||||
const explorer = await this.widgetManager.getWidget(EXPLORER_VIEW_CONTAINER_ID);
|
||||
if (explorer && event.added && event.added.length > 0) {
|
||||
attachTimeline(explorer);
|
||||
} else if (event.removed && this.timelineService.getSources().length === 0) {
|
||||
const timeline = await this.widgetManager.getWidget(TimelineWidget.ID);
|
||||
if (timeline) {
|
||||
timeline.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
commands.registerCommand(LOAD_MORE_COMMAND, {
|
||||
execute: async () => {
|
||||
const widget = toArray(this.shell.mainPanel.widgets()).find(w => Navigatable.is(w) && w.isVisible && !w.isHidden);
|
||||
if (Navigatable.is(widget)) {
|
||||
const uri = widget.getResourceUri();
|
||||
const timeline = await this.widgetManager.getWidget<TimelineWidget>(TimelineWidget.ID);
|
||||
if (uri && timeline) {
|
||||
timeline.loadTimeline(uri, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
commands.registerCommand({ id: this.toolbarItem.command }, {
|
||||
execute: widget => this.checkWidget(widget, async () => {
|
||||
const timeline = await this.widgetManager.getWidget(TimelineWidget.ID);
|
||||
if (timeline) {
|
||||
timeline.update();
|
||||
}
|
||||
}),
|
||||
isEnabled: widget => this.checkWidget(widget, () => true),
|
||||
isVisible: widget => this.checkWidget(widget, () => true)
|
||||
});
|
||||
}
|
||||
|
||||
private checkWidget<T>(widget: Widget, cb: () => T): T | false {
|
||||
if (widget instanceof TimelineWidget && widget.id === TimelineWidget.ID) {
|
||||
return cb();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
41
packages/timeline/src/browser/timeline-empty-widget.tsx
Normal file
41
packages/timeline/src/browser/timeline-empty-widget.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2020 RedHat and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
import { ReactWidget } from '@theia/core/lib/browser';
|
||||
import { AlertMessage } from '@theia/core/lib/browser/widgets/alert-message';
|
||||
import * as React from '@theia/core/shared/react';
|
||||
import { nls } from '@theia/core';
|
||||
|
||||
@injectable()
|
||||
export class TimelineEmptyWidget extends ReactWidget {
|
||||
|
||||
static ID = 'timeline-empty-widget';
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.addClass('theia-timeline-empty');
|
||||
this.id = TimelineEmptyWidget.ID;
|
||||
}
|
||||
|
||||
protected render(): React.ReactNode {
|
||||
return <AlertMessage
|
||||
type='WARNING'
|
||||
header={nls.localizeByDefault('The active editor cannot provide timeline information.')}
|
||||
/>;
|
||||
}
|
||||
|
||||
}
|
||||
71
packages/timeline/src/browser/timeline-frontend-module.ts
Normal file
71
packages/timeline/src/browser/timeline-frontend-module.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2020 RedHat and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { Container, ContainerModule, interfaces } from '@theia/core/shared/inversify';
|
||||
import { WidgetFactory } from '@theia/core/lib/browser/widget-manager';
|
||||
import { TimelineService } from './timeline-service';
|
||||
import { TimelineWidget } from './timeline-widget';
|
||||
import { TimelineTreeWidget } from './timeline-tree-widget';
|
||||
import { createTreeContainer, } from '@theia/core/lib/browser';
|
||||
import { TimelineTreeModel } from './timeline-tree-model';
|
||||
import { TimelineEmptyWidget } from './timeline-empty-widget';
|
||||
import { TimelineContextKeyService } from './timeline-context-key-service';
|
||||
import { TimelineContribution } from './timeline-contribution';
|
||||
|
||||
import '../../src/browser/style/index.css';
|
||||
import { CommandContribution } from '@theia/core/lib/common';
|
||||
import { TabBarToolbarContribution } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
|
||||
|
||||
export default new ContainerModule(bind => {
|
||||
bind(TimelineContribution).toSelf().inSingletonScope();
|
||||
bind(CommandContribution).toService(TimelineContribution);
|
||||
bind(TabBarToolbarContribution).toService(TimelineContribution);
|
||||
|
||||
bind(TimelineContextKeyService).toSelf().inSingletonScope();
|
||||
bind(TimelineService).toSelf().inSingletonScope();
|
||||
|
||||
bind(TimelineWidget).toSelf();
|
||||
bind(WidgetFactory).toDynamicValue(({ container }) => ({
|
||||
id: TimelineWidget.ID,
|
||||
createWidget: () => container.get(TimelineWidget)
|
||||
})).inSingletonScope();
|
||||
bind(TimelineTreeWidget).toDynamicValue(ctx => {
|
||||
const child = createTimelineTreeContainer(ctx.container);
|
||||
return child.get(TimelineTreeWidget);
|
||||
});
|
||||
bind(WidgetFactory).toDynamicValue(({ container }) => ({
|
||||
id: TimelineTreeWidget.ID,
|
||||
createWidget: () => container.get(TimelineTreeWidget)
|
||||
})).inSingletonScope();
|
||||
bind(TimelineEmptyWidget).toSelf();
|
||||
bind(WidgetFactory).toDynamicValue(({ container }) => ({
|
||||
id: TimelineEmptyWidget.ID,
|
||||
createWidget: () => container.get(TimelineEmptyWidget)
|
||||
})).inSingletonScope();
|
||||
});
|
||||
|
||||
export function createTimelineTreeContainer(parent: interfaces.Container): Container {
|
||||
const child = createTreeContainer(parent, {
|
||||
props: {
|
||||
virtualized: true,
|
||||
search: true
|
||||
},
|
||||
widget: TimelineTreeWidget,
|
||||
model: TimelineTreeModel
|
||||
});
|
||||
|
||||
return child;
|
||||
}
|
||||
124
packages/timeline/src/browser/timeline-service.ts
Normal file
124
packages/timeline/src/browser/timeline-service.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2020 RedHat and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
import { Disposable, Emitter, Event } from '@theia/core/lib/common';
|
||||
import { URI } from '@theia/core/shared/vscode-uri';
|
||||
import {
|
||||
InternalTimelineOptions,
|
||||
Timeline,
|
||||
TimelineChangeEvent, TimelineItem, TimelineOptions,
|
||||
TimelineProvider,
|
||||
TimelineProvidersChangeEvent,
|
||||
TimelineSource
|
||||
} from '../common/timeline-model';
|
||||
|
||||
@injectable()
|
||||
export class TimelineService {
|
||||
private readonly providers = new Map<string, TimelineProvider>();
|
||||
|
||||
private readonly onDidChangeProvidersEmitter = new Emitter<TimelineProvidersChangeEvent>();
|
||||
readonly onDidChangeProviders: Event<TimelineProvidersChangeEvent> = this.onDidChangeProvidersEmitter.event;
|
||||
|
||||
private readonly onDidChangeTimelineEmitter = new Emitter<TimelineChangeEvent>();
|
||||
readonly onDidChangeTimeline: Event<TimelineChangeEvent> = this.onDidChangeTimelineEmitter.event;
|
||||
|
||||
registerTimelineProvider(provider: TimelineProvider): Disposable {
|
||||
const id = provider.id;
|
||||
|
||||
this.providers.set(id, provider);
|
||||
if (provider.onDidChange) {
|
||||
provider.onDidChange(e => this.onDidChangeTimelineEmitter.fire(e));
|
||||
}
|
||||
this.onDidChangeProvidersEmitter.fire({ added: [id] });
|
||||
|
||||
return Disposable.create(() => this.unregisterTimelineProvider(id));
|
||||
}
|
||||
|
||||
unregisterTimelineProvider(id: string): void {
|
||||
const provider = this.providers.get(id);
|
||||
if (provider) {
|
||||
provider.dispose();
|
||||
this.providers.delete(id);
|
||||
this.onDidChangeProvidersEmitter.fire({ removed: [id] });
|
||||
}
|
||||
}
|
||||
|
||||
getSources(): TimelineSource[] {
|
||||
return [...this.providers.values()].map(p => ({ id: p.id, label: p.label }));
|
||||
}
|
||||
|
||||
getSchemas(): string[] {
|
||||
const result: string[] = [];
|
||||
Array.from(this.providers.values()).forEach(provider => {
|
||||
const scheme = provider.scheme;
|
||||
if (typeof scheme === 'string') {
|
||||
result.push(scheme);
|
||||
} else {
|
||||
scheme.forEach(s => result.push(s));
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
getTimeline(id: string, uri: URI, options: TimelineOptions, internalOptions?: InternalTimelineOptions): Promise<Timeline | undefined> {
|
||||
const provider = this.providers.get(id);
|
||||
if (!provider) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
if (typeof provider.scheme === 'string') {
|
||||
if (provider.scheme !== '*' && provider.scheme !== uri.scheme) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
return provider.provideTimeline(uri, options, internalOptions)
|
||||
.then(result => {
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
result.items = result.items.map(item => ({ ...item, source: provider.id }));
|
||||
return result;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class TimelineAggregate {
|
||||
readonly items: TimelineItem[];
|
||||
readonly source: string;
|
||||
readonly uri: string;
|
||||
|
||||
private _cursor?: string;
|
||||
get cursor(): string | undefined {
|
||||
return this._cursor;
|
||||
}
|
||||
|
||||
set cursor(cursor: string | undefined) {
|
||||
this._cursor = cursor;
|
||||
}
|
||||
|
||||
constructor(timeline: Timeline) {
|
||||
this.source = timeline.source;
|
||||
this.items = timeline.items;
|
||||
this._cursor = timeline.paging?.cursor;
|
||||
}
|
||||
|
||||
add(items: TimelineItem[]): void {
|
||||
this.items.push(...items);
|
||||
this.items.sort((a, b) => b.timestamp - a.timestamp);
|
||||
}
|
||||
}
|
||||
79
packages/timeline/src/browser/timeline-tree-model.ts
Normal file
79
packages/timeline/src/browser/timeline-tree-model.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2020 RedHat and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable } from '@theia/core/shared/inversify';
|
||||
import {
|
||||
CompositeTreeNode,
|
||||
SelectableTreeNode,
|
||||
TreeModelImpl,
|
||||
} from '@theia/core/lib/browser/tree';
|
||||
import { TimelineItem } from '../common/timeline-model';
|
||||
import { Command, nls } from '@theia/core';
|
||||
|
||||
export const LOAD_MORE_COMMAND: Command = {
|
||||
id: 'timeline-load-more'
|
||||
};
|
||||
|
||||
export interface TimelineNode extends SelectableTreeNode {
|
||||
timelineItem: TimelineItem;
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class TimelineTreeModel extends TreeModelImpl {
|
||||
|
||||
updateTree(items: TimelineItem[], hasMoreItems: boolean): void {
|
||||
const root = {
|
||||
id: 'timeline-tree-root',
|
||||
parent: undefined,
|
||||
visible: false,
|
||||
children: []
|
||||
} as CompositeTreeNode;
|
||||
const children = items.map(item =>
|
||||
({
|
||||
timelineItem: item,
|
||||
id: item.id ? item.id : item.timestamp.toString(),
|
||||
parent: root,
|
||||
detail: item.tooltip,
|
||||
selected: false,
|
||||
visible: true
|
||||
} as TimelineNode)
|
||||
);
|
||||
let loadMore;
|
||||
if (hasMoreItems) {
|
||||
const loadMoreNode: TimelineItem = {
|
||||
label: nls.localizeByDefault('Load more'),
|
||||
timestamp: 0,
|
||||
handle: '',
|
||||
uri: '',
|
||||
source: '',
|
||||
icon: 'blank'
|
||||
};
|
||||
loadMoreNode.command = LOAD_MORE_COMMAND;
|
||||
loadMore = {
|
||||
timelineItem: loadMoreNode,
|
||||
id: 'load-more',
|
||||
parent: root,
|
||||
selected: true
|
||||
} as TimelineNode;
|
||||
children.push(loadMore);
|
||||
}
|
||||
root.children = children;
|
||||
this.root = root;
|
||||
if (loadMore) {
|
||||
this.selectionService.addSelection(loadMore);
|
||||
}
|
||||
}
|
||||
}
|
||||
157
packages/timeline/src/browser/timeline-tree-widget.tsx
Normal file
157
packages/timeline/src/browser/timeline-tree-widget.tsx
Normal file
@@ -0,0 +1,157 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2020 RedHat and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { injectable, inject } from '@theia/core/shared/inversify';
|
||||
import { CommandRegistry, MenuModelRegistry, MenuPath } from '@theia/core/lib/common';
|
||||
import { TreeWidget, TreeProps, NodeProps, TREE_NODE_SEGMENT_GROW_CLASS, TREE_NODE_INFO_CLASS } from '@theia/core/lib/browser/tree';
|
||||
import { codicon, ContextMenuRenderer, HoverService } from '@theia/core/lib/browser';
|
||||
import { TimelineNode, TimelineTreeModel } from './timeline-tree-model';
|
||||
import { TimelineService } from './timeline-service';
|
||||
import { TimelineContextKeyService } from './timeline-context-key-service';
|
||||
import * as React from '@theia/core/shared/react';
|
||||
import { TimelineItem } from '../common/timeline-model';
|
||||
import { MarkdownString } from '@theia/core/lib/common/markdown-rendering';
|
||||
import { isThemeIcon } from '@theia/core/lib/common/theme';
|
||||
|
||||
export const TIMELINE_ITEM_CONTEXT_MENU: MenuPath = ['timeline-item-context-menu'];
|
||||
|
||||
@injectable()
|
||||
export class TimelineTreeWidget extends TreeWidget {
|
||||
|
||||
static ID = 'timeline-tree-widget';
|
||||
static PAGE_SIZE = 20;
|
||||
|
||||
@inject(MenuModelRegistry) protected readonly menus: MenuModelRegistry;
|
||||
@inject(TimelineContextKeyService) protected readonly contextKeys: TimelineContextKeyService;
|
||||
@inject(TimelineService) protected readonly timelineService: TimelineService;
|
||||
@inject(CommandRegistry) protected readonly commandRegistry: CommandRegistry;
|
||||
@inject(HoverService) protected readonly hoverService: HoverService;
|
||||
|
||||
constructor(
|
||||
@inject(TreeProps) props: TreeProps,
|
||||
@inject(TimelineTreeModel) override readonly model: TimelineTreeModel,
|
||||
@inject(ContextMenuRenderer) contextMenuRenderer: ContextMenuRenderer,
|
||||
) {
|
||||
super(props, model, contextMenuRenderer);
|
||||
this.id = TimelineTreeWidget.ID;
|
||||
this.addClass('timeline-outer-container');
|
||||
}
|
||||
|
||||
protected override renderNode(node: TimelineNode, props: NodeProps): React.ReactNode {
|
||||
const attributes = this.createNodeAttributes(node, props);
|
||||
const content = <TimelineItemNode
|
||||
timelineItem={node.timelineItem}
|
||||
commandRegistry={this.commandRegistry}
|
||||
contextKeys={this.contextKeys}
|
||||
contextMenuRenderer={this.contextMenuRenderer}
|
||||
hoverService={this.hoverService} />;
|
||||
return React.createElement('div', attributes, content);
|
||||
}
|
||||
|
||||
protected override handleEnter(event: KeyboardEvent): void {
|
||||
const node = this.model.getFocusedNode() as TimelineNode;
|
||||
const command = node?.timelineItem?.command;
|
||||
if (command) {
|
||||
this.commandRegistry.executeCommand(command.id, ...(command.arguments ? command.arguments : []));
|
||||
}
|
||||
}
|
||||
|
||||
protected override async handleLeft(event: KeyboardEvent): Promise<void> {
|
||||
this.model.selectPrevNode();
|
||||
}
|
||||
}
|
||||
|
||||
export namespace TimelineItemNode {
|
||||
export interface Props {
|
||||
timelineItem: TimelineItem;
|
||||
commandRegistry: CommandRegistry;
|
||||
contextKeys: TimelineContextKeyService;
|
||||
contextMenuRenderer: ContextMenuRenderer;
|
||||
hoverService: HoverService;
|
||||
}
|
||||
}
|
||||
|
||||
export class TimelineItemNode extends React.Component<TimelineItemNode.Props> {
|
||||
override render(): JSX.Element | undefined {
|
||||
const { label, description, tooltip, accessibilityInformation, icon } = this.props.timelineItem;
|
||||
|
||||
let iconString: string = '';
|
||||
if (icon) {
|
||||
if (typeof icon === 'string') {
|
||||
iconString = codicon(icon);
|
||||
} else if (isThemeIcon(icon)) {
|
||||
iconString = codicon(icon.id);
|
||||
}
|
||||
}
|
||||
|
||||
return <div className='theia-TreeNodeContent'
|
||||
onContextMenu={this.renderContextMenu}
|
||||
onClick={this.open}
|
||||
onMouseEnter={e => this.requestHover(e, tooltip)}
|
||||
aria-label={accessibilityInformation?.label}
|
||||
role={accessibilityInformation?.role}
|
||||
>
|
||||
<div className={`timeline-item noWrapInfo ${TREE_NODE_SEGMENT_GROW_CLASS} no-select`}>
|
||||
<span className={`${iconString} timeline-item-icon`} />
|
||||
<div className='noWrapInfo'>
|
||||
<span className='timeline-item-label'>
|
||||
{label}
|
||||
</span>
|
||||
<span className={`timeline-item-description ${TREE_NODE_INFO_CLASS}`}>
|
||||
{description}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div >;
|
||||
}
|
||||
|
||||
protected open = () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const command: any = this.props.timelineItem.command;
|
||||
if (command) {
|
||||
this.props.commandRegistry.executeCommand(command.id, ...command.arguments ? command.arguments : []);
|
||||
}
|
||||
};
|
||||
|
||||
protected requestHover(e: React.MouseEvent<HTMLElement, MouseEvent>, content?: string | MarkdownString): void {
|
||||
if (content) {
|
||||
this.props.hoverService.requestHover({
|
||||
content,
|
||||
target: e.currentTarget,
|
||||
position: 'right',
|
||||
interactive: MarkdownString.is(content),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected renderContextMenu = (event: React.MouseEvent<HTMLElement>) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const { timelineItem, contextKeys, contextMenuRenderer } = this.props;
|
||||
const currentTimelineItem = contextKeys.timelineItem.get();
|
||||
contextKeys.timelineItem.set(timelineItem.contextValue);
|
||||
try {
|
||||
contextMenuRenderer.render({
|
||||
menuPath: TIMELINE_ITEM_CONTEXT_MENU,
|
||||
anchor: event.nativeEvent,
|
||||
args: [timelineItem],
|
||||
context: event.currentTarget
|
||||
});
|
||||
} finally {
|
||||
contextKeys.timelineItem.set(currentTimelineItem);
|
||||
}
|
||||
};
|
||||
}
|
||||
179
packages/timeline/src/browser/timeline-widget.tsx
Normal file
179
packages/timeline/src/browser/timeline-widget.tsx
Normal file
@@ -0,0 +1,179 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2020 RedHat and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
import { Message } from '@theia/core/shared/@lumino/messaging';
|
||||
import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
|
||||
import {
|
||||
ApplicationShell,
|
||||
BaseWidget,
|
||||
MessageLoop, Navigatable,
|
||||
NavigatableWidget,
|
||||
Panel,
|
||||
PanelLayout
|
||||
} from '@theia/core/lib/browser';
|
||||
import { TimelineTreeWidget } from './timeline-tree-widget';
|
||||
import { TimelineService, TimelineAggregate } from './timeline-service';
|
||||
import { CommandRegistry, SelectionService } from '@theia/core/lib/common';
|
||||
import { TimelineEmptyWidget } from './timeline-empty-widget';
|
||||
import { toArray } from '@theia/core/shared/@lumino/algorithm';
|
||||
import URI from '@theia/core/lib/common/uri';
|
||||
import { URI as CodeURI } from '@theia/core/shared/vscode-uri';
|
||||
import { nls } from '@theia/core/lib/common/nls';
|
||||
|
||||
@injectable()
|
||||
export class TimelineWidget extends BaseWidget {
|
||||
|
||||
protected panel: Panel;
|
||||
static ID = 'timeline-view';
|
||||
|
||||
private readonly timelinesBySource = new Map<string, TimelineAggregate>();
|
||||
|
||||
@inject(TimelineTreeWidget) protected readonly resourceWidget: TimelineTreeWidget;
|
||||
@inject(TimelineService) protected readonly timelineService: TimelineService;
|
||||
@inject(CommandRegistry) protected readonly commandRegistry: CommandRegistry;
|
||||
@inject(ApplicationShell) protected readonly applicationShell: ApplicationShell;
|
||||
@inject(TimelineEmptyWidget) protected readonly timelineEmptyWidget: TimelineEmptyWidget;
|
||||
@inject(SelectionService) protected readonly selectionService: SelectionService;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.id = TimelineWidget.ID;
|
||||
this.title.label = nls.localizeByDefault('Timeline');
|
||||
this.title.caption = this.title.label;
|
||||
this.addClass('theia-timeline');
|
||||
}
|
||||
|
||||
@postConstruct()
|
||||
protected init(): void {
|
||||
const layout = new PanelLayout();
|
||||
this.layout = layout;
|
||||
this.panel = new Panel({ layout: new PanelLayout({}) });
|
||||
this.panel.node.tabIndex = -1;
|
||||
layout.addWidget(this.panel);
|
||||
this.containerLayout!.addWidget(this.resourceWidget);
|
||||
this.containerLayout!.addWidget(this.timelineEmptyWidget);
|
||||
|
||||
this.refresh();
|
||||
this.toDispose.push(this.timelineService.onDidChangeTimeline(event => {
|
||||
const currentWidgetUri = this.getCurrentWidgetUri();
|
||||
if (currentWidgetUri) {
|
||||
this.loadTimeline(currentWidgetUri, event.reset);
|
||||
}
|
||||
})
|
||||
);
|
||||
this.toDispose.push(this.selectionService.onSelectionChanged(selection => {
|
||||
if (Array.isArray(selection) && !!selection[0] && 'uri' in selection[0]) {
|
||||
this.refresh(selection[0].uri);
|
||||
}
|
||||
}));
|
||||
this.toDispose.push(this.applicationShell.onDidChangeCurrentWidget(async e => {
|
||||
if ((e.newValue && Navigatable.is(e.newValue)) || !this.suitableWidgetsOpened()) {
|
||||
this.refresh();
|
||||
}
|
||||
}));
|
||||
this.toDispose.push(this.applicationShell.onDidRemoveWidget(widget => {
|
||||
if (NavigatableWidget.is(widget)) {
|
||||
this.refresh();
|
||||
}
|
||||
}));
|
||||
this.toDispose.push(this.timelineService.onDidChangeProviders(() => this.refresh()));
|
||||
}
|
||||
|
||||
protected async loadTimelineForSource(source: string, uri: CodeURI, reset: boolean): Promise<void> {
|
||||
if (reset) {
|
||||
this.timelinesBySource.delete(source);
|
||||
}
|
||||
let timeline = this.timelinesBySource.get(source);
|
||||
const cursor = timeline?.cursor;
|
||||
const options = { cursor: reset ? undefined : cursor, limit: TimelineTreeWidget.PAGE_SIZE };
|
||||
const timelineResult = await this.timelineService.getTimeline(source, uri, options, { cacheResults: true, resetCache: reset });
|
||||
if (timelineResult) {
|
||||
const items = timelineResult.items;
|
||||
if (items) {
|
||||
if (timeline) {
|
||||
timeline.add(items);
|
||||
timeline.cursor = timelineResult.paging?.cursor;
|
||||
} else {
|
||||
timeline = new TimelineAggregate(timelineResult);
|
||||
}
|
||||
this.timelinesBySource.set(source, timeline);
|
||||
this.resourceWidget.model.updateTree(timeline.items, !!timeline.cursor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async loadTimeline(uri: URI, reset: boolean): Promise<void> {
|
||||
for (const source of this.timelineService.getSources().map(s => s.id)) {
|
||||
this.loadTimelineForSource(source, CodeURI.parse(uri.toString()), reset);
|
||||
}
|
||||
}
|
||||
|
||||
refresh(uri?: URI): void {
|
||||
if (!uri) {
|
||||
uri = this.getCurrentWidgetUri();
|
||||
}
|
||||
if (uri) {
|
||||
this.timelineEmptyWidget.hide();
|
||||
this.resourceWidget.show();
|
||||
this.loadTimeline(uri, true);
|
||||
} else if (!this.suitableWidgetsOpened()) {
|
||||
this.timelineEmptyWidget.show();
|
||||
this.resourceWidget.hide();
|
||||
}
|
||||
}
|
||||
|
||||
private suitableWidgetsOpened(): boolean {
|
||||
return !!toArray(this.applicationShell.mainPanel.widgets()).find(widget => {
|
||||
if (NavigatableWidget.is(widget)) {
|
||||
const uri = widget.getResourceUri();
|
||||
if (uri?.scheme && this.timelineService.getSchemas().indexOf(uri?.scheme) > -1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private getCurrentWidgetUri(): URI | undefined {
|
||||
let current = this.applicationShell.currentWidget;
|
||||
if (!NavigatableWidget.is(current)) {
|
||||
current = toArray(this.applicationShell.mainPanel.widgets()).find(widget => {
|
||||
if (widget.isVisible && !widget.isHidden) {
|
||||
return widget;
|
||||
}
|
||||
});
|
||||
}
|
||||
return NavigatableWidget.is(current) ? current.getResourceUri() : undefined;
|
||||
}
|
||||
|
||||
protected get containerLayout(): PanelLayout | undefined {
|
||||
return this.panel.layout as PanelLayout;
|
||||
}
|
||||
|
||||
protected override onUpdateRequest(msg: Message): void {
|
||||
MessageLoop.sendMessage(this.resourceWidget, msg);
|
||||
MessageLoop.sendMessage(this.timelineEmptyWidget, msg);
|
||||
this.refresh();
|
||||
super.onUpdateRequest(msg);
|
||||
}
|
||||
|
||||
protected override onAfterAttach(msg: Message): void {
|
||||
this.node.appendChild(this.resourceWidget.node);
|
||||
this.node.appendChild(this.timelineEmptyWidget.node);
|
||||
super.onAfterAttach(msg);
|
||||
this.update();
|
||||
}
|
||||
|
||||
}
|
||||
89
packages/timeline/src/common/timeline-model.ts
Normal file
89
packages/timeline/src/common/timeline-model.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2020 RedHat and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
// some code copied and modified from https://github.com/microsoft/vscode/blob/3aab025eaebde6c9544293b6c7554f3f583e15d0/src/vs/workbench/contrib/timeline/common/timeline.ts
|
||||
|
||||
import { Command, Disposable, Event } from '@theia/core/lib/common';
|
||||
import { URI } from '@theia/core/shared/vscode-uri';
|
||||
import { ThemeIcon } from '@theia/core/lib/common/theme';
|
||||
import { MarkdownString } from '@theia/core/lib/common/markdown-rendering';
|
||||
import { AccessibilityInformation } from '@theia/core/lib/common/accessibility';
|
||||
|
||||
export interface TimelineItem {
|
||||
source: string;
|
||||
uri: string;
|
||||
handle: string;
|
||||
timestamp: number;
|
||||
label: string;
|
||||
id?: string;
|
||||
icon?: string | { light: string; dark: string } | ThemeIcon
|
||||
description?: string;
|
||||
tooltip?: string | MarkdownString | undefined;
|
||||
command?: Command & { arguments?: unknown[] };
|
||||
contextValue?: string;
|
||||
accessibilityInformation?: AccessibilityInformation;
|
||||
}
|
||||
|
||||
export interface TimelineChangeEvent {
|
||||
id: string;
|
||||
uri: URI | undefined;
|
||||
reset: boolean
|
||||
}
|
||||
|
||||
export interface TimelineProvidersChangeEvent {
|
||||
readonly added?: string[];
|
||||
readonly removed?: string[];
|
||||
}
|
||||
|
||||
export interface TimelineOptions {
|
||||
cursor?: string;
|
||||
limit?: number | { timestamp: number; id?: string };
|
||||
}
|
||||
|
||||
export interface InternalTimelineOptions {
|
||||
cacheResults: boolean;
|
||||
resetCache: boolean;
|
||||
}
|
||||
|
||||
export interface Timeline {
|
||||
source: string;
|
||||
|
||||
paging?: {
|
||||
readonly cursor: string | undefined;
|
||||
}
|
||||
|
||||
items: TimelineItem[];
|
||||
}
|
||||
|
||||
export interface TimelineProviderDescriptor {
|
||||
id: string;
|
||||
label: string;
|
||||
scheme: string | string[];
|
||||
}
|
||||
|
||||
export interface TimelineProvider extends TimelineProviderDescriptor, Disposable {
|
||||
onDidChange?: Event<TimelineChangeEvent>;
|
||||
provideTimeline(uri: URI, options: TimelineOptions, internalOptions?: InternalTimelineOptions): Promise<Timeline | undefined>;
|
||||
}
|
||||
|
||||
export interface TimelineSource {
|
||||
id: string;
|
||||
label: string;
|
||||
}
|
||||
29
packages/timeline/src/package.spec.ts
Normal file
29
packages/timeline/src/package.spec.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
// *****************************************************************************
|
||||
// Copyright (C) 2019 TypeFox and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// http://www.eclipse.org/legal/epl-2.0.
|
||||
//
|
||||
// This Source Code may also be made available under the following Secondary
|
||||
// Licenses when the conditions for such availability set forth in the Eclipse
|
||||
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
// with the GNU Classpath Exception which is available at
|
||||
// https://www.gnu.org/software/classpath/license.html.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
// *****************************************************************************
|
||||
|
||||
/* note: this bogus test file is required so that
|
||||
we are able to run mocha unit tests on this
|
||||
package, without having any actual unit tests in it.
|
||||
This way a coverage report will be generated,
|
||||
showing 0% coverage, instead of no report.
|
||||
This file can be removed once we have real unit
|
||||
tests in place. */
|
||||
|
||||
describe('timeline package', () => {
|
||||
|
||||
it('support code coverage statistics', () => true);
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user