diff --git a/scripts/build-release.js b/scripts/build-release.js index 426ab9db3..df2047e4f 100755 --- a/scripts/build-release.js +++ b/scripts/build-release.js @@ -17,6 +17,8 @@ const { getVersionNumber, genMercurialRevision, } = require('./build-utils.js'); +const fetch = require('node-fetch'); +const {ICONS, getIconURL} = require('../src/utils/icons.js'); function generateManifest(versionNumber) { const filePath = path.join(__dirname, '..', 'dist'); @@ -112,11 +114,51 @@ function copyStaticFolder(buildFolder) { }); } +function downloadIcons(buildFolder) { + const iconURLs = Object.entries(ICONS).reduce((acc, [name, sizes]) => { + acc.push( + // get icons in @1x and @2x + ...sizes.map(size => ({name, size, density: 1})), + ...sizes.map(size => ({name, size, density: 2})), + ); + return acc; + }, []); + + return Promise.all( + iconURLs.map(({name, size, density}) => + fetch(getIconURL(name, size, density)) + .then(res => { + if (res.status !== 200) { + throw new Error(`Could not download the icon: ${name}`); + } + return res; + }) + .then( + res => + new Promise((resolve, reject) => { + const fileStream = fs.createWriteStream( + path.join( + buildFolder, + 'icons', + `${name}-${size}@${density}x.png`, + ), + ); + res.body.pipe(fileStream); + res.body.on('error', reject); + fileStream.on('finish', resolve); + }), + ) + .catch(console.error), + ), + ); +} + (async () => { const dir = await buildFolder(); // eslint-disable-next-line no-console console.log('Created build directory', dir); copyStaticFolder(dir); + await downloadIcons(dir); await compileDefaultPlugins(path.join(dir, 'defaultPlugins')); await compile(dir, path.join(__dirname, '..', 'src', 'init.js')); const versionNumber = getVersionNumber(); diff --git a/src/init.js b/src/init.js index 1e0b59909..75ccbd140 100644 --- a/src/init.js +++ b/src/init.js @@ -8,7 +8,6 @@ import {Provider} from 'react-redux'; import ReactDOM from 'react-dom'; import {ContextMenuProvider} from 'flipper'; -import {precachedIcons} from './utils/icons.js'; import GK from './fb-stubs/GK.js'; import {init as initLogger} from './fb-stubs/Logger'; import App from './App.js'; @@ -49,23 +48,6 @@ const AppFrame = () => ( function init() { // $FlowFixMe: this element exists! ReactDOM.render(, document.getElementById('root')); - // $FlowFixMe: service workers exist! - navigator.serviceWorker - .register( - process.env.NODE_ENV === 'production' - ? path.join(__dirname, 'serviceWorker.js') - : './serviceWorker.js', - ) - .then((r: ServiceWorkerRegistration) => { - const client = r.installing || r.active; - if (client != null) { - client.postMessage({precachedIcons}); - } else { - console.error('Service worker registration failed: ', r); - } - }) - .catch(console.error); - initLauncherHooks(config(), store); const sessionId = store.getState().application.sessionId; initCrashReporter(sessionId || ''); diff --git a/src/ui/components/Glyph.js b/src/ui/components/Glyph.js index f64203d3c..461152536 100644 --- a/src/ui/components/Glyph.js +++ b/src/ui/components/Glyph.js @@ -7,8 +7,8 @@ import React from 'react'; import styled from '../styled/index.js'; -const PropTypes = require('prop-types'); -import {getIconUrl} from '../../utils/icons.js'; +import PropTypes from 'prop-types'; +import {getIconURL} from '../../utils/icons.js'; const ColoredIconBlack = styled('img')(({size}) => ({ height: size, @@ -91,7 +91,11 @@ export default class Glyph extends React.Component<{ className={className} color={color} size={size} - src={getIconUrl(name, size, variant)} + src={getIconURL( + variant === 'outline' ? `${name}-outline` : name, + size, + typeof window !== 'undefined' ? window.devicePixelRatio : 1, + )} /> ); } diff --git a/src/utils/icons.js b/src/utils/icons.js index 7e82897dd..34f98763f 100644 --- a/src/utils/icons.js +++ b/src/utils/icons.js @@ -5,133 +5,88 @@ * @format */ -// list of icons that are prefetched in the service worker when launching the app -export const precachedIcons: Array = [ - { - name: 'arrow-right', - size: 12, - }, - { - name: 'caution-octagon', - }, - { - name: 'caution-triangle', - }, - { - name: 'info-circle', - }, - { - name: 'magic-wand', - size: 20, - }, - { - name: 'magnifying-glass', - }, - { - name: 'minus-circle', - size: 12, - }, - { - name: 'mobile', - size: 12, - }, - { - name: 'box', - size: 12, - }, - { - name: 'desktop', - size: 12, - }, - { - name: 'bug', - size: 12, - }, - { - name: 'posts', - size: 20, - }, - { - name: 'rocket', - size: 20, - }, - { - name: 'tools', - size: 20, - }, - { - name: 'triangle-down', - size: 12, - }, - { - name: 'triangle-right', - size: 12, - }, - { - name: 'chevron-right', - size: 8, - }, - { - name: 'chevron-down', - size: 8, - }, - { - name: 'star', - size: 16, - variant: 'filled', - }, - { - name: 'star', - size: 16, - variant: 'outline', - }, -].map(icon => - getIconUrl(icon.name, icon.size || undefined, icon.variant || undefined), -); +/* This file needs to be plain JS to be imported by scripts/build-release.js */ +/* eslint-disable import/no-commonjs */ -export function getIconUrl( - name: string, - size?: number = 16, - variant?: 'filled' | 'outline' = 'filled', -): string { - if (name.indexOf('/') > -1) { - return name; - } +const AVAILABLE_SIZES = [8, 10, 12, 16, 18, 20, 24, 32]; +const DENSITIES = [1, 1.5, 2, 3, 4]; +const fs = require('fs'); +const path = require('path'); +const {remote} = require('electron'); - const AVAILABLE_SIZES = [8, 10, 12, 16, 18, 20, 24, 32]; - const SCALE = [1, 1.5, 2, 3, 4]; +module.exports = { + ICONS: { + 'arrow-right': [12], + 'caution-octagon': [16], + 'caution-triangle': [16], + 'info-circle': [16], + 'magic-wand': [20], + 'magnifying-glass': [20], + 'minus-circle': [12], + mobile: [12], + box: [12], + desktop: [12], + bug: [12], + posts: [20], + rocket: [20], + tools: [20], + 'triangle-down': [12], + 'triangle-right': [12], + 'chevron-right': [8], + 'chevron-down': [8], + star: [16], + 'star-outline': [16], + }, - let requestedSize: number = size; - if (!AVAILABLE_SIZES.includes(size)) { - // find the next largest size - const possibleSize: ?number = AVAILABLE_SIZES.find(size => { - return size > requestedSize; - }); - - // set to largest size if the real size is larger than what we have - if (possibleSize == null) { - requestedSize = Math.max(...AVAILABLE_SIZES); - } else { - requestedSize = possibleSize; + // $FlowFixMe: not using flow in this file + getIconURL(name, size, density) { + if (name.indexOf('/') > -1) { + return name; } - } - let requestedScale: number = - typeof window !== 'undefined' ? window.devicePixelRatio : 1; + let requestedSize = size; + if (!AVAILABLE_SIZES.includes(size)) { + // find the next largest size + const possibleSize = AVAILABLE_SIZES.find(size => { + return size > requestedSize; + }); - if (!SCALE.includes(requestedScale)) { - // find the next largest size - const possibleScale: ?number = SCALE.find(scale => { - return scale > requestedScale; - }); - - // set to largest size if the real size is larger than what we have - if (possibleScale == null) { - requestedScale = Math.max(...SCALE); - } else { - requestedScale = possibleScale; + // set to largest size if the real size is larger than what we have + if (possibleSize == null) { + requestedSize = Math.max(...AVAILABLE_SIZES); + } else { + requestedSize = possibleSize; + } } - } - return `https://external.xx.fbcdn.net/assets/?name=${name}&variant=${variant}&size=${requestedSize}&set=facebook_icons&density=${requestedScale}x`; -} + if (!DENSITIES.includes(density)) { + // find the next largest size + const possibleDensity = DENSITIES.find(scale => { + return scale > density; + }); + + // set to largest size if the real size is larger than what we have + if (possibleDensity == null) { + density = Math.max(...DENSITIES); + } else { + density = possibleDensity; + } + } + + let variant = 'filled'; + if (name.endsWith('-outline')) { + name = name.replace('-outline', ''); + variant = 'outline'; + } + + const localPath = path.join('icons', `${name}-${size}@${density}x.png`); + // resolve icon locally if possible + if ( + remote && + fs.existsSync(path.join(remote.app.getAppPath(), localPath)) + ) { + return localPath; + } + return `https://external.xx.fbcdn.net/assets/?name=${name}&variant=${variant}&size=${requestedSize}&set=facebook_icons&density=${density}x`; + }, +}; diff --git a/static/serviceWorker.js b/static/serviceWorker.js deleted file mode 100644 index db48e3abe..000000000 --- a/static/serviceWorker.js +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright 2018-present Facebook. - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * @format - */ - -const CACHE_NAME = 'v1'; - -self.addEventListener('message', e => { - if (e.data.precachedIcons) { - caches.open(CACHE_NAME).then(cache => cache.addAll(e.data.precachedIcons)); - } -}); - -self.addEventListener('fetch', function(event) { - if (event.request.url.startsWith('https://external.xx.fbcdn.net/assets/')) { - event.respondWith( - // Cache falling back to the network - caches.match(event.request).then(cacheResponse => { - return ( - cacheResponse || - fetch(event.request).then(response => { - const clone = response.clone(); - // write to cache - caches - .open(CACHE_NAME) - .then(cache => cache.put(event.request, clone)); - return response; - }) - ); - }), - ); - } -}); diff --git a/tsconfig.json b/tsconfig.json index 08217bba6..44f675aea 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,7 @@ { "compilerOptions": { "module": "system", + "lib": ["es7", "dom"], "removeComments": true, "preserveConstEnums": true, "outFile": "../../built/local/tsc.js",