UNPKG

attached-windows

Version:

Using Chrome extension API attach multiple normal and/or popup windows together that behave like a single window.

173 lines (172 loc) 8.51 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; const minimumWidths = { 'normal': 525, 'popup': 200 }; const onBoundsChanged = ({ id: currentId, height, top, state, left, width }) => { if (AttachedWindows.isBusy) return false; const { windows, container } = AttachedWindows; const currentIndex = windows.findIndex(({ id }) => id === currentId); if (currentIndex !== -1) { const { bounds } = windows[currentIndex]; // Update container if (state !== bounds.state) container.state = (state === 'normal' || state === 'minimized') ? state : 'normal'; if (height !== bounds.height) container.height = height; if (top !== bounds.top) container.top = top; if ((left !== bounds.left) && (width === bounds.width || currentIndex === 0)) container.left += left - bounds.left; if (width !== bounds.width) { // Condition: (RESIZED FROM THE LEFT SIDE OF THE FIRST WINDOW) || (RESIZED FROM THE RIGHT SIDE OF THE LAST WINDOW) if ((currentIndex === 0 && left !== bounds.left) || (left === bounds.left && currentIndex === windows.length - 1)) { windows[currentIndex].widthFraction = windows[currentIndex].widthFraction / bounds.width * width; container.width += width - bounds.width; } else { const { widthFraction: widthFractionPrevious } = windows[currentIndex]; windows[currentIndex].widthFraction = widthFractionPrevious / bounds.width * width; if (left === bounds.left) { windows[currentIndex + 1].widthFraction += (widthFractionPrevious - windows[currentIndex].widthFraction); } else { windows[currentIndex - 1].widthFraction += (widthFractionPrevious - windows[currentIndex].widthFraction); } } } // Apply changes AttachedWindows.calculateBounds(); AttachedWindows.syncPositions(); } }; const onRemoved = (closedWindowId) => __awaiter(void 0, void 0, void 0, function* () { if (AttachedWindows.isBusy) return false; const { windows } = AttachedWindows; const currentIndex = windows.findIndex(({ id }) => id === closedWindowId); if (currentIndex !== -1) { // Terminate if primary window removed const window = windows[currentIndex]; if (window.isPrimary) yield AttachedWindows.terminate(); else { // Remove closed window windows.splice(currentIndex, 1); // Apply changes AttachedWindows.calculateBounds(); AttachedWindows.syncPositions(); } // Trigger listener if available if (AttachedWindows.onRemoveAttachedWindow) AttachedWindows.onRemoveAttachedWindow(window); } }); const onBeforeUnload = () => __awaiter(void 0, void 0, void 0, function* () { // Terminate on before unload yield AttachedWindows.terminate(); }); class AttachedWindows { static terminate({ closeWindows = true, closePrimary = false } = {}) { return __awaiter(this, void 0, void 0, function* () { // Remove event listeners if (chrome.windows.onBoundsChanged.hasListener(onBoundsChanged)) chrome.windows.onBoundsChanged.removeListener(onBoundsChanged); if (chrome.windows.onRemoved.hasListener(onRemoved)) chrome.windows.onRemoved.removeListener(onRemoved); removeEventListener('beforeunload', onBeforeUnload, false); // Close windows let availableWindows = []; yield Promise.all(this.windows.map(({ id, isPrimary }) => __awaiter(this, void 0, void 0, function* () { if ((isPrimary && closePrimary) || (!isPrimary && closeWindows)) yield chrome.windows.remove(id).catch(() => { }); else availableWindows.push(Object.assign({ id }, (isPrimary ? { isPrimary } : {}))); }))); // Restore primary window if not closing if (!closePrimary) { const foundPrimaryWindow = this.windows.find(({ isPrimary }) => isPrimary); if (foundPrimaryWindow) { this.windows = [foundPrimaryWindow]; this.calculateBounds(); yield this.syncPositions(true); } } // Empty windows array this.windows = []; this.container = {}; // Return available / not closed windows return availableWindows; }); } static calculateBounds() { // Calculate window bounds const { container: { top, height, state, left: containerLeft, width: containerWidth }, windows } = this; const containerWidthFraction = windows.reduce((total, { widthFraction, isHidden }) => total + (isHidden ? 0 : widthFraction), 0); windows.reduce(([left, availableWidth], window) => { if (window.isHidden) { window.bounds = { state: 'minimized' }; return [left, availableWidth]; } else { let width = Math.round(containerWidth * window.widthFraction / containerWidthFraction); if (width < minimumWidths[window.type]) width = minimumWidths[window.type]; if (width > availableWidth) width = availableWidth; window.bounds = { top, height, state, left, width: Math.round(width) }; return [left + width, availableWidth - width]; } }, [containerLeft, containerWidth]); } static syncPositions(ignoreCheck = false) { return __awaiter(this, void 0, void 0, function* () { // Apply changes to the windows if (this.isBusy) return false; this.isBusy = true; yield Promise.all(this.windows.map(({ id, bounds }) => chrome.windows.update(id, bounds.state === 'minimized' ? { state: 'minimized' } : bounds).catch(() => { }).catch(() => { }))); this.isBusy = false; // Terminate if total number of window is 0 or 1 if (!ignoreCheck) { if (this.windows.length === 0) yield this.terminate(); else if (this.windows.length === 1) yield this.terminate({ closePrimary: false }); } // Return true if succeed return true; }); } static initialize({ container, windows }) { return __awaiter(this, void 0, void 0, function* () { if (!windows.find(({ isPrimary }) => isPrimary)) throw new Error(`No primary window. 'windows' must have one primary window.`); if (this.windows.length) yield this.terminate(); this.container = container; this.windows = windows; this.calculateBounds(); yield this.syncPositions(); // Add event listeners if (!chrome.windows.onBoundsChanged.hasListener(onBoundsChanged)) chrome.windows.onBoundsChanged.addListener(onBoundsChanged); if (!chrome.windows.onRemoved.hasListener(onRemoved)) chrome.windows.onRemoved.addListener(onRemoved); addEventListener('beforeunload', onBeforeUnload, false); }); } } AttachedWindows.isBusy = false; AttachedWindows.container = {}; AttachedWindows.windows = []; export default AttachedWindows;