Skip to content

Commit

Permalink
Readd deleted extension (Code Viewer, Scala and R editors)
Browse files Browse the repository at this point in the history
  • Loading branch information
caponetto committed Jan 15, 2025
1 parent b2574bb commit 8c7e542
Show file tree
Hide file tree
Showing 24 changed files with 1,303 additions and 0 deletions.
86 changes: 86 additions & 0 deletions cypress/tests/reditor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* Copyright 2018-2025 Elyra Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

describe.skip('R Editor tests (skipped as this extension is not part of ODH distribution)', () => {
before(() => {
cy.resetJupyterLab();
cy.bootstrapFile('helloworld.r'); // load R file used to check existing contents
});

after(() => {
// delete files created for testing
cy.deleteFile('untitled*.r');
cy.deleteFile('helloworld.r'); // delete R file used for testing
});

// R Editor Tests
it('opens blank R file from launcher', () => {
cy.createNewScriptEditor('R');
cy.get('.lm-TabBar-tab[data-type="document-title"]');
cy.closeTab(-1);
});

// Flaky test: Missing expected items in the context menu; it works manually though.
it.skip('check R editor tab right click content', () => {
cy.createNewScriptEditor('R');
cy.checkRightClickTabContent('R');
cy.closeTab(-1);
});

it('close R editor', () => {
cy.createNewScriptEditor('R');
cy.closeTab(-1);
});

it('open R file with expected content', () => {
cy.openFileAndCheckContent('r');
cy.closeTab(-1);
});

it('check icons', () => {
// Check file menu editor contents
cy.findByRole('menuitem', { name: /file/i }).click();
cy.findByText(/^new$/i).click();
cy.get(
'[data-command="script-editor:create-new-r-editor"] svg[data-icon="elyra:rIcon"]'
);

// Check r icons from launcher & file explorer
cy.get(
'.jp-LauncherCard[data-category="Elyra"][title="Create a new R Editor"] svg[data-icon="elyra:rIcon"]'
).click();
cy.get(
'#filebrowser [title*="Name: untitled.r"] svg[data-icon="elyra:rIcon"]'
);
cy.closeTab(-1);
});

it('opens blank R file from menu', () => {
cy.findByRole('menuitem', { name: /file/i }).click();
cy.findByText(/^new$/i).click();

cy.get(
'[data-command="script-editor:create-new-r-editor"] > .lm-Menu-itemLabel'
).click();
cy.closeTab(-1);
});

it('check toolbar and its content for R file', () => {
cy.createNewScriptEditor('R');
cy.checkScriptEditorToolbarContent();
cy.closeTab(-1);
});
});
5 changes: 5 additions & 0 deletions packages/code-viewer/install.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"packageManager": "python",
"packageName": "elyra_code_viewer_extension",
"uninstallInstructions": "Use your Python package manager (pip, conda, etc.) to uninstall the package elyra_code_viewer_extension"
}
75 changes: 75 additions & 0 deletions packages/code-viewer/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
{
"name": "@elyra/code-viewer-extension",
"version": "0.0.0-dev",
"description": "JupyterLab extension - Display code without a file",
"keywords": [
"jupyter",
"jupyterlab",
"jupyterlab-extension"
],
"homepage": "https://github.com/elyra-ai/elyra",
"bugs": {
"url": "https://github.com/elyra-ai/elyra/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/elyra-ai/elyra/"
},
"license": "Apache-2.0",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"style": "style/index.css",
"files": [
"lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
"src/**/*.{ts,tsx}",
"style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}"
],
"scripts": {
"build": "jlpm build:lib && jlpm build:labextension:dev",
"build:prod": "jlpm clean && jlpm build:lib:prod && jlpm build:labextension",
"build:labextension": "jupyter labextension build .",
"build:labextension:dev": "jupyter labextension build --development True .",
"build:lib": "tsc --sourceMap",
"build:lib:prod": "tsc",
"clean": "jlpm clean:lib",
"clean:lib": "rimraf lib tsconfig.tsbuildinfo",
"clean:lintcache": "rimraf .eslintcache .stylelintcache",
"clean:labextension": "rimraf ../../../../labextensions/elyra_code_viewer_extension/labextension ../../../../labextensions/elyra_code_viewer_extension/_version.py",
"clean:all": "jlpm clean:lib && jlpm clean:labextension && jlpm clean:lintcache",
"cy:instrument": "npx nyc instrument --compact=false --in-place src/ src/",
"eslint": "jlpm eslint:check --fix",
"eslint:check": "eslint . --cache --ext .ts,.tsx",
"install:extension": "jlpm build",
"lint": "jlpm stylelint && jlpm prettier && jlpm eslint",
"lint:check": "jlpm stylelint:check && jlpm prettier:check && jlpm eslint:check",
"prettier": "jlpm prettier:base --write --list-different",
"prettier:base": "prettier \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}\"",
"prettier:check": "jlpm prettier:base --check",
"stylelint": "jlpm stylelint:check --fix",
"stylelint:check": "stylelint --cache \"style/**/*.css\"",
"test": "jest --coverage --passWithNoTests",
"watch": "run-p watch:src watch:labextension",
"watch:src": "tsc -w --sourceMap",
"watch:labextension": "jupyter labextension watch ."
},
"dependencies": {
"@jupyterlab/application": "^4.2.5",
"@jupyterlab/apputils": "^4.2.5",
"@jupyterlab/codeeditor": "^4.2.5",
"@jupyterlab/ui-components": "^4.2.5",
"@lumino/algorithm": "*",
"@lumino/widgets": "^2.3.1"
},
"devDependencies": {
"@jupyterlab/builder": "^4.2.5",
"rimraf": "~5.0.5",
"typescript": "~5.1.6"
},
"publishConfig": {
"access": "public"
},
"jupyterlab": {
"extension": true,
"outputDir": "../../labextensions/elyra_code_viewer_extension/labextension"
}
}
1 change: 1 addition & 0 deletions packages/code-viewer/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__import__("setuptools").setup()
92 changes: 92 additions & 0 deletions packages/code-viewer/src/CodeViewerWidget.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright 2018-2025 Elyra Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { CodeEditor, CodeEditorWrapper } from '@jupyterlab/codeeditor';
import { StackedLayout, Widget } from '@lumino/widgets';

