mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-19 08:51:24 +00:00
Migrate new graph vertex panel to react (#702)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
This commit is contained in:
@@ -1,75 +0,0 @@
|
||||
import * as ko from "knockout";
|
||||
import { NewVertexComponent, NewVertexViewModel } from "./NewVertexComponent";
|
||||
|
||||
const component = NewVertexComponent;
|
||||
|
||||
describe("New Vertex Component", () => {
|
||||
let vm: NewVertexViewModel;
|
||||
let partitionKeyProperty: ko.Observable<string>;
|
||||
|
||||
beforeEach(async () => {
|
||||
document.body.innerHTML = component.template as any;
|
||||
partitionKeyProperty = ko.observable(null);
|
||||
vm = new component.viewModel({
|
||||
newVertexData: null,
|
||||
partitionKeyProperty,
|
||||
});
|
||||
ko.applyBindings(vm);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
ko.cleanNode(document);
|
||||
});
|
||||
|
||||
describe("Rendering", () => {
|
||||
it("should display property list with input and +Add Property", () => {
|
||||
expect(document.querySelector(".newVertexComponent .newVertexForm")).not.toBeNull();
|
||||
expect(document.querySelector(".newVertexComponent .edgeInput")).not.toBeNull();
|
||||
expect(document.querySelector(".newVertexComponent .rightPaneAddPropertyBtn")).not.toBeNull();
|
||||
});
|
||||
|
||||
it("should display partition key property if set", () => {
|
||||
partitionKeyProperty("testKey");
|
||||
expect(
|
||||
(document.querySelector(".newVertexComponent .newVertexForm .labelCol input") as HTMLInputElement).value
|
||||
).toEqual("testKey");
|
||||
});
|
||||
|
||||
it("should NOT display partition key property if NOT set", () => {
|
||||
expect(document.getElementsByClassName("valueCol").length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Behavior", () => {
|
||||
let clickSpy: jasmine.Spy;
|
||||
|
||||
beforeEach(() => {
|
||||
clickSpy = jasmine.createSpy("Command button click spy");
|
||||
});
|
||||
|
||||
it("should add new property row when +Add property button is pressed", () => {
|
||||
document.querySelector(".newVertexComponent .rightPaneAddPropertyBtn").dispatchEvent(new Event("click"));
|
||||
document.querySelector(".newVertexComponent .rightPaneAddPropertyBtn").dispatchEvent(new Event("click"));
|
||||
document.querySelector(".newVertexComponent .rightPaneAddPropertyBtn").dispatchEvent(new Event("click"));
|
||||
expect(document.getElementsByClassName("valueCol").length).toBe(3);
|
||||
expect(document.getElementsByClassName("rightPaneTrashIcon").length).toBe(3);
|
||||
});
|
||||
|
||||
it("should remove property row when trash button is pressed", () => {
|
||||
document.querySelector(".newVertexComponent .rightPaneAddPropertyBtn").dispatchEvent(new Event("click"));
|
||||
document.querySelector(".newVertexComponent .rightPaneAddPropertyBtn").dispatchEvent(new Event("click"));
|
||||
|
||||
// Mark this one to delete
|
||||
const elts = document.querySelectorAll(".newVertexComponent .rightPaneTrashIconImg");
|
||||
elts[elts.length - 1].className += " deleteme";
|
||||
|
||||
document.querySelector(".newVertexComponent .rightPaneAddPropertyBtn").dispatchEvent(new Event("click"));
|
||||
document
|
||||
.querySelector(".newVertexComponent .rightPaneTrashIconImg.deleteme")
|
||||
.parentElement.dispatchEvent(new Event("click"));
|
||||
expect(document.getElementsByClassName("valueCol").length).toBe(2);
|
||||
expect(document.getElementsByClassName("rightPaneTrashIcon").length).toBe(2);
|
||||
expect(document.querySelectorAll(".newVertexComponent .rightPaneTrashIconImg.deleteme").length).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,74 +0,0 @@
|
||||
<div class="newVertexComponent" data-bind="setTemplateReady: true">
|
||||
<div class="newVertexForm">
|
||||
<div class="newVertexFormRow">
|
||||
<label for="VertexLabel" class="labelCol">Label</label>
|
||||
<input
|
||||
class="edgeInput"
|
||||
type="text"
|
||||
data-bind="textInput:$data.newVertexData().label, hasFocus: $data.firstFieldHasFocus"
|
||||
aria-label="Enter vertex label"
|
||||
role="textbox"
|
||||
tabindex="0"
|
||||
placeholder="Enter vertex label"
|
||||
autocomplete="off"
|
||||
id="VertexLabel"
|
||||
/>
|
||||
<div class="actionCol"></div>
|
||||
</div>
|
||||
|
||||
<!-- ko foreach:{ data:newVertexData().properties, as: 'property' } -->
|
||||
<div class="newVertexFormRow">
|
||||
<div class="labelCol">
|
||||
<input
|
||||
type="text"
|
||||
id="propertyKeyNewVertexPane"
|
||||
data-bind="textInput: property.key, attr: { 'aria-label': 'Enter key for property '+ ($index() + 1) }"
|
||||
placeholder="Key"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
<div class="valueCol">
|
||||
<input
|
||||
class="edgeInput"
|
||||
type="text"
|
||||
data-bind="textInput: property.values[0].value, , attr: { 'aria-label': 'Enter value for property '+ ($index() + 1) }"
|
||||
placeholder="Value"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<select
|
||||
class="typeSelect"
|
||||
required
|
||||
data-bind="options:$parent.propertyTypes, value:property.values[0].type, attr: { 'aria-label': property.values[0].type + ': for property '+ ($index() + 1) }"
|
||||
></select>
|
||||
</div>
|
||||
<div class="actionCol">
|
||||
<div
|
||||
class="rightPaneTrashIcon rightPaneBtns"
|
||||
data-bind="click:$parent.removeNewVertexProperty.bind($parent, $index()), event: { keypress: $parent.removeNewVertexPropertyKeyPress.bind($parent, $index()) }, attr: { 'aria-label': 'Remove property '+ ($index() + 1) }"
|
||||
tabindex="0"
|
||||
role="button"
|
||||
>
|
||||
<img class="refreshcol rightPaneTrashIconImg" src="/delete.svg" alt="Remove property" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /ko -->
|
||||
|
||||
<div class="newVertexFormRow">
|
||||
<span class="rightPaneAddPropertyBtnPadding">
|
||||
<span
|
||||
class="rightPaneAddPropertyBtn rightPaneBtns"
|
||||
id="addProperyNewVertexBtn"
|
||||
data-bind="click:onAddNewProperty, event: { keypress: onAddNewPropertyKeyPress }"
|
||||
tabindex="0"
|
||||
role="button"
|
||||
>
|
||||
<img class="refreshcol rightPaneAddPropertyImg" src="/Add-property.svg" alt="Add property" /> Add
|
||||
Property</span
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,97 @@
|
||||
@import "../../../../less/Common/Constants";
|
||||
|
||||
.newVertexComponent {
|
||||
padding: @LargeSpace 20px 20px 0px;
|
||||
width: 400px;
|
||||
|
||||
.newVertexForm {
|
||||
width: 100%;
|
||||
.flex-display();
|
||||
.flex-direction();
|
||||
|
||||
.newVertexFormRow {
|
||||
.flex-display();
|
||||
.flex-direction(@direction: row);
|
||||
padding: 4px 5px;
|
||||
|
||||
label {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.valueCol {
|
||||
flex-grow: 1;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.rightPaneAddPropertyBtnPadding {
|
||||
padding-top: 14px;
|
||||
}
|
||||
|
||||
.edgeLabel {
|
||||
padding-right: 41px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.actionCol {
|
||||
min-width: 30px;
|
||||
padding: 0px 4px;
|
||||
}
|
||||
|
||||
.labelCol {
|
||||
width: 72px;
|
||||
min-width: 72px;
|
||||
|
||||
input {
|
||||
max-width: 65px;
|
||||
padding-left: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.edgeInput {
|
||||
width: 100%;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.typeSelect {
|
||||
height: 23px;
|
||||
width: 70px;
|
||||
}
|
||||
|
||||
.rightPaneTrashIcon {
|
||||
padding: 4px 1px 0px 4px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.rightPaneTrashIconImg {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.rightPaneAddPropertyBtn {
|
||||
padding: 7px 7px 8px 8px;
|
||||
margin-left: -8px;
|
||||
}
|
||||
|
||||
.rightPaneBtns {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: @BaseLow;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: @AccentMediumLow;
|
||||
}
|
||||
}
|
||||
|
||||
.rightPaneAddPropertyImg {
|
||||
margin-right: 5px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.contentScroll {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
import { fireEvent, render, screen } from "@testing-library/react";
|
||||
import React from "react";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import { NewVertexComponent } from "./NewVertexComponent";
|
||||
|
||||
describe("New Vertex Component", () => {
|
||||
beforeEach(() => {
|
||||
const fakeNewVertexData: ViewModels.NewVertexData = {
|
||||
label: "",
|
||||
properties: [
|
||||
{
|
||||
key: "test1",
|
||||
values: [
|
||||
{
|
||||
value: "",
|
||||
type: "string",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
const props = {
|
||||
newVertexDataProp: fakeNewVertexData,
|
||||
partitionKeyPropertyProp: "test1",
|
||||
onChangeProp: (): void => undefined,
|
||||
};
|
||||
|
||||
render(<NewVertexComponent {...props} />);
|
||||
});
|
||||
|
||||
it("should render default prpoerty", () => {
|
||||
const fakeNewVertexData: ViewModels.NewVertexData = {
|
||||
label: "",
|
||||
properties: [],
|
||||
};
|
||||
const props = {
|
||||
newVertexDataProp: fakeNewVertexData,
|
||||
partitionKeyPropertyProp: "",
|
||||
onChangeProp: (): void => undefined,
|
||||
};
|
||||
|
||||
const { asFragment } = render(<NewVertexComponent {...props} />);
|
||||
expect(asFragment).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should render Add property button", () => {
|
||||
const span = screen.getByText("Add Property");
|
||||
expect(span).toBeDefined();
|
||||
});
|
||||
|
||||
it("should call onAddNewProperty method on span click", () => {
|
||||
const onAddNewProperty = jest.fn();
|
||||
const span = screen.getByText("Add Property");
|
||||
span.onclick = onAddNewProperty();
|
||||
fireEvent.click(span);
|
||||
expect(onAddNewProperty).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should call onAddNewPropertyKeyPress method on span keyPress", () => {
|
||||
const onAddNewPropertyKeyPress = jest.fn();
|
||||
const span = screen.getByText("Add Property");
|
||||
span.onkeypress = onAddNewPropertyKeyPress();
|
||||
fireEvent.keyPress(span, { key: "Enter", code: 13, charCode: 13 });
|
||||
expect(onAddNewPropertyKeyPress).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should call onLabelChange method on input change", () => {
|
||||
const onLabelChange = jest.fn();
|
||||
const input = screen.getByLabelText("Label");
|
||||
input.onchange = onLabelChange();
|
||||
fireEvent.change(input, { target: { value: "Label" } });
|
||||
expect(onLabelChange).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should call onKeyChange method on key input change", () => {
|
||||
const onKeyChange = jest.fn();
|
||||
const input = screen.queryByPlaceholderText("Key");
|
||||
input.onchange = onKeyChange();
|
||||
fireEvent.change(input, { target: { value: "pk1" } });
|
||||
expect(onKeyChange).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should call onValueChange method on value input change", () => {
|
||||
const onValueChange = jest.fn();
|
||||
const input = screen.queryByPlaceholderText("Value");
|
||||
input.onchange = onValueChange();
|
||||
fireEvent.change(input, { target: { value: "abc" } });
|
||||
expect(onValueChange).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should call removeNewVertexProperty method on remove button click", () => {
|
||||
const removeNewVertexProperty = jest.fn();
|
||||
const div = screen.getAllByRole("button");
|
||||
div[0].onclick = removeNewVertexProperty();
|
||||
fireEvent.click(div[0]);
|
||||
expect(removeNewVertexProperty).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should call removeNewVertexProperty method on remove button keyPress", () => {
|
||||
const removeNewVertexPropertyKeyPress = jest.fn();
|
||||
const div = screen.getAllByRole("button");
|
||||
div[0].onkeypress = removeNewVertexPropertyKeyPress();
|
||||
fireEvent.keyPress(div[0], { key: "Enter", code: 13, charCode: 13 });
|
||||
expect(removeNewVertexPropertyKeyPress).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should call onTypeChange method on type dropdown change", () => {
|
||||
const DOWN_ARROW = { keyCode: 40 };
|
||||
const onTypeChange = jest.fn();
|
||||
const dropdown = screen.getByRole("listbox");
|
||||
dropdown.onclick = onTypeChange();
|
||||
dropdown.onkeydown = onTypeChange();
|
||||
|
||||
fireEvent.keyDown(screen.getByRole("listbox"), DOWN_ARROW);
|
||||
fireEvent.click(screen.getByText(/number/));
|
||||
expect(onTypeChange).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -1,99 +0,0 @@
|
||||
import * as ko from "knockout";
|
||||
import { EditorNodePropertiesComponent } from "../GraphExplorerComponent/EditorNodePropertiesComponent";
|
||||
import { NewVertexData, InputProperty } from "../../../Contracts/ViewModels";
|
||||
import { WaitsForTemplateViewModel } from "../../WaitsForTemplateViewModel";
|
||||
import * as Constants from "../../../Common/Constants";
|
||||
import template from "./NewVertexComponent.html";
|
||||
|
||||
/**
|
||||
* Parameters for this component
|
||||
*/
|
||||
export interface NewVertexParams {
|
||||
// Data to be edited by the component
|
||||
newVertexData: ko.Observable<NewVertexData>;
|
||||
partitionKeyProperty: ko.Observable<string>;
|
||||
firstFieldHasFocus?: ko.Observable<boolean>;
|
||||
|
||||
/**
|
||||
* Callback triggered when the template is bound to the component (for testing purposes)
|
||||
*/
|
||||
onTemplateReady?: () => void;
|
||||
}
|
||||
|
||||
export class NewVertexViewModel extends WaitsForTemplateViewModel {
|
||||
private static readonly DEFAULT_PROPERTY_TYPE = "string";
|
||||
|
||||
private newVertexData: ko.Observable<NewVertexData>;
|
||||
private firstFieldHasFocus: ko.Observable<boolean>;
|
||||
private propertyTypes: string[];
|
||||
|
||||
public constructor(params: NewVertexParams) {
|
||||
super();
|
||||
super.onTemplateReady((isTemplateReady: boolean) => {
|
||||
if (isTemplateReady && params.onTemplateReady) {
|
||||
params.onTemplateReady();
|
||||
}
|
||||
});
|
||||
|
||||
this.newVertexData =
|
||||
params.newVertexData ||
|
||||
ko.observable({
|
||||
label: "",
|
||||
properties: <InputProperty[]>[],
|
||||
});
|
||||
this.firstFieldHasFocus = params.firstFieldHasFocus || ko.observable(false);
|
||||
this.propertyTypes = EditorNodePropertiesComponent.VERTEX_PROPERTY_TYPES;
|
||||
if (params.partitionKeyProperty) {
|
||||
params.partitionKeyProperty.subscribe((newKeyProp: string) => {
|
||||
if (!newKeyProp) {
|
||||
return;
|
||||
}
|
||||
this.addNewVertexProperty(newKeyProp);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public onAddNewProperty() {
|
||||
this.addNewVertexProperty();
|
||||
document.getElementById("propertyKeyNewVertexPane").focus();
|
||||
}
|
||||
|
||||
public onAddNewPropertyKeyPress = (source: any, event: KeyboardEvent): boolean => {
|
||||
if (event.keyCode === Constants.KeyCodes.Enter || event.keyCode === Constants.KeyCodes.Space) {
|
||||
this.onAddNewProperty();
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
public addNewVertexProperty(key?: string) {
|
||||
let ap = this.newVertexData().properties;
|
||||
ap.push({ key: key || "", values: [{ value: "", type: NewVertexViewModel.DEFAULT_PROPERTY_TYPE }] });
|
||||
this.newVertexData.valueHasMutated();
|
||||
}
|
||||
|
||||
public removeNewVertexProperty(index: number) {
|
||||
let ap = this.newVertexData().properties;
|
||||
ap.splice(index, 1);
|
||||
this.newVertexData.valueHasMutated();
|
||||
document.getElementById("addProperyNewVertexBtn").focus();
|
||||
}
|
||||
|
||||
public removeNewVertexPropertyKeyPress = (index: number, source: any, event: KeyboardEvent): boolean => {
|
||||
if (event.keyCode === Constants.KeyCodes.Enter || event.keyCode === Constants.KeyCodes.Space) {
|
||||
this.removeNewVertexProperty(index);
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class for ko component registration
|
||||
*/
|
||||
export const NewVertexComponent = {
|
||||
viewModel: NewVertexViewModel,
|
||||
template,
|
||||
};
|
||||
213
src/Explorer/Graph/NewVertexComponent/NewVertexComponent.tsx
Normal file
213
src/Explorer/Graph/NewVertexComponent/NewVertexComponent.tsx
Normal file
@@ -0,0 +1,213 @@
|
||||
import { Dropdown, IDropdownOption, Stack, TextField } from "office-ui-fabric-react";
|
||||
import React, { FunctionComponent, useRef, useState } from "react";
|
||||
import AddIcon from "../../../../images/Add-property.svg";
|
||||
import DeleteIcon from "../../../../images/delete.svg";
|
||||
import { NormalizedEventKey } from "../../../Common/Constants";
|
||||
import { GremlinPropertyValueType, InputPropertyValueTypeString, NewVertexData } from "../../../Contracts/ViewModels";
|
||||
import { EditorNodePropertiesComponent } from "../GraphExplorerComponent/EditorNodePropertiesComponent";
|
||||
import "./NewVertexComponent.less";
|
||||
export interface INewVertexComponentProps {
|
||||
newVertexDataProp: NewVertexData;
|
||||
partitionKeyPropertyProp: string;
|
||||
onChangeProp: (labelData: NewVertexData) => void;
|
||||
}
|
||||
|
||||
export const NewVertexComponent: FunctionComponent<INewVertexComponentProps> = ({
|
||||
newVertexDataProp,
|
||||
partitionKeyPropertyProp,
|
||||
onChangeProp,
|
||||
}: INewVertexComponentProps): JSX.Element => {
|
||||
const DEFAULT_PROPERTY_TYPE = "string";
|
||||
const [newVertexData, setNewVertexData] = useState<NewVertexData>(
|
||||
newVertexDataProp || {
|
||||
label: "",
|
||||
properties: [
|
||||
{
|
||||
key: partitionKeyPropertyProp,
|
||||
values: [{ value: "", type: DEFAULT_PROPERTY_TYPE }],
|
||||
},
|
||||
],
|
||||
}
|
||||
);
|
||||
|
||||
const propertyTypes: string[] = EditorNodePropertiesComponent.VERTEX_PROPERTY_TYPES;
|
||||
const input = useRef(undefined);
|
||||
|
||||
const onAddNewProperty = () => {
|
||||
addNewVertexProperty();
|
||||
setTimeout(() => {
|
||||
input.current.focus();
|
||||
}, 100);
|
||||
};
|
||||
|
||||
const onAddNewPropertyKeyPress = (event: React.KeyboardEvent) => {
|
||||
if (event.key === NormalizedEventKey.Space || event.key === NormalizedEventKey.Enter) {
|
||||
onAddNewProperty();
|
||||
event.stopPropagation();
|
||||
}
|
||||
};
|
||||
|
||||
const addNewVertexProperty = () => {
|
||||
let key: string;
|
||||
const ap = newVertexData.properties;
|
||||
if (ap.length === 0) {
|
||||
key = partitionKeyPropertyProp;
|
||||
}
|
||||
ap.push({
|
||||
key: key || "",
|
||||
values: [{ value: "", type: DEFAULT_PROPERTY_TYPE }],
|
||||
});
|
||||
setNewVertexData((prevData) => ({
|
||||
...prevData,
|
||||
properties: ap,
|
||||
}));
|
||||
onChangeProp(newVertexData);
|
||||
};
|
||||
|
||||
const removeNewVertexProperty = (event?: React.MouseEvent<HTMLDivElement>, index?: number) => {
|
||||
const ap = newVertexData.properties;
|
||||
ap.splice(index, 1);
|
||||
setNewVertexData((prevData) => ({
|
||||
...prevData,
|
||||
properties: ap,
|
||||
}));
|
||||
onChangeProp(newVertexData);
|
||||
document.getElementById("addProperyNewVertexBtn").focus();
|
||||
};
|
||||
|
||||
const removeNewVertexPropertyKeyPress = (event: React.KeyboardEvent<HTMLDivElement>, index: number) => {
|
||||
if (event.key === NormalizedEventKey.Space || event.key === NormalizedEventKey.Enter) {
|
||||
removeNewVertexProperty(undefined, index);
|
||||
event.stopPropagation();
|
||||
}
|
||||
};
|
||||
|
||||
const onLabelChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setNewVertexData((prevData) => ({
|
||||
...prevData,
|
||||
label: event.target.value,
|
||||
}));
|
||||
onChangeProp(newVertexData);
|
||||
};
|
||||
|
||||
const onKeyChange = (event: React.ChangeEvent<HTMLInputElement>, index: number) => {
|
||||
const newState = { ...newVertexData };
|
||||
newState.properties[index].key = event.target.value;
|
||||
setNewVertexData(newState);
|
||||
onChangeProp(newVertexData);
|
||||
};
|
||||
|
||||
const onValueChange = (event: React.ChangeEvent<HTMLInputElement>, index: number) => {
|
||||
const newState = { ...newVertexData };
|
||||
newState.properties[index].values[0].value = event.target.value as GremlinPropertyValueType;
|
||||
setNewVertexData(newState);
|
||||
onChangeProp(newVertexData);
|
||||
};
|
||||
|
||||
const onTypeChange = (option: string, index: number) => {
|
||||
const newState = { ...newVertexData };
|
||||
if (newState.properties[index]) {
|
||||
newState.properties[index].values[0].type = option as InputPropertyValueTypeString;
|
||||
setNewVertexData(newState);
|
||||
onChangeProp(newVertexData);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<div className="newVertexComponent">
|
||||
<div className="newVertexForm">
|
||||
<div className="newVertexFormRow">
|
||||
<TextField
|
||||
label="Label"
|
||||
className="edgeInput"
|
||||
type="text"
|
||||
ariaLabel="Enter vertex label"
|
||||
role="textbox"
|
||||
tabIndex={0}
|
||||
placeholder="Enter vertex label"
|
||||
autoComplete="off"
|
||||
id="VertexLabel"
|
||||
value={newVertexData.label}
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
onLabelChange(event);
|
||||
}}
|
||||
/>
|
||||
<div className="actionCol"></div>
|
||||
</div>
|
||||
{newVertexData.properties.map((data, index) => {
|
||||
return (
|
||||
<div key={index} className="newVertexFormRow">
|
||||
<div className="labelCol">
|
||||
<TextField
|
||||
className="edgeInput"
|
||||
type="text"
|
||||
id="propertyKeyNewVertexPane"
|
||||
componentRef={input}
|
||||
placeholder="Key"
|
||||
autoComplete="off"
|
||||
aria-label={`Enter value for propery ${index + 1}`}
|
||||
value={data.key}
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => onKeyChange(event, index)}
|
||||
/>
|
||||
</div>
|
||||
<div className="valueCol">
|
||||
<TextField
|
||||
className="edgeInput"
|
||||
type="text"
|
||||
placeholder="Value"
|
||||
autoComplete="off"
|
||||
aria-label={`Enter value for propery ${index + 1}`}
|
||||
value={data.values[0].value.toString()}
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => onValueChange(event, index)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Dropdown
|
||||
role="listbox"
|
||||
placeholder="Select an option"
|
||||
defaultSelectedKey={data.values[0].type}
|
||||
style={{ width: 100 }}
|
||||
options={propertyTypes.map((type) => ({
|
||||
key: type,
|
||||
text: type,
|
||||
}))}
|
||||
onChange={(_, options: IDropdownOption) => onTypeChange(options.key.toString(), index)}
|
||||
/>
|
||||
</div>
|
||||
<div className="actionCol">
|
||||
<div
|
||||
className="rightPaneTrashIcon rightPaneBtns"
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
onClick={(event: React.MouseEvent<HTMLDivElement>) => removeNewVertexProperty(event, index)}
|
||||
onKeyPress={(event: React.KeyboardEvent<HTMLDivElement>) =>
|
||||
removeNewVertexPropertyKeyPress(event, index)
|
||||
}
|
||||
>
|
||||
<img className="refreshcol rightPaneTrashIconImg" src={DeleteIcon} alt="Remove property" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
<div className="newVertexFormRow">
|
||||
<span className="rightPaneAddPropertyBtnPadding">
|
||||
<span
|
||||
className="rightPaneAddPropertyBtn rightPaneBtns"
|
||||
id="addProperyNewVertexBtn"
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
onClick={onAddNewProperty}
|
||||
onKeyPress={(event: React.KeyboardEvent<HTMLSpanElement>) => onAddNewPropertyKeyPress(event)}
|
||||
>
|
||||
<img className="refreshcol rightPaneAddPropertyImg" src={AddIcon} alt="Add property" /> Add Property
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`New Vertex Component should render default prpoerty 1`] = `[Function]`;
|
||||
@@ -1,97 +0,0 @@
|
||||
@import "../../../../less/Common/Constants";
|
||||
|
||||
.newVertexComponent {
|
||||
padding: @LargeSpace 20px 20px 0px;
|
||||
width: 400px;
|
||||
|
||||
.newVertexForm {
|
||||
width: 100%;
|
||||
.flex-display();
|
||||
.flex-direction();
|
||||
|
||||
.newVertexFormRow {
|
||||
.flex-display();
|
||||
.flex-direction(@direction: row);
|
||||
padding: 4px 5px;
|
||||
|
||||
label {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.valueCol {
|
||||
flex-grow: 1;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.rightPaneAddPropertyBtnPadding {
|
||||
padding-top: 14px;
|
||||
}
|
||||
|
||||
.edgeLabel {
|
||||
padding-right: 41px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.actionCol {
|
||||
min-width: 30px;
|
||||
padding: 0px 4px;
|
||||
}
|
||||
|
||||
.labelCol {
|
||||
width: 72px;
|
||||
min-width: 72px;
|
||||
|
||||
input {
|
||||
max-width: 65px;
|
||||
padding-left: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.edgeInput {
|
||||
width: 100%;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.typeSelect {
|
||||
height: 23px;
|
||||
width: 70px;
|
||||
}
|
||||
|
||||
.rightPaneTrashIcon {
|
||||
padding: 4px 1px 0px 4px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.rightPaneTrashIconImg {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.rightPaneAddPropertyBtn {
|
||||
padding: 7px 7px 8px 8px;
|
||||
margin-left: -8px;
|
||||
}
|
||||
|
||||
.rightPaneBtns {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: @BaseLow ;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: @AccentMediumLow;
|
||||
}
|
||||
}
|
||||
|
||||
.rightPaneAddPropertyImg {
|
||||
margin-right: 5px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.contentScroll {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user