Skip to content

Commit

Permalink
#912 Display Locked/Read Only label on Canvas (#913)
Browse files Browse the repository at this point in the history
  • Loading branch information
tomlyn authored Jan 18, 2022
1 parent 0b6dee2 commit 8bd7ed3
Show file tree
Hide file tree
Showing 14 changed files with 278 additions and 14 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2017-2020 Elyra Authors
* Copyright 2017-2022 Elyra Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -22,6 +22,7 @@ import Toolbar from "../../src/toolbar/toolbar.jsx";
import NotificationPanel from "../../src/notification-panel/notification-panel.jsx";
import CanvasBottomPanel from "../../src/common-canvas/cc-bottom-panel.jsx";
import CommonCanvasRightFlyout from "../../src/common-canvas/cc-right-flyout.jsx";
import CommonCanvasStateTag from "../../src/common-canvas/cc-state-tag.jsx";
import { createIntlCommonCanvas } from "../_utils_/common-canvas-utils.js";
import { expect } from "chai";
import sinon from "sinon";
Expand Down Expand Up @@ -65,6 +66,29 @@ describe("CommonCanvas renders correctly", () => {
expect(canvasController.isRightFlyoutOpen() === true).to.be.true;
});

it("should NOT render one <CommonCanvasStateTag/> component when enableStateTag = None", () => {
const config = { enableStateTag: "None" };
const canvasParams = { };
const wrapper = createCommonCanvas(config, canvasController, canvasParams);
expect(wrapper.find(CommonCanvasStateTag)).to.have.length(0);
});

it("should render one <CommonCanvasStateTag/> component when enableStateTag = Locked", () => {
const config = { enableStateTag: "Locked" };
const canvasParams = { };
const wrapper = createCommonCanvas(config, canvasController, canvasParams);
expect(wrapper.find(CommonCanvasStateTag)).to.have.length(1);
expect(wrapper.find("div.state-tag").text()).to.equal("Locked");
});

it("should render one <CommonCanvasStateTag/> component when enableStateTag = ReadOnly", () => {
const config = { enableStateTag: "ReadOnly" };
const canvasParams = { };
const wrapper = createCommonCanvas(config, canvasController, canvasParams);
expect(wrapper.find(CommonCanvasStateTag)).to.have.length(1);
expect(wrapper.find("div.state-tag").text()).to.equal("Read-only");
});

