windowmanager
Version:
A framework to manage multiple dockable, HTML windows
1,014 lines (884 loc) • 34.5 kB
JavaScript
import windowmanager from './global';
import { EventHandler, getUniqueWindowName } from '../../utils/index';
import { BoundingBox, Position, Size, Vector } from '../../geometry/index';
import 'core-js/fn/string/includes';
const defaultConfig = {
width: 800,
height: 500,
frame: true,
resizable: true,
show: true,
icon: location.href + 'favicon.ico',
url: '.',
minWidth: 100,
minHeight: 100,
maxWidth: Infinity,
maxHeight: Infinity
};
const configMap = {
};
const acceptedEventHandlers = [
'ready',
'drag-start', 'drag-before', 'drag-stop',
'dock-before',
'move', 'move-before',
'resize-before',
'close',
'show', 'hide', 'restore', 'minimize', 'maximize',
'focus', 'blur'];
const transformPropNames = ['-ms-transform', '-moz-transform', '-o-transform',
'-webkit-transform', 'transform'];
/**
* @callback Callback
* @param {String|null} error - String on error, or null if no error
* @param {Object|null} result - Object on success, or null if error
*/
/**
* A Window class, used to create windows.
* <h5>Example:</h5>
* ```javascript
* // Create a new window:
* let newWindow = new windowmanager.Window({
* url: "child.html", // Loads "child.html" based on the current window's url.
* width: 500,
* height: 500,
* show: false // Don't automatically show window on creation
* });
*
* newWindow.onReady(() => {
* // Setup window:
* // ...
*
* // Display window:
* this.show();
* this.focus();
* });
* ```
* @extends EventHandler
*/
class Window extends EventHandler {
/**
* Creates a window object.
* <h5>Example:</h5>
* ```javascript
* // Create a new window:
* let newWindow = new windowmanager.Window({
* url: "child.html", // Loads "child.html" based on the current window's url.
* width: 500,
* height: 500
* });
* ```
* @param {Object} [config] - Window Configuration
* @param {Number} [config.width=800]
* @param {Number} [config.height=500]
* @param {Boolean} [config.frame=true] - When true, enables standard OS framing around the window
* @param {Boolean} [config.resizable=true] - When true, allows user to resize window by dragging edges
* @param {Boolean} [config.show=true] - When true, starts the window in the "show" state rather than "hidden"
* @param {String} [config.icon='favicon.ico'] - Location to favicon
* @param {String} [config.url='.'] - Location to page that the window should load
* @param {Boolean} [config.draggable=true] - When true, enables dragging of window.
* @param {String} [config.container] - The id of the div to attach the window to
*/
constructor(config) {
// Call the parent constructor:
super(acceptedEventHandlers);
config = config || {}; // If no arguments are passed, assume we are creating a default blank window
const isArgConfig = !(config instanceof window.Window);
// Setup private variables:
this._ready = false;
// TODO: Identify current states.
this._isClosed = false;
this._isHidden = false;
this._isMinimized = false;
this._isMaximized = false;
this._isResizable = false;
this._dockedGroup = [this];
this._children = []; // TODO: Add way to remove or change heirarchy.
this._parent = undefined;
this._title = undefined;
this._id = getUniqueWindowName();
if (isArgConfig) {
for (const prop in config) {
if (config.hasOwnProperty(prop) && configMap[prop] !== undefined) {
config[configMap[prop]] = config[prop];
delete config[prop];
}
}
for (const prop in defaultConfig) {
if (defaultConfig.hasOwnProperty(prop)) {
config[prop] = (config[prop] != null ? config[prop] : defaultConfig[prop]);
}
}
this._title = config.title == null ? this._id : config.title;
let that = this;
if (config.parent) {
config.parent._children.push(this);
this._parent = config.parent;
// TODO: Emit event 'child-added' on parent
delete config.parent;
}
this._minSize = new Size(config.minWidth, config.minHeight);
this._maxSize = new Size(config.maxWidth, config.maxHeight);
this._isResizable = config.resizable;
let newWindow = windowmanager._launcher.document.createElement('div');
let iframe = windowmanager._launcher.document.createElement('iframe');
// Only give the window an absolute position if it is not being put in a container.
// This makes it possible for a Layout object to control the position.
if (!config.container) {
newWindow.style.position = 'absolute';
}
iframe.style.margin = iframe.style.padding = iframe.style.border = 0;
newWindow.style.resize = 'both';
newWindow.style.overflow = 'visible';
if (!Number.isFinite(config.left)) {
config.left = (windowmanager._launcher.innerWidth - config.width) / 2;
}
newWindow.style.left = config.left + 'px';
if (!Number.isFinite(config.top)) {
config.top = (windowmanager._launcher.innerHeight - config.height) / 2;
}
newWindow.style.top = config.top + 'px';
newWindow.style.width = config.width + 'px';
newWindow.style.height = config.height + 'px';
newWindow.style.minWidth = this._minSize.left + 'px';
newWindow.style.minHeight = this._minSize.top + 'px';
newWindow.style.maxWidth = this._maxSize.left + 'px';
newWindow.style.maxHeight = this._maxSize.top + 'px';
newWindow.addEventListener('focus', () => {
that.emit('focus');
});
newWindow.addEventListener('blur', () => {
that.emit('blur');
});
// Attach our new window to its container if one is specified.
if (config.container) {
let containerElem;
if (typeof config.container === 'string') {
containerElem = document.getElementById(config.container);
} else {
containerElem = config.container;
}
containerElem.appendChild(newWindow);
} else {
windowmanager._launcher.document.body.appendChild(newWindow);
}
// Check whether to show the window or not.
if (!config.show) {
newWindow.style.display = 'none';
}
// Set up iframe for page:
iframe.src = config.url;
iframe.style.margin = iframe.style.padding = iframe.style.border = 0;
iframe.style.width = iframe.style.height = '100%';
newWindow.appendChild(iframe);
// Set up resize:
this._resize = Object.create(null);
if (config.draggable) {
for (const dir of ['w', 'nw', 'n', 'ne', 'e', 'se', 's', 'sw']) {
let edge = windowmanager._launcher.document.createElement('div');
this._resize[dir] = edge;
// Setup styling:
edge.style.display = (this._isResizable ? '' : 'none');
edge.style.position = 'absolute';
edge.style['user-select'] = 'none';
edge.style.cursor = `${dir}-resize`;
edge.style.width = (dir === 'n' || dir === 's' ? 'calc(100% - 6px)' : '6px');
edge.style.height = (dir === 'w' || dir === 'e' ? 'calc(100% - 6px)' : '6px');
edge.style[dir.includes('e') ? 'right' : 'left'] = (dir === 'n' || dir === 's' ? '3px' : '-3px');
edge.style[dir.includes('s') ? 'bottom' : 'top'] = (dir === 'w' || dir === 'e' ? '3px' : '-3px');
// Setup event handler to start dragging edge:
edge.addEventListener('mousedown', (event) => {
if (!that._isResizable) return;
// TODO: The overlay prevents underlying clicks and triggered mousemove events while resizing.
// It also prevents css rendering that changes cursor, is there something better?
windowmanager._overlay.style.display = '';
windowmanager._overlay.style.cursor = `${dir}-resize`;
windowmanager._drag.down = dir;
windowmanager._drag.mouseStart = new Position(event.screenX, event.screenY);
windowmanager._drag.target = that;
windowmanager._drag.targetStartBounds = that.getBounds();
});
// Add to window wrapper:
newWindow.appendChild(edge);
}
}
this._window = iframe;
this._wrapper = newWindow;
windowmanager._windows.set(this._id, this);
this._ready = true;
this.emit('ready');
windowmanager._internalBus.emit('window-create', this);
this.bringToFront();
this.focus();
} else {
this._minSize = new BoundingBox(defaultConfig.minWidth, defaultConfig.minHeight);
this._maxSize = new BoundingBox(defaultConfig.maxWidth, defaultConfig.maxHeight);
this._wrapper = this._window = config.document.body;
windowmanager._windows.set(this._id, this);
this._ready = true;
}
}
/**
* Returns true if the {@link Window} instance is created, not closed, and ready for method calls.
* @returns {Boolean}
*/
isReady() {
return this._ready;
}
/**
* Calls a callback when window is ready and setup.
* <h5>Example:</h5>
* ```javascript
* newWindow.onReady(() => {
* // newWindow methods can be executed in here
* });
* ```
* @param {Callback=}
*/
onReady(callback) {
if (this.isClosed()) { throw new Error('onReady can\'t be called on a closed window'); }
if (this.isReady()) { return callback.call(this); }
this.once('ready', callback);
}
/**
* Returns whether window has been closed already.
* @returns {Boolean}
*/
isClosed() {
return this._isClosed;
}
/**
* Returns window's current position.
* @returns {Vector}
*/
getPosition() {
return new Position(this._wrapper.getBoundingClientRect());
}
getMinWidth() {
return this._minSize.left;
}
/**
* Returns window's width.
* @returns {Number}
*/
getWidth() {
return this._window.getBoundingClientRect().width;
}
getMaxWidth() {
return this._maxSize.left;
}
getMinHeight() {
return this._minSize.top;
}
/**
* Returns window's height.
* @returns {Number}
*/
getHeight() {
return this._wrapper.getBoundingClientRect().height;
}
getMaxHeight() {
return this._maxSize.top;
}
getMinSize() {
return this._minSize.clone();
}
/**
* Returns window's size.
* @returns {Size}
*/
getSize() {
let box = this._wrapper.getBoundingClientRect();
return new Size(box.width, box.height);
}
getMaxSize() {
return this._maxSize.clone();
}
/**
* Returns window's bounding box.
* @returns {BoundingBox}
*/
getBounds() {
return new BoundingBox(this._window.getBoundingClientRect());
}
getParent() {
return this._parent;
}
setParent(parent) {
// TODO: Execute appropriate checks (if not closed, and is this new parent a window)
if (parent === this._parent) { return; }
if (this._parent) {
const index = this._parent._children.indexOf(this);
if (index >= 0) { this._parent._children.splice(index, 1); }
// TODO: Emit event 'child-removed' on current parent.
}
if (parent) {
this._parent = parent;
this._parent._children.push(this);
// TODO: Emit event 'child-added on parent'.
}
}
getChildren() {
return this._children.slice();
}
addChild(child) {
child.setParent(this);
}
/**
* Returns window's title.
* @returns {String}
*/
getTitle() {
return this._title;
}
/**
* Sets window's title.
* @param {String}
*/
setTitle(title) {
if (!title) { throw new Error('setTitle requires one argument of type String'); }
this._title = title;
}
/**
* Returns true if window is hidden.
* @returns {Boolean}
*/
isHidden() {
return this._isHidden;
}
/**
* Returns true if window is not hidden.
* @returns {Boolean}
*/
isShown() {
return !this._isHidden;
}
/**
* Returns true if window is minimized.
* @returns {Boolean}
*/
isMinimized() {
return this._isMinimized;
}
/**
* Returns true if window is maximized.
* @returns {Boolean}
*/
isMaximized() {
return this._isMaximized;
}
/**
* Returns true if window is not hidden or minimize or maximized.
* @returns {Boolean}
*/
isRestored() {
return this.isShown() && !this.isMinimized() && !this.isMaximized();
}
/**
* Returns true if window is not hidden or minimize or maximized.
* @returns {Boolean}
*/
isResizable() {
return this._isResizable;
}
/**
* Closes the window instance.
* @param {Callback=}
*/
close(callback) {
if (this.isClosed()) { return callback && callback(); }
this._wrapper.parentElement.removeChild(this._wrapper);
windowmanager._windows.delete(this._id);
// Undock:
this.undock();
// Move children to parent:
const parent = this.getParent();
for (const child of this.getChildren()) {
// We use getChildren to have a copy of the list, so child.setParent doesn't modify this loop's list!
// TODO: Optimize this loop, by not making a copy of children, and not executing splice in each setParent!
child.setParent(parent);
}
this.setParent(undefined); // Remove from parent
this._isClosed = true;
if (callback) { callback(); }
this.emit('close');
windowmanager._internalBus.emit('window-close', this);
}
/**
* Minimizes the window instance.
* @param {Callback=}
*/
minimize(callback) {
if (!this._ready) { throw new Error('minimize can\'t be called on an unready window'); }
// TODO: What do we do on minimize in this runtime?
for (let window of this._dockedGroup) {
window._isMinimized = true;
window.emit('minimize');
}
}
/**
* Maximizes the window instance.
* @param {Callback=}
*/
maximize(callback) {
if (!this._ready) { throw new Error('maximize can\'t be called on an unready window'); }
this._restoreBounds = this.getBounds();
this._wrapper.style.left = 0;
this._wrapper.style.top = 0;
this._wrapper.style.width = '100%';
this._wrapper.style.height = '100%';
this._isMaximized = true;
window.emit('maximize');
if (callback) { callback(); }
}
/**
* Unhides the window instance.
* @param {Callback=}
*/
show(callback) {
if (!this._ready) { throw new Error('show can\'t be called on an unready window'); }
for (let window of this._dockedGroup) {
window._wrapper.style.display = '';
window._isHidden = false;
window.emit('show');
}
if (callback) { callback(); }
}
/**
* Hides the window instance.
* @param {Callback=}
*/
hide(callback) {
if (!this._ready) { throw new Error('hide can\'t be called on an unready window'); }
for (let window of this._dockedGroup) {
window._wrapper.style.display = 'none';
window._isHidden = true;
window.emit('hide');
}
if (callback) { callback(); }
}
/**
* Restores the window instance from the minimized or maximized states.
* @param {Callback=}
*/
restore(callback) {
if (!this._ready) { throw new Error('restore can\'t be called on an unready window'); }
for (let window of this._dockedGroup) {
if (window._isMaximized) {
window._wrapper.style.left = window._restoreBounds.left + 'px';
window._wrapper.style.top = window._restoreBounds.top + 'px';
window._wrapper.style.width = window._restoreBounds.getWidth() + 'px';
window._wrapper.style.height = window._restoreBounds.getHeight() + 'px';
window._isHidden = false;
window._isMinimized = false;
window._isMaximized = false;
window.emit('restore');
}
}
if (callback) { callback(); }
}
/**
* Sets whether the window instance is resizable.
* @param {Boolean} resizable
* @param {Callback=}
*/
resizable(resizable, callback) {
if (!this._ready) { throw new Error('restore can\'t be called on an unready window'); }
// TODO: What if called while dragging?
if (this._isResizable !== resizable) {
this._isResizable = resizable;
for (const field in this._resize) {
this._resize[field].style.display = (resizable ? '' : 'none');
}
}
if (callback) { callback(); }
}
/**
* Brings the window instance to the front of all windows.
* @param {Callback=}
*/
bringToFront(callback) {
if (!this._ready) { throw new Error('bringToFront can\'t be called on an unready window'); }
for (let window of this._dockedGroup) {
if (window !== this) {
window._wrapper.style['z-index'] = windowmanager._getNextZIndex();
}
}
this._wrapper.style['z-index'] = windowmanager._getNextZIndex();
if (callback) { callback(); }
}
/**
* Sets focus to the window instance.
* @param {Callback=}
*/
focus(callback) {
if (!this._ready) { throw new Error('focus can\'t be called on an unready window'); }
for (let window of this._dockedGroup) {
if (window !== this) { window._window.contentWindow.focus(); }
}
this._window.contentWindow.focus();
this.emit('focus');
if (callback) { callback(); }
}
/**
* Resizes the window instance.
* @param {Number} width
* @param {Number} height
* @param {Callback=}
*/
resizeTo(width, height, callback) {
if (!this._ready) { throw new Error('resizeTo can\'t be called on an unready window'); }
if (!this.emit('resize-before')) { return; } // Allow preventing resize
let size = new Position(width, height);
this.undock();
this._wrapper.style.width = size.left + 'px';
this._wrapper.style.height = size.top + 'px';
if (callback) { callback(); }
}
/**
* Moves the window instance.
* @param {Number} left
* @param {Number} top
* @param {Callback=}
*/
moveTo(left, top, callback) {
if (!this._ready) { throw new Error('moveTo can\'t be called on an unready window'); }
if (!this.emit('move-before')) { return; } // Allow preventing move
let deltaPos = (new Position(left, top)).subtract(this.getPosition());
for (let window of this._dockedGroup) {
let pos = window.getPosition().add(deltaPos);
window._wrapper.style.left = pos.left + 'px';
window._wrapper.style.top = pos.top + 'px';
}
if (callback) { callback(); }
}
/**
* Moves the window instance relative to its current position.
* @param {Number} deltaLeft
* @param {Number} deltaTop
* @param {Callback=}
*/
moveBy(deltaLeft, deltaTop, callback) {
if (!this._ready) { throw new Error('moveBy can\'t be called on an unready window'); }
if (!this.emit('move-before')) { return; } // Allow preventing move
let deltaPos = new Position(deltaLeft, deltaTop);
for (let window of this._dockedGroup) {
let pos = window.getPosition().add(deltaPos);
window._wrapper.style.left = pos.left + 'px';
window._wrapper.style.top = pos.top + 'px';
}
if (callback) { callback(); }
for (let window of this._dockedGroup) {
window.emit('move');
}
}
setMinSize(width, height, callback) {
if (!this._ready) { throw new Error('setMinSize can\'t be called on an unready window'); }
const size = new Size(width, height);
this.undock(); // TODO: Support changing size when docked.
this._minSize.left = size.left;
this._minSize.top = size.top;
this._wrapper.style.minWidth = this._minSize.left + 'px';
this._wrapper.style.minHeight = this._minSize.top + 'px';
if (this.getWidth() < size.left || this.getHeight() < size.top) {
// Resize window to meet new min size:
// TODO: Take into account transform?
this._wrapper.style.width = Math.max(this.getWidth(), size.left) + 'px';
this._wrapper.style.height = Math.max(this.getHeight(), size.top) + 'px';
if (callback) { callback(); }
this.emit('resize');
} else {
if (callback) { callback(); }
}
}
setSize(width, height, callback) {
if (!this._ready) { throw new Error('setMaxSize can\'t be called on an unready window'); }
const size = new Size(width, height);
this.undock(); // TODO: Support changing size when docked.
this._wrapper.style.width = Math.min(this._maxSize.left, Math.max(this._minSize.left, size.left)) + 'px';
this._wrapper.style.height = Math.min(this._maxSize.top, Math.max(this._minSize.top, size.top)) + 'px';
// Clear transform:
for (let transformPropName of transformPropNames) {
this._wrapper.style[transformPropName] = '';
}
if (callback) { callback(); }
this.emit('resize');
}
forceScaledSize(width, height, callback) {
if (!this._ready) { throw new Error('setMaxSize can\'t be called on an unready window'); }
const size = new Size(Math.min(this._maxSize.left, Math.max(this._minSize.left, width)),
Math.min(this._maxSize.top, Math.max(this._minSize.top, height)));
this.undock(); // TODO: Support changing size when docked.
this._wrapper.style.width = size.left + 'px';
this._wrapper.style.height = size.top + 'px';
// TODO: Calc transform:
let transform = Math.min(width / size.left, height / size.top);
for (let transformPropName of transformPropNames) {
this._wrapper.style[transformPropName] = 'scale(' + transform + ')';
}
if (callback) { callback(); }
this.emit('resize');
}
setMaxSize(width, height, callback) {
if (!this._ready) { throw new Error('setMaxSize can\'t be called on an unready window'); }
const size = new Size(width, height);
this.undock(); // TODO: Support changing size when docked.
this._maxSize.left = size.left;
this._maxSize.top = size.top;
this._wrapper.style.maxWidth = this._maxSize.left + 'px';
this._wrapper.style.maxHeight = this._maxSize.top + 'px';
if (this.getWidth() > size.left || this.getHeight() > size.top) {
// Resize window to meet new min size:
// TODO: Take into account transform?
this._wrapper.style.width = Math.min(this.getWidth(), size.left) + 'px';
this._wrapper.style.height = Math.min(this.getHeight(), size.top) + 'px';
// Clear transform:
for (let transformPropName of transformPropNames) {
this._wrapper.style[transformPropName] = '';
}
if (callback) { callback(); }
this.emit('resize');
} else {
if (callback) { callback(); }
}
}
/**
* Sets the bounds of the window instance.
* @param {Number} left
* @param {Number} top
* @param {Number} right
* @param {Number} bottom
* @param {Callback=}
*/
setBounds(left, top, right, bottom, callback) {
if (!this._ready) { throw new Error('resizeTo can\'t be called on an unready window'); }
let bounds = new BoundingBox(left, top, right, bottom);
this.undock(); // TODO: Support changing size when docked.
this._wrapper.style.left = bounds.left + 'px';
this._wrapper.style.top = bounds.top + 'px';
// TODO: Take into account transform?
this._wrapper.style.width = Math.min(this._maxSize.left, Math.max(this._minSize.left, bounds.getWidth())) + 'px';
this._wrapper.style.height = Math.min(this._maxSize.top, Math.max(this._minSize.top, bounds.getHeight())) + 'px';
// Clear transform:
for (let transformPropName of transformPropNames) {
this._wrapper.style[transformPropName] = '';
}
// TODO: Events
if (callback) { callback(); }
}
/**
* Force docking this window to another. They don't need to be touching.
* @param {Window}
* @param {Callback=}
*/
dock(other) {
// TODO: Check if otherGroup is touching
if (!this.emit('dock-before')) { return; } // Allow preventing dock
if (other === undefined) { return; } // Failed to find other. TODO: Return error
// If other is already in the group, return:
if (this._dockedGroup.indexOf(other) >= 0) { return; }
// Loop through all windows in otherGroup and add them to this's group:
for (let otherWin of other._dockedGroup) {
this._dockedGroup.push(otherWin);
// Sharing the array between window objects makes it easier to manage:
otherWin._dockedGroup = otherWin._dockedGroup;
}
}
/**
* Force undocking this window from it's group.<br>
* TODO: Redock those still touching, EXCEPT 'this'.
* @param {Window}
* @param {Callback=}
*/
undock(other) {
// Check to see if window is already undocked:
if (this._dockedGroup.length === 1) { return; }
// Undock this:
this._dockedGroup.splice(this._dockedGroup.indexOf(this), 1);
this._dockedGroup = [this];
// TODO: Redock those still touching, EXCEPT 'this'.
}
_dragStart() {
if (!this.emit('drag-start')) { return; } // Allow preventing drag
this.restore();
for (let window of this._dockedGroup) {
window._dragStartPos = window.getPosition();
}
}
_dragBy(deltaLeft, deltaTop) {
if (!this.emit('drag-before')) { return; } // Allow preventing drag
// Perform Snap:
const thisBounds = this.getBounds().moveTo(this._dragStartPos.left + deltaLeft,
this._dragStartPos.top + deltaTop);
let snapDelta = new Vector(NaN, NaN);
for (const other of windowmanager._windows.values()) {
if (other._dockedGroup !== this._dockedGroup) {
snapDelta.setMin(thisBounds.getSnapDelta(other.getBounds()));
}
}
deltaLeft += snapDelta.left || 0;
deltaTop += snapDelta.top || 0;
for (let other of this._dockedGroup) {
let pos = other._dragStartPos;
// If other doesn't have a drag position, start it:
if (pos === undefined) {
pos = other._dragStartPos = other.getPosition();
pos.left -= deltaLeft;
pos.top -= deltaTop;
}
other._wrapper.style.left = (pos.left + deltaLeft) + 'px';
other._wrapper.style.top = (pos.top + deltaTop) + 'px';
other.emit('move');
}
}
_dragStop() {
// Dock to those it snapped to:
const thisBounds = this.getBounds();
for (const other of windowmanager._windows.values()) {
if (thisBounds.isTouching(other.getBounds())) {
this.dock(other);
}
}
for (let window of this._dockedGroup) {
delete window._dragStartPos;
}
this.emit('drag-stop');
}
/**
* Returns a list of all {@link Window} instances open.
* <h5>Example:</h5>
* ```javascript
* // Close all windows:
* let allWindows = windowmanager.Window.getAll();
* ```
* @returns {Window[]}
*/
static getAll() {
return Array.from(windowmanager._windows.values());
}
/**
* Returns the {@link Window} instance that has `id`.
* <h5>Example:</h5>
* ```javascript
* // Get window with ID:
* let window = windowmanager.Window.getById(windowID);
* ```
* @param {String|Number}
* @returns {Window|undefined}
*/
static getByID(id) {
return windowmanager._windows.get(id);
}
static getByElement(el) {
let doc = el.ownerDocument;
let win = doc.defaultView || doc.parentWindow;
return windowmanager._windows.get(win.windowmanager.Window.current._id);
}
/**
* Returns the {@link Window} instance that is the main window.
* <h5>Example:</h5>
* ```javascript
* // Get main window of the application:
* let mainWindow = windowmanager.Window.getMain();
* ```
* @returns {Window}
*/
static getMain() {
return windowmanager._launcher.windowmanager.Window.getCurrent();
}
/**
* Returns the {@link Window} instance that calls this function.
* <h5>Example:</h5>
* ```javascript
* // Get current window this code is executing in:
* let currentWindow = windowmanager.Window.getCurrent();
* ```
* @returns {Window}
*/
static getCurrent() {
return Window.current;
}
}
// Add launcher to list of windows:
if (windowmanager.runtime.isMain) {
window.document.body.contentWindow = window;
Window.current = new Window(window); // Force add launcher to window list
} else {
// No need to do this for child windows, since _windows is shared across windows.
// Handle current window in this context:
Window.current = (function () {
for (let win of windowmanager._windows.values()) {
if (win._window.contentWindow === window) { return win; }
}
})();
}
if (!windowmanager.runtime.isMain) {
// Setup handlers on this child window:
window.addEventListener('focus', function () {
Window.current.bringToFront();
});
const mousedown = (event) => {
if (event.target.classList && event.target.classList.contains('window-drag')) {
event.preventDefault();
Window.current._dragStart();
// TODO: The overlay prevents underlying clicks and triggered mousemove events while resizing.
// It also prevents css rendering that changes cursor, is there something better?
windowmanager._drag.down = 'm';
windowmanager._drag.mouseStart = new Position(event.screenX, event.screenY);
windowmanager._drag.target = Window.current;
windowmanager._drag.targetStartBounds = Window.current.getBounds();
if (windowmanager.runtime.name !== 'Firefox') {
windowmanager._overlay.style.display = '';
windowmanager._overlay.style.cursor = '';
windowmanager._overlay.focus();
}
}
};
window.addEventListener('mousedown', mousedown, true);
window.addEventListener('touchstart', (event) => {
mousedown({
screenX: event.touches[0].screenX,
screenY: event.touches[0].screenY,
preventDefault: event.preventDefault
});
}, true);
if (windowmanager.runtime.name === 'Firefox') {
const mousemove = (event) => {
if (windowmanager._drag.down === 'm') {
event.preventDefault();
windowmanager._overlay.style.display = '';
const scale = windowmanager._getScale();
windowmanager._overlay.style.display = 'none';
const delta = new Position(event.screenX, event.screenY).subtract(windowmanager._drag.mouseStart);
// Account for scaling:
delta.left /= scale;
delta.top /= scale;
// Stop text selection:
window.getSelection().removeAllRanges();
// Drag:
Window.current._dragBy(delta.left, delta.top);
}
};
window.addEventListener('mousemove', mousemove, true);
window.addEventListener('touchmove', (event) => {
mousemove({
screenX: event.touches[0].screenX,
screenY: event.touches[0].screenY,
preventDefault: event.preventDefault
});
}, true);
const mouseup = (event) => {
if (windowmanager._drag.down === 'm') {
event.preventDefault();
Window.current._dragStop();
// Turn off resizing mode:
windowmanager._drag.down = '';
windowmanager._overlay.style.display = 'none';
}
};
window.addEventListener('mouseup', mouseup, true);
window.addEventListener('touchend', mouseup, true);
}
}
windowmanager.Window = Window;
export default Window;