Skip to content

Commit

Permalink
Merge branch 'master' into (eclipsesource#35)Ecore-File-Generation
Browse files Browse the repository at this point in the history
  • Loading branch information
sgraband committed Nov 28, 2019
2 parents 140d5fd + 1e68385 commit 7bf93bb
Show file tree
Hide file tree
Showing 15 changed files with 549 additions and 42 deletions.
68 changes: 57 additions & 11 deletions client/sprotty-ecore/css/diagram.css
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
:root {
--sprotty-background: var(--theia-layout-color3);
--sprotty-edge: var(--theia-ui-font-color1);
--sprotty-border: var(--theia-border-color2);
}

svg {
margin-top: 15px;
width: 100%;
height: 500px;
border-style: solid;
border-width: 1px;
border-color: #bbb;
border-color: var(--sprotty-border);
}

.label-edit input {
background: rgba(255, 255, 255, 0.5);
color: black;
}

.sprotty {
Expand All @@ -14,7 +25,7 @@ svg {
.sprotty-graph {
font-size: 15pt;
height: 100%;
background: #fff
background: var(--sprotty-background);
}

.ecore-node {
Expand Down Expand Up @@ -94,10 +105,18 @@ svg {

.ecore-edge {
fill: none;
stroke: black;
stroke: var(--sprotty-edge);
stroke-width: 2px;
}

.ecore-edge>.sprotty-label {
stroke-width: 0;
width: inherit;
fill: var(--sprotty-edge);
font-weight: inherit;
font-size: 100%;
}

.feedback-edge {
stroke-width: 2px;
stroke: black;
Expand Down Expand Up @@ -146,23 +165,19 @@ svg {
}

.ecore-edge>.triangle.inheritance {
fill: white;
}

.ecore-edge.inheritance {
stroke: #888888;
fill: var(--sprotty-background);
}

.ecore-edge.aggregation .ecore-edge.composition {
stroke: black;
stroke: var(--sprotty-edge);
}

.ecore-edge>.diamond.aggregation {
fill: white;
fill: var(--sprotty-background);
}

.ecore-edge>.diamond.composition {
fill: black;
fill: var(--sprotty-edge);
}

.ecore-edge.selected {
Expand All @@ -181,3 +196,34 @@ svg {
.italic {
font-style: italic;
}

.autocomplete {
position: relative;
display: inline-block;
}
.autocomplete-items {
position: absolute;
border: 1px solid #d4d4d4;
color: black;
border-bottom: none;
border-top: none;
z-index: 99;
top: 100%;
left: 0;
right: 0;
overflow-y: scroll;
}
.autocomplete-items div {
padding: 10px;
cursor: pointer;
background-color: #fff;
border-bottom: 1px solid #d4d4d4;
}
.autocomplete-items div:hover {
background-color: #e9e9e9;
}
.autocomplete-active {
background-color: DodgerBlue !important;
color: #ffffff;
}

13 changes: 8 additions & 5 deletions client/sprotty-ecore/src/di.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,18 @@ import {
} from "@glsp/sprotty-client/lib";
import executeCommandModule from "@glsp/sprotty-client/lib/features/execute/di.config";
import { Container, ContainerModule } from "inversify";

import {EditLabelUIAutocomplete} from "./features/edit-label-autocomplete";
import { EditLabelUI } from "sprotty/lib";
import { LabelSelectionFeedback } from "./feedback";
import { Icon, LabeledNode, SEditableLabel, SLabelNode } from "./model";
import {ArrowEdge, CompositionEdge, Icon, InheritanceEdge, LabeledNode, SEditableLabel, SLabelNode} from "./model";
import { ArrowEdgeView, ClassNodeView, CompositionEdgeView, IconView, InheritanceEdgeView, LabelNodeView } from "./views";

export default (containerId: string) => {
const classDiagramModule = new ContainerModule((bind, unbind, isBound, rebind) => {
rebind(TYPES.ILogger).to(ConsoleLogger).inSingletonScope();
rebind(TYPES.LogLevel).toConstantValue(LogLevel.info);
rebind(EditLabelUI).to(EditLabelUIAutocomplete);

const context = { bind, unbind, isBound, rebind };
bind(TYPES.IVNodePostprocessor).to(LabelSelectionFeedback);
configureModelElement(context, 'graph', GLSPGraph, SGraphView);
Expand All @@ -96,9 +99,9 @@ export default (containerId: string) => {
configureModelElement(context, 'html', HtmlRoot, HtmlRootView);
configureModelElement(context, 'routing-point', SRoutingHandle, SRoutingHandleView);
configureModelElement(context, 'volatile-routing-point', SRoutingHandle, SRoutingHandleView);
configureModelElement(context, 'edge:reference', SEdge, ArrowEdgeView);
configureModelElement(context, 'edge:inheritance', SEdge, InheritanceEdgeView);
configureModelElement(context, 'edge:composition', SEdge, CompositionEdgeView);
configureModelElement(context, 'edge:reference', ArrowEdge, ArrowEdgeView);
configureModelElement(context, 'edge:inheritance', InheritanceEdge, InheritanceEdgeView);
configureModelElement(context, 'edge:composition', CompositionEdge, CompositionEdgeView);
configureModelElement(context, 'edge', SEdge, PolylineEdgeView);
configureViewerOptions(context, {
needsClientLayout: true,
Expand Down
190 changes: 190 additions & 0 deletions client/sprotty-ecore/src/features/edit-label-autocomplete.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/*******************************************************************************
* Copyright (c) 2019 EclipseSource and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
******************************************************************************/
import { RequestAction, ResponseAction, generateRequestId, SModelRoot } from "sprotty/lib";
import { inject, injectable } from "inversify";
import { matchesKeystroke } from "sprotty/lib/utils/keyboard";
import { EditLabelUI } from "sprotty/lib";
import { GLSPActionDispatcher, TYPES } from "@glsp/sprotty-client/lib";

export class AttributeTypesAction implements RequestAction<ReturnAttributeTypesAction> {
static readonly KIND = 'getAttributeTypes';
kind = AttributeTypesAction.KIND;
constructor(public readonly requestId: string = generateRequestId()) { }
}

export class ReturnAttributeTypesAction implements ResponseAction {
static readonly KIND = 'returnAttributeTypes';
kind = ReturnAttributeTypesAction.KIND;
types: string[];
constructor(public readonly actions: string[], public readonly responseId: string = '') {
this.types = actions;
}
}

@injectable()
export class EditLabelUIAutocomplete extends EditLabelUI {

protected showAutocomplete: boolean = false;
protected outerDiv: HTMLElement;
protected listContainer: HTMLElement;
protected currentFocus: number;
protected types: string[] = [];


constructor(@inject(TYPES.IActionDispatcher) protected actionDispatcher: GLSPActionDispatcher) {
super();
}

protected initializeContents(containerElement: HTMLElement) {
this.outerDiv = containerElement;
super.initializeContents(containerElement);
}

protected handleKeyDown(event: KeyboardEvent) {
super.handleKeyDown(event);

if (matchesKeystroke(event, 'Space', 'ctrl')) {
this.showAutocomplete = true;
if (this.isAutoCompleteEnabled()) {
this.createAutocomplete();
}
}

this.updateAutocomplete(event);
}

protected validateLabelIfContentChange(event: KeyboardEvent, value: string) {
if (this.isAutoCompleteEnabled() && this.previousLabelContent !== value) {
// recreate autocomplete list if value changed
this.createAutocomplete();
}
super.validateLabelIfContentChange(event, value);
}

protected updateAutocomplete(event: KeyboardEvent) {
if (matchesKeystroke(event, 'ArrowDown')) {
this.currentFocus++;
this.addActive();
} else if (matchesKeystroke(event, 'ArrowUp')) {
this.currentFocus--;
this.addActive();
} else if (matchesKeystroke(event, 'Enter')) {
event.preventDefault();
if (this.currentFocus > -1) {
if (this.listContainer) {
const children = this.listContainer.children;
(<HTMLElement>children[this.currentFocus]).click();
}
}
}
}

protected createAutocomplete() {
const input: String = this.inputElement.value;
let val: String = "";
if (input.includes(":")) {
val = input.split(":")[1].trim();
}

this.closeAllLists();
this.currentFocus = -1;

this.listContainer = document.createElement("div");
this.listContainer.setAttribute("id", this.inputElement.id + "autocomplete-list");
this.listContainer.setAttribute("class", "autocomplete-items");
this.outerDiv.appendChild(this.listContainer);

// create autocomlete items starting with input
for (let i = 0; i < this.types.length; i++) {
if (this.types[i].substr(0, val.length).toLowerCase() === val.toLowerCase()) {
const element = document.createElement("div");
element.innerHTML = "<strong>" + this.types[i].substr(0, val.length) + "</strong>";
element.innerHTML += this.types[i].substr(val.length);
element.innerHTML += "<input type='hidden' value='" + this.types[i] + "'>";
element.addEventListener("click", e => {
// change the type of the label
let name: String = this.inputElement.value;
if (name.includes(":")) {
name = name.split(":")[0];
}
this.inputElement.value = name + ": " + element.getElementsByTagName("input")[0].value;
this.closeAllLists();
});
this.listContainer.appendChild(element);
}
}

// set max height for scrolling
const parent = this.outerDiv.parentElement;
if (parent) {
const parentHeight = parent.offsetHeight;
const parentPosY = parent.offsetTop;
const posY = this.outerDiv.offsetTop + this.inputElement.offsetHeight;
const maxHeight = parentHeight - (posY - parentPosY);
this.listContainer.style.maxHeight = `${maxHeight}px`;
}
}

protected addActive() {
if (!this.listContainer) return;
this.removeActive();
const children = this.listContainer.children;
if (children.length > 0) {
if (this.currentFocus >= children.length) this.currentFocus = 0;
if (this.currentFocus < 0) this.currentFocus = (children.length - 1);
children[this.currentFocus].classList.add("autocomplete-active");
}
}

protected removeActive() {
const children = this.listContainer.children;
for (let i = 0; i < children.length; i++) {
children[i].classList.remove("autocomplete-active");
}
}

protected closeAllLists() {
const x = this.outerDiv.getElementsByClassName("autocomplete-items");
for (let i = 0; i < x.length; i++) {
this.outerDiv.removeChild(x[i]);
}
}

protected onBeforeShow(containerElement: HTMLElement, root: Readonly<SModelRoot>, ...contextElementIds: string[]) {
super.onBeforeShow(containerElement, root, ...contextElementIds);

// request possible element types
this.actionDispatcher.requestUntil(new AttributeTypesAction()).then(response => {
if (response) {
const action: ReturnAttributeTypesAction = <ReturnAttributeTypesAction> response;
this.types = action.types;
}
});
}

protected isAutoCompleteEnabled() {
if (this.label) {
return this.label.type === "node:attribute" && this.showAutocomplete;
}
return false;
}

public hide() {
super.hide();
this.showAutocomplete = false;
this.closeAllLists();
}
}
15 changes: 14 additions & 1 deletion client/sprotty-ecore/src/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ import {
SLabel,
SShapeElement,
WithEditableLabel,
withEditLabelFeature
withEditLabelFeature,
SEdge
} from "sprotty/lib";


Expand Down Expand Up @@ -84,3 +85,15 @@ export class SLabelNode extends SLabel implements EditableLabel {
return (feature === selectFeature || feature === editLabelFeature || feature === popupFeature || feature === deletableFeature || feature === hoverFeedbackFeature || super.hasFeature(feature));
}
}

export class ArrowEdge extends SEdge {
public readonly targetAnchorCorrection = 3.3;
}

export class CompositionEdge extends SEdge {
public readonly sourceAnchorCorrection = 3.0;
}

export class InheritanceEdge extends SEdge {
public readonly targetAnchorCorrection = 2.3;
}
Loading

0 comments on commit 7bf93bb

Please sign in to comment.