Skip to content

Commit

Permalink
[IMPROVE] New tray icons (#1093)
Browse files Browse the repository at this point in the history
* Add new tray icons for MacOS

* Add new tray icons for Linux

* Add new tray icons for Windows

* Remove option to show user status in tray icon

* Remove old icon module

* Don't use gulp to update icons

* Optimize tray icons

* Refactor icon module

* Build app icons

* Refactor and test icon module

* Fix Windows icons
  • Loading branch information
tassoevan authored Feb 20, 2019
1 parent bc4d160 commit 37382ca
Show file tree
Hide file tree
Showing 106 changed files with 2,228 additions and 250 deletions.
Binary file modified build/appx/Square150x150Logo.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 modified build/appx/Square44x44Logo.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 modified build/appx/StoreLogo.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 modified build/appx/Wide310x150Logo.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 modified build/icon.icns
Binary file not shown.
Binary file modified build/icon.ico
Binary file not shown.
Binary file modified build/icons/512x512.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 modified build/installerIcon.ico
Binary file not shown.
Binary file modified build/uninstallerIcon.ico
Binary file not shown.
Binary file removed build/win/setup-banner.bmp
Binary file not shown.
Binary file removed build/win/setup-icon.ico
Binary file not shown.
2 changes: 0 additions & 2 deletions gulpfile.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
'use strict';

require('./tasks/build-app');
require('./tasks/build-tests');
require('./tasks/release');
Expand Down
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"postremove": "electron-builder install-app-deps",
"start": "gulp start",
"build": "gulp build-app",
"build:icons": "node tasks/build-icons.js",
"changelog": "conventional-changelog --config .github/changelog.js -i HISTORY.md -s",
"release": "gulp release --env=production",
"release-dev": "gulp release --env=development",
Expand All @@ -43,10 +44,12 @@
"tmp": "^0.0.33"
},
"devDependencies": {
"@fiahfy/icns-convert": "^0.0.5",
"@rocket.chat/eslint-config": "^0.1.2",
"builtin-modules": "^3.0.0",
"chai": "^4.2.0",
"conventional-changelog-cli": "^2.0.11",
"convert-svg-to-png": "^0.5.0",
"electron": "^4.0.1",
"electron-builder": "^20.38.4",
"electron-mocha": "^6.0.4",
Expand All @@ -71,6 +74,7 @@
"run-sequence": "^2.2.1",
"sinon": "^7.2.2",
"spectron": "^5.0.0",
"to-ico": "^1.1.5",
"xvfb-maybe": "^0.2.1"
},
"devEngines": {
Expand Down
31 changes: 6 additions & 25 deletions src/background/dock.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import { app } from 'electron';
import { EventEmitter } from 'events';
import { getMainWindow } from './mainWindow';
import icon from './icon';
import { getTrayIconImage, getAppIconImage } from './icon';


const getBadgeText = ({ badge: { title, count } }) => {
if (title === '•') {
return '•';
} else if (count > 0) {
return count > 9 ? '9+' : String(count);
} else if (title) {
return '!';
return String(count);
}
};

Expand All @@ -19,7 +17,7 @@ let state = {
title: '',
count: 0,
},
status: 'online',
hasTrayIcon: false,
};

