[MM-46993] Implement CallsWidgetWindow (#2265)
* Initial implementation of CallsWidgetWindow * Refactor + implement widget resizing logic * Add tests * Enable screen sharing * Channel link * Add more tests * Move constants to common file * Extract boundsDiff into util * Set background color on initialization * Fix channel link * Support installations under a subpath * Fix path, caching issues and pass title * [MM-48142] Fix remaining call state issues in main window (#2349) * Update widget URL to new format * Slightly bump widget dimensions to account for border * Fix call state on parent window
This commit is contained in:
parent
c319038704
commit
47edeea601
|
@ -122,9 +122,6 @@ export const UPDATE_PATHS = 'update-paths';
|
||||||
|
|
||||||
export const UPDATE_URL_VIEW_WIDTH = 'update-url-view-width';
|
export const UPDATE_URL_VIEW_WIDTH = 'update-url-view-width';
|
||||||
|
|
||||||
export const DISPATCH_GET_DESKTOP_SOURCES = 'dispatch-get-desktop-sources';
|
|
||||||
export const DESKTOP_SOURCES_RESULT = 'desktop-sources-result';
|
|
||||||
|
|
||||||
export const RELOAD_CURRENT_VIEW = 'reload-current-view';
|
export const RELOAD_CURRENT_VIEW = 'reload-current-view';
|
||||||
|
|
||||||
export const PING_DOMAIN = 'ping-domain';
|
export const PING_DOMAIN = 'ping-domain';
|
||||||
|
@ -136,6 +133,17 @@ export const GET_AVAILABLE_LANGUAGES = 'get-available-languages';
|
||||||
|
|
||||||
export const VIEW_FINISHED_RESIZING = 'view-finished-resizing';
|
export const VIEW_FINISHED_RESIZING = 'view-finished-resizing';
|
||||||
|
|
||||||
|
// Calls
|
||||||
|
export const DISPATCH_GET_DESKTOP_SOURCES = 'dispatch-get-desktop-sources';
|
||||||
|
export const DESKTOP_SOURCES_RESULT = 'desktop-sources-result';
|
||||||
|
export const DESKTOP_SOURCES_MODAL_REQUEST = 'desktop-sources-modal-request';
|
||||||
|
export const CALLS_JOIN_CALL = 'calls-join-call';
|
||||||
|
export const CALLS_LEAVE_CALL = 'calls-leave-call';
|
||||||
|
export const CALLS_WIDGET_RESIZE = 'calls-widget-resize';
|
||||||
|
export const CALLS_WIDGET_SHARE_SCREEN = 'calls-widget-share-screen';
|
||||||
|
export const CALLS_WIDGET_CHANNEL_LINK_CLICK = 'calls-widget-channel-link-click';
|
||||||
|
export const CALLS_JOINED_CALL = 'calls-joined-call';
|
||||||
|
|
||||||
export const REQUEST_CLEAR_DOWNLOADS_DROPDOWN = 'request-clear-downloads-dropdown';
|
export const REQUEST_CLEAR_DOWNLOADS_DROPDOWN = 'request-clear-downloads-dropdown';
|
||||||
export const CLOSE_DOWNLOADS_DROPDOWN = 'close-downloads-dropdown';
|
export const CLOSE_DOWNLOADS_DROPDOWN = 'close-downloads-dropdown';
|
||||||
export const OPEN_DOWNLOADS_DROPDOWN = 'open-downloads-dropdown';
|
export const OPEN_DOWNLOADS_DROPDOWN = 'open-downloads-dropdown';
|
||||||
|
|
|
@ -25,6 +25,11 @@ export const DEFAULT_WINDOW_HEIGHT = 800;
|
||||||
export const MINIMUM_WINDOW_WIDTH = 700;
|
export const MINIMUM_WINDOW_WIDTH = 700;
|
||||||
export const MINIMUM_WINDOW_HEIGHT = 240;
|
export const MINIMUM_WINDOW_HEIGHT = 240;
|
||||||
|
|
||||||
|
// Calls
|
||||||
|
export const MINIMUM_CALLS_WIDGET_WIDTH = 284;
|
||||||
|
export const MINIMUM_CALLS_WIDGET_HEIGHT = 90;
|
||||||
|
export const CALLS_PLUGIN_ID = 'com.mattermost.calls';
|
||||||
|
|
||||||
export const DOWNLOADS_DROPDOWN_HEIGHT = 360;
|
export const DOWNLOADS_DROPDOWN_HEIGHT = 360;
|
||||||
export const DOWNLOADS_DROPDOWN_WIDTH = 280;
|
export const DOWNLOADS_DROPDOWN_WIDTH = 280;
|
||||||
export const DOWNLOADS_DROPDOWN_PADDING = 24;
|
export const DOWNLOADS_DROPDOWN_PADDING = 24;
|
||||||
|
|
|
@ -88,4 +88,31 @@ describe('common/utils/util', () => {
|
||||||
expect(Utils.isVersionGreaterThanOrEqualTo(a, b)).toEqual(true);
|
expect(Utils.isVersionGreaterThanOrEqualTo(a, b)).toEqual(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('boundsDiff', () => {
|
||||||
|
it('diff', () => {
|
||||||
|
const base = {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 400,
|
||||||
|
height: 200,
|
||||||
|
};
|
||||||
|
|
||||||
|
const actual = {
|
||||||
|
x: 100,
|
||||||
|
y: -100,
|
||||||
|
width: 600,
|
||||||
|
height: 100,
|
||||||
|
};
|
||||||
|
|
||||||
|
const diff = {
|
||||||
|
x: -100,
|
||||||
|
y: 100,
|
||||||
|
width: -200,
|
||||||
|
height: 100,
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(Utils.boundsDiff(base, actual)).toEqual(diff);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
// Copyright (c) 2015-2016 Yuya Ochiai
|
// Copyright (c) 2015-2016 Yuya Ochiai
|
||||||
|
|
||||||
|
import {Rectangle} from 'electron';
|
||||||
|
|
||||||
import {DEVELOPMENT, PRODUCTION} from './constants';
|
import {DEVELOPMENT, PRODUCTION} from './constants';
|
||||||
|
|
||||||
function runMode() {
|
function runMode() {
|
||||||
|
@ -47,8 +49,18 @@ export function t(s: string) {
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function boundsDiff(base: Rectangle, actual: Rectangle) {
|
||||||
|
return {
|
||||||
|
x: base.x - actual.x,
|
||||||
|
y: base.y - actual.y,
|
||||||
|
width: base.width - actual.width,
|
||||||
|
height: base.height - actual.height,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
runMode,
|
runMode,
|
||||||
shorten,
|
shorten,
|
||||||
isVersionGreaterThanOrEqualTo,
|
isVersionGreaterThanOrEqualTo,
|
||||||
|
boundsDiff,
|
||||||
};
|
};
|
||||||
|
|
75
src/main/preload/callsWidget.js
Normal file
75
src/main/preload/callsWidget.js
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import {ipcRenderer} from 'electron';
|
||||||
|
|
||||||
|
import {
|
||||||
|
CALLS_LEAVE_CALL,
|
||||||
|
CALLS_JOINED_CALL,
|
||||||
|
CALLS_WIDGET_RESIZE,
|
||||||
|
CALLS_WIDGET_SHARE_SCREEN,
|
||||||
|
CALLS_WIDGET_CHANNEL_LINK_CLICK,
|
||||||
|
DESKTOP_SOURCES_RESULT,
|
||||||
|
DESKTOP_SOURCES_MODAL_REQUEST,
|
||||||
|
DISPATCH_GET_DESKTOP_SOURCES,
|
||||||
|
} from 'common/communication';
|
||||||
|
|
||||||
|
window.addEventListener('message', ({origin, data = {}} = {}) => {
|
||||||
|
const {type, message = {}} = data;
|
||||||
|
|
||||||
|
if (origin !== window.location.origin) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'get-app-version': {
|
||||||
|
ipcRenderer.invoke('get-app-version').then(({name, version}) => {
|
||||||
|
window.postMessage(
|
||||||
|
{
|
||||||
|
type: 'register-desktop',
|
||||||
|
message: {
|
||||||
|
name,
|
||||||
|
version,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
window.location.origin,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'get-desktop-sources': {
|
||||||
|
ipcRenderer.send(DISPATCH_GET_DESKTOP_SOURCES, 'widget', message);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DESKTOP_SOURCES_MODAL_REQUEST:
|
||||||
|
case CALLS_WIDGET_CHANNEL_LINK_CLICK:
|
||||||
|
case CALLS_WIDGET_RESIZE:
|
||||||
|
case CALLS_JOINED_CALL:
|
||||||
|
case CALLS_LEAVE_CALL: {
|
||||||
|
ipcRenderer.send(type, message);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcRenderer.on(DESKTOP_SOURCES_RESULT, (event, sources) => {
|
||||||
|
window.postMessage(
|
||||||
|
{
|
||||||
|
type: DESKTOP_SOURCES_RESULT,
|
||||||
|
message: sources,
|
||||||
|
},
|
||||||
|
window.location.origin,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcRenderer.on(CALLS_WIDGET_SHARE_SCREEN, (event, message) => {
|
||||||
|
window.postMessage(
|
||||||
|
{
|
||||||
|
type: CALLS_WIDGET_SHARE_SCREEN,
|
||||||
|
message,
|
||||||
|
},
|
||||||
|
window.location.origin,
|
||||||
|
);
|
||||||
|
});
|
|
@ -30,6 +30,11 @@ import {
|
||||||
DISPATCH_GET_DESKTOP_SOURCES,
|
DISPATCH_GET_DESKTOP_SOURCES,
|
||||||
DESKTOP_SOURCES_RESULT,
|
DESKTOP_SOURCES_RESULT,
|
||||||
VIEW_FINISHED_RESIZING,
|
VIEW_FINISHED_RESIZING,
|
||||||
|
CALLS_JOIN_CALL,
|
||||||
|
CALLS_JOINED_CALL,
|
||||||
|
CALLS_LEAVE_CALL,
|
||||||
|
DESKTOP_SOURCES_MODAL_REQUEST,
|
||||||
|
CALLS_WIDGET_SHARE_SCREEN,
|
||||||
CLOSE_DOWNLOADS_DROPDOWN,
|
CLOSE_DOWNLOADS_DROPDOWN,
|
||||||
} from 'common/communication';
|
} from 'common/communication';
|
||||||
|
|
||||||
|
@ -157,6 +162,18 @@ window.addEventListener('message', ({origin, data = {}} = {}) => {
|
||||||
ipcRenderer.send(DISPATCH_GET_DESKTOP_SOURCES, viewName, message);
|
ipcRenderer.send(DISPATCH_GET_DESKTOP_SOURCES, viewName, message);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case CALLS_JOIN_CALL: {
|
||||||
|
ipcRenderer.send(CALLS_JOIN_CALL, viewName, message);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CALLS_WIDGET_SHARE_SCREEN: {
|
||||||
|
ipcRenderer.send(CALLS_WIDGET_SHARE_SCREEN, viewName, message);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CALLS_LEAVE_CALL: {
|
||||||
|
ipcRenderer.send(CALLS_LEAVE_CALL, viewName, message);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -307,6 +324,25 @@ ipcRenderer.on(DESKTOP_SOURCES_RESULT, (event, sources) => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcRenderer.on(DESKTOP_SOURCES_MODAL_REQUEST, () => {
|
||||||
|
window.postMessage(
|
||||||
|
{
|
||||||
|
type: DESKTOP_SOURCES_MODAL_REQUEST,
|
||||||
|
},
|
||||||
|
window.location.origin,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcRenderer.on(CALLS_JOINED_CALL, (event, message) => {
|
||||||
|
window.postMessage(
|
||||||
|
{
|
||||||
|
type: CALLS_JOINED_CALL,
|
||||||
|
message,
|
||||||
|
},
|
||||||
|
window.location.origin,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
/* eslint-enable no-magic-numbers */
|
/* eslint-enable no-magic-numbers */
|
||||||
|
|
||||||
window.addEventListener('resize', () => {
|
window.addEventListener('resize', () => {
|
||||||
|
|
286
src/main/windows/callsWidgetWindow.test.js
Normal file
286
src/main/windows/callsWidgetWindow.test.js
Normal file
|
@ -0,0 +1,286 @@
|
||||||
|
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import {EventEmitter} from 'events';
|
||||||
|
import {BrowserWindow} from 'electron';
|
||||||
|
|
||||||
|
import {CALLS_WIDGET_SHARE_SCREEN, CALLS_JOINED_CALL} from 'common/communication';
|
||||||
|
import {
|
||||||
|
MINIMUM_CALLS_WIDGET_WIDTH,
|
||||||
|
MINIMUM_CALLS_WIDGET_HEIGHT,
|
||||||
|
CALLS_PLUGIN_ID,
|
||||||
|
} from 'common/utils/constants';
|
||||||
|
|
||||||
|
import CallsWidgetWindow from './callsWidgetWindow';
|
||||||
|
|
||||||
|
jest.mock('electron', () => ({
|
||||||
|
BrowserWindow: jest.fn(),
|
||||||
|
ipcMain: {
|
||||||
|
on: jest.fn(),
|
||||||
|
off: jest.fn(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('main/windows/callsWidgetWindow', () => {
|
||||||
|
describe('create CallsWidgetWindow', () => {
|
||||||
|
const widgetConfig = {
|
||||||
|
callID: 'test-call-id',
|
||||||
|
siteURL: 'http://localhost:8065',
|
||||||
|
title: '',
|
||||||
|
serverName: 'test-server-name',
|
||||||
|
channelURL: '/team/channel_id',
|
||||||
|
};
|
||||||
|
|
||||||
|
const mainWindow = {
|
||||||
|
getBounds: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const mainView = {
|
||||||
|
view: {
|
||||||
|
webContents: {
|
||||||
|
send: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const baseWindow = new EventEmitter();
|
||||||
|
baseWindow.loadURL = jest.fn();
|
||||||
|
baseWindow.focus = jest.fn();
|
||||||
|
baseWindow.setVisibleOnAllWorkspaces = jest.fn();
|
||||||
|
baseWindow.setAlwaysOnTop = jest.fn();
|
||||||
|
baseWindow.setBackgroundColor = jest.fn();
|
||||||
|
baseWindow.setMenuBarVisibility = jest.fn();
|
||||||
|
baseWindow.setBounds = jest.fn();
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mainWindow.getBounds.mockImplementation(() => {
|
||||||
|
return {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 1280,
|
||||||
|
height: 720,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
baseWindow.getBounds = jest.fn(() => {
|
||||||
|
return {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: MINIMUM_CALLS_WIDGET_WIDTH,
|
||||||
|
height: MINIMUM_CALLS_WIDGET_HEIGHT,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
baseWindow.loadURL.mockImplementation(() => ({
|
||||||
|
catch: jest.fn(),
|
||||||
|
}));
|
||||||
|
BrowserWindow.mockImplementation(() => baseWindow);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.resetAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('verify initial configuration', () => {
|
||||||
|
const widgetWindow = new CallsWidgetWindow(mainWindow, mainView, widgetConfig);
|
||||||
|
expect(widgetWindow).toBeDefined();
|
||||||
|
expect(BrowserWindow).toHaveBeenCalledWith(expect.objectContaining({
|
||||||
|
width: MINIMUM_CALLS_WIDGET_WIDTH,
|
||||||
|
height: MINIMUM_CALLS_WIDGET_HEIGHT,
|
||||||
|
minWidth: MINIMUM_CALLS_WIDGET_WIDTH,
|
||||||
|
minHeight: MINIMUM_CALLS_WIDGET_HEIGHT,
|
||||||
|
fullscreen: false,
|
||||||
|
resizable: false,
|
||||||
|
frame: false,
|
||||||
|
transparent: true,
|
||||||
|
show: false,
|
||||||
|
alwaysOnTop: true,
|
||||||
|
backgroundColor: '#00ffffff',
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('showing window', () => {
|
||||||
|
baseWindow.show = jest.fn(() => {
|
||||||
|
baseWindow.emit('show');
|
||||||
|
});
|
||||||
|
|
||||||
|
const widgetWindow = new CallsWidgetWindow(mainWindow, mainView, widgetConfig);
|
||||||
|
widgetWindow.win.emit('ready-to-show');
|
||||||
|
|
||||||
|
expect(widgetWindow.win.show).toHaveBeenCalled();
|
||||||
|
expect(widgetWindow.win.setAlwaysOnTop).toHaveBeenCalled();
|
||||||
|
expect(widgetWindow.win.setBounds).toHaveBeenCalledWith({
|
||||||
|
x: 12,
|
||||||
|
y: 618,
|
||||||
|
width: MINIMUM_CALLS_WIDGET_WIDTH,
|
||||||
|
height: MINIMUM_CALLS_WIDGET_HEIGHT,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('loadURL error', () => {
|
||||||
|
baseWindow.show = jest.fn(() => {
|
||||||
|
baseWindow.emit('show');
|
||||||
|
});
|
||||||
|
|
||||||
|
baseWindow.loadURL = jest.fn(() => {
|
||||||
|
return Promise.reject(new Error('failed to load URL'));
|
||||||
|
});
|
||||||
|
|
||||||
|
const widgetWindow = new CallsWidgetWindow(mainWindow, mainView, widgetConfig);
|
||||||
|
expect(widgetWindow.win.loadURL).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('open devTools', () => {
|
||||||
|
process.env.MM_DEBUG_CALLS_WIDGET = 'true';
|
||||||
|
|
||||||
|
baseWindow.show = jest.fn(() => {
|
||||||
|
baseWindow.emit('show');
|
||||||
|
});
|
||||||
|
|
||||||
|
baseWindow.webContents = {
|
||||||
|
openDevTools: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const widgetWindow = new CallsWidgetWindow(mainWindow, mainView, widgetConfig);
|
||||||
|
widgetWindow.win.emit('ready-to-show');
|
||||||
|
|
||||||
|
expect(widgetWindow.win.webContents.openDevTools).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('closing window', () => {
|
||||||
|
baseWindow.close = jest.fn(() => {
|
||||||
|
baseWindow.emit('closed');
|
||||||
|
});
|
||||||
|
|
||||||
|
const widgetWindow = new CallsWidgetWindow(mainWindow, mainView, widgetConfig);
|
||||||
|
widgetWindow.close();
|
||||||
|
expect(widgetWindow.win.close).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resize', () => {
|
||||||
|
baseWindow.show = jest.fn(() => {
|
||||||
|
baseWindow.emit('show');
|
||||||
|
});
|
||||||
|
|
||||||
|
let winBounds = {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: MINIMUM_CALLS_WIDGET_WIDTH,
|
||||||
|
height: MINIMUM_CALLS_WIDGET_HEIGHT,
|
||||||
|
};
|
||||||
|
baseWindow.getBounds = jest.fn(() => {
|
||||||
|
return winBounds;
|
||||||
|
});
|
||||||
|
|
||||||
|
baseWindow.setBounds = jest.fn((bounds) => {
|
||||||
|
winBounds = bounds;
|
||||||
|
});
|
||||||
|
|
||||||
|
const widgetWindow = new CallsWidgetWindow(mainWindow, mainView, widgetConfig);
|
||||||
|
widgetWindow.win.emit('ready-to-show');
|
||||||
|
|
||||||
|
expect(baseWindow.setBounds).toHaveBeenCalledTimes(2);
|
||||||
|
|
||||||
|
widgetWindow.onResize(null, {
|
||||||
|
element: 'calls-widget-menu',
|
||||||
|
height: 100,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(baseWindow.setBounds).toHaveBeenCalledWith({
|
||||||
|
x: 12,
|
||||||
|
y: 518,
|
||||||
|
width: MINIMUM_CALLS_WIDGET_WIDTH,
|
||||||
|
height: MINIMUM_CALLS_WIDGET_HEIGHT + 100,
|
||||||
|
});
|
||||||
|
|
||||||
|
widgetWindow.onResize(null, {
|
||||||
|
element: 'calls-widget-audio-menu',
|
||||||
|
width: 100,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(baseWindow.setBounds).toHaveBeenCalledWith({
|
||||||
|
x: 12,
|
||||||
|
y: 518,
|
||||||
|
width: MINIMUM_CALLS_WIDGET_WIDTH + 100,
|
||||||
|
height: MINIMUM_CALLS_WIDGET_HEIGHT + 100,
|
||||||
|
});
|
||||||
|
|
||||||
|
widgetWindow.onResize(null, {
|
||||||
|
element: 'calls-widget-audio-menu',
|
||||||
|
width: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(baseWindow.setBounds).toHaveBeenCalledWith({
|
||||||
|
x: 12,
|
||||||
|
y: 518,
|
||||||
|
width: MINIMUM_CALLS_WIDGET_WIDTH,
|
||||||
|
height: MINIMUM_CALLS_WIDGET_HEIGHT + 100,
|
||||||
|
});
|
||||||
|
|
||||||
|
widgetWindow.onResize(null, {
|
||||||
|
element: 'calls-widget-menu',
|
||||||
|
height: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(baseWindow.setBounds).toHaveBeenCalledWith({
|
||||||
|
x: 12,
|
||||||
|
y: 618,
|
||||||
|
width: MINIMUM_CALLS_WIDGET_WIDTH,
|
||||||
|
height: MINIMUM_CALLS_WIDGET_HEIGHT,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getServerName', () => {
|
||||||
|
const widgetWindow = new CallsWidgetWindow(mainWindow, mainView, widgetConfig);
|
||||||
|
expect(widgetWindow.getServerName()).toBe('test-server-name');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getChannelURL', () => {
|
||||||
|
const widgetWindow = new CallsWidgetWindow(mainWindow, mainView, widgetConfig);
|
||||||
|
expect(widgetWindow.getChannelURL()).toBe('/team/channel_id');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getChannelURL', () => {
|
||||||
|
const widgetWindow = new CallsWidgetWindow(mainWindow, mainView, widgetConfig);
|
||||||
|
expect(widgetWindow.getCallID()).toBe('test-call-id');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getWidgetURL', () => {
|
||||||
|
const config = {
|
||||||
|
...widgetConfig,
|
||||||
|
siteURL: 'http://localhost:8065/subpath',
|
||||||
|
title: 'call test title #/&',
|
||||||
|
};
|
||||||
|
const widgetWindow = new CallsWidgetWindow(mainWindow, mainView, config);
|
||||||
|
const expected = `${config.siteURL}/plugins/${CALLS_PLUGIN_ID}/standalone/widget.html?call_id=${config.callID}&title=call+test+title+%23%2F%26`;
|
||||||
|
expect(widgetWindow.getWidgetURL()).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('onShareScreen', () => {
|
||||||
|
baseWindow.webContents = {
|
||||||
|
send: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const widgetWindow = new CallsWidgetWindow(mainWindow, mainView, widgetConfig);
|
||||||
|
const message = {
|
||||||
|
sourceID: 'test-source-id',
|
||||||
|
withAudio: false,
|
||||||
|
};
|
||||||
|
widgetWindow.onShareScreen(null, '', message);
|
||||||
|
expect(widgetWindow.win.webContents.send).toHaveBeenCalledWith(CALLS_WIDGET_SHARE_SCREEN, message);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('onJoinedCall', () => {
|
||||||
|
baseWindow.webContents = {
|
||||||
|
send: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const widgetWindow = new CallsWidgetWindow(mainWindow, mainView, widgetConfig);
|
||||||
|
const message = {
|
||||||
|
callID: 'test-call-id',
|
||||||
|
};
|
||||||
|
widgetWindow.onJoinedCall(null, message);
|
||||||
|
expect(widgetWindow.mainView.view.webContents.send).toHaveBeenCalledWith(CALLS_JOINED_CALL, message);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
213
src/main/windows/callsWidgetWindow.ts
Normal file
213
src/main/windows/callsWidgetWindow.ts
Normal file
|
@ -0,0 +1,213 @@
|
||||||
|
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import url from 'url';
|
||||||
|
|
||||||
|
import {EventEmitter} from 'events';
|
||||||
|
import {BrowserWindow, Rectangle, ipcMain, IpcMainEvent} from 'electron';
|
||||||
|
import log from 'electron-log';
|
||||||
|
|
||||||
|
import {
|
||||||
|
CallsWidgetWindowConfig,
|
||||||
|
CallsWidgetResizeMessage,
|
||||||
|
CallsWidgetShareScreenMessage,
|
||||||
|
CallsJoinedCallMessage,
|
||||||
|
} from 'types/calls';
|
||||||
|
|
||||||
|
import {MattermostView} from 'main/views/MattermostView';
|
||||||
|
|
||||||
|
import {getLocalPreload} from 'main/utils';
|
||||||
|
|
||||||
|
import {
|
||||||
|
MINIMUM_CALLS_WIDGET_WIDTH,
|
||||||
|
MINIMUM_CALLS_WIDGET_HEIGHT,
|
||||||
|
CALLS_PLUGIN_ID,
|
||||||
|
} from 'common/utils/constants';
|
||||||
|
import Utils from 'common/utils/util';
|
||||||
|
import {
|
||||||
|
CALLS_WIDGET_RESIZE,
|
||||||
|
CALLS_WIDGET_SHARE_SCREEN,
|
||||||
|
CALLS_JOINED_CALL,
|
||||||
|
} from 'common/communication';
|
||||||
|
|
||||||
|
type LoadURLOpts = {
|
||||||
|
extraHeaders: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class CallsWidgetWindow extends EventEmitter {
|
||||||
|
public win: BrowserWindow;
|
||||||
|
private main: BrowserWindow;
|
||||||
|
private mainView: MattermostView;
|
||||||
|
private config: CallsWidgetWindowConfig;
|
||||||
|
private boundsErr: Rectangle = {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
};
|
||||||
|
private offsetsMap = {
|
||||||
|
'calls-widget-menu': {
|
||||||
|
height: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(mainWindow: BrowserWindow, mainView: MattermostView, config: CallsWidgetWindowConfig) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.config = config;
|
||||||
|
this.main = mainWindow;
|
||||||
|
this.mainView = mainView;
|
||||||
|
this.win = new BrowserWindow({
|
||||||
|
width: MINIMUM_CALLS_WIDGET_WIDTH,
|
||||||
|
height: MINIMUM_CALLS_WIDGET_HEIGHT,
|
||||||
|
minWidth: MINIMUM_CALLS_WIDGET_WIDTH,
|
||||||
|
minHeight: MINIMUM_CALLS_WIDGET_HEIGHT,
|
||||||
|
title: 'Calls Widget',
|
||||||
|
fullscreen: false,
|
||||||
|
resizable: false,
|
||||||
|
frame: false,
|
||||||
|
transparent: true,
|
||||||
|
show: false,
|
||||||
|
alwaysOnTop: true,
|
||||||
|
backgroundColor: '#00ffffff',
|
||||||
|
webPreferences: {
|
||||||
|
preload: getLocalPreload('callsWidget.js'),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.win.once('ready-to-show', () => this.win.show());
|
||||||
|
this.win.once('show', this.onShow);
|
||||||
|
this.win.on('closed', this.onClosed);
|
||||||
|
ipcMain.on(CALLS_WIDGET_RESIZE, this.onResize);
|
||||||
|
ipcMain.on(CALLS_WIDGET_SHARE_SCREEN, this.onShareScreen);
|
||||||
|
ipcMain.on(CALLS_JOINED_CALL, this.onJoinedCall);
|
||||||
|
|
||||||
|
this.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
public close() {
|
||||||
|
log.debug('CallsWidgetWindow.close');
|
||||||
|
this.win.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getServerName() {
|
||||||
|
return this.config.serverName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getChannelURL() {
|
||||||
|
return this.config.channelURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getCallID() {
|
||||||
|
return this.config.callID;
|
||||||
|
}
|
||||||
|
|
||||||
|
private load() {
|
||||||
|
const opts = {} as LoadURLOpts;
|
||||||
|
this.win.loadURL(this.getWidgetURL(), opts).catch((reason) => {
|
||||||
|
log.error(`Calls widget window failed to load: ${reason}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private onClosed = () => {
|
||||||
|
log.debug('CallsWidgetWindow.onClosed');
|
||||||
|
this.emit('closed');
|
||||||
|
this.removeAllListeners('closed');
|
||||||
|
ipcMain.off(CALLS_WIDGET_RESIZE, this.onResize);
|
||||||
|
ipcMain.off(CALLS_WIDGET_SHARE_SCREEN, this.onShareScreen);
|
||||||
|
ipcMain.off(CALLS_JOINED_CALL, this.onJoinedCall);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getWidgetURL() {
|
||||||
|
const u = new url.URL(this.config.siteURL);
|
||||||
|
u.pathname += `/plugins/${CALLS_PLUGIN_ID}/standalone/widget.html`;
|
||||||
|
u.searchParams.append('call_id', this.config.callID);
|
||||||
|
if (this.config.title) {
|
||||||
|
u.searchParams.append('title', this.config.title);
|
||||||
|
}
|
||||||
|
|
||||||
|
return u.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private onResize = (event: IpcMainEvent, msg: CallsWidgetResizeMessage) => {
|
||||||
|
log.debug('CallsWidgetWindow.onResize');
|
||||||
|
|
||||||
|
const currBounds = this.win.getBounds();
|
||||||
|
|
||||||
|
switch (msg.element) {
|
||||||
|
case 'calls-widget-audio-menu': {
|
||||||
|
const newBounds = {
|
||||||
|
x: currBounds.x,
|
||||||
|
y: currBounds.y,
|
||||||
|
width: msg.width > 0 ? currBounds.width + msg.width : MINIMUM_CALLS_WIDGET_WIDTH,
|
||||||
|
height: currBounds.height,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.setBounds(newBounds);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'calls-widget-menu': {
|
||||||
|
const hOff = this.offsetsMap[msg.element].height;
|
||||||
|
|
||||||
|
const newBounds = {
|
||||||
|
x: currBounds.x,
|
||||||
|
y: msg.height === 0 ? currBounds.y + hOff : currBounds.y - (msg.height - hOff),
|
||||||
|
width: MINIMUM_CALLS_WIDGET_WIDTH,
|
||||||
|
height: MINIMUM_CALLS_WIDGET_HEIGHT + msg.height,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.setBounds(newBounds);
|
||||||
|
|
||||||
|
this.offsetsMap[msg.element].height = msg.height;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private onShareScreen = (ev: IpcMainEvent, viewName: string, message: CallsWidgetShareScreenMessage) => {
|
||||||
|
this.win.webContents.send(CALLS_WIDGET_SHARE_SCREEN, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onJoinedCall = (ev: IpcMainEvent, message: CallsJoinedCallMessage) => {
|
||||||
|
this.mainView.view.webContents.send(CALLS_JOINED_CALL, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private setBounds(bounds: Rectangle) {
|
||||||
|
// NOTE: this hack is needed to fix positioning on certain systems where
|
||||||
|
// BrowserWindow.setBounds() is not consistent.
|
||||||
|
bounds.x += this.boundsErr.x;
|
||||||
|
bounds.y += this.boundsErr.y;
|
||||||
|
bounds.height += this.boundsErr.height;
|
||||||
|
bounds.width += this.boundsErr.width;
|
||||||
|
|
||||||
|
this.win.setBounds(bounds);
|
||||||
|
this.boundsErr = Utils.boundsDiff(bounds, this.win.getBounds());
|
||||||
|
}
|
||||||
|
|
||||||
|
private onShow = () => {
|
||||||
|
log.debug('CallsWidgetWindow.onShow');
|
||||||
|
|
||||||
|
this.win.focus();
|
||||||
|
this.win.setVisibleOnAllWorkspaces(true, {visibleOnFullScreen: true});
|
||||||
|
this.win.setAlwaysOnTop(true, 'screen-saver');
|
||||||
|
|
||||||
|
const bounds = this.win.getBounds();
|
||||||
|
const mainBounds = this.main.getBounds();
|
||||||
|
const initialBounds = {
|
||||||
|
x: mainBounds.x + 12,
|
||||||
|
y: (mainBounds.y + mainBounds.height) - bounds.height - 12,
|
||||||
|
width: MINIMUM_CALLS_WIDGET_WIDTH,
|
||||||
|
height: MINIMUM_CALLS_WIDGET_HEIGHT,
|
||||||
|
};
|
||||||
|
this.win.setMenuBarVisibility(false);
|
||||||
|
|
||||||
|
if (process.env.MM_DEBUG_CALLS_WIDGET) {
|
||||||
|
this.win.webContents.openDevTools({mode: 'detach'});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setBounds(initialBounds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -16,6 +16,8 @@ import {WindowManager} from './windowManager';
|
||||||
import createMainWindow from './mainWindow';
|
import createMainWindow from './mainWindow';
|
||||||
import {createSettingsWindow} from './settingsWindow';
|
import {createSettingsWindow} from './settingsWindow';
|
||||||
|
|
||||||
|
import CallsWidgetWindow from './callsWidgetWindow';
|
||||||
|
|
||||||
jest.mock('path', () => ({
|
jest.mock('path', () => ({
|
||||||
resolve: jest.fn(),
|
resolve: jest.fn(),
|
||||||
join: jest.fn(),
|
join: jest.fn(),
|
||||||
|
@ -74,6 +76,8 @@ jest.mock('../downloadsManager', () => ({
|
||||||
getDownloads: () => {},
|
getDownloads: () => {},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
jest.mock('./callsWidgetWindow');
|
||||||
|
|
||||||
describe('main/windows/windowManager', () => {
|
describe('main/windows/windowManager', () => {
|
||||||
describe('handleUpdateConfig', () => {
|
describe('handleUpdateConfig', () => {
|
||||||
const windowManager = new WindowManager();
|
const windowManager = new WindowManager();
|
||||||
|
@ -991,4 +995,185 @@ describe('main/windows/windowManager', () => {
|
||||||
expect(view1.isAtRoot).toBe(true);
|
expect(view1.isAtRoot).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('createCallsWidgetWindow', () => {
|
||||||
|
const view = {
|
||||||
|
name: 'server-1_tab-messaging',
|
||||||
|
serverInfo: {
|
||||||
|
remoteInfo: {
|
||||||
|
siteURL: 'http://server-1.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const windowManager = new WindowManager();
|
||||||
|
windowManager.viewManager = {
|
||||||
|
views: new Map([
|
||||||
|
['server-1_tab-messaging', view],
|
||||||
|
]),
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should create calls widget window', () => {
|
||||||
|
expect(windowManager.callsWidgetWindow).toBeUndefined();
|
||||||
|
windowManager.createCallsWidgetWindow(null, 'server-1_tab-messaging', {callID: 'test'});
|
||||||
|
expect(windowManager.callsWidgetWindow).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not create a new window if call is the same', () => {
|
||||||
|
const widgetWindow = windowManager.callsWidgetWindow;
|
||||||
|
expect(widgetWindow).toBeDefined();
|
||||||
|
widgetWindow.getCallID = jest.fn(() => 'test');
|
||||||
|
windowManager.createCallsWidgetWindow(null, 'server-1_tab-messaging', {callID: 'test'});
|
||||||
|
expect(windowManager.callsWidgetWindow).toEqual(widgetWindow);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create a new window if switching calls', () => {
|
||||||
|
const widgetWindow = windowManager.callsWidgetWindow;
|
||||||
|
expect(widgetWindow).toBeDefined();
|
||||||
|
widgetWindow.getCallID = jest.fn(() => 'test');
|
||||||
|
windowManager.createCallsWidgetWindow(null, 'server-1_tab-messaging', {callID: 'test2'});
|
||||||
|
expect(windowManager.callsWidgetWindow).not.toEqual(widgetWindow);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('handleDesktopSourcesModalRequest', () => {
|
||||||
|
const windowManager = new WindowManager();
|
||||||
|
windowManager.switchServer = jest.fn();
|
||||||
|
windowManager.viewManager = {
|
||||||
|
showByName: jest.fn(),
|
||||||
|
getCurrentView: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
CallsWidgetWindow.mockImplementation(() => {
|
||||||
|
return {
|
||||||
|
getServerName: () => 'server-1',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
Config.teams = [
|
||||||
|
{
|
||||||
|
name: 'server-1',
|
||||||
|
order: 1,
|
||||||
|
tabs: [
|
||||||
|
{
|
||||||
|
name: 'tab-1',
|
||||||
|
order: 0,
|
||||||
|
isOpen: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'tab-2',
|
||||||
|
order: 2,
|
||||||
|
isOpen: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}, {
|
||||||
|
name: 'server-2',
|
||||||
|
order: 0,
|
||||||
|
tabs: [
|
||||||
|
{
|
||||||
|
name: 'tab-1',
|
||||||
|
order: 0,
|
||||||
|
isOpen: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'tab-2',
|
||||||
|
order: 2,
|
||||||
|
isOpen: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
lastActiveTab: 2,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const map = Config.teams.reduce((arr, item) => {
|
||||||
|
item.tabs.forEach((tab) => {
|
||||||
|
arr.push([`${item.name}_${tab.name}`, {}]);
|
||||||
|
});
|
||||||
|
return arr;
|
||||||
|
}, []);
|
||||||
|
windowManager.viewManager.views = new Map(map);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.resetAllMocks();
|
||||||
|
Config.teams = [];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should switch server', () => {
|
||||||
|
windowManager.callsWidgetWindow = new CallsWidgetWindow();
|
||||||
|
windowManager.handleDesktopSourcesModalRequest();
|
||||||
|
expect(windowManager.switchServer).toHaveBeenCalledWith('server-1');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('handleCallsWidgetChannelLinkClick', () => {
|
||||||
|
const windowManager = new WindowManager();
|
||||||
|
windowManager.switchServer = jest.fn();
|
||||||
|
windowManager.viewManager = {
|
||||||
|
showByName: jest.fn(),
|
||||||
|
getCurrentView: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
CallsWidgetWindow.mockImplementation(() => {
|
||||||
|
return {
|
||||||
|
getServerName: () => 'server-2',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
Config.teams = [
|
||||||
|
{
|
||||||
|
name: 'server-1',
|
||||||
|
order: 1,
|
||||||
|
tabs: [
|
||||||
|
{
|
||||||
|
name: 'tab-1',
|
||||||
|
order: 0,
|
||||||
|
isOpen: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'tab-2',
|
||||||
|
order: 2,
|
||||||
|
isOpen: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}, {
|
||||||
|
name: 'server-2',
|
||||||
|
order: 0,
|
||||||
|
tabs: [
|
||||||
|
{
|
||||||
|
name: 'tab-1',
|
||||||
|
order: 0,
|
||||||
|
isOpen: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'tab-2',
|
||||||
|
order: 2,
|
||||||
|
isOpen: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
lastActiveTab: 2,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const map = Config.teams.reduce((arr, item) => {
|
||||||
|
item.tabs.forEach((tab) => {
|
||||||
|
arr.push([`${item.name}_${tab.name}`, {}]);
|
||||||
|
});
|
||||||
|
return arr;
|
||||||
|
}, []);
|
||||||
|
windowManager.viewManager.views = new Map(map);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.resetAllMocks();
|
||||||
|
Config.teams = [];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should switch server', () => {
|
||||||
|
windowManager.callsWidgetWindow = new CallsWidgetWindow();
|
||||||
|
windowManager.handleCallsWidgetChannelLinkClick();
|
||||||
|
expect(windowManager.switchServer).toHaveBeenCalledWith('server-2');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,6 +7,10 @@ import path from 'path';
|
||||||
import {app, BrowserWindow, nativeImage, systemPreferences, ipcMain, IpcMainEvent, IpcMainInvokeEvent, desktopCapturer} from 'electron';
|
import {app, BrowserWindow, nativeImage, systemPreferences, ipcMain, IpcMainEvent, IpcMainInvokeEvent, desktopCapturer} from 'electron';
|
||||||
import log from 'electron-log';
|
import log from 'electron-log';
|
||||||
|
|
||||||
|
import {
|
||||||
|
CallsJoinCallMessage,
|
||||||
|
} from 'types/calls';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
MAXIMIZE_CHANGE,
|
MAXIMIZE_CHANGE,
|
||||||
HISTORY,
|
HISTORY,
|
||||||
|
@ -27,6 +31,10 @@ import {
|
||||||
DESKTOP_SOURCES_RESULT,
|
DESKTOP_SOURCES_RESULT,
|
||||||
RELOAD_CURRENT_VIEW,
|
RELOAD_CURRENT_VIEW,
|
||||||
VIEW_FINISHED_RESIZING,
|
VIEW_FINISHED_RESIZING,
|
||||||
|
CALLS_JOIN_CALL,
|
||||||
|
CALLS_LEAVE_CALL,
|
||||||
|
DESKTOP_SOURCES_MODAL_REQUEST,
|
||||||
|
CALLS_WIDGET_CHANNEL_LINK_CLICK,
|
||||||
} from 'common/communication';
|
} from 'common/communication';
|
||||||
import urlUtils from 'common/utils/url';
|
import urlUtils from 'common/utils/url';
|
||||||
import {SECOND} from 'common/utils/constants';
|
import {SECOND} from 'common/utils/constants';
|
||||||
|
@ -49,6 +57,8 @@ import downloadsManager from 'main/downloadsManager';
|
||||||
import {createSettingsWindow} from './settingsWindow';
|
import {createSettingsWindow} from './settingsWindow';
|
||||||
import createMainWindow from './mainWindow';
|
import createMainWindow from './mainWindow';
|
||||||
|
|
||||||
|
import CallsWidgetWindow from './callsWidgetWindow';
|
||||||
|
|
||||||
// singleton module to manage application's windows
|
// singleton module to manage application's windows
|
||||||
|
|
||||||
export class WindowManager {
|
export class WindowManager {
|
||||||
|
@ -57,6 +67,7 @@ export class WindowManager {
|
||||||
mainWindow?: BrowserWindow;
|
mainWindow?: BrowserWindow;
|
||||||
mainWindowReady: boolean;
|
mainWindowReady: boolean;
|
||||||
settingsWindow?: BrowserWindow;
|
settingsWindow?: BrowserWindow;
|
||||||
|
callsWidgetWindow?: CallsWidgetWindow;
|
||||||
viewManager?: ViewManager;
|
viewManager?: ViewManager;
|
||||||
teamDropdown?: TeamDropdownView;
|
teamDropdown?: TeamDropdownView;
|
||||||
downloadsDropdown?: DownloadsDropdownView;
|
downloadsDropdown?: DownloadsDropdownView;
|
||||||
|
@ -81,6 +92,10 @@ export class WindowManager {
|
||||||
ipcMain.on(DISPATCH_GET_DESKTOP_SOURCES, this.handleGetDesktopSources);
|
ipcMain.on(DISPATCH_GET_DESKTOP_SOURCES, this.handleGetDesktopSources);
|
||||||
ipcMain.on(RELOAD_CURRENT_VIEW, this.handleReloadCurrentView);
|
ipcMain.on(RELOAD_CURRENT_VIEW, this.handleReloadCurrentView);
|
||||||
ipcMain.on(VIEW_FINISHED_RESIZING, this.handleViewFinishedResizing);
|
ipcMain.on(VIEW_FINISHED_RESIZING, this.handleViewFinishedResizing);
|
||||||
|
ipcMain.on(CALLS_JOIN_CALL, this.createCallsWidgetWindow);
|
||||||
|
ipcMain.on(CALLS_LEAVE_CALL, () => this.callsWidgetWindow?.close());
|
||||||
|
ipcMain.on(DESKTOP_SOURCES_MODAL_REQUEST, this.handleDesktopSourcesModalRequest);
|
||||||
|
ipcMain.on(CALLS_WIDGET_CHANNEL_LINK_CLICK, this.handleCallsWidgetChannelLinkClick);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleUpdateConfig = () => {
|
handleUpdateConfig = () => {
|
||||||
|
@ -89,6 +104,53 @@ export class WindowManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createCallsWidgetWindow = (event: IpcMainEvent, viewName: string, msg: CallsJoinCallMessage) => {
|
||||||
|
log.debug('WindowManager.createCallsWidgetWindow');
|
||||||
|
if (this.callsWidgetWindow) {
|
||||||
|
// trying to join again the call we are already in should not be allowed.
|
||||||
|
if (this.callsWidgetWindow.getCallID() === msg.callID) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.callsWidgetWindow.close();
|
||||||
|
}
|
||||||
|
const currentView = this.viewManager?.views.get(viewName);
|
||||||
|
if (!currentView) {
|
||||||
|
log.error('unable to create calls widget window: currentView is missing');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.callsWidgetWindow = new CallsWidgetWindow(this.mainWindow!, currentView, {
|
||||||
|
siteURL: currentView.serverInfo.remoteInfo.siteURL!,
|
||||||
|
callID: msg.callID,
|
||||||
|
title: msg.title,
|
||||||
|
serverName: this.currentServerName!,
|
||||||
|
channelURL: msg.channelURL,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.callsWidgetWindow.on('closed', () => delete this.callsWidgetWindow);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDesktopSourcesModalRequest = () => {
|
||||||
|
log.debug('WindowManager.handleDesktopSourcesModalRequest');
|
||||||
|
|
||||||
|
if (this.callsWidgetWindow) {
|
||||||
|
this.switchServer(this.callsWidgetWindow?.getServerName());
|
||||||
|
this.mainWindow?.focus();
|
||||||
|
const currentView = this.viewManager?.getCurrentView();
|
||||||
|
currentView?.view.webContents.send(DESKTOP_SOURCES_MODAL_REQUEST);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCallsWidgetChannelLinkClick = () => {
|
||||||
|
log.debug('WindowManager.handleCallsWidgetChannelLinkClick');
|
||||||
|
if (this.callsWidgetWindow) {
|
||||||
|
this.switchServer(this.callsWidgetWindow.getServerName());
|
||||||
|
this.mainWindow?.focus();
|
||||||
|
const currentView = this.viewManager?.getCurrentView();
|
||||||
|
currentView?.view.webContents.send(BROWSER_HISTORY_PUSH, this.callsWidgetWindow.getChannelURL());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
showSettingsWindow = () => {
|
showSettingsWindow = () => {
|
||||||
log.debug('WindowManager.showSettingsWindow');
|
log.debug('WindowManager.showSettingsWindow');
|
||||||
|
|
||||||
|
@ -743,19 +805,26 @@ export class WindowManager {
|
||||||
handleGetDesktopSources = async (event: IpcMainEvent, viewName: string, opts: Electron.SourcesOptions) => {
|
handleGetDesktopSources = async (event: IpcMainEvent, viewName: string, opts: Electron.SourcesOptions) => {
|
||||||
log.debug('WindowManager.handleGetDesktopSources', {viewName, opts});
|
log.debug('WindowManager.handleGetDesktopSources', {viewName, opts});
|
||||||
|
|
||||||
|
const globalWidget = viewName === 'widget' && this.callsWidgetWindow;
|
||||||
const view = this.viewManager?.views.get(viewName);
|
const view = this.viewManager?.views.get(viewName);
|
||||||
if (!view) {
|
if (!view && !globalWidget) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
desktopCapturer.getSources(opts).then((sources) => {
|
desktopCapturer.getSources(opts).then((sources) => {
|
||||||
view.view.webContents.send(DESKTOP_SOURCES_RESULT, sources.map((source) => {
|
const message = sources.map((source) => {
|
||||||
return {
|
return {
|
||||||
id: source.id,
|
id: source.id,
|
||||||
name: source.name,
|
name: source.name,
|
||||||
thumbnailURL: source.thumbnail.toDataURL(),
|
thumbnailURL: source.thumbnail.toDataURL(),
|
||||||
};
|
};
|
||||||
}));
|
});
|
||||||
|
|
||||||
|
if (view) {
|
||||||
|
view.view.webContents.send(DESKTOP_SOURCES_RESULT, message);
|
||||||
|
} else {
|
||||||
|
this.callsWidgetWindow?.win.webContents.send(DESKTOP_SOURCES_RESULT, message);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
30
src/types/calls.ts
Normal file
30
src/types/calls.ts
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
export type CallsWidgetWindowConfig = {
|
||||||
|
siteURL: string;
|
||||||
|
callID: string;
|
||||||
|
title: string;
|
||||||
|
serverName: string;
|
||||||
|
channelURL: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CallsJoinCallMessage = {
|
||||||
|
callID: string;
|
||||||
|
title: string;
|
||||||
|
channelURL: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CallsWidgetResizeMessage = {
|
||||||
|
element: string;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CallsWidgetShareScreenMessage = {
|
||||||
|
sourceID: string;
|
||||||
|
withAudio: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CallsJoinedCallMessage = {
|
||||||
|
callID: string;
|
||||||
|
}
|
|
@ -23,6 +23,7 @@ module.exports = merge(base, {
|
||||||
modalPreload: './src/main/preload/modalPreload.js',
|
modalPreload: './src/main/preload/modalPreload.js',
|
||||||
loadingScreenPreload: './src/main/preload/loadingScreenPreload.js',
|
loadingScreenPreload: './src/main/preload/loadingScreenPreload.js',
|
||||||
urlView: './src/main/preload/urlView.js',
|
urlView: './src/main/preload/urlView.js',
|
||||||
|
callsWidget: './src/main/preload/callsWidget.js',
|
||||||
},
|
},
|
||||||
externals: {
|
externals: {
|
||||||
'macos-notification-state': 'require("macos-notification-state")',
|
'macos-notification-state': 'require("macos-notification-state")',
|
||||||
|
|
Loading…
Reference in a new issue