Files
Undecked/Frontend/pages/home/ts/ActionEditor.ts
2023-08-29 19:55:48 +02:00

584 lines
16 KiB
TypeScript

var ActionEditor: ActionEditor = {
elements: {
up: {
container: document.querySelector('.event.up'),
selector: document.querySelector('.event.up').querySelector('.actionselector'),
actions: document.querySelector('.event.up').querySelector('.actions')
},
down: {
container: document.querySelector('.event.down'),
selector: document.querySelector('.event.down').querySelector('.actionselector'),
actions: document.querySelector('.event.down').querySelector('.actions')
},
latch: {
container: document.querySelector('.event.toggle'),
selector: document.querySelector('.event.toggle').querySelector('.tlatch').querySelector('.actionselector'),
actions: document.querySelector('.event.toggle').querySelector('.tlatch').querySelector('.actions')
},
unlatch: {
container: document.querySelector('.event.toggle'),
selector: document
.querySelector('.event.toggle')
.querySelector('.tunlatch')
.querySelector('.actionselector'),
actions: document.querySelector('.event.toggle').querySelector('.tunlatch').querySelector('.actions')
}
},
openEditors: { up: {}, down: {}, latch: {}, unlatch: {} },
listeners() {
if (ActionSelector) {
ActionSelector.register(ActionEditor.elements.up.selector, (integrationID: string, actionID: string) =>
handleNewAction(integrationID, actionID, 'up')
);
ActionSelector.register(ActionEditor.elements.down.selector, (integrationID: string, actionID: string) =>
handleNewAction(integrationID, actionID, 'down')
);
ActionSelector.register(ActionEditor.elements.latch.selector, (integrationID: string, actionID: string) =>
handleNewAction(integrationID, actionID, 'latch')
);
ActionSelector.register(ActionEditor.elements.unlatch.selector, (integrationID: string, actionID: string) =>
handleNewAction(integrationID, actionID, 'unlatch')
);
function handleNewAction(
integrationID: string,
actionID: string,
type: 'up' | 'down' | 'latch' | 'unlatch'
) {
socket.emit(
'actioneditor',
'create',
PageHandler.currentPageID,
Editor.currentX,
Editor.currentY,
type,
integrationID,
actionID,
(actionInstance: Page_Key_Action) => {
ActionEditor.openAction(actionInstance, type);
}
);
}
} else setTimeout(ActionEditor.listeners, 100);
},
open(actions: Page_Key_ActionTypes, x: string, y: string, key: Page_Key, pageID: string) {
for (var type in actions) {
ActionEditor.elements[type].actions.innerHTML = '';
for (var actionInstanceID in actions[type]) {
ActionEditor.openAction(actions[type][actionInstanceID], type);
}
}
},
openAction(actionInstance: Page_Key_Action, type: string) {
var actionInstanceID = actionInstance.actionInstanceID;
var container = <HTMLDivElement>ce('div', 'actioncontainer', { actionInstanceID });
var header = ce('div', 'header');
header.appendChild(
ce('div', 'integration', null, ActionSelector.maps.integrationNames[actionInstance.integrationID])
);
header.appendChild(ce('div', 'action', null, ActionSelector.maps.actionNames[actionInstance.actionID]));
var buttons = ce('div', 'buttons');
var actionLogs = ce(
'div',
[
'btn',
'logs'
],
null,
'Logs'
);
actionLogs.onclick = () => UndeckedNotification('Not implented yet', 'error');
var actionRemove = ce(
'div',
[
'btn',
'remove'
],
null,
'Remove'
);
actionRemove.onclick = () => {
if (container.parentElement) container.parentElement.removeChild(container);
if (
ActionEditor.openEditors[type] != undefined &&
ActionEditor.openEditors[type][actionInstanceID] != undefined
) {
ActionEditor.openEditors[type][actionInstanceID].destroy();
}
socket.emit(
'actioneditor',
'remove',
PageHandler.currentPageID,
Editor.currentX,
Editor.currentY,
actionInstanceID,
type
);
Editor.registerChange();
};
buttons.appendChild(actionLogs);
buttons.appendChild(actionRemove);
header.appendChild(buttons);
container.appendChild(header);
var fields = <HTMLDivElement>ce('div', 'fields');
container.appendChild(fields);
ActionEditor.elements[type].actions.appendChild(container);
ActionEditor.openEditors[type][actionInstanceID] = new Action(
{
integrationID: actionInstance.integrationID,
actionID: actionInstance.actionID,
actionType: <any>type,
keyX: Editor.currentX,
keyY: Editor.currentY,
actionInstanceID,
pageID: PageHandler.currentPageID
},
fields
);
},
close() {
for (var type in ActionEditor.openEditors) {
for (var actionInstanceID in ActionEditor.openEditors[type]) {
ActionEditor.openEditors[type][actionInstanceID].destroy();
}
}
ActionEditor.elements.up.actions.innerHTML = '';
ActionEditor.elements.down.actions.innerHTML = '';
}
};
ActionEditor.listeners();
var Action = class Action {
settings: Action_Settings;
container: HTMLDivElement;
actionEditorID: string;
lastFields: EditorAPI_Field[];
constructor(settings: Action_Settings, container: HTMLDivElement) {
this.settings = settings;
this.container = container;
socket.emit(
'actioneditor',
'start',
this.settings,
(error: string, actionEditorID: string, ready: () => void) => {
if (error) return UndeckedNotification(error, 'error', 5000);
this.actionEditorID = actionEditorID;
this.listeners();
this.emit('ready');
}
);
}
emit(query: string, ...args: any[]) {
socket.emit('actioneditor', 'instance', this.actionEditorID, query, ...args);
}
listeners() {
socket.on(`AE_${this.actionEditorID}`, (query: string, ...args: any[]) => {
switch (query) {
case 'fields':
var fields: EditorAPI_Field[] = args[0];
this.render(fields);
break;
}
});
}
destroy() {
if (this.container && this.container.parentElement) this.container.parentElement.removeChild(this.container);
this.emit('close');
}
render(fields: EditorAPI_Field[]) {
var valueNameMap = {};
var updateMulti = (selected: string[], input: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement) => {
var text: string =
selected != undefined && selected.length > 0
? selected
.map((value) => {
return valueNameMap[value];
})
.join(', ')
: 'None';
input.innerHTML = '';
input.appendChild(ce('option', 'mutli', { value: selected.join(',') }, text));
};
var getMultiValues = (dropdowninner: HTMLDivElement): string[] => {
var values: string[] = [];
dropdowninner.querySelectorAll('.option').forEach((option: HTMLDivElement) => {
var checkbox: HTMLInputElement = option.querySelector('input');
var optionID = option.getAttribute('optionID');
if (checkbox.checked) values.push(optionID);
});
return values;
};
fields.forEach((field) => {
var fieldcontainer: HTMLDivElement = this.container.querySelector(`.field_${field.id}`);
if (fieldcontainer == null) {
fieldcontainer = <HTMLDivElement>ce('div', [
'field',
`field_${field.id}`
]);
this.container.appendChild(fieldcontainer);
}
var label: HTMLDivElement = fieldcontainer.querySelector('.fieldlabel');
if (label == null) {
label = <HTMLDivElement>ce('div', 'fieldlabel', null, `${field.name}${field.required ? ' *' : ''}`);
fieldcontainer.appendChild(label);
} else label.innerText = `${field.name}${field.required ? ' *' : ''}`;
var input: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement = fieldcontainer.querySelector(
'.input'
);
if (input == null) {
switch (field.type) {
case 'number':
case 'text':
case 'color':
input = <HTMLInputElement>ce('input', 'input', { type: field.type, value: field.value });
fieldcontainer.appendChild(input);
break;
case 'select':
case 'connection':
if (field.multi != undefined && field.multi == true) {
var multiSelect = ce('div', [
'multiselect'
]);
input = <HTMLInputElement>ce('select', [
'input',
'multiinput'
]);
input.onmouseup = (e: MouseEvent) => {
e.preventDefault();
e.stopPropagation();
dropdown.style.display = 'block';
input.classList.add('open');
var handleWindowClick = (e: MouseEvent) => {
if (e.target) {
var target = <HTMLElement>e.target;
if (
!target.classList.contains('msdp') &&
!target.classList.contains('multiinput')
) {
window.removeEventListener('click', handleWindowClick);
dropdown.style.display = 'none';
input.classList.remove('open');
}
}
};
window.addEventListener('click', handleWindowClick);
};
input.onmousedown = (e: MouseEvent) => {
e.preventDefault();
e.stopPropagation();
};
multiSelect.appendChild(input);
var dropdown = ce('div', [
'dropdown',
'msdp'
]);
var dropdowninner = <HTMLDivElement>ce('div', [
'inner',
'msdp'
]);
dropdown.appendChild(dropdowninner);
multiSelect.appendChild(dropdown);
if (field.values != undefined && field.values.length > 0)
for (let i = 0; i < field.values.length; i++) {
((value) => {
valueNameMap[value.id] = field.values[i].text;
var item = ce(
'div',
[
'option',
'msdp'
],
{ optionID: value.id }
);
var checkbox = <HTMLInputElement>ce('input', 'msdp', {
type: 'checkbox'
});
checkbox.checked = field.value != undefined && field.value.includes(value.id);
item.onclick = () => {
checkbox.checked = !checkbox.checked;
updateMulti(getMultiValues(dropdowninner), input);
this.emit('fields', this.export());
};
item.appendChild(checkbox);
item.appendChild(
ce(
'div',
[
'text',
'msdp'
],
null,
value.text
)
);
dropdowninner.appendChild(item);
})(field.values[i]);
}
updateMulti(field.value, input);
fieldcontainer.appendChild(multiSelect);
} else {
input = <HTMLInputElement>ce('select', 'input');
if (field.values != undefined && field.values.length > 0)
for (let i = 0; i < field.values.length; i++) {
var option = ce(
'option',
null,
{ value: field.values[i].id },
field.values[i].text
);
if (field.value == field.values[i].id) option.setAttribute('selected', '');
input.appendChild(option);
}
fieldcontainer.appendChild(input);
}
break;
}
if (input) input.oninput = () => this.emit('fields', this.export());
} else {
input.value = field.value;
if (field.type == 'select' || field.type == 'connection') {
var parsedOptionValues = [];
if (field.multi != undefined && field.multi == true) {
var dropdown_inner = <HTMLDivElement>input.parentElement.querySelector('.inner');
if (field.values != undefined && field.values.length > 0)
for (let i = 0; i < field.values.length; i++) {
var selectValue = field.values[i];
var existingOption: HTMLDivElement = dropdown_inner.querySelector(
`.option[optionid="${selectValue.id}"]`
);
parsedOptionValues.push(selectValue.id);
if (existingOption) {
var text: HTMLDivElement = existingOption.querySelector('.text');
text.innerText = selectValue.text;
} else {
((value) => {
valueNameMap[value.id] = field.values[i].text;
var item = ce(
'div',
[
'option',
'msdp'
],
{ optionID: value.id }
);
var checkbox = <HTMLInputElement>ce('input', 'msdp', {
type: 'checkbox'
});
checkbox.checked = field.value != undefined && field.value.includes(value.id);
item.onclick = () => {
checkbox.checked = !checkbox.checked;
updateMulti(getMultiValues(dropdown_inner), input);
this.emit('fields', this.export());
};
item.appendChild(checkbox);
item.appendChild(
ce(
'div',
[
'text',
'msdp'
],
null,
value.text
)
);
dropdown_inner.appendChild(item);
})(selectValue);
}
}
else dropdown_inner.innerHTML = '';
dropdown_inner.querySelectorAll('.option').forEach((option: HTMLOptionElement) => {
var optionValue = option.getAttribute('optionID');
var checkbox: HTMLInputElement = option.querySelector('input');
if (!parsedOptionValues.includes(optionValue)) option.parentElement.removeChild(option);
else checkbox.checked = field.value.includes(optionValue);
});
} else {
if (field.values != undefined && field.values.length > 0)
for (let i = 0; i < field.values.length; i++) {
var selectValue = field.values[i];
var existing: HTMLOptionElement = input.querySelector(
`option[value="${selectValue.id}"]`
);
if (existing) existing.innerText = selectValue.text;
else {
existing = <HTMLOptionElement>ce(
'option',
null,
{ value: selectValue.id },
selectValue.text
);
input.appendChild(existing);
}
parsedOptionValues.push(selectValue.id);
}
else input.innerHTML = '';
input.querySelectorAll('option').forEach((option: HTMLOptionElement) => {
var optionValue = option.getAttribute('value');
option.removeAttribute('selected');
if (!parsedOptionValues.includes(optionValue)) option.parentElement.removeChild(option);
else if (field.value == option) option.setAttribute('selected', '');
});
}
// if (field.value != undefined && input.value.length > 0) input.value = field.value;
}
}
});
this.lastFields = fields;
}
export(): EditorAPI_Field[] {
var copyOfLast = JSON.parse(JSON.stringify(this.lastFields));
for (let i = 0; i < copyOfLast.length; i++) {
var field = copyOfLast[i];
var fieldcontainer: HTMLDivElement = this.container.querySelector(`.field_${field.id}`);
if (fieldcontainer) {
var input: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement = fieldcontainer.querySelector(
'.input'
);
if (input) {
if (!input.classList.contains('multiinput')) copyOfLast[i].value = input.value;
else copyOfLast[i].value = input.value.split(',');
}
}
}
return copyOfLast;
}
};
interface Action_Settings {
integrationID: string;
actionID: string;
actionInstanceID: string;
pageID: string;
actionType: 'up' | 'down';
keyX: number | string;
keyY: number | string;
}
interface ActionEditor {
elements: {
up: {
container: HTMLDivElement;
selector: HTMLInputElement;
actions: HTMLDivElement;
};
down: {
container: HTMLDivElement;
selector: HTMLInputElement;
actions: HTMLDivElement;
};
latch: {
container: HTMLDivElement;
selector: HTMLInputElement;
actions: HTMLDivElement;
};
unlatch: {
container: HTMLDivElement;
selector: HTMLInputElement;
actions: HTMLDivElement;
};
};
openEditors: {
up: { [actionInstanceID: string]: typeof Action };
down: { [actionInstanceID: string]: typeof Action };
latch: { [actionInstanceID: string]: typeof Action };
unlatch: { [actionInstanceID: string]: typeof Action };
};
listeners: () => void;
open: (actions: Page_Key_ActionTypes, x: string, y: string, key: Page_Key, pageID: string) => void;
openAction: (actionInstance: Page_Key_Action, type: string) => void;
close: () => void;
}
interface Page_Key_ActionTypes {
up: Page_Key_Actions;
down: Page_Key_Actions;
}
interface Page_Key_Actions {
[actionInstanceID: string]: Page_Key_Action;
}
interface Page_Key_Action {
integrationID: string;
actionID: string;
actionInstanceID: string;
properties: { [property: string]: any };
logs: { timestamp: number; type: 'error' | 'info' | 'warning'; text: string }[];
}
interface EditorAPI_Field {
id: string;
name: string;
type: 'text' | 'number' | 'select' | 'connection' | 'color';
value: any;
values?: { id: string; text: string }[];
required?: boolean;
multi?: boolean;
}