it("should render one <CanvasContents/> component", () => {
const config = {};
const wrapper = createCommonCanvas(config, canvasController);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,16 @@
"canvas.deleteObject": "Delete",
"canvas.flowIsEmpty": "Your flow is empty.",
"canvas.addNodeToStart": "Get started by adding a node.",
"canvas.stateTagLocked": "Locked",
"canvas.stateTagReadOnly": "Read-only",
"canvas.stateTagTipLocked": "This flow is locked and cannot be edited.",
"canvas.stateTagTipReadOnly": "This flow cannot be edited.",
"node.createSupernode": "Create supernode",
"node.createSupernodeExternal": "Create external supernode",
"node.expandSupernode": "Expand supernode",
"node.displaySupernodeFullPage": "Display full page",
"node.collapseSupernodeInPlace": "Collapse supernode",
"node.deconstructSupernode": "Deconstruct supernode",
"node.deconstructSupernode": "Deconstruct supernode",
"node.convertSupernodeExternalToLocal": "Convert external to local",
"node.convertSupernodeLocalToExternal": "Convert local to external",
"canvas.validateFlow": "Validate Flow",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
"canvas.deleteObject": "[Esperanto~Delete~eo]",
"canvas.flowIsEmpty": "[Esperanto~Your flow is empty.~~~~~eo]",
"canvas.addNodeToStart": "[Esperanto~Get started by adding a node.~~~~~~~~~eo]",
"canvas.stateTagLocked": "[Esperanto~Locked~~eo]",
"canvas.stateTagReadOnly": "[Esperanto~Read-only~~~eo]",
"canvas.stateTagTipLocked": "[Esperanto~This flow is locked and cannot be edited.~~~~~~~~~eo]",
"canvas.stateTagTipReadOnly": "[Esperanto~This flow cannot be edited.~~~~~~~~~eo]",
"node.createSupernode": "[Esperanto~Create supernode~~eo]",
"node.createSupernodeExternal": "[Esperanto~Create external supernode~~~~~~eo]",
"node.expandSupernode": "[Esperanto~Expand supernode~~eo]",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1665,6 +1665,11 @@ export default class CanvasController {
data.pipelineId = tipConfig.pipelineId;
data.link = tipConfig.link;
break;
case constants.TIP_TYPE_STATE_TAG:
data.stateTagText = tipConfig.stateTagText;
data.stateTagType = tipConfig.stateTagType;
break;

default:
}

Expand Down Expand Up @@ -1709,6 +1714,8 @@ export default class CanvasController {
return canvasConfig.tipConfig.decorations;
case constants.TIP_TYPE_LINK:
return canvasConfig.tipConfig.links;
case constants.TIP_TYPE_STATE_TAG:
return canvasConfig.tipConfig.stateTag;
default:
return false;
}
Expand Down
22 changes: 20 additions & 2 deletions canvas_modules/common-canvas/src/common-canvas/cc-contents.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2017-2021 Elyra Authors
* Copyright 2017-2022 Elyra Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -23,8 +23,9 @@ import { connect } from "react-redux";
import { injectIntl } from "react-intl";
import defaultMessages from "../../locales/common-canvas/locales/en.json";
import CommonCanvasContextMenu from "./cc-context-menu.jsx";
import CommonCanvasStateTag from "./cc-state-tag.jsx";
import { FlowData16 } from "@carbon/icons-react";
import { DND_DATA_TEXT } from "./constants/canvas-constants";
import { DND_DATA_TEXT, STATE_TAG_LOCKED, STATE_TAG_READ_ONLY } from "./constants/canvas-constants";

import Logger from "../logging/canvas-logger.js";
import SVGCanvasD3 from "./svg-canvas-d3.js";
Expand Down Expand Up @@ -155,6 +156,21 @@ class CanvasContents extends React.Component {
return this.svgCanvasD3;
}

getStateTag() {
let stateTag = null;

if (this.props.canvasConfig.enableStateTag === STATE_TAG_READ_ONLY ||
this.props.canvasConfig.enableStateTag === STATE_TAG_LOCKED) {
stateTag = (
<CommonCanvasStateTag
stateTagType={this.props.canvasConfig.enableStateTag}
canvasController={this.props.canvasController}
/>
);
}
return stateTag;
}

getEmptyCanvas() {
let emptyCanvas = null;
if (this.props.canvasController.isPrimaryPipelineEmpty()) {
Expand Down Expand Up @@ -351,6 +367,7 @@ class CanvasContents extends React.Component {
render() {
this.logger.log("render");

const stateTag = this.getStateTag();
const emptyCanvas = this.getEmptyCanvas();
const contextMenu = this.getContextMenu();
const dropZoneCanvas = this.getDropZone();
Expand Down Expand Up @@ -387,6 +404,7 @@ class CanvasContents extends React.Component {
{svgCanvasDiv}
{contextMenu}
{dropZoneCanvas}
{stateTag}
</div>
</ReactResizeDetector>
</main>
Expand Down
99 changes: 99 additions & 0 deletions canvas_modules/common-canvas/src/common-canvas/cc-state-tag.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* Copyright 2022 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 React from "react";
import PropTypes from "prop-types";
import Logger from "../logging/canvas-logger.js";
import defaultMessages from "../../locales/common-canvas/locales/en.json";
import { injectIntl } from "react-intl";
import { Locked16, EditOff16 } from "@carbon/icons-react";
import { STATE_TAG_LOCKED, STATE_TAG_READ_ONLY, TIP_TYPE_STATE_TAG }
from "../common-canvas/constants/canvas-constants.js";

class CommonCanvasStateTag extends React.Component {
constructor(props) {
super(props);
this.logger = new Logger("CC-StateTag");

this.onMouseOver = this.onMouseOver.bind(this);
this.onMouseLeave = this.onMouseLeave.bind(this);
}

onMouseOver(ev) {
let tipText;

if (this.props.stateTagType === STATE_TAG_LOCKED) {
tipText = this.getLabel("canvas.stateTagTipLocked");

} else if (this.props.stateTagType === STATE_TAG_READ_ONLY) {
tipText = this.getLabel("canvas.stateTagTipReadOnly");
}

this.props.canvasController.openTip({
id: "stateTagTip",
type: TIP_TYPE_STATE_TAG,
stateTagType: this.props.stateTagType,
stateTagText: tipText,
targetObj: ev.currentTarget
});
}

onMouseLeave() {
this.props.canvasController.closeTip();
}

getLabel(labelId) {
return this.props.intl.formatMessage({ id: labelId, defaultMessage: defaultMessages[labelId] });
}

getStateTag(label, icon) {
return (
<div className={"state-tag"}
onMouseOver={this.onMouseOver}
onMouseLeave={this.onMouseLeave}
>
{icon}
<span>{label}</span>
</div>
);
}

render() {
this.logger.log("render");

let stateTag = null;

if (this.props.stateTagType === STATE_TAG_LOCKED) {
const label = this.getLabel("canvas.stateTagLocked");
stateTag = this.getStateTag(label, (<Locked16 />));

} else if (this.props.stateTagType === STATE_TAG_READ_ONLY) {
const label = this.getLabel("canvas.stateTagReadOnly");
stateTag = this.getStateTag(label, (<EditOff16 />));
}

return stateTag;
}
}

CommonCanvasStateTag.propTypes = {
// Provided by CommonCanvas
intl: PropTypes.object.isRequired,
stateTagType: PropTypes.string,
canvasController: PropTypes.object
};

export default injectIntl(CommonCanvasStateTag);
22 changes: 17 additions & 5 deletions canvas_modules/common-canvas/src/common-canvas/cc-tooltip.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2017-2020 Elyra Authors
* Copyright 2017-2022 Elyra Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -20,7 +20,8 @@ import PropTypes from "prop-types";
import Tooltip from "../tooltip/tooltip.jsx";
import Icon from "../icons/icon.jsx";
import { isEmpty } from "lodash";
import { TIP_TYPE_PALETTE_ITEM, TIP_TYPE_PALETTE_CATEGORY, TIP_TYPE_DEC, TIP_TYPE_NODE, TIP_TYPE_PORT, TIP_TYPE_LINK,
import { TIP_TYPE_PALETTE_ITEM, TIP_TYPE_PALETTE_CATEGORY, TIP_TYPE_DEC,
TIP_TYPE_NODE, TIP_TYPE_PORT, TIP_TYPE_LINK, TIP_TYPE_STATE_TAG,
ERROR, WARNING } from "../common-canvas/constants/canvas-constants.js";

class CommonCanvasTooltip extends React.Component {
Expand Down Expand Up @@ -55,6 +56,7 @@ class CommonCanvasTooltip extends React.Component {
case TIP_TYPE_PORT:
case TIP_TYPE_DEC:
case TIP_TYPE_LINK:
case TIP_TYPE_STATE_TAG:
default:
direction = "bottom";
}
Expand Down Expand Up @@ -131,6 +133,14 @@ class CommonCanvasTooltip extends React.Component {
break;

case TIP_TYPE_LINK:
break;

case TIP_TYPE_STATE_TAG:
content = isEmpty(this.props.stateTagText) ? null : (
<div className="tip-state-tag">{this.props.stateTagText}</div>
);
break;

default:
content = null;
}
Expand All @@ -152,15 +162,16 @@ CommonCanvasTooltip.propTypes = {
canvasController: PropTypes.object.isRequired,
// The rest provided by redux
id: PropTypes.string,
type: PropTypes.oneOf([TIP_TYPE_PALETTE_CATEGORY, TIP_TYPE_PALETTE_ITEM, TIP_TYPE_NODE, TIP_TYPE_PORT, TIP_TYPE_DEC, TIP_TYPE_LINK]),
type: PropTypes.oneOf([TIP_TYPE_PALETTE_CATEGORY, TIP_TYPE_PALETTE_ITEM, TIP_TYPE_NODE, TIP_TYPE_PORT, TIP_TYPE_DEC, TIP_TYPE_LINK, TIP_TYPE_STATE_TAG]),
targetObj: PropTypes.object,
mousePos: PropTypes.object,
customContent: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
node: PropTypes.object,
port: PropTypes.object,
decoration: PropTypes.object,
nodeTemplate: PropTypes.object,
category: PropTypes.object
category: PropTypes.object,
stateTagText: PropTypes.string
};

const mapStateToProps = (state, ownProps) => ({
Expand All @@ -173,7 +184,8 @@ const mapStateToProps = (state, ownProps) => ({
port: state.tooltip.port,
decoration: state.tooltip.decoration,
nodeTemplate: state.tooltip.nodeTemplate,
category: state.tooltip.category
category: state.tooltip.category,
stateTagText: state.tooltip.stateTagText
});

export default connect(mapStateToProps)(CommonCanvasTooltip);
24 changes: 24 additions & 0 deletions canvas_modules/common-canvas/src/common-canvas/common-canvas.scss
Original file line number Diff line number Diff line change
Expand Up @@ -199,5 +199,29 @@ $bottom-panel-height: 393px;
&.info > svg > .dot {
fill: $support-04;
}
}

.state-tag {
@include carbon--type-style("productive-heading-01");
color: $inverse-01;
background-color: $background-inverse;
height: 40px;
position: absolute;
left: 50%;
top: 16px;
transform: translate(-50%, 0%);
border-radius: 20px;
padding: 9px 0;
user-select: none; /* Prevent elements from being selectable */

svg {
color: $inverse-01;
position: relative;
top: 3px;
margin-left: 14px;
}

span {
margin: 0 16px 0 10px;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2017-2021 Elyra Authors
* Copyright 2017-2022 Elyra Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -31,13 +31,16 @@ export const DEFAULT_NOTIFICATION_HEADER = "Notifications";
// Used by both toolbar and notification panel.
export const NOTIFICATION_ICON_CLASS = "notificationCounterIcon";

// Values for enableInteractionType config parameter
export const INTERACTION_MOUSE = "Mouse";
export const INTERACTION_TRACKPAD = "Trackpad";

// Values for enableLinkType config parameter
export const LINK_TYPE_CURVE = "Curve";
export const LINK_TYPE_ELBOW = "Elbow";
export const LINK_TYPE_STRAIGHT = "Straight";

// Values for enableLinkDirection config parameter
export const LINK_DIR_LEFT_RIGHT = "LeftRight";
export const LINK_DIR_TOP_BOTTOM = "TopBottom";
export const LINK_DIR_BOTTOM_TOP = "BottomTop";
Expand Down Expand Up @@ -66,6 +69,11 @@ export const TOOLBAR_LAYOUT_TOP = "Top";
export const ASSOC_RIGHT_SIDE_CURVE = "RightSideCurve";
export const ASSOC_STRAIGHT = "Straight";

// Values for enableStateTag config parameter
export const STATE_TAG_NONE = "None";
export const STATE_TAG_LOCKED = "Locked";
export const STATE_TAG_READ_ONLY = "ReadOnly";

export const ERROR = "error";
export const WARNING = "warning";
export const INFO = "info";
Expand Down Expand Up @@ -101,6 +109,7 @@ export const TIP_TYPE_LINK = "tipTypeLink";
export const TIP_TYPE_PALETTE_ITEM = "tipTypePaletteItem";
export const TIP_TYPE_PALETTE_CATEGORY = "tipTypePaletteCategory";
export const TIP_TYPE_TOOLBAR_ITEM = "tipTypeToolbarItem";
export const TIP_TYPE_STATE_TAG = "tipTypeStateTag";

export const CREATE_NODE = "create_node";
export const CLONE_NODE = "clone_node";
Expand Down
Loading

0 comments on commit 8bd7ed3

Please sign in to comment.