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

Update node properties to allow changing file #867

Merged
merged 9 commits into from
Aug 18, 2020
Merged
37 changes: 37 additions & 0 deletions packages/pipeline-editor/src/PipelineEditorWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
pipelineIcon,
savePipelineIcon,
runtimesIcon,
showBrowseFileDialog,
showFormDialog,
errorIcon
} from '@elyra/ui-components';
Expand Down Expand Up @@ -200,6 +201,7 @@ export class PipelineEditor extends React.Component<
position = 10;
node: React.RefObject<HTMLDivElement>;
propertiesInfo: any;
propertiesController: any;

constructor(props: any) {
super(props);
Expand Down Expand Up @@ -233,6 +235,10 @@ export class PipelineEditor extends React.Component<
this.applyPropertyChanges = this.applyPropertyChanges.bind(this);
this.closePropertiesDialog = this.closePropertiesDialog.bind(this);
this.openPropertiesDialog = this.openPropertiesDialog.bind(this);
this.propertiesActionHandler = this.propertiesActionHandler.bind(this);
this.propertiesControllerHandler = this.propertiesControllerHandler.bind(
this
);

this.node = React.createRef();
this.handleEvent = this.handleEvent.bind(this);
Expand Down Expand Up @@ -342,6 +348,8 @@ export class PipelineEditor extends React.Component<
];

const propertiesCallbacks = {
actionHandler: this.propertiesActionHandler,
controllerHandler: this.propertiesControllerHandler,
applyPropertyChanges: this.applyPropertyChanges,
closePropertiesDialog: this.closePropertiesDialog
};
Expand Down Expand Up @@ -456,6 +464,13 @@ export class PipelineEditor extends React.Component<
}
const app_data = node.app_data;

