diff --git a/.gitignore b/.gitignore index 8ed94c5..f82c298 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ npm-debug.log *.zip *.pem .DS_Store +.idea diff --git a/dist.crx b/dist.crx index b80517d..f5d2530 100644 Binary files a/dist.crx and b/dist.crx differ diff --git a/manifest.json b/manifest.json index 25c8922..5b49d83 100644 --- a/manifest.json +++ b/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 2, "name": "Spongy", "author": "Diego Hernandez", - "version": "1.0.0", + "version": "1.0.1", "description": "A Google Chrome eyedropper for IBM Design Language's colors and grades.", @@ -33,11 +33,11 @@ "content_scripts": [{ "matches": ["http://*/*", "https://*/*"], - "js": ["scripts/main.js"], - "css": ["styles/main.css"] + "js": ["scripts/main.js"] }], "web_accessible_resources": [ - "fonts/*" + "fonts/*", + "styles/*" ] } diff --git a/package.json b/package.json index 9fe15d5..68c4d2e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "spongy", - "version": "1.0.0", + "version": "1.0.1", "description": "A Google Chrome eyedropper for IBM Design Language's colors and grades.", "license": "Apache-2.0", "main": "index.js", @@ -21,13 +21,13 @@ "eye dropper" ], "author": "Diego Hernandez ", - "license": "SEE LICENSE IN LICENSE.md", "bugs": { "url": "https://github.com/IBM-Design/spongy/issues" }, "dependencies": { "d3-color": "^1.0.2", - "ibm-design-colors": "^2.0.3" + "ibm-design-colors": "^2.0.3", + "lodash": "^4.17.4" }, "devDependencies": { "babel-core": "^6.18.2", diff --git a/src/scripts/background.js b/src/scripts/background.js index cef814c..01cecb0 100644 --- a/src/scripts/background.js +++ b/src/scripts/background.js @@ -1,3 +1,5 @@ -import {sendScreenshot} from './utils/chrome'; +import { sendScreenshot, processExtensionMessage, stopApp } from './utils/chrome'; +import MessageType from './constants/MessageType'; -chrome.runtime.onMessage.addListener(sendScreenshot); +chrome.runtime.onMessage.addListener(processExtensionMessage(MessageType.SCREENSHOT_REQUEST, sendScreenshot)); +chrome.runtime.onMessage.addListener(processExtensionMessage(MessageType.STOP_APP_REQUEST, stopApp)); diff --git a/src/scripts/components/App.js b/src/scripts/components/App.js index 49eed4f..1925684 100644 --- a/src/scripts/components/App.js +++ b/src/scripts/components/App.js @@ -1,22 +1,21 @@ import * as IBMColors from '../../../node_modules/ibm-design-colors/source/colors'; +import { debounce } from 'lodash'; import { createLoupe, updateLoupePixelColors, getMiddlePixelColor } from './Loupe'; import { createColorBox, updateColorBox } from './ColorBox'; import { createScreenshot, updateScreenshot, getColorData } from './Screenshot'; -import { createDiv, appendChildren } from '../utils/dom'; +import { createElement, createDiv, appendChildren } from '../utils/dom'; import { addBrandColorsToArray } from '../utils/color'; -import { requestScreenshot, processExtensionMessage } from '../utils/chrome'; -import PLATFORMS from '../constants/platforms'; +import { requestScreenshot, processExtensionMessage, requestStopApp } from '../utils/chrome'; +import MessageType from '../constants/MessageType'; +import KeyCode from '../constants/KeyCode'; function App(options = {}) { const SIZE = 5; const PREFIX = 'spongy-app'; const APP = createDiv(PREFIX); - let isAppActive = false; - const ui = createDiv(`${PREFIX}-container`); - const loupe = createLoupe(SIZE, PREFIX); - const colorBox = createColorBox(PREFIX); - const screenshot = createScreenshot(PREFIX); + loadStyles(); + requestScreenshot(); // Set brand colors let brandColors = []; @@ -24,26 +23,20 @@ function App(options = {}) { configure(options.colors); } - let requestScreenshotFunc; + chrome.runtime.onMessage.addListener(processExtensionMessage(MessageType.SCREENSHOT_DATA, getScreenshotData)); + chrome.runtime.onMessage.addListener(processExtensionMessage(MessageType.STOP_APP, deactivate)); - if (options.platform) { - switch (options.platform) { - case PLATFORMS.CHROME: - default: { - setDefault(); - } - } - } else { - setDefault(); - } + const ui = createDiv(`${PREFIX}-container`); + const loupe = createLoupe(SIZE, PREFIX); + const colorBox = createColorBox(PREFIX); + const screenshot = createScreenshot(PREFIX); /** - * Set default screenshot request and processing functions to Chrome. + * De-bounce refresh function so that it can be added and removed as event listener handlers. + * + * @type {function} */ - function setDefault() { - requestScreenshotFunc = requestScreenshot; - chrome.runtime.onMessage.addListener(processExtensionMessage(getScreenshotData)); - } + const deBounceRefresh = debounce(refresh, 50); /** * Function to configure brand color data of App. @@ -59,6 +52,26 @@ function App(options = {}) { addBrandColorsToArray(brandColors, data); } + /** + * Load styles on to current page. + */ + function loadStyles() { + const stylesUrl = chrome.extension.getURL('/styles/main.css'); + const exitingStyles = document.querySelectorAll(`[href='${stylesUrl}'`); + + // Check if there are already existing links that point to the styles URL. If there is none, load the styles. + if (exitingStyles.length === 0) { + // Create link element + const styles = createElement('link'); + styles.href = stylesUrl; + styles.rel = 'stylesheet'; + + // Append link to head. + const head = document.querySelector('head'); + head.appendChild(styles); + } + } + /** * Handle getting screenshot data. * @@ -73,19 +86,17 @@ function App(options = {}) { configure(IBMColors.palettes); } - if (!isAppActive) { - activate(); - } + activate(); } /** - * Request extension to send new screenshot data. + * Request extension to send new screen shot data. * * @callback */ function refresh() { removeUI(); - requestScreenshotFunc(appendUI); + setTimeout(requestScreenshot, 0); } @@ -93,16 +104,15 @@ function App(options = {}) { * Activate application by adding event listeners and showing the UI. */ function activate() { - document.addEventListener('mousemove', move); - document.addEventListener('click', readColor); - document.addEventListener('keyup', handleKeyCommand); - document.addEventListener('scroll', refresh); - window.addEventListener('resize', refresh); - - isAppActive = true; - appendChildren(ui, loupe.element, colorBox.element); - appendChildren(APP, ui); - appendUI(); + if (!document.body.contains(document.getElementById(PREFIX))) { + document.addEventListener('mousemove', move); + document.addEventListener('click', readColor); + document.addEventListener('keyup', handleKeyCommand); + document.addEventListener('scroll', deBounceRefresh); + window.addEventListener('resize', deBounceRefresh); + + appendUI(); + } } @@ -113,10 +123,9 @@ function App(options = {}) { document.removeEventListener('mousemove', move); document.removeEventListener('click', readColor); document.removeEventListener('keyup', handleKeyCommand); - document.removeEventListener('scroll', refresh); - window.removeEventListener('resize', refresh); + document.removeEventListener('scroll', deBounceRefresh); + window.removeEventListener('resize', deBounceRefresh); - isAppActive = false; removeUI(); } @@ -125,6 +134,8 @@ function App(options = {}) { * Append main App element from document body. */ function appendUI() { + appendChildren(ui, loupe.element, colorBox.element); + appendChildren(APP, ui); document.body.appendChild(APP); } @@ -133,7 +144,7 @@ function App(options = {}) { * Remove main App element from document body. */ function removeUI() { - if (document.getElementById(PREFIX)) { + if (document.body.contains(APP)) { document.body.removeChild(APP); } } @@ -156,7 +167,7 @@ function App(options = {}) { const x = pageX - scrollLeft; const y = pageY - scrollTop; - // Get color data from document Screenshot area based on x and y coordinates. + // Get color data from document Screen shot area based on x and y coordinates. const colorData = getColorData(screenshot, x, y, SIZE); // Get height and width of main UI ui. @@ -201,13 +212,12 @@ function App(options = {}) { event.preventDefault(); switch (keyCode) { - // Deactivate extension with key - case 27: { - deactivate(); + case KeyCode.ESC: { + requestStopApp(); break; } - // Reload extension with key - case 82: { + + case KeyCode.R: { refresh(); break; } diff --git a/src/scripts/components/Loupe.js b/src/scripts/components/Loupe.js index 27af69d..0f5ecae 100644 --- a/src/scripts/components/Loupe.js +++ b/src/scripts/components/Loupe.js @@ -66,7 +66,7 @@ function getMiddlePixelIndex(size) { * Color all of the pixels inside the Loupe. * * @param {object} loupe Loupe elements object. - * @param {number[]} colorData Array of RGBA values for an area the size of the Loupe. + * @param {Uint8ClampedArray} colorData Array of RGBA values for an area the size of the Loupe. * @public */ function updateLoupePixelColors(loupe, colorData) { diff --git a/src/scripts/components/Screenshot.js b/src/scripts/components/Screenshot.js index dde3bfc..5b7bc43 100644 --- a/src/scripts/components/Screenshot.js +++ b/src/scripts/components/Screenshot.js @@ -37,7 +37,6 @@ function width() { return window.innerWidth * window.devicePixelRatio; }; - /** * Update the canvas and the image the screenshot represents. * @@ -54,7 +53,9 @@ function updateScreenshot(screenshot, imageData) { element.width = width(); image.src = imageData; - context.drawImage(image, 0, 0); + image.addEventListener('load', () => { + context.drawImage(image, 0, 0); + }); } diff --git a/src/scripts/constants/KeyCode.js b/src/scripts/constants/KeyCode.js new file mode 100644 index 0000000..b093f01 --- /dev/null +++ b/src/scripts/constants/KeyCode.js @@ -0,0 +1,9 @@ +/** + * Keyboard event key code enumerable. + */ +const KeyCode = { + ESC: 27, + R: 82, +}; + +export default KeyCode; \ No newline at end of file diff --git a/src/scripts/constants/MessageType.js b/src/scripts/constants/MessageType.js new file mode 100644 index 0000000..46230fd --- /dev/null +++ b/src/scripts/constants/MessageType.js @@ -0,0 +1,9 @@ +const MessageType = { + START_APP: 'START_APP', + STOP_APP_REQUEST: 'STOP_APP_REQUEST', + STOP_APP: 'STOP_APP', + SCREENSHOT_DATA: 'SCREENSHOT_DATA', + SCREENSHOT_REQUEST: 'SCREENSHOT_REQUEST', +}; + +export default MessageType; diff --git a/src/scripts/constants/message_types.js b/src/scripts/constants/message_types.js deleted file mode 100644 index f0ed2bb..0000000 --- a/src/scripts/constants/message_types.js +++ /dev/null @@ -1,6 +0,0 @@ -const MESSAGE_TYPES = { - SCREENSHOT_DATA: 'SCREENSHOT_DATA', - SCREENSHOT_REQUEST: 'SCREENSHOT_REQUEST', -}; - -export default MESSAGE_TYPES; diff --git a/src/scripts/constants/platforms.js b/src/scripts/constants/platforms.js deleted file mode 100644 index c80915b..0000000 --- a/src/scripts/constants/platforms.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Supported platforms for application. - * - * @enum {string} - */ -const PLATFORMS = { - CHROME: 'CHROME', -} - -export default PLATFORMS; diff --git a/src/scripts/main.js b/src/scripts/main.js index 31b25f3..3fd9957 100644 --- a/src/scripts/main.js +++ b/src/scripts/main.js @@ -1,4 +1,17 @@ import * as IBMColors from '../../node_modules/ibm-design-colors/source/colors'; import App from './components/App'; +import { processExtensionMessage } from './utils/chrome'; +import MessageType from './constants/MessageType'; -App().configure(IBMColors.palettes); +let isAppRunning = false; + +chrome.runtime.onMessage.addListener(processExtensionMessage(MessageType.START_APP, () => { + if (!isAppRunning) { + App().configure(IBMColors.palettes); + isAppRunning = true; + } +})); + +chrome.runtime.onMessage.addListener(processExtensionMessage(MessageType.STOP_APP, () => { + isAppRunning = false; +})); diff --git a/src/scripts/popup.js b/src/scripts/popup.js index 90571b1..0673bac 100644 --- a/src/scripts/popup.js +++ b/src/scripts/popup.js @@ -1,4 +1,4 @@ -import {sendScreenshot} from './utils/chrome'; +import { startApp } from './utils/chrome'; const osCommandKeys = document.querySelectorAll('[data-keyboard="command"]'); const osIsMac = navigator.platform.includes('Mac'); @@ -7,4 +7,4 @@ for (const key of osCommandKeys) { key.textContent = osIsMac ? '⌘' : 'Ctrl'; } -sendScreenshot(); +startApp(); diff --git a/src/scripts/utils/chrome.js b/src/scripts/utils/chrome.js index 5d48b3c..d570559 100644 --- a/src/scripts/utils/chrome.js +++ b/src/scripts/utils/chrome.js @@ -1,60 +1,107 @@ -import MESSAGE_TYPES from '../constants/message_types'; +import MessageType from '../constants/MessageType'; /** - * FOR INJECTED SCRIPT: Request screenshot data. + * FOR INJECTED SCRIPT: Request screen shot data. * - * @param {function} callback Function to call when screenshot request has sent. + * @param {function} callback Function to call when screen shot request has sent. */ function requestScreenshot(callback = () => {}) { if (typeof callback !== 'function') { throw new Error('[SPONGY] Callback must be a function'); } - chrome.runtime.sendMessage(null, {type: MESSAGE_TYPES.SCREENSHOT_REQUEST}, () => { + chrome.runtime.sendMessage(null, {type: MessageType.SCREENSHOT_REQUEST}, () => { callback(); }); } + + /** * FOR INJECTED SCRIPT: Handle messages from extension. * - * @param {function} callback Function to call when screenshot data is ready. + * @param {string} messageType The event to listen to events to. + * @param {function} callback Function to call when screen shot data is ready. * @callback */ -function processExtensionMessage(callback = () => {}) { +function processExtensionMessage(messageType, callback = () => {}) { if (typeof callback !== 'function') { throw new Error('[SPONGY] Callback must be a function'); } return (message) => { - switch (message.type) { - case MESSAGE_TYPES.SCREENSHOT_DATA: { - callback(message.data); - break; - } - default: { - console.error('[SPONGY] Extention message not recognized', request); - } + if (message.type === messageType) { + callback(message.data); } } } + +/** + * Wrapper function to query active current tab and send a message to that tab. + * + * @param {function} callback Function to invoke when active tab has been queried. + * @private + */ +function getActiveTab(callback = () => {}) { + chrome.tabs.query({active: true, currentWindow: true}, function(tabs) { + const tab = tabs[0]; + callback(tab); + }); +} + /** * FOR EXTENSION: Respond to a new screenshot request from an injected script. * * @public */ function sendScreenshot() { - chrome.tabs.query({active: true, currentWindow: true}, function(tabs) { - const tab = tabs[0]; + getActiveTab((tab) => { chrome.tabs.captureVisibleTab(null, {format: 'png'}, function(data) { - chrome.tabs.sendMessage(tab.id, {type: MESSAGE_TYPES.SCREENSHOT_DATA, data}); + chrome.tabs.sendMessage(tab.id, {type: MessageType.SCREENSHOT_DATA, data}); }); }); } +/** + * FOR EXTENSION: Sends message to active tab to start the application. + * + * @public + */ +function startApp() { + getActiveTab((tab) => { + chrome.tabs.sendMessage(tab.id, {type: MessageType.START_APP}); + }); +} + +/** + * FOR EXTENSION: Sends message to active tab to stop the application. + * + * @public + */ +function stopApp() { + getActiveTab((tab) => { + chrome.tabs.sendMessage(tab.id, {type: MessageType.STOP_APP}); + }); +} + + +/** + * FOR INJECTED SCRIPT: Sends message to extension that the user has decided to stop the application. + * + * @param {function} callback Function to call when the message has been sent. + */ +function requestStopApp(callback = () => {}) { + chrome.runtime.sendMessage(null, {type: MessageType.STOP_APP_REQUEST}, () => { + callback(); + }); +} + export { requestScreenshot, processExtensionMessage, sendScreenshot, + startApp, + stopApp, + requestStopApp, } diff --git a/src/scripts/utils/color.js b/src/scripts/utils/color.js index 12c59e1..f818ef0 100644 --- a/src/scripts/utils/color.js +++ b/src/scripts/utils/color.js @@ -1,4 +1,3 @@ -import * as d3 from 'd3-color'; import { roundToDecimal } from './math'; /** * Turn a hexadecimal color value into an array of red, green, blue values in base 10. diff --git a/src/styles/main.scss b/src/styles/main.scss index 637969a..6662a1a 100644 --- a/src/styles/main.scss +++ b/src/styles/main.scss @@ -21,5 +21,7 @@ font-weight: 400; text-align: left; + background-color: transparent; + cursor: crosshair; } diff --git a/webpack.config.js b/webpack.config.js index 81f056f..d9f848d 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -4,6 +4,7 @@ const src = path.join(__dirname, 'src/scripts'); const dist = path.join(__dirname, 'dist/scripts'); module.exports = { + devtool: 'source-map', entry: { main: path.join(src, 'main.js'), popup: path.join(src, 'popup.js'),