Skip to content

Commit

Permalink
Add support for linking windows to state changes of other windows. Mi…
Browse files Browse the repository at this point in the history
…nimize and restore all windows from the main window or within window groups. (#138)

electron.js, app.js:
- Change default behavior of example to leverage the new window state tracking of main or group.

desktop.ts:
- Expose through default export the new base GroupWindowManager and the WindowStateTracking enum

window.ts:
- Add GroupWindowManager for tracking window state changes to drive state of other windows.
- Refactor SnapAssistWindowManager core window tracking to new base GroupWindowManager.
  • Loading branch information
bingenito authored Mar 29, 2018
1 parent e6b54cc commit 2cd8b98
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 24 deletions.
5 changes: 4 additions & 1 deletion examples/electron/electron.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ function createWindow() {

desktopJS.ContainerWindow.addListener("window-created", (e) => console.log("Window created - static (ContainerWindow): " + e.windowId + ", " + e.windowName));

snapAssist = new desktopJS.SnapAssistWindowManager(container);
snapAssist = new desktopJS.SnapAssistWindowManager(container,
{
windowStateTracking: desktopJS.WindowStateTracking.Main | desktopJS.WindowStateTracking.Group
});

container.createWindow('http://localhost:8000', { name: "desktopJS", main: true }).then(win => mainWindow = win);

Expand Down
5 changes: 4 additions & 1 deletion examples/web/assets/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,10 @@ document.addEventListener("DOMContentLoaded", function (event) {
$('[data-toggle="popover"]').popover();

if (container.getCurrentWindow().id === "desktopJS") {
snapAssist = new desktopJS.SnapAssistWindowManager(container);
snapAssist = new desktopJS.SnapAssistWindowManager(container,
{
windowStateTracking: desktopJS.WindowStateTracking.Main | desktopJS.WindowStateTracking.Group
});
}
});

Expand Down
4 changes: 3 additions & 1 deletion src/desktop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { resolveContainer, registerContainer, ContainerRegistration } from "./re
import { Container } from "./container";
import { ContainerNotification } from "./notification";
import { ObjectTransform } from "./propertymapping";
import { ContainerWindow, SnapAssistWindowManager } from "./window";
import { ContainerWindow, WindowStateTracking, GroupWindowManager, SnapAssistWindowManager } from "./window";
import * as Default from "./Default/default";
import * as Electron from "./Electron/electron";
import * as OpenFin from "./OpenFin/openfin";
Expand All @@ -18,5 +18,7 @@ export default class Desktop { //tslint:disable-line
static get Default(): typeof Default { return Default; }
static get Electron(): typeof Electron { return Electron; }
static get OpenFin(): typeof OpenFin { return OpenFin; }
static get WindowStateTracking(): typeof WindowStateTracking { return WindowStateTracking; }
static get GroupWindowManager(): typeof GroupWindowManager { return GroupWindowManager; }
static get SnapAssistWindowManager(): typeof SnapAssistWindowManager { return SnapAssistWindowManager; }
}
98 changes: 79 additions & 19 deletions src/window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,8 +259,82 @@ export class PersistedWindowLayout {

type WindowGroupStatus = {window: ContainerWindow, isGrouped: boolean};

export class SnapAssistWindowManager {
private readonly container: Container;
/** Specifies the tracking behavior for window state linking */
export enum WindowStateTracking {
/** No window state tracking */
None = 0,

/** All windows will follow the window state of the main window */
Main = 1 << 0,

/** All grouped windows will follow the window state of each other */
Group = 1 << 1
}

export class GroupWindowManager {
protected readonly container: Container;
public windowStateTracking: WindowStateTracking = WindowStateTracking.None;

public constructor(container: Container, options? : any) {
this.container = container;

if (options) {
if ("windowStateTracking" in options) {
this.windowStateTracking = options.windowStateTracking;
}
}

this.attach();
}

public attach(win?: ContainerWindow) {
if (win) {
win.addListener((typeof fin !== "undefined") ? <WindowEventType> "minimized" : "minimize", (e) => {
if ((this.windowStateTracking & WindowStateTracking.Main) && this.container.getMainWindow().id === e.sender.id) {
this.container.getAllWindows().then(windows => {
windows.forEach(window => window.innerWindow.minimize());
});
}

if (this.windowStateTracking & WindowStateTracking.Group) {
e.sender.getGroup().then(windows => {
windows.forEach(window => window.innerWindow.minimize());
});
}
});

win.addListener((typeof fin !== "undefined") ? <WindowEventType> "restored" : "restore", (e) => {
if ((this.windowStateTracking & WindowStateTracking.Main) && this.container.getMainWindow().id === e.sender.id) {
this.container.getAllWindows().then(windows => {
windows.forEach(window => window.innerWindow.restore());
});
}

if (this.windowStateTracking & WindowStateTracking.Group) {
e.sender.getGroup().then(windows => {
windows.forEach(window => window.innerWindow.restore());
});
}
});
} else {
// Attach handlers to any new windows that open
ContainerWindow.addListener("window-created", (args) => {
this.container.getWindowById(args.windowId).then(window => {
this.attach(window);
});
});

// Attach handlers to any windows already open
if (this.container) {
this.container.getAllWindows().then(windows => {
windows.forEach(window => this.attach(window));
});
}
}
}
}

export class SnapAssistWindowManager extends GroupWindowManager {
private readonly floater: ContainerWindow;
public autoGrouping: boolean = true;
public snapThreshold: number = 20;
Expand All @@ -269,7 +343,7 @@ export class SnapAssistWindowManager {
protected readonly targetGroup: Map<string, ContainerWindow> = new Map();

public constructor(container: Container, options?: any) {
this.container = container;
super(container, options);

if (options) {
if ("snapThreshold" in options) {
Expand All @@ -284,8 +358,6 @@ export class SnapAssistWindowManager {
this.autoGrouping = options.autoGrouping;
}
}

this.attach();
}

/**
Expand All @@ -310,6 +382,8 @@ export class SnapAssistWindowManager {
}

public attach(win?: ContainerWindow) {
super.attach(win);

if (win) {
this.onAttached(win);

Expand Down Expand Up @@ -367,20 +441,6 @@ export class SnapAssistWindowManager {
});
});
});
} else {
// Attach handlers to any new windows that open
ContainerWindow.addListener("window-created", (args) => {
this.container.getWindowById(args.windowId).then(window => {
this.attach(window);
});
});

// Attach handlers to any windows already open
if (this.container) {
this.container.getAllWindows().then(windows => {
windows.forEach(window => this.attach(window));
});
}
}
}

Expand Down
74 changes: 72 additions & 2 deletions tests/unit/window.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { Container } from "../../src/container";
import { ContainerWindow, WindowEventType, WindowEventArgs, SnapAssistWindowManager, Rectangle } from "../../src/window";
import { ContainerWindow, WindowEventType, WindowEventArgs, WindowStateTracking, GroupWindowManager, SnapAssistWindowManager, Rectangle } from "../../src/window";
import { EventArgs, EventEmitter } from "../../src/events";
import { TestContainer, MockMessageBus } from "./container.spec";

class MockWindow extends ContainerWindow {
protected attachListener(eventName: WindowEventType, listener: (event: EventArgs) => void): void {
return;
}
}

describe ("static events", () => {
Expand Down Expand Up @@ -90,6 +93,73 @@ describe("Rectangle", () => {
});
});


describe("GroupWindowManager", () => {
it ("default options", () => {
const mgr = new GroupWindowManager(null);
expect(mgr.windowStateTracking).toEqual(0);
});

it ("overrides read from options", () => {
const mgr = new GroupWindowManager(null, { windowStateTracking: WindowStateTracking.Main });
expect(mgr.windowStateTracking).toEqual(WindowStateTracking.Main);
});

it ("Minimize with Main", () => {
const innerWin = jasmine.createSpyObj("innerwindow", ["minimize", "addListener"]);
const container = jasmine.createSpyObj("container", ["getAllWindows", "getMainWindow"]);
const win = new MockWindow(innerWin);
spyOn(win, "getGroup").and.returnValue(Promise.resolve([win]));
container.getAllWindows.and.returnValue(Promise.resolve([win]));
container.getMainWindow.and.returnValue(win);
const mgr = new GroupWindowManager(container, { windowStateTracking: WindowStateTracking.Main });
mgr.attach(win);
win.emit("minimize", {name: "minimize", sender: win });
expect(container.getAllWindows).toHaveBeenCalledTimes(2); // Once for initial GroupWindowManager ctor
expect(win.getGroup).toHaveBeenCalledTimes(0);
});

it ("Minimize with Group", () => {
const innerWin = jasmine.createSpyObj("innerwindow", ["minimize", "addListener"]);
const container = jasmine.createSpyObj("container", ["getAllWindows", "getMainWindow"]);
const win = new MockWindow(innerWin);
spyOn(win, "getGroup").and.returnValue(Promise.resolve([win]));
container.getAllWindows.and.returnValue(Promise.resolve([win]));
const mgr = new GroupWindowManager(container, { windowStateTracking: WindowStateTracking.Group });
mgr.attach(win);
win.emit("minimize", {name: "minimize", sender: win });
expect(container.getAllWindows).toHaveBeenCalledTimes(1); // Once for initial GroupWindowManager ctor
expect(win.getGroup).toHaveBeenCalled();
});

it ("Restore with Main", () => {
const innerWin = jasmine.createSpyObj("innerwindow", ["restore", "addListener"]);
const container = jasmine.createSpyObj("container", ["getAllWindows", "getMainWindow"]);
const win = new MockWindow(innerWin);
spyOn(win, "getGroup").and.returnValue(Promise.resolve([win]));
container.getAllWindows.and.returnValue(Promise.resolve([win]));
container.getMainWindow.and.returnValue(win);
const mgr = new GroupWindowManager(container, { windowStateTracking: WindowStateTracking.Main });
mgr.attach(win);
win.emit("restore", {name: "restore", sender: win });
expect(container.getAllWindows).toHaveBeenCalledTimes(2); // Once for initial GroupWindowManager ctor
expect(win.getGroup).toHaveBeenCalledTimes(0);
});

it ("Restore with Group", () => {
const innerWin = jasmine.createSpyObj("innerwindow", ["restore", "addListener"]);
const container = jasmine.createSpyObj("container", ["getAllWindows", "getMainWindow"]);
const win = new MockWindow(innerWin);
spyOn(win, "getGroup").and.returnValue(Promise.resolve([win]));
container.getAllWindows.and.returnValue(Promise.resolve([win]));
const mgr = new GroupWindowManager(container, { windowStateTracking: WindowStateTracking.Group });
mgr.attach(win);
win.emit("restore", {name: "restore", sender: win });
expect(container.getAllWindows).toHaveBeenCalledTimes(1); // Once for initial GroupWindowManager ctor
expect(win.getGroup).toHaveBeenCalled();
});
});

describe("SnapAssistWindowManager", () => {
it ("default options", () => {
const mgr = new SnapAssistWindowManager(null);
Expand Down Expand Up @@ -309,4 +379,4 @@ describe("SnapAssistWindowManager", () => {
});
});
});
});
});

0 comments on commit 2cd8b98

Please sign in to comment.