if (app_data.filename !== propertySet.filename) {
app_data.filename = propertySet.filename;
Copy link
Member

Choose a reason for hiding this comment

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

As a note: this line will need to be updated to work with #861 is merged (in whichever PR is merged last)

Comment on lines +467 to +468
Copy link
Member

Choose a reason for hiding this comment

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

Note this would specifically need to be updated to the following once #861 is merged or in #861 once this is merged.

Suggested change
if (app_data.filename !== propertySet.filename) {
app_data.filename = propertySet.filename;
const propertyFilename = PipelineService.getPipelineRelativeNodePath(
this.widgetContext.path,
propertySet.filename
);
if (app_data.filename !== propertyFilename) {
app_data.filename = propertyFilename;

node.label = propertySet.filename
.replace(/^.*[\\/]/, '')
.replace(/\.[^/.]+$/, '');
kevin-bates marked this conversation as resolved.
Show resolved Hide resolved
}

app_data.runtime_image = propertySet.runtime_image;
app_data.outputs = propertySet.outputs;
app_data.env_vars = propertySet.env_vars;
Expand All @@ -471,6 +486,28 @@ export class PipelineEditor extends React.Component<
this.setState({ showPropertiesDialog: false, propertiesInfo: propsInfo });
}

propertiesControllerHandler(propertiesController: any): void {
this.propertiesController = propertiesController;
}

propertiesActionHandler(id: string, appData: any, data: any): void {
if (id === 'browse_file') {
const propertyId = { name: data.parameter_ref };
showBrowseFileDialog(this.browserFactory.defaultBrowser.model.manager, {
filter: (model: any): boolean => {
return model.type == 'notebook';
Copy link
Member

Choose a reason for hiding this comment

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

Is the intension here that we would update this when we add support for other file types?

Copy link
Contributor Author

@vabarbosa vabarbosa Aug 17, 2020

Choose a reason for hiding this comment

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

yes, the filter is so you only show files that should be selectable. in this case, we only want to show notebook files (no point in showing all available files if user shouldn't be selecting them). and yeah, if the dialog is to be re-used in other context then you can configure what files do appear. for example, with File Dependencies (#873) browser dialog would show files other than just notebook files

}
}).then((result: any) => {
if (result.button.accept && result.value.length) {
this.propertiesController.updatePropertyValue(
propertyId,
result.value[0].path
);
}
});
}
}

/*
* Add options to the node context menu
* Pipeline specific context menu items are:
Expand Down
53 changes: 53 additions & 0 deletions packages/pipeline-editor/src/properties.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,59 @@
"default": "Files generated during execution that will become available to all subsequent pipeline steps.\nOne filename (including subdirectory) per line."
}
}
],
"action_info": [
{
"id": "browse_file",
"label": {
"default": "Browse..."
},
"control": "button",
"data": {
"parameter_ref": "filename"
}
}
],
"group_info": [
{
"id": "nodeGroupInfo",
"label": {
"default": "Node Properties"
},
"type": "panels",
"group_info": [
{
"id": "browseFilePanel",
"type": "columnPanel",
"label": {
"default": "Browse File Panel"
},
"group_info": [
{
"id": "nodeFileControl",
"type": "controls",
"parameter_refs": ["filename"]
},
{
"id": "nodeBrowseFileAction",
"type": "actionPanel",
"action_refs": ["browse_file"]
}
]
},
{
"id": "nodePropertiesControls",
"type": "controls",
"parameter_refs": [
"runtime_image",
"dependencies",
"include_subdirectories",
"env_vars",
"outputs"
]
}
]
}
]
},
"resources": {}
Expand Down
8 changes: 8 additions & 0 deletions packages/pipeline-editor/style/canvas.css
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,14 @@ body[data-jp-theme-light='false'] .bx--modal.is-visible {
.bx--modal-footer .bx--btn--secondary {
background: var(--md-grey-500);
}
.bx--modal-content .bx--btn--tertiary {
border-color: var(--md-blue-500);
color: var(--md-blue-500);
}
.bx--modal-content .bx--btn--tertiary:hover {
background: var(--md-blue-500);
color: #ffffff;
}
.bx--modal-footer .bx--btn:disabled {
background-color: var(--jp-layout-color3);
opacity: 0.3;
Expand Down
9 changes: 9 additions & 0 deletions packages/pipeline-editor/style/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,12 @@ td {
padding-left: 10px;
padding-right: 10px;
}

[data-id='properties-nodeBrowseFileAction'] {
flex-direction: column;
justify-content: end;
align-items: flex-end;
}
[data-id='properties-nodeBrowseFileAction'] .bx--btn--tertiary {
margin-bottom: 0.6rem;
}
145 changes: 145 additions & 0 deletions packages/ui-components/src/BrowseFileDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/*
* Copyright 2018-2020 IBM Corporation
*
* 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 { Dialog } from '@jupyterlab/apputils';
import { IDocumentManager } from '@jupyterlab/docmanager';
import {
BreadCrumbs,
DirListing,
FilterFileBrowserModel
} from '@jupyterlab/filebrowser';
import { Widget, PanelLayout } from '@lumino/widgets';

const BROWSE_FILE_CLASS = 'elyra-browseFileDialog';

export interface IBrowseFileDialogOptions {
filter?: (model: any) => boolean;
multiselect?: boolean;
includeDir?: boolean;
}

/**
* Browse file widget for dialog body
*/
export class BrowseFileDialog extends Widget
implements Dialog.IBodyWidget<IBrowseFileDialogOptions> {
directoryListing: DirListing;
breadCrumbs: BreadCrumbs;
dirListingHandleEvent: (event: Event) => void;
multiselect: boolean;
includeDir: boolean;

constructor(props: any) {
super(props);

const model = new FilterFileBrowserModel({
manager: props.manager,
filter: props.filter
});

const layout = (this.layout = new PanelLayout());

this.directoryListing = new DirListing({
model: model
});

this.multiselect = props.multiselect;
this.includeDir = props.includeDir;
this.dirListingHandleEvent = this.directoryListing.handleEvent;
this.directoryListing.handleEvent = (event: Event): void => {
this.handleEvent(event);
};

this.breadCrumbs = new BreadCrumbs({
model: model
});

layout.addWidget(this.breadCrumbs);
layout.addWidget(this.directoryListing);
}

getValue(): any {
const itemsIter = this.directoryListing.selectedItems();
const selected = [];
let item = null;

while ((item = itemsIter.next()) !== undefined) {
if (this.includeDir || item.type !== 'directory') {
selected.push(item);
}
}

return selected;
}

handleEvent(event: Event): void {
let modifierKey = false;
if (event instanceof MouseEvent) {
modifierKey =
(event as MouseEvent).shiftKey || (event as MouseEvent).metaKey;
} else if (event instanceof KeyboardEvent) {
modifierKey =
(event as KeyboardEvent).shiftKey || (event as KeyboardEvent).metaKey;
}

switch (event.type) {
case 'keydown':
case 'keyup':
case 'mousedown':
case 'mouseup':
case 'click':
if (this.multiselect || !modifierKey) {
this.dirListingHandleEvent.call(this.directoryListing, event);
}
break;
case 'dblclick': {
const clickedItem = this.directoryListing.modelForClick(
event as MouseEvent
);
if (clickedItem.type === 'directory') {
this.dirListingHandleEvent.call(this.directoryListing, event);
} else {
event.preventDefault();
event.stopPropagation();
}
break;
}
default:
this.dirListingHandleEvent.call(this.directoryListing, event);
break;
}
}
}

export const showBrowseFileDialog = (
manager: IDocumentManager,
options: IBrowseFileDialogOptions
): Promise<Dialog.IResult<any>> => {
const dialog = new Dialog({
title: 'Select a file',
body: new BrowseFileDialog({
manager: manager,
filter: options.filter,
multiselect: options.multiselect,
includeDir: options.includeDir
}),
buttons: [Dialog.cancelButton(), Dialog.okButton({ label: 'Select' })]
});

dialog.addClass(BROWSE_FILE_CLASS);

return dialog.launch();
};
1 change: 1 addition & 0 deletions packages/ui-components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './BrowseFileDialog';
export * from './ExpandableErrorDialog';
export * from './ExpandableComponent';
export * from './FormDialog';
Expand Down
5 changes: 5 additions & 0 deletions packages/ui-components/style/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,8 @@
word-wrap: break-word;
z-index: 999;
}

.elyra-browseFileDialog .jp-Dialog-content {
height: 400px;
width: 600px;
}