(function(window, document, E) {
var gridPad = 5;
function vectorAdd(a, b) {
return a.map((val, dim) => val + b[dim]);
}
function vectorSub(a, b) {
return a.map((val, dim) => val - b[dim]);
}
function vectorDistSquare(a, b) {
return vectorSub(a, b).reduce((acc, val) => {
return acc + (val * val);
}, 0);
}
function Canvas(elem, socket) {
this.elem = elem;
this.view = E('div', {
className: 'view',
parent: this.elem,
});
this.pos = [0, 0];
this.screens = [];
this.focuses = {};
function dragStart(e) {
if (e.ctrlKey) {
this.addDummyScreen(this.localizePos(e.pos));
return;
}
if (this.focuses[e.id]) {
return;
}
var elem = e.target;
while (elem != this.elem && !elem.dataset.id) {
elem = elem.parentElement;
}
var target;
if (elem != this.elem) {
target = this.screens[elem.dataset.id];
this.view.appendChild(target.elem);
} else {
target = this;
}
for (var key in this.focuses) {
if (this.focuses[key].target == target) {
return;
}
}
target.elem.classList.add('dragging');
if (target.dragStart) {
e.source = this;
e.delta = [0, 0];
target.dragStart(e);
}
this.focuses[e.id] = {
target: target,
lastPos: e.pos,
};
};
function dragMove(e) {
var focus = this.focuses[e.id];
if (!focus) {
return;
}
var target = focus.target;
if (target.dragMove) {
e.source = this;
e.delta = vectorSub(e.pos, focus.lastPos);
target.dragMove(e);
}
focus.lastPos = e.pos;
};
function dragEnd(e) {
var focus = this.focuses[e.id];
if (!focus) {
return;
}
var target = focus.target;
target.elem.classList.remove('dragging');
if (target.dragEnd) {
e.source = this;
e.delta = vectorSub(e.pos, focus.lastPos);
target.dragEnd(e);
}
socket.send(JSON.stringify({
Screens: this.cluster.screens.map((screen, id) => {
if (this.screens[id]) {
var edges = this.screens[id].edges;
screen.edges = {
left: edges[0][0] ? edges[0][0].id : null,
right: edges[0][1] ? edges[0][1].id : null,
top: edges[1][0] ? edges[1][0].id : null,
bottom: edges[1][1] ? edges[1][1].id : null,
};
}
return screen;
}),
}));
delete this.focuses[e.id];
};
function mouseEvent(cb, e) {
if (e.button !== 0) return;
cb.call(this, {
id: null,
target: e.target,
pos: [e.clientX, e.clientY],
ctrlKey: e.ctrlKey,
altKey: e.altKey,
shiftKey: e.shiftKey,
});
}
elem.addEventListener('mousedown', mouseEvent.bind(this, dragStart), false);
window.addEventListener('mousemove', mouseEvent.bind(this, dragMove), false);
window.addEventListener('mouseup', mouseEvent.bind(this, dragEnd), false);
function touchEvent(cb, e) {
for (var i = 0; i < e.changedTouches.length; i++) {
var touch = e.changedTouches[i];
cb.call(this, {
id: touch.identifier,
target: e.target,
pos: [touch.clientX, touch.clientY],
ctrlKey: e.ctrlKey,
altKey: e.altKey,
shiftKey: e.shiftKey,
});
}
e.preventDefault();
}
elem.addEventListener('touchstart', touchEvent.bind(this, dragStart), false);
elem.addEventListener('touchmove', touchEvent.bind(this, dragMove), false);
elem.addEventListener('touchend', touchEvent.bind(this, dragEnd), false);
socket.onmessage = (e) => {
var obj = JSON.parse(e.data);
var type = Object.keys(obj)[0];
var event = obj[type];
switch(type) {
case "Cluster":
this.replaceCluster(event);
break;
}
};
}
Canvas.prototype.setPos = function(pos) {
this.pos = pos;
this.view.style.transform = 'translate(' + this.pos[0] + 'px,' + this.pos[1] + 'px)';
};
Canvas.prototype.getCenter = function() {
var rect = this.elem.getBoundingClientRect();
return [
rect.width / 2,
rect.height / 2,
];
};
Canvas.prototype.localizePos = function(pos) {
var rect = this.elem.getBoundingClientRect();
return [
pos[0] - rect.left - this.pos[0],
pos[1] - rect.top - this.pos[1],
];
};
Canvas.prototype.replaceCluster = function(cluster) {
this.cluster = cluster;
this.screens.forEach((screen) => {
screen.elem.parentElement.removeChild(screen.elem)
});
this.screens = [];
this.addScreens(cluster, cluster.local_screen, this.getCenter());
};
Canvas.prototype.addScreens = function(cluster, id, pos) {
var obj = this.screens[id];
if (obj) return obj;
var obj = cluster.screens[id];
if (!obj) return null;
var screen = new Screen({
id: id,
name: obj.name,
pos: pos,
size: [200, 125],
local: cluster.local_screen === id,
});
this.screens[id] = screen;
this.view.appendChild(screen.elem);
screen.edges = [[
this.addScreens(cluster, obj.edges.left, vectorSub(pos, [screen.size[0] + gridPad, 0])),
this.addScreens(cluster, obj.edges.right, vectorAdd(pos, [screen.size[0] + gridPad, 0])),
], [
this.addScreens(cluster, obj.edges.top, vectorSub(pos, [0, screen.size[1] + gridPad])),
this.addScreens(cluster, obj.edges.bottom, vectorAdd(pos, [0, screen.size[1] + gridPad])),
]];
return screen;
};
Canvas.prototype.addDummyScreen = function(pos) {
var screen = new Screen({
id: this.screens.length,
name: "Dummy Screen",
pos: pos,
size: [200, 125],
});
screen.connectClosest(this.getScreens());
this.screens.push(screen);
this.view.appendChild(screen.elem);
return screen;
};
Canvas.prototype.getScreens = function() {
return this.screens.filter((screen) => {
for (var key in this.focuses) {
if (screen == this.focuses[key].target) {
return false;
}
}
return true;
});
};
Canvas.prototype.dragMove = function(e) {
this.setPos(vectorAdd(this.pos, e.delta));
}
function Screen(params) {
this.id = params.id;
this.elem = E('div', {
dataset: { id: params.id },
className: ['screen', params.local ? 'local' : '', 'draggable'],
children: [E('h3', {
className: ['screen-name'],
textContent: params.name,
})]
});
this.setSize(params.size);
this.setPos(params.pos);
this.edges = [[null, null], [null, null]];
}
Screen.prototype.setPos = function(pos) {
this.pos = pos;
this.elem.style.left = this.pos[0] - this.size[0] / 2 + 'px';
this.elem.style.top = this.pos[1] - this.size[1] / 2 + 'px';
};
Screen.prototype.setSize = function(size) {
this.size = size;
this.elem.style.width = this.size[0] + 'px';
this.elem.style.height = this.size[1] + 'px';
};
Screen.prototype.closest = function(screens) {
var pos = this.pos;
return screens.reduce((min, curr) => {
var dist = vectorDistSquare(curr.pos, pos);
if (min == null || dist < min.dist) {
return {screen: curr, dist: dist};
}
return min;
}, null);
};
Screen.prototype.connect = function(other) {
var delta = vectorSub(this.pos, other.pos);
var dim = delta.reduce((max, curr, dim) => {
if (max == null || Math.abs(curr) > Math.abs(delta[max])) {
return dim;
}
return max;
}, null);
var newEdges = [[null, null], [null, null]];
var side = delta[dim] < 0 ? 0 : 1;
newEdges[dim][1 - side] = other;
other.edges.forEach((edgeDim, pathDim) => {
if (pathDim == dim) return;
edgeDim.forEach((screen, pathSide) => {
if (!screen) return;
screen = screen.edges[dim][side];
if (!screen) return;
newEdges[pathDim][pathSide] = screen;
screen = screen.edges[dim][side];
if (!screen) return;
screen = screen.edges[pathDim][1 - pathSide];
if (!screen) return;
newEdges[dim][side] = screen;
});
});
this.edges.forEach((edgeDim, dim) => {
edgeDim.forEach((screen, side) => {
if (screen) {
screen.edges[dim][1 - side] = null;
}
});
});
this.edges = newEdges;
this.edges.forEach((edgeDim, dim) => {
edgeDim.forEach((screen, side) => {
if (screen) {
screen.edges[dim][1 - side] = this;
}
});
});
var pos = other.pos.slice();
var offset = ((this.size[dim] + other.size[dim]) / 2 + gridPad);
pos[dim] += (2 * side - 1) * offset;
this.setPos(pos);
};
Screen.prototype.connectClosest = function(screens) {
var closest = this.closest(screens);
if (closest) this.connect(closest.screen);
};
Screen.prototype.dragMove = function(e) {
this.setPos(vectorAdd(this.pos, e.delta));
};
Screen.prototype.dragEnd = function(e) {
this.connectClosest(e.source.getScreens());
};
var socket = new WebSocket("ws://127.0.0.1:3012");
socket.onopen = function(e) {
new Canvas(document.querySelector('.canvas'), socket);
};
socket.onerror = function(e) {
alert("Failed to connect to server");
};
})(window, document, element.html)