Skip to content

Commit

Permalink
Add support for disabling snap dock for a window via creation (#180)
Browse files Browse the repository at this point in the history
  • Loading branch information
bingenito authored Jul 26, 2018
1 parent f3ff566 commit d08c95d
Show file tree
Hide file tree
Showing 8 changed files with 163 additions and 77 deletions.
6 changes: 6 additions & 0 deletions src/Default/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ export class DefaultContainerWindow extends ContainerWindow {
});
}

public getOptions(): Promise<any> {
return new Promise<any>((resolve, reject) => {
resolve(this.innerWindow[Container.windowOptionsPropertyKey]);
});
}

protected attachListener(eventName: string, listener: (...args: any[]) => void): void {
this.innerWindow.addEventListener(windowEventMap[eventName] || eventName, listener);
}
Expand Down
16 changes: 16 additions & 0 deletions src/Electron/electron.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class InternalMessageType {
public static readonly getGroup: string = "desktopJS.window-getGroup";
public static readonly joinGroup: string = "desktopJS.window-joinGroup";
public static readonly leaveGroup: string = "desktopJS.window-leaveGroup";
public static readonly getOptions: string = "desktopJS.window-getOptions";
}

const windowEventMap = {};
Expand Down Expand Up @@ -164,6 +165,16 @@ export class ElectronContainerWindow extends ContainerWindow {
return Promise.resolve();
}

public getOptions(): Promise<any> {
return new Promise<any>((resolve, reject) => {
const options = (this.isRemote)
? (<any>this.container.internalIpc).sendSync(InternalMessageType.getOptions, { source: this.id })
: this.innerWindow[Container.windowOptionsPropertyKey];

resolve(options);
});
}

protected attachListener(eventName: string, listener: (...args: any[]) => void): void {
this.innerWindow.addListener(windowEventMap[eventName] || eventName, listener);
}
Expand Down Expand Up @@ -506,6 +517,11 @@ export class ElectronWindowManager {
const { "source": sourceId } = message;
event.returnValue = this.getGroup(this.browserWindow.fromId(sourceId));
});

this.ipc.on(InternalMessageType.getOptions, (event: any, message: any) => {
const { "source": sourceId } = message;
event.returnValue = this.browserWindow.fromId(sourceId)[Container.windowOptionsPropertyKey];
});
}

public initializeWindow(win: any, name: string, options: any) {
Expand Down
7 changes: 7 additions & 0 deletions src/OpenFin/openfin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,12 @@ export class OpenFinContainerWindow extends ContainerWindow {
});
}

public getOptions(): Promise<any> {
return new Promise<any>((resolve, reject) => {
this.innerWindow.getOptions(options => resolve(options.customData ? JSON.parse(options.customData) : undefined), reject);
});
}

