Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for disabling snap dock for a window via snap option parameter #180

Merged
merged 1 commit into from
Jul 26, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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