Skip to content
This repository has been archived by the owner on Apr 4, 2023. It is now read-only.

Commit

Permalink
Show recent Che workspaces in Theia editor
Browse files Browse the repository at this point in the history
Signed-off-by: Vladyslav Zhukovskyi <[email protected]>
  • Loading branch information
vzhukovs committed Jul 15, 2020
1 parent 727b6c5 commit 647d813
Show file tree
Hide file tree
Showing 8 changed files with 328 additions and 1 deletion.
1 change: 1 addition & 0 deletions che-theia-init-sources.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ sources:
- extensions/eclipse-che-theia-messaging
- extensions/eclipse-che-theia-file-sync-tracker
- extensions/eclipse-che-theia-cli-endpoint
- extensions/eclipse-che-theia-workspace
plugins:
- plugins/containers-plugin
- plugins/workspace-plugin
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ context('TypeScript', () => {
// open /tmp
cy.get('#theia-top-panel').should('exist').then(() => {

cy.theiaCommandPaletteClick('Open Workspace...', '{downarrow}').then(() => {
cy.theiaCommandPaletteClick('Open Workspace...', '{downarrow}{downarrow}').then(() => {
cy.get('.theia-LocationList').should('exist');
cy.get('.theia-LocationList').select('file:///');
cy.wait(2000);
Expand Down
7 changes: 7 additions & 0 deletions extensions/eclipse-che-theia-workspace/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
node_modules
.browser_modules
lib
*.log
*-app/*
!*-app/package.json
.idea
31 changes: 31 additions & 0 deletions extensions/eclipse-che-theia-workspace/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "@eclipse-che/theia-workspace-extension",
"keywords": [
"theia-extension"
],
"version": "0.0.1",
"files": [
"lib",
"src"
],
"dependencies": {
"@eclipse-che/api": "latest",
"@theia/workspace": "next",
"@eclipse-che/theia-plugin-ext": "^0.0.1"
},
"scripts": {
"prepare": "yarn clean && yarn build",
"clean": "rimraf lib",
"format": "tsfmt -r --useTsfmt ../../configs/tsfmt.json",
"lint": "eslint --cache=true --no-error-on-unmatched-pattern=true \"{src,test}/**/*.{ts,tsx}\"",
"compile": "tsc",
"build": "concurrently -n \"format,lint,compile\" -c \"red,green,blue\" \"yarn format\" \"yarn lint\" \"yarn compile\"",
"watch": "tsc -w"
},
"license": "EPL-2.0",
"theiaExtensions": [
{
"frontend": "lib/browser/che-workspace-module"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
/*********************************************************************
* Copyright (c) 2019 Red Hat, Inc.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
import { inject, injectable } from 'inversify';
import {
QuickOpenGroupItem,
QuickOpenItem,
QuickOpenMode,
QuickOpenModel
} from '@theia/core/lib/common/quick-open-model';
import { LabelProvider, QuickOpenService } from '@theia/core/lib/browser';
import { CheApiService } from '@eclipse-che/theia-plugin-ext/lib/common/che-protocol';
import { che as cheApi } from '@eclipse-che/api';
import { OauthUtils } from '@eclipse-che/theia-plugin-ext/lib/browser/oauth-utils';
import { AbstractDialog } from '@theia/core/lib/browser/dialogs';
import { MessageService } from '@theia/core/lib/common/message-service';
import * as moment from 'moment';
import { Message } from '@theia/core/lib/browser/widgets/index';
import { Key } from '@theia/core/lib/browser/keyboard/keys';

@injectable()
export class QuickOpenCheWorkspace implements QuickOpenModel {
protected items: QuickOpenGroupItem[];
protected currentWorkspace: cheApi.workspace.Workspace;

@inject(QuickOpenService) protected readonly quickOpenService: QuickOpenService;
@inject(CheApiService) protected readonly cheApi: CheApiService;
@inject(OauthUtils) protected readonly oAuthUtils: OauthUtils;
@inject(LabelProvider) protected readonly labelProvider: LabelProvider;
@inject(MessageService) protected readonly messageService: MessageService;

private async open(workspaces: cheApi.workspace.Workspace[]): Promise<void> {
this.items = [];

if (!workspaces.length) {
this.items.push(new QuickOpenGroupItem({
label: 'No Recent Workspaces',
run: (mode: QuickOpenMode): boolean => false
}));
return;
}

for (const workspace of workspaces) {
const icon = this.labelProvider.folderIcon;
const iconClass = icon + ' file-icon';
this.items.push(new QuickOpenGroupItem({
label: this.getWorkspaceName(workspace) + (this.isCurrentWorkspace(workspace) ? ' (Current)' : ''),
detail: `Stack: ${this.getWorkspaceStack(workspace)}`,
groupLabel: `last modified ${moment(this.getWorkspaceModificationTime(workspace)).fromNow()}`,
iconClass,
run: (mode: QuickOpenMode): boolean => {
if (mode !== QuickOpenMode.OPEN) {
return false;
}

if (this.isCurrentWorkspace(workspace)) {
return true;
}

this.openWorkspace(workspace);

return true;
},
}));
}

this.quickOpenService.open(this, {
placeholder: 'Type the name of the Che workspace you want to open',
fuzzyMatchLabel: true,
fuzzySort: false
});
}

onType(lookFor: string, acceptor: (items: QuickOpenItem[]) => void): void {
acceptor(this.items);
}

async select(): Promise<void> {
this.items = [];

const token = await this.oAuthUtils.getUserToken();

if (!this.currentWorkspace) {
this.currentWorkspace = await this.cheApi.currentWorkspace();
}

if (!this.currentWorkspace.namespace) {
return;
}

const workspaces = await this.cheApi.getAllByNamespace(this.currentWorkspace.namespace, token);

workspaces.sort((a: cheApi.workspace.Workspace, b: cheApi.workspace.Workspace) => {
const updatedA: number = this.getWorkspaceModificationTime(a);
const updatedB: number = this.getWorkspaceModificationTime(b);

if (isNaN(updatedA) || isNaN(updatedB)) {
return 0;
} else {
return updatedB - updatedA;
}
});

await this.open(workspaces);
}

private getWorkspaceName(workspace: cheApi.workspace.Workspace): string | undefined {
if (workspace.devfile && workspace.devfile.metadata) {
return workspace.devfile.metadata.name;
}
}

private getWorkspaceStack(workspace: cheApi.workspace.Workspace): string | undefined {
return workspace.attributes && workspace.attributes.stackName ? workspace.attributes.stackName : 'Custom';
}

private getWorkspaceModificationTime(workspace: cheApi.workspace.Workspace): number {
if (workspace.attributes) {
if (workspace.attributes.updated) {
return parseInt(workspace.attributes.updated);
} else if (workspace.attributes.created) {
return parseInt(workspace.attributes.created);
}
}

return NaN;
}

private stopCurrentWorkspace(): Promise<boolean | undefined> {
class StopWorkspaceDialog extends AbstractDialog<boolean | undefined> {
protected confirmed: boolean | undefined = true;
protected readonly dontStopButton: HTMLButtonElement;

constructor() {
super({
title: 'Open Workspace'
});

this.contentNode.appendChild(this.createMessageNode('Do you want to stop current workspace?'));
this.appendCloseButton('Cancel');
this.dontStopButton = this.appendDontStopButton();
this.appendAcceptButton('Yes');
}

get value(): boolean | undefined {
return this.confirmed;
}

protected appendDontStopButton(): HTMLButtonElement {
const button = this.createButton('No');
this.controlPanel.appendChild(button);
button.classList.add('secondary');
return button;
}

protected onAfterAttach(msg: Message): void {
super.onAfterAttach(msg);
this.addKeyListener(this.dontStopButton, Key.ENTER, () => {
this.confirmed = false;
this.accept();
}, 'click');
}

protected onCloseRequest(msg: Message): void {
super.onCloseRequest(msg);
this.confirmed = undefined;
this.accept();
}

protected createMessageNode(msg: string | HTMLElement): HTMLElement {
if (typeof msg === 'string') {
const messageNode = document.createElement('div');
messageNode.textContent = msg;
return messageNode;
}
return msg;
}

}

return new StopWorkspaceDialog().open();
}

private async openWorkspace(workspace: cheApi.workspace.Workspace): Promise<void> {
const result = await this.stopCurrentWorkspace();
if (typeof result === 'boolean') {
if (result) {
await this.cheApi.stop();
}
window.parent.postMessage(`open-workspace:${workspace.id}`, '*');
}
}

private isCurrentWorkspace(workspace: cheApi.workspace.Workspace): boolean {
return this.currentWorkspace.id === workspace.id;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*********************************************************************
* Copyright (c) 2019 Red Hat, Inc.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
import { inject, injectable } from 'inversify';
import { CommandContribution, CommandRegistry, MenuContribution, MenuModelRegistry } from '@theia/core/lib/common';
import { CommonMenus } from '@theia/core/lib/browser';
import { WorkspaceCommands } from '@theia/workspace/lib/browser/workspace-commands';
import { QuickOpenCheWorkspace } from './che-quick-open-workspace';
import { Command } from '@theia/core/lib/common/command';

export namespace CheWorkspaceCommands {

const FILE_CATEGORY = 'File';

export const OPEN_RECENT_WORKSPACE: Command = {
id: 'che.openRecentWorkspace',
category: FILE_CATEGORY,
label: 'Open Recent Workspace...'
};
}

@injectable()
export class CheWorkspaceContribution implements CommandContribution, MenuContribution {

@inject(QuickOpenCheWorkspace) protected readonly quickOpenWorkspace: QuickOpenCheWorkspace;

registerCommands(commands: CommandRegistry): void {
commands.registerCommand(CheWorkspaceCommands.OPEN_RECENT_WORKSPACE, {
execute: () => this.quickOpenWorkspace.select()
});
}

registerMenus(menus: MenuModelRegistry): void {
menus.unregisterMenuAction({
commandId: WorkspaceCommands.OPEN_RECENT_WORKSPACE.id
}, CommonMenus.FILE_OPEN);

menus.registerMenuAction(CommonMenus.FILE_OPEN, {
commandId: CheWorkspaceCommands.OPEN_RECENT_WORKSPACE.id,
label: CheWorkspaceCommands.OPEN_RECENT_WORKSPACE.label,
order: 'a20'
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*********************************************************************
* Copyright (c) 2019 Red Hat, Inc.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
import { ContainerModule } from 'inversify';
import { QuickOpenCheWorkspace } from './che-quick-open-workspace';
import { CommandContribution, MenuContribution } from '@theia/core/lib/common';
import { CheWorkspaceContribution } from './che-workspace-contribution';

export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(QuickOpenCheWorkspace).toSelf().inSingletonScope();
bind(CheWorkspaceContribution).toSelf().inSingletonScope();
for (const identifier of [CommandContribution, MenuContribution]) {
bind(identifier).toService(CheWorkspaceContribution);
}
});
14 changes: 14 additions & 0 deletions extensions/eclipse-che-theia-workspace/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"extends": "../../configs/base.tsconfig.json",
"compilerOptions": {
"lib": [
"es6",
"dom"
],
"rootDir": "src",
"outDir": "lib"
},
"include": [
"src"
]
}

0 comments on commit 647d813

Please sign in to comment.