export class CodeViewerWidget extends Widget {
/**
* Construct a new code viewer widget.
*/
constructor(options: CodeViewerWidget.IOptions) {
super();
this.model = options.model;

const editorWidget = new CodeEditorWrapper({
factory: options.factory,
model: options.model
});
this.editor = editorWidget.editor;
this.editor.setOption('readOnly', true);

const layout = (this.layout = new StackedLayout());
layout.addWidget(editorWidget);
}

static getCodeViewer(
options: CodeViewerWidget.INoModelOptions
): CodeViewerWidget {
const model = new CodeEditor.Model({ mimeType: options.mimeType });
model.sharedModel.source = options.content;
return new CodeViewerWidget({ factory: options.factory, model });
}

getContent = (): string => this.model.sharedModel.getSource();
getMimeType = (): string => this.model.mimeType;

model: CodeEditor.IModel;
editor: CodeEditor.IEditor;
}

/**
* The namespace for code viewer widget.
*/
export namespace CodeViewerWidget {
/**
* The options used to create an code viewer widget.
*/
export interface IOptions {
/**
* A code editor factory.
*/
factory: CodeEditor.Factory;

/**
* The content model for the viewer.
*/
model: CodeEditor.IModel;
}

/**
* The options used to create an code viewer widget without a model.
*/
export interface INoModelOptions {
/**
* A code editor factory.
*/
factory: CodeEditor.Factory;

/**
* The content to display in the viewer.
*/
content: string;

/**
* The mime type for the content.
*/
mimeType?: string;
}
}
126 changes: 126 additions & 0 deletions packages/code-viewer/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*
* Copyright 2018-2025 Elyra Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import {
ILayoutRestorer,
JupyterFrontEnd,
JupyterFrontEndPlugin
} from '@jupyterlab/application';
import { MainAreaWidget, WidgetTracker } from '@jupyterlab/apputils';
import { CodeEditor, IEditorServices } from '@jupyterlab/codeeditor';
import { textEditorIcon } from '@jupyterlab/ui-components';
import { toArray } from '@lumino/algorithm';

import { CodeViewerWidget } from './CodeViewerWidget';

const ELYRA_CODE_VIEWER_NAMESPACE = 'elyra-code-viewer-extension';

interface IOpenCodeViewerArgs {
content: string;
label?: string;
mimeType?: string;
extension?: string;
widgetId?: string;
}

/**
* The command IDs used by the code-viewer plugin.
*/
const CommandIDs = {
openViewer: 'elyra-code-viewer:open'
};

/**
* Initialization data for the code-viewer extension.
*/
const extension: JupyterFrontEndPlugin<void> = {
id: ELYRA_CODE_VIEWER_NAMESPACE,
autoStart: true,
requires: [IEditorServices],
optional: [ILayoutRestorer],
activate: (
app: JupyterFrontEnd,
editorServices: IEditorServices,
restorer: ILayoutRestorer
) => {
console.log('Elyra - code-viewer extension is activated!');

const tracker = new WidgetTracker<MainAreaWidget<CodeViewerWidget>>({
namespace: ELYRA_CODE_VIEWER_NAMESPACE
});

// Handle state restoration
if (restorer) {
void restorer.restore(tracker, {
command: CommandIDs.openViewer,
args: (widget) => ({
content: widget.content.getContent(),
label: widget.content.title.label,
mimeType: widget.content.getMimeType(),
widgetId: widget.content.id
}),
name: (widget) => widget.content.id
});
}

const openCodeViewer = async (
args: IOpenCodeViewerArgs
): Promise<CodeViewerWidget> => {
const func = editorServices.factoryService.newDocumentEditor;
const factory: CodeEditor.Factory = (options) => {
return func(options);
};

// Derive mimetype from extension
let mimetype = args.mimeType;
if (!mimetype && args.extension) {
mimetype = editorServices.mimeTypeService.getMimeTypeByFilePath(
`temp.${args.extension.replace(/\\.$/, '')}`
);
}

const widget = CodeViewerWidget.getCodeViewer({
factory,
content: args.content,
mimeType: mimetype
});
widget.title.label = args.label || 'Code Viewer';
widget.title.caption = widget.title.label;

// Get the fileType based on the mimetype to determine the icon
const fileType = toArray(app.docRegistry.fileTypes()).find((fileType) => {
return mimetype ? fileType.mimeTypes.includes(mimetype) : undefined;
});
widget.title.icon = fileType?.icon ?? textEditorIcon;

if (args.widgetId) {
widget.id = args.widgetId;
}
const main = new MainAreaWidget({ content: widget });
await tracker.add(main);
app.shell.add(main, 'main');
return widget;
};

app.commands.addCommand(CommandIDs.openViewer, {
execute: (args) => {
return openCodeViewer(args as unknown as IOpenCodeViewerArgs);
}
});
}
};

export default extension;
Loading

0 comments on commit 8c7e542

Please sign in to comment.