Skip to content

Commit

Permalink
add basic constraint menu
Browse files Browse the repository at this point in the history
  • Loading branch information
Kr0nox committed Nov 21, 2024
1 parent 3ce2f47 commit 1e8d4df
Show file tree
Hide file tree
Showing 4 changed files with 281 additions and 0 deletions.
127 changes: 127 additions & 0 deletions src/features/constraintMenu/ConstraintMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { injectable } from "inversify";
import "./constraintMenu.css";
import { AbstractUIExtension } from "sprotty";
import { calculateTextSize } from "../../utils";

@injectable()
export class ConstraintMenu extends AbstractUIExtension {
static readonly ID = "constraint-menu";

id(): string {
return ConstraintMenu.ID;
}
containerClass(): string {
return ConstraintMenu.ID;
}
protected initializeContents(containerElement: HTMLElement): void {
containerElement.classList.add("ui-float");
containerElement.innerHTML = `
<input type="checkbox" id="expand-state-constraint" hidden>
<label id="constraint-menu-expand-label" for="expand-state-constraint">
<div class="expand-button">
Constraints
</div>
</label>
`;
containerElement.appendChild(this.buildConstraintInputWrapper());
containerElement.appendChild(this.buildConstraintListWrapper(["Test123", "Test456", "Test789"]));
containerElement.appendChild(this.buildRunButton());

// Set the first item as selected
setTimeout(() => this.selectConstraintListItem("Test123"), 0);
}

private buildConstraintInputWrapper(): HTMLElement {
const wrapper = document.createElement("div");
wrapper.id = "constraint-menu-input";
wrapper.innerHTML = `
<input type="text" id="constraint-input" placeholder="Enter constraint here">
`;
return wrapper;
}

private buildConstraintListWrapper(constrains: string[]): HTMLElement {
const wrapper = document.createElement("div");
wrapper.id = "constraint-menu-list";

constrains.forEach((constraint) => {
wrapper.appendChild(this.buildConstraintListItem(constraint));
});

return wrapper;
}

private buildConstraintListItem(constraint: string): HTMLElement {
const valueElement = document.createElement("div");
valueElement.classList.add("constrain-label");

valueElement.onclick = () => {
const elements = document.getElementsByClassName("constraint-label");
for (let i = 0; i < elements.length; i++) {
elements[i].classList.remove("selected");
}
valueElement.classList.add("selected");
this.selectConstraintListItem(constraint);
};

const valueInput = document.createElement("input");
valueInput.value = constraint;
valueInput.placeholder = "Name";
this.dynamicallySetInputSize(valueInput);

valueElement.appendChild(valueInput);

const deleteButton = document.createElement("button");
deleteButton.innerHTML = '<span class="codicon codicon-trash"></span>';
deleteButton.onclick = () => {
console.log("Delete button clicked");
};
valueElement.appendChild(deleteButton);
return valueElement;
}

private selectConstraintListItem(constraint: string): void {
const input = document.getElementById("constraint-input") as HTMLInputElement;
input.value = constraint;
}

/**
* Sets and dynamically updates the size property of the passed input element.
* When the text is zero the width is set to the placeholder length to make place for it.
* When the text is changed the size gets updated with the keyup event.
* @param inputElement the html dom input element to set the size property for
*/
private dynamicallySetInputSize(inputElement: HTMLInputElement): void {
const handleResize = () => {
const displayText = inputElement.value || inputElement.placeholder;
const { width } = calculateTextSize(displayText, window.getComputedStyle(inputElement).font);

// Values have higher padding for the rounded border
const widthPadding = 8;
const finalWidth = width + widthPadding;

inputElement.style.width = finalWidth + "px";
};

inputElement.onkeyup = handleResize;

// The inputElement is not added to the DOM yet, so we cannot set the size now.
// Wait for next JS tick, after which the element has been added to the DOM and we can set the initial size
setTimeout(handleResize, 0);
}

private buildRunButton(): HTMLElement {
const wrapper = document.createElement("div");
wrapper.id = "run-button-container";

const button = document.createElement("button");
button.id = "run-button";
button.innerHTML = "Run";
button.onclick = () => {
console.log("Run button clicked");
};

wrapper.appendChild(button);
return wrapper;
}
}
138 changes: 138 additions & 0 deletions src/features/constraintMenu/constraintMenu.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
div.constraint-menu {
right: 20px;
bottom: 20px;
padding: 10px 10px;
display: grid;
grid-template-columns: auto 1fr;
grid-template-rows: 1fr;
grid-auto-rows: 0;
overflow: hidden;
gap: 8px;
}

