Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 20 additions & 26 deletions Frontend/WebEditor/src/features/dfdElements/AssignmentLanguage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
Token,
WordCompletion,
} from "../constraintMenu/AutoCompletion";
import { SModelElementImpl, SModelRootImpl, SParentElementImpl, SPortImpl } from "sprotty";
import { SModelElementImpl, SParentElementImpl, SPortImpl } from "sprotty";
import { LabelTypeRegistry } from "../labels/labelTypeRegistry";
import { DfdNodeImpl } from "./nodes";

Expand Down Expand Up @@ -148,14 +148,14 @@ export class ReplaceAutoCompleteTree extends AutoCompleteTree {

export namespace TreeBuilder {
export function buildTree(
model: SModelRootImpl,
labelTypeRegistry: LabelTypeRegistry,
port?: SPortImpl,
): AutoCompleteNode<WordOrReplacableWord>[] {
return [
buildSetOrUnsetStatement(labelTypeRegistry, "set"),
buildSetOrUnsetStatement(labelTypeRegistry, "unset"),
buildForwardStatement(model),
buildAssignStatement(labelTypeRegistry, model),
buildForwardStatement(port),
buildAssignStatement(labelTypeRegistry, port),
];
}

Expand All @@ -173,9 +173,9 @@ export namespace TreeBuilder {
};
}

function buildForwardStatement(model: SModelRootImpl) {
function buildForwardStatement(port?: SPortImpl) {
const inputNode: AutoCompleteNode = {
word: new InputListWord(model),
word: new InputListWord(port),
children: [],
};
return {
Expand All @@ -186,20 +186,20 @@ export namespace TreeBuilder {

function buildAssignStatement(
labelTypeRegistry: LabelTypeRegistry,
model: SModelRootImpl,
port?: SPortImpl,
): AutoCompleteNode<WordOrReplacableWord> {
const fromNode: AutoCompleteNode = {
word: new ConstantWord("from"),
children: [
{
word: new InputListWord(model),
word: new InputListWord(port),
children: [],
},
],
};
const ifNode: AutoCompleteNode = {
word: new ConstantWord("if"),
children: buildCondition(model, labelTypeRegistry, fromNode),
children: buildCondition(labelTypeRegistry, fromNode, port),
};
return {
word: new ConstantWord("assign"),
Expand All @@ -212,7 +212,7 @@ export namespace TreeBuilder {
};
}

function buildCondition(model: SModelRootImpl, labelTypeRegistry: LabelTypeRegistry, nextNode: AutoCompleteNode) {
function buildCondition(labelTypeRegistry: LabelTypeRegistry, nextNode: AutoCompleteNode, port?: SPortImpl) {
const connectors: AutoCompleteNode[] = ["&&", "||"].map((o) => ({
word: new ConstantWord(o),
children: [],
Expand All @@ -221,7 +221,7 @@ export namespace TreeBuilder {
const expressors: AutoCompleteNode[] = [
new ConstantWord("TRUE"),
new ConstantWord("FALSE"),
new InputLabelWord(model, labelTypeRegistry),
new InputLabelWord(labelTypeRegistry, port),
].map((e) => ({
word: e,
children: [...connectors, nextNode],
Expand All @@ -236,20 +236,14 @@ export namespace TreeBuilder {
}

abstract class InputAwareWord {
constructor(private model: SModelRootImpl) {}
constructor(private port?: SPortImpl) {}

protected getAvailableInputs(): string[] {
const selectedPorts = this.getSelectedPorts(this.model);
if (selectedPorts.length === 0) {
return [];
}
return selectedPorts.flatMap((port) => {
const parent = port.parent;
if (!(parent instanceof DfdNodeImpl)) {
return [];
}
const parent = this.port?.parent;
if (parent && parent instanceof DfdNodeImpl) {
return parent.getAvailableInputs().filter((input) => input !== undefined) as string[];
});
}
return [];
}

private getSelectedPorts(node: SModelElementImpl): SPortImpl[] {
Expand Down Expand Up @@ -386,8 +380,8 @@ class InputWord extends InputAwareWord implements ReplaceableAbstractWord {

class InputListWord implements ReplaceableAbstractWord {
private inputWord: InputWord;
constructor(model: SModelRootImpl) {
this.inputWord = new InputWord(model);
constructor(port?: SPortImpl) {
this.inputWord = new InputWord(port);
}

completionOptions(word: string): WordCompletion[] {
Expand Down Expand Up @@ -426,8 +420,8 @@ class InputLabelWord implements ReplaceableAbstractWord {
private inputWord: InputWord;
private labelWord: LabelWord;

constructor(model: SModelRootImpl, labelTypeRegistry: LabelTypeRegistry) {
this.inputWord = new InputWord(model);
constructor(labelTypeRegistry: LabelTypeRegistry, port?: SPortImpl) {
this.inputWord = new InputWord(port);
this.labelWord = new LabelWord(labelTypeRegistry);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
SEdgeImpl,
SLabelImpl,
SModelElementImpl,
SModelRootImpl,
SParentElementImpl,
TYPES,
} from "sprotty";
Expand Down Expand Up @@ -87,8 +86,8 @@ export class DFDBehaviorRefactorer {
this.logger.log(this, "Changed labels", changedLabels);

const model = await this.commandStack.executeAll([]);
const tree = new ReplaceAutoCompleteTree(TreeBuilder.buildTree(model, this.registry));
this.traverseDfdOutputPorts(model, (port) => {
const tree = new ReplaceAutoCompleteTree(TreeBuilder.buildTree(this.registry, port));
this.renameLabelsForPort(port, changedLabels, tree);
});

Expand Down Expand Up @@ -118,7 +117,6 @@ export class DFDBehaviorRefactorer {
port: DfdInputPortImpl,
oldLabelText: string,
newLabelText: string,
root: SModelRootImpl,
): Map<string, string> {
label.text = oldLabelText;
const oldInputName = port.getName();
Expand All @@ -131,7 +129,7 @@ export class DFDBehaviorRefactorer {
return behaviorChanges;
}

const tree = new ReplaceAutoCompleteTree(TreeBuilder.buildTree(root, this.registry));
const tree = new ReplaceAutoCompleteTree(TreeBuilder.buildTree(this.registry, port));

node.children.forEach((child) => {
if (!(child instanceof DfdOutputPortImpl)) {
Expand Down Expand Up @@ -209,7 +207,6 @@ export class RefactorInputNameInDFDBehaviorCommand extends Command {
port,
oldInputName,
newInputName,
context.root,
);
behaviorChanges.forEach((updatedBehavior, id) => {
const port = context.root.index.getById(id);
Expand Down
4 changes: 2 additions & 2 deletions Frontend/WebEditor/src/features/dfdElements/elementStyles.css
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@
/* Ports */

.sprotty-port rect {
stroke: var(--color-foreground);
fill: color-mix(in srgb, var(--color-primary), var(--color-background) 25%);
stroke: var(--port-border, var(--color-foreground));
fill: color-mix(in srgb, var(--port-color, var(--color-primary)), var(--color-background) 25%);
stroke-width: 0.5;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ export class OutputPortEditUI extends AbstractUIExtension implements Switchable
readOnly: this.editorModeController?.isReadOnly() ?? false,
});

this.tree = new ReplaceAutoCompleteTree(TreeBuilder.buildTree(root, this.labelTypeRegistry));
this.tree = new ReplaceAutoCompleteTree(TreeBuilder.buildTree(this.labelTypeRegistry, this.port));

// Validation of loaded behavior text.
this.validateBehavior();
Expand Down
49 changes: 45 additions & 4 deletions Frontend/WebEditor/src/features/dfdElements/ports.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ import {
} from "sprotty";
import { Bounds, SPort } from "sprotty-protocol";
import { injectable } from "inversify";
import { VNode } from "snabbdom";
import { VNode, VNodeStyle } from "snabbdom";
import { ArrowEdgeImpl } from "./edges";
import { AutoCompleteTree } from "../constraintMenu/AutoCompletion";
import { TreeBuilder } from "./AssignmentLanguage";
import { labelTypeRegistry } from "../..";

const defaultPortFeatures = [...SPortImpl.DEFAULT_FEATURES, moveFeature, deletableFeature];
const portSize = 7;
Expand Down Expand Up @@ -93,7 +96,14 @@ export interface DfdOutputPort extends SPort {
export class DfdOutputPortImpl extends SPortImpl {
static readonly DEFAULT_FEATURES = [...defaultPortFeatures, withEditLabelFeature];

behavior: string = "";
private _behavior: string = "";
private tree: AutoCompleteTree;
private validBehavior: boolean = true;

constructor() {
super();
this.tree = new AutoCompleteTree(TreeBuilder.buildTree(labelTypeRegistry, this));
}

override get bounds(): Bounds {
return {
Expand All @@ -117,19 +127,50 @@ export class DfdOutputPortImpl extends SPortImpl {
// Only allow edges from this port outwards
return role === "source";
}

/**
* Generates the per-node inline style object for the view.
*/
geViewStyleObject(): VNodeStyle {
const style: VNodeStyle = {
opacity: this.opacity.toString(),
};
if (!labelTypeRegistry) return style;

if (!this.validBehavior) {
style["--port-border"] = "#ff0000";
style["--port-color"] = "#ff6961";
}

return style;
}

get behavior(): string {
return this._behavior;
}

set behavior(value: string) {
this._behavior = value;
if (value === "") {
this.validBehavior = true;
return;
}
const errors = this.tree.verify(this.behavior.split("\n"));
this.validBehavior = errors.length === 0;
}
}

@injectable()
export class DfdOutputPortView extends ShapeView {
render(node: Readonly<SPortImpl>, context: RenderingContext): VNode | undefined {
render(node: Readonly<DfdOutputPortImpl>, context: RenderingContext): VNode | undefined {
if (!this.isVisible(node, context)) {
return undefined;
}

const { width, height } = node.bounds;

return (
<g class-sprotty-port={true} class-selected={node.selected} style={{ opacity: node.opacity.toString() }}>
<g class-sprotty-port={true} class-selected={node.selected} style={node.geViewStyleObject()}>
<rect x="0" y="0" width={width} height={height} />
<text x={width / 2} y={height / 2} class-port-text={true}>
O
Expand Down
2 changes: 2 additions & 0 deletions Frontend/WebEditor/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { settingsModule } from "./features/settingsMenu/di.config";
import { LoadDiagramAction } from "./features/serialize/load";
import { commandPaletteModule } from "./features/commandPalette/di.config";
import { LoadingIndicator } from "./common/loadingIndicator";
import { LabelTypeRegistry } from "./features/labels/labelTypeRegistry";

const container = new Container();

Expand Down Expand Up @@ -77,6 +78,7 @@ export function setModelFileName(name: string): void {
export function getModelFileName(): string {
return modelFileName;
}
export const labelTypeRegistry = container.get<LabelTypeRegistry>(LabelTypeRegistry);

export function setModelSource(file: File): void {
modelSource
Expand Down