const instance = new (class Dock extends EventEmitter {});
Expand All @@ -32,33 +30,16 @@ const update = async(previousState) => {
const mainWindow = await getMainWindow();
const badgeText = getBadgeText(state);

if (process.platform === 'win32') {
const image = badgeText ? await icon.render({
overlay: true,
size: 16,
badgeText,
}) : null;
mainWindow.setOverlayIcon(image, badgeText || '');

mainWindow.removeListener('show', update);
mainWindow.on('show', update);
}

if (process.platform === 'darwin') {
app.dock.setBadge(badgeText || '');
if (state.badge.count > 0 && previousState.badge.count === 0) {
app.dock.bounce();
}
}

if (process.platform === 'linux') {
mainWindow.setIcon(await icon.render({
badgeText,
size: {
win32: [256, 128, 64, 48, 32, 24, 16],
linux: 128,
}[process.platform],
}));
if (process.platform === 'linux' || process.platform === 'win32') {
const image = state.hasTrayIcon ? getAppIconImage() : getTrayIconImage(state.badge);
mainWindow.setIcon(image);
}

if (!mainWindow.isFocused()) {
Expand Down
162 changes: 56 additions & 106 deletions src/background/icon.js
Original file line number Diff line number Diff line change
@@ -1,121 +1,71 @@
import { BrowserWindow, nativeImage } from 'electron';
import jetpack from 'fs-jetpack';
import { whenReadyToShow } from './utils';
import { nativeImage, systemPreferences } from 'electron';

let rendererWindow = null;

const getRendererWindow = async() => {
if (!rendererWindow) {
rendererWindow = new BrowserWindow({ show: false });

const dataURL = `data:text/html,<!doctype html>
${ jetpack.read(`${ __dirname }/public/images/icon.svg`) }`;

rendererWindow.loadURL(dataURL);
await whenReadyToShow(rendererWindow);
function getTrayIconSet({ platform, dark }) {
if (platform === 'darwin') {
return `darwin${ dark ? '-dark' : '' }`;
}

return rendererWindow;
};

/* istanbul ignore next */
const renderInWindow = async(style) => {
const statusColors = {
offline: null,
away: 'yellow',
busy: 'red',
online: 'lime',
};

const create = ({ overlay, template, status, badgeText } = {}) => {
const svg = document.querySelector('#icon').cloneNode(true);

svg.querySelector('.logo .baloon').style.fill = template ? '#FFFFFF' : '#DB2323';
svg.querySelector('.logo .circles').style.fill = template ? '#FFFFFF' : '#DB2323';
svg.querySelector('.status .away').style.fill = template ? '#FFFFFF' : '#DB2323';
svg.querySelector('.status .busy').style.fill = template ? '#FFFFFF' : '#DB2323';

svg.querySelector('.logo .bubble').style.display = template ? 'none' : null;

svg.querySelector('.badge').style.display = (!template && badgeText) ? null : 'none';
svg.querySelector('.badge text').innerHTML = badgeText;

svg.querySelector('.logo .circles').style.display = (template && status && status !== 'online') ? 'none' : '';
svg.querySelector('.status circle').style.display = (template || !status) ? 'none' : null;
svg.querySelector('.status .away').style.display = (template && status === 'away') ? null : 'none';
svg.querySelector('.status .busy').style.display = (template && status === 'busy') ? null : 'none';
svg.querySelector('.status circle').style.fill = statusColors[status];
return platform;
}

if (overlay) {
const overlaySVG = svg.cloneNode(true);
svg.remove();

overlaySVG.querySelector('.logo').remove();
overlaySVG.querySelector('.status').remove();
overlaySVG.setAttribute('viewBox', '96 -32 160 160');

return overlaySVG;
}

return svg;
};

const rasterize = async(svg, size) => {
const image = new Image();
image.src = `data:image/svg+xml,${ encodeURIComponent(svg.outerHTML) }`;
image.width = image.height = size;
await new Promise((resolve, reject) => {
image.onload = resolve;
image.onerror = reject;
});
function getTrayIconName({ title, count, platform }) {
if (platform === 'darwin') {
return (title || count) ? 'notification' : 'default';
}

const canvas = document.createElement('canvas');
canvas.width = canvas.height = size;
if (title === '•') {
return 'notification-dot';
} else if (count > 0) {
return count > 9 ? 'notification-plus-9' : `notification-${ String(count) }`;
}

const ctx = canvas.getContext('2d');
ctx.drawImage(image, 0, 0);
return 'default';
}

return canvas.toDataURL('image/png');
};
function getTrayIconExtension({ platform }) {
if (platform === 'win32') {
return 'ico';
}

const svg = create(style);
const pixelRatio = window.devicePixelRatio;
const sizes = Array.isArray(style.size) ? style.size : [style.size || 256];
const images = await Promise.all(sizes.map(async(size) => ({
dataURL: await rasterize(svg, size * pixelRatio),
size,
pixelRatio,
})));
svg.remove();
return images;
};
return 'png';
}

const render = async(style = {}) => {
const encodedArgs = JSON.stringify(style);
render.cache = render.cache || [];
export function getAppIconPath() {
return 'public/images/icon.png';
}

if (render.cache[encodedArgs]) {
return render.cache[encodedArgs];
export function getTrayIconPath({ title, count, platform, dark } = {}) {
if (typeof platform === 'undefined') {
platform = process.platform;
}

const rendererWindow = await getRendererWindow();
const jsCode = `(${ renderInWindow.toString() })(${ encodedArgs })`;
const images = await rendererWindow.webContents.executeJavaScript(jsCode);
const image = nativeImage.createEmpty();
for (const { dataURL, size, pixelRatio } of images) {
image.addRepresentation({
scaleFactor: pixelRatio,
width: size,
height: size,
dataURL,
});
if (platform === 'darwin' && typeof dark === 'undefined') {
dark = systemPreferences.isDarkMode();
}
image.setTemplateImage(style.template || false);
render.cache[encodedArgs] = image;

return image;
};

export default {
render,
};
const params = { title, count, platform, dark };
const iconset = getTrayIconSet(params);
const name = getTrayIconName(params);
const extension = getTrayIconExtension(params);
return `public/images/tray/${ iconset }/${ name }.${ extension }`;
}

export function getAppIconImage() {
return nativeImage.createFromPath(`${ __dirname }/${ getAppIconPath() }`);
}

export function getTrayIconImage({ title, count, platform, dark } = {}) {
return nativeImage.createFromPath(`${ __dirname }/${ getTrayIconPath({ title, count, platform, dark }) }`);
}

export function getIconImage({ badge: { title, count } }) {
const iconsetsPath = `${ __dirname }/public/images/tray`;
const { platform } = process;
const dark = systemPreferences.isDarkMode();
const params = { title, count, platform, dark };
const iconset = getTrayIconSet(params);
const name = getTrayIconName(params);
const extension = getTrayIconExtension(params);
return nativeImage.createFromPath(`${ iconsetsPath }/${ iconset }/${ name }.${ extension }`);
}
84 changes: 84 additions & 0 deletions src/background/icon.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { expect } from 'chai';
import { describe, it } from 'mocha';
import { getAppIconPath, getTrayIconPath, getAppIconImage, getTrayIconImage } from './icon';

describe('icon', () => {

describe('paths', () => {
it('app', () => {
expect(getAppIconPath()).to.be.equals('public/images/icon.png');
});

describe('tray', () => {

it('darwin', () => {

expect(getTrayIconPath({ platform: 'darwin', dark: false })).to.be.equals('public/images/tray/darwin/default.png');
expect(getTrayIconPath({ title: '•', platform: 'darwin', dark: false })).to.be.equals('public/images/tray/darwin/notification.png');
expect(getTrayIconPath({ count: 1, platform: 'darwin', dark: false })).to.be.equals('public/images/tray/darwin/notification.png');
expect(getTrayIconPath({ count: 2, platform: 'darwin', dark: false })).to.be.equals('public/images/tray/darwin/notification.png');
expect(getTrayIconPath({ count: 3, platform: 'darwin', dark: false })).to.be.equals('public/images/tray/darwin/notification.png');
expect(getTrayIconPath({ count: 4, platform: 'darwin', dark: false })).to.be.equals('public/images/tray/darwin/notification.png');
expect(getTrayIconPath({ count: 5, platform: 'darwin', dark: false })).to.be.equals('public/images/tray/darwin/notification.png');
expect(getTrayIconPath({ count: 6, platform: 'darwin', dark: false })).to.be.equals('public/images/tray/darwin/notification.png');
expect(getTrayIconPath({ count: 7, platform: 'darwin', dark: false })).to.be.equals('public/images/tray/darwin/notification.png');
expect(getTrayIconPath({ count: 8, platform: 'darwin', dark: false })).to.be.equals('public/images/tray/darwin/notification.png');
expect(getTrayIconPath({ count: 9, platform: 'darwin', dark: false })).to.be.equals('public/images/tray/darwin/notification.png');
expect(getTrayIconPath({ count: 10, platform: 'darwin', dark: false })).to.be.equals('public/images/tray/darwin/notification.png');
});

it('darwin-dark', () => {

expect(getTrayIconPath({ platform: 'darwin', dark: true })).to.be.equals('public/images/tray/darwin-dark/default.png');
expect(getTrayIconPath({ title: '•', platform: 'darwin', dark: true })).to.be.equals('public/images/tray/darwin-dark/notification.png');
expect(getTrayIconPath({ count: 1, platform: 'darwin', dark: true })).to.be.equals('public/images/tray/darwin-dark/notification.png');
expect(getTrayIconPath({ count: 2, platform: 'darwin', dark: true })).to.be.equals('public/images/tray/darwin-dark/notification.png');
expect(getTrayIconPath({ count: 3, platform: 'darwin', dark: true })).to.be.equals('public/images/tray/darwin-dark/notification.png');
expect(getTrayIconPath({ count: 4, platform: 'darwin', dark: true })).to.be.equals('public/images/tray/darwin-dark/notification.png');
expect(getTrayIconPath({ count: 5, platform: 'darwin', dark: true })).to.be.equals('public/images/tray/darwin-dark/notification.png');
expect(getTrayIconPath({ count: 6, platform: 'darwin', dark: true })).to.be.equals('public/images/tray/darwin-dark/notification.png');
expect(getTrayIconPath({ count: 7, platform: 'darwin', dark: true })).to.be.equals('public/images/tray/darwin-dark/notification.png');
expect(getTrayIconPath({ count: 8, platform: 'darwin', dark: true })).to.be.equals('public/images/tray/darwin-dark/notification.png');
expect(getTrayIconPath({ count: 9, platform: 'darwin', dark: true })).to.be.equals('public/images/tray/darwin-dark/notification.png');
expect(getTrayIconPath({ count: 10, platform: 'darwin', dark: true })).to.be.equals('public/images/tray/darwin-dark/notification.png');
});

it('linux', () => {

expect(getTrayIconPath({ platform: 'linux' })).to.be.equals('public/images/tray/linux/default.png');
expect(getTrayIconPath({ title: '•', platform: 'linux' })).to.be.equals('public/images/tray/linux/notification-dot.png');
expect(getTrayIconPath({ count: 1, platform: 'linux' })).to.be.equals('public/images/tray/linux/notification-1.png');
expect(getTrayIconPath({ count: 2, platform: 'linux' })).to.be.equals('public/images/tray/linux/notification-2.png');
expect(getTrayIconPath({ count: 3, platform: 'linux' })).to.be.equals('public/images/tray/linux/notification-3.png');
expect(getTrayIconPath({ count: 4, platform: 'linux' })).to.be.equals('public/images/tray/linux/notification-4.png');
expect(getTrayIconPath({ count: 5, platform: 'linux' })).to.be.equals('public/images/tray/linux/notification-5.png');
expect(getTrayIconPath({ count: 6, platform: 'linux' })).to.be.equals('public/images/tray/linux/notification-6.png');
expect(getTrayIconPath({ count: 7, platform: 'linux' })).to.be.equals('public/images/tray/linux/notification-7.png');
expect(getTrayIconPath({ count: 8, platform: 'linux' })).to.be.equals('public/images/tray/linux/notification-8.png');
expect(getTrayIconPath({ count: 9, platform: 'linux' })).to.be.equals('public/images/tray/linux/notification-9.png');
expect(getTrayIconPath({ count: 10, platform: 'linux' })).to.be.equals('public/images/tray/linux/notification-plus-9.png');
});

it('win32', () => {

expect(getTrayIconPath({ platform: 'win32' })).to.be.equals('public/images/tray/win32/default.ico');
expect(getTrayIconPath({ title: '•', platform: 'win32' })).to.be.equals('public/images/tray/win32/notification-dot.ico');
expect(getTrayIconPath({ count: 1, platform: 'win32' })).to.be.equals('public/images/tray/win32/notification-1.ico');
expect(getTrayIconPath({ count: 2, platform: 'win32' })).to.be.equals('public/images/tray/win32/notification-2.ico');
expect(getTrayIconPath({ count: 3, platform: 'win32' })).to.be.equals('public/images/tray/win32/notification-3.ico');
expect(getTrayIconPath({ count: 4, platform: 'win32' })).to.be.equals('public/images/tray/win32/notification-4.ico');
expect(getTrayIconPath({ count: 5, platform: 'win32' })).to.be.equals('public/images/tray/win32/notification-5.ico');
expect(getTrayIconPath({ count: 6, platform: 'win32' })).to.be.equals('public/images/tray/win32/notification-6.ico');
expect(getTrayIconPath({ count: 7, platform: 'win32' })).to.be.equals('public/images/tray/win32/notification-7.ico');
expect(getTrayIconPath({ count: 8, platform: 'win32' })).to.be.equals('public/images/tray/win32/notification-8.ico');
expect(getTrayIconPath({ count: 9, platform: 'win32' })).to.be.equals('public/images/tray/win32/notification-9.ico');
expect(getTrayIconPath({ count: 10, platform: 'win32' })).to.be.equals('public/images/tray/win32/notification-plus-9.ico');
});
});
});

describe('image', () => {
expect(getAppIconImage()).to.not.be.null;
expect(getTrayIconImage()).to.not.be.null;
});
});
10 changes: 0 additions & 10 deletions src/background/mainWindow.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { app, BrowserWindow, ipcMain } from 'electron';
import createWindowStateKeeper from './windowState';
import { whenReady, whenReadyToShow } from './utils';
import env from '../env';
import icon from './icon';


let mainWindow = null;
Expand Down Expand Up @@ -84,15 +83,6 @@ export const getMainWindow = async() => {
mainWindow.loadURL(`file://${ __dirname }/public/app.html`);
attachWindowStateHandling(mainWindow);

if (process.platform !== 'darwin') {
mainWindow.setIcon(await icon.render({
size: {
win32: [256, 128, 64, 48, 32, 24, 16],
linux: 128,
}[process.platform],
}));
}

if (env.name === 'development') {
mainWindow.openDevTools();
}
Expand Down
Loading

0 comments on commit 37382ca

Please sign in to comment.