protected attachListener(eventName: string, listener: (...args: any[]) => void): void {
this.innerWindow.addEventListener(windowEventMap[eventName] || eventName, listener);
}
Expand Down Expand Up @@ -411,6 +417,7 @@ export class OpenFinContainer extends WebContainerBase {

protected getWindowOptions(options?: any): any {
const newOptions = ObjectTransform.transformProperties(options, this.windowOptionsMap);
newOptions.customData = options ? JSON.stringify(options) : undefined;

// Default behavior is to show window so if there is no override in options, show the window
if (!("autoShow" in newOptions)) {
Expand Down
127 changes: 72 additions & 55 deletions src/window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export type WindowEventType =
"maximize" |
"minimize" |
"restore"
;
;

export class WindowEventArgs extends EventArgs {
public readonly window?: ContainerWindow;
Expand All @@ -83,7 +83,7 @@ export abstract class ContainerWindow extends EventEmitter {
public constructor(wrap: any) {
super();
this.innerWindow = wrap;
}
}

/** Gives focus to the window. */
public abstract focus(): Promise<void>;
Expand Down Expand Up @@ -138,6 +138,8 @@ export abstract class ContainerWindow extends EventEmitter {
return Promise.resolve();
}

public abstract getOptions(): Promise<any>;

/**
* Override to provide custom container logic for adding an event handler.
*/
Expand Down Expand Up @@ -267,7 +269,7 @@ export class PersistedWindowLayout {
}
}

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

/** Specifies the tracking behavior for window state linking */
export enum WindowStateTracking {
Expand All @@ -285,7 +287,7 @@ export class GroupWindowManager {
protected readonly container: Container;
public windowStateTracking: WindowStateTracking = WindowStateTracking.None;

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

if (options) {
Expand All @@ -299,7 +301,7 @@ export class GroupWindowManager {

public attach(win?: ContainerWindow) {
if (win) {
win.addListener((typeof fin !== "undefined") ? <WindowEventType> "minimized" : "minimize", (e) => {
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.minimize());
Expand All @@ -313,7 +315,7 @@ export class GroupWindowManager {
}
});

win.addListener((typeof fin !== "undefined") ? <WindowEventType> "restored" : "restore", (e) => {
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.restore());
Expand All @@ -339,8 +341,8 @@ export class GroupWindowManager {
// Attach handlers to any windows already open
if (this.container) {
this.container.getAllWindows().then(windows => {
windows.forEach(window => this.attach(window));
});
windows.forEach(window => this.attach(window));
});
}
}
}
Expand Down Expand Up @@ -384,7 +386,7 @@ export class SnapAssistWindowManager extends GroupWindowManager {
// Attach listeners for handling when the move/resize of a window is done
if (typeof fin !== "undefined") {
// OpenFin moved handler
win.addListener(<WindowEventType> "disabled-frame-bounds-changed", () => this.onMoved(win));
win.addListener(<WindowEventType>"disabled-frame-bounds-changed", () => this.onMoved(win));
} else {
// Electron windows specific moved handler
if (win.innerWindow && win.innerWindow.hookWindowMessage) {
Expand All @@ -397,63 +399,78 @@ export class SnapAssistWindowManager extends GroupWindowManager {
super.attach(win);

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

win.addListener((typeof fin !== "undefined") ? <WindowEventType> "disabled-frame-bounds-changing" : "move", (e) => {
const id = e.sender.id;

if (this.snappingWindow === id) {
win.getOptions().then(options => {
if (options && typeof (options.snap) !== "undefined" && options.snap === false) {
return;
}

e.sender.getGroup().then(groupedWindows => {
const getBounds: Promise<Rectangle> = (typeof fin !== "undefined")
? Promise.resolve(new Rectangle(e.innerEvent.left, e.innerEvent.top, e.innerEvent.width, e.innerEvent.height))
: e.sender.getBounds();
this.onAttached(win);
win.addListener((typeof fin !== "undefined") ? <WindowEventType>"disabled-frame-bounds-changing" : "move", (e) => this.onMoving(e));
});
}
}

getBounds.then(bounds => {
// If we are already in a group, don't snap or group with other windows, ungrouped windows need to group to us
if (groupedWindows.length > 0) {
if (typeof fin !== "undefined") {
this.moveWindow(e.sender, bounds);
}
protected onMoving(e: any) {
const id = e.sender.id;

if (this.snappingWindow === id) {
return;
}

return;
e.sender.getOptions().then(senderOptions => {
if (senderOptions && typeof (senderOptions.snap) !== "undefined" && senderOptions.snap === false) {
return;
}

e.sender.getGroup().then(groupedWindows => {
const getBounds: Promise<Rectangle> = (typeof fin !== "undefined")
? Promise.resolve(new Rectangle(e.innerEvent.left, e.innerEvent.top, e.innerEvent.width, e.innerEvent.height))
: e.sender.getBounds();

getBounds.then(bounds => {
// If we are already in a group, don't snap or group with other windows, ungrouped windows need to group to us
if (groupedWindows.length > 0) {
if (typeof fin !== "undefined") {
this.moveWindow(e.sender, bounds);
}

const promises: Promise<{window: ContainerWindow, bounds: Rectangle}>[] = [];
this.container.getAllWindows().then(windows => {
windows.filter(window => id !== window.id).forEach(window => {
promises.push(new Promise(resolve => {
window.getBounds().then(targetBounds => resolve({ window: window, bounds: targetBounds}));
}));
});

Promise.all(promises).then(responses => {
let isSnapped = false;
let snapHint;

for (const target of responses) {
snapHint = this.getSnapBounds(snapHint || bounds, target.bounds);
if (snapHint) {
isSnapped = true;
this.showGroupingHint(target.window);
this.moveWindow(e.sender, snapHint);
} else {
this.hideGroupingHint(target.window);
}
}
return;
}

// If the window wasn't moved as part of snapping, we need to manually move for OpenFin since dragging was disabled
if (!isSnapped && typeof fin !== "undefined") {
this.moveWindow(e.sender, bounds);
const promises: Promise<{ window: ContainerWindow, bounds: Rectangle, options: any }>[] = [];
this.container.getAllWindows().then(windows => {
windows.filter(window => id !== window.id).forEach(window => {
promises.push(new Promise(resolve => {
window.getOptions().then(targetOptions => {
window.getBounds().then(targetBounds => resolve({ window: window, bounds: targetBounds, options: targetOptions }));
});
}));
});

Promise.all(promises).then(responses => {
let isSnapped = false;
let snapHint;

for (const target of responses.filter(response => !(response.options && typeof (response.options.snap) !== "undefined" && response.options.snap === false))) {
snapHint = this.getSnapBounds(snapHint || bounds, target.bounds);
if (snapHint) {
isSnapped = true;
this.showGroupingHint(target.window);
this.moveWindow(e.sender, snapHint);
} else {
this.hideGroupingHint(target.window);
}
});
}

// If the window wasn't moved as part of snapping, we need to manually move for OpenFin since dragging was disabled
if (!isSnapped && typeof fin !== "undefined") {
this.moveWindow(e.sender, bounds);
}
});
});
});
});
}
});
}

protected moveWindow(win: ContainerWindow, bounds: Rectangle) {
Expand Down Expand Up @@ -491,15 +508,15 @@ export class SnapAssistWindowManager extends GroupWindowManager {

protected showGroupingHint(win: ContainerWindow) {
if (win.innerWindow && win.innerWindow.updateOptions) {
win.innerWindow.updateOptions({opacity: 0.75});
win.innerWindow.updateOptions({ opacity: 0.75 });
}

this.targetGroup.set(win.id, win);
}

protected hideGroupingHint(win: ContainerWindow) {
if (win.innerWindow && win.innerWindow.updateOptions) {
win.innerWindow.updateOptions({opacity: 1.0});
win.innerWindow.updateOptions({ opacity: 1.0 });
}

this.targetGroup.delete(win.id);
Expand Down
8 changes: 8 additions & 0 deletions tests/unit/Default/default.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,14 @@ describe("DefaultContainerWindow", () => {
});
});

it("getOptions", async (done) => {
const win = await new DefaultContainer(<any>new MockWindow()).createWindow("url", { a: "foo" });
win.getOptions().then(options => {
expect(options).toBeDefined();
expect(options).toEqual({ a: "foo"});
}).then(done);
});

describe("addListener", () => {
it("addListener calls underlying window addEventListener with mapped event name", () => {
spyOn(win.innerWindow, "addEventListener").and.callThrough()
Expand Down
15 changes: 14 additions & 1 deletion tests/unit/Electron/electron.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,18 @@ describe("ElectronContainerWindow", () => {
});
});

it ("getOptions sends synchronous ipc message", (done) => {
spyOn(container.internalIpc, "sendSync").and.returnValue({ foo: "bar"});
spyOnProperty(win, "id", "get").and.returnValue(5);
spyOn(container, "wrapWindow").and.returnValue(new MockWindow());
spyOn(container.browserWindow, "fromId").and.returnValue(innerWin);

win.getOptions().then(options => {
expect(container.internalIpc.sendSync).toHaveBeenCalledWith("desktopJS.window-getOptions", { source: 5});
expect(options).toEqual({ foo: "bar" });
}).then(done);
});

it("addListener calls underlying Electron window addListener", () => {
spyOn(win.innerWindow, "addListener").and.callThrough()
win.addListener("move", () => {});
Expand Down Expand Up @@ -633,11 +645,12 @@ describe("ElectronWindowManager", () => {
const ipc = new MockMainIpc();
spyOn(ipc, "on").and.callThrough();
new ElectronWindowManager({}, ipc, { });
expect(ipc.on).toHaveBeenCalledTimes(4);
expect(ipc.on).toHaveBeenCalledTimes(5);
expect(ipc.on).toHaveBeenCalledWith("desktopJS.window-initialize", jasmine.any(Function));
expect(ipc.on).toHaveBeenCalledWith("desktopJS.window-joinGroup", jasmine.any(Function));
expect(ipc.on).toHaveBeenCalledWith("desktopJS.window-leaveGroup", jasmine.any(Function));
expect(ipc.on).toHaveBeenCalledWith("desktopJS.window-getGroup", jasmine.any(Function));
expect(ipc.on).toHaveBeenCalledWith("desktopJS.window-getOptions", jasmine.any(Function));
});

it ("initializeWindow on non-main does not attach to close", () => {
Expand Down
Loading

0 comments on commit d08c95d

Please sign in to comment.