META-INF.dirigible.ide-hdbti.js.editor.js Maven / Gradle / Ivy
/*
* Copyright (c) 2022 codbex or an codbex affiliate company and contributors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* SPDX-FileCopyrightText: 2022 codbex or an codbex affiliate company and contributors
* SPDX-License-Identifier: EPL-2.0
*/
let editorView = angular.module('hdbti-editor', []);
editorView.factory('$messageHub', [function () {
let messageHub = new FramesMessageHub();
let announceAlert = function (title, message, type) {
messageHub.post({
data: {
title: title,
message: message,
type: type
}
}, 'ide.alert');
};
let announceAlertError = function (title, message) {
announceAlert(title, message, "error");
};
let message = function (evtName, data) {
messageHub.post({ data: data }, evtName);
};
// Temp thing
let post = function (data, evtName) {
messageHub.post(data, evtName);
};
let on = function (topic, callback) {
messageHub.subscribe(callback, topic);
};
return {
announceAlert: announceAlert,
announceAlertError: announceAlertError,
message: message,
post: post,
on: on,
};
}]);
editorView.directive('validateInput', () => {
return {
restrict: 'A',
require: 'ngModel',
scope: {
regex: '@validateInput'
},
link: (scope, element, attrs, controller) => {
controller.$validators.forbiddenName = value => {
if (attrs.hasOwnProperty("id")) {
if (attrs["id"] === "table") {
let correctSchema = scope.$parent.validateSchema();
let correctTable = scope.$parent.validateTable(value);
scope.$parent.setSaveEnabled(correctSchema && correctTable);
return correctTable;
} else if (attrs["id"] === "schema") {
let correctTable = scope.$parent.validateTable();
let correctSchema = scope.$parent.validateSchema(value);
scope.$parent.setSaveEnabled(correctSchema && correctTable);
return correctSchema;
} else if (attrs["id"] === "filepath") {
return scope.$parent.validateFilepath(element, value, scope.regex);
} else {
return scope.$parent.validateInput(element, value, scope.regex);
}
}
return scope.$parent.validateInput(element, value, scope.regex);
};
}
};
});
editorView.directive('uniqueField', () => {
return {
restrict: 'A',
require: 'ngModel',
scope: {
regex: '@uniqueField'
},
link: (scope, element, attrs, controller) => {
controller.$validators.forbiddenName = value => {
let unique = true;
let correct = RegExp(scope.regex, 'g').test(value);
if (correct) {
if ("index" in attrs) {
for (let i = 0; i < scope.$parent.jsonData[scope.$parent.activeItemId].keys.length; i++) {
if (i != attrs.index) {
if (value === scope.$parent.jsonData[scope.$parent.activeItemId].keys[i].column) {
unique = false;
break;
}
}
}
} else if ("kindex" in attrs && "vindex" in attrs) {
for (let i = 0; i < scope.$parent.jsonData[scope.$parent.activeItemId].keys[attrs.kindex].values.length; i++) {
if (i != attrs.vindex) {
if (value === scope.$parent.$parent.jsonData[scope.$parent.activeItemId].keys[attrs.kindex].values[i]) {
unique = false;
break;
}
}
}
}
}
if (correct && unique) {
element.removeClass("error-input");
} else {
element.addClass('error-input');
}
scope.$parent.setSaveEnabled(correct && unique);
return unique;
};
}
};
});
editorView.controller('EditorViewController', ['$scope', '$http', '$messageHub', '$window', function ($scope, $http, $messageHub, $window) {
let isFileChanged = false;
const ctrlKey = 17;
let ctrlDown = false;
let isMac = false;
let workspace = 'workspace'; // This needs to be replace with an API.
let emptyHdbti = ["", "import=[]", "import=[];", "import=[{}]", "import=[{}];"];
let tableValidationList = [
{ regex: '^[A-Za-z0-9_\\-$.]+::[A-Za-z0-9_\\-$.]+$', contains: '::' },
{ regex: '^[A-Za-z0-9_\\-$.]+$' }
];
let schemaValidation = '^[A-Za-z0-9_\\-$.]+$';
let csrfToken;
let tableField = document.getElementById("table");
let schemaField = document.getElementById("schema");
$scope.schemaError = { hasError: false, msg: '' };
$scope.tableError = { hasError: false, msg: '' };
$scope.filepathError = {
hasError: false,
msg: 'Path can only contain letters (a-z, A-Z), numbers (0-9), hyphens ("-"), forward slashes ("/"), dots ("."), underscores ("_"), and dollar signs ("$")'
};
$scope.fileExists = true;
$scope.saveEnabled = true;
$scope.editEnabled = false;
$scope.dataEmpty = true;
$scope.dataLoaded = false;
$scope.jsonData = [];
$scope.activeItemId = 0;
$scope.delimiterList = [',', '\\t', '|', ';', '#'];
$scope.quoteCharList = ["'", "\"", "#"];
$scope.openFile = function () {
if ($scope.checkResource($scope.csvimData[$scope.activeItemId].file)) {
$messageHub.post({
resourcePath: `/${workspace}${$scope.csvimData[$scope.activeItemId].file}`,
resourceLabel: $scope.csvimData[$scope.activeItemId].name,
contentType: "text/csv",
extraArgs: {
"header": $scope.csvimData[$scope.activeItemId].header,
"delimiter": $scope.csvimData[$scope.activeItemId].delimField,
"quotechar": $scope.csvimData[$scope.activeItemId].delimEnclosing
},
}, 'ide-core.openEditor');
}
};
$scope.validateTable = function (value = null) {
if (value === null) value = tableField.value;
let correct = false;
if (value) {
for (let i = 0; i < tableValidationList.length; i++) {
if (tableValidationList[i].contains) {
if (value.includes(tableValidationList[i].contains)) {
correct = RegExp(tableValidationList[i].regex, 'g').test(value);
break;
}
} else {
correct = RegExp(tableValidationList[i].regex, 'g').test(value);
break;
}
}
}
$scope.showTableError(!correct);
return correct;
};
$scope.validateSchema = function (value = null) {
if (value === null) value = schemaField.value;
let correct = false;
if (!value) {
if (tableField.value.trim().length > 0 && tableField.value.includes('::')) {
correct = true;
$scope.showSchemaError(false);
} else {
correct = false;
$scope.showSchemaError(
true,
'Schema must be specified either in the table name ("schemaName::tableName") or in the schema field.'
);
}
$scope.setSaveEnabled(correct);
return correct;
}
correct = RegExp(schemaValidation, 'g').test(value);
$scope.showSchemaError(!correct);
$scope.setSaveEnabled(correct);
return correct;
};
$scope.validateFilepath = function (element, value, regex) {
let correct = false;
if (value) {
correct = RegExp(regex, 'g').test(value);
}
$scope.fileExists = true;
if (correct) {
element.removeClass("error-input");
$scope.filepathError.hasError = false;
}
else {
element.addClass('error-input');
$scope.filepathError.hasError = true;
}
$scope.setSaveEnabled(correct);
return correct;
};
$scope.validateInput = function (element, value, regex) {
let correct = false;
if (!value) return correct;
correct = RegExp(regex, 'g').test(value);
if (correct) element.removeClass("error-input");
else element.addClass('error-input');
$scope.setSaveEnabled(correct);
return correct;
};
$scope.showTableError = function (hasError, msg) {
$scope.tableError.hasError = hasError;
if (msg !== undefined) {
$scope.tableError.msg = msg;
} else $scope.tableError.msg = 'Table can only contain letters (a-z, A-Z), numbers (0-9), hyphens ("-"), dots ("."), underscores ("_"), and dollar signs ("$"). Two colons ("::") are permitted only when table name contains schema ("schemaName::tableName").';
if (hasError) tableField.classList.add('error-input');
else tableField.classList.remove('error-input');
};
$scope.showSchemaError = function (hasError, msg = '') {
$scope.schemaError.hasError = hasError;
if (msg !== undefined) {
$scope.schemaError.msg = msg;
} else $scope.schemaError.msg = 'Schema can only contain letters (a-z, A-Z), numbers (0-9), hyphens ("-"), dots ("."), underscores ("_"), and dollar signs ("$")';
if (hasError) schemaField.classList.add('error-input');
else schemaField.classList.remove('error-input');
};
$scope.inputsHaveErrors = function () {
let inputs = document.getElementsByClassName("form-control");
for (let i = 0; i < inputs.length; i++) {
if (inputs[i].classList.contains('error-input')) return true;
}
return false;
};
$scope.setSaveEnabled = function (enabled) {
if (enabled && !$scope.inputsHaveErrors()) $scope.saveEnabled = true;
else $scope.saveEnabled = false;
};
$scope.setEditEnabled = function (enabled) {
if (enabled != undefined) {
$scope.editEnabled = enabled;
} else {
$scope.editEnabled = !$scope.editEnabled;
}
};
$scope.addNew = function () {
let newCsv = {
"name": "Untitled",
"visible": true,
"table": "",
"schema": "",
"file": "",
"header": false,
"useHeaderNames": false,
"delimField": ";",
"delimEnclosing": "\"",
"distinguishEmptyFromNull": true,
"keys": []
};
// Clean search bar
$scope.filesSearch = "";
$scope.filterFiles();
$scope.jsonData.push(newCsv);
$scope.activeItemId = $scope.jsonData.length - 1;
$scope.dataEmpty = false;
$scope.setEditEnabled(false);
$scope.fileChanged();
};
$scope.getFileName = function (str, canBeEmpty = true) {
if (canBeEmpty) {
return str.split('\\').pop().split('/').pop();
}
let title = str.split('\\').pop().split('/').pop();
if (title) return title;
else return "Untitled";
};
$scope.fileSelected = function (id) {
if (!$scope.inputsHaveErrors()) {
$scope.setEditEnabled(false);
$scope.fileExists = true;
$scope.activeItemId = id;
}
};
$scope.isDelimiterSupported = function (delimiter) {
return $scope.delimiterList.includes(delimiter);
};
$scope.isQuoteCharSupported = function (quoteChar) {
return $scope.quoteCharList.includes(quoteChar);
};
$scope.delimiterChanged = function (delimiter) {
$scope.jsonData[$scope.activeItemId].delimField = delimiter;
$scope.fileChanged();
};
$scope.quoteCharChanged = function (quoteChar) {
$scope.jsonData[$scope.activeItemId].delimEnclosing = quoteChar;
$scope.fileChanged();
};
$scope.addValueToKey = function (column) {
let entry_num = 1;
for (let i = 0; i < $scope.jsonData[$scope.activeItemId].keys.length; i++) {
if ($scope.jsonData[$scope.activeItemId].keys[i].column === column) {
for (let k = 0; k < $scope.jsonData[$scope.activeItemId].keys[i].values.length; k++) {
let num = getNumber(
$scope.jsonData[$scope.activeItemId].keys[i].values[k].replace("NEW_ENTRY_", '')
);
if (!isNaN(num) && num >= entry_num) {
entry_num = num + 1;
}
}
$scope.jsonData[$scope.activeItemId].keys[i].values.push(`NEW_ENTRY_${entry_num}`);
break;
}
}
$scope.fileChanged();
};
$scope.removeValueFromKey = function (columnIndex, valueIndex) {
$scope.jsonData[$scope.activeItemId].keys[columnIndex].values.splice(valueIndex, 1);
$scope.fileChanged();
};
$scope.addKeyColumn = function () {
let num = 1;
for (let i = 0; i < $scope.jsonData[$scope.activeItemId].keys.length; i++) {
if ($scope.jsonData[$scope.activeItemId].keys[i].column === `NEW_ENTRY_${num}`) {
num++;
}
}
$scope.jsonData[$scope.activeItemId].keys.push(
{
"column": `NEW_ENTRY_${num}`,
"values": []
}
);
$scope.fileChanged();
};
$scope.removeKeyColumn = function (index) {
$scope.jsonData[$scope.activeItemId].keys.splice(index, 1);
$scope.fileChanged();
};
$scope.save = function () {
if (isFileChanged && $scope.saveEnabled) {
$scope.checkResource($scope.jsonData[$scope.activeItemId].file);
$scope.jsonData[$scope.activeItemId].name = $scope.getFileName($scope.jsonData[$scope.activeItemId].file, false);
parseJsonToHdbti(JSON.stringify($scope.jsonData, cleanForOutput, 2));
}
};
$scope.deleteFile = function () {
// Clean search bar
$scope.jsonData.splice($scope.activeItemId, 1);
$scope.setEditEnabled(false);
$scope.fileExists = true;
if ($scope.jsonData.length > 0) {
$scope.dataEmpty = false;
$scope.activeItemId = $scope.jsonData.length - 1;
} else {
$scope.dataEmpty = true;
$scope.activeItemId = 0;
}
$scope.fileChanged();
};
$scope.filterFiles = function () {
if ($scope.filesSearch) {
for (let i = 0; i < $scope.jsonData.length; i++) {
if ($scope.jsonData[i].name.toLowerCase().includes($scope.filesSearch.toLowerCase())) {
$scope.jsonData[i].visible = true;
} else {
$scope.jsonData[i].visible = false;
}
}
} else {
for (let i = 0; i < $scope.jsonData.length; i++) {
$scope.jsonData[i].visible = true;
}
}
};
$scope.fileChanged = function () {
isFileChanged = true;
$messageHub.message('editor.file.dirty', $scope.file);
};
$scope.keyDownFunc = function ($event) {
if (
ctrlDown &&
String.fromCharCode($event.which).toLowerCase() == 's'
) {
$event.preventDefault();
if (isFileChanged)
$scope.save();
}
};
angular.element($window).bind("keyup", function (/*$event*/) {
ctrlDown = false;
});
angular.element($window).bind("keydown", function ($event) {
if (isMac && "metaKey" in $event && $event.metaKey)
ctrlDown = true;
else if ($event.keyCode == ctrlKey)
ctrlDown = true;
});
$scope.checkResource = function (resourcePath) {
if (resourcePath != "") {
let xhr = new XMLHttpRequest();
xhr.open('HEAD', `/services/v4/ide/workspaces/${workspace}${resourcePath}`, false);
xhr.setRequestHeader('X-CSRF-Token', 'Fetch');
xhr.send();
if (xhr.status === 200) {
csrfToken = xhr.getResponseHeader("x-csrf-token");
$scope.fileExists = true;
} else {
$scope.fileExists = false;
}
} else {
$scope.fileExists = false;
}
return $scope.fileExists;
};
function getNumber(str) {
if (typeof str != "string") return NaN;
let strNum = parseFloat(str);
// use type coercion to parse the _entirety_ of the string (`parseFloat` alone does not do this) and ensure strings of whitespace fail
let isNumber = !isNaN(str) && !isNaN(strNum);
if (isNumber) return strNum;
else return NaN;
}
/**
* Used for removing some keys from the object before turning it into a string.
*/
function cleanForOutput(key, value) {
if (key === "name" || key === "visible") return undefined;
else if (key === "schema" && value === "") return undefined;
return value;
}
/**
* Sends hdbti file, receives json file
*/
function parseHdbti(hdbti) {
let blob = new Blob([hdbti], { type: "application/hdbti" });
let formData = new FormData();
formData.append('file', blob);
$http.post(`/services/v4/parse/hdbti?location="${$scope.file}"`, formData, {
transformRequest: angular.identity,
headers: { 'Content-Type': undefined }
}).then(function (response) {
$scope.jsonData = response.data;
for (let i = 0; i < $scope.jsonData.length; i++) {
$scope.jsonData[i]["name"] = $scope.getFileName($scope.jsonData[i].file, false);
$scope.jsonData[i]["visible"] = true;
}
$scope.activeItemId = 0;
if ($scope.jsonData.length > 0) {
$scope.dataEmpty = false;
} else {
$scope.dataEmpty = true;
}
$scope.dataLoaded = true;
}, function (response) {
console.error(response);
$messageHub.announceAlertError(
"Error while loading file",
response.data.error.message
);
});
}
/**
* Sends json in text form, receives hdbti string
*/
function parseJsonToHdbti(json) {
$http.post("/services/v4/parse/csvim", json, {
headers: { 'Content-Type': 'application/json' }
}).then(function (response) {
saveContents(response.data);
}, function (response) {
if (response.data === "Missing schema property") {
$scope.showSchemaError(
true,
'Schema must be specified either in the table name ("schemaName::tableName") or in the schema field.'
);
$scope.setSaveEnabled(false);
}
$messageHub.announceAlertError(
"Error while saving the file",
"Please look at the console for more information"
);
console.error(response);
});
}
function getViewParameters() {
if (window.frameElement.hasAttribute("data-parameters")) {
let params = JSON.parse(window.frameElement.getAttribute("data-parameters"));
$scope.file = params["file"];
} else {
let searchParams = new URLSearchParams(window.location.search);
$scope.file = searchParams.get('file');
}
}
function loadFileContents() {
getViewParameters();
if ($scope.file) {
$http.get('/services/v4/ide/workspaces' + $scope.file)
.then(function (response) {
let data = response.data;
if (
emptyHdbti.includes(
data.replaceAll(" ", '').replaceAll('\n', '').replaceAll('\t', '')
)
) {
data = "import = [];";
}
parseHdbti(data);
}, function (response) {
if (response.data) {
$messageHub.announceAlertError(
"Error while loading file",
response.data.error.message
);
if ("error" in response.data) {
console.error("Loading file:", response.data.error.message);
}
} else {
console.error("Error loading file.");
}
});
} else {
console.error("HDBTI Editor: file parameter is missing");
}
}
function saveContents(text) {
if ($scope.file) {
let xhr = new XMLHttpRequest();
xhr.open('PUT', '/services/v4/ide/workspaces' + $scope.file);
xhr.setRequestHeader('X-Requested-With', 'Fetch');
xhr.setRequestHeader('X-CSRF-Token', csrfToken);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
$messageHub.post({
name: $scope.file.substring($scope.file.lastIndexOf('/') + 1),
path: $scope.file.substring($scope.file.indexOf('/', 1)),
contentType: 'application/hdbti', // TODO: Take this from data-parameters
workspace: $scope.file.substring(1, $scope.file.indexOf('/', 1)),
}, 'ide.file.saved');
$messageHub.post({ message: `File '${$scope.file}' saved` }, 'ide.status.message');
$messageHub.post({ resourcePath: $scope.file, isDirty: false }, 'ide-core.setEditorDirty');
}
};
xhr.onerror = function (error) {
console.error(`Error saving '${$scope.file}'`, error);
$messageHub.post({
message: `Error saving '${$scope.file}'`
}, 'ide.status.error');
$messageHub.announceAlertError(
"Error while saving the file",
"Please look at the console for more information"
);
};
xhr.send(text);
isFileChanged = false;
} else {
console.error("HDBTI Editor: file parameter is missing");
}
}
function checkPlatform() {
let platform = window.navigator.platform; // This needs improvement
let macosPlatforms = ['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K', 'darwin', 'Mac', 'mac', 'macOS'];
if (macosPlatforms.indexOf(platform) !== -1) isMac = true;
}
function getCurrentWorkspace() { // This needs to be replaced with an API
let storedWorkspace = JSON.parse(localStorage.getItem('DIRIGIBLE.workspace') || '{}');
if ('name' in storedWorkspace) workspace = storedWorkspace.name;
else workspace = 'workspace';
}
$messageHub.on(
"editor.file.save.all",
function () {
if (isFileChanged) {
$scope.save();
}
},
);
$messageHub.on(
"editor.file.save",
function (msg) {
let file = msg.data && typeof msg.data === 'object' && msg.data.file;
if (file && file === $scope.file && isFileChanged)
$scope.save();
},
);
getCurrentWorkspace();
checkPlatform();
loadFileContents();
}]);
© 2015 - 2025 Weber Informatics LLC | Privacy Policy