Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[NEW] Support for MacBooks Touch Bar #1044

Merged
merged 3 commits into from
Dec 18, 2018
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/background/mainWindow.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ const attachWindowStateHandling = (mainWindow) => {
});

const close = () => {
mainWindow.blur();

if (process.platform === 'darwin' || state.hideOnClose) {
mainWindow.hide();
} else if (process.platform === 'win32') {
Expand Down
2 changes: 2 additions & 0 deletions src/i18n/lang/en.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"Enter_your_server_URL": "Enter your server URL",
"Error": "Error",
"&File": "&File",
"Formatting": "Formatting",
"&Forward": "&Forward",
"Full screen": "Full screen",
"&Help": "&Help",
Expand Down Expand Up @@ -74,6 +75,7 @@
"Save_Image": "Save Image",
"Select_a_screen_to_share": "Select a screen to share",
"Select &all": "Select &all",
"Select_server": "Select server",
"Server_Failed_to_Load": "Server Failed to Load",
"Server list": "Server list",
"Share_Your_Screen": "Share Your Screen",
Expand Down
2 changes: 2 additions & 0 deletions src/i18n/lang/ru.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"Enter_your_server_URL": "Введите URL Вашего сервера",
"Error": "Ошибка",
"&File": "&Файл",
"Formatting": "Форматирование",
"&Forward": "Вперед",
"Full screen": "Полноэкранный режим",
"&Help": "&Справка",
Expand Down Expand Up @@ -73,6 +74,7 @@
"Reset zoom": "Восстановить масштаб",
"Select_a_screen_to_share": "Выберите экран для совместного использования",
"Select &all": "Выделить все",
"Select_server": "Выбрать сервер",
"Server_Failed_to_Load": "Не удалось загрузить сервер",
"Server list": "Список серверов",
"Share_Your_Screen": "Демонстрация Вашего экрана",
Expand Down
Binary file added src/public/images/icon-bold.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/public/images/icon-code.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/public/images/icon-italic.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/public/images/icon-multi-line.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/public/images/icon-strike.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 12 additions & 2 deletions src/scripts/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { remote } from 'electron';
import servers from './servers';
import sidebar from './sidebar';
import webview from './webview';
import { TouchBarBuilder, SelectServerPanel, FormattingPanel } from './touchBar';


const { app, getCurrentWindow, shell } = remote;
Expand Down Expand Up @@ -173,8 +174,7 @@ export default () => {
webview.on('ipc-message-unread-changed', (hostUrl, [count]) => {
if (typeof count === 'number' && localStorage.getItem('showWindowOnUnreadChanged') === 'true') {
const mainWindow = remote.getCurrentWindow();
const isNeededToShow = !mainWindow.isFocused() || (mainWindow.isFocused() && !mainWindow.isVisible());
if (isNeededToShow) {
if (!mainWindow.isFocused()) {
mainWindow.once('focus', () => mainWindow.flashFrame(false));
mainWindow.showInactive();
mainWindow.flashFrame(true);
Expand All @@ -187,6 +187,16 @@ export default () => {
dock.setState({ status });
});

if (process.platform === 'darwin') {
servers.once('active-setted', () => {
const touchBar = new TouchBarBuilder()
.addSelectServerPanel(new SelectServerPanel())
.addFormattingPanel(new FormattingPanel())
.build();
getCurrentWindow().setTouchBar(touchBar);
abicur marked this conversation as resolved.
Show resolved Hide resolved
});
}


servers.restoreActive();
updatePreferences();
Expand Down
224 changes: 224 additions & 0 deletions src/scripts/touchBar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
import { remote } from 'electron';
import { EventEmitter } from 'events';
import servers from './servers';
import webview from './webview';
import i18n from '../i18n/index.js';

const { TouchBar, nativeImage } = remote.require('electron');
abicur marked this conversation as resolved.
Show resolved Hide resolved
const { TouchBarButton, TouchBarLabel, TouchBarSegmentedControl, TouchBarScrubber, TouchBarPopover, TouchBarGroup } = TouchBar;

export class SelectServerPanel extends EventEmitter {
abicur marked this conversation as resolved.
Show resolved Hide resolved

constructor() {
super();

this._MAX_LENGTH_FOR_SEGMENTS_CONTROL = 76 - i18n.__('Select_server').length;
this._patternRemoveSchema = new RegExp('^http(s?):\/\/');
this._hosts = [];

this._setHostsArray();
this._subscribe();
}

_isSegmentedControl() {
return this.control && this.control.hasOwnProperty('selectedIndex');
}

_getActiveServerIndex() {
return this._hosts.findIndex((value) => value.host === servers.active);
}

_setActiveServer() {
if (this._isSegmentedControl()) {
this.control.selectedIndex = this._getActiveServerIndex();
} else {
this._update();
}
}

_setHostsArray() {
this._hosts = Object.keys(servers.hosts).map((key) => ({ label: key.replace(this._patternRemoveSchema, ''), host: key }));
abicur marked this conversation as resolved.
Show resolved Hide resolved
this._hosts = this._trimHostsNames(this._hosts);
}

_getTotalLengthOfHostsNames() {
return this._hosts.reduce((acc, host) => acc + host.label.length, 0);
}

_update() {
this._setHostsArray();
if (this.control) {
if (this._isSegmentedControl()) {
this.control.segments = this._hosts;
} else {
this.control.items = this._hosts;
}
} else {
this.build();
}
}

build() {
const popoverItems = this._buildSelectServersPopoverItems();

this.touchBarPopover = new TouchBarPopover({
label: i18n.__('Select_server'),
items: new TouchBar({
items: popoverItems,
}),
});
return this.touchBarPopover;
}

_buildSelectServersPopoverItems() {
const items = [
new TouchBarLabel({ label: i18n.__('Select_server') }),
];

// The maximum length of available display area is limited. If exceed the length of displayed data, then
// touchbar element is not displayed. If the length of displayed host names exceeds the limit, then
// the touchBarScrubber is used. In other case SegmentedControl is used.
const hostsNamesLength = this._getTotalLengthOfHostsNames();

if (this._hosts.length) {
if (hostsNamesLength <= this._MAX_LENGTH_FOR_SEGMENTS_CONTROL) {
items.push(this._buildTouchBarSegmentedControl());
} else {
items.push(this._buildTouchBarScrubber());
}
}
return items;
}

_buildTouchBarSegmentedControl() {
this.control = new TouchBarSegmentedControl({
segmentStyle: 'separated',
selectedIndex: this._getActiveServerIndex(),
segments: this._hosts,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about mapping the hosts with their respective favicons?

Copy link
Contributor Author

@abicur abicur Dec 16, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I understand, TouchBarScrubber and TouchBarSegmentedControl allow you to display either an icon or a label. In my experience, the default icon often does not change. I thought it was therefore better to display the label.

If we want to display both Icon and label, then may be we must use a group of buttons (TouchBarButton can display both icon and label) but the buttons are not related to each other. Therefore, we need more logic to color the selected server. I can look into it.

change: (index) => {
servers.setActive(this._hosts[index].host);
},
});
return this.control;
}

_buildTouchBarScrubber() {
this.control = new TouchBarScrubber({
selectedStyle: 'background',
showArrowButtons: true,
mode: 'fixed',
items: this._hosts,
highlight: (index) => {
servers.setActive(this._hosts[index].host);
},
});
return this.control;
}

_subscribe() {
servers.on('active-setted', () => this._setActiveServer());
servers.on('host-added', () => this._update());
servers.on('host-removed', () => this._update());
}

/**
* If it is possible to fit the hosts names to the specific limit, then trim the hosts names to the format "open.rocke.."
* @param arr {Array} array of hosts
* @returns {Array} array of hosts
*/
_trimHostsNames(arr) {
const hostsNamesLength = this._getTotalLengthOfHostsNames();

if (hostsNamesLength <= this._MAX_LENGTH_FOR_SEGMENTS_CONTROL) {
return arr;
}

// The total length of hosts names with reserved space for '..' characters
const amountOfCharsToDisplay = this._MAX_LENGTH_FOR_SEGMENTS_CONTROL - 2 * arr.length;
const amountOfCharsPerHost = Math.floor(amountOfCharsToDisplay / arr.length);

if (amountOfCharsPerHost > 0) {
let additionChars = amountOfCharsToDisplay % arr.length;
return arr.map((host) => {
if (amountOfCharsPerHost < host.label.length) {
let additionChar = 0;
if (additionChars) {
additionChar = 1;
additionChars--;
}
host.label = `${ host.label.slice(0, amountOfCharsPerHost + additionChar) }..`;
}
return host;
});
}
return arr;
}
}

export class FormattingPanel extends EventEmitter {
abicur marked this conversation as resolved.
Show resolved Hide resolved

constructor() {
super();
this._buttonClasses = ['bold', 'italic', 'strike', 'code', 'multi-line'];
this._BACKGROUND_COLOR = '#A4A4A4';
}

build() {
const formatButtons = [];

this._buttonClasses.forEach((buttonClass) => {
const touchBarButton = new TouchBarButton({
backgroundColor: this._BACKGROUND_COLOR,
icon: nativeImage.createFromPath(`${ __dirname }/images/icon-${ buttonClass }.png`),
click: () => {
webview.getActive().executeJavaScript(`
var svg = document.querySelector("button svg[class$='${ buttonClass }']");
svg && svg.parentNode.click();
`.trim());
},
});
formatButtons.push(touchBarButton);
});

this._touchBarGroup = new TouchBarGroup({
items: [
new TouchBarLabel({ label: i18n.__('Formatting') }),
...formatButtons,
],
});
return this._touchBarGroup;
}
}

export class TouchBarBuilder extends EventEmitter {
abicur marked this conversation as resolved.
Show resolved Hide resolved

constructor() {
super();
this._touchBarElements = {};
}

build() {
this._touchBar = new TouchBar({
items: Object.values(this._touchBarElements).map((element) => element.build()),
});
return this._touchBar;
}

addSelectServerPanel(panel) {
if (this._isPanel(panel)) {
this._touchBarElements.selectServerPanel = panel;
}
return this;
}

addFormattingPanel(panel) {
if (this._isPanel(panel)) {
this._touchBarElements.formattingtPanel = panel;
}
return this;
}

_isPanel(panel) {
return panel && typeof panel.build === 'function';
}
}