diff --git a/.circleci/config.yml b/.circleci/config.yml index 4529184df..1a38619ce 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -72,6 +72,10 @@ jobs: name: Test component command: yarn run test:component --chrome.browserWSEndpoint "ws://localhost:3000" --no-launch + - run: + name: Test mashup + command: yarn run test:mashup --chrome.browserWSEndpoint "ws://localhost:3000" --no-launch + - run: name: Test integration command: APP_ID=$DOC_ID yarn run test:integration --chrome.browserWSEndpoint "ws://localhost:3000" --no-launch @@ -93,5 +97,9 @@ jobs: cd generated/barchart yarn run build APP_ID=$DOC_ID yarn run test:integration --mocha.timeout 30000 --chrome.browserWSEndpoint "ws://localhost:3000" --no-launch + - store_artifacts: path: generated/barchart/screenshots + + - store_artifacts: + path: test/mashup/__artifacts__ diff --git a/.gitignore b/.gitignore index f9938fb75..72f44971a 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ node_modules/ coverage/ dist/ temp/ +test/**/__artifacts__/regression +test/**/__artifacts__/diff \ No newline at end of file diff --git a/apis/locale/src/translator.js b/apis/locale/src/translator.js index b8c46fd40..dde4a89aa 100644 --- a/apis/locale/src/translator.js +++ b/apis/locale/src/translator.js @@ -12,6 +12,9 @@ export default function translator({ initial = 'en-US', fallback = 'en-US' } = { * @interface Translator */ const api = { + language: () => { + return currentLocale; + }, /** * Register a string in multiple locales * @param {object} item diff --git a/apis/nucleus/src/components/Cell.jsx b/apis/nucleus/src/components/Cell.jsx index 23b6c9293..179ea71c7 100644 --- a/apis/nucleus/src/components/Cell.jsx +++ b/apis/nucleus/src/components/Cell.jsx @@ -1,5 +1,5 @@ /* eslint-disable react/jsx-props-no-spreading */ -import React, { forwardRef, useImperativeHandle, useEffect, useState, useContext, useReducer } from 'react'; +import React, { forwardRef, useImperativeHandle, useEffect, useState, useContext, useReducer, useRef } from 'react'; import { Grid, Paper } from '@material-ui/core'; import { useTheme } from '@nebula.js/ui/theme'; @@ -170,6 +170,7 @@ const Cell = forwardRef(({ nebulaContext, model, initialSnContext, initialSnOpti const [contentRef, contentRect, , contentNode] = useRect(); const [snContext, setSnContext] = useState(initialSnContext); const [snOptions, setSnOptions] = useState(initialSnOptions); + const cellRef = useRef(); useEffect(() => { const validate = sn => { @@ -231,7 +232,7 @@ const Cell = forwardRef(({ nebulaContext, model, initialSnContext, initialSnOpti () => ({ setSnContext, setSnOptions, - takeSnapshot: async () => { + async takeSnapshot() { const snapshot = { ...layout, snapshotData: { @@ -248,8 +249,29 @@ const Cell = forwardRef(({ nebulaContext, model, initialSnContext, initialSnOpti } return snapshot; }, + async exportImage() { + if (!nebulaContext.snapshot.capture) { + throw new Error('Nebula has not been configured with snapshot.capture'); + } + const lyt = await this.takeSnapshot(); // eslint-disable-line + const { width, height } = cellRef.current.getBoundingClientRect(); + const s = { + meta: { + language: translator.language(), + theme: theme.name, + // direction: 'ltr', + size: { + width: Math.round(width), + height: Math.round(height), + }, + }, + layout: lyt, + }; + + return nebulaContext.snapshot.capture(s); + }, }), - [state.sn, contentRect, layout] + [state.sn, contentRect, layout, theme.name] ); // console.log('content', state); let Content = null; @@ -276,6 +298,7 @@ const Cell = forwardRef(({ nebulaContext, model, initialSnContext, initialSnOpti elevation={0} square className="nebulajs-cell" + ref={cellRef} > { + const res = await fetch(`/njs/snapshot/${id}`); + if (!res.ok) { + throw new Error(res.statusText); + } + return res.json(); + }, + capture(payload) { + return fetch(`/njs/capture`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(payload), + }).then(res => res.json()); + }, + }, }; const mergeConfigs = (base, c) => ({ @@ -57,6 +76,9 @@ const mergeConfigs = (base, c) => ({ locale: { language: (c.locale ? c.locale.language : '') || base.locale.language, }, + snapshot: { + ...(c.snapshot || base.snapshot), + }, types: [ // TODO - filter to avoid duplicates ...(base.types || []), @@ -143,6 +165,7 @@ function nuked(configuration = {}, prev = {}) { types, root, theme: appTheme.externalAPI, + snapshot: configuration.snapshot, }; let currentThemePromise = appTheme.setTheme(configuration.theme); @@ -251,6 +274,7 @@ function nuked(configuration = {}, prev = {}) { * nucleus(app).create({ type: 'mekko' }); // will throw error since 'mekko' is not a register type on the default instance */ nucleus.configured = c => nuked(mergeConfigs(configuration, c)); + nucleus.config = configuration; return nucleus; } diff --git a/apis/nucleus/src/viz.js b/apis/nucleus/src/viz.js index bb2905b7e..91814888b 100644 --- a/apis/nucleus/src/viz.js +++ b/apis/nucleus/src/viz.js @@ -113,8 +113,12 @@ export default function viz({ model, context: nebulaContext } = {}) { return api; }, takeSnapshot() { + // TODO - decide if this method is useful at all return cellRef.current.takeSnapshot(); }, + exportImage(settings) { + return cellRef.current.exportImage(settings); + }, // QVisualization API // close() {}, diff --git a/apis/snapshooter/lib/snapshooter.js b/apis/snapshooter/lib/snapshooter.js new file mode 100644 index 000000000..b32b8d7f7 --- /dev/null +++ b/apis/snapshooter/lib/snapshooter.js @@ -0,0 +1,80 @@ +/* eslint no-param-reassign: 0 */ + +const puppeteer = require('puppeteer'); + +function snapshooter({ snapshotUrl, chrome = {} } = {}) { + const snapshots = {}; + const images = {}; + let browser; + let cachedPage; + let rendering = false; + + const hasConnectOptions = chrome.browserWSEndpoint || chrome.browserURL; + + const createBrowser = () => (hasConnectOptions ? puppeteer.connect(chrome) : puppeteer.launch(chrome)); + + const doIt = async (snapshot, runCached) => { + browser = browser || (await createBrowser()); + let page; + if (runCached) { + cachedPage = cachedPage || (await browser.newPage()); + page = cachedPage; + } else { + page = await browser.newPage(); + } + await page.setViewport({ + width: snapshot.meta.size.width, + height: snapshot.meta.size.height, + }); + await page.goto(`${snapshotUrl}?snapshot=${snapshot.key}`); + try { + await page.waitFor( + () => + (document.querySelector('.nebulajs-sn') && + +document.querySelector('.nebulajs-sn').getAttribute('data-render-count') > 0) || + document.querySelector('[data-njs-error]') + ); + } catch (e) { + // empty + } + const image = await page.screenshot(); + images[snapshot.key] = image; + rendering = false; + return snapshot.key; + }; + + return { + getStoredImage(id) { + return images[id]; + }, + getStoredSnapshot(id) { + return snapshots[id]; + }, + storeSnapshot(snapshot) { + if (!snapshot) { + throw new Error('Empty snapshot'); + } + if (!snapshot.key) { + snapshot.key = String(+Date.now()); + } + snapshots[snapshot.key] = snapshot; + return snapshot.key; + }, + async captureImageOfSnapshot(snapshot) { + this.storeSnapshot(snapshot); + if (rendering) { + return doIt(snapshot); + } + rendering = true; + const key = await doIt(snapshot, true); + rendering = false; + return key; + }, + async close() { + if (browser) { + await browser.close(); + } + }, + }; +} +module.exports = snapshooter; diff --git a/apis/snapshooter/package.json b/apis/snapshooter/package.json new file mode 100644 index 000000000..4053870eb --- /dev/null +++ b/apis/snapshooter/package.json @@ -0,0 +1,32 @@ +{ + "name": "@nebula.js/snapshooter", + "version": "0.1.0-alpha.25", + "description": "", + "license": "MIT", + "author": "QlikTech International AB", + "keywords": [ + "qlik", + "nebula", + "supernova", + "snapshot" + ], + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "https://github.com/qlik-oss/nebula.js.git" + }, + "main": "lib/snapshooter.js", + "files": [ + "lib", + "dist" + ], + "scripts": { + "build": "cross-env NODE_ENV=production rollup --config ./rollup.config.js", + "prepublishOnly": "rm -rf dist && yarn run build" + }, + "dependencies": { + "puppeteer": "2.0.0" + } +} diff --git a/apis/snapshooter/rollup.config.js b/apis/snapshooter/rollup.config.js new file mode 100644 index 000000000..a93fa612f --- /dev/null +++ b/apis/snapshooter/rollup.config.js @@ -0,0 +1,61 @@ +const path = require('path'); +const babel = require('rollup-plugin-babel'); // eslint-disable-line +const { terser } = require('rollup-plugin-terser'); // eslint-disable-line + +const cwd = process.cwd(); +const pkg = require(path.join(cwd, 'package.json')); // eslint-disable-line +const { name, version, license } = pkg; + +const banner = `/* +* ${name} v${version} +* Copyright (c) ${new Date().getFullYear()} QlikTech International AB +* Released under the ${license} license. +*/ +`; + +const browserList = [ + 'last 2 Chrome versions', + 'last 2 Firefox versions', + 'last 2 Edge versions', + 'Safari >= 10.0', + 'iOS >= 11.2', +]; + +const cfg = { + input: path.resolve(cwd, 'src', 'renderer'), + output: { + file: path.resolve(cwd, 'dist/renderer.js'), + format: 'umd', + exports: 'default', + name: 'snapshooter', + banner, + }, + plugins: [ + babel({ + babelrc: false, + presets: [ + [ + '@babel/preset-env', + { + modules: false, + targets: { + browsers: [...browserList, ...['ie 11', 'chrome 47']], + }, + }, + ], + ], + }), + ], +}; + +if (process.env.NODE_ENV === 'production') { + cfg.plugins.push( + terser({ + output: { + preamble: banner, + }, + }) + ); +} + +module.exports = [cfg].filter(Boolean); diff --git a/apis/snapshooter/src/renderer.js b/apis/snapshooter/src/renderer.js new file mode 100644 index 000000000..e24a674fe --- /dev/null +++ b/apis/snapshooter/src/renderer.js @@ -0,0 +1,81 @@ +/* eslint no-param-reassign: 0 */ +async function renderSnapshot({ nucleus, element }) { + const params = (() => { + const opts = {}; + const { pathname } = window.location; + const am = pathname.match(/\/app\/([^/?&]+)/); + if (am) { + opts.app = decodeURIComponent(am[1]); + } + window.location.search + .substring(1) + .split('&') + .forEach(pair => { + const idx = pair.indexOf('='); + const name = pair.substr(0, idx); + const value = decodeURIComponent(pair.substring(idx + 1)); + opts[name] = value; + }); + + return opts; + })(); + + let snapshot = {}; + const renderError = e => { + element.innerHTML = ` +

${e.message}

+ `; + element.setAttribute('data-njs-error', e.message); + }; + try { + snapshot = await nucleus.config.snapshot.get(params.snapshot); + } catch (e) { + renderError(e); + throw e; + } + + const { + meta: { theme, language }, + layout, + } = snapshot; + + const objectModel = { + async getLayout() { + return layout; + }, + on() {}, + once() {}, + }; + + const app = { + async getObject(id) { + if (id === layout.qInfo.qId) { + return objectModel; + } + return Promise.reject(); + }, + }; + + const nebbie = await nucleus(app, { + theme, + locale: { + language, + }, + }); + + nebbie + .get( + { + id: layout.qInfo.qId, + }, + { + element, + } + ) + .catch(e => { + renderError(e || { message: 'Failed to render supernova' }); + throw e; + }); +} + +export default renderSnapshot; diff --git a/commands/serve/lib/snapshot-router.js b/commands/serve/lib/snapshot-router.js new file mode 100644 index 000000000..f98ea7357 --- /dev/null +++ b/commands/serve/lib/snapshot-router.js @@ -0,0 +1,54 @@ +const express = require('express'); +const bodyParser = require('body-parser'); + +module.exports = function router({ base, snapshotUrl, snapshooter }) { + const r = express.Router(); + + r.use( + bodyParser.json({ + limit: '10mb', + }) + ); + + // returns an existing image + r.get('/image/:id', (req, res) => { + const p = snapshooter.getStoredImage(req.params.id); + if (p) { + res.contentType('image/png'); + res.end(p, 'binary'); + } else { + res.sendStatus('404'); + } + }); + + r.post('/capture', async (req, res) => { + try { + const key = await snapshooter.captureImageOfSnapshot(req.body); + res.send({ + url: `${base}/image/${key}`, + }); + } catch (e) { + console.error(e); + res.sendStatus('500'); + } + }); + + // post snapshot + r.post('/snapshot', (req, res) => { + const key = snapshooter.storeSnapshot(req.body); + res.json({ + url: `${snapshotUrl}?snapshot=${key}`, + }); + }); + + r.get('/snapshot/:id', (req, res) => { + const p = snapshooter.getStoredSnapshot(req.params.id); + if (p) { + res.json(p); + } else { + res.sendStatus('404'); + } + }); + + return r; +}; diff --git a/commands/serve/lib/snapshot.js b/commands/serve/lib/snapshot.js deleted file mode 100644 index faaf37161..000000000 --- a/commands/serve/lib/snapshot.js +++ /dev/null @@ -1,90 +0,0 @@ -const bodyParser = require('body-parser'); -const puppeteer = require('puppeteer'); - -function snapshotter({ host, port }) { - const snapshots = {}; - const images = {}; - - let browser; - - return { - addRoutes(app) { - app.use( - bodyParser.json({ - limit: '10mb', - }) - ); - - app.get('/image/:id', (req, res) => { - const p = images[req.params.id]; - if (p) { - res.contentType('image/png'); - res.end(p, 'binary'); - } else { - res.sendStatus('404'); - } - }); - app.post('/image', async (req, res) => { - let snap; - try { - snap = req.body; - if (!snap.key) { - snap.key = String(+Date.now()); - } - snapshots[snap.key] = snap; - } catch (e) { - res.sendStatus('400'); - } - - try { - browser = browser || (await puppeteer.launch()); - const page = await browser.newPage(); - await page.setViewport({ - width: snap.meta.size.width, - height: snap.meta.size.height, - }); - await page.goto(`http://${host}:${port}/render?snapshot=${snap.key}`); - await page.waitFor( - () => - document.querySelector('.nebulajs-sn') && - +document.querySelector('.nebulajs-sn').getAttribute('data-render-count') > 0 - ); - const image = await page.screenshot(); - images[snap.key] = image; - res.send({ - url: `/image/${snap.key}`, - }); - } catch (e) { - console.error(e); - res.sendStatus('503'); - } - }); - - app.post('/snapshot', (req, res) => { - try { - const snap = req.body; - if (!snap.key) { - snap.key = String(+Date.now()); - } - snapshots[snap.key] = snap; - res.json({ - url: `/render?snapshot=${snap.key}`, - }); - } catch (e) { - console.error(e); - res.sendStatus('400'); - } - }); - - app.get('/snapshot/:id', (req, res) => { - const p = snapshots[req.params.id]; - if (p) { - res.json(p); - } else { - res.sendStatus('404'); - } - }); - }, - }; -} -module.exports = snapshotter; diff --git a/commands/serve/lib/webpack.build.js b/commands/serve/lib/webpack.build.js index 43500c12b..b452bfa9e 100644 --- a/commands/serve/lib/webpack.build.js +++ b/commands/serve/lib/webpack.build.js @@ -30,6 +30,7 @@ const cfg = ({ srcDir, distDir, dev = false, serveConfig = {} }) => { '@nebula.js/nucleus/src/hooks': path.resolve(process.cwd(), 'apis/nucleus/src/hooks'), '@nebula.js/nucleus/src/object': path.resolve(process.cwd(), 'apis/nucleus/src/object'), '@nebula.js/nucleus': path.resolve(process.cwd(), 'apis/nucleus/src'), + '@nebula.js/snapshooter/dist': path.resolve(process.cwd(), 'apis/snapshooter/src'), '@nebula.js/supernova': path.resolve(process.cwd(), 'apis/supernova/src'), '@nebula.js/theme': path.resolve(process.cwd(), 'apis/theme/src'), '@nebula.js/locale': path.resolve(process.cwd(), 'apis/locale/src'), diff --git a/commands/serve/lib/webpack.serve.js b/commands/serve/lib/webpack.serve.js index 94dd49645..2977edeca 100644 --- a/commands/serve/lib/webpack.serve.js +++ b/commands/serve/lib/webpack.serve.js @@ -6,7 +6,8 @@ const express = require('express'); const webpack = require('webpack'); const WebpackDevServer = require('webpack-dev-server'); -const snapshotter = require('./snapshot'); +const snapshooter = require('@nebula.js/snapshooter'); +const snapshotRouter = require('./snapshot-router'); module.exports = async ({ host, @@ -23,10 +24,12 @@ module.exports = async ({ let config; let contentBase; - const snapper = snapshotter({ - host, - port, - images: serveConfig.images || [], + const snapshotRoute = '/njs'; + + const snapRouter = snapshotRouter({ + base: `http://${host}:${port}${snapshotRoute}`, + snapshotUrl: `http://${host}:${port}/eRender.html`, + snapshooter: snapshooter({ snapshotUrl: `http://${host}:${port}/eRender.html` }), }); const themes = serveConfig.themes || []; @@ -69,7 +72,7 @@ module.exports = async ({ index: '/eHub.html', }, before(app) { - snapper.addRoutes(app); + app.use(snapshotRoute, snapRouter); entryWatcher.addRoutes(app); diff --git a/commands/serve/package.json b/commands/serve/package.json index 0a40571ee..bd06dbb58 100644 --- a/commands/serve/package.json +++ b/commands/serve/package.json @@ -27,6 +27,7 @@ }, "dependencies": { "@nebula.js/cli-build": "0.1.0-alpha.25", + "@nebula.js/snapshooter": "0.1.0-alpha.25", "body-parser": "1.19.0", "chalk": "3.0.0", "chokidar": "3.3.0", diff --git a/commands/serve/web/components/Cell.jsx b/commands/serve/web/components/Cell.jsx index 2e533faa3..88ad7af18 100644 --- a/commands/serve/web/components/Cell.jsx +++ b/commands/serve/web/components/Cell.jsx @@ -11,30 +11,6 @@ import VizContext from '../contexts/VizContext'; import Chart from './Chart'; -const takeAndSendSnapshot = ({ ref, route = '/snapshot', theme, language }) => - ref.viz.takeSnapshot().then(snapshot => { - const containerSize = ref.el.getBoundingClientRect(); - return fetch(route, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - key: snapshot.qInfo.qId, - meta: { - language, - theme, - direction: '', - size: { - width: Math.round(containerSize.width), - height: Math.round(containerSize.height), - }, - }, - layout: snapshot, - }), - }).then(response => response.json()); - }); - export default function({ id, expandable, minHeight }) { const language = 'en-US'; // TODO - useLocale const app = useContext(AppContext); @@ -90,7 +66,7 @@ export default function({ id, expandable, minHeight }) { } if (doExport) { setExporting(true); - takeAndSendSnapshot({ ref: vizRef.current, route: '/image', theme: currentThemeName, language }).then(res => { + vizRef.current.viz.exportImage().then(res => { if (res && res.url) { window.open(res.url); } @@ -98,10 +74,11 @@ export default function({ id, expandable, minHeight }) { }); } else { const containerSize = vizRef.current.el.getBoundingClientRect(); - takeAndSendSnapshot({ ref: vizRef.current, theme: currentThemeName, language }).then(res => { + vizRef.current.viz.exportImage().then(res => { if (res && res.url) { + const key = /\/([A-z0-9_]+)$/.exec(res.url)[1]; window.open( - res.url, + `/render?snapshot=${key}`, 'snapshot', `height=${Math.round(containerSize.height)},width=${Math.round(containerSize.width)}` ); diff --git a/commands/serve/web/eRender.js b/commands/serve/web/eRender.js index 5a59a9a08..9c6ff9b56 100644 --- a/commands/serve/web/eRender.js +++ b/commands/serve/web/eRender.js @@ -1,4 +1,6 @@ import nucleus from '@nebula.js/nucleus'; +import snapshooter from '@nebula.js/snapshooter/dist/renderer'; + import { openApp, params, info as serverInfo } from './connect'; import runFixture from './run-fixture'; @@ -65,56 +67,28 @@ async function renderWithEngine() { } async function renderSnapshot() { - const info = await serverInfo; + const { themes, supernova } = await serverInfo; const element = document.querySelector('#chart-container'); element.classList.toggle('full', true); - const snapshot = await (await fetch(`/snapshot/${params.snapshot}`)).json(); - const layout = { - ...snapshot.layout, - visualization: info.supernova.name, - }; - - const { - meta: { theme, language }, - } = snapshot; - - const objectModel = { - async getLayout() { - return layout; - }, - on() {}, - once() {}, - }; - const app = { - async getObject(id) { - if (id === layout.qInfo.qId) { - return objectModel; - } - return Promise.reject(); - }, - }; - - const nebbie = await nuke({ app, ...info, theme, language }); - const getCfg = { - id: layout.qInfo.qId, - }; - const vizCfg = { - element, - context: { - permissions: ['passive'], - }, - options: { - onInitialRender() { - document.querySelector('.nebulajs-sn').setAttribute('data-rendered', '1'); + const n = nucleus.configured({ + themes: themes + ? themes.map(t => ({ + key: t, + load: async () => (await fetch(`/theme/${t}`)).json(), + })) + : undefined, + types: [ + { + load: (type, config) => config.Promise.resolve(window[type.name]), + name: supernova.name, }, - }, - }; - const render = () => { - nebbie.get(getCfg, vizCfg); - }; - - window.onHotChange(info.supernova.name, () => render()); + ], + }); + snapshooter({ + nucleus: n, + element, + }); } const renderFixture = async () => { diff --git a/package.json b/package.json index a67b84e51..efdd9000a 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "start:ui": "start-storybook", "spec": "lerna run spec --stream --concurrency 99", "test": "yarn run test:unit", + "test:mashup": "aw puppet -c aw.config.js --testExt '*.int.js' --glob 'test/mashup/**/*.int.js'", "test:integration": "aw puppet -c aw.config.js --testExt '*.int.js' --glob 'test/integration/*.int.js'", "test:component": "aw puppet -c aw.config.js --testExt '*.comp.js' --glob 'test/component/**/*.comp.js'", "test:unit": "aw -c aw.config.js --nyc.reportDir coverage/unit --coverage" @@ -45,6 +46,7 @@ "@storybook/react": "5.2.8", "babel-loader": "8.0.6", "babel-plugin-istanbul": "5.2.0", + "body-parser": "1.19.0", "cross-env": "6.0.3", "enigma.js": "2.6.3", "eslint": "6.7.2", @@ -55,7 +57,9 @@ "eslint-plugin-mocha": "6.2.2", "eslint-plugin-prettier": "3.1.1", "eslint-plugin-react": "7.17.0", + "express": "4.17.1", "husky": "3.1.0", + "jimp": "^0.9.3", "lerna": "3.19.0", "lint-staged": "9.5.0", "mocha-junit-reporter": "^1.23.1", diff --git a/packages/ui/theme/create.js b/packages/ui/theme/create.js index 0327b772b..3647df0a3 100644 --- a/packages/ui/theme/create.js +++ b/packages/ui/theme/create.js @@ -80,7 +80,9 @@ const overrides = theme => ({ export default function create(definition) { let def = light; + let name = ''; if (typeof definition === 'string') { + name = definition; if (definition !== 'light' && definition !== 'dark') { console.warn(`Invalid theme: '${definition}'`); } else if (definition === 'dark') { @@ -116,32 +118,7 @@ export default function create(definition) { overrides: overrides(withDefaults), }); - // cache[key] = createMuiTheme({ - // typography: { - // ...defaults.typography, - // }, - // shadows: defaults.shadows, - // props: { - // ...defaults.props, - // }, - // shape: { - // ...defaults.shape, - // }, - // overrides: { - // ...defaults.overrides, - // ...def.overrides, - // }, - // palette: { - // secondary: { - // light: '#0AAF54', - // main: '#009845', - // dark: '#006937', - // contrastText: '#fff', - // }, - // type: def.type, - // ...def.palette, - // }, - // }); + cache[key].name = name; return cache[key]; } diff --git a/rollup.config.js b/rollup.config.js index 47c416803..dec1d06dd 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -137,6 +137,7 @@ const config = isEsm => { include: [ '/**/apis/locale/**', '/**/apis/nucleus/**', + '/**/apis/snapshooter/**', '/**/apis/supernova/**', '/**/apis/theme/**', '/**/packages/ui/**', @@ -170,4 +171,4 @@ const config = isEsm => { return cfg; }; -module.exports = [watch ? false : config(), config(true)].filter(Boolean); +module.exports = [watch ? false : config(), pkg.module ? config(true) : false].filter(Boolean); diff --git a/test/mashup/__artifacts__/baseline/snaps-bar.png b/test/mashup/__artifacts__/baseline/snaps-bar.png new file mode 100644 index 000000000..71ad3d416 Binary files /dev/null and b/test/mashup/__artifacts__/baseline/snaps-bar.png differ diff --git a/test/mashup/server.js b/test/mashup/server.js new file mode 100644 index 000000000..608d631eb --- /dev/null +++ b/test/mashup/server.js @@ -0,0 +1,77 @@ +const path = require('path'); +const express = require('express'); +const bodyParser = require('body-parser'); +const yargs = require('yargs/yargs'); + +module.exports = async () => { + let server; + let snapshooter; + const app = express(); + const port = 8050; + + const url = `http://localhost:${port}`; + + const args = yargs(process.argv.slice(2)).argv; + + // eslint-disable-next-line + snapshooter = require('@nebula.js/snapshooter')({ + snapshotUrl: `${url}/snaps/single.html`, + chrome: args.chrome || {}, + }); + + app.use(express.static(path.resolve(__dirname))); + app.use('/apis', express.static(path.resolve(__dirname, '../../apis'))); + app.use('/node_modules', express.static(path.resolve(__dirname, '../../node_modules'))); + + app.use( + bodyParser.json({ + limit: '10mb', + }) + ); + + app.get('/njs/image/:id', (req, res) => { + const p = snapshooter.getStoredImage(req.params.id); + if (p) { + res.contentType('image/png'); + res.end(p, 'binary'); + } else { + res.sendStatus('404'); + } + }); + + app.post('/njs/capture', async (req, res) => { + try { + const key = await snapshooter.captureImageOfSnapshot(req.body); + res.send({ + url: `/njs/image/${key}`, + }); + } catch (e) { + console.error(e); + res.sendStatus('500'); + } + }); + + app.get('/njs/snapshot/:id', (req, res) => { + const p = snapshooter.getStoredSnapshot(req.params.id); + if (p) { + res.json(p); + } else { + res.sendStatus('404'); + } + }); + + await new Promise(resolve => { + server = app.listen(port, () => { + console.log(`Running mashup server at localhost:${port}`); + resolve(); + }); + }); + + return { + url, + async close() { + server.close(); + await snapshooter.close(); + }, + }; +}; diff --git a/test/mashup/setup.int.js b/test/mashup/setup.int.js new file mode 100644 index 000000000..e08f12799 --- /dev/null +++ b/test/mashup/setup.int.js @@ -0,0 +1,22 @@ +const server = require('./server'); + +if (!process.env.BASE_URL) { + let s; + before(async () => { + s = await server(); + process.env.BASE_URL = s.url; + page.on('pageerror', e => { + console.error('Web: ', e.message); + }); + + page.on('console', msg => { + for (let i = 0; i < msg.args().length; ++i) { + console.log(`console ${msg.text()}`); + } + }); + }); + + after(async () => { + await s.close(); + }); +} diff --git a/test/mashup/snaps/configured.js b/test/mashup/snaps/configured.js new file mode 100644 index 000000000..907fa4ee1 --- /dev/null +++ b/test/mashup/snaps/configured.js @@ -0,0 +1,41 @@ +const pie = { + component: { + mounted(el) { + el.innerHTML = 'Hello pie'; // eslint-disable-line + }, + }, +}; + +const bar = function(env) { + env.translator.add({ + id: 'hello', + locale: { + 'sv-SE': 'Hej {0}!', + }, + }); + return { + component: { + mounted(el) { + el.innerHTML = `
${env.translator.get('hello', ['bar'])}
`; // eslint-disable-line + }, + }, + }; +}; + +// eslint-disable-next-line +const configured = nucleus.configured({ + theme: 'dark', + locale: { + language: 'sv-SE', + }, + types: [ + { + name: 'pie', + load: () => Promise.resolve(pie), + }, + { + name: 'bar', + load: () => Promise.resolve(bar), + }, + ], +}); diff --git a/test/mashup/snaps/single.html b/test/mashup/snaps/single.html new file mode 100644 index 000000000..ab0880a9b --- /dev/null +++ b/test/mashup/snaps/single.html @@ -0,0 +1,33 @@ + + + + + + + + + + + +
+ + + diff --git a/test/mashup/snaps/snapper.html b/test/mashup/snaps/snapper.html new file mode 100644 index 000000000..879ac10de --- /dev/null +++ b/test/mashup/snaps/snapper.html @@ -0,0 +1,29 @@ + + + + + Mashup + + + + + + +
+
+ + + diff --git a/test/mashup/snaps/snapper.int.js b/test/mashup/snaps/snapper.int.js new file mode 100644 index 000000000..1501d6654 --- /dev/null +++ b/test/mashup/snaps/snapper.int.js @@ -0,0 +1,37 @@ +const path = require('path'); +const jimp = require('jimp'); + +const differ = () => { + const artifacts = path.resolve(__dirname, '../__artifacts__/'); + + return { + async looksLike(name, captured) { + const file = `${path.basename(__dirname)}-${name}`; + const stored = await jimp.read(path.resolve(artifacts, 'baseline', file)); + const distance = jimp.distance(stored, captured); + const diff = jimp.diff(stored, captured); + if (distance > 0.001 || diff.percent > 0.001) { + captured.writeAsync(path.resolve(artifacts, 'regression', file)); + throw new Error(`Images differ too much - distance: ${distance}, percent: ${diff.percent}`); + } + }, + }; +}; + +const d = differ(); + +describe('snapper', () => { + const object = '[data-type="bar"]'; + const selector = `${object} .nebulajs-sn`; + it('should capture an image of a bar', async () => { + await page.goto(`${process.env.BASE_URL}/snaps/snapper.html`); + + await page.waitForSelector(selector, { visible: true }); + await page.click(selector); + await page.waitForSelector(`${object}[data-captured]`, { visible: true }); + const imgSrc = await page.$eval(`${object}[data-captured]`, el => el.getAttribute('data-captured')); + + const captured = await jimp.read(`${process.env.BASE_URL}${imgSrc}`); + await d.looksLike('bar.png', captured); + }); +}); diff --git a/test/mashup/snaps/snapper.js b/test/mashup/snaps/snapper.js new file mode 100644 index 000000000..8ce143f50 --- /dev/null +++ b/test/mashup/snaps/snapper.js @@ -0,0 +1,39 @@ +/* global configured */ +(() => { + document.querySelectorAll('.object').forEach(element => { + const type = element.getAttribute('data-type'); + const obj = { + getLayout: () => + Promise.resolve({ + qInfo: { + qId: 'id:', + }, + visualization: type, + }), + on() {}, + once() {}, + }; + + const app = { + createSessionObject: () => Promise.resolve({}), + getObject: () => Promise.resolve(obj), + }; + + const n = configured(app); + + n.create( + { + type, + }, + { + element, + } + ).then(viz => { + element.addEventListener('click', () => { + viz.exportImage().then(res => { + element.setAttribute('data-captured', res.url); + }); + }); + }); + }); +})(); diff --git a/yarn.lock b/yarn.lock index 5773b43c1..47636b7cf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2040,6 +2040,13 @@ dependencies: regenerator-runtime "^0.13.2" +"@babel/runtime@^7.7.2": + version "7.7.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.7.6.tgz#d18c511121aff1b4f2cd1d452f1bac9601dd830f" + integrity sha512-BWAJxpNVa0QlE5gZdWjSxXtemZyZ9RmrmVozxt3NUXeZhVIJ5ANyqmMc0JDrivBZyxUuQvFxlvH4OWWOogGfUw== + dependencies: + regenerator-runtime "^0.13.2" + "@babel/template@^7.1.0", "@babel/template@^7.2.2": version "7.2.2" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.2.2.tgz#005b3fdf0ed96e88041330379e0da9a708eb2907" @@ -2611,6 +2618,16 @@ bmp-js "^0.1.0" core-js "^2.5.7" +"@jimp/bmp@^0.9.3": + version "0.9.3" + resolved "https://registry.yarnpkg.com/@jimp/bmp/-/bmp-0.9.3.tgz#98eafc81674ce750f428ac9380007f1a4e90255e" + integrity sha512-wXZYccgGQAsIK8DZX0wZE3gbSd2mL2+eheSJMts6I5hQjxhVRZd1Gwu425nUQGzfKCOgKYTW0nLv7/8OoOTTkw== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.9.3" + bmp-js "^0.1.0" + core-js "^3.4.1" + "@jimp/core@^0.6.8": version "0.6.8" resolved "https://registry.yarnpkg.com/@jimp/core/-/core-0.6.8.tgz#6a41089792516f6e64a5302d12eb562aa7847c7b" @@ -2628,6 +2645,24 @@ pixelmatch "^4.0.2" tinycolor2 "^1.4.1" +"@jimp/core@^0.9.3": + version "0.9.3" + resolved "https://registry.yarnpkg.com/@jimp/core/-/core-0.9.3.tgz#bffbf955c046569bf4b682b575228e31bb41e445" + integrity sha512-kB9lvst1QhgYOC963SAuPgv+DdVfxTProphrSffAAoo5eLeQab/Ca3ZUeX1E/SnLSr+NGVnNCd8c9gyuKDiENg== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.9.3" + any-base "^1.1.0" + buffer "^5.2.0" + core-js "^3.4.1" + exif-parser "^0.1.12" + file-type "^9.0.0" + load-bmfont "^1.3.1" + mkdirp "0.5.1" + phin "^2.9.1" + pixelmatch "^4.0.2" + tinycolor2 "^1.4.1" + "@jimp/custom@^0.6.8": version "0.6.8" resolved "https://registry.yarnpkg.com/@jimp/custom/-/custom-0.6.8.tgz#0476d7b3f5da3121d98895a2e14f2899e602f2b6" @@ -2636,6 +2671,15 @@ "@jimp/core" "^0.6.8" core-js "^2.5.7" +"@jimp/custom@^0.9.3": + version "0.9.3" + resolved "https://registry.yarnpkg.com/@jimp/custom/-/custom-0.9.3.tgz#b49dfe1d6b24e62fd4101a7db77104024c8d97e8" + integrity sha512-2E7yabQMeqjcK8+ZFu3Ja5cWyrB0zv/pmzNSDg/BBPJ59HE0fj/qcERAz6VklcjHUYRUfmE5uODsb+4DE0o/YQ== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/core" "^0.9.3" + core-js "^3.4.1" + "@jimp/gif@^0.6.8": version "0.6.8" resolved "https://registry.yarnpkg.com/@jimp/gif/-/gif-0.6.8.tgz#848dd4e6e1a56ca2b3ce528969e44dfa99a53b14" @@ -2645,6 +2689,16 @@ core-js "^2.5.7" omggif "^1.0.9" +"@jimp/gif@^0.9.3": + version "0.9.3" + resolved "https://registry.yarnpkg.com/@jimp/gif/-/gif-0.9.3.tgz#b2b1a519092f94a913a955f252996f9a968930db" + integrity sha512-DshKgMQ8lXorI/xTRyeRkZqZ3JqgnL2aGYAhx0SkAunyHgXji27chmrOGj/6KVDBucrDf/6mSexnSoUDnlWrfA== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.9.3" + core-js "^3.4.1" + omggif "^1.0.9" + "@jimp/jpeg@^0.6.8": version "0.6.8" resolved "https://registry.yarnpkg.com/@jimp/jpeg/-/jpeg-0.6.8.tgz#4cad85a6d1e15759acb56bddef29aa3473859f2c" @@ -2654,6 +2708,16 @@ core-js "^2.5.7" jpeg-js "^0.3.4" +"@jimp/jpeg@^0.9.3": + version "0.9.3" + resolved "https://registry.yarnpkg.com/@jimp/jpeg/-/jpeg-0.9.3.tgz#a759cb3bccf3cb163166873b9bdc0c949c5991b5" + integrity sha512-AJzcTJXfN9BHtpzAbICwR3+GoH0pSr6OYXbAS6yuKwz+xVn9UHrEjQb74CIzIRqrT/VWcIKg29cMQxgokzWY7w== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.9.3" + core-js "^3.4.1" + jpeg-js "^0.3.4" + "@jimp/plugin-blit@^0.6.8": version "0.6.8" resolved "https://registry.yarnpkg.com/@jimp/plugin-blit/-/plugin-blit-0.6.8.tgz#646ebb631f35afc28c1e8908524bc43d1e9afa3d" @@ -2662,6 +2726,15 @@ "@jimp/utils" "^0.6.8" core-js "^2.5.7" +"@jimp/plugin-blit@^0.9.3": + version "0.9.3" + resolved "https://registry.yarnpkg.com/@jimp/plugin-blit/-/plugin-blit-0.9.3.tgz#740346ac62ec0f7ae4458f5fd59c7582e630a8e8" + integrity sha512-+UxCsJ3XkRSdpigpTBJ9WkdwUc3OtBlhVZdU6OL6M9ldume5Gj3rTyWvMCqytOK1tZ/+7HmxoWe4IWX31hz9qA== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.9.3" + core-js "^3.4.1" + "@jimp/plugin-blur@^0.6.8": version "0.6.8" resolved "https://registry.yarnpkg.com/@jimp/plugin-blur/-/plugin-blur-0.6.8.tgz#7b753ae94f6099103f57c268c3b2679047eefe95" @@ -2670,6 +2743,15 @@ "@jimp/utils" "^0.6.8" core-js "^2.5.7" +"@jimp/plugin-blur@^0.9.3": + version "0.9.3" + resolved "https://registry.yarnpkg.com/@jimp/plugin-blur/-/plugin-blur-0.9.3.tgz#9df505aaa63de138060264cf83ed4a98304bf105" + integrity sha512-RADcYjZ5vbk5ZrUiK7qv0G4xOpHtu19HWVVX9JTDbm4VByWTxPboVKlgiYLA6l+IxIXNtEqDclsADIM0s9FQhA== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.9.3" + core-js "^3.4.1" + "@jimp/plugin-color@^0.6.8": version "0.6.8" resolved "https://registry.yarnpkg.com/@jimp/plugin-color/-/plugin-color-0.6.8.tgz#4101cb1208879b331db6e43ea6b96eaf8dbaedbc" @@ -2679,6 +2761,16 @@ core-js "^2.5.7" tinycolor2 "^1.4.1" +"@jimp/plugin-color@^0.9.3": + version "0.9.3" + resolved "https://registry.yarnpkg.com/@jimp/plugin-color/-/plugin-color-0.9.3.tgz#4a5ad28f68901355878f5330186c260f4f87f944" + integrity sha512-gHDA5GVx4/R4fitEACKmWH7hNy0aU48MZWYRxmATvuqY39KidJ0fjwp+brQ3Ivgb35AgFVc2jQYc3U/JXv4RxQ== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.9.3" + core-js "^3.4.1" + tinycolor2 "^1.4.1" + "@jimp/plugin-contain@^0.6.8": version "0.6.8" resolved "https://registry.yarnpkg.com/@jimp/plugin-contain/-/plugin-contain-0.6.8.tgz#af95d33b63d0478943374ae15dd2607fc69cad14" @@ -2687,6 +2779,15 @@ "@jimp/utils" "^0.6.8" core-js "^2.5.7" +"@jimp/plugin-contain@^0.9.3": + version "0.9.3" + resolved "https://registry.yarnpkg.com/@jimp/plugin-contain/-/plugin-contain-0.9.3.tgz#d0da9892edea25549611c88e125bfcc59045c426" + integrity sha512-vdYAtp65LNDT/hMctow5o0a/SbD41/y7Z9AO7MGsfUIK92Woq90SNTWx7JplDl4HSZGrqaBONnfiEhRiYlDrdg== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.9.3" + core-js "^3.4.1" + "@jimp/plugin-cover@^0.6.8": version "0.6.8" resolved "https://registry.yarnpkg.com/@jimp/plugin-cover/-/plugin-cover-0.6.8.tgz#490e3186627a34d93cc015c4169bac9070d6ad17" @@ -2695,6 +2796,15 @@ "@jimp/utils" "^0.6.8" core-js "^2.5.7" +"@jimp/plugin-cover@^0.9.3": + version "0.9.3" + resolved "https://registry.yarnpkg.com/@jimp/plugin-cover/-/plugin-cover-0.9.3.tgz#2fca63620fcf8145bdecf315cf461588b09d9488" + integrity sha512-yOwsvakgyS2/C4iZF1a1wg63QKfYvqb2d6k+rgY/0vaAe44JtEx+Gbg+7iOt4EaMm5BDlxRwmcA2Q8Pef8TvAQ== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.9.3" + core-js "^3.4.1" + "@jimp/plugin-crop@^0.6.8": version "0.6.8" resolved "https://registry.yarnpkg.com/@jimp/plugin-crop/-/plugin-crop-0.6.8.tgz#ffec8951a2f3eccad1e3cff9afff5326bd980ce7" @@ -2703,6 +2813,15 @@ "@jimp/utils" "^0.6.8" core-js "^2.5.7" +"@jimp/plugin-crop@^0.9.3": + version "0.9.3" + resolved "https://registry.yarnpkg.com/@jimp/plugin-crop/-/plugin-crop-0.9.3.tgz#9b19c11293714a99c03d4b517ab597a5f88823e8" + integrity sha512-kqMXSyY8hrfo0idr6qY2USOWPrNqpDWs+D6Vwa+kV6SGJhj3rMTIcptQDaamIETSxbjkE8rwUu3K4Q5UD69D7w== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.9.3" + core-js "^3.4.1" + "@jimp/plugin-displace@^0.6.8": version "0.6.8" resolved "https://registry.yarnpkg.com/@jimp/plugin-displace/-/plugin-displace-0.6.8.tgz#89df05ab7daaff6befc190bb8ac54ec8d57e533b" @@ -2711,6 +2830,15 @@ "@jimp/utils" "^0.6.8" core-js "^2.5.7" +"@jimp/plugin-displace@^0.9.3": + version "0.9.3" + resolved "https://registry.yarnpkg.com/@jimp/plugin-displace/-/plugin-displace-0.9.3.tgz#07645687b29ebc8a8491244410172795d511ba21" + integrity sha512-0AdwxYRWDmJ2wIRIj2RR3sRmNjMhcy5Kwt9Jbi/RRnzxkRScZAiyzkNZhBul23EM7ClfjrUrZufuUvRMHxZRDw== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.9.3" + core-js "^3.4.1" + "@jimp/plugin-dither@^0.6.8": version "0.6.8" resolved "https://registry.yarnpkg.com/@jimp/plugin-dither/-/plugin-dither-0.6.8.tgz#17e5b9f56575a871e329fef8b388e614b92d84f8" @@ -2719,6 +2847,15 @@ "@jimp/utils" "^0.6.8" core-js "^2.5.7" +"@jimp/plugin-dither@^0.9.3": + version "0.9.3" + resolved "https://registry.yarnpkg.com/@jimp/plugin-dither/-/plugin-dither-0.9.3.tgz#292b3ee617a5dcfe065d13b643055e910f8b6934" + integrity sha512-8OE+Xak9xepiCwSV+oAsb/gupTnttG3aDKxtpSZjwHebnr+k1VG8NgICbMSFATTVJqqZ18oj6LC+5726qHUJ9w== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.9.3" + core-js "^3.4.1" + "@jimp/plugin-flip@^0.6.8": version "0.6.8" resolved "https://registry.yarnpkg.com/@jimp/plugin-flip/-/plugin-flip-0.6.8.tgz#153df0c677f79d4078bb9e4c1f2ac392b96dc3a1" @@ -2727,6 +2864,15 @@ "@jimp/utils" "^0.6.8" core-js "^2.5.7" +"@jimp/plugin-flip@^0.9.3": + version "0.9.3" + resolved "https://registry.yarnpkg.com/@jimp/plugin-flip/-/plugin-flip-0.9.3.tgz#a755ffa1d860106067215987cbac213501d22b41" + integrity sha512-w+lzE1ZF/UOjB8qJdeIm+dLQtOK1obZwGYdCIbgxZxw4SfkkjAftJdY8o8RNOXhHDZqGu+cYQZbMKP1zcoNkyQ== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.9.3" + core-js "^3.4.1" + "@jimp/plugin-gaussian@^0.6.8": version "0.6.8" resolved "https://registry.yarnpkg.com/@jimp/plugin-gaussian/-/plugin-gaussian-0.6.8.tgz#100abc7ae1f19fe9c09ed41625b475aae7c6093c" @@ -2735,6 +2881,15 @@ "@jimp/utils" "^0.6.8" core-js "^2.5.7" +"@jimp/plugin-gaussian@^0.9.3": + version "0.9.3" + resolved "https://registry.yarnpkg.com/@jimp/plugin-gaussian/-/plugin-gaussian-0.9.3.tgz#b10b5a5b4c37cb4edc3ed22a9b25294e68daf2f8" + integrity sha512-RPrWwzlZsbWC2opSgeyWt30JU9Uwg1+GwBnoNpEMLKeqm0Dv6snASASa4zVtviGWAIq//p3Jrap7g57hKqL0Cg== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.9.3" + core-js "^3.4.1" + "@jimp/plugin-invert@^0.6.8": version "0.6.8" resolved "https://registry.yarnpkg.com/@jimp/plugin-invert/-/plugin-invert-0.6.8.tgz#f40bfaa3b592d21ff14ede0e49aabec88048cad0" @@ -2743,6 +2898,15 @@ "@jimp/utils" "^0.6.8" core-js "^2.5.7" +"@jimp/plugin-invert@^0.9.3": + version "0.9.3" + resolved "https://registry.yarnpkg.com/@jimp/plugin-invert/-/plugin-invert-0.9.3.tgz#723a873133a1d62f9b93e023991f262c85917c78" + integrity sha512-0lRsh7IPkzyYqExrZDT50h38xdlB/+KrdiDcuxWwWyIlKauLMR0kInjwf8sPeb3elPLeETmze7uwPAxrIAtsGQ== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.9.3" + core-js "^3.4.1" + "@jimp/plugin-mask@^0.6.8": version "0.6.8" resolved "https://registry.yarnpkg.com/@jimp/plugin-mask/-/plugin-mask-0.6.8.tgz#e64405f7dacf0672bff74f3b95b724d9ac517f86" @@ -2751,6 +2915,15 @@ "@jimp/utils" "^0.6.8" core-js "^2.5.7" +"@jimp/plugin-mask@^0.9.3": + version "0.9.3" + resolved "https://registry.yarnpkg.com/@jimp/plugin-mask/-/plugin-mask-0.9.3.tgz#6329ec861269244ab10ab9b3f54b1624c4ce0bab" + integrity sha512-nZ0J62Hly9JtMZctlSDVgnTd8Fg2XGikzAYilSTCjzIRtbXL5Be/qSAZrMfLD3CZ8exTxdlEGRkEJI3RZKXYCw== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.9.3" + core-js "^3.4.1" + "@jimp/plugin-normalize@^0.6.8": version "0.6.8" resolved "https://registry.yarnpkg.com/@jimp/plugin-normalize/-/plugin-normalize-0.6.8.tgz#a0180f2b8835e3638cdc5e057b44ac63f60db6ba" @@ -2759,6 +2932,15 @@ "@jimp/utils" "^0.6.8" core-js "^2.5.7" +"@jimp/plugin-normalize@^0.9.3": + version "0.9.3" + resolved "https://registry.yarnpkg.com/@jimp/plugin-normalize/-/plugin-normalize-0.9.3.tgz#564155032d1b9dc567dbb7427a85606a25427c30" + integrity sha512-0IvgTt4R15QJnoCHvvqlK56zOtCsQV7Mkx757kdNah8uyPGjadTcFBuqCaOMK943X36IIv+o7Ix7yvNUJZt4aw== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.9.3" + core-js "^3.4.1" + "@jimp/plugin-print@^0.6.8": version "0.6.8" resolved "https://registry.yarnpkg.com/@jimp/plugin-print/-/plugin-print-0.6.8.tgz#66309549e01896473111e3a0ad2cee428638bd6e" @@ -2768,6 +2950,16 @@ core-js "^2.5.7" load-bmfont "^1.4.0" +"@jimp/plugin-print@^0.9.3": + version "0.9.3" + resolved "https://registry.yarnpkg.com/@jimp/plugin-print/-/plugin-print-0.9.3.tgz#b4470137312232de9b35eaf412cd753f999c58d8" + integrity sha512-pV6oX5Bhe9O/dbgrotz46Bv6u1M+/n9G0kRUunDjwzXrvON5raBFEJHQDPcTXiqPT25Gc9Ba4/Akfo/Zl6+wgQ== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.9.3" + core-js "^3.4.1" + load-bmfont "^1.4.0" + "@jimp/plugin-resize@^0.6.8": version "0.6.8" resolved "https://registry.yarnpkg.com/@jimp/plugin-resize/-/plugin-resize-0.6.8.tgz#c26d9a973f7eec51ad9018fcbbac1146f7a73aa0" @@ -2776,6 +2968,15 @@ "@jimp/utils" "^0.6.8" core-js "^2.5.7" +"@jimp/plugin-resize@^0.9.3": + version "0.9.3" + resolved "https://registry.yarnpkg.com/@jimp/plugin-resize/-/plugin-resize-0.9.3.tgz#916abd57c4f9b426984354c77555ade1efda7a82" + integrity sha512-YzqVE8QoDIZpVuI52v+WejwEjEEiJfNFviQfprfm5af7uSSseZgDw1sJ0koqAu+liMSY+Ewp79v2SDrKoJKqtg== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.9.3" + core-js "^3.4.1" + "@jimp/plugin-rotate@^0.6.8": version "0.6.8" resolved "https://registry.yarnpkg.com/@jimp/plugin-rotate/-/plugin-rotate-0.6.8.tgz#2afda247984eeebed95c1bb1b13ccd3be5973299" @@ -2784,6 +2985,15 @@ "@jimp/utils" "^0.6.8" core-js "^2.5.7" +"@jimp/plugin-rotate@^0.9.3": + version "0.9.3" + resolved "https://registry.yarnpkg.com/@jimp/plugin-rotate/-/plugin-rotate-0.9.3.tgz#aa0d674c08726c0ae3ebc7f2adbfca0a927b1d9f" + integrity sha512-kADY2pI3/yMyHbuyvKB4nqPoKf8DPQBU1b4zz2K7SxcwKh1krFf4Fa9mmhhDLoFwuNSy0SPb1JCMUO4BtFCFLA== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.9.3" + core-js "^3.4.1" + "@jimp/plugin-scale@^0.6.8": version "0.6.8" resolved "https://registry.yarnpkg.com/@jimp/plugin-scale/-/plugin-scale-0.6.8.tgz#5de403345859bb0b30bf3e242dedd8ceb6ecb96c" @@ -2792,6 +3002,15 @@ "@jimp/utils" "^0.6.8" core-js "^2.5.7" +"@jimp/plugin-scale@^0.9.3": + version "0.9.3" + resolved "https://registry.yarnpkg.com/@jimp/plugin-scale/-/plugin-scale-0.9.3.tgz#427fed7642883c27601aae33c25413980b6a2c50" + integrity sha512-vZaiL5Qc+WrgGEfUe4Y0vG+qbT6pe2TW68/mu124E1tKVcZjHKZUeFN0Wr/hP2myN6nqTYj0/sord2OS/04JpA== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.9.3" + core-js "^3.4.1" + "@jimp/plugins@^0.6.8": version "0.6.8" resolved "https://registry.yarnpkg.com/@jimp/plugins/-/plugins-0.6.8.tgz#5618170a986ced1ea795adcd9376122f2543b856" @@ -2817,6 +3036,32 @@ core-js "^2.5.7" timm "^1.6.1" +"@jimp/plugins@^0.9.3": + version "0.9.3" + resolved "https://registry.yarnpkg.com/@jimp/plugins/-/plugins-0.9.3.tgz#bdff9d49484469c4d74ef47c2708e75773ca22b9" + integrity sha512-KYCSgFGoZBNC0224X5yUnMHCZnCdUVrsu2Yo67o3XZfUgDjO81J+vdzZ0twpPQ6qLLVAP+nQ8hkRV/QzEUstMw== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/plugin-blit" "^0.9.3" + "@jimp/plugin-blur" "^0.9.3" + "@jimp/plugin-color" "^0.9.3" + "@jimp/plugin-contain" "^0.9.3" + "@jimp/plugin-cover" "^0.9.3" + "@jimp/plugin-crop" "^0.9.3" + "@jimp/plugin-displace" "^0.9.3" + "@jimp/plugin-dither" "^0.9.3" + "@jimp/plugin-flip" "^0.9.3" + "@jimp/plugin-gaussian" "^0.9.3" + "@jimp/plugin-invert" "^0.9.3" + "@jimp/plugin-mask" "^0.9.3" + "@jimp/plugin-normalize" "^0.9.3" + "@jimp/plugin-print" "^0.9.3" + "@jimp/plugin-resize" "^0.9.3" + "@jimp/plugin-rotate" "^0.9.3" + "@jimp/plugin-scale" "^0.9.3" + core-js "^3.4.1" + timm "^1.6.1" + "@jimp/png@^0.6.8": version "0.6.8" resolved "https://registry.yarnpkg.com/@jimp/png/-/png-0.6.8.tgz#ee06cf078b381137ec7206c4bb1b4cfcbe15ca6f" @@ -2826,6 +3071,16 @@ core-js "^2.5.7" pngjs "^3.3.3" +"@jimp/png@^0.9.3": + version "0.9.3" + resolved "https://registry.yarnpkg.com/@jimp/png/-/png-0.9.3.tgz#5c1bbb89b32e2332891a13efdb423e87287a8321" + integrity sha512-LJXUemDTSbTGAGEp9hNQH0uTRSB8gYeE6FsfT3M00oZincu6/WzDzl0P8E95rMjNxZqAihdTyOP3+kcrbbqX+w== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/utils" "^0.9.3" + core-js "^3.4.1" + pngjs "^3.3.3" + "@jimp/tiff@^0.6.8": version "0.6.8" resolved "https://registry.yarnpkg.com/@jimp/tiff/-/tiff-0.6.8.tgz#79bd22ed435edbe29d02a2c8c9bf829f988ebacc" @@ -2834,6 +3089,15 @@ core-js "^2.5.7" utif "^2.0.1" +"@jimp/tiff@^0.9.3": + version "0.9.3" + resolved "https://registry.yarnpkg.com/@jimp/tiff/-/tiff-0.9.3.tgz#a4498c0616fb24034f5512b159b75b0aea389e9c" + integrity sha512-w9H6dT+GDHN//Srsv27JhRn7R2byzUahOGfFw7KpIn95jg0ogcxjKTo/RAGQC56sr4U092e4Npl7E85Lt934WQ== + dependencies: + "@babel/runtime" "^7.7.2" + core-js "^3.4.1" + utif "^2.0.1" + "@jimp/types@^0.6.8": version "0.6.8" resolved "https://registry.yarnpkg.com/@jimp/types/-/types-0.6.8.tgz#4510eb635cd00b201745d70e38f791748baa7075" @@ -2847,6 +3111,20 @@ core-js "^2.5.7" timm "^1.6.1" +"@jimp/types@^0.9.3": + version "0.9.3" + resolved "https://registry.yarnpkg.com/@jimp/types/-/types-0.9.3.tgz#75337245a1a8c7c84a414beca3cfeded338c0ef1" + integrity sha512-hUJKoT2IhnbO/trxNWzN19n8g+p7aKbM1R+71n4wMZnD41PzrVtz+sBBCdB+JCjBJs/i7fJt4d9z0i3Xe8m7Zw== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/bmp" "^0.9.3" + "@jimp/gif" "^0.9.3" + "@jimp/jpeg" "^0.9.3" + "@jimp/png" "^0.9.3" + "@jimp/tiff" "^0.9.3" + core-js "^3.4.1" + timm "^1.6.1" + "@jimp/utils@^0.6.8": version "0.6.8" resolved "https://registry.yarnpkg.com/@jimp/utils/-/utils-0.6.8.tgz#09f794945631173567aa50f72ac28170de58a63d" @@ -2854,6 +3132,14 @@ dependencies: core-js "^2.5.7" +"@jimp/utils@^0.9.3": + version "0.9.3" + resolved "https://registry.yarnpkg.com/@jimp/utils/-/utils-0.9.3.tgz#fd7af0d1138febbeacc841be4b802218444ce088" + integrity sha512-9D2Of6BcjYONtl77YfmU2y5aRMLe0/O2e2aQvfCxdNwD33jRdwNdN4i3m73dpiClNquApIjL4nYGhTixA4UstA== + dependencies: + "@babel/runtime" "^7.7.2" + core-js "^3.4.1" + "@lerna/add@3.19.0": version "3.19.0" resolved "https://registry.yarnpkg.com/@lerna/add/-/add-3.19.0.tgz#33b6251c669895f842c14f05961432d464166249" @@ -6733,6 +7019,11 @@ core-js@^3.0.1, core-js@^3.0.4: resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.3.6.tgz#6ad1650323c441f45379e176ed175c0d021eac92" integrity sha512-u4oM8SHwmDuh5mWZdDg9UwNVq5s1uqq6ZDLLIs07VY+VJU91i3h4f3K/pgFvtUQPGdeStrZ+odKyfyt4EnKHfA== +core-js@^3.4.1: + version "3.4.8" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.4.8.tgz#e0fc0c61f2ef90cbc10c531dbffaa46dfb7152dd" + integrity sha512-b+BBmCZmVgho8KnBUOXpvlqEMguko+0P+kXCwD4vIprsXC6ht1qgPxtb1OK6XgSlrySF71wkwBQ0Hv695bk9gQ== + core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -10501,6 +10792,18 @@ jimp@0.6.8: core-js "^2.5.7" regenerator-runtime "^0.13.3" +jimp@^0.9.3: + version "0.9.3" + resolved "https://registry.yarnpkg.com/jimp/-/jimp-0.9.3.tgz#85e8e80eea65a7e6de806c6bb622ec6a7244e6f3" + integrity sha512-dIxvT1OMRkd3+B18XUhJ5WZ2Dw7Hp8mvjaTqfi945zZ7fga6LT22h3NLYDorHHAiy9z30KjfNnOgpBoxrdjDZg== + dependencies: + "@babel/runtime" "^7.7.2" + "@jimp/custom" "^0.9.3" + "@jimp/plugins" "^0.9.3" + "@jimp/types" "^0.9.3" + core-js "^3.4.1" + regenerator-runtime "^0.13.3" + jpeg-js@^0.3.4: version "0.3.4" resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.3.4.tgz#dc2ba501ee3d58b7bb893c5d1fab47294917e7e7"