mirror of
https://github.com/finos/SymphonyElectron.git
synced 2025-02-25 18:55:29 -06:00
Typescript - Completed screen sharing indicator and fixed i18n usage issue
This commit is contained in:
@@ -237,7 +237,7 @@
|
||||
}
|
||||
}, stream => {
|
||||
handleStream(stream, source.display_id);
|
||||
}, handleError)
|
||||
}, handleError);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
12
gulpfile.js
Normal file
12
gulpfile.js
Normal file
@@ -0,0 +1,12 @@
|
||||
const gulp = require('gulp');
|
||||
const less = require('gulp-less');
|
||||
const sourcemaps = require('gulp-sourcemaps');
|
||||
const path = require('path');
|
||||
|
||||
gulp.task('less', function () {
|
||||
return gulp.src('./src/**/*.less')
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(less())
|
||||
.pipe(sourcemaps.write())
|
||||
.pipe(gulp.dest(path.join(__dirname, 'src')));
|
||||
});
|
||||
@@ -12,7 +12,7 @@
|
||||
"tsc": "git clean -xdf ./lib && npm run lint && tsc",
|
||||
"lint": "tslint --project tsconfig.json",
|
||||
"start": "npm run compile-css && npm run browserify-preload && cross-env ELECTRON_DEV=true electron .",
|
||||
"compile-css": "lessc src/renderer/styles/main.less src/renderer/styles/main.css",
|
||||
"compile-css": "gulp less",
|
||||
"prebuild": "npm run rebuild && npm run browserify-preload",
|
||||
"browserify-preload": "browserify -o src/renderer/_preload-main.js -x electron --insert-global-vars=__filename,__dirname src/renderer/preload-main.js",
|
||||
"rebuild": "electron-rebuild -f",
|
||||
@@ -81,6 +81,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/auto-launch": "^5.0.0",
|
||||
"@types/ffi": "0.2.1",
|
||||
"@types/lodash.omit": "^4.5.4",
|
||||
"@types/node": "10.11.4",
|
||||
"@types/react": "16.4.18",
|
||||
@@ -107,6 +108,9 @@
|
||||
"eslint-plugin-jsx-a11y": "6.1.1",
|
||||
"eslint-plugin-react": "7.11.0",
|
||||
"glob": "7.1.3",
|
||||
"gulp": "4.0.0",
|
||||
"gulp-less": "4.0.1",
|
||||
"gulp-sourcemaps": "2.6.4",
|
||||
"jest": "23.6.0",
|
||||
"jest-html-reporter": "2.4.2",
|
||||
"less": "3.8.1",
|
||||
|
||||
@@ -126,6 +126,12 @@ ipcMain.on(apiName.symphonyApi, (event: Electron.Event, arg: IApiArgs) => {
|
||||
case apiCmds.closeWindow:
|
||||
windowHandler.closeWindow(arg.windowType);
|
||||
break;
|
||||
case apiCmds.openScreenSharingIndicator:
|
||||
const { displayId, id } = arg;
|
||||
if (typeof displayId === 'string' && typeof id === 'number') {
|
||||
windowHandler.createScreenSharingIndicatorWindow(event.sender, displayId, id);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as electron from 'electron';
|
||||
import { BrowserWindow, crashReporter, ipcMain, webContents } from 'electron';
|
||||
import { BrowserWindow, crashReporter, ipcMain } from 'electron';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { format, parse } from 'url';
|
||||
@@ -8,6 +8,7 @@ import { buildNumber, clientVersion, version } from '../../package.json';
|
||||
import DesktopCapturerSource = Electron.DesktopCapturerSource;
|
||||
import { apiName, WindowTypes } from '../common/api-interface';
|
||||
import { isMac, isWindowsOS } from '../common/env';
|
||||
import { i18n } from '../common/i18n';
|
||||
import { getCommandLineArgs, getGuid } from '../common/utils';
|
||||
import { AppMenu } from './app-menu';
|
||||
import { config, IConfig } from './config-handler';
|
||||
@@ -45,6 +46,11 @@ export class WindowHandler {
|
||||
show: false,
|
||||
title: 'Symphony',
|
||||
width: 400,
|
||||
webPreferences: {
|
||||
sandbox: true,
|
||||
nodeIntegration: false,
|
||||
devTools: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -69,6 +75,30 @@ export class WindowHandler {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Screen sharing indicator window opts
|
||||
*/
|
||||
private static getScreenSharingIndicatorOpts(): ICustomBrowserWindowConstructorOpts {
|
||||
return {
|
||||
width: 592,
|
||||
height: 48,
|
||||
show: false,
|
||||
modal: true,
|
||||
frame: false,
|
||||
focusable: false,
|
||||
transparent: true,
|
||||
autoHideMenuBar: true,
|
||||
resizable: false,
|
||||
alwaysOnTop: true,
|
||||
webPreferences: {
|
||||
sandbox: true,
|
||||
nodeIntegration: false,
|
||||
devTools: false,
|
||||
},
|
||||
winKey: getGuid(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies if the url is valid and
|
||||
* forcefully appends https if not present
|
||||
@@ -97,11 +127,12 @@ export class WindowHandler {
|
||||
private readonly windows: object;
|
||||
private readonly isCustomTitleBarAndWindowOS: boolean;
|
||||
|
||||
private mainWindow: ICustomBrowserWindow | null;
|
||||
private loadingWindow: Electron.BrowserWindow | null;
|
||||
private aboutAppWindow: Electron.BrowserWindow | null;
|
||||
private moreInfoWindow: Electron.BrowserWindow | null;
|
||||
private screenPickerWindow: Electron.BrowserWindow | null;
|
||||
private mainWindow: ICustomBrowserWindow | null = null;
|
||||
private loadingWindow: Electron.BrowserWindow | null = null;
|
||||
private aboutAppWindow: Electron.BrowserWindow | null = null;
|
||||
private moreInfoWindow: Electron.BrowserWindow | null = null;
|
||||
private screenPickerWindow: Electron.BrowserWindow | null = null;
|
||||
private screenSharingIndicatorWindow: Electron.BrowserWindow | null = null;
|
||||
|
||||
constructor(opts?: Electron.BrowserViewConstructorOptions) {
|
||||
// Settings
|
||||
@@ -115,12 +146,6 @@ export class WindowHandler {
|
||||
this.isCustomTitleBarAndWindowOS = isWindowsOS && this.config.isCustomTitleBar;
|
||||
|
||||
this.appMenu = null;
|
||||
// Window references
|
||||
this.mainWindow = null;
|
||||
this.loadingWindow = null;
|
||||
this.aboutAppWindow = null;
|
||||
this.moreInfoWindow = null;
|
||||
this.screenPickerWindow = null;
|
||||
|
||||
try {
|
||||
const extra = { podUrl: this.globalConfig.url, process: 'main' };
|
||||
@@ -174,7 +199,7 @@ export class WindowHandler {
|
||||
this.mainWindow.webContents.insertCSS(
|
||||
fs.readFileSync(path.join(__dirname, '..', '/renderer/styles/snack-bar.css'), 'utf8').toString(),
|
||||
);
|
||||
this.mainWindow.webContents.send('page-load', { isWindowsOS });
|
||||
this.mainWindow.webContents.send('page-load', { isWindowsOS, resources: i18n.loadedResources });
|
||||
this.appMenu = new AppMenu();
|
||||
this.monitorWindowActions();
|
||||
// Ready to show the window
|
||||
@@ -211,7 +236,7 @@ export class WindowHandler {
|
||||
*
|
||||
* @param windowType
|
||||
*/
|
||||
public closeWindow(windowType: WindowTypes) {
|
||||
public closeWindow(windowType: WindowTypes): void {
|
||||
switch (windowType) {
|
||||
case 'screen-picker':
|
||||
if (this.screenPickerWindow && !this.screenPickerWindow.isDestroyed()) this.screenPickerWindow.close();
|
||||
@@ -224,7 +249,7 @@ export class WindowHandler {
|
||||
*
|
||||
* @param shouldAutoReload {boolean}
|
||||
*/
|
||||
public setIsAutoReload(shouldAutoReload: boolean) {
|
||||
public setIsAutoReload(shouldAutoReload: boolean): void {
|
||||
this.isAutoReload = shouldAutoReload;
|
||||
}
|
||||
|
||||
@@ -243,7 +268,7 @@ export class WindowHandler {
|
||||
* Displays a loading window until the main
|
||||
* application is loaded
|
||||
*/
|
||||
public showLoadingScreen() {
|
||||
public showLoadingScreen(): void {
|
||||
this.loadingWindow = createComponentWindow('loading-screen', WindowHandler.getLoadingWindowOpts());
|
||||
this.loadingWindow.webContents.once('did-finish-load', () => {
|
||||
if (this.loadingWindow) {
|
||||
@@ -257,7 +282,7 @@ export class WindowHandler {
|
||||
/**
|
||||
* Creates a about app window
|
||||
*/
|
||||
public createAboutAppWindow() {
|
||||
public createAboutAppWindow(): void {
|
||||
this.aboutAppWindow = createComponentWindow('about-app');
|
||||
this.aboutAppWindow.webContents.once('did-finish-load', () => {
|
||||
if (this.aboutAppWindow) {
|
||||
@@ -269,8 +294,8 @@ export class WindowHandler {
|
||||
/**
|
||||
* Creates a more info window
|
||||
*/
|
||||
public createMoreInfoWindow() {
|
||||
this.moreInfoWindow = createComponentWindow('more-info-window');
|
||||
public createMoreInfoWindow(): void {
|
||||
this.moreInfoWindow = createComponentWindow('more-info');
|
||||
this.moreInfoWindow.webContents.once('did-finish-load', () => {
|
||||
if (this.aboutAppWindow) {
|
||||
this.aboutAppWindow.webContents.send('more-info-data');
|
||||
@@ -280,10 +305,14 @@ export class WindowHandler {
|
||||
|
||||
/**
|
||||
* Creates a screen picker window
|
||||
*
|
||||
* @param win
|
||||
* @param sources
|
||||
* @param id
|
||||
*/
|
||||
public createScreenPickerWindow(win: webContents, sources: DesktopCapturerSource[], id: number) {
|
||||
public createScreenPickerWindow(win: Electron.WebContents, sources: DesktopCapturerSource[], id: number): void {
|
||||
const opts = WindowHandler.getScreenPickerWindowOpts();
|
||||
this.screenPickerWindow = createComponentWindow('screen-picker-window', opts);
|
||||
this.screenPickerWindow = createComponentWindow('screen-picker', opts);
|
||||
this.screenPickerWindow.webContents.once('did-finish-load', () => {
|
||||
if (this.screenPickerWindow) {
|
||||
this.screenPickerWindow.webContents.send('screen-picker-data', { sources, id });
|
||||
@@ -300,6 +329,53 @@ export class WindowHandler {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a screen sharing indicator whenever uses start
|
||||
* sharing the screen
|
||||
*
|
||||
* @param screenSharingWebContents {Electron.webContents}
|
||||
* @param displayId {string}
|
||||
* @param id {number}
|
||||
*/
|
||||
public createScreenSharingIndicatorWindow(screenSharingWebContents: Electron.webContents, displayId: string, id: number): void {
|
||||
const indicatorScreen = (displayId && electron.screen.getAllDisplays().filter((d) => displayId.includes(d.id.toString()))[0]) || electron.screen.getPrimaryDisplay();
|
||||
const screenRect = indicatorScreen.workArea;
|
||||
let opts = WindowHandler.getScreenSharingIndicatorOpts();
|
||||
if (opts.width && opts.height) {
|
||||
opts = Object.assign({}, opts, {
|
||||
x: screenRect.x + Math.round((screenRect.width - opts.width) / 2),
|
||||
y: screenRect.y + screenRect.height - opts.height,
|
||||
});
|
||||
}
|
||||
this.screenSharingIndicatorWindow = createComponentWindow('screen-sharing-indicator', opts);
|
||||
this.screenSharingIndicatorWindow.setVisibleOnAllWorkspaces(true);
|
||||
this.screenSharingIndicatorWindow.webContents.once('did-finish-load', () => {
|
||||
if (this.screenSharingIndicatorWindow) {
|
||||
this.screenSharingIndicatorWindow.webContents.send('screen-sharing-indicator-data', { id });
|
||||
|
||||
const stopScreenSharing = (_event, indicatorId) => {
|
||||
if (id === indicatorId) {
|
||||
screenSharingWebContents.send('screen-sharing-stopped', id);
|
||||
}
|
||||
};
|
||||
|
||||
const destroyScreenSharingIndicator = (_event, indicatorId) => {
|
||||
if (id === indicatorId && this.screenSharingIndicatorWindow && !this.screenSharingIndicatorWindow.isDestroyed()) {
|
||||
this.screenSharingIndicatorWindow.close();
|
||||
}
|
||||
};
|
||||
|
||||
this.screenSharingIndicatorWindow.once('close', () => {
|
||||
ipcMain.removeListener('stop-screen-sharing', stopScreenSharing);
|
||||
ipcMain.removeListener('destroy-screen-sharing-indicator', destroyScreenSharingIndicator);
|
||||
});
|
||||
|
||||
ipcMain.once('stop-screen-sharing', stopScreenSharing);
|
||||
ipcMain.once('destroy-screen-sharing-indicator', destroyScreenSharingIndicator);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens an external url in the system's default browser
|
||||
*
|
||||
|
||||
@@ -46,15 +46,14 @@ export const preventWindowNavigation = (browserWindow: Electron.BrowserWindow, i
|
||||
*/
|
||||
export const createComponentWindow = (
|
||||
componentName: string,
|
||||
opts?: Electron.BrowserWindowConstructorOptions): BrowserWindow => {
|
||||
opts?: Electron.BrowserWindowConstructorOptions,
|
||||
): BrowserWindow => {
|
||||
|
||||
const parent = windowHandler.getMainWindow() || undefined;
|
||||
const options = {
|
||||
const options: Electron.BrowserWindowConstructorOptions = {
|
||||
center: true,
|
||||
height: 300,
|
||||
maximizable: false,
|
||||
minimizable: false,
|
||||
parent,
|
||||
resizable: false,
|
||||
show: false,
|
||||
width: 300,
|
||||
@@ -71,7 +70,10 @@ export const createComponentWindow = (
|
||||
const targetUrl = url.format({
|
||||
pathname: require.resolve('../renderer/react-window.html'),
|
||||
protocol: 'file',
|
||||
query: { componentName },
|
||||
query: {
|
||||
componentName,
|
||||
locale: i18n.getLocale(),
|
||||
},
|
||||
slashes: true,
|
||||
});
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ export enum apiCmds {
|
||||
openScreenSnippet = 'open-screen-snippet',
|
||||
keyPress = 'key-press',
|
||||
closeWindow = 'close-window',
|
||||
openScreenSharingIndicator = 'open-screen-sharing-indicator',
|
||||
}
|
||||
|
||||
export enum apiName {
|
||||
@@ -41,6 +42,7 @@ export interface IApiArgs {
|
||||
locale: string;
|
||||
keyCode: number;
|
||||
windowType: WindowTypes;
|
||||
displayId: string;
|
||||
}
|
||||
|
||||
export type WindowTypes = 'screen-picker';
|
||||
@@ -70,6 +72,14 @@ export interface IBoundsChange extends Electron.Rectangle {
|
||||
windowName: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Screen sharing indicator
|
||||
*/
|
||||
export interface IScreenSharingIndicator {
|
||||
type: string;
|
||||
reason?: string;
|
||||
}
|
||||
|
||||
export enum KeyCodes {
|
||||
Esc = 27,
|
||||
Alt = 18,
|
||||
|
||||
86
src/common/i18n-preload.ts
Normal file
86
src/common/i18n-preload.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { formatString } from './format-string';
|
||||
|
||||
const localeCodeRegex = /^([a-z]{2})-([A-Z]{2})$/;
|
||||
|
||||
export type LocaleType = 'en-US' | 'ja-JP';
|
||||
|
||||
type formaterFunction = (...args: any[]) => string;
|
||||
|
||||
class Translation {
|
||||
|
||||
/**
|
||||
* Returns translated string with respect to value, resource & name space
|
||||
*
|
||||
* @param value {string} key field in the resources
|
||||
* @param resource {string} current locale resource
|
||||
* @param namespace {string} name space in the resource
|
||||
*/
|
||||
private static translate(value: string, resource: JSON | null, namespace: string | undefined): string {
|
||||
return resource ? Translation.getResource(resource, namespace)[value] : null;
|
||||
}
|
||||
private static getResource = (resource: JSON, namespace: string | undefined): JSON => namespace ? resource[namespace] : resource;
|
||||
private locale: LocaleType = 'en-US';
|
||||
private loadedResources: object = {};
|
||||
|
||||
/**
|
||||
* Apply the locale for translation
|
||||
*
|
||||
* @param locale
|
||||
*/
|
||||
public setLocale(locale: LocaleType): void {
|
||||
const localeMatch: string[] | null = locale.match(localeCodeRegex);
|
||||
if (!locale && (!localeMatch || localeMatch.length < 1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.locale = locale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current locale
|
||||
*/
|
||||
public getLocale(): LocaleType {
|
||||
return this.locale;
|
||||
}
|
||||
|
||||
/**
|
||||
* fetches and returns the translated value
|
||||
*
|
||||
* @param value {string}
|
||||
* @param namespace {string}
|
||||
* @example t('translate and formats {data} ', namespace)({ data: 'string' })
|
||||
* @returns translate and formats string
|
||||
*/
|
||||
public t(value: string, namespace?: string): formaterFunction {
|
||||
return (...args: any[]): string => {
|
||||
if (this.loadedResources && this.loadedResources[this.locale]) {
|
||||
return formatString(Translation.translate(value, this.loadedResources[this.locale], namespace), args);
|
||||
}
|
||||
const resource = this.loadResource(this.locale);
|
||||
return formatString(Translation.translate(value, resource, namespace) || value, args);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Keeps ref of loaded resources from the main process
|
||||
*
|
||||
* @param resource
|
||||
*/
|
||||
public setResource(resource: JSON): void {
|
||||
this.loadedResources = resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the resources dir and returns the data
|
||||
*
|
||||
* @param locale
|
||||
*/
|
||||
private loadResource(locale: LocaleType): JSON | null {
|
||||
return this.loadedResources[locale];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const i18n = new Translation();
|
||||
|
||||
export { i18n };
|
||||
@@ -21,8 +21,8 @@ class Translation {
|
||||
return resource ? Translation.getResource(resource, namespace)[value] : null;
|
||||
}
|
||||
private static getResource = (resource: JSON, namespace: string | undefined): JSON => namespace ? resource[namespace] : resource;
|
||||
public loadedResources: object = {};
|
||||
private locale: LocaleType = 'en-US';
|
||||
private loadedResource: object = {};
|
||||
|
||||
/**
|
||||
* Apply the locale for translation
|
||||
@@ -55,8 +55,8 @@ class Translation {
|
||||
*/
|
||||
public t(value: string, namespace?: string): formaterFunction {
|
||||
return (...args: any[]): string => {
|
||||
if (this.loadedResource && this.loadedResource[this.locale]) {
|
||||
return formatString(Translation.translate(value, this.loadedResource[this.locale], namespace), args);
|
||||
if (this.loadedResources && this.loadedResources[this.locale]) {
|
||||
return formatString(Translation.translate(value, this.loadedResources[this.locale], namespace), args);
|
||||
}
|
||||
const resource = this.loadResource(this.locale);
|
||||
return formatString(Translation.translate(value, resource, namespace) || value, args);
|
||||
@@ -68,14 +68,14 @@ class Translation {
|
||||
*
|
||||
* @param locale
|
||||
*/
|
||||
public loadResource(locale: LocaleType): JSON | null {
|
||||
private loadResource(locale: LocaleType): JSON | null {
|
||||
const resourcePath = path.resolve(__dirname, '..', 'locale', `${locale}.json`);
|
||||
|
||||
if (!fs.existsSync(resourcePath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.loadedResource[this.locale] = require(resourcePath);
|
||||
return this.loadedResources[this.locale] = require(resourcePath);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -110,6 +110,11 @@
|
||||
"Select Screen": "Select Screen",
|
||||
"Share": "Share"
|
||||
},
|
||||
"ScreenSharingIndicator": {
|
||||
"You are sharing your screen on ": "You are sharing your screen on ",
|
||||
"Stop sharing": "Stop sharing",
|
||||
"Hide": "Hide"
|
||||
},
|
||||
"ScreenSnippet": {
|
||||
"Done": "Done",
|
||||
"Erase": "Erase",
|
||||
|
||||
86
src/renderer/components/screen-sharing-indicator.tsx
Normal file
86
src/renderer/components/screen-sharing-indicator.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
import classNames from 'classnames';
|
||||
import { ipcRenderer, remote } from 'electron';
|
||||
import * as React from 'react';
|
||||
|
||||
import { isMac } from '../../common/env';
|
||||
import { i18n } from '../../common/i18n';
|
||||
|
||||
interface IState {
|
||||
id: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Window that display a banner when the users starting sharing screen
|
||||
*/
|
||||
export default class ScreenSharingIndicator extends React.Component<{}, IState> {
|
||||
|
||||
private readonly eventHandlers = {
|
||||
onStopScreenSharing: (id) => this.stopScreenShare(id),
|
||||
onClose: (id) => this.close(id),
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
id: 0,
|
||||
};
|
||||
this.updateState = this.updateState.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* main render function
|
||||
*/
|
||||
public render(): JSX.Element {
|
||||
const { id } = this.state;
|
||||
const namespace = 'ScreenSharingIndicator';
|
||||
return (
|
||||
<div className={classNames('ScreenSharingIndicator', { mac: isMac })}>
|
||||
<span className='drag-area'/>
|
||||
<span className='text-label'>{i18n.t(`You are sharing your screen on `, namespace)()}</span>
|
||||
<span className='text-label'><b>{remote.app.getName()}</b></span>
|
||||
<span className='buttons'>
|
||||
<a className='hide-button' href='#' onClick={() => this.eventHandlers.onClose(id)}>{i18n.t('Hide', namespace)()}</a>
|
||||
<button className='stop-sharing-button' onClick={() => this.eventHandlers.onStopScreenSharing(id)}>
|
||||
{i18n.t('Stop sharing', namespace)()}
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
public componentDidMount(): void {
|
||||
ipcRenderer.on('screen-sharing-indicator-data', this.updateState);
|
||||
}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
ipcRenderer.removeListener('screen-sharing-indicator-data', this.updateState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops sharing screen
|
||||
*
|
||||
* @param id
|
||||
*/
|
||||
private stopScreenShare(id: number): void {
|
||||
ipcRenderer.send('stop-screen-sharing', id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the screen sharing indicator window
|
||||
*
|
||||
* @param id
|
||||
*/
|
||||
private close(id): void {
|
||||
ipcRenderer.send('destroy-screen-sharing-indicator', id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the About app state
|
||||
*
|
||||
* @param _event
|
||||
* @param data {Object} { buildNumber, clientVersion, version }
|
||||
*/
|
||||
private updateState(_event, data): void {
|
||||
this.setState(data as IState);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import { ipcRenderer } from 'electron';
|
||||
import * as React from 'react';
|
||||
|
||||
import Timer = NodeJS.Timer;
|
||||
import { i18n } from '../../common/i18n';
|
||||
import { i18n } from '../../common/i18n-preload';
|
||||
|
||||
interface IState {
|
||||
show: boolean;
|
||||
|
||||
@@ -2,7 +2,7 @@ import { ipcRenderer, remote } from 'electron';
|
||||
import * as React from 'react';
|
||||
|
||||
import { apiCmds, apiName } from '../../common/api-interface';
|
||||
import { i18n } from '../../common/i18n';
|
||||
import { i18n } from '../../common/i18n-preload';
|
||||
|
||||
interface IState {
|
||||
isMaximized: boolean;
|
||||
|
||||
@@ -1,10 +1,28 @@
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
|
||||
import { i18n, LocaleType } from '../common/i18n';
|
||||
import AboutBox from './components/about-app';
|
||||
import LoadingScreen from './components/loading-screen';
|
||||
import MoreInfo from './components/more-info';
|
||||
import ScreenPicker from './components/screen-picker';
|
||||
import ScreenSharingIndicator from './components/screen-sharing-indicator';
|
||||
|
||||
const enum components {
|
||||
aboutApp = 'about-app',
|
||||
loadingScreen = 'loading-screen',
|
||||
moreInfo = 'more-info',
|
||||
screenPicker = 'screen-picker',
|
||||
screenSharingIndicator = 'screen-sharing-indicator',
|
||||
}
|
||||
|
||||
const loadStyle = (style) => {
|
||||
const styles = document.createElement('link');
|
||||
styles.rel = 'stylesheet';
|
||||
styles.type = 'text/css';
|
||||
styles.href = `./styles/${style}.css`;
|
||||
document.getElementsByTagName('head')[0].appendChild(styles);
|
||||
};
|
||||
|
||||
/**
|
||||
* Loads the appropriate component
|
||||
@@ -12,21 +30,31 @@ import ScreenPicker from './components/screen-picker';
|
||||
const load = () => {
|
||||
const query = new URL(window.location.href).searchParams;
|
||||
const componentName = query.get('componentName');
|
||||
const locale = query.get('locale');
|
||||
i18n.setLocale(locale as LocaleType);
|
||||
|
||||
let component;
|
||||
switch (componentName) {
|
||||
case 'about-app':
|
||||
case components.aboutApp:
|
||||
loadStyle(components.aboutApp);
|
||||
component = AboutBox;
|
||||
break;
|
||||
case 'loading-screen':
|
||||
case components.loadingScreen:
|
||||
loadStyle(components.loadingScreen);
|
||||
component = LoadingScreen;
|
||||
break;
|
||||
case 'more-info-window':
|
||||
case components.moreInfo:
|
||||
loadStyle(components.moreInfo);
|
||||
component = MoreInfo;
|
||||
break;
|
||||
case 'screen-picker-window':
|
||||
case components.screenPicker:
|
||||
loadStyle(components.screenPicker);
|
||||
component = ScreenPicker;
|
||||
break;
|
||||
case components.screenSharingIndicator:
|
||||
loadStyle(components.screenSharingIndicator);
|
||||
component = ScreenSharingIndicator;
|
||||
break;
|
||||
}
|
||||
const element = React.createElement(component);
|
||||
ReactDOM.render(element, document.getElementById('Root'));
|
||||
|
||||
@@ -2,6 +2,7 @@ import { ipcRenderer } from 'electron';
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
|
||||
import { i18n } from '../common/i18n-preload';
|
||||
import SnackBar from './components/snack-bar';
|
||||
import WindowsTitleBar from './components/windows-title-bar';
|
||||
import { SSFApi } from './ssf-api';
|
||||
@@ -37,7 +38,10 @@ const createAPI = () => {
|
||||
createAPI();
|
||||
|
||||
// When the window is completely loaded
|
||||
ipcRenderer.on('page-load', (_event, { isWindowsOS }) => {
|
||||
ipcRenderer.on('page-load', (_event, { isWindowsOS, resources }) => {
|
||||
|
||||
i18n.setResource(resources);
|
||||
|
||||
if (isWindowsOS) {
|
||||
// injects custom window title bar
|
||||
const titleBar = React.createElement(WindowsTitleBar);
|
||||
|
||||
@@ -17,10 +17,8 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="./styles/main.css" />
|
||||
</head>
|
||||
<body style="overflow: hidden; background-color: white; margin: 0; height: 100%">
|
||||
<body>
|
||||
<div id="Root"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -6,21 +6,23 @@ import {
|
||||
apiName,
|
||||
IActivityDetection,
|
||||
IBadgeCount,
|
||||
IBoundsChange,
|
||||
IBoundsChange, IScreenSharingIndicator,
|
||||
IScreenSnippet, KeyCodes,
|
||||
} from '../common/api-interface';
|
||||
import { i18n, LocaleType } from '../common/i18n';
|
||||
import { i18n, LocaleType } from '../common/i18n-preload';
|
||||
import { throttle } from '../common/throttle';
|
||||
import { getSource } from './desktop-capturer';
|
||||
|
||||
let isAltKey = false;
|
||||
let isMenuOpen = false;
|
||||
let isAltKey: boolean = false;
|
||||
let isMenuOpen: boolean = false;
|
||||
let nextId = 0;
|
||||
|
||||
interface ILocalObject {
|
||||
ipcRenderer;
|
||||
activityDetectionCallback?: (arg: IActivityDetection) => void;
|
||||
screenSnippetCallback?: (arg: IScreenSnippet) => void;
|
||||
boundsChangeCallback?: (arg: IBoundsChange) => void;
|
||||
screenSharingIndicatorCallback?: (arg: IScreenSharingIndicator) => void;
|
||||
}
|
||||
|
||||
const local: ILocalObject = {
|
||||
@@ -141,6 +143,41 @@ export class SSFApi {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a banner that informs user that the screen is being shared.
|
||||
*
|
||||
* @param options object with following fields:
|
||||
* - stream https://developer.mozilla.org/en-US/docs/Web/API/MediaStream/MediaStream object.
|
||||
* The indicator automatically destroys itself when stream becomes inactive (see MediaStream.active).
|
||||
* - displayId id of the display that is being shared or that contains the shared app
|
||||
* @param callback callback function that will be called to handle events.
|
||||
* Callback receives event object { type: string }. Types:
|
||||
* - 'error' - error occured. Event object contains 'reason' field.
|
||||
* - 'stopRequested' - user clicked "Stop Sharing" button.
|
||||
*/
|
||||
public showScreenSharingIndicator(options, callback): void {
|
||||
const { stream, displayId } = options;
|
||||
|
||||
if (typeof callback === 'function') {
|
||||
if (!stream || !stream.active || stream.getVideoTracks().length !== 1) {
|
||||
callback({ type: 'error', reason: 'bad stream' });
|
||||
return;
|
||||
}
|
||||
if (displayId && typeof (displayId) !== 'string') {
|
||||
callback({ type: 'error', reason: 'bad displayId' });
|
||||
return;
|
||||
}
|
||||
|
||||
local.screenSharingIndicatorCallback = callback;
|
||||
const id = ++nextId;
|
||||
ipcRenderer.send(apiName.symphonyApi, {
|
||||
cmd: apiCmds.openScreenSharingIndicator,
|
||||
displayId: options.displayId,
|
||||
id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -213,6 +250,12 @@ local.ipcRenderer.on('boundsChange', (_event, arg: IBoundsChange): void => {
|
||||
}
|
||||
});
|
||||
|
||||
local.ipcRenderer.on('screen-sharing-stopped', () => {
|
||||
if (typeof local.screenSharingIndicatorCallback === 'function') {
|
||||
local.screenSharingIndicatorCallback({ type: 'stopRequested' });
|
||||
}
|
||||
});
|
||||
|
||||
// Invoked whenever the app is reloaded/navigated
|
||||
const sanitize = (): void => {
|
||||
if (window.name === apiName.mainWindowName) {
|
||||
|
||||
@@ -4,6 +4,13 @@
|
||||
@font-family: sans-serif;
|
||||
@text-padding: 10px;
|
||||
|
||||
body {
|
||||
overflow: hidden;
|
||||
background-color: white;
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.AboutApp {
|
||||
text-align: center;
|
||||
display: flex;
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
@font-family: sans-serif;
|
||||
@text-padding: 10px;
|
||||
|
||||
body {
|
||||
overflow: hidden;
|
||||
background-color: white;
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.LoadingScreen {
|
||||
margin: 0;
|
||||
height: 200px;
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
@import "about-app";
|
||||
@import "loading-screen";
|
||||
@import "screen-picker";
|
||||
|
||||
html, body {
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
font-family: @font-family;
|
||||
}
|
||||
@@ -20,6 +20,12 @@
|
||||
src: local(".SFNSText-Light"), local(".HelveticaNeueDeskInterface-Light"), local(".LucidaGrandeUI"), local("Ubuntu Light"), local("Segoe UI Light"), local("Roboto-Light"), local("DroidSans"), local("Tahoma");
|
||||
}
|
||||
|
||||
body {
|
||||
overflow: hidden;
|
||||
background-color: white;
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
.ScreenPicker {
|
||||
|
||||
86
src/renderer/styles/screen-sharing-indicator.less
Normal file
86
src/renderer/styles/screen-sharing-indicator.less
Normal file
@@ -0,0 +1,86 @@
|
||||
@white: rgba(255,255,255, 1);
|
||||
@background: #6b717c;
|
||||
@button-color: #303237;
|
||||
@stop-sharing-background: #107ccc;
|
||||
@font: "system";
|
||||
|
||||
@font-face {
|
||||
font-family: system;
|
||||
font-style: normal;
|
||||
src: local(".SFNSText"), local(".HelveticaNeueDeskInterface"), local("Ubuntu Light"), local("Segoe UI"), local("Roboto"), local("Tahoma");
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: @font;
|
||||
font-size: 13px;
|
||||
padding: 0;
|
||||
margin: 10px;
|
||||
user-select: none;
|
||||
-webkit-app-region: drag;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
.ScreenSharingIndicator {
|
||||
|
||||
.window-border {
|
||||
border: 2px solid rgba(68, 68, 68, 1);
|
||||
}
|
||||
|
||||
.buttons {
|
||||
float: right;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.stop-sharing-button {
|
||||
text-transform: uppercase;
|
||||
background: @stop-sharing-background;
|
||||
color: @white;
|
||||
border: none;
|
||||
font-size: inherit;
|
||||
border-radius: 14px;
|
||||
height: 28px;
|
||||
padding: 6px 14px;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
.hide-button {
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
color: @background;
|
||||
margin-right: 29px;
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
.text-label {
|
||||
position: relative;
|
||||
top: -2px;
|
||||
left: 16px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.drag-area {
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 22px;
|
||||
position: relative;
|
||||
cursor: move;
|
||||
top: 3px;
|
||||
text-transform: uppercase;
|
||||
background: repeating-linear-gradient(0deg, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0) 1px, rgba(0, 0, 0, 0.12) 1px, rgba(0, 0, 0, 0.12) 2px);
|
||||
}
|
||||
|
||||
.mac {
|
||||
.hide-button {
|
||||
color: @button-color;
|
||||
}
|
||||
|
||||
.stop-sharing-button {
|
||||
padding: 6px 18px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user