Typescript - Completed screen sharing indicator and fixed i18n usage issue

This commit is contained in:
Kiran Niranjan
2019-01-02 15:49:50 +05:30
parent 196ee2c0fe
commit c58678eeb2
22 changed files with 514 additions and 57 deletions

View File

@@ -237,7 +237,7 @@
}
}, stream => {
handleStream(stream, source.display_id);
}, handleError)
}, handleError);
});
});

12
gulpfile.js Normal file
View 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')));
});

View File

@@ -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",

View File

@@ -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:
}

View File

@@ -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
*

View File

@@ -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,
});

View File

@@ -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,

View 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 };

View File

@@ -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);
}
}

View File

@@ -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",

View 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);
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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'));

View File

@@ -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);

View File

@@ -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>

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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;

View File

@@ -1,9 +0,0 @@
@import "about-app";
@import "loading-screen";
@import "screen-picker";
html, body {
margin: 0;
height: 100%;
font-family: @font-family;
}

View File

@@ -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 {

View 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;
}
}
}