atheos-ide
Version:
Web-based IDE framework
286 lines (234 loc) • 9.03 kB
JavaScript
//////////////////////////////////////////////////////////////////////////////80
// SplitView Module
//////////////////////////////////////////////////////////////////////////////80
// Copyright (c) Atheos & Liam Siira (Atheos.io), distributed as-is and without
// warranty under the MIT License. See [root]/LICENSE.md for more.
// This information must remain intact.
//////////////////////////////////////////////////////////////////////////////80
// Authors: Codiad Team, @Fluidbyte, Atheos Team, @hlsiira
//////////////////////////////////////////////////////////////////////////////80
(function(global) {
var atheos = global.atheos,
carbon = global.carbon;
carbon.subscribe('system.loadExtra', () => atheos.splitview.init());
atheos.splitview = {
splitMenuOpen: false,
init: function() {
atheos.common.initMenuHandler(oX('#split'), oX('#split-options-menu'));
oX('#split-vertically').on('click', function(e) {
atheos.editor.addInstance(atheos.editor.activeInstance.getSession(), 'bottom');
});
oX('#split-horizontally').on('click', function(e) {
atheos.editor.addInstance(atheos.editor.activeInstance.getSession(), 'right');
});
oX('#merge-all').on('click', function(e) {
var activeSession = atheos.editor.activeInstance.getSession();
atheos.editor.exterminate();
atheos.editor.addInstance(activeSession);
});
}
};
})(this);
var separatorWidth = 5;
//////////////////////////////////////////////////////////////////////////////80
// SplitView Class
//////////////////////////////////////////////////////////////////////////////80
// Notes:
// Getting rid of self and moving all the functions inside of Split Container
// might clean up the code a little more.
//
// I've thought about pushing the mini CSS library into a global scope to make
// the code a little cleaner as well, but am worried that someone might try to
// use it instead of Onyx, which while it's not a problem to do so, it goes
// against what I consider best practices. I might add it to Onyx in a sort of
// high performance little brother type deal for Onyx later.
//
// The cMajor, cMinor, and Borders are are held miniCSS objects, while the
// Parent is still a raw dom element. It added a bit of a headache to try to
// turn it into a handler so I left it as it is.
//
// The performance side of the SplitContainer I consider to be pretty much done
// with little left to improve as far as I can tell, the readability leaves a
// lot to desire. I tried to do a lot of CodeGolf with this one in order to
// challenge myself a little bit and will probably use this module as a sandbox
// for non-best practices / CodeGolf.
//
// There is room for improvement I'm pretty sure in using Right/Bottom as well
// as Left/Top for each child in that you'd no longer have to set those values
// after you initially did so, only having to change all the widths. Or even
// switching the magical FlexBox container, but I'm not super familiar with
// FlexBox, and rewriting this to use Right/Bottom seems like a very low
// priority to me right now. I'm happy where SplitContainer is currently.
// - Liam Siira
//////////////////////////////////////////////////////////////////////////////80
function SplitContainer(parent, children, splitType) {
var self = this,
Q = self.helper;
self.parent = parent;
self.splitType = splitType;
self.splitProp = 0.5;
self.border = Q(document.createElement('div'));
if (splitType === 'horizontal') {
self.border.dom.classList.add('splitter', 'h-splitter');
self.border.width(separatorWidth);
self.border.height(Q(self.parent).height());
} else if (splitType === 'vertical') {
self.border.dom.classList.add('splitter', 'v-splitter');
self.border.height(separatorWidth);
self.border.width(Q(self.parent).width());
}
self.parent.append(self.border.dom);
self.setChild(0, children[0]);
self.setChild(1, children[1]);
// You might be tempted in removing the arrow function here, but it would
// break scope on this/self down in the Drag function.
self.border.on('mousedown', (e) => self.drag(e));
Q(self.parent).on('h-resize', (e) => self.resize(e, 'width'));
Q(self.parent).on('v-resize', (e) => self.resize(e, 'height'));
Q(self.parent).trigger('h-resize');
Q(self.parent).trigger('v-resize');
}
SplitContainer.prototype = {
setChild: function(splitID, element) {
var self = this,
Q = self.helper;
if (element instanceof SplitContainer) {
element.splitID = splitID;
element = element.parent;
}
if (splitID === 0) {
self.cMajor = Q(element);
} else {
self.cMinor = Q(element);
}
self.parent.append(element);
Q(element).top(0);
Q(element).left(0);
Q(self.parent).trigger('h-resize');
Q(self.parent).trigger('v-resize');
},
resize: function(event, type) {
var self = this;
var s0, s1, s2;
s0 = self.helper(self.parent)[type]();
s1 = s0 * self.splitProp - separatorWidth / 2;
s2 = s0 * (1 - self.splitProp) - separatorWidth / 2;
if (self.splitType === 'horizontal' && type === 'width') {
self.cMajor.width(s1);
self.cMinor.width(s2);
self.cMinor.left(s1 + separatorWidth);
self.border.left(s1);
} else if (self.splitType === 'vertical' && type === 'height') {
self.cMajor.height(s1);
self.cMinor.height(s2);
self.cMinor.top(s1 + separatorWidth);
self.border.top(s1);
} else {
self.cMajor[type](s0);
self.cMinor[type](s0);
self.border[type](s0);
}
self.cMajor.trigger(event.type);
self.cMinor.trigger(event.type);
},
drag: function(event) {
//References: http://jsfiddle.net/8wtq17L8/ & https://jsfiddle.net/tovic/Xcb8d/
var self = this,
Q = self.helper;
var border = event.target,
mouseX = window.event.clientX,
mouseY = window.event.clientY,
splitX = border.offsetLeft,
splitY = border.offsetTop;
var rHeight = Q(self.parent).height();
var rWidth = Q(self.parent).width();
var timeout = false;
// Set the throttle as a named variable in order to add AND remove
// the event listener later on. Without it being a named item, it can't
// be removed.
var moveElement = throttle(function(event) {
var left = splitX + event.clientX - mouseX,
top = splitY + event.clientY - mouseY;
border.style.left = self.splitType === 'horizontal' ? left + 'px' : '';
border.style.top = self.splitType === 'vertical' ? top + 'px' : '';
if (self.splitType === 'horizontal') {
var percent = left / rWidth;
percent = percent < 0.1 ? 0.1 : percent;
percent = percent > 0.9 ? 0.9 : percent;
self.splitProp = percent;
Q(self.parent).trigger('h-resize');
} else {
self.splitProp = top / rHeight;
Q(self.parent).trigger('v-resize');
}
}, 10);
function disableSelect(event) {
event.preventDefault();
}
// Destroy the object when we are done
function removeListeners() {
document.removeEventListener('mousemove', moveElement, false);
document.removeEventListener('mouseup', removeListeners, false);
window.removeEventListener('selectstart', disableSelect);
}
document.addEventListener('mousemove', moveElement, false);
document.addEventListener('mouseup', removeListeners, false);
window.addEventListener('selectstart', disableSelect);
},
helper: function(element) {
let raw = {
'display': element.style.display,
'visibility': element.style.visibility,
'opacity': element.style.opacity
};
let setCSS = (o) => {
const entries = Object.entries(o);
for (const [key, value] of entries) {
element.style[key] = value;
}
};
let setSize = (t, v) => {
if (v) {
element.style[t] = v + 'px';
}
setCSS({
'display': 'block',
'visibility': 'hidden',
'opacity': 0
});
var computedStyle = window.getComputedStyle(element);
var size = parseFloat(computedStyle[t].replace('px', ''));
setCSS(raw);
return size;
};
let triggerEvent = (type) => {
return element.dispatchEvent(new CustomEvent(type, {
bubbles: false,
cancelable: true
}));
};
let updateListener = (type, fnc, change) => {
type = type.split(',');
type.forEach((t) => {
t = t.trim();
if (change === 'add') {
element.addEventListener(t, fnc);
} else {
element.removeEventListener(t, fnc);
}
});
};
return {
top: (v) => element.style.top = v + 'px',
left: (v) => element.style.left = v + 'px',
right: (v) => element.style.right = v + 'px',
bottom: (v) => element.style.bottom = v + 'px',
width: (v) => setSize('width', v),
height: (v) => setSize('height', v),
on: (t, f) => updateListener(t, f, 'add'),
off: (t, f) => updateListener(t, f, 'remove'),
trigger: (t) => triggerEvent(t),
dom: element
};
}
};