diff --git a/WebSocketClient/Dockerfile b/WebSocketClient/Dockerfile
index e729244..b9e6f01 100644
--- a/WebSocketClient/Dockerfile
+++ b/WebSocketClient/Dockerfile
@@ -9,7 +9,9 @@ COPY . .
RUN npm run build
# Install Nginx
-RUN apt-get update && apt-get install -y nginx
+RUN apt-get update
+RUN apt-get install -y nginx
+RUN apt-get clean
# Copy your static files into the Nginx document root directory
RUN cp -r dist/* /var/www/html/
diff --git a/WebSocketClient/dark_mode_dev_env.sh b/WebSocketClient/dark_mode_dev_env.sh
new file mode 100755
index 0000000..7d49484
--- /dev/null
+++ b/WebSocketClient/dark_mode_dev_env.sh
@@ -0,0 +1,6 @@
+cp -r ./dist/assets/dark_modern* ./node_modules/.vite/deps/dark_modern.json
+cp -r ./dist/assets/dark_plus* ./node_modules/.vite/deps/dark_plus.json
+cp -r ./dist/assets/dark_vs* ./node_modules/.vite/deps/dark_vs.json
+cp -r ./dist/assets/light_modern* ./node_modules/.vite/deps/light_modern.json
+cp -r ./dist/assets/light_plus* ./node_modules/.vite/deps/light_plus.json
+cp -r ./dist/assets/light_vs* ./node_modules/.vite/deps/light_vs.json
\ No newline at end of file
diff --git a/WebSocketClient/index.html b/WebSocketClient/index.html
index 12bd2fe..cb22d23 100644
--- a/WebSocketClient/index.html
+++ b/WebSocketClient/index.html
@@ -1,32 +1,382 @@
-
-
-
-
+
+
+
+
+
+
+
+
+
UVL Playground
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
diff --git a/WebSocketClient/package-lock.json b/WebSocketClient/package-lock.json
index 41ef745..42def1f 100644
--- a/WebSocketClient/package-lock.json
+++ b/WebSocketClient/package-lock.json
@@ -19,6 +19,7 @@
"@codingame/monaco-vscode-theme-service-override": "~1.83.2",
"@viz-js/viz": "^3.2.3",
"dotenv": "^16.3.1",
+ "intro.js": "^7.2.0",
"lodash": "^4.17.21",
"monaco-editor": "^0.44.0",
"monaco-editor-workers": "~0.44.0",
@@ -594,6 +595,11 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
+ "node_modules/intro.js": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/intro.js/-/intro.js-7.2.0.tgz",
+ "integrity": "sha512-qbMfaB70rOXVBceIWNYnYTpVTiZsvQh/MIkfdQbpA9di9VBfj1GigUPfcCv3aOfsbrtPcri8vTLTA4FcEDcHSQ=="
+ },
"node_modules/jsonc-parser": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz",
diff --git a/WebSocketClient/package.json b/WebSocketClient/package.json
index 9115e1c..de17250 100644
--- a/WebSocketClient/package.json
+++ b/WebSocketClient/package.json
@@ -4,7 +4,7 @@
"version": "0.0.0",
"type": "module",
"scripts": {
- "dev": "vite dev",
+ "dev": "./dark_mode_dev_env.sh && vite dev",
"build": "tsc && vite build",
"preview": "vite preview"
},
@@ -28,6 +28,7 @@
"@codingame/monaco-vscode-theme-service-override": "~1.83.2",
"@viz-js/viz": "^3.2.3",
"dotenv": "^16.3.1",
+ "intro.js": "^7.2.0",
"lodash": "^4.17.21",
"monaco-editor": "^0.44.0",
"monaco-editor-workers": "~0.44.0",
diff --git a/WebSocketClient/split.css b/WebSocketClient/split.css
new file mode 100644
index 0000000..1bf50c4
--- /dev/null
+++ b/WebSocketClient/split.css
@@ -0,0 +1,35 @@
+.splitter {
+ width: 100%;
+ height: 80vh;
+ display: flex;
+
+}
+
+#separator {
+ cursor: col-resize;
+ background-color: #aaa;
+ background-image: url("data:image/svg+xml;utf8,
");
+ background-repeat: no-repeat;
+ background-position: center;
+ width: 10px;
+ height: 100%;
+
+ /* Prevent the browser's built-in drag from interfering */
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+
+#first {
+ background-color: rgb(255, 255, 255);
+ width: 100%;
+ height: 100%;
+ min-width: 10px;
+}
+
+#second {
+ background-color: rgb(255, 255, 255);
+ width: 0%;
+ height: 100%;
+ min-width: 10px;
+}
\ No newline at end of file
diff --git a/WebSocketClient/src/config.ts b/WebSocketClient/src/config.ts
index 95bab79..445cd1a 100644
--- a/WebSocketClient/src/config.ts
+++ b/WebSocketClient/src/config.ts
@@ -2,4 +2,6 @@ export default {
port: 30000,
languageServerHostName: "localhost",
debug: true,
+ MAX_NUMBER_LINES: 100,
+ MAX_NUMBER_CHARACTERS: 10000,
};
\ No newline at end of file
diff --git a/WebSocketClient/src/intro.ts b/WebSocketClient/src/intro.ts
new file mode 100644
index 0000000..e8f348c
--- /dev/null
+++ b/WebSocketClient/src/intro.ts
@@ -0,0 +1,49 @@
+import introJs from "intro.js";
+import { sendGenerateGraphCommand } from "./main";
+
+
+export const initIntroJS = () => {
+
+
+ var intro = introJs();
+
+ intro.setOptions({
+ steps: [{
+ element: '#container', intro: 'This is the texteditor where you write and edit your uvl feature model.',
+ }, {
+ element: '.codelens-decoration', intro: 'You can use the buttons to activate and deactivate functionality.'
+ }, {
+ element: "[id='1']",
+ intro: 'Click here to visualize your feature model und click on it again to hide it again.'
+ }, {
+ element: "#separator", intro: 'You can change the size of the editor and the visualization with your mouse.'
+ }],
+ });
+
+ const button = document.getElementById("tutorialButton");
+
+ intro.onchange(function(targetElement) {
+ if(targetElement.id === "1"){
+ sendGenerateGraphCommand();
+ }
+
+ });
+
+ button?.addEventListener("click", () => {
+ intro.setOption("exitOnOverlayClick", false);
+ intro.setOption("overlayOpacity", 0);
+ intro.setOption("disableInteraction", true);
+ intro.setOption("highlightClass", "tmpClass");
+ intro.start();
+
+ var helperLayer = document.getElementsByClassName("tmpClass");
+ setTimeout(() => {
+ if(helperLayer[0] instanceof HTMLElement){
+ helperLayer[0].style["box-shadow"] = "rgb(255, 255, 255) 0px 0px 1px 2px, rgba(230, 230, 230, 0.44) 0px 0px 0px 5000px";
+ }
+ }, 100);
+
+
+ });
+
+}
\ No newline at end of file
diff --git a/WebSocketClient/src/main.ts b/WebSocketClient/src/main.ts
index 53f9457..1d81a01 100644
--- a/WebSocketClient/src/main.ts
+++ b/WebSocketClient/src/main.ts
@@ -1,31 +1,38 @@
-/* --------------------------------------------------------------------------------------------
- * Copyright (c) 2018-2022 TypeFox GmbH (http://www.typefox.io). All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- * ------------------------------------------------------------------------------------------ */
-
import * as monaco from 'monaco-editor';
+import {editor} from 'monaco-editor';
import * as vscode from 'vscode';
-import { whenReady } from '@codingame/monaco-vscode-theme-defaults-default-extension';
+import {LogLevel, Uri} from 'vscode';
+import {whenReady} from '@codingame/monaco-vscode-theme-defaults-default-extension';
import '@codingame/monaco-vscode-python-default-extension';
-import { createConfiguredEditor, createModelReference } from 'vscode/monaco';
-import { ExtensionHostKind, registerExtension } from 'vscode/extensions';
-import getConfigurationServiceOverride, { updateUserConfiguration } from '@codingame/monaco-vscode-configuration-service-override';
+import {createConfiguredEditor, createModelReference} from 'vscode/monaco';
+import {ExtensionHostKind, registerExtension} from 'vscode/extensions';
+import getConfigurationServiceOverride, {
+ updateUserConfiguration
+} from '@codingame/monaco-vscode-configuration-service-override';
import getKeybindingsServiceOverride from '@codingame/monaco-vscode-keybindings-service-override';
import getThemeServiceOverride from '@codingame/monaco-vscode-theme-service-override';
import getTextmateServiceOverride from '@codingame/monaco-vscode-textmate-service-override';
-import { initServices, MonacoLanguageClient } from 'monaco-languageclient';
-import { CloseAction, ErrorAction, MessageTransports } from 'vscode-languageclient';
-import { WebSocketMessageReader, WebSocketMessageWriter, toSocket } from 'vscode-ws-jsonrpc';
-import { RegisteredFileSystemProvider, registerFileSystemOverlay, RegisteredMemoryFile } from 'vscode/service-override/files';
-import {LogLevel, Uri} from 'vscode';
+import {initServices, MonacoLanguageClient} from 'monaco-languageclient';
+import {CloseAction, ErrorAction, MessageTransports} from 'vscode-languageclient';
+import {toSocket, WebSocketMessageReader, WebSocketMessageWriter} from 'vscode-ws-jsonrpc';
+import {
+ RegisteredFileSystemProvider,
+ RegisteredMemoryFile,
+ registerFileSystemOverlay
+} from 'vscode/service-override/files';
import config from './config.js';
-import { instance } from "@viz-js/viz";
-import { Message } from 'vscode-jsonrpc';
-import { v4 as uuidv4 } from 'uuid';
+import {instance} from "@viz-js/viz";
+import {Message} from 'vscode-jsonrpc';
+import {v4 as uuidv4} from 'uuid';
import lodash from 'lodash';
-import { ExecuteCommandRequest } from 'vscode-languageserver-protocol'
+import {ExecuteCommandRequest} from 'vscode-languageserver-protocol'
+
+import {buildWorkerDefinition} from 'monaco-editor-workers';
+import {initIntroJS} from "./intro.ts";
+import IOverlayWidget = editor.IOverlayWidget;
+import IContentWidget = editor.IContentWidget;
+import IStandaloneCodeEditor = editor.IStandaloneCodeEditor;
-import { buildWorkerDefinition } from 'monaco-editor-workers';
buildWorkerDefinition('./node_modules/monaco-editor-workers/dist/workers', new URL('', window.location.href).href, false);
const languageId = 'uvls';
@@ -33,138 +40,158 @@ let languageClient: MonacoLanguageClient;
let fileID;
let model;
const connectionText = document.getElementById("connection");
+let debounceGenGraph;
+let updateGraph = false;
-const createUrl = (hostname: string, port: number, path: string, searchParams: Record
= {}, secure: boolean): string => {
+const createUrl = (hostname: string, port: number, path: string, secure: boolean): string => {
const protocol = secure ? 'wss' : 'ws';
const url = new URL(`${protocol}://${hostname}:${port}${path}`);
- for (let [key, value] of Object.entries(searchParams)) {
- if (value instanceof Array) {
- value = value.join(',');
- }
- if (value) {
- url.searchParams.set(key, value);
- }
- }
return url.toString();
};
const createWebSocket = (url: string): WebSocket => {
const webSocket = new WebSocket(url);
+
webSocket.onerror = () => {
- if(connectionText){
- connectionText.textContent = "Could not connect to language server. Reconnecting ...";
+ if (connectionText) {
+ displayEditorError("Could not connect to language server. Reconnecting ...");
}
setTimeout(() => {
createWebSocket(url);
- }, 1000);
+ }, 500);
};
- webSocket.onopen = async () => {
- if(connectionText){
- connectionText.textContent = "";
+ webSocket.onopen = () => {
+ if (connectionText) {
+ displayEditorError("");
}
const socket = toSocket(webSocket);
const reader = new WebSocketMessageReader(socket);
const writer = new WebSocketMessageWriter(socket);
languageClient = createLanguageClient({
- reader,
- writer
+ reader, writer
});
- await languageClient.start();
+
+ languageClient.start();
reader.onClose(() => {
- languageClient.stop();
- createWebSocket(url);
+ setTimeout(() => {
+ createWebSocket(url);
+ }, 500);
});
};
+
return webSocket;
};
const createLanguageClient = (transports: MessageTransports): MonacoLanguageClient => {
const client = new MonacoLanguageClient({
- name: 'UVL Language Client',
- clientOptions: {
+ name: 'UVL Language Client', clientOptions: {
// use a language id as a document selector
- documentSelector: [languageId],
- // disable the default error handler
+ documentSelector: [languageId], // disable the default error handler
errorHandler: {
- error: () => ({ action: ErrorAction.Continue }),
- closed: () => ({ action: CloseAction.DoNotRestart })
- },
- // pyright requires a workspace folder to be present, otherwise it will not work
+ error: () => ({action: ErrorAction.Continue}), closed: () => ({action: CloseAction.DoNotRestart})
+ }, // pyright requires a workspace folder to be present, otherwise it will not work
workspaceFolder: {
- index: 0,
- name: 'workspace',
- uri: monaco.Uri.parse('/workspace')
- },
- synchronize: {
+ index: 0, name: 'workspace', uri: monaco.Uri.parse('/workspace')
+ }, synchronize: {
fileEvents: [vscode.workspace.createFileSystemWatcher('**')]
- },
- connectionOptions: {
+ }, connectionOptions: {
// This construct can be used to filter the messages we are receiving from the language server
messageStrategy: {
handleMessage(message: Message, next: (message: Message) => void) {
- if(Message.isRequest(message)){
+ if (Message.isRequest(message)) {
// Filters requests send by uvls -> Anti-Pattern in our opinion
- }
- else if(Message.isResponse(message)){
+ } else if (Message.isResponse(message)) {
// Filters responses send by uvls
- }
- else if(Message.isNotification(message)){
+ } else if (Message.isNotification(message)) {
// Filters Notification messages following json-rpc spec
}
// "next" is the default behaviour
next(message);
}
}
- },
- // The Middleware allows us to intercept all messages that would be sent to the language server
+ }, // The Middleware allows us to intercept all messages that would be sent to the language server
middleware: {
executeCommand(command, args, next) {
const information = {command: command, arguments: args};
- if(command === "uvls/open_config") {
+ debounceGenGraph = lodash.debounce(() => {
+ client?.sendRequest(ExecuteCommandRequest.type, information).then((res) => {
+ createDiagramFromDot(res as string);
+ });
+ }, 500);
+ console.log("command: " + command);
+ console.log("args: " + args);
+
+ if (command === "uvls/open_config") {
+ const dialog: HTMLDialogElement | null = document.querySelector("#dialog")
+ const modalClose: HTMLButtonElement | null = document.querySelector('#modalClose');
+ if (modalClose) {
+ modalClose.onclick = () => dialog?.close();
+ }
+ dialog?.showModal();
//we do not support config view
return;
- }
- else if(command === "uvls/generate_diagram") {
+ } else if (command === "uvls/generate_diagram") {
client?.sendRequest(ExecuteCommandRequest.type, information).then((res) => {
createDiagramFromDot(res as string);
});
- }
- else {
+
+ if (!updateGraph) {
+ updateGraph = true;
+
+ const firstPane = document.getElementById("first");
+ const secondPane = document.getElementById("second");
+ if (firstPane && secondPane) {
+ firstPane.style.width = "50%";
+ secondPane.style.width = "50%";
+ }
+
+ } else {
+ updateGraph = false;
+ const div = document.getElementsByClassName("graph");
+ const firstPane = document.getElementById("first");
+ while (div[0].firstChild) {
+ div[0].removeChild(div[0].firstChild);
+ }
+ const secondPane = document.getElementById("second");
+ if (firstPane && secondPane) {
+ firstPane.style.width = "100%";
+ secondPane.style.width = "0%";
+ }
+ }
+
+ } else {
next(command, args);
}
},
}
- },
- // create a language client connection from the JSON RPC connection on demand
+ }, // create a language client connection from the JSON RPC connection on demand
connectionProvider: {
get: () => {
return Promise.resolve(transports);
}
}
});
+
return client;
};
function createDiagramFromDot(res: string): void {
instance().then(viz => {
const div = document.getElementsByClassName("graph");
- div[0].replaceChildren(viz.renderSVGElement(res!));
+ let svg = viz.renderSVGElement(res!);
+ svg.id = "SVGGraph";
+ div[0].replaceChildren(svg);
});
}
-export const startPythonClient = async () => {
+export const startUvlClient = async () => {
// init vscode-api
const useDebugLogging = config.debug ? LogLevel.Debug : LogLevel.Off;
await initServices({
userServices: {
- ...getThemeServiceOverride(),
- ...getTextmateServiceOverride(),
- ...getConfigurationServiceOverride(Uri.file('/workspace')),
- ...getKeybindingsServiceOverride(),
- },
- debugLogging: config.debug,
- logLevel: useDebugLogging,
+ ...getThemeServiceOverride(), ...getTextmateServiceOverride(), ...getConfigurationServiceOverride(Uri.file('/workspace')), ...getKeybindingsServiceOverride(),
+ }, debugLogging: config.debug, logLevel: useDebugLogging,
});
await whenReady();
@@ -172,21 +199,11 @@ export const startPythonClient = async () => {
// https://github.com/microsoft/pyright/blob/main/packages/vscode-pyright/package.json
// only a minimum is required to get pyright working
const extension = {
- name: 'uvl-client',
- publisher: 'monaco-languageclient-project',
- version: '1.0.0',
- engines: {
+ name: 'uvl-client', publisher: 'monaco-languageclient-project', version: '1.0.0', engines: {
vscode: '^1.78.0'
- },
- contributes: {
+ }, contributes: {
languages: [{
- id: languageId,
- aliases: [
- 'UVL'
- ],
- extensions: [
- '.uvl',
- ]
+ id: languageId, aliases: ['UVL'], extensions: ['.uvl',]
}]
}
};
@@ -204,46 +221,165 @@ export const startPythonClient = async () => {
registerFileSystemOverlay(1, fileSystemProvider);
// create the web socket and configure to start the language client on open, can add extra parameters to the url if needed.
- createWebSocket(createUrl(config.languageServerHostName,config.port,'/', {
- // Used to parse an auth token or additional parameters such as import IDs to the language server
- authorization: 'UserAuth'
- // By commenting above line out and commenting below line in, connection to language server will be denied.
- // authorization: 'FailedUserAuth'
- }, location.protocol === 'https:'));
-
+ createWebSocket(createUrl(config.languageServerHostName, config.port, '/', location.protocol === 'https:'));
// use the file create before
const modelRef = await createModelReference(monaco.Uri.file(`/workspace/${fileID}.uvl`));
model = modelRef.object;
+ modelRef.object.setLanguageId(languageId);
- const debouncedSave = lodash.debounce(saveFm, 1000);
- modelRef.object.onDidChangeContent(() => {
- debouncedSave();
+ // create monaco editor
+ const editor = createConfiguredEditor(document.getElementById('container')!, {
+ model: modelRef.object.textEditorModel, automaticLayout: true
});
+ window["editor"] = editor;
+ editor.onDidChangeModelContent(() => {
+ const model = editor.getModel();
+ if (!model) {
+ return;
+ }
+ const lineCount = model?.getLineCount();
+ let numberCharacters = aggregateCharacters(model);
- modelRef.object.setLanguageId(languageId);
+ if (lineCount && lineCount > config.MAX_NUMBER_LINES) {
+ if (lineCount > config.MAX_NUMBER_LINES + 1) {
+ vscode.commands.executeCommand("undo");
+ } else {
+ vscode.commands.executeCommand("deleteLeft");
+ }
+ if (connectionText) {
+ displayEditorErrorAtContent(editor, `The Editor only allows content up to ${config.MAX_NUMBER_LINES} Lines!`);
+ }
+ } else if (numberCharacters > config.MAX_NUMBER_CHARACTERS) {
+ if (numberCharacters > config.MAX_NUMBER_CHARACTERS + 1) {
+ vscode.commands.executeCommand("undo");
+ }
+ else {
+ vscode.commands.executeCommand("deleteLeft");
+ }
+ if (connectionText) {
+ displayEditorErrorAtContent(editor, `The Editor only allows content up to ${config.MAX_NUMBER_CHARACTERS} Characters!`);
+ }
+ }
+ debouncedSave();
+ if (updateGraph && debounceGenGraph !== undefined) {
+ debounceGenGraph();
+ }
+ })
- // create monaco editor
- createConfiguredEditor(document.getElementById('container')!, {
- model: modelRef.object.textEditorModel,
- automaticLayout: true
- });
+ initIntroJS();
+ const debouncedSave = lodash.debounce(saveFm, 1000);
+
+ globalEditor = editor;
};
-function getInitialFm(){
+let globalEditor: IStandaloneCodeEditor | null;
+let currentWidget: IOverlayWidget | null;
+
+
+function displayEditorError(msg: string) {
+ if (!globalEditor) {
+ return;
+ }
+ const overlayWidget: IOverlayWidget = {
+ getId(): string {
+ return 'myCustomWidget';
+ }, getPosition(): editor.IOverlayWidgetPosition | null {
+ return {
+ preference: monaco.editor.OverlayWidgetPositionPreference.TOP_CENTER
+ }
+ }, getDomNode(): HTMLElement {
+ const node = document.createElement('div');
+ const span = document.createElement('span');
+ span.textContent = msg;
+ span.className = "top-error";
+ node.replaceChildren(span);
+ return node;
+ }
+ }
+ if (currentWidget) {
+ globalEditor.removeOverlayWidget(currentWidget);
+ }
+ currentWidget = overlayWidget;
+ globalEditor.addOverlayWidget(overlayWidget);
+ // setTimeout(() => {
+ // if(!globalEditor) return;
+ // globalEditor.removeOverlayWidget(overlayWidget);
+ // }, 2000);
+}
+
+let currentContentWidget: IContentWidget | null;
+
+function displayEditorErrorAtContent(editor: editor.IStandaloneCodeEditor, msg: string) {
+
+ const selection = editor.getSelection();
+ const contentWidget: IContentWidget = {
+ getId(): string {
+ return 'myCustomWidget';
+ }, getPosition(): editor.IContentWidgetPosition | null {
+ if (selection) {
+ return {
+ position: selection.getStartPosition(),
+ preference: [monaco.editor.ContentWidgetPositionPreference.BELOW]
+ }
+ }
+ return {
+ position: {lineNumber: 1, column: 1}, preference: [monaco.editor.ContentWidgetPositionPreference.BELOW]
+ }
+ }, getDomNode(): HTMLElement {
+ const node = document.createElement('div');
+ const span = document.createElement('span');
+ node.className = "uvl-tooltip";
+ span.className = "tooltip-text";
+ span.textContent = msg;
+ node.replaceChildren(span);
+ return node;
+ }
+ }
+ if (currentContentWidget) {
+ editor.removeContentWidget(currentContentWidget);
+ }
+ currentContentWidget = contentWidget;
+ editor.addContentWidget(contentWidget);
+
+ debouceRemoveWidget(editor);
+}
+
+const debouceRemoveWidget = lodash.debounce(removeWidget, 2000);
+
+function removeWidget(editor: IStandaloneCodeEditor) {
+ console.log("Editor: ", editor);
+ if (currentContentWidget) {
+ editor.removeContentWidget(currentContentWidget);
+ }
+ currentContentWidget = null;
+}
+
+function aggregateCharacters(model: editor.ITextModel): number {
+ let addReducer = (previousValue: number, currentValue: string) => {
+ return previousValue + currentValue.length
+ };
+ return model?.getLinesContent().reduce(addReducer, 0);
+}
+
+function getInitialFm() {
let initialFm = "features\n\tfeature1\n\t\tor\n\t\t\tfeature2\n\t\t\tfeature3\n\nconstraints\n\tfeature1";
const storedFm = window.localStorage.getItem("fm");
- if(storedFm !== null){
+ if (storedFm !== null) {
initialFm = storedFm;
}
return initialFm;
}
-function saveFm(){
- if(model !== undefined){
+function saveFm() {
+ if (model !== undefined) {
const content = model.textEditorModel?.getValue();
window.localStorage.setItem("fm", content);
}
+}
+
+export function sendGenerateGraphCommand() {
+ vscode.commands.executeCommand("uvls/generate_diagram", `file:///workspace/${fileID}.uvl`);
}
\ No newline at end of file
diff --git a/WebSocketClient/style.css b/WebSocketClient/style.css
index 768b2cf..aee7f15 100644
--- a/WebSocketClient/style.css
+++ b/WebSocketClient/style.css
@@ -1,81 +1,203 @@
/* Reset default browser styles */
body, h1, p {
- margin: 0;
- padding: 0;
+ margin: 0;
+ padding: 0;
}
body {
- font-family: Arial, sans-serif;
- background-color: #1e1e1e; /* Dark background color */
- color: #ffffff; /* Light text color */
+ font-family: Arial, sans-serif;
+ background-color: #1e1e1e; /* Dark background color */
}
.container {
- display: flex;
- width: 50%;
- height: 100%
+ display: flex;
+ width: 50%;
+ height: 100%
}
.header {
- text-align: center;
+ text-align: center;
+ color: #ffffff; /* Light text color */
}
.footer {
- text-align: center;
+ text-align: center;
+ color: #ffffff; /* Light text color */
}
-iframe {
- border: none;
+.graph {
+ overflow: scroll;
+ background: #1e1e1e;
+ height: 100%;
}
-.graph {
- padding: 10px;
- overflow: scroll;
- text-align: center;
+#SVGGraph {
+ margin: 30vh auto;
+ display: block;
+}
+
+.tooltip * {
+ background-color: #1e1e1e;
+ color: white;
+
+ & button {
+ border: 2px solid red;
+ }
}
-.flex-container {
- display: flex;
- height: 75%;
- justify-content: space-around;
+.tooltip {
+ & button {
+ border: 2px solid red;
+ }
}
-iframe {
- flex-grow: 1;
- max-width: 50%;
+::backdrop {
+ background-image: linear-gradient(
+ 45deg,
+ darkgray,
+ black
+ );
+ opacity: 0.75;
}
+#dialog {
+ background: #1e1e1e;
+ color: white;
+ width: 50vw;
+ height: 70vh;
+
+ #gif_in_dialog {
+ display: block;
+ margin: 10px auto;
+ max-width: 40vw;
+ min-width: 20vw;
+ }
+
+ & div {
+ text-align: right;
+
+ & button {
+ color: white;
+ background: #1e1e1e;
+ border: none;
+ font-size: 20pt;
+ }
+ }
+}
+
+
h1 {
- font-size: 30px;
- margin-bottom: 20px;
- text-align: center;
+ font-size: 30px;
+ margin: 20px;
+ text-align: center;
}
.editor {
background-color: #2d2d2d;
- padding: 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
min-width: 50%;
max-width: 100%;
- height:80ch;
+ height:80vh;
border:1px solid grey;
- margin-left: 10px;
- margin-right: 10px;
+
+ & .uvl-tooltip {
+ width: max-content;
+ background-color: #2e2e2e;
+ border: 1px solid black;
+
+ & .tooltip-text {
+ color: #bb0000;
+ margin: 10px;
+ }
+ }
}
p {
- font-size: 20px;
- line-height: 1.5;
- margin-top: 20px;
- width: 80%;
- hyphens: auto;
- text-align: justify;
- margin-left: auto;
- margin-right: auto;
-}
-
-a:link { color: lightskyblue; }
-a:visited { color: rgb(73, 185, 255); }
-a:hover { color: #ffffff; }
-a:active { color: #ff4040; text-decoration:none; font-weight:normal; }
\ No newline at end of file
+ font-size: 20px;
+ line-height: 1.5;
+ margin-top: 20px;
+ width: 80%;
+ hyphens: auto;
+ text-align: justify;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+a:link {
+ color: lightskyblue;
+}
+
+a:visited {
+ color: rgb(73, 185, 255);
+}
+
+a:hover {
+ color: #ffffff;
+}
+
+a:active {
+ color: #ff4040;
+ text-decoration: none;
+ font-weight: normal;
+}
+
+.tutorial-button {
+ position: fixed;
+ top: 10px;
+ right: 10px;
+ background-color: #1e1e1e; /* Light gray color for dark mode */
+ color: white; /* Dark text color for dark mode */
+ border: 2px solid #fff;
+ border-radius: 50%;
+ padding: 10px;
+
+ cursor: pointer;
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+ transition: background-color 0.3s ease;
+ width: 20px; /* Adjust the size as needed */
+ height: 20px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ .tutorial-button-text {
+ font-size: 20px;
+ }
+
+ .tutorial-button:hover {
+ background-color: #95a5a6; /* Darker gray on hover for dark mode */
+ }
+
+ .uvl-tutorial-button {
+ position: fixed;
+ top: 10px;
+ right: 60px;
+ background-color: #1e1e1e; /* Light gray color for dark mode */
+ color: white; /* Dark text color for dark mode */
+ border: 2px solid #fff;
+ border-radius: 50%;
+ padding: 10px;
+
+ cursor: pointer;
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+ transition: background-color 0.3s ease;
+ width: 20px; /* Adjust the size as needed */
+ height: 20px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ .uvl-tutorial-button-text {
+ font-size: 20px;
+ }
+
+ .uvl-tutorial-button:hover {
+ background-color: #95a5a6; /* Darker gray on hover for dark mode */
+ }
+
+ .tutorial {
+ color: white;
+ }
\ No newline at end of file
diff --git a/WebSocketClient/uvl-icon.PNG b/WebSocketClient/uvl-icon.PNG
new file mode 100644
index 0000000..054db41
Binary files /dev/null and b/WebSocketClient/uvl-icon.PNG differ
diff --git a/WebSocketLanguageServer/Dockerfile b/WebSocketLanguageServer/Dockerfile
index 8d180d3..94ef769 100644
--- a/WebSocketLanguageServer/Dockerfile
+++ b/WebSocketLanguageServer/Dockerfile
@@ -3,7 +3,8 @@ FROM node:18
WORKDIR /usr/src/app
RUN apt-get update
-RUN apt-get install z3
+RUN apt-get install -y z3
+RUN apt-get clean
COPY package*.json ./
diff --git a/WebSocketLanguageServer/lib/uvls b/WebSocketLanguageServer/lib/uvls
index bf9a583..b8d7fc6 100755
Binary files a/WebSocketLanguageServer/lib/uvls and b/WebSocketLanguageServer/lib/uvls differ
diff --git a/WebSocketLanguageServer/package-lock.json b/WebSocketLanguageServer/package-lock.json
index 4b0cb4c..01101b1 100644
--- a/WebSocketLanguageServer/package-lock.json
+++ b/WebSocketLanguageServer/package-lock.json
@@ -11,7 +11,6 @@
"dependencies": {
"@types/express": "^4.17.20",
"@types/ws": "^8.5.7",
- "bidirectional-map": "^1.1.1",
"express": "^4.18.2",
"vscode-languageserver": "^9.0.1",
"vscode-ws-jsonrpc": "^3.0.0",
@@ -159,11 +158,6 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
- "node_modules/bidirectional-map": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/bidirectional-map/-/bidirectional-map-1.1.1.tgz",
- "integrity": "sha512-qfpqHKQbSpc5Eev0W35FZoPnoNUvItDrHbVU0YYKkAOBCZ4Dp3CYi9tjAQcdmqN6ui/9+ioIkb9m0uz8EZqPkA=="
- },
"node_modules/binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
diff --git a/WebSocketLanguageServer/src/index.ts b/WebSocketLanguageServer/src/index.ts
index 384382d..98ab47f 100644
--- a/WebSocketLanguageServer/src/index.ts
+++ b/WebSocketLanguageServer/src/index.ts
@@ -13,6 +13,8 @@ import { createConnection, createServerProcess, forward } from 'vscode-ws-jsonrp
import { Message, InitializeRequest, InitializeParams } from 'vscode-languageserver';
import config from './config.js';
+const MAX_MESSAGE_SIZE = 11000;
+
const launchLanguageServer = (socket: IWebSocket) => {
const serverName: string = 'UVLS';
const ls = config.languageServerBinary;
@@ -22,20 +24,31 @@ const launchLanguageServer = (socket: IWebSocket) => {
const writer = new WebSocketMessageWriter(socket);
const socketConnection = createConnection(reader, writer, () => socket.dispose());
if (serverConnection) {
+
forward(socketConnection, serverConnection, message => {
+ if(JSON.stringify(message).length > MAX_MESSAGE_SIZE){
+ console.log(`blocked message larger than ${MAX_MESSAGE_SIZE}`);
+ message = {jsonrpc: "2.0"};
+ socketConnection.dispose();
+ return message;
+ }
if (Message.isRequest(message)) {
console.log(`${serverName} Server received:`);
- console.log(message);
+ logObjectRecursively(message);
if (message.method === InitializeRequest.type.method) {
const initializeParams = message.params as InitializeParams;
initializeParams.processId = process.pid;
}
}
+ if(Message.isNotification(message)){
+ logObjectRecursively(message);
+ }
if (Message.isResponse(message)) {
console.log(`${serverName} Server sent:`);
logObjectRecursively(message);
}
return message;
+
});
}
};
diff --git a/docker-compose.yaml b/docker-compose.yaml
index 14eff74..0378073 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -4,23 +4,20 @@ services:
traefik:
image: traefik:v3.0
ports:
- - 80:80
- - 443:443
- - 30000:30000
- - 3000:3000
+ - "80:80"
+ - "443:443"
+ - "30000:30000"
+ - "3000:3000"
networks:
- proxy
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- letsencrypt:/letsencrypt
- #- /var/log:/var/log
command:
- --api.dashboard=true
- --api.insecure=true
- --log.level=DEBUG
- #- --log.filepath=/var/log/traefik.log
- --accesslog=true
- #- --accesslog.filepath=/var/log/traefik-access.log
- --providers.docker.network=proxy
- --providers.docker.exposedByDefault=false
- --entrypoints.web.address=:80
@@ -37,11 +34,11 @@ services:
- --certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json
- --certificatesResolvers.myresolver.acme.caServer=https://acme-v02.api.letsencrypt.org/directory
labels:
- - traefik.enable=true
- - traefik.http.routers.mydashboard.rule=Host(`${HOSTNAME}`) && (PathPrefix(`/dashboard/`) || PathPrefix(`/api`))
- - traefik.http.routers.mydashboard.service=api@internal
- - traefik.http.routers.mydashboard.middlewares=myauth
- - traefik.http.middlewares.myauth.basicauth.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/
+ - "traefik.enable=true"
+ - "traefik.http.routers.mydashboard.rule=Host(`${HOSTNAME}`) && (PathPrefix(`/dashboard/`) || PathPrefix(`/api`))"
+ - "traefik.http.routers.mydashboard.service=api@internal"
+ - "traefik.http.routers.mydashboard.middlewares=myauth"
+ - "traefik.http.middlewares.myauth.basicauth.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/"
webserver:
build:
@@ -56,8 +53,8 @@ services:
- "traefik.http.services.webserver.loadbalancer.server.port=80"
- traefik.http.middlewares.mywwwredirect.redirectregex.regex=^https://www\.(.*)
- - traefik.http.middlewares.mywwwredirect.redirectregex.replacement=https://$${1}
- - traefik.http.routers.webserver.middlewares=mywwwredirect
+ - "traefik.http.middlewares.mywwwredirect.redirectregex.replacement=https://$${1}"
+ - "traefik.http.routers.webserver.middlewares=mywwwredirect"
languageserver:
build:
diff --git a/sonar-project.properties b/sonar-project.properties
new file mode 100644
index 0000000..bdb469a
--- /dev/null
+++ b/sonar-project.properties
@@ -0,0 +1 @@
+sonar.projectKey=UVLP
\ No newline at end of file