div.constraint-menu:has(> input:checked) {
grid-template-rows: 1fr auto 1fr;
}

div.constraint-menu > * {
grid-column-start: 1;
grid-column-end: 2;
grid-row-start: 1;
grid-row-end: 2;
}

#run-button {
background-color: green;
color: white;
border: none;
border-radius: 8px;
padding: 5px 10px;
text-align: center;
text-decoration: none;
display: inline-block;
width: fit-content;
}

#run-button-container {
grid-column-start: 2;
grid-column-end: 3;
grid-row-start: 1;
grid-row-end: 2;
}

#expand-state-constraint:checked ~ #run-button-container {
grid-column-start: 2;
grid-column-end: 3;
grid-row-start: 3;
grid-row-end: 4;
}

#expand-state-constraint:checked ~ #run-button-container > #run-button {
width: 100%;
}

#run-button::before {
content: "";
background-image: url("@fortawesome/fontawesome-free/svgs/solid/play.svg");
display: inline-block;
filter: invert(var(--dark-mode));
height: 16px;
width: 16px;
background-size: 16px 16px;
vertical-align: text-top;
}

#constraint-menu-input {
grid-row-start: 2;
grid-row-end: 4;
grid-column-start: 1;
grid-column-end: 2;
display: none;
}

#expand-state-constraint:checked ~ #constraint-menu-input {
display: block;
}

#constraint-menu-list {
grid-row-start: 2;
grid-row-end: 3;
grid-column-start: 2;
grid-column-end: 3;
display: none;
}

#expand-state-constraint:checked ~ #constraint-menu-list {
display: block;
}

#constraint-menu-expand-label {
padding-right: 2em;
position: relative;
display: flex;
grid-column-start: 1;
grid-column-end: 2;
align-items: center;
}

#expand-state-constraint:checked ~ #constraint-menu-expand-label {
grid-column-end: 3;
}

#constraint-menu-expand-label::after {
content: "";
background-image: url("@fortawesome/fontawesome-free/svgs/solid/chevron-up.svg");
right: 0.5em;
position: absolute;
display: inline-block;

/* only filter=invert(1) if dark mode is enabled aka --dark-mode is set to 1 */
filter: invert(var(--dark-mode));

width: 16px;
height: 16px;
background-size: 16px 16px;

transition: transform 500ms ease;
transform: scaleY(1);
}

#expand-state-constraint:checked ~ #constraint-menu-expand-label::after {
transform: scaleY(-1);
}

.constrain-label input {
background-color: var(--color-background);
text-align: center;
border: 1px solid var(--color-foreground);
border-radius: 15px;
padding: 3px;
margin: 4px;
}

.constrain-label button {
background-color: transparent;
border: none;
cursor: pointer;
padding: 0;
}
14 changes: 14 additions & 0 deletions src/features/constraintMenu/di.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ContainerModule } from "inversify";
import { EDITOR_TYPES } from "../../utils";
import { ConstraintMenu } from "./ConstraintMenu";
import { TYPES } from "sprotty";

// This module contains an UI extension that adds a tool palette to the editor.
// This tool palette allows the user to create new nodes and edges.
// Additionally it contains the tools that are used to create the nodes and edges.

export const constraintMenuModule = new ContainerModule((bind) => {
bind(ConstraintMenu).toSelf().inSingletonScope();
bind(TYPES.IUIExtension).toService(ConstraintMenu);
bind(EDITOR_TYPES.DefaultUIElement).toService(ConstraintMenu);
});
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { dfdElementsModule } from "./features/dfdElements/di.config";
import { copyPasteModule } from "./features/copyPaste/di.config";
import { EDITOR_TYPES } from "./utils";
import { editorModeModule } from "./features/editorMode/di.config";
import { constraintMenuModule } from "./features/constraintMenu/di.config";

import "sprotty/css/sprotty.css";
import "sprotty/css/edit-label.css";
Expand Down Expand Up @@ -52,6 +53,7 @@ container.load(
editorModeModule,
toolPaletteModule,
copyPasteModule,
constraintMenuModule,
);

const dispatcher = container.get<ActionDispatcher>(TYPES.IActionDispatcher);
Expand Down

0 comments on commit 1e8d4df

Please sign in to comment.