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

[IMPROVE] New tray icons #1093

Merged
merged 11 commits into from
Feb 20, 2019
Merged
Show file tree
Hide file tree
Changes from all 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
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