diff --git a/packages/web/src/scripts/console.js b/packages/web/src/scripts/console.js index 2025399a..a276e994 100644 --- a/packages/web/src/scripts/console.js +++ b/packages/web/src/scripts/console.js @@ -27,7 +27,7 @@ import { visualize } from './visualize' import { validationView, validationTab } from './validation' import { convertTDYamlToJson } from '../../../core/dist/web-bundle.min.js' import { detectProtocolSchemes } from '@thingweb/td-utils/dist/web-bundle.min.js' -import { generateOAP, generateAAP, addDefaultsUtil, validate, generateAAS, resetValidationStatus } from './util' +import { generateOAP, generateAAP, addDefaultsUtil, validate, generateAAS, resetValidationStatus, checkDocumentType } from './util' import { editorList, getEditorData } from './editor' import { textIcon } from './main.js' @@ -143,68 +143,71 @@ visualizationOptions.forEach(option => { let td = JSON.parse(editorValue) hideConsoleError() - if ((td["@type"] === "tm:ThingModel" && option.id === "open-api-tab") || (td["@type"] === "tm:ThingModel" && option.id === "async-api-tab") || (td["@type"] === "tm:ThingModel" && option.id === "defaults-tab") || (td["@type"] === "tm:ThingModel" && option.id === "aas-tab")) { - showConsoleError("This function is only allowed for Thing Descriptions!") + if ( + (checkDocumentType(td) === "tm" && option.id === "open-api-tab") || + (checkDocumentType(td) === "tm" && option.id === "async-api-tab") || + (checkDocumentType(td) === "tm" && option.id === "defaults-tab") || + (checkDocumentType(td) === "tm" && option.id === "aas-tab") + ) { + showConsoleError("This function is only allowed for Thing Descriptions!"); } else { switch (option.id) { case "open-api-tab": { - if (fileType === "yaml") { - openApiJsonBtn.disabled = false - openApiYamlBtn.disabled = true + openApiJsonBtn.disabled = false; + openApiYamlBtn.disabled = true; } else { - openApiJsonBtn.disabled = true - openApiYamlBtn.disabled = false + openApiJsonBtn.disabled = true; + openApiYamlBtn.disabled = false; } - enableAPIConversionWithProtocol(editorInstance) + enableAPIConversionWithProtocol(editorInstance); break; } case "async-api-tab": { if (fileType === "yaml") { - asyncApiJsonBtn.disabled = false - asyncApiYamlBtn.disabled = true + asyncApiJsonBtn.disabled = false; + asyncApiYamlBtn.disabled = true; } else { - asyncApiJsonBtn.disabled = true - asyncApiYamlBtn.disabled = false + asyncApiJsonBtn.disabled = true; + asyncApiYamlBtn.disabled = false; } - enableAPIConversionWithProtocol(editorInstance) + enableAPIConversionWithProtocol(editorInstance); break; } case "aas-tab": { - - generateAAS(fileType, editorInstance) - AASView.classList.remove("hidden") + generateAAS(fileType, editorInstance); + AASView.classList.remove("hidden"); break; } case "defaults-tab": { if (fileType === "yaml") { - defaultsJsonBtn.disabled = false - defaultsYamlBtn.disabled = true + defaultsJsonBtn.disabled = false; + defaultsYamlBtn.disabled = true; } else { - defaultsJsonBtn.disabled = true - defaultsYamlBtn.disabled = false + defaultsJsonBtn.disabled = true; + defaultsYamlBtn.disabled = false; } - addDefaultsUtil(editorInstance) - defaultsAddBtn.disabled = true - defaultsView.classList.remove("hidden") + addDefaultsUtil(editorInstance); + defaultsAddBtn.disabled = true; + defaultsView.classList.remove("hidden"); break; } case "visualize-tab": { - visualize(td) + visualize(td); break; } case "validation-tab": { - validationView.classList.remove("hidden") - const editorData = getEditorData(editorInstance) - validate(editorData[1], editorValue) + validationView.classList.remove("hidden"); + const editorData = getEditorData(editorInstance); + validate(editorData[1], editorValue); break; } @@ -212,7 +215,6 @@ visualizationOptions.forEach(option => { break; } } - } } diff --git a/packages/web/src/scripts/editor.js b/packages/web/src/scripts/editor.js index ed53c272..4e2ba746 100644 --- a/packages/web/src/scripts/editor.js +++ b/packages/web/src/scripts/editor.js @@ -1,15 +1,15 @@ -/* +/* * Copyright (c) 2023 Contributors to the Eclipse Foundation - * + * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. - * + * * 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, or the W3C Software Notice and * Document License (2015-05-13) which is available at * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document. - * + * * SPDX-License-Identifier: EPL-2.0 OR W3C-20150513 */ @@ -17,44 +17,44 @@ * @file The `editor.js` contains the main functionality * for the generated monaco editors and the surrounding elements * such as the tab functionality. It utilizes multiple other files and dependencies - * such as the monaco-editor dependency, the monochrome-theme file to add the custom + * such as the monaco-editor dependency, the monochrome-theme file to add the custom * theme, some util functions, the td and tm schemas from the core @thing-description-playground * as well as the "Validators" and the "JsonSpellChecker" from the json-spell-checker dependency */ -import { editor, languages, MarkerSeverity } from 'monaco-editor' -import { getEditorValue, validate } from "./util" -import { setFontSize, editorForm, fontSizeSlider } from './settings-menu' -import { autoValidateBtn, validationTab, validationView } from './validation' -import { jsonBtn, yamlBtn } from './json-yaml' -import tdSchema from '../../../core/td-schema.json' -import tmSchema from '../../../core/tm-schema.json' -import { convertTDJsonToYaml, convertTDYamlToJson } from '../../../core/dist/web-bundle.min.js' -import { configure, checkTypos } from '@thingweb/json-spell-checker/dist/web-bundle.min.js' -import { clearConsole } from './console' +import { editor, languages, MarkerSeverity } from "monaco-editor"; +import { checkDocumentType, getEditorValue, validate } from "./util"; +import { setFontSize, editorForm, fontSizeSlider } from "./settings-menu"; +import { autoValidateBtn, validationTab, validationView } from "./validation"; +import { jsonBtn, yamlBtn } from "./json-yaml"; +import tdSchema from "../../../core/td-schema.json"; +import tmSchema from "../../../core/tm-schema.json"; +import { convertTDJsonToYaml, convertTDYamlToJson } from "../../../core/dist/web-bundle.min.js"; +import { configure, checkTypos } from "@thingweb/json-spell-checker/dist/web-bundle.min.js"; +import { clearConsole } from "./console"; /***********************************************************/ /* Editor and tabs */ /***********************************************************/ //Declare all necessary item from the DOM -const addTab = document.querySelector(".ide__tabs__add") -const tabsLeftContainer = document.querySelector(".ide__tabs__left") -const ideContainer = document.querySelector(".ide__container") -export let tabsLeft = document.querySelectorAll(".ide__tabs__left li:not(:last-child)") +const addTab = document.querySelector(".ide__tabs__add"); +const tabsLeftContainer = document.querySelector(".ide__tabs__left"); +const ideContainer = document.querySelector(".ide__container"); +export let tabsLeft = document.querySelectorAll(".ide__tabs__left li:not(:last-child)"); //Editor list array where all the generated editor will be added and referenced from -export let editorList = [] +export let editorList = []; export const ideCount = { - ideNumber: 1 -} + ideNumber: 1, +}; //Initiate by generating the first editor and the respective tab -createIde(ideCount.ideNumber) +createIde(ideCount.ideNumber); //Initialized the program with an open validation view -validationTab.checked = true -validationView.classList.remove("hidden") +validationTab.checked = true; +validationView.classList.remove("hidden"); /** * Function which creates a tab for the respective editor @@ -64,165 +64,177 @@ validationView.classList.remove("hidden") * @param {String} thingType - the type of the object TD or TM */ function createTab(tabNumber, exampleName, thingType) { + const newTab = document.createElement("li"); + //assign the tabNumber to the data-tab-id property + newTab.setAttribute("data-tab-id", tabNumber); + newTab.setAttribute("id", "tab"); + + //Add thing type icon to the tab + const tabIcon = document.createElement("p"); + tabIcon.classList.add("tab-icon"); + if (thingType === "TM") { + tabIcon.innerText = "TM"; + } else { + tabIcon.innerText = "TD"; + } - const newTab = document.createElement("li") - //assign the tabNumber to the data-tab-id property - newTab.setAttribute("data-tab-id", tabNumber) - newTab.setAttribute('id', 'tab'); - - //Add thing type icon to the tab - const tabIcon = document.createElement("p") - tabIcon.classList.add("tab-icon") - if (thingType === "TM") { - tabIcon.innerText = "TM" - } else { - tabIcon.innerText = "TD" - } - - const tabContent = document.createElement("p") - //If there is not specified example name give the default Thing Description + tabNumber - //Else, if the the user uses TD/TM example use the example name as the tab name - if (exampleName === undefined || exampleName === "") { - tabContent.innerText = `Thing Description ${tabNumber}` - } - else { - tabContent.innerText = exampleName - } - tabContent.classList.add("content-tab") - //Add the close btn element - const closeBtn = document.createElement("div") - closeBtn.classList.add("close-tab") - - // Create the svg close icon - // Close icon gotten from: https://fontawesome.com/icons/xmark?f=classic&s=solid - const closeIconSvg = document.createElementNS("http://www.w3.org/2000/svg", "svg") - closeIconSvg.setAttribute("xmlns", "http://www.w3.org/2000/svg") - closeIconSvg.setAttribute("width", "100%") - closeIconSvg.setAttribute("height", "100%") - closeIconSvg.setAttribute("viewBox", "0 0 384 512") - - // Create a path element and set its attributes - const closeIconPath = document.createElementNS("http://www.w3.org/2000/svg", "path") - closeIconPath.setAttribute("d", "M342.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192 210.7 86.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L146.7 256 41.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3 297.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L237.3 256 342.6 150.6z") - - //Create and append Font Awesome attribution comment - const commentCloseIcon = document.createComment(" !Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc. ") - closeIconSvg.appendChild(commentCloseIcon) - - //append the path to the svg and the svg to the button - closeIconSvg.appendChild(closeIconPath) - closeBtn.appendChild(closeIconSvg) - - //Create the close confirmation buttons - const confirmBtns = document.createElement("div") - confirmBtns.classList.add("confirm-btns", "hidden") - - //Confirm close button - const confirmTabClose = document.createElement("button") - confirmTabClose.classList.add("confirm-tab-close") - confirmTabClose.textContent = "Close" - - // Create the svg confirm close icon - // Check icon gotten from: https://fontawesome.com/icons/check?f=classic&s=solid - const confirmIconSvg = document.createElementNS("http://www.w3.org/2000/svg", "svg") - confirmIconSvg.setAttribute("xmlns", "http://www.w3.org/2000/svg") - confirmIconSvg.setAttribute("width", "100%") - confirmIconSvg.setAttribute("height", "100%") - confirmIconSvg.setAttribute("viewBox", "0 0 448 512") - confirmIconSvg.classList.add("icon") - - // Create a path element and set its attributes - const confirmIconPath = document.createElementNS("http://www.w3.org/2000/svg", "path") - confirmIconPath.setAttribute("d", "M438.6 105.4c12.5 12.5 12.5 32.8 0 45.3l-256 256c-12.5 12.5-32.8 12.5-45.3 0l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L160 338.7 393.4 105.4c12.5-12.5 32.8-12.5 45.3 0z") - - //Create and append Font Awesome attribution comment - const commentConfirmIcon = document.createComment(" !Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc. ") - confirmIconSvg.appendChild(commentConfirmIcon) - - confirmIconSvg.appendChild(confirmIconPath) - confirmTabClose.appendChild(confirmIconSvg) - - //Cancel close button - const cancelTabClose = document.createElement("button") - cancelTabClose.classList.add("cancel-tab-close") - cancelTabClose.textContent = "Cancel" - - // Create the svg close cancel icon - // Close icon gotten from: https://fontawesome.com/icons/xmark?f=classic&s=solid - const cancelIconSvg = document.createElementNS("http://www.w3.org/2000/svg", "svg") - cancelIconSvg.setAttribute("xmlns", "http://www.w3.org/2000/svg") - cancelIconSvg.setAttribute("width", "100%") - cancelIconSvg.setAttribute("height", "100%") - cancelIconSvg.setAttribute("viewBox", "0 0 384 512") - cancelIconSvg.classList.add("icon") - - // Create a path element and set its attributes - const cancelIconPath = document.createElementNS("http://www.w3.org/2000/svg", "path") - cancelIconPath.setAttribute("d", "M342.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192 210.7 86.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L146.7 256 41.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3 297.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L237.3 256 342.6 150.6z") - - //Create and append Font Awesome attribution comment - const commentCancelIcon = document.createComment(" !Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc. ") - cancelIconSvg.appendChild(commentCancelIcon) - - cancelIconSvg.appendChild(cancelIconPath) - cancelTabClose.appendChild(cancelIconSvg) - - cancelTabClose.addEventListener("click", () => { - cancelTabClose.parentElement.classList.add("hidden") - }) - - confirmBtns.appendChild(confirmTabClose) - confirmBtns.appendChild(cancelTabClose) - - newTab.appendChild(tabIcon) - newTab.appendChild(tabContent) - newTab.appendChild(closeBtn) - newTab.appendChild(confirmBtns) - - //Insert the newly created list at the end of the tab container but before the add new tab button - tabsLeftContainer.insertBefore(newTab, tabsLeftContainer.children[(tabsLeftContainer.children.length) - 1]) - tabsLeft = document.querySelectorAll(".ide__tabs__left li:not(:last-child)") - - //Once the new tab is created remove "active class from all other tab" - //and give the class "active to the new tab" - tabsLeft.forEach(tab => { - tab.classList.remove("active") - }) - newTab.classList.add("active") - - confirmTabClose.addEventListener("click", () => { - //If there is only one tab and its closed create a completely editor and tab and restart the counter - //If not the last one adjust the styling accordingly and update the amount of tabs - if (tabsLeft.length == 1) { - ideCount.ideNumber = 0 - editorList.forEach(ide => { - if (confirmTabClose.parentElement.parentElement.dataset.tabId === ide["_domElement"].dataset.ideId) { - //remove the editor from the editor list array and from the DOM - const index = editorList.indexOf(ide) - editorList.splice(index, 1) - ide["_domElement"].remove() - } - }) - //remove tab - confirmTabClose.parentElement.parentElement.remove() - //create new tab - createIde(++ideCount.ideNumber) - jsonBtn.checked = true + const tabContent = document.createElement("p"); + //If there is not specified example name give the default Thing Description + tabNumber + //Else, if the the user uses TD/TM example use the example name as the tab name + if (exampleName === undefined || exampleName === "") { + tabContent.innerText = `Thing Description ${tabNumber}`; + } else { + tabContent.innerText = exampleName; } - else { - editorList.forEach(ide => { - if (confirmTabClose.parentElement.parentElement.dataset.tabId === ide["_domElement"].dataset.ideId) { - const index = editorList.indexOf(ide) - editorList.splice(index, 1) - ide["_domElement"].remove() + tabContent.classList.add("content-tab"); + //Add the close btn element + const closeBtn = document.createElement("div"); + closeBtn.classList.add("close-tab"); + + // Create the svg close icon + // Close icon gotten from: https://fontawesome.com/icons/xmark?f=classic&s=solid + const closeIconSvg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + closeIconSvg.setAttribute("xmlns", "http://www.w3.org/2000/svg"); + closeIconSvg.setAttribute("width", "100%"); + closeIconSvg.setAttribute("height", "100%"); + closeIconSvg.setAttribute("viewBox", "0 0 384 512"); + + // Create a path element and set its attributes + const closeIconPath = document.createElementNS("http://www.w3.org/2000/svg", "path"); + closeIconPath.setAttribute( + "d", + "M342.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192 210.7 86.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L146.7 256 41.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3 297.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L237.3 256 342.6 150.6z" + ); + + //Create and append Font Awesome attribution comment + const commentCloseIcon = document.createComment( + " !Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc. " + ); + closeIconSvg.appendChild(commentCloseIcon); + + //append the path to the svg and the svg to the button + closeIconSvg.appendChild(closeIconPath); + closeBtn.appendChild(closeIconSvg); + + //Create the close confirmation buttons + const confirmBtns = document.createElement("div"); + confirmBtns.classList.add("confirm-btns", "hidden"); + + //Confirm close button + const confirmTabClose = document.createElement("button"); + confirmTabClose.classList.add("confirm-tab-close"); + confirmTabClose.textContent = "Close"; + + // Create the svg confirm close icon + // Check icon gotten from: https://fontawesome.com/icons/check?f=classic&s=solid + const confirmIconSvg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + confirmIconSvg.setAttribute("xmlns", "http://www.w3.org/2000/svg"); + confirmIconSvg.setAttribute("width", "100%"); + confirmIconSvg.setAttribute("height", "100%"); + confirmIconSvg.setAttribute("viewBox", "0 0 448 512"); + confirmIconSvg.classList.add("icon"); + + // Create a path element and set its attributes + const confirmIconPath = document.createElementNS("http://www.w3.org/2000/svg", "path"); + confirmIconPath.setAttribute( + "d", + "M438.6 105.4c12.5 12.5 12.5 32.8 0 45.3l-256 256c-12.5 12.5-32.8 12.5-45.3 0l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L160 338.7 393.4 105.4c12.5-12.5 32.8-12.5 45.3 0z" + ); + + //Create and append Font Awesome attribution comment + const commentConfirmIcon = document.createComment( + " !Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc. " + ); + confirmIconSvg.appendChild(commentConfirmIcon); + + confirmIconSvg.appendChild(confirmIconPath); + confirmTabClose.appendChild(confirmIconSvg); + + //Cancel close button + const cancelTabClose = document.createElement("button"); + cancelTabClose.classList.add("cancel-tab-close"); + cancelTabClose.textContent = "Cancel"; + + // Create the svg close cancel icon + // Close icon gotten from: https://fontawesome.com/icons/xmark?f=classic&s=solid + const cancelIconSvg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + cancelIconSvg.setAttribute("xmlns", "http://www.w3.org/2000/svg"); + cancelIconSvg.setAttribute("width", "100%"); + cancelIconSvg.setAttribute("height", "100%"); + cancelIconSvg.setAttribute("viewBox", "0 0 384 512"); + cancelIconSvg.classList.add("icon"); + + // Create a path element and set its attributes + const cancelIconPath = document.createElementNS("http://www.w3.org/2000/svg", "path"); + cancelIconPath.setAttribute( + "d", + "M342.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192 210.7 86.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L146.7 256 41.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3 297.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L237.3 256 342.6 150.6z" + ); + + //Create and append Font Awesome attribution comment + const commentCancelIcon = document.createComment( + " !Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc. " + ); + cancelIconSvg.appendChild(commentCancelIcon); + + cancelIconSvg.appendChild(cancelIconPath); + cancelTabClose.appendChild(cancelIconSvg); + + cancelTabClose.addEventListener("click", () => { + cancelTabClose.parentElement.classList.add("hidden"); + }); + + confirmBtns.appendChild(confirmTabClose); + confirmBtns.appendChild(cancelTabClose); + + newTab.appendChild(tabIcon); + newTab.appendChild(tabContent); + newTab.appendChild(closeBtn); + newTab.appendChild(confirmBtns); + + //Insert the newly created list at the end of the tab container but before the add new tab button + tabsLeftContainer.insertBefore(newTab, tabsLeftContainer.children[tabsLeftContainer.children.length - 1]); + tabsLeft = document.querySelectorAll(".ide__tabs__left li:not(:last-child)"); + + //Once the new tab is created remove "active class from all other tab" + //and give the class "active to the new tab" + tabsLeft.forEach((tab) => { + tab.classList.remove("active"); + }); + newTab.classList.add("active"); + + confirmTabClose.addEventListener("click", () => { + //If there is only one tab and its closed create a completely editor and tab and restart the counter + //If not the last one adjust the styling accordingly and update the amount of tabs + if (tabsLeft.length == 1) { + ideCount.ideNumber = 0; + editorList.forEach((ide) => { + if (confirmTabClose.parentElement.parentElement.dataset.tabId === ide["_domElement"].dataset.ideId) { + //remove the editor from the editor list array and from the DOM + const index = editorList.indexOf(ide); + editorList.splice(index, 1); + ide["_domElement"].remove(); + } + }); + //remove tab + confirmTabClose.parentElement.parentElement.remove(); + //create new tab + createIde(++ideCount.ideNumber); + jsonBtn.checked = true; + } else { + editorList.forEach((ide) => { + if (confirmTabClose.parentElement.parentElement.dataset.tabId === ide["_domElement"].dataset.ideId) { + const index = editorList.indexOf(ide); + editorList.splice(index, 1); + ide["_domElement"].remove(); + } + }); + confirmTabClose.parentElement.parentElement.remove(); + tabsLeft = document.querySelectorAll(".ide__tabs__left li:not(:last-child)"); + tabsLeft[0].classList.add("active"); + editorList[0]["_domElement"].classList.add("active"); } - }) - confirmTabClose.parentElement.parentElement.remove() - tabsLeft = document.querySelectorAll(".ide__tabs__left li:not(:last-child)") - tabsLeft[0].classList.add("active") - editorList[0]["_domElement"].classList.add("active") - } - }) + }); } /** @@ -232,149 +244,148 @@ function createTab(tabNumber, exampleName, thingType) { * @param {Object} exampleValue - the td or tm as a json object */ export function createIde(ideNumber, exampleValue) { - clearConsole() - const url = getEditorValue(window.location.hash.substring(1)) - let defaultValue = {} - let editorLanguage = "json" - - if (url === "") { - // If example value is empty utilize a preset of the most basic form of a td - // else utilize the td/tm from the exampleValue - if (exampleValue === undefined) { - defaultValue = { - "@context": "https://www.w3.org/2022/wot/td/v1.1", - "id": "urn:uuid:0804d572-cce8-422a-bb7c-4412fcd56f06", - "@type": "Thing", - "title": `Thing Template`, - "description": "This is your customizable template. Edit it to fit your Thing Description or Thing Model needs!", - "securityDefinitions": { - "basic_sc": { "scheme": "basic", "in": "header" } - }, - "security": "basic_sc", - "properties": {}, - "actions": {}, - "events": {} - } + clearConsole(); + const url = getEditorValue(window.location.hash.substring(1)); + let defaultValue = {}; + let editorLanguage = "json"; + + if (url === "") { + // If example value is empty utilize a preset of the most basic form of a td + // else utilize the td/tm from the exampleValue + if (exampleValue === undefined) { + defaultValue = { + "@context": "https://www.w3.org/2022/wot/td/v1.1", + id: "urn:uuid:0804d572-cce8-422a-bb7c-4412fcd56f06", + "@type": "Thing", + title: `Thing Template`, + description: + "This is your customizable template. Edit it to fit your Thing Description or Thing Model needs!", + securityDefinitions: { + basic_sc: { scheme: "basic", in: "header" }, + }, + security: "basic_sc", + properties: {}, + actions: {}, + events: {}, + }; + } else { + delete exampleValue["$title"]; + delete exampleValue["$description"]; + defaultValue = exampleValue; + } + } else { + editorLanguage = url.substring(2, 6); + defaultValue = JSON.parse(url.substring(6)); + //remove the hash from the url to allow new editor to be created + history.replaceState(null, null, window.location.origin + window.location.pathname); } - else { - delete exampleValue["$title"] - delete exampleValue["$description"] - defaultValue = exampleValue + + //Create the container for the new editor and add all necessary attributes for styling and identifiers + const newIde = document.createElement("div"); + newIde.classList.add("editor"); + newIde.setAttribute("id", `editor${ideNumber}`); + newIde.setAttribute("data-ide-id", ideNumber); + ideContainer.appendChild(newIde); + + //New monaco editor is created + initEditor(ideNumber, defaultValue, editorLanguage); + + //remove the active class from previous editor + editorList.forEach((editor) => { + editor["_domElement"].classList.remove("active"); + }); + + //Add active class to new editor + newIde.classList.add("active"); + + //Create the new tab depending if its a TM or TD + if (checkDocumentType(defaultValue) === "tm") { + createTab(ideNumber, defaultValue["title"], "TM"); + } else { + createTab(ideNumber, defaultValue["title"], "TD"); } - } - else { - editorLanguage = url.substring(2, 6) - defaultValue = JSON.parse(url.substring(6)) - //remove the hash from the url to allow new editor to be created - history.replaceState(null, null, window.location.origin + window.location.pathname); - } - - //Create the container for the new editor and add all necessary attributes for styling and identifiers - const newIde = document.createElement("div") - newIde.classList.add("editor") - newIde.setAttribute('id', `editor${ideNumber}`) - newIde.setAttribute("data-ide-id", ideNumber) - ideContainer.appendChild(newIde) - - //New monaco editor is created - initEditor(ideNumber, defaultValue, editorLanguage) - - //remove the active class from previous editor - editorList.forEach(editor => { - editor["_domElement"].classList.remove("active") - }) - - //Add active class to new editor - newIde.classList.add("active") - - //Create the new tab depending if its a TM or TD - if (defaultValue["@type"] === "tm:ThingModel") { - createTab(ideNumber, defaultValue["title"], "TM") - } - else { - createTab(ideNumber, defaultValue["title"], "TD") - } - - findFileType() + + findFileType(); } /** * Async function to initiate the editors - * @param {Number} ideNumber - * @param {Object} defaultValue - * @param {String} editorLanguage + * @param {Number} ideNumber + * @param {Object} defaultValue + * @param {String} editorLanguage */ async function initEditor(ideNumber, editorValue, editorLanguage) { - editorValue = editorLanguage === "json" ? JSON.stringify(editorValue, null, 2) : convertTDJsonToYaml(JSON.stringify(editorValue)) - let editorInstance = editor.create(document.getElementById(`editor${ideNumber}`), { - value: editorValue, - language: editorLanguage, - automaticLayout: true, - formatOnPaste: true - }) - - //Bind the font size slider from the settings to the editor(s) and assign the specified font size - document.onload = setFontSize(editorInstance) - fontSizeSlider.addEventListener("input", () => { - setFontSize(editorInstance) - }) - - //Bind the reset button form the settings to the editor and assign the specified font size - editorForm.addEventListener("reset", () => { - setFontSize(editorInstance) - }) - - editorInstance.getModel().onDidChangeContent(() => { - - try { - const editorValues = getEditorData(editorInstance) - changeThingIcon(editorValues[1]) - updateTabName(editorValues[2]["title"]) - - if (autoValidateBtn.checked === true && validationTab.checked === true) { - validate(editorValues[1], JSON.stringify(editorValues[2])) - }else{ - clearConsole() - } - - //Only use the spell checker if file is json - if (editorValues[0] === "json") { - //Get if thing type and set the respective schema - if (editorValues[2]["@type"] === "tm:ThingModel") { - // Configure JSON language support with schemas and schema associations - languages.json.jsonDefaults.setDiagnosticsOptions({ - validate: true, - schemas: [ - { - fileMatch: [editorInstance.getModel().uri.toString()], - schema: tmSchema, - uri: 'file:///tm-schema.json' - } - ] - }); + editorValue = + editorLanguage === "json" + ? JSON.stringify(editorValue, null, 2) + : convertTDJsonToYaml(JSON.stringify(editorValue)); + let editorInstance = editor.create(document.getElementById(`editor${ideNumber}`), { + value: editorValue, + language: editorLanguage, + automaticLayout: true, + formatOnPaste: true, + }); + + //Bind the font size slider from the settings to the editor(s) and assign the specified font size + document.onload = setFontSize(editorInstance); + fontSizeSlider.addEventListener("input", () => { + setFontSize(editorInstance); + }); + + //Bind the reset button form the settings to the editor and assign the specified font size + editorForm.addEventListener("reset", () => { + setFontSize(editorInstance); + }); + + editorInstance.getModel().onDidChangeContent(() => { + try { + const editorValues = getEditorData(editorInstance); + changeThingIcon(editorValues[1]); + updateTabName(editorValues[2]["title"]); + + if (autoValidateBtn.checked === true && validationTab.checked === true) { + validate(editorValues[1], JSON.stringify(editorValues[2])); + } else { + clearConsole(); + } + + //Only use the spell checker if file is json + if (editorValues[0] === "json") { + //Get if thing type and set the respective schema + + if (checkDocumentType(editorValues[2]) === "tm") { + // Configure JSON language support with schemas and schema associations + languages.json.jsonDefaults.setDiagnosticsOptions({ + validate: true, + schemas: [ + { + fileMatch: [editorInstance.getModel().uri.toString()], + schema: tmSchema, + uri: "file:///tm-schema.json", + }, + ], + }); + } else { + languages.json.jsonDefaults.setDiagnosticsOptions({ + validate: true, + schemas: [ + { + fileMatch: [editorInstance.getModel().uri.toString()], + schema: tdSchema, + uri: "file:///td-schema.json", + }, + ], + }); + } + + markTypos(editorInstance.getModel()); + } + } catch (err) { + console.error("Invalid JSON object"); } - else { - languages.json.jsonDefaults.setDiagnosticsOptions({ - validate: true, - schemas: [ - { - fileMatch: [editorInstance.getModel().uri.toString()], - schema: tdSchema, - uri: 'file:///td-schema.json' - } - ] - }); - } - - markTypos(editorInstance.getModel()); - } - } catch (err) { - console.error("Invalid JSON object"); - } + }); - }); - - editorList.push(editorInstance) + editorList.push(editorInstance); } /** @@ -382,36 +393,39 @@ async function initEditor(ideNumber, editorValue, editorLanguage) { * @param {object} model - The model that represents the loaded Monaco editor */ function markTypos(model) { - const markers = [] - - configure() - const typos = checkTypos(model.getValue()) - - typos.forEach(typo => { - markers.push({ - message: typo.message, - severity: MarkerSeverity.Warning, - startLineNumber: typo.startLineNumber, - startColumn: typo.startColumn, - endLineNumber: typo.endLineNumber, - endColumn: typo.endColumn - }) - }) - - editor.setModelMarkers(model, 'typo', markers) + const markers = []; + + configure(); + const typos = checkTypos(model.getValue()); + + typos.forEach((typo) => { + markers.push({ + message: typo.message, + severity: MarkerSeverity.Warning, + startLineNumber: typo.startLineNumber, + startColumn: typo.startColumn, + endLineNumber: typo.endLineNumber, + endColumn: typo.endColumn, + }); + }); + + editor.setModelMarkers(model, "typo", markers); } /** * Check for the content of the editor and return the format (json, yaml), the content and the thing type (TD, TM) - * @param { monaco object } editor + * @param { monaco object } editor * @returns {String, String, JSON Object} , [formatType, thingType, editorContent] */ export function getEditorData(editorInstance) { - const formatType = editorInstance["_domElement"].dataset.modeId - const editorContent = formatType === "json" ? JSON.parse(editorInstance.getValue()) : JSON.parse(convertTDYamlToJson(editorInstance.getValue())) - const thingType = editorContent["@type"] === "tm:ThingModel" ? "tm" : "td" - - return [formatType, thingType, editorContent] + const formatType = editorInstance["_domElement"].dataset.modeId; + const editorContent = + formatType === "json" + ? JSON.parse(editorInstance.getValue()) + : JSON.parse(convertTDYamlToJson(editorInstance.getValue())); + const thingType = checkDocumentType(editorContent); + + return [formatType, thingType, editorContent]; } /** @@ -419,11 +433,11 @@ export function getEditorData(editorInstance) { * @param { string } thingType - TM or TD to modify the tab icon */ function changeThingIcon(thingType) { - tabsLeft.forEach(tab => { - if (tab.classList.contains("active")) { - tab.children[0].innerText = thingType.toUpperCase() - } - }) + tabsLeft.forEach((tab) => { + if (tab.classList.contains("active")) { + tab.children[0].innerText = thingType.toUpperCase(); + } + }); } /** @@ -432,82 +446,78 @@ function changeThingIcon(thingType) { * Set the json btn to true */ addTab.addEventListener("click", () => { - createIde(++ideCount.ideNumber) - jsonBtn.checked = true -}) + createIde(++ideCount.ideNumber); + jsonBtn.checked = true; +}); /** * Getting and managing all event inside the tabs, such as closing and selecting each tab * @param {event} e - click event */ tabsLeftContainer.addEventListener("click", (e) => { - //getting the initial target - const selectedElement = e.target - clearConsole() - tabsLeft.forEach(tab => { - tab.children[3].classList.add("hidden") - }) - - //Add the active styling when tab is clicked - if (selectedElement.id == "tab" || selectedElement.parentElement.id == "tab") { - - //Removing the active style from all tabs - tabsLeft.forEach(tab => { - tab.classList.remove("active") - }) - //removing the active style from all editors - editorList.forEach(ide => { - ide["_domElement"].classList.remove("active") - }) - - //if the target element is the tab itself add the active class - //else if the target element is a child of the element add the active class to the parent element - if (selectedElement.id == "tab") { - selectedElement.classList.add("active") - } - else { - selectedElement.parentElement.classList.add("active") - } - - //Get the id of the element and setting the active style to the respective editor - if (selectedElement.dataset.tabId) { - editorList.forEach(ide => { - if (selectedElement.dataset.tabId === ide["_domElement"].dataset.ideId) { - ide["_domElement"].classList.add("active") + //getting the initial target + const selectedElement = e.target; + clearConsole(); + tabsLeft.forEach((tab) => { + tab.children[3].classList.add("hidden"); + }); + + //Add the active styling when tab is clicked + if (selectedElement.id == "tab" || selectedElement.parentElement.id == "tab") { + //Removing the active style from all tabs + tabsLeft.forEach((tab) => { + tab.classList.remove("active"); + }); + //removing the active style from all editors + editorList.forEach((ide) => { + ide["_domElement"].classList.remove("active"); + }); + + //if the target element is the tab itself add the active class + //else if the target element is a child of the element add the active class to the parent element + if (selectedElement.id == "tab") { + selectedElement.classList.add("active"); + } else { + selectedElement.parentElement.classList.add("active"); } - }) - } - else { - editorList.forEach(ide => { - if (selectedElement.parentElement.dataset.tabId === ide["_domElement"].dataset.ideId) { - ide["_domElement"].classList.add("active") + + //Get the id of the element and setting the active style to the respective editor + if (selectedElement.dataset.tabId) { + editorList.forEach((ide) => { + if (selectedElement.dataset.tabId === ide["_domElement"].dataset.ideId) { + ide["_domElement"].classList.add("active"); + } + }); + } else { + editorList.forEach((ide) => { + if (selectedElement.parentElement.dataset.tabId === ide["_domElement"].dataset.ideId) { + ide["_domElement"].classList.add("active"); + } + }); } - }) } - } - //Closing tabs only when the click event happens on the close icon of the tab - if (selectedElement.className == "close-tab" && tabsLeft.length >= 1) { - selectedElement.nextElementSibling.classList.remove("hidden") - } + //Closing tabs only when the click event happens on the close icon of the tab + if (selectedElement.className == "close-tab" && tabsLeft.length >= 1) { + selectedElement.nextElementSibling.classList.remove("hidden"); + } - findFileType() -}) + findFileType(); +}); /** * Find if active editor is json or yaml and change the json/yaml buttons respectively */ function findFileType() { - editorList.forEach(editor => { - if (editor["_domElement"].classList.contains("active")) { - if (editor["_domElement"].dataset.modeId === "json") { - jsonBtn.checked = true - } - else { - yamlBtn.checked = true - } - } - }) + editorList.forEach((editor) => { + if (editor["_domElement"].classList.contains("active")) { + if (editor["_domElement"].dataset.modeId === "json") { + jsonBtn.checked = true; + } else { + yamlBtn.checked = true; + } + } + }); } /** @@ -515,9 +525,9 @@ function findFileType() { * @param { String } docTitle - title of the TD/TM document */ function updateTabName(docTitle) { - tabsLeft.forEach(tab => { - if (tab.classList.contains("active")) { - tab.children[1].textContent = docTitle - } - }) -} \ No newline at end of file + tabsLeft.forEach((tab) => { + if (tab.classList.contains("active")) { + tab.children[1].textContent = docTitle; + } + }); +} diff --git a/packages/web/src/scripts/util.js b/packages/web/src/scripts/util.js index e255c448..1239098e 100644 --- a/packages/web/src/scripts/util.js +++ b/packages/web/src/scripts/util.js @@ -20,33 +20,41 @@ * and offers a few utility functions. */ -import { editor } from 'monaco-editor' -import { convertTDJsonToYaml, convertTDYamlToJson, tdValidator, tmValidator, compress, decompress } from '../../../core/dist/web-bundle.min.js' -import tdToOpenAPI from '@thingweb/open-api-converter/dist/web-bundle.min.js' -import tdToAsyncAPI from '@thingweb/async-api-converter/dist/web-bundle.min.js' -import { addDefaults, removeDefaults } from '../../../defaults/dist/web-bundle.min.js' +import { editor } from "monaco-editor"; +import { + convertTDJsonToYaml, + convertTDYamlToJson, + tdValidator, + tmValidator, + compress, + decompress, +} from "../../../core/dist/web-bundle.min.js"; +import tdToOpenAPI from "@thingweb/open-api-converter/dist/web-bundle.min.js"; +import tdToAsyncAPI from "@thingweb/async-api-converter/dist/web-bundle.min.js"; +import { addDefaults, removeDefaults } from "../../../defaults/dist/web-bundle.min.js"; import { AssetInterfacesDescription } from "@thingweb/aas-aid"; -import { validateJsonLdBtn, tmConformanceBtn, sectionHeaders } from './validation' +import { validateJsonLdBtn, tmConformanceBtn, sectionHeaders } from "./validation"; - -let errorMessages = [] +let errorMessages = []; /** * Fetch the TD from the given address and return the JSON object * @param {string} urlAddr url of the TD to fetch */ export function getTdUrl(urlAddr) { - return new Promise(resolve => { - + return new Promise((resolve) => { fetch(urlAddr) - .then(res => res.json()) - .then(data => { - resolve(data) - }, err => { alert("JSON could not be fetched from: " + urlAddr + "\n Error: " + err) }) - - }) + .then((res) => res.json()) + .then( + (data) => { + resolve(data); + }, + (err) => { + alert("JSON could not be fetched from: " + urlAddr + "\n Error: " + err); + } + ); + }); } - /** * Offers a given content for download as a file. * @param {string} fileName The title of the csv file @@ -54,26 +62,26 @@ export function getTdUrl(urlAddr) { * @param {string} type The content-type to output, e.g., text/csv;charset=utf-8; */ export function offerFileDownload(fileName, content, type) { - const blob = new Blob([content], { type }); - if (navigator.msSaveBlob) { // IE 10+ + if (navigator.msSaveBlob) { + // IE 10+ navigator.msSaveBlob(blob, fileName); } else { const link = document.createElement("a"); - if (link.download !== undefined) { // feature detection + if (link.download !== undefined) { + // feature detection // Browsers that support HTML5 download attribute - const url = URL.createObjectURL(blob) - link.setAttribute("href", url) - link.setAttribute("download", fileName) - link.setAttribute("src", url.slice(5)) + const url = URL.createObjectURL(blob); + link.setAttribute("href", url); + link.setAttribute("download", fileName); + link.setAttribute("src", url.slice(5)); link.style.visibility = "hidden"; - document.body.appendChild(link) + document.body.appendChild(link); link.click(); - document.body.removeChild(link) - URL.revokeObjectURL(url) + document.body.removeChild(link); + URL.revokeObjectURL(url); } } - } /** @@ -83,27 +91,26 @@ export function offerFileDownload(fileName, content, type) { */ export function generateTD(fileType, editorInstance) { return new Promise((res, rej) => { - const tdToValidate = editorInstance.getValue() + const tdToValidate = editorInstance.getValue(); if (tdToValidate === "") { - rej("No TD given to generate TD instance") - } - else if (fileType !== "json" && fileType !== "yaml") { - rej("Wrong content type required: " + fileType) - } - else { + rej("No TD given to generate TD instance"); + } else if (fileType !== "json" && fileType !== "yaml") { + rej("Wrong content type required: " + fileType); + } else { try { - const content = fileType === "json" - ? JSON.stringify(JSON.parse(convertTDYamlToJson(tdToValidate)), undefined, 4) - : convertTDJsonToYaml(tdToValidate) + const content = + fileType === "json" + ? JSON.stringify(JSON.parse(convertTDYamlToJson(tdToValidate)), undefined, 4) + : convertTDJsonToYaml(tdToValidate); - editor.setModelLanguage(editorInstance.getModel(), fileType) - editorInstance.setValue(content) + editor.setModelLanguage(editorInstance.getModel(), fileType); + editorInstance.setValue(content); } catch (err) { - rej("TD generation problem: " + err) + rej("TD generation problem: " + err); } } - }) + }); } /** @@ -113,24 +120,27 @@ export function generateTD(fileType, editorInstance) { */ export function generateOAP(fileType, editorInstance) { return new Promise((res, rej) => { - const tdToValidate = fileType === "json" - ? editorInstance.getValue() - : convertTDYamlToJson(editorInstance.getValue()) + const tdToValidate = + fileType === "json" ? editorInstance.getValue() : convertTDYamlToJson(editorInstance.getValue()); if (tdToValidate === "") { - rej("No TD given to generate OpenAPI instance") - } - else if (fileType !== "json" && fileType !== "yaml") { - rej("Wrong content type required: " + fileType) - } - else { - tdToOpenAPI(JSON.parse(tdToValidate)).then(openAPI => { - const content = fileType === "json" ? JSON.stringify(openAPI[fileType], undefined, 4) : openAPI[fileType] - editor.setModelLanguage(window.openApiEditor.getModel(), fileType) - window.openApiEditor.getModel().setValue(content) - }, err => { rej("OpenAPI generation problem: " + err) }) + rej("No TD given to generate OpenAPI instance"); + } else if (fileType !== "json" && fileType !== "yaml") { + rej("Wrong content type required: " + fileType); + } else { + tdToOpenAPI(JSON.parse(tdToValidate)).then( + (openAPI) => { + const content = + fileType === "json" ? JSON.stringify(openAPI[fileType], undefined, 4) : openAPI[fileType]; + editor.setModelLanguage(window.openApiEditor.getModel(), fileType); + window.openApiEditor.getModel().setValue(content); + }, + (err) => { + rej("OpenAPI generation problem: " + err); + } + ); } - }) + }); } /** @@ -140,24 +150,27 @@ export function generateOAP(fileType, editorInstance) { */ export function generateAAP(fileType, editorInstance) { return new Promise((res, rej) => { - const tdToValidate = fileType === "json" - ? editorInstance.getValue() - : convertTDYamlToJson(editorInstance.getValue()) + const tdToValidate = + fileType === "json" ? editorInstance.getValue() : convertTDYamlToJson(editorInstance.getValue()); if (tdToValidate === "") { - rej("No TD given to generate AsyncAPI instance") - } - else if (fileType !== "json" && fileType !== "yaml") { - rej("Wrong content type required: " + fileType) - } - else { - tdToAsyncAPI(JSON.parse(tdToValidate)).then(asyncAPI => { - const content = fileType === "json" ? JSON.stringify(asyncAPI[fileType], undefined, 4) : asyncAPI[fileType] - editor.setModelLanguage(window.asyncApiEditor.getModel(), fileType) - window.asyncApiEditor.getModel().setValue(content) - }, err => { rej("AsyncAPI generation problem: " + err) }) + rej("No TD given to generate AsyncAPI instance"); + } else if (fileType !== "json" && fileType !== "yaml") { + rej("Wrong content type required: " + fileType); + } else { + tdToAsyncAPI(JSON.parse(tdToValidate)).then( + (asyncAPI) => { + const content = + fileType === "json" ? JSON.stringify(asyncAPI[fileType], undefined, 4) : asyncAPI[fileType]; + editor.setModelLanguage(window.asyncApiEditor.getModel(), fileType); + window.asyncApiEditor.getModel().setValue(content); + }, + (err) => { + rej("AsyncAPI generation problem: " + err); + } + ); } - }) + }); } /** @@ -166,18 +179,17 @@ export function generateAAP(fileType, editorInstance) { * @param { Monaco Object } editorInstance - Monaco editor object */ export function generateAAS(fileType, editorInstance) { - const assetInterfaceDescription = new AssetInterfacesDescription() + const assetInterfaceDescription = new AssetInterfacesDescription(); - const tdToConvert = fileType === "json" - ? editorInstance.getValue() - : convertTDYamlToJson(editorInstance.getValue()) + const tdToConvert = + fileType === "json" ? editorInstance.getValue() : convertTDYamlToJson(editorInstance.getValue()); - const AASInstance = assetInterfaceDescription.transformTD2AID(tdToConvert) + const AASInstance = assetInterfaceDescription.transformTD2AID(tdToConvert); try { - const content = JSON.stringify(JSON.parse(AASInstance), undefined, 4) + const content = JSON.stringify(JSON.parse(AASInstance), undefined, 4); - editor.setModelLanguage(window.AASEditor.getModel(), 'json') - window.AASEditor.getModel().setValue(content) + editor.setModelLanguage(window.AASEditor.getModel(), "json"); + window.AASEditor.getModel().setValue(content); } catch (err) { console.error(err); } @@ -188,14 +200,15 @@ export function generateAAS(fileType, editorInstance) { * to the TD in the editor */ export function addDefaultsUtil(editorInstance) { - const tdToExtend = editorInstance["_domElement"].dataset.modeId === "json" - ? JSON.parse(editorInstance.getValue()) - : JSON.parse(convertTDYamlToJson(editorInstance.getValue())) - addDefaults(tdToExtend) - window.defaultsEditor.getModel().setValue(JSON.stringify(tdToExtend, undefined, 4)) - editor.setModelLanguage(window.defaultsEditor.getModel(), editorInstance["_domElement"].dataset.modeId) + const tdToExtend = + editorInstance["_domElement"].dataset.modeId === "json" + ? JSON.parse(editorInstance.getValue()) + : JSON.parse(convertTDYamlToJson(editorInstance.getValue())); + addDefaults(tdToExtend); + window.defaultsEditor.getModel().setValue(JSON.stringify(tdToExtend, undefined, 4)); + editor.setModelLanguage(window.defaultsEditor.getModel(), editorInstance["_domElement"].dataset.modeId); if (editorInstance["_domElement"].dataset.modeId === "yaml") { - generateTD("yaml", window.defaultsEditor) + generateTD("yaml", window.defaultsEditor); } } @@ -205,14 +218,15 @@ export function addDefaultsUtil(editorInstance) { * in the editor */ export function removeDefaultsUtil(editorInstance) { - const tdToReduce = editorInstance["_domElement"].dataset.modeId === "json" - ? JSON.parse(editorInstance.getValue()) - : JSON.parse(convertTDYamlToJson(editorInstance.getValue())) - removeDefaults(tdToReduce) - window.defaultsEditor.getModel().setValue(JSON.stringify(tdToReduce, undefined, 4)) - editor.setModelLanguage(window.defaultsEditor.getModel(), editorInstance["_domElement"].dataset.modeId) + const tdToReduce = + editorInstance["_domElement"].dataset.modeId === "json" + ? JSON.parse(editorInstance.getValue()) + : JSON.parse(convertTDYamlToJson(editorInstance.getValue())); + removeDefaults(tdToReduce); + window.defaultsEditor.getModel().setValue(JSON.stringify(tdToReduce, undefined, 4)); + editor.setModelLanguage(window.defaultsEditor.getModel(), editorInstance["_domElement"].dataset.modeId); if (editorInstance["_domElement"].dataset.modeId === "yaml") { - generateTD("yaml", window.defaultsEditor) + generateTD("yaml", window.defaultsEditor); } } @@ -227,86 +241,94 @@ export function removeDefaultsUtil(editorInstance) { * *Error circle icon gotten from: https://fontawesome.com/icons/circle-xmark?f=classic&s=solid */ export function validate(thingType, editorContent) { - - resetValidationStatus() - - const checkJsonLd = validateJsonLdBtn.checked - const checkTmConformance = tmConformanceBtn.checked - - const validator = thingType === "td" ? tdValidator : tmValidator - - validator(editorContent, log, { checkDefaults: true, checkJsonLd, checkTmConformance }) - .then(result => { - Object.keys(result.report).forEach(el => { - const spotName = "spot-" + el - document.getElementById(spotName).removeAttribute('open') - const resultIcon = document.getElementById(spotName).children[0].children[0].children[0] - - if (result.report[el] === "passed") { - resultIcon.children[0].setAttribute("d", "M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM369 209L241 337c-9.4 9.4-24.6 9.4-33.9 0l-64-64c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l47 47L335 175c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9z") - resultIcon.classList.remove("neutral-circle-icon") - resultIcon.classList.add("success-circle-icon") - } - else if (result.report[el] === "warning") { - resultIcon.children[0].setAttribute("d", "M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zm0-384c13.3 0 24 10.7 24 24V264c0 13.3-10.7 24-24 24s-24-10.7-24-24V152c0-13.3 10.7-24 24-24zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z") - resultIcon.classList.remove("neutral-circle-icon") - resultIcon.classList.add("warning-circle-icon") - } - else if (result.report[el] === "failed") { - resultIcon.children[0].setAttribute("d", "M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM175 175c9.4-9.4 24.6-9.4 33.9 0l47 47 47-47c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-47 47 47 47c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0l-47-47-47 47c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l47-47-47-47c-9.4-9.4-9.4-24.6 0-33.9z") - resultIcon.classList.remove("neutral-circle-icon") - resultIcon.classList.add("error-circle-icon") - } - else if (result.report[el] === null) { + resetValidationStatus(); + + const checkJsonLd = validateJsonLdBtn.checked; + const checkTmConformance = tmConformanceBtn.checked; + + const validator = thingType === "td" ? tdValidator : tmValidator; + + validator(editorContent, log, { checkDefaults: true, checkJsonLd, checkTmConformance }).then((result) => { + Object.keys(result.report).forEach((el) => { + const spotName = "spot-" + el; + document.getElementById(spotName).removeAttribute("open"); + const resultIcon = document.getElementById(spotName).children[0].children[0].children[0]; + + if (result.report[el] === "passed") { + resultIcon.children[0].setAttribute( + "d", + "M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM369 209L241 337c-9.4 9.4-24.6 9.4-33.9 0l-64-64c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l47 47L335 175c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9z" + ); + resultIcon.classList.remove("neutral-circle-icon"); + resultIcon.classList.add("success-circle-icon"); + } else if (result.report[el] === "warning") { + resultIcon.children[0].setAttribute( + "d", + "M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zm0-384c13.3 0 24 10.7 24 24V264c0 13.3-10.7 24-24 24s-24-10.7-24-24V152c0-13.3 10.7-24 24-24zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z" + ); + resultIcon.classList.remove("neutral-circle-icon"); + resultIcon.classList.add("warning-circle-icon"); + } else if (result.report[el] === "failed") { + resultIcon.children[0].setAttribute( + "d", + "M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM175 175c9.4-9.4 24.6-9.4 33.9 0l47 47 47-47c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-47 47 47 47c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0l-47-47-47 47c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l47-47-47-47c-9.4-9.4-9.4-24.6 0-33.9z" + ); + resultIcon.classList.remove("neutral-circle-icon"); + resultIcon.classList.add("error-circle-icon"); + } else if (result.report[el] === null) { + // do nothing + } else { + console.error("unknown report feedback value"); + } + }); + + Object.keys(result.details).forEach((el) => { + const detailsName = el + "-section"; + if (document.getElementById(detailsName)) { + document.getElementById(detailsName).removeAttribute("open"); + const detailsIcon = document.getElementById(detailsName).children[0].children[0].children[0]; + + if (result.details[el] === "passed") { + detailsIcon.children[0].setAttribute( + "d", + "M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM369 209L241 337c-9.4 9.4-24.6 9.4-33.9 0l-64-64c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l47 47L335 175c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9z" + ); + detailsIcon.classList.remove("neutral-circle-icon"); + detailsIcon.classList.add("success-circle-icon"); + } else if (result.details[el] === "warning" || result.details[el] === "not-impl") { + detailsIcon.children[0].setAttribute( + "d", + "M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zm0-384c13.3 0 24 10.7 24 24V264c0 13.3-10.7 24-24 24s-24-10.7-24-24V152c0-13.3 10.7-24 24-24zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z" + ); + detailsIcon.classList.remove("neutral-circle-icon"); + detailsIcon.classList.add("warning-circle-icon"); + } else if (result.details[el] === "failed") { + detailsIcon.children[0].setAttribute( + "d", + "M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM175 175c9.4-9.4 24.6-9.4 33.9 0l47 47 47-47c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-47 47 47 47c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0l-47-47-47 47c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l47-47-47-47c-9.4-9.4-9.4-24.6 0-33.9z" + ); + detailsIcon.classList.remove("neutral-circle-icon"); + detailsIcon.classList.add("error-circle-icon"); + } else if (result.details[el] === null) { // do nothing + } else { + console.error("unknown report feedback value"); } - else { - console.error("unknown report feedback value") - } - }) - - Object.keys(result.details).forEach(el => { - const detailsName = el + "-section" - if (document.getElementById(detailsName)) { - document.getElementById(detailsName).removeAttribute('open') - const detailsIcon = document.getElementById(detailsName).children[0].children[0].children[0] - - if (result.details[el] === "passed") { - detailsIcon.children[0].setAttribute("d", "M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM369 209L241 337c-9.4 9.4-24.6 9.4-33.9 0l-64-64c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l47 47L335 175c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9z") - detailsIcon.classList.remove("neutral-circle-icon") - detailsIcon.classList.add("success-circle-icon") - } - else if (result.details[el] === "warning" || result.details[el] === "not-impl") { - detailsIcon.children[0].setAttribute("d", "M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zm0-384c13.3 0 24 10.7 24 24V264c0 13.3-10.7 24-24 24s-24-10.7-24-24V152c0-13.3 10.7-24 24-24zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z") - detailsIcon.classList.remove("neutral-circle-icon") - detailsIcon.classList.add("warning-circle-icon") - } - else if (result.details[el] === "failed") { - detailsIcon.children[0].setAttribute("d", "M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM175 175c9.4-9.4 24.6-9.4 33.9 0l47 47 47-47c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-47 47 47 47c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0l-47-47-47 47c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l47-47-47-47c-9.4-9.4-9.4-24.6 0-33.9z") - detailsIcon.classList.remove("neutral-circle-icon") - detailsIcon.classList.add("error-circle-icon") - } - else if (result.details[el] === null) { - // do nothing - } - else { - console.error("unknown report feedback value") - } - } - }) + } + }); - Object.keys(result.detailComments).forEach(el => { - const detailsName = el + "-section" + Object.keys(result.detailComments).forEach((el) => { + const detailsName = el + "-section"; - if (document.querySelector(`#${detailsName} .description`)) { - const detailsDesc = document.querySelector(`#${detailsName} .description`) + if (document.querySelector(`#${detailsName} .description`)) { + const detailsDesc = document.querySelector(`#${detailsName} .description`); - detailsDesc.textContent = result.detailComments[el] - } - }) + detailsDesc.textContent = result.detailComments[el]; + } + }); - populateCategory(errorMessages) - }) + populateCategory(errorMessages); + }); } /** @@ -314,27 +336,29 @@ export function validate(thingType, editorContent) { */ export function resetValidationStatus() { while (errorMessages.length > 0) { - errorMessages.pop() + errorMessages.pop(); } - sectionHeaders.forEach(header => { - const headerIcon = header.children[0].children[0] + sectionHeaders.forEach((header) => { + const headerIcon = header.children[0].children[0]; - if(!headerIcon.classList.contains("neutral-circle-icon")){ - headerIcon.children[0].setAttribute("d", "M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512z") - headerIcon.classList.remove("success-circle-icon", "warning-circle-icon", "error-circle-icon") - headerIcon.classList.add("neutral-circle-icon") + if (!headerIcon.classList.contains("neutral-circle-icon")) { + headerIcon.children[0].setAttribute("d", "M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512z"); + headerIcon.classList.remove("success-circle-icon", "warning-circle-icon", "error-circle-icon"); + headerIcon.classList.add("neutral-circle-icon"); } - }) - - document.querySelectorAll("#spot-json, #spot-schema, #spot-defaults, #spot-jsonld, #spot-additional").forEach(category => { - category.open = false - category.classList.add("disabled") - const categoryContainer = category.querySelector("ul.section-content") - while (categoryContainer.children.length > 0) { - categoryContainer.children[0].remove() - } - }) + }); + + document + .querySelectorAll("#spot-json, #spot-schema, #spot-defaults, #spot-jsonld, #spot-additional") + .forEach((category) => { + category.open = false; + category.classList.add("disabled"); + const categoryContainer = category.querySelector("ul.section-content"); + while (categoryContainer.children.length > 0) { + categoryContainer.children[0].remove(); + } + }); } /** @@ -342,7 +366,7 @@ export function resetValidationStatus() { * @param { String } message - text sent from the validator */ function log(message) { - errorMessages.push(message) + errorMessages.push(message); } //TODO: This function should only be used for the moment being as it should be changed or adapted when the corresponding changes to the Validator have been finalized @@ -352,34 +376,39 @@ function log(message) { */ function populateCategory(messagesList) { // console.log(messagesList); - document.querySelectorAll("#spot-json, #spot-schema, #spot-defaults, #spot-jsonld, #spot-additional").forEach(category => { - const categoryContainer = category.querySelector("ul.section-content") - category.classList.remove("disabled") - - const categoryIcon = category.children[0].children[0].children[0] - - if (categoryIcon.classList.contains("error-circle-icon") || categoryIcon.classList.contains("warning-circle-icon")) { - const noticePrompt = document.createElement("p") - noticePrompt.textContent = "*This feature is still in the testing phase, and it may not refer to the correct source of the error*" - noticePrompt.classList.add("notice-prompt") - categoryContainer.append(noticePrompt) - messagesList.forEach(message => { - const listElement = document.createElement("li") - listElement.textContent = message - categoryContainer.append(listElement) - }) - } - else if (categoryIcon.classList.contains("success-circle-icon")) { - const successMessage = document.createElement("li") - successMessage.textContent = "Validated Successfully" - categoryContainer.append(successMessage) - } - else { - const nullMessage = document.createElement("li") - nullMessage.textContent = "A previous validation has failed or it is only available for Thing Descriptions" - categoryContainer.append(nullMessage) - } - }) + document + .querySelectorAll("#spot-json, #spot-schema, #spot-defaults, #spot-jsonld, #spot-additional") + .forEach((category) => { + const categoryContainer = category.querySelector("ul.section-content"); + category.classList.remove("disabled"); + + const categoryIcon = category.children[0].children[0].children[0]; + + if ( + categoryIcon.classList.contains("error-circle-icon") || + categoryIcon.classList.contains("warning-circle-icon") + ) { + const noticePrompt = document.createElement("p"); + noticePrompt.textContent = + "*This feature is still in the testing phase, and it may not refer to the correct source of the error*"; + noticePrompt.classList.add("notice-prompt"); + categoryContainer.append(noticePrompt); + messagesList.forEach((message) => { + const listElement = document.createElement("li"); + listElement.textContent = message; + categoryContainer.append(listElement); + }); + } else if (categoryIcon.classList.contains("success-circle-icon")) { + const successMessage = document.createElement("li"); + successMessage.textContent = "Validated Successfully"; + categoryContainer.append(successMessage); + } else { + const nullMessage = document.createElement("li"); + nullMessage.textContent = + "A previous validation has failed or it is only available for Thing Descriptions"; + categoryContainer.append(nullMessage); + } + }); } /** @@ -388,16 +417,15 @@ function populateCategory(messagesList) { * @param {string} format "json" or "yaml" */ export async function save(formatType, thingType, editorContent) { - - const value = JSON.stringify(editorContent) + const value = JSON.stringify(editorContent); if (!value) { alert(`No ${thingType.toUpperCase()} provided`); return; } - const data = thingType + formatType + value - const compressed = compress(data) - return `${window.location.href}#${compressed}` + const data = thingType + formatType + value; + const compressed = compress(data); + return `${window.location.href}#${compressed}`; } /** @@ -406,24 +434,23 @@ export async function save(formatType, thingType, editorContent) { * @param {string} format "json" or "yaml" */ export async function openEditdor(formatType, thingType, editorInstance) { - - const value = formatType === "yaml" ? convertTDYamlToJson(editorInstance.getValue()) : editorInstance.getValue() + const value = formatType === "yaml" ? convertTDYamlToJson(editorInstance.getValue()) : editorInstance.getValue(); if (!value) { - alert(`No ${thingType.toUpperCase()} provided`) + alert(`No ${thingType.toUpperCase()} provided`); return; } - const data = thingType + formatType + value - const compressed = compress(data) - const URL = `https://eclipse.github.io/editdor/?td=${compressed}` - window.open(URL, '_blank') + const data = thingType + formatType + value; + const compressed = compress(data); + const URL = `https://eclipse.github.io/editdor/?td=${compressed}`; + window.open(URL, "_blank"); } /** * Given a URL fragment construct current value of an editor. */ export function getEditorValue(fragment) { - const data = decompress(fragment) - return data || ''; + const data = decompress(fragment); + return data || ""; } // Monaco Location Pointer @@ -434,24 +461,24 @@ export function getEditorValue(fragment) { * @param {ITextModel} The text model of Monaco editor */ export function findJSONLocationOfMonacoText(text, textModel) { - const matches = textModel.findMatches(text, false, false, false, null, false) - const results = [] + const matches = textModel.findMatches(text, false, false, false, null, false); + const results = []; - matches.forEach(match => { - const path = searchPath(textModel, getEndPositionOfMatch(match)) - results.push({ match, path }) - }) + matches.forEach((match) => { + const path = searchPath(textModel, getEndPositionOfMatch(match)); + results.push({ match, path }); + }); - return results + return results; } -const QUOTE = '"' -const LEFT_BRACKET = "{" -const RIGHT_BRACKET = "}" -const SEMICOLON = ":" -const LEFT_SQUARE_BRACKET = "[" -const RIGHT_SQUARE_BRACKET = "]" -const COMMA = "," +const QUOTE = '"'; +const LEFT_BRACKET = "{"; +const RIGHT_BRACKET = "}"; +const SEMICOLON = ":"; +const LEFT_SQUARE_BRACKET = "["; +const RIGHT_SQUARE_BRACKET = "]"; +const COMMA = ","; /** * Looks for specific characters on the model to figure out the path of the position/search text @@ -460,104 +487,104 @@ const COMMA = "," * @returns A string that is the path of the searched text. Search is done with the text's position on the editor */ function searchPath(textModel, position) { - let path = '/' - let parentKey = '' - const stack = [] - let recordingParent = false - let isValue = true - let commaCount = 0 + let path = "/"; + let parentKey = ""; + const stack = []; + let recordingParent = false; + let isValue = true; + let commaCount = 0; for (let i = position.lineNumber; i > 0; i--) { - const currentColumnIndex = (i === position.lineNumber ? position.column : textModel.getLineLength(i)) - 1 - const lineContent = textModel.getLineContent(i) + const currentColumnIndex = (i === position.lineNumber ? position.column : textModel.getLineLength(i)) - 1; + const lineContent = textModel.getLineContent(i); for (let j = currentColumnIndex; j >= 0; j--) { - const currentChar = lineContent[j] + const currentChar = lineContent[j]; if (recordingParent) { if (currentChar === QUOTE) { if (stack[stack.length - 1] === QUOTE) { - stack.pop() - path = "/" + parentKey + path - parentKey = "" - recordingParent = false + stack.pop(); + path = "/" + parentKey + path; + parentKey = ""; + recordingParent = false; } else { - stack.push(currentChar) - continue + stack.push(currentChar); + continue; } } if (stack[stack.length - 1] === QUOTE) { - parentKey = currentChar + parentKey - continue + parentKey = currentChar + parentKey; + continue; } } else { if (currentChar === SEMICOLON) { - recordingParent = isValue + recordingParent = isValue; if (stack.length > 0) { - const top = stack[stack.length - 1] + const top = stack[stack.length - 1]; if (top === LEFT_SQUARE_BRACKET) { - parentKey = "/" + commaCount.toString() - stack.pop() - recordingParent = true + parentKey = "/" + commaCount.toString(); + stack.pop(); + recordingParent = true; } if (top === LEFT_BRACKET) { - stack.pop() - recordingParent = true + stack.pop(); + recordingParent = true; } } } if (currentChar === LEFT_SQUARE_BRACKET) { - isValue = false + isValue = false; if (stack.length > 0) { if (stack[stack.length - 1] === RIGHT_SQUARE_BRACKET) { - stack.pop() + stack.pop(); } if (stack[stack.length - 1] === LEFT_BRACKET) { - stack.pop() - stack.push(currentChar) + stack.pop(); + stack.push(currentChar); } } else { - commaCount = 0 - stack.push(currentChar) + commaCount = 0; + stack.push(currentChar); } } if (currentChar === LEFT_BRACKET) { - isValue = false + isValue = false; if (stack.length > 0 && stack[stack.length - 1] === RIGHT_BRACKET) { - stack.pop() + stack.pop(); } else { - commaCount = 0 - stack.push(currentChar) + commaCount = 0; + stack.push(currentChar); } } if (currentChar === COMMA) { - isValue = false + isValue = false; if (stack.length <= 1) { - commaCount++ + commaCount++; } } if (currentChar === RIGHT_SQUARE_BRACKET) { - isValue = false - stack.push(currentChar) + isValue = false; + stack.push(currentChar); } if (currentChar === RIGHT_BRACKET) { - isValue = false - stack.push(currentChar) + isValue = false; + stack.push(currentChar); } } } } - return path + return path; } /** @@ -568,8 +595,8 @@ function searchPath(textModel, position) { function getEndPositionOfMatch(match) { return { column: match.range.endColumn, - lineNumber: match.range.endLineNumber - } + lineNumber: match.range.endLineNumber, + }; } /** @@ -580,17 +607,44 @@ function getEndPositionOfMatch(match) { * @returns The location of the text on Monaco editor by describing its column and line number range */ export function findMonacoLocationOfJSONText(jsonPath, text, textModel) { - const results = findJSONLocationOfMonacoText(text, textModel) - let monacoLocation = {} + const results = findJSONLocationOfMonacoText(text, textModel); + let monacoLocation = {}; if (results) { - results.forEach(result => { + results.forEach((result) => { if (jsonPath.localeCompare(result.path) === 0) { - monacoLocation = result.match.range - return + monacoLocation = result.match.range; + return; } - }) + }); } - return monacoLocation + return monacoLocation; } + +/** + * Checks if the input is a TD or TM + * @param {jsonDocument} + * @returns "tm" or "td" + */ +export function checkDocumentType(jsonDocument) { + //TODO: Move to core package after refactoring + if ( "@type" in jsonDocument ){ + if (typeof jsonDocument["@type"] === "string") { + if (jsonDocument["@type"] === "tm:ThingModel") { + return "tm"; + } else { + return "td"; + } + } else { + // then it is an array + if (jsonDocument["@type"].includes("tm:ThingModel")) { + return "tm"; + } else { + return "td"; + } + } + } else { + return "td" + } +} \ No newline at end of file