Initial commit
This commit is contained in:
583
Frontend/pages/home/ts/ActionEditor.ts
Normal file
583
Frontend/pages/home/ts/ActionEditor.ts
Normal file
@@ -0,0 +1,583 @@
|
||||
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;
|
||||
}
|
||||
110
Frontend/pages/home/ts/ActionSelector.ts
Normal file
110
Frontend/pages/home/ts/ActionSelector.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
var ActionSelector: ActionSelector = {
|
||||
elements: {
|
||||
actiondialog: document.querySelector('.actiondialog')
|
||||
},
|
||||
|
||||
fadeTimeout: null,
|
||||
|
||||
maps: {
|
||||
integrationNames: {},
|
||||
actionNames: {}
|
||||
},
|
||||
|
||||
init() {
|
||||
ActionSelector.elements.actiondialog.querySelectorAll('.item').forEach((item: HTMLDivElement) => {
|
||||
var integrationID = item.getAttribute('integrationID');
|
||||
var actionID = item.getAttribute('actionID');
|
||||
var integrationName = (<HTMLDivElement>item.querySelector('.integration')).innerText.trim();
|
||||
var actionName = (<HTMLDivElement>item.querySelector('.action')).innerText.trim();
|
||||
|
||||
if (ActionSelector.maps.integrationNames[integrationID] == undefined)
|
||||
ActionSelector.maps.integrationNames[integrationID] = integrationName;
|
||||
if (ActionSelector.maps.actionNames[actionID] == undefined)
|
||||
ActionSelector.maps.actionNames[actionID] = actionName;
|
||||
});
|
||||
},
|
||||
|
||||
register(input: HTMLInputElement, callback: (integrationID: string, actionID: string) => void) {
|
||||
input.onfocus = () => ActionSelector.show(input, callback);
|
||||
input.onblur = () => setTimeout(ActionSelector.hide, 200);
|
||||
input.oninput = () => ActionSelector.search(input.value);
|
||||
},
|
||||
|
||||
show(input: HTMLInputElement, callback: (integrationID: string, actionID: string) => void) {
|
||||
clearTimeout(ActionSelector.fadeTimeout);
|
||||
|
||||
ActionSelector.search('');
|
||||
|
||||
var boundingbox = input.getBoundingClientRect();
|
||||
|
||||
ActionSelector.elements.actiondialog.style.top = `${boundingbox.top - 31}px`;
|
||||
ActionSelector.elements.actiondialog.style.left = `${boundingbox.left - 10}px`;
|
||||
ActionSelector.elements.actiondialog.style.width = `${boundingbox.width - 4}px`;
|
||||
ActionSelector.elements.actiondialog.style.maxHeight = `${Math.min(
|
||||
window.innerHeight - boundingbox.top,
|
||||
200
|
||||
)}px`;
|
||||
|
||||
ActionSelector.elements.actiondialog.style.display = 'flex';
|
||||
ActionSelector.elements.actiondialog.style.transitionDuration = '.3s';
|
||||
ActionSelector.elements.actiondialog.style.opacity = '1';
|
||||
ActionSelector.elements.actiondialog.style.pointerEvents = 'auto';
|
||||
|
||||
ActionSelector.elements.actiondialog.querySelectorAll('.item').forEach((item: HTMLDivElement) => {
|
||||
item.onclick = () => {
|
||||
var integrationID = item.getAttribute('integrationID');
|
||||
var actionID = item.getAttribute('actionID');
|
||||
callback(integrationID, actionID);
|
||||
|
||||
input.value = '';
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
hide() {
|
||||
ActionSelector.elements.actiondialog.style.transitionDuration = '.3s';
|
||||
ActionSelector.elements.actiondialog.style.opacity = '0';
|
||||
ActionSelector.elements.actiondialog.style.pointerEvents = 'none';
|
||||
|
||||
clearTimeout(ActionSelector.fadeTimeout);
|
||||
ActionSelector.fadeTimeout = setTimeout(() => {
|
||||
ActionSelector.elements.actiondialog.style.display = 'none';
|
||||
}, 300);
|
||||
},
|
||||
|
||||
search(query: string) {
|
||||
ActionSelector.elements.actiondialog.querySelectorAll('.item').forEach((item: HTMLDivElement) => {
|
||||
var integration: HTMLDivElement = item.querySelector('.integration');
|
||||
var action: HTMLDivElement = item.querySelector('.action');
|
||||
|
||||
var interactionQuery = integration.innerText.toLowerCase();
|
||||
var actionQuery = action.innerText.toLowerCase();
|
||||
|
||||
query = query.toLowerCase();
|
||||
|
||||
if (interactionQuery.includes(query) || actionQuery.includes(query) || query.length == 0)
|
||||
item.classList.remove('hidden');
|
||||
else item.classList.add('hidden');
|
||||
});
|
||||
}
|
||||
};
|
||||
ActionSelector.init();
|
||||
|
||||
interface ActionSelector {
|
||||
elements: {
|
||||
actiondialog: HTMLDivElement;
|
||||
};
|
||||
|
||||
fadeTimeout: any;
|
||||
|
||||
maps: {
|
||||
integrationNames: { [integrationID: string]: string };
|
||||
actionNames: { [actionID: string]: string };
|
||||
};
|
||||
|
||||
init: () => void;
|
||||
register: (input: HTMLInputElement, callback: (integrationID: string, actionID: string) => void) => void;
|
||||
show: (input: HTMLInputElement, callback: (integrationID: string, actionID: string) => void) => void;
|
||||
hide: () => void;
|
||||
search: (query: string) => void;
|
||||
}
|
||||
72
Frontend/pages/home/ts/Clipboard.ts
Normal file
72
Frontend/pages/home/ts/Clipboard.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
var UndeckedClipboard = new class UndeckedClipboard {
|
||||
constructor() {}
|
||||
|
||||
hasKeyInClipboard() {
|
||||
return localStorage.getItem('clipboard') != undefined && localStorage.getItem('clipboard').length > 0;
|
||||
}
|
||||
|
||||
copyKey(originKeyX: number, originKeyY: number) {
|
||||
localStorage.setItem('clipboard', `key_copy_${PageHandler.currentPageID}.${originKeyX}.${originKeyY}`);
|
||||
UndeckedNotification('Key has been copied to clipboard');
|
||||
}
|
||||
|
||||
copyGhostKey(originKeyX: number, originKeyY: number) {
|
||||
localStorage.setItem('clipboard', `key_ghost_${PageHandler.currentPageID}.${originKeyX}.${originKeyY}`);
|
||||
UndeckedNotification('Key has been copied to clipboard as a ghost');
|
||||
}
|
||||
|
||||
cutKey(originKeyX: number, originKeyY: number) {
|
||||
//TODO: Implement something in the front end to show that the item is being cut right now
|
||||
localStorage.setItem('clipboard', `key_cut_${PageHandler.currentPageID}.${originKeyX}.${originKeyY}`);
|
||||
UndeckedNotification('Key has been cut to clipboard');
|
||||
}
|
||||
|
||||
pasteKey(destinationKeyX: number, destinationKeyY: number) {
|
||||
if (this.hasKeyInClipboard()) {
|
||||
var clipboard = this.decodeClipboard();
|
||||
|
||||
if (clipboard.elementType == 'key') {
|
||||
socket.emit(
|
||||
'page',
|
||||
'operation',
|
||||
clipboard.operationType,
|
||||
clipboard.id,
|
||||
clipboard.x,
|
||||
clipboard.y,
|
||||
PageHandler.currentPageID,
|
||||
destinationKeyX,
|
||||
destinationKeyY
|
||||
);
|
||||
|
||||
if (clipboard.operationType == 'cut') localStorage.setItem('clipboard', '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
decodeClipboard(): {
|
||||
elementType: 'key';
|
||||
operationType: 'cut' | 'copy' | 'ghost';
|
||||
id: string;
|
||||
x?: number;
|
||||
y?: number;
|
||||
} {
|
||||
var clipboard = {
|
||||
elementType: null,
|
||||
operationType: null,
|
||||
id: null,
|
||||
x: null,
|
||||
y: null
|
||||
};
|
||||
|
||||
if (this.hasKeyInClipboard()) {
|
||||
var raw = localStorage.getItem('clipboard').split('_');
|
||||
clipboard.elementType = raw[0];
|
||||
clipboard.operationType = raw[1];
|
||||
var args = raw[2].split('.');
|
||||
clipboard.id = args[0];
|
||||
clipboard.x = args[1];
|
||||
clipboard.y = args[2];
|
||||
return clipboard;
|
||||
} else return clipboard;
|
||||
}
|
||||
}();
|
||||
75
Frontend/pages/home/ts/Communication.ts
Normal file
75
Frontend/pages/home/ts/Communication.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
declare var ce: (
|
||||
type: string,
|
||||
classList?: string | string[],
|
||||
attributes?: { [key: string]: string },
|
||||
innerText?: string,
|
||||
innerHTML?: string
|
||||
) => HTMLElement;
|
||||
declare var UndeckedNotification: (message: string, type?: 'info' | 'error', time?: number) => void;
|
||||
declare var io: any;
|
||||
|
||||
var responseToken = Math.random().toString(16).substr(2, 8);
|
||||
|
||||
var socket = io('/');
|
||||
|
||||
socket.on('connect', () => {
|
||||
console.log('Connected to server');
|
||||
|
||||
socket.emit('init', 'home');
|
||||
});
|
||||
|
||||
var fontSizeRatio: number = null;
|
||||
var renderQuality: number = null;
|
||||
socket.on('quality', (quality: number) => {
|
||||
document.querySelectorAll('canvas.ready').forEach((canvas: HTMLCanvasElement) => {
|
||||
canvas.width = quality;
|
||||
canvas.height = quality;
|
||||
|
||||
var context = canvas.getContext('2d');
|
||||
context.textBaseline = 'middle';
|
||||
context.textAlign = 'center';
|
||||
});
|
||||
|
||||
renderQuality = quality;
|
||||
fontSizeRatio = quality / 100;
|
||||
});
|
||||
|
||||
socket.on('pagelist', (pagelist: PageListItem[]) => {
|
||||
(function render() {
|
||||
if (fontSizeRatio != null) PageList.render(pagelist);
|
||||
else setTimeout(render, 100);
|
||||
})();
|
||||
});
|
||||
|
||||
socket.on('connectedlist', (connected: ConnectedList[]) => Connections.renderConnected(connected));
|
||||
|
||||
socket.on('page', (query: string, ...args: any[]) => {
|
||||
switch (query) {
|
||||
case 'updatename':
|
||||
var pageID: string = args[0];
|
||||
var newName: string = args[1];
|
||||
|
||||
PageList.updateName(pageID, newName);
|
||||
break;
|
||||
|
||||
case 'updatekey':
|
||||
var pageID: string = args[0];
|
||||
var x: string = args[1];
|
||||
var y: string = args[2];
|
||||
var key: Page_Key = args[3];
|
||||
var returnResponseToken: string = args[4];
|
||||
|
||||
if (PageHandler.currentPageID == pageID) {
|
||||
if (PageHandler.currentPage.keys[y] != undefined && PageHandler.currentPage.keys[y][x] != undefined)
|
||||
PageHandler.currentPage.keys[y][x] = key;
|
||||
|
||||
if (responseToken != returnResponseToken) {
|
||||
KeyHandler.render(x, y, key);
|
||||
|
||||
if (Editor.currentKey != undefined && Editor.currentKey.id == key.id) Editor.open(x, y, key);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
});
|
||||
206
Frontend/pages/home/ts/Connections.ts
Normal file
206
Frontend/pages/home/ts/Connections.ts
Normal file
@@ -0,0 +1,206 @@
|
||||
var Connections: Connections = {
|
||||
elements: {
|
||||
connectionbrowser: document.querySelector('.connectionbrowser'),
|
||||
|
||||
table: document.querySelector('.connectedtable'),
|
||||
|
||||
dialog: {
|
||||
container: document.querySelector('.connectiondialog'),
|
||||
dialog: document.querySelector('.connectiondialog').querySelector('.dialog'),
|
||||
fields: document.querySelector('.connectiondialog').querySelector('.fields'),
|
||||
message: document.querySelector('.connectiondialog').querySelector('.message'),
|
||||
link: document.querySelector('.connectiondialog').querySelector('.link'),
|
||||
cancel: document.querySelector('.connectiondialog').querySelector('.cn'),
|
||||
connect: document.querySelector('.connectiondialog').querySelector('.co')
|
||||
}
|
||||
},
|
||||
|
||||
init() {
|
||||
Connections.elements.connectionbrowser.querySelectorAll('.available').forEach((item: HTMLDivElement) => {
|
||||
var integrationID = item.getAttribute('integrationID');
|
||||
var connectionType = item.getAttribute('connectionType');
|
||||
var button: HTMLDivElement = item.querySelector('.button');
|
||||
button.onclick = () => Connections.requestNewDevice(integrationID, connectionType);
|
||||
});
|
||||
},
|
||||
|
||||
requestNewDevice(integrationID: string, connectionType: string) {
|
||||
socket.emit(
|
||||
'connections',
|
||||
'request',
|
||||
integrationID,
|
||||
connectionType,
|
||||
(connectionRequestData: ConnectionRequestData) => {
|
||||
if (connectionRequestData.fields && connectionRequestData.fields.length > 0)
|
||||
Connections.openDialog(integrationID, connectionType, connectionRequestData);
|
||||
else UndeckedNotification('Unable to add a new device of this type.', 'error', 5000);
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
openDialog(integrationID, connectionType, connectionRequestData: ConnectionRequestData) {
|
||||
Connections.elements.dialog.fields.innerHTML = '';
|
||||
|
||||
var nameField: Connection_Field = {
|
||||
id: '_internal_name',
|
||||
name: 'Connection Name',
|
||||
type: 'text'
|
||||
};
|
||||
connectionRequestData.fields = [
|
||||
nameField,
|
||||
...connectionRequestData.fields
|
||||
];
|
||||
|
||||
if (connectionRequestData.message != undefined) {
|
||||
Connections.elements.dialog.message.style.display = 'block';
|
||||
Connections.elements.dialog.message.innerText = connectionRequestData.message;
|
||||
} else Connections.elements.dialog.message.style.display = 'none';
|
||||
|
||||
if (connectionRequestData.link != undefined) {
|
||||
Connections.elements.dialog.link.style.display = 'inline-block';
|
||||
Connections.elements.dialog.link.innerText = connectionRequestData.link.title;
|
||||
Connections.elements.dialog.link.href = connectionRequestData.link.address;
|
||||
} else Connections.elements.dialog.link.style.display = 'none';
|
||||
|
||||
connectionRequestData.fields.forEach((field) => {
|
||||
var fieldcontainer = ce('div', [
|
||||
'field',
|
||||
`field_${field.id}`
|
||||
]);
|
||||
Connections.elements.dialog.fields.appendChild(fieldcontainer);
|
||||
|
||||
var label = ce('div', 'fieldlabel', null, `${field.name}`);
|
||||
fieldcontainer.appendChild(label);
|
||||
|
||||
var input: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement = null;
|
||||
|
||||
switch (field.type) {
|
||||
case 'number':
|
||||
case 'text':
|
||||
input = <HTMLInputElement>ce('input', 'input', { type: field.type, fieldID: field.id });
|
||||
if (field.value != undefined) input.value = field.value;
|
||||
fieldcontainer.appendChild(input);
|
||||
break;
|
||||
case 'select':
|
||||
input = <HTMLInputElement>ce('select', 'input', { fieldID: field.id });
|
||||
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 != undefined && field.values[i].id == field.value)
|
||||
option.setAttribute('selected', '');
|
||||
input.appendChild(option);
|
||||
}
|
||||
|
||||
fieldcontainer.appendChild(input);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
Connections.elements.dialog.cancel.onclick = Connections.closeDialog;
|
||||
Connections.elements.dialog.connect.onclick = () => {
|
||||
var properties = {};
|
||||
var inputs: NodeListOf<HTMLInputElement> = Connections.elements.dialog.fields.querySelectorAll('.input');
|
||||
for (let i = 0; i < inputs.length; i++)
|
||||
if (inputs[i].hasAttribute('fieldID')) properties[inputs[i].getAttribute('fieldID')] = inputs[i].value;
|
||||
|
||||
Connections.elements.dialog.dialog.style.display = 'none';
|
||||
socket.emit(
|
||||
'connections',
|
||||
'create',
|
||||
integrationID,
|
||||
connectionType,
|
||||
properties,
|
||||
(succeed: boolean, errormessage?: string) => {
|
||||
if (succeed == true) {
|
||||
Connections.closeDialog();
|
||||
} else {
|
||||
Connections.elements.dialog.dialog.style.display = 'block';
|
||||
UndeckedNotification(
|
||||
errormessage != undefined ? errormessage : 'Unable to validate connection',
|
||||
'error',
|
||||
5000
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
Connections.elements.dialog.dialog.style.display = 'block';
|
||||
Connections.elements.dialog.container.style.display = 'flex';
|
||||
},
|
||||
|
||||
closeDialog() {
|
||||
Connections.elements.dialog.container.style.display = 'none';
|
||||
},
|
||||
|
||||
renderConnected(connectList: ConnectedList[]) {
|
||||
var table = Connections.elements.table;
|
||||
|
||||
for (let i = 0; i < connectList.length; i++) {
|
||||
var connected = connectList[i];
|
||||
if (table.querySelector(`tr[connectionid="${connected.connectionID}"]`) == null) {
|
||||
var row = ce('tr', null, { connectionID: connected.connectionID });
|
||||
var status = ce('td', 'status');
|
||||
var statuscontainer = ce('div', 'statuscontainer');
|
||||
statuscontainer.appendChild(
|
||||
ce('div', [
|
||||
'value',
|
||||
'online'
|
||||
])
|
||||
);
|
||||
status.appendChild(statuscontainer);
|
||||
row.appendChild(status);
|
||||
row.appendChild(ce('td', 'name', null, connected.name));
|
||||
row.appendChild(ce('td', 'integration', null, connected.integrationName));
|
||||
row.appendChild(ce('td', 'type', null, connected.connectionType));
|
||||
table.appendChild(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Connections.init();
|
||||
|
||||
interface Connections {
|
||||
elements: {
|
||||
connectionbrowser: HTMLDivElement;
|
||||
table: HTMLTableElement;
|
||||
|
||||
dialog: {
|
||||
container: HTMLDivElement;
|
||||
dialog: HTMLDivElement;
|
||||
message: HTMLDivElement;
|
||||
link: HTMLAnchorElement;
|
||||
fields: HTMLDivElement;
|
||||
cancel: HTMLDivElement;
|
||||
connect: HTMLDivElement;
|
||||
};
|
||||
};
|
||||
|
||||
init: () => void;
|
||||
requestNewDevice: (integrationID: string, connectionType: string) => void;
|
||||
openDialog: (integrationID, connectionID, connectionRequestData: ConnectionRequestData) => void;
|
||||
closeDialog: () => void;
|
||||
renderConnected: (connectList: ConnectedList[]) => void;
|
||||
}
|
||||
|
||||
interface Connection_Field {
|
||||
id: string;
|
||||
name: string;
|
||||
type: 'text' | 'number' | 'select';
|
||||
values?: { id: string; text: string }[];
|
||||
value?: string;
|
||||
}
|
||||
|
||||
interface ConnectedList {
|
||||
connectionID: string;
|
||||
integrationName: string;
|
||||
connectionType: string;
|
||||
name: string;
|
||||
online: boolean;
|
||||
}
|
||||
|
||||
interface ConnectionRequestData {
|
||||
fields: Connection_Field[];
|
||||
message?: string;
|
||||
link?: { address: string; title: string };
|
||||
}
|
||||
137
Frontend/pages/home/ts/ContextMenu.ts
Normal file
137
Frontend/pages/home/ts/ContextMenu.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
var ContextMenu = new class ContextMenu {
|
||||
elements: {
|
||||
menu: HTMLDivElement;
|
||||
|
||||
items: {
|
||||
copy: HTMLDivElement;
|
||||
paste: HTMLDivElement;
|
||||
cut: HTMLDivElement;
|
||||
ghost: HTMLDivElement;
|
||||
osc: HTMLDivElement;
|
||||
http: HTMLDivElement;
|
||||
};
|
||||
};
|
||||
|
||||
open: boolean;
|
||||
contextHolder: HTMLElement;
|
||||
|
||||
constructor() {
|
||||
this.elements = {
|
||||
menu: document.querySelector('.contextmenu'),
|
||||
items: {
|
||||
copy: document.querySelector('.contextmenu').querySelector('.copy'),
|
||||
paste: document.querySelector('.contextmenu').querySelector('.paste'),
|
||||
cut: document.querySelector('.contextmenu').querySelector('.cut'),
|
||||
ghost: document.querySelector('.contextmenu').querySelector('.ghost'),
|
||||
osc: document.querySelector('.contextmenu').querySelector('.osc'),
|
||||
http: document.querySelector('.contextmenu').querySelector('.http')
|
||||
}
|
||||
};
|
||||
|
||||
this.open = false;
|
||||
this.contextHolder = null;
|
||||
|
||||
window.addEventListener('contextmenu', (ev: MouseEvent) => this.handle(this.elements.menu, ev));
|
||||
window.addEventListener('mousedown', (ev: MouseEvent) => {
|
||||
if (this.open == true)
|
||||
if (ev.target) {
|
||||
var target = ev.target as HTMLElement;
|
||||
|
||||
if (
|
||||
target.classList.contains('contextmenu') ||
|
||||
(target.parentElement != undefined && target.parentElement.classList.contains('contextmenu'))
|
||||
) {
|
||||
} else {
|
||||
this.close();
|
||||
}
|
||||
} else this.close();
|
||||
});
|
||||
}
|
||||
|
||||
setItems(items: DropdownItemTypes[]) {
|
||||
for (var type in this.elements.items) {
|
||||
if (items.includes(<DropdownItemTypes>type)) this.elements.items[type].style.display = 'block';
|
||||
else this.elements.items[type].style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
close() {
|
||||
this.elements.menu.style.display = 'none';
|
||||
if (this.contextHolder != null) {
|
||||
this.contextHolder.classList.remove('context');
|
||||
this.contextHolder = null;
|
||||
}
|
||||
this.open = false;
|
||||
}
|
||||
|
||||
handle(menu: HTMLDivElement, ev: MouseEvent) {
|
||||
if (ev.target != undefined) {
|
||||
var target = ev.target as HTMLElement;
|
||||
|
||||
var types = {
|
||||
key: (element: HTMLElement) => {
|
||||
if (element.hasAttribute('x') && element.hasAttribute('y')) {
|
||||
var keyX = element.getAttribute('x');
|
||||
var keyY = element.getAttribute('y');
|
||||
|
||||
var items: DropdownItemTypes[] = [
|
||||
'osc',
|
||||
'http'
|
||||
];
|
||||
if (PageHandler.currentPage.keys[keyY][keyX].state.type != 'empty') items.push('copy', 'cut');
|
||||
if (PageHandler.currentPage.keys[keyY][keyX].state.type == 'custom') items.push('ghost');
|
||||
if (UndeckedClipboard.hasKeyInClipboard()) items.push('paste');
|
||||
|
||||
this.setItems(items);
|
||||
|
||||
this.elements.items.copy.onclick = () => {
|
||||
UndeckedClipboard.copyKey(parseInt(keyX), parseInt(keyY));
|
||||
this.close();
|
||||
};
|
||||
|
||||
this.elements.items.cut.onclick = () => {
|
||||
UndeckedClipboard.cutKey(parseInt(keyX), parseInt(keyY));
|
||||
this.close();
|
||||
};
|
||||
|
||||
this.elements.items.paste.onclick = () => {
|
||||
UndeckedClipboard.pasteKey(parseInt(keyX), parseInt(keyY));
|
||||
this.close();
|
||||
};
|
||||
|
||||
this.elements.items.ghost.onclick = () => {
|
||||
UndeckedClipboard.copyGhostKey(parseInt(keyX), parseInt(keyY));
|
||||
this.close();
|
||||
};
|
||||
|
||||
return items.length > 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (var type in types)
|
||||
if (target.classList.contains(type)) {
|
||||
ev.preventDefault();
|
||||
|
||||
var valid = types[type](target);
|
||||
|
||||
if (valid) {
|
||||
this.contextHolder = target;
|
||||
target.classList.add('context');
|
||||
|
||||
setTimeout(() => {
|
||||
this.open = true;
|
||||
}, 100);
|
||||
|
||||
menu.style.left = `${ev.pageX}px`;
|
||||
menu.style.top = `${ev.pageY - 50}px`;
|
||||
menu.style.display = 'block';
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}();
|
||||
|
||||
type DropdownItemTypes = 'copy' | 'paste' | 'cut' | 'ghost' | 'osc' | 'http';
|
||||
468
Frontend/pages/home/ts/Editor.ts
Normal file
468
Frontend/pages/home/ts/Editor.ts
Normal file
@@ -0,0 +1,468 @@
|
||||
var Editor: Editor = {
|
||||
elements: {
|
||||
container: document.querySelector('.edit'),
|
||||
appearence: {
|
||||
text: {
|
||||
content: document.querySelector('.ap_text_value'),
|
||||
size: document.querySelector('.ap_text_size'),
|
||||
color: document.querySelector('.ap_text_color'),
|
||||
offsetX: document.querySelector('.ap_text_offsetx').querySelector('input[type="range"]'),
|
||||
offsetY: document.querySelector('.ap_text_offsety').querySelector('input[type="range"]')
|
||||
},
|
||||
background: {
|
||||
color: document.querySelector('.ap_background_color')
|
||||
},
|
||||
image: {
|
||||
size: document.querySelector('.ap_image_size').querySelector('input[type="range"]'),
|
||||
offsetX: document.querySelector('.ap_image_offsetx').querySelector('input[type="range"]'),
|
||||
offsetY: document.querySelector('.ap_image_offsety').querySelector('input[type="range"]'),
|
||||
rotation: document.querySelector('.ap_image_rotation').querySelector('input[type="range"]')
|
||||
}
|
||||
},
|
||||
imageui: {
|
||||
imagetypes: document.querySelector('.imagetype'),
|
||||
imageinfopanels: document.querySelector('.imagemenu').querySelector('.info'),
|
||||
imagepanels: document.querySelector('.row.image').querySelector('.panels'),
|
||||
advanced: document.querySelector('.row.image').querySelector('.advanced')
|
||||
},
|
||||
buttonui: {
|
||||
buttontypes: document.querySelector('.buttontype'),
|
||||
visual: document.querySelector('.vis'),
|
||||
actioninner: document.querySelector('.actioninner'),
|
||||
containerTitles: document.querySelector('.editcontainer').querySelectorAll('.containertitle')
|
||||
},
|
||||
checks: {
|
||||
toggle: {
|
||||
container: document.querySelector('.checks').querySelector('.check.toggle'),
|
||||
checkbox: document.querySelector('.checks').querySelector('.check.toggle').querySelector('input')
|
||||
},
|
||||
confirm: {
|
||||
container: document.querySelector('.checks').querySelector('.check.confirm'),
|
||||
checkbox: document.querySelector('.checks').querySelector('.check.confirm').querySelector('input')
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
currentX: null,
|
||||
currentY: null,
|
||||
currentKey: null,
|
||||
currentImageType: null,
|
||||
currentButtonType: null,
|
||||
isToggle: null,
|
||||
isConfirm: null,
|
||||
|
||||
isOpen: false,
|
||||
|
||||
registerChange() {
|
||||
if (Editor.currentKey != null) {
|
||||
var editorExport = Editor.export();
|
||||
KeyHandler.render(Editor.currentX, Editor.currentY, editorExport);
|
||||
|
||||
socket.emit(
|
||||
'page',
|
||||
'setkey',
|
||||
PageHandler.currentPageID,
|
||||
Editor.currentX,
|
||||
Editor.currentY,
|
||||
editorExport,
|
||||
responseToken
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
listeners() {
|
||||
for (var inputCategory in Editor.elements.appearence) {
|
||||
for (var inputName in Editor.elements.appearence[inputCategory]) {
|
||||
var inputElement: HTMLInputElement = Editor.elements.appearence[inputCategory][inputName];
|
||||
|
||||
inputElement.oninput = Editor.registerChange;
|
||||
|
||||
if (inputElement.type == 'range')
|
||||
((range: HTMLInputElement, number: HTMLInputElement) => {
|
||||
range.addEventListener('input', () => {
|
||||
number.value = range.value;
|
||||
});
|
||||
number.addEventListener('input', () => {
|
||||
range.value = number.value;
|
||||
Editor.registerChange();
|
||||
});
|
||||
})(inputElement, inputElement.parentElement.querySelector('input[type="number"]'));
|
||||
}
|
||||
}
|
||||
|
||||
Editor.elements.imageui.imagetypes.querySelectorAll('.selectoritem').forEach((item: HTMLDivElement) => {
|
||||
item.onclick = () => {
|
||||
var panelType: 'none' | 'icon' | 'upload' = <any>item.getAttribute('panel');
|
||||
|
||||
Editor.selectImageTab(panelType);
|
||||
};
|
||||
});
|
||||
|
||||
Editor.elements.buttonui.buttontypes.querySelectorAll('.buttonitem').forEach((item: HTMLDivElement) => {
|
||||
item.onclick = () => {
|
||||
var buttonType: 'empty' | 'custom' | 'pageup' | 'pagedown' = <any>item.getAttribute('type');
|
||||
|
||||
Editor.selectButtonType(buttonType);
|
||||
};
|
||||
});
|
||||
|
||||
Editor.elements.checks.toggle.container.onclick = () =>
|
||||
Editor.setActionOptions(!Editor.elements.checks.toggle.checkbox.checked, Editor.isConfirm);
|
||||
|
||||
Editor.elements.checks.confirm.container.onclick = () =>
|
||||
Editor.setActionOptions(Editor.isToggle, !Editor.elements.checks.confirm.checkbox.checked);
|
||||
},
|
||||
|
||||
export(): Page_Key {
|
||||
if (Editor.isOpen) {
|
||||
return {
|
||||
id: Editor.currentKey.id,
|
||||
actions: Editor.currentKey.actions,
|
||||
state: {
|
||||
type: Editor.currentButtonType,
|
||||
confirm: Editor.isConfirm,
|
||||
toggle: Editor.isToggle
|
||||
},
|
||||
appearence: {
|
||||
text: Editor.getElementCategoryValues('text'),
|
||||
background: Editor.getElementCategoryValues('background'),
|
||||
image: Editor.getElementCategoryValues('image')
|
||||
}
|
||||
};
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
open(x: string, y: string, key: Page_Key) {
|
||||
if (Editor.isOpen) Editor.close();
|
||||
Editor.currentKey = key;
|
||||
Editor.currentX = x;
|
||||
Editor.currentY = y;
|
||||
Editor.isOpen = true;
|
||||
|
||||
document.querySelectorAll('.actionselector').forEach((selector: HTMLInputElement) => {
|
||||
selector.value = '';
|
||||
});
|
||||
|
||||
// --------- TEXT ---------
|
||||
{
|
||||
var textElems = Editor.elements.appearence.text;
|
||||
var textData: Page_Key_Text = Editor.getAppearenceCategory('text', key);
|
||||
textElems.content.value = textData.value != undefined ? textData.value : '';
|
||||
textElems.size.value = textData.size != undefined ? String(textData.size) : '10';
|
||||
textElems.color.value = textData.color != undefined ? textData.color : '#000000';
|
||||
textElems.offsetX.value = textData.offsetX != undefined ? String(textData.offsetX) : '0';
|
||||
var offsetXNumber: HTMLInputElement = textElems.offsetX.parentElement.querySelector('input[type="number"]');
|
||||
offsetXNumber.value = textData.offsetX != undefined ? String(textData.offsetX) : '0';
|
||||
textElems.offsetY.value = textData.offsetY != undefined ? String(textData.offsetY) : '0';
|
||||
var offsetYNumber: HTMLInputElement = textElems.offsetY.parentElement.querySelector('input[type="number"]');
|
||||
offsetYNumber.value = textData.offsetY != undefined ? String(textData.offsetY) : '0';
|
||||
}
|
||||
|
||||
// --------- BACKGROUND ---------
|
||||
{
|
||||
var backgroundElems = Editor.elements.appearence.background;
|
||||
var backgroundData: Page_Key_Background = Editor.getAppearenceCategory('background', key);
|
||||
backgroundElems.color.value = backgroundData.color != undefined ? backgroundData.color : '#000000';
|
||||
}
|
||||
|
||||
// --------- IMAGE ---------
|
||||
{
|
||||
var imageElems = Editor.elements.appearence.image;
|
||||
var imageData: Page_Key_Image = Editor.getAppearenceCategory('image', key);
|
||||
imageElems.size.value = imageData.size != undefined ? String(imageData.size) : '100';
|
||||
var sizeNumber: HTMLInputElement = imageElems.size.parentElement.querySelector('input[type="number"]');
|
||||
sizeNumber.value = imageData.size != undefined ? String(imageData.size) : '100';
|
||||
imageElems.rotation.value = imageData.rotation != undefined ? String(imageData.rotation) : '0';
|
||||
var rotationNumber: HTMLInputElement = imageElems.rotation.parentElement.querySelector(
|
||||
'input[type="number"]'
|
||||
);
|
||||
rotationNumber.value = imageData.rotation != undefined ? String(imageData.rotation) : '0';
|
||||
imageElems.offsetX.value = imageData.offsetX != undefined ? String(imageData.offsetX) : '0';
|
||||
var offsetXNumber: HTMLInputElement = imageElems.offsetX.parentElement.querySelector(
|
||||
'input[type="number"]'
|
||||
);
|
||||
offsetXNumber.value = imageData.offsetX != undefined ? String(imageData.offsetX) : '0';
|
||||
imageElems.offsetY.value = imageData.offsetY != undefined ? String(imageData.offsetY) : '0';
|
||||
var offsetYNumber: HTMLInputElement = imageElems.offsetY.parentElement.querySelector(
|
||||
'input[type="number"]'
|
||||
);
|
||||
offsetYNumber.value = imageData.offsetY != undefined ? String(imageData.offsetY) : '0';
|
||||
}
|
||||
|
||||
if (imageData.address != undefined) {
|
||||
Editor.selectImageTab('upload', true);
|
||||
} else if (imageData.iconid != undefined) {
|
||||
Editor.selectImageTab('icon', true);
|
||||
Icons.select(imageData.iconid);
|
||||
}
|
||||
|
||||
Editor.selectButtonType(key.state.type, true);
|
||||
Editor.setActionOptions(key.state.toggle, key.state.confirm, true);
|
||||
|
||||
var actions = key.actions != undefined ? key.actions : { up: {}, down: {} };
|
||||
ActionEditor.open(actions, x, y, key, PageHandler.currentPageID);
|
||||
|
||||
Editor.elements.container.classList.remove('disabled');
|
||||
},
|
||||
|
||||
close() {
|
||||
Editor.isOpen = false;
|
||||
Editor.currentKey = null;
|
||||
Editor.currentX = null;
|
||||
Editor.currentY = null;
|
||||
|
||||
document.querySelectorAll('.actionselector').forEach((selector: HTMLInputElement) => {
|
||||
selector.value = '';
|
||||
});
|
||||
|
||||
var text = Editor.elements.appearence.text;
|
||||
text.content.value = 'Button';
|
||||
text.size.value = '20';
|
||||
text.color.value = '#ffffff';
|
||||
|
||||
var background = Editor.elements.appearence.background;
|
||||
background.color.value = '#000000';
|
||||
|
||||
Editor.elements.container.classList.add('disabled');
|
||||
|
||||
Editor.selectImageTab('none', true);
|
||||
|
||||
ActionEditor.close();
|
||||
|
||||
Icons.deselect();
|
||||
},
|
||||
|
||||
selectImageTab(panelType: 'none' | 'icon' | 'upload', ignoreUpdate = false) {
|
||||
Editor.elements.imageui.imagetypes.querySelectorAll('.selectoritem').forEach((selectoritem: HTMLDivElement) => {
|
||||
if (panelType == selectoritem.getAttribute('panel')) selectoritem.classList.add('selected');
|
||||
else selectoritem.classList.remove('selected');
|
||||
});
|
||||
Editor.elements.imageui.imagepanels.querySelectorAll('.panel').forEach((panel: HTMLDivElement) => {
|
||||
if (panelType == panel.getAttribute('panel')) panel.classList.add('selected');
|
||||
else panel.classList.remove('selected');
|
||||
});
|
||||
Editor.elements.imageui.imageinfopanels.querySelectorAll('.infopanel').forEach((infopanel: HTMLDivElement) => {
|
||||
if (panelType == infopanel.getAttribute('panel')) infopanel.classList.add('selected');
|
||||
else infopanel.classList.remove('selected');
|
||||
});
|
||||
|
||||
Editor.currentImageType = panelType != 'none' ? panelType : null;
|
||||
|
||||
if (panelType == 'none') Editor.elements.imageui.advanced.style.display = 'none';
|
||||
else Editor.elements.imageui.advanced.style.display = 'flex';
|
||||
|
||||
if (panelType == 'icon') Icons.loadOnScreen();
|
||||
if (ignoreUpdate == false) Editor.registerChange();
|
||||
},
|
||||
|
||||
selectButtonType(buttonType: KeyTypes, ignoreUpdate = false) {
|
||||
if (buttonType == 'ghost') buttonType = 'custom';
|
||||
|
||||
Editor.elements.buttonui.buttontypes.querySelectorAll('.buttonitem').forEach((selectoritem: HTMLDivElement) => {
|
||||
if (buttonType == selectoritem.getAttribute('type')) selectoritem.classList.add('selected');
|
||||
else selectoritem.classList.remove('selected');
|
||||
});
|
||||
|
||||
Editor.currentButtonType = buttonType;
|
||||
|
||||
switch (buttonType) {
|
||||
case 'empty':
|
||||
case 'pageup':
|
||||
case 'pagedown':
|
||||
case 'currentpage':
|
||||
Editor.elements.buttonui.visual.style.display = 'none';
|
||||
Editor.elements.buttonui.actioninner.style.display = 'none';
|
||||
|
||||
Editor.elements.buttonui.containerTitles.forEach((title) => (title.style.display = 'none'));
|
||||
break;
|
||||
|
||||
case 'custom':
|
||||
Editor.elements.buttonui.visual.style.display = 'block';
|
||||
Editor.elements.buttonui.actioninner.style.display = 'block';
|
||||
Editor.elements.buttonui.containerTitles.forEach((title) => (title.style.display = 'flex'));
|
||||
break;
|
||||
}
|
||||
|
||||
if (ignoreUpdate == false) Editor.registerChange();
|
||||
},
|
||||
|
||||
setActionOptions(toggle: boolean, confirm: boolean, ignoreUpdate = false) {
|
||||
if (toggle) {
|
||||
ActionEditor.elements.up.container.style.display = 'none';
|
||||
ActionEditor.elements.latch.container.style.display = 'block';
|
||||
} else {
|
||||
ActionEditor.elements.up.container.style.display = 'block';
|
||||
ActionEditor.elements.latch.container.style.display = 'none';
|
||||
}
|
||||
if (confirm) {
|
||||
ActionEditor.elements.down.container.style.display = 'none';
|
||||
} else {
|
||||
ActionEditor.elements.down.container.style.display = 'block';
|
||||
}
|
||||
|
||||
Editor.isToggle = toggle;
|
||||
Editor.isConfirm = confirm;
|
||||
|
||||
Editor.elements.checks.toggle.checkbox.checked = toggle;
|
||||
Editor.elements.checks.confirm.checkbox.checked = confirm;
|
||||
|
||||
if (ignoreUpdate == false) Editor.registerChange();
|
||||
},
|
||||
|
||||
getAppearenceCategory(category: Editor_Categories, key: Page_Key = Editor.currentKey) {
|
||||
switch (category) {
|
||||
case 'text':
|
||||
var textData: Page_Key_Text =
|
||||
key.appearence != undefined && key.appearence.text != undefined
|
||||
? key.appearence.text
|
||||
: { value: '', size: 10, color: '#000000', offsetX: 0, offsetY: 0 };
|
||||
return textData;
|
||||
case 'background':
|
||||
var backgroundData: Page_Key_Background =
|
||||
key.appearence != undefined && key.appearence.background != undefined
|
||||
? key.appearence.background
|
||||
: { color: '#000000' };
|
||||
return backgroundData;
|
||||
case 'image':
|
||||
var imageData: Page_Key_Image =
|
||||
key.appearence != undefined && key.appearence.image != undefined
|
||||
? key.appearence.image
|
||||
: { offsetX: 0, offsetY: 0, size: 100, rotation: 0 };
|
||||
return imageData;
|
||||
}
|
||||
},
|
||||
|
||||
getElementCategoryValues(category: Editor_Categories) {
|
||||
switch (category) {
|
||||
case 'text':
|
||||
var textData: Page_Key_Text = {
|
||||
value: Editor.currentButtonType != 'empty' ? Editor.elements.appearence.text.content.value : '',
|
||||
color:
|
||||
Editor.currentButtonType != 'empty' ? Editor.elements.appearence.text.color.value : '#ffffff',
|
||||
size:
|
||||
Editor.currentButtonType != 'empty' ? parseInt(Editor.elements.appearence.text.size.value) : 20,
|
||||
offsetX:
|
||||
Editor.currentButtonType != 'empty'
|
||||
? parseFloat(Editor.elements.appearence.text.offsetX.value)
|
||||
: 0,
|
||||
offsetY:
|
||||
Editor.currentButtonType != 'empty'
|
||||
? parseFloat(Editor.elements.appearence.text.offsetY.value)
|
||||
: 0
|
||||
};
|
||||
return textData;
|
||||
case 'background':
|
||||
var backgroundData: Page_Key_Background = {
|
||||
color:
|
||||
Editor.currentButtonType != 'empty'
|
||||
? Editor.elements.appearence.background.color.value
|
||||
: '#000000'
|
||||
};
|
||||
return backgroundData;
|
||||
case 'image':
|
||||
var imageData: Page_Key_Image = {
|
||||
size:
|
||||
Editor.currentButtonType != 'empty'
|
||||
? parseFloat(Editor.elements.appearence.image.size.value)
|
||||
: 100,
|
||||
rotation:
|
||||
Editor.currentButtonType != 'empty'
|
||||
? parseFloat(Editor.elements.appearence.image.rotation.value)
|
||||
: 0,
|
||||
offsetX:
|
||||
Editor.currentButtonType != 'empty'
|
||||
? parseFloat(Editor.elements.appearence.image.offsetX.value)
|
||||
: 0,
|
||||
offsetY:
|
||||
Editor.currentButtonType != 'empty'
|
||||
? parseFloat(Editor.elements.appearence.image.offsetY.value)
|
||||
: 0
|
||||
};
|
||||
|
||||
if (Editor.currentImageType != null && Editor.currentButtonType != 'empty') {
|
||||
switch (Editor.currentImageType) {
|
||||
case 'icon':
|
||||
imageData.iconid = Icons.currentSelected;
|
||||
imageData.iconstyle = 'white';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return imageData;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Editor.listeners();
|
||||
|
||||
interface Editor {
|
||||
elements: {
|
||||
container: HTMLDivElement;
|
||||
appearence: {
|
||||
text: {
|
||||
content: HTMLInputElement;
|
||||
size: HTMLSelectElement;
|
||||
color: HTMLInputElement;
|
||||
offsetX: HTMLInputElement;
|
||||
offsetY: HTMLInputElement;
|
||||
};
|
||||
background: {
|
||||
color: HTMLInputElement;
|
||||
};
|
||||
image: {
|
||||
size: HTMLInputElement;
|
||||
offsetX: HTMLInputElement;
|
||||
offsetY: HTMLInputElement;
|
||||
rotation: HTMLInputElement;
|
||||
};
|
||||
};
|
||||
imageui: {
|
||||
imagetypes: HTMLDivElement;
|
||||
imageinfopanels: HTMLDivElement;
|
||||
imagepanels: HTMLDivElement;
|
||||
advanced: HTMLDivElement;
|
||||
};
|
||||
|
||||
buttonui: {
|
||||
buttontypes: HTMLDivElement;
|
||||
visual: HTMLDivElement;
|
||||
actioninner: HTMLDivElement;
|
||||
containerTitles: NodeListOf<HTMLDivElement>;
|
||||
};
|
||||
|
||||
checks: {
|
||||
toggle: {
|
||||
container: HTMLDivElement;
|
||||
checkbox: HTMLInputElement;
|
||||
};
|
||||
confirm: {
|
||||
container: HTMLDivElement;
|
||||
checkbox: HTMLInputElement;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
isOpen: boolean;
|
||||
currentKey: Page_Key;
|
||||
currentX: string;
|
||||
currentY: string;
|
||||
currentImageType: 'icon' | 'upload';
|
||||
currentButtonType: KeyTypes;
|
||||
isToggle: boolean;
|
||||
isConfirm: boolean;
|
||||
|
||||
listeners: () => void;
|
||||
export: () => Page_Key;
|
||||
open: (x: string, y: string, key: Page_Key) => void;
|
||||
close: () => void;
|
||||
registerChange: () => void;
|
||||
selectImageTab: (panelType: string, ignoreUpdate?: boolean) => void;
|
||||
selectButtonType: (buttonType: KeyTypes, ignoreUpdate?: boolean) => void;
|
||||
setActionOptions: (toggle: boolean, confirm: boolean, ignoreUpdate?: boolean) => void;
|
||||
|
||||
getAppearenceCategory: (category: Editor_Categories, key?: Page_Key) => any;
|
||||
getElementCategoryValues: (category: Editor_Categories) => any;
|
||||
}
|
||||
|
||||
type Editor_Categories = 'text' | 'background' | 'image';
|
||||
85
Frontend/pages/home/ts/Icons.ts
Normal file
85
Frontend/pages/home/ts/Icons.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
var Icons: Icons = {
|
||||
elements: {
|
||||
container: document.querySelector('.panel.icons').querySelector('.list')
|
||||
},
|
||||
|
||||
resizeTimeout: null,
|
||||
|
||||
currentSelected: null,
|
||||
|
||||
loadOnScreen() {
|
||||
Icons.elements.container.querySelectorAll('.icon').forEach((icon: HTMLDivElement) => {
|
||||
var position = icon.getBoundingClientRect();
|
||||
|
||||
// checking for partial visibility
|
||||
if (position.top < window.innerHeight && position.bottom >= 0) {
|
||||
if (icon.hasAttribute('notloaded')) {
|
||||
icon.removeAttribute('notloaded');
|
||||
var iconID = icon.getAttribute('iconID');
|
||||
|
||||
var whiteImg: HTMLImageElement = icon.querySelector('img.white');
|
||||
var blackImg: HTMLImageElement = icon.querySelector('img.black');
|
||||
|
||||
whiteImg.src = `/stc/materialicons/white/${iconID}.png`;
|
||||
blackImg.src = `/stc/materialicons/black/${iconID}.png`;
|
||||
whiteImg.style.opacity = '1';
|
||||
blackImg.style.opacity = '0';
|
||||
}
|
||||
}
|
||||
|
||||
icon.onclick = () => Icons.select(icon.getAttribute('iconID'));
|
||||
});
|
||||
},
|
||||
|
||||
listeners() {
|
||||
var counter = 0;
|
||||
Icons.elements.container.onscroll = () => {
|
||||
counter++;
|
||||
if (counter > 5) {
|
||||
Icons.loadOnScreen();
|
||||
counter = 0;
|
||||
}
|
||||
clearTimeout(Icons.resizeTimeout);
|
||||
Icons.resizeTimeout = setTimeout(() => {
|
||||
Icons.loadOnScreen();
|
||||
}, 100);
|
||||
};
|
||||
// Icons.loadOnScreen();
|
||||
},
|
||||
|
||||
select(iconID: string, ignoreUpdate: boolean = false) {
|
||||
var icons = Icons.elements.container.querySelectorAll('.icon');
|
||||
for (let i = 0; i < icons.length; i++) {
|
||||
var testIconID = icons[i].getAttribute('iconID');
|
||||
if (iconID != testIconID) icons[i].classList.remove('selected');
|
||||
else icons[i].classList.add('selected');
|
||||
}
|
||||
Icons.currentSelected = iconID;
|
||||
|
||||
if (ignoreUpdate == false) Editor.registerChange();
|
||||
},
|
||||
|
||||
deselect() {
|
||||
var icons = Icons.elements.container.querySelectorAll('.icon');
|
||||
for (let i = 0; i < icons.length; i++) icons[i].classList.remove('selected');
|
||||
|
||||
Icons.currentSelected = null;
|
||||
}
|
||||
};
|
||||
|
||||
Icons.listeners();
|
||||
|
||||
interface Icons {
|
||||
elements: {
|
||||
container: HTMLDivElement;
|
||||
};
|
||||
|
||||
resizeTimeout: any;
|
||||
|
||||
currentSelected: string;
|
||||
|
||||
loadOnScreen: () => void;
|
||||
listeners: () => void;
|
||||
select: (iconID: string, ignoreUpdate?: boolean) => void;
|
||||
deselect: () => void;
|
||||
}
|
||||
57
Frontend/pages/home/ts/KeyBoardHandler.ts
Normal file
57
Frontend/pages/home/ts/KeyBoardHandler.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
window.addEventListener('keydown', (ev: KeyboardEvent) => {
|
||||
if (document.activeElement && document.activeElement.nodeName != 'INPUT') {
|
||||
if ((isWin() && ev.ctrlKey) || (isMac() && ev.metaKey)) {
|
||||
switch (ev.key) {
|
||||
case 'c':
|
||||
ev.preventDefault();
|
||||
if (PageHandler.currentPageID && KeyHandler.selected.length == 1) {
|
||||
UndeckedClipboard.copyKey(
|
||||
parseInt(KeyHandler.selected[0].split(',')[0]),
|
||||
parseInt(KeyHandler.selected[0].split(',')[1])
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'x':
|
||||
ev.preventDefault();
|
||||
if (PageHandler.currentPageID && KeyHandler.selected.length == 1) {
|
||||
UndeckedClipboard.cutKey(
|
||||
parseInt(KeyHandler.selected[0].split(',')[0]),
|
||||
parseInt(KeyHandler.selected[0].split(',')[1])
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'v':
|
||||
ev.preventDefault();
|
||||
if (PageHandler.currentPageID && KeyHandler.selected.length == 1) {
|
||||
UndeckedClipboard.pasteKey(
|
||||
parseInt(KeyHandler.selected[0].split(',')[0]),
|
||||
parseInt(KeyHandler.selected[0].split(',')[1])
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'g':
|
||||
ev.preventDefault();
|
||||
if (PageHandler.currentPageID && KeyHandler.selected.length == 1) {
|
||||
UndeckedClipboard.copyGhostKey(
|
||||
parseInt(KeyHandler.selected[0].split(',')[0]),
|
||||
parseInt(KeyHandler.selected[0].split(',')[1])
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (ev.key) {
|
||||
case 'Delete':
|
||||
var x = parseInt(KeyHandler.selected[0].split(',')[0]);
|
||||
var y = parseInt(KeyHandler.selected[0].split(',')[1]);
|
||||
socket.emit('page', 'operation', 'delete', PageHandler.currentPageID, x, y);
|
||||
Editor.close();
|
||||
KeyHandler.select(String(x), String(y));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
372
Frontend/pages/home/ts/KeyHandler.ts
Normal file
372
Frontend/pages/home/ts/KeyHandler.ts
Normal file
@@ -0,0 +1,372 @@
|
||||
var KeyHandler: KeyHandler = {
|
||||
elements: {
|
||||
keys: {}
|
||||
},
|
||||
|
||||
selected: [],
|
||||
imagecache: {},
|
||||
ghostImage: null,
|
||||
|
||||
init() {
|
||||
KeyHandler.ghostImage = new Image();
|
||||
KeyHandler.ghostImage.src = '/stc/icon/ghost.png';
|
||||
KeyHandler.ghostImage.onload = () => {
|
||||
for (var y = 0; y < 4; y++) {
|
||||
if (KeyHandler.elements.keys[y] == undefined) KeyHandler.elements.keys[y] = {};
|
||||
for (var x = 0; x < 8; x++) {
|
||||
var keyCheck: HTMLDivElement = document.querySelector(`.key[y="${y}"][x="${x}"]`);
|
||||
if (keyCheck) {
|
||||
((keyX: number, keyY: number, key: HTMLDivElement) => {
|
||||
var canvas = key.querySelector('canvas');
|
||||
var context = canvas.getContext('2d');
|
||||
KeyHandler.elements.keys[keyY][keyX] = { key, canvas, context };
|
||||
|
||||
context.textBaseline = 'middle';
|
||||
context.textAlign = 'center';
|
||||
|
||||
key.onclick = () => {
|
||||
if (altDown == false) KeyHandler.select(String(keyX), String(keyY));
|
||||
else {
|
||||
socket.emit('page', 'executekey', PageHandler.currentPageID, keyX, keyY);
|
||||
}
|
||||
};
|
||||
})(x, y, keyCheck);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
clear() {
|
||||
for (var y in KeyHandler.elements.keys) {
|
||||
for (var x in KeyHandler.elements.keys[y]) {
|
||||
var key = KeyHandler.elements.keys[y][x];
|
||||
|
||||
key.context.clearRect(0, 0, 100, 100);
|
||||
key.key.classList.remove('selected');
|
||||
}
|
||||
}
|
||||
KeyHandler.selected = [];
|
||||
Editor.close();
|
||||
},
|
||||
|
||||
render(x: string, y: string, data: Page_Key) {
|
||||
if (KeyHandler.elements.keys[String(y)]) {
|
||||
if (KeyHandler.elements.keys[String(y)][String(x)]) {
|
||||
var context = KeyHandler.elements.keys[String(y)][String(x)].context;
|
||||
|
||||
context.clearRect(0, 0, renderQuality, renderQuality);
|
||||
|
||||
if (data.state.type == 'custom') {
|
||||
var appearence = data.appearence;
|
||||
|
||||
render(appearence);
|
||||
} else if (data.state.type == 'ghost') {
|
||||
var appearence = data.appearence;
|
||||
|
||||
if (appearence == undefined) appearence = {};
|
||||
|
||||
appearence.system = { ghost: true };
|
||||
|
||||
render(appearence);
|
||||
} else if (data.state.type == 'pageup') {
|
||||
render({
|
||||
text: { value: 'Up', color: '#ffffff', size: 18, offsetX: 0, offsetY: 25 },
|
||||
background: { color: '#4676b7' },
|
||||
image: {
|
||||
size: 100,
|
||||
rotation: 0,
|
||||
offsetX: 0,
|
||||
offsetY: -15,
|
||||
iconid: 'keyboard_arrow_up',
|
||||
iconstyle: 'white'
|
||||
},
|
||||
system: {
|
||||
border: {
|
||||
color: '#253e5e',
|
||||
thickness: 8
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (data.state.type == 'pagedown') {
|
||||
render({
|
||||
text: { value: 'Down', color: '#ffffff', size: 18, offsetX: 0, offsetY: -25 },
|
||||
background: { color: '#4676b7' },
|
||||
image: {
|
||||
size: 100,
|
||||
rotation: 0,
|
||||
offsetX: 0,
|
||||
offsetY: 15,
|
||||
iconid: 'keyboard_arrow_down',
|
||||
iconstyle: 'white'
|
||||
},
|
||||
system: {
|
||||
border: {
|
||||
color: '#253e5e',
|
||||
thickness: 8
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (data.state.type == 'currentpage') {
|
||||
render({
|
||||
text: {
|
||||
value: `Page\\n\\n${PageHandler.currentIndex + 1}`,
|
||||
color: '#ffffff',
|
||||
size: 22,
|
||||
offsetX: 0,
|
||||
offsetY: 0
|
||||
},
|
||||
background: { color: '#4676b7' },
|
||||
system: {
|
||||
border: {
|
||||
color: '#253e5e',
|
||||
thickness: 8
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} else console.error(`Invalid x '${x}'`);
|
||||
} else console.error(`Invalid y '${y}'`);
|
||||
|
||||
function render(appearence: Page_Key_Appearence) {
|
||||
if (appearence.background != undefined) {
|
||||
context.fillStyle = appearence.background.color;
|
||||
context.fillRect(0, 0, renderQuality, renderQuality);
|
||||
context.fill();
|
||||
}
|
||||
|
||||
if (appearence.text != undefined) {
|
||||
context.fillStyle = appearence.text.color;
|
||||
context.font = `800 ${appearence.text.size * fontSizeRatio}px "Montserrat"`;
|
||||
|
||||
var text = appearence.text.value;
|
||||
var lineHeight = appearence.text.size * fontSizeRatio;
|
||||
|
||||
var centerX = renderQuality / 2 + appearence.text.offsetX / 100 * (renderQuality * 2);
|
||||
var centerY = renderQuality / 2 + appearence.text.offsetY / 100 * renderQuality;
|
||||
var canvasYCounter = centerY;
|
||||
|
||||
var words = text.replace(/\\n/g, ' \\n ').split(' ');
|
||||
var line = '';
|
||||
|
||||
var totalLineHeight = 0;
|
||||
for (var n = 0; n < words.length; n++) {
|
||||
if (words[n].length == 0) continue;
|
||||
|
||||
var testLine = line + words[n] + ' ';
|
||||
var metrics = context.measureText(testLine);
|
||||
var testWidth = metrics.width;
|
||||
if (words[n] != '\\n')
|
||||
if (testWidth > renderQuality && n > 0) {
|
||||
line = words[n] + ' ';
|
||||
totalLineHeight += lineHeight;
|
||||
} else {
|
||||
line = testLine;
|
||||
}
|
||||
else {
|
||||
totalLineHeight += lineHeight;
|
||||
line = '';
|
||||
}
|
||||
}
|
||||
|
||||
line = '';
|
||||
canvasYCounter = canvasYCounter - totalLineHeight / 2;
|
||||
|
||||
var firstSkip = false;
|
||||
for (var n = 0; n < words.length; n++) {
|
||||
if (words[n].length == 0) continue;
|
||||
var testLine = line + words[n] + ' ';
|
||||
var metrics = context.measureText(testLine);
|
||||
var testWidth = metrics.width;
|
||||
if (words[n] != '\\n')
|
||||
if (testWidth > renderQuality && n > 0) {
|
||||
context.fillText(line, centerX, canvasYCounter);
|
||||
line = words[n] + ' ';
|
||||
canvasYCounter += lineHeight;
|
||||
} else {
|
||||
line = testLine;
|
||||
}
|
||||
else {
|
||||
context.fillText(line, centerX, canvasYCounter);
|
||||
line = '';
|
||||
|
||||
canvasYCounter += firstSkip ? lineHeight * 2 : lineHeight;
|
||||
if (firstSkip) firstSkip = false;
|
||||
}
|
||||
}
|
||||
context.fillText(line, centerX, canvasYCounter);
|
||||
}
|
||||
|
||||
if (appearence.image != undefined) {
|
||||
var imageAddress =
|
||||
appearence.image.address != undefined
|
||||
? appearence.image.address
|
||||
: appearence.image.iconid != undefined
|
||||
? `/stc/materialicons/white/${appearence.image.iconid}.png`
|
||||
: null;
|
||||
|
||||
var imageSize =
|
||||
appearence.image.size != undefined ? appearence.image.size / 100 * renderQuality : renderQuality;
|
||||
if (imageAddress) {
|
||||
var centerX = renderQuality / 2 + appearence.image.offsetX / 100 * renderQuality;
|
||||
var centerY = renderQuality / 2 + appearence.image.offsetY / 100 * renderQuality;
|
||||
|
||||
if (KeyHandler.imagecache[imageAddress] != undefined)
|
||||
renderImage(KeyHandler.imagecache[imageAddress]);
|
||||
else {
|
||||
KeyHandler.imagecache[imageAddress] = new Image();
|
||||
KeyHandler.imagecache[imageAddress].src = imageAddress;
|
||||
KeyHandler.imagecache[imageAddress].onload = () =>
|
||||
renderImage(KeyHandler.imagecache[imageAddress]);
|
||||
}
|
||||
|
||||
function renderImage(image) {
|
||||
context.save();
|
||||
context.translate(centerX, centerY);
|
||||
context.rotate(appearence.image.rotation * Math.PI / 180);
|
||||
context.drawImage(
|
||||
image,
|
||||
imageSize / 2 - imageSize,
|
||||
imageSize / 2 - imageSize,
|
||||
imageSize,
|
||||
imageSize
|
||||
);
|
||||
context.restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (appearence.system != undefined) {
|
||||
if (appearence.system.border != undefined) {
|
||||
var relativeThickness = appearence.system.border.thickness / 100 * renderQuality;
|
||||
context.fillStyle = appearence.system.border.color;
|
||||
context.fillRect(0, 0, renderQuality, relativeThickness);
|
||||
context.rect(0, renderQuality - relativeThickness, renderQuality, relativeThickness);
|
||||
context.rect(0, 0, relativeThickness, renderQuality);
|
||||
context.rect(renderQuality - relativeThickness, 0, relativeThickness, renderQuality);
|
||||
context.fill();
|
||||
}
|
||||
|
||||
if (appearence.system.ghost == true) {
|
||||
var size = 50 / 100 * renderQuality;
|
||||
context.save();
|
||||
context.globalAlpha = 0.7;
|
||||
context.translate(renderQuality / 2, renderQuality / 2);
|
||||
context.drawImage(KeyHandler.ghostImage, size / 2 - size, size / 2 - size, size, size);
|
||||
context.restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
select(x: string, y: string, multi: boolean = false) {
|
||||
if (PageHandler.currentPage != null) {
|
||||
if (PageHandler.currentPage.keys[y] != undefined && PageHandler.currentPage.keys[y][x] != undefined) {
|
||||
// var keyConfig = PageHandler.currentPage.keys[y][x];
|
||||
|
||||
var query = `${x},${y}`;
|
||||
if (multi) {
|
||||
if (!KeyHandler.selected.includes(query)) KeyHandler.selected.push(query);
|
||||
} else
|
||||
KeyHandler.selected = [
|
||||
query
|
||||
];
|
||||
|
||||
document.querySelectorAll('.key').forEach((key: HTMLDivElement) => {
|
||||
var checkX = key.getAttribute('x');
|
||||
var checkY = key.getAttribute('y');
|
||||
var checkQuery = `${checkX},${checkY}`;
|
||||
if (KeyHandler.selected.includes(checkQuery)) key.classList.add('selected');
|
||||
else key.classList.remove('selected');
|
||||
});
|
||||
|
||||
if (KeyHandler.selected.length > 1) Editor.close();
|
||||
else {
|
||||
socket.emit('page', 'getkey', PageHandler.currentPageID, x, y, (key: Page_Key) => {
|
||||
Editor.open(x, y, key);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
KeyHandler.init();
|
||||
|
||||
var altDown = false;
|
||||
|
||||
window.addEventListener('keydown', (e: KeyboardEvent) => {
|
||||
if (e.key == 'alt' || e.altKey == true) altDown = true;
|
||||
});
|
||||
window.addEventListener('keyup', (e: KeyboardEvent) => {
|
||||
if (e.key == 'alt' || e.altKey == false) altDown = false;
|
||||
});
|
||||
|
||||
interface KeyHandler {
|
||||
elements: {
|
||||
keys: {
|
||||
[y: string]: {
|
||||
[x: string]: {
|
||||
key: HTMLDivElement;
|
||||
canvas: HTMLCanvasElement;
|
||||
context: CanvasRenderingContext2D;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
selected: string[];
|
||||
imagecache: { [iconID: string]: HTMLImageElement };
|
||||
ghostImage: any;
|
||||
|
||||
init: () => void;
|
||||
clear: () => void;
|
||||
render: (x: string, y: string, data: Page_Key) => void;
|
||||
select: (x: string, y: string, multi?: boolean) => void;
|
||||
}
|
||||
|
||||
interface Page_Key {
|
||||
id: string;
|
||||
state: {
|
||||
type: KeyTypes;
|
||||
toggle: boolean;
|
||||
confirm: boolean;
|
||||
};
|
||||
actions: Page_Key_ActionTypes;
|
||||
appearence: Page_Key_Appearence;
|
||||
}
|
||||
interface Page_Key_Appearence {
|
||||
text?: Page_Key_Text;
|
||||
image?: Page_Key_Image;
|
||||
background?: Page_Key_Background;
|
||||
system?: {
|
||||
border?: {
|
||||
color: string;
|
||||
thickness: number;
|
||||
};
|
||||
ghost?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
interface Page_Key_Text {
|
||||
value: string;
|
||||
size: number;
|
||||
color: string;
|
||||
offsetX: number;
|
||||
offsetY: number;
|
||||
}
|
||||
|
||||
interface Page_Key_Image {
|
||||
address?: string;
|
||||
iconid?: string;
|
||||
iconstyle?: 'black' | 'white';
|
||||
size: number;
|
||||
offsetX: number;
|
||||
offsetY: number;
|
||||
rotation: number;
|
||||
}
|
||||
|
||||
interface Page_Key_Background {
|
||||
color: string;
|
||||
}
|
||||
|
||||
type KeyTypes = 'empty' | 'custom' | 'pageup' | 'pagedown' | 'currentpage' | 'ghost';
|
||||
17
Frontend/pages/home/ts/MetaData.ts
Normal file
17
Frontend/pages/home/ts/MetaData.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
function isMac() {
|
||||
return navigator.appVersion.indexOf('Mac') != -1;
|
||||
}
|
||||
function isWin() {
|
||||
return navigator.appVersion.indexOf('Win') != -1;
|
||||
}
|
||||
function isLinux() {
|
||||
return navigator.appVersion.indexOf('Linux') != -1;
|
||||
}
|
||||
function isUnix() {
|
||||
return navigator.appVersion.indexOf('X11') != -1;
|
||||
}
|
||||
|
||||
document.querySelectorAll('.shortcutKey').forEach((command: HTMLDivElement) => {
|
||||
if (isMac()) command.innerText = 'Command';
|
||||
else command.innerText = 'Ctrl';
|
||||
});
|
||||
17
Frontend/pages/home/ts/OverviewScaler.ts
Normal file
17
Frontend/pages/home/ts/OverviewScaler.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
function scaleOverview() {
|
||||
var overview: HTMLDivElement = document.querySelector('.overview');
|
||||
var deck: HTMLDivElement = overview.querySelector('.deck');
|
||||
|
||||
if (overview.clientWidth > 0) {
|
||||
var margin = 10;
|
||||
|
||||
var widthScale = overview.clientWidth / (deck.clientWidth + margin * 2);
|
||||
var heightScale = overview.clientHeight / (deck.clientHeight + margin * 2);
|
||||
|
||||
if (deck.clientHeight * widthScale > overview.clientHeight) deck.style.transform = `scale(${heightScale})`;
|
||||
else deck.style.transform = `scale(${widthScale})`;
|
||||
} else setTimeout(scaleOverview, 100);
|
||||
}
|
||||
|
||||
window.addEventListener('resize', scaleOverview);
|
||||
scaleOverview();
|
||||
141
Frontend/pages/home/ts/PageHandler.ts
Normal file
141
Frontend/pages/home/ts/PageHandler.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
var PageHandler: PageHandler = {
|
||||
elements: {
|
||||
pagename: document.querySelector('.pagename'),
|
||||
left: document.querySelector('.pageselector').querySelector('.box.left'),
|
||||
right: document.querySelector('.pageselector').querySelector('.box.right'),
|
||||
add: document.querySelector('.pageselector').querySelector('.box.add')
|
||||
},
|
||||
|
||||
currentPageID: null,
|
||||
currentPage: null,
|
||||
currentIndex: null,
|
||||
|
||||
request(pageID: string, index: number) {
|
||||
socket.emit('page', 'request', pageID, (err?: string, page?: Page_Config) => {
|
||||
if (err) UndeckedNotification(`Error whilst getting page ${pageID}: ${err}`, 'error', 5000);
|
||||
else {
|
||||
PageHandler.currentIndex = index;
|
||||
PageHandler.render(page);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
render(page: Page_Config) {
|
||||
KeyHandler.clear();
|
||||
|
||||
PageHandler.currentPageID = page.pageID;
|
||||
PageHandler.currentPage = page;
|
||||
|
||||
PageList.select(page.pageID);
|
||||
|
||||
PageHandler.elements.pagename.value = page.name;
|
||||
PageHandler.elements.pagename.removeAttribute('disabled');
|
||||
|
||||
for (var y in page.keys) {
|
||||
for (var x in page.keys[y]) {
|
||||
KeyHandler.render(x, y, page.keys[y][x]);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
listeners() {
|
||||
PageHandler.elements.pagename.oninput = () => {
|
||||
if (PageHandler.currentPageID != null && PageHandler.elements.pagename.value.length > 0) {
|
||||
socket.emit('page', 'setname', PageHandler.currentPageID, PageHandler.elements.pagename.value);
|
||||
}
|
||||
};
|
||||
|
||||
PageHandler.elements.add.onclick = () => {
|
||||
var pageName = prompt('New page name', 'Untitled page');
|
||||
if (pageName && pageName.length > 0) {
|
||||
socket.emit('page', 'create', pageName);
|
||||
}
|
||||
};
|
||||
|
||||
PageHandler.elements.left.onclick = () => {
|
||||
var selected = document.querySelector('.pageitem.selected');
|
||||
if (selected && selected.previousElementSibling) (<HTMLElement>selected.previousElementSibling).click();
|
||||
};
|
||||
|
||||
PageHandler.elements.right.onclick = () => {
|
||||
var selected = document.querySelector('.pageitem.selected');
|
||||
if (selected && selected.nextElementSibling) (<HTMLElement>selected.nextElementSibling).click();
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
PageHandler.listeners();
|
||||
|
||||
interface PageHandler {
|
||||
elements: {
|
||||
pagename: HTMLInputElement;
|
||||
|
||||
left: HTMLDivElement;
|
||||
right: HTMLDivElement;
|
||||
add: HTMLDivElement;
|
||||
};
|
||||
|
||||
currentPageID: string;
|
||||
currentPage: Page_Config;
|
||||
currentIndex: number;
|
||||
|
||||
request: (pageID: string, index: number) => void;
|
||||
render: (page: Page_Config) => void;
|
||||
listeners: () => void;
|
||||
}
|
||||
|
||||
interface Page_Config {
|
||||
pageID: string;
|
||||
name?: string;
|
||||
|
||||
keys?: Page_Config_Keys;
|
||||
}
|
||||
|
||||
interface Page_Config_Keys {
|
||||
'0'?: {
|
||||
'0'?: Page_Key;
|
||||
'1'?: Page_Key;
|
||||
'2'?: Page_Key;
|
||||
'3'?: Page_Key;
|
||||
'4'?: Page_Key;
|
||||
'5'?: Page_Key;
|
||||
'6'?: Page_Key;
|
||||
'7'?: Page_Key;
|
||||
'8'?: Page_Key;
|
||||
};
|
||||
'1'?: {
|
||||
'0'?: Page_Key;
|
||||
'1'?: Page_Key;
|
||||
'2'?: Page_Key;
|
||||
'3'?: Page_Key;
|
||||
'4'?: Page_Key;
|
||||
'5'?: Page_Key;
|
||||
'6'?: Page_Key;
|
||||
'7'?: Page_Key;
|
||||
'8'?: Page_Key;
|
||||
};
|
||||
'2'?: {
|
||||
'0'?: Page_Key;
|
||||
'1'?: Page_Key;
|
||||
'2'?: Page_Key;
|
||||
'3'?: Page_Key;
|
||||
'4'?: Page_Key;
|
||||
'5'?: Page_Key;
|
||||
'6'?: Page_Key;
|
||||
'7'?: Page_Key;
|
||||
'8'?: Page_Key;
|
||||
};
|
||||
'3'?: {
|
||||
'0'?: Page_Key;
|
||||
'1'?: Page_Key;
|
||||
'2'?: Page_Key;
|
||||
'3'?: Page_Key;
|
||||
'4'?: Page_Key;
|
||||
'5'?: Page_Key;
|
||||
'6'?: Page_Key;
|
||||
'7'?: Page_Key;
|
||||
'8'?: Page_Key;
|
||||
};
|
||||
}
|
||||
|
||||
type Icon = '';
|
||||
115
Frontend/pages/home/ts/PageList.ts
Normal file
115
Frontend/pages/home/ts/PageList.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
var PageList: PageList = {
|
||||
elements: {
|
||||
container: document.querySelector('.pageselector')
|
||||
},
|
||||
|
||||
order: null,
|
||||
|
||||
firstRender: true,
|
||||
|
||||
render(pagelist: PageListItem[]) {
|
||||
var list = ce('div', 'list');
|
||||
|
||||
PageList.order = pagelist.map((page) => {
|
||||
return page.pageID;
|
||||
});
|
||||
|
||||
pagelist.forEach((pagelistitem, index) => {
|
||||
var pageitem = ce('div', 'pageitem', { pageID: pagelistitem.pageID });
|
||||
var name = ce('div', 'name', null, pagelistitem.name);
|
||||
pageitem.appendChild(name);
|
||||
var move = ce('div', 'move');
|
||||
|
||||
var up = ce('div', [
|
||||
'moveitem',
|
||||
'up'
|
||||
]);
|
||||
up.appendChild(ce('img', 'normal', { src: '/stc/icon/up.png' }));
|
||||
up.appendChild(ce('img', 'selected', { src: '/stc/icon/up_gray.png' }));
|
||||
var down = ce('div', [
|
||||
'moveitem',
|
||||
'down'
|
||||
]);
|
||||
down.appendChild(ce('img', 'normal', { src: '/stc/icon/down.png' }));
|
||||
down.appendChild(ce('img', 'selected', { src: '/stc/icon/down_gray.png' }));
|
||||
up.onclick = (e: MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
|
||||
var current = PageList.order.indexOf(pagelistitem.pageID);
|
||||
var newIndex = Math.max(current - 1, 0);
|
||||
|
||||
PageList.order.splice(current, 1);
|
||||
PageList.order.splice(newIndex, 0, pagelistitem.pageID);
|
||||
|
||||
socket.emit('page', 'setorder', PageList.order);
|
||||
};
|
||||
down.onclick = (e: MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
|
||||
var current = PageList.order.indexOf(pagelistitem.pageID);
|
||||
var newIndex = Math.max(current + 1, 0);
|
||||
|
||||
PageList.order.splice(current, 1);
|
||||
PageList.order.splice(newIndex, 0, pagelistitem.pageID);
|
||||
|
||||
socket.emit('page', 'setorder', PageList.order);
|
||||
};
|
||||
move.appendChild(up);
|
||||
move.appendChild(down);
|
||||
pageitem.appendChild(move);
|
||||
pageitem.onclick = () => {
|
||||
PageHandler.request(pagelistitem.pageID, index);
|
||||
};
|
||||
list.appendChild(pageitem);
|
||||
|
||||
if (PageHandler.currentPageID == pagelistitem.pageID) {
|
||||
PageHandler.request(pagelistitem.pageID, index);
|
||||
}
|
||||
});
|
||||
|
||||
var existing = PageList.elements.container.querySelector('.list');
|
||||
if (existing) existing.parentElement.removeChild(existing);
|
||||
PageList.elements.container.appendChild(list);
|
||||
},
|
||||
|
||||
select(pageID: string): boolean {
|
||||
var selectedFound = false;
|
||||
PageList.elements.container.querySelectorAll('.pageitem').forEach((item: HTMLDivElement) => {
|
||||
var itemPageID = item.getAttribute('pageID');
|
||||
if (itemPageID == pageID) {
|
||||
item.classList.add('selected');
|
||||
selectedFound = true;
|
||||
} else item.classList.remove('selected');
|
||||
});
|
||||
|
||||
if (selectedFound) {
|
||||
history.pushState(null, null, `/pages/${pageID}`);
|
||||
TabController.setTitle(`Page ${pageID}`);
|
||||
}
|
||||
return selectedFound;
|
||||
},
|
||||
|
||||
updateName(pageID: string, name: string) {
|
||||
var item = PageList.elements.container.querySelector(`.pageitem[pageid="${pageID}"]`);
|
||||
if (item) {
|
||||
var nameElement: HTMLDivElement = item.querySelector('.name');
|
||||
nameElement.innerText = name;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
interface PageList {
|
||||
elements: {
|
||||
container: HTMLDivElement;
|
||||
};
|
||||
firstRender: boolean;
|
||||
order: string[];
|
||||
|
||||
render: (pagelist: PageListItem[]) => void;
|
||||
select: (pageID: string) => boolean;
|
||||
updateName: (pageID: string, name: string) => void;
|
||||
}
|
||||
interface PageListItem {
|
||||
pageID: string;
|
||||
name: string;
|
||||
}
|
||||
95
Frontend/pages/home/ts/TabControllers.ts
Normal file
95
Frontend/pages/home/ts/TabControllers.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
var TabController: TabController = {
|
||||
elements: {
|
||||
menu: document.querySelector('.tabmenu'),
|
||||
pages: document.querySelector('.tabpages')
|
||||
},
|
||||
|
||||
setTitle(title: string) {
|
||||
var titleElement = document.querySelector('title');
|
||||
titleElement.innerText = `Undecked - ${title}`;
|
||||
},
|
||||
|
||||
show(tabname: string) {
|
||||
TabController.elements.menu.querySelectorAll('.item').forEach((element: HTMLDivElement) => {
|
||||
var tab = element.getAttribute('tab');
|
||||
if (tab == tabname) element.classList.add('active');
|
||||
else element.classList.remove('active');
|
||||
});
|
||||
TabController.elements.pages.querySelectorAll('.item').forEach((element: HTMLDivElement) => {
|
||||
var tab = element.getAttribute('tab');
|
||||
if (tab == tabname) element.classList.add('active');
|
||||
else element.classList.remove('active');
|
||||
});
|
||||
|
||||
if (tabname == 'pages') {
|
||||
var pageID = PageHandler.currentPageID;
|
||||
|
||||
history.pushState(null, null, `/pages/pageID`);
|
||||
TabController.setTitle(`Page ${pageID}`);
|
||||
} else {
|
||||
history.pushState(null, null, `/${tabname}`);
|
||||
|
||||
TabController.setTitle(tabname.charAt(0).toUpperCase() + tabname.slice(1));
|
||||
}
|
||||
},
|
||||
|
||||
registerListeners() {
|
||||
TabController.elements.menu.querySelectorAll('.item').forEach((element: HTMLDivElement) => {
|
||||
var tab = element.getAttribute('tab');
|
||||
element.onclick = () => {
|
||||
TabController.show(tab);
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
init() {
|
||||
var args = window.location.pathname.split('/');
|
||||
if (args.length > 0) args.splice(0, 1);
|
||||
|
||||
var tab = args.length > 0 ? args[0] : null;
|
||||
var subTab = args.length > 1 ? args[1] : null;
|
||||
|
||||
if (PageList.order != null) {
|
||||
if (tab != undefined && tab.length > 0) {
|
||||
if (tab == 'pages') {
|
||||
var valid = PageList.select(subTab);
|
||||
|
||||
if (valid) {
|
||||
var index = PageList.order.indexOf(subTab);
|
||||
PageHandler.request(PageList.order[index], index);
|
||||
TabController.show('pages');
|
||||
} else {
|
||||
PageHandler.request(PageList.order[0], 0);
|
||||
TabController.show('pages');
|
||||
}
|
||||
} else {
|
||||
PageHandler.request(PageList.order[0], 0);
|
||||
TabController.show(tab);
|
||||
}
|
||||
} else {
|
||||
PageList.firstRender = false;
|
||||
PageHandler.request(PageList.order[0], 0);
|
||||
TabController.show('pages');
|
||||
}
|
||||
} else
|
||||
setTimeout(() => {
|
||||
TabController.init();
|
||||
}, 200);
|
||||
}
|
||||
};
|
||||
|
||||
TabController.init();
|
||||
TabController.registerListeners();
|
||||
|
||||
interface TabController {
|
||||
elements: {
|
||||
menu: HTMLDivElement;
|
||||
pages: HTMLDivElement;
|
||||
};
|
||||
|
||||
setTitle: (title: string) => void;
|
||||
show: (tabname: string) => void;
|
||||
registerListeners: () => void;
|
||||
|
||||
init: () => void;
|
||||
}
|
||||
Reference in New Issue
Block a user