From 38b1aff5ca271571b8842b543b534652b55af9e1 Mon Sep 17 00:00:00 2001 From: techninja Date: Tue, 23 Jun 2020 16:12:06 -0700 Subject: [PATCH 001/147] Add first draft promise error handler callback for REST requests --- .../core/comms/api/cncserver.api.content.js | 20 ++------------- .../core/comms/api/cncserver.api.projects.js | 21 ++-------------- src/components/core/comms/cncserver.rest.js | 25 +++++++++++++++++++ 3 files changed, 29 insertions(+), 37 deletions(-) diff --git a/src/components/core/comms/api/cncserver.api.content.js b/src/components/core/comms/api/cncserver.api.content.js index 052024e..3cd9172 100644 --- a/src/components/core/comms/api/cncserver.api.content.js +++ b/src/components/core/comms/api/cncserver.api.content.js @@ -24,15 +24,7 @@ module.exports = (cncserver) => { .then(body => content.normalizeInput(body)) .then(content.addItem) .then((item) => { res.status(200).send(item); }) - .catch((err) => { - const errBody = { - status: 'error', - message: err.message || err, - }; - - if (err.stack) errBody.stack = err.stack.split('\n'); - res.status(406).send(errBody); - }); + .catch(cncserver.rest.err(res)); return true; // Tell endpoint wrapper we'll handle the response } @@ -74,15 +66,7 @@ module.exports = (cncserver) => { cncserver.schemas.validateData('content', mergedItem) .then(() => content.editItem(item, req.body, mergedItem)) .then((finalItem) => { res.status(200).send(finalItem); }) - .catch((err) => { - const errBody = { - status: 'error', - message: err.message || err, - }; - - if (err.stack) errBody.stack = err.stack.split('\n'); - res.status(406).send(errBody); - }); + .catch(cncserver.rest.err(res)); return true; // Tell endpoint wrapper we'll handle the response } diff --git a/src/components/core/comms/api/cncserver.api.projects.js b/src/components/core/comms/api/cncserver.api.projects.js index 7bd0f9c..355d40e 100644 --- a/src/components/core/comms/api/cncserver.api.projects.js +++ b/src/components/core/comms/api/cncserver.api.projects.js @@ -26,16 +26,7 @@ module.exports = (cncserver) => { cncserver.schemas.validateData('projects', req.body, true) .then(body => projects.addItem(body)) .then((item) => { res.status(200).send(item); }) - .catch((err) => { - console.error('Error on Projects request:', err); - const errBody = { - status: 'error', - message: err, - }; - - if (err.stack) errBody.stack = err.stack; - res.status(406).send(errBody); - }); + .catch(cncserver.rest.err(res)); return true; // Tell endpoint wrapper we'll handle the response } @@ -105,15 +96,7 @@ module.exports = (cncserver) => { cncserver.schemas.validateData('projects', mergedProject) .then(() => projects.editItem(project, req.body)) .then((item) => { res.status(200).send(item); }) - .catch((err) => { - const errBody = { - status: 'error', - message: err, - }; - - if (err.stack) errBody.stack = err.stack; - res.status(406).send(errBody); - }); + .catch(cncserver.rest.err(res)); return true; // Tell endpoint wrapper we'll handle the response } diff --git a/src/components/core/comms/cncserver.rest.js b/src/components/core/comms/cncserver.rest.js index 54db859..a831449 100644 --- a/src/components/core/comms/cncserver.rest.js +++ b/src/components/core/comms/cncserver.rest.js @@ -101,6 +101,31 @@ module.exports = (cncserver) => { }); }; + /** + * Standardized response error handler (to be passed to promise catches). + * + * The intent is to handle all error objects and give something useful to the + * client in the message and associated objects. This is curried to hand the catch a + * new function once we get the local response object. + * + * @param {HTTP Response} res + * Specific request response object. + * @param {number} code + * Response code to use. + * + * @returns {function} + * Catch callback that takes a single error object as arg. + */ + rest.err = (res, code = 406) => (err) => { + const errBody = { + status: 'error', + message: err.message || err, + }; + + if (err.stack) errBody.stack = err.stack.split('\n'); + res.status(code).send(errBody); + }; + // Exports. rest.exports = { createStaticEndpoint: rest.createStaticEndpoint, From 41ea88d864b2bf59c7a8c36d7bea71296806d89d Mon Sep 17 00:00:00 2001 From: techninja Date: Tue, 23 Jun 2020 16:38:27 -0700 Subject: [PATCH 002/147] Simplify project api promise workflow --- src/components/core/comms/api/cncserver.api.projects.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/core/comms/api/cncserver.api.projects.js b/src/components/core/comms/api/cncserver.api.projects.js index 355d40e..85da409 100644 --- a/src/components/core/comms/api/cncserver.api.projects.js +++ b/src/components/core/comms/api/cncserver.api.projects.js @@ -24,7 +24,7 @@ module.exports = (cncserver) => { if (req.route.method === 'post') { // Validate the request data against the schema before continuing. cncserver.schemas.validateData('projects', req.body, true) - .then(body => projects.addItem(body)) + .then(projects.addItem) .then((item) => { res.status(200).send(item); }) .catch(cncserver.rest.err(res)); From 1b2da09fe3ce48db94d212e6babfac5e8fcb406c Mon Sep 17 00:00:00 2001 From: techninja Date: Wed, 24 Jun 2020 09:57:31 -0700 Subject: [PATCH 003/147] Finish out canvas-print documentation --- .../widgets/canvas/canvas-print.mjs | 52 +++++++++++-------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/src/interface/modules/components/widgets/canvas/canvas-print.mjs b/src/interface/modules/components/widgets/canvas/canvas-print.mjs index 6de3328..44d08bb 100644 --- a/src/interface/modules/components/widgets/canvas/canvas-print.mjs +++ b/src/interface/modules/components/widgets/canvas/canvas-print.mjs @@ -13,24 +13,6 @@ let destinationPos = {}; let stepsPerMM = null; let tempPosition = null; -// TODO: Does this go here? Probably not. -/* -function getColorName(index) { - return document.querySelector('panel-colors').items.filter(({ name }) => name === index)[0].title; -} - -// Catch when it's time to manually swap pen over. -cncserver.socket.on('manualswap trigger', ({ index }) => { - const message = `We are now ready to draw with ${getColorName(index)}. When it's in and ready, click ok.`; - - // eslint-disable-next-line no-alert - if (window.confirm(message)) { - cncserver.api.tools.change('manualresume'); - } -}); - -*/ - /** * Util function to get x/y steps converted to MM. * @@ -44,7 +26,12 @@ function stepsToPaper({ x, y }) { return [x / stepsPerMM.x, y / stepsPerMM.y]; } -// Initialize print specific canvas stuff. +/** + * Initialize print specific canvas stuff, called from init() -> paperInit(). + * + * @param {Hybrids} host + * Host object to operate on. + */ function initPrint(host) { host.canvas.scope.activate(); host.canvas.layers.overlay.activate(); @@ -76,7 +63,12 @@ function initPrint(host) { } } -// Bind socket to pen update positions. +/** + * Bind socket to pen update positions, called from init()->apiInit(). + * + * @param {Hybrids} host + * Host object to operate on. + */ function bindSocketPosition(host) { cncserver.socket.on('pen update', ({ x, y, lastDuration: dur }) => { if (stepsPerMM) { @@ -87,8 +79,16 @@ function bindSocketPosition(host) { }); } -// What happens when position changes from sockets? -export function positionChangeFactory(defaultPos = { pos: [0, 0], dur: 0 }) { +/** + * Position change factory for updates from socket. + * + * @param {object} [defaultPos={ pos: [0, 0], dur: 0 }] + * The new next position and duration for how long it should take to get there in ms. + * + * @returns + * Hybrids factory for managing host method state. + */ +function positionChangeFactory(defaultPos = { pos: [0, 0], dur: 0 }) { return { set: (host, value) => { // Are we ready to set the value? @@ -111,6 +111,14 @@ export function positionChangeFactory(defaultPos = { pos: [0, 0], dur: 0 }) { }; } +/** + * Paper canvas init event trigger. + * + * @param {Hybrids} host + * Host object to operate on. + * @param {object} { detail } + * Detail object containing reference to the paper-canvas host object. + */ function init(host, { detail }) { // Set the canvas host.canvas = detail.host; From 87629380783cb213c5242a1cc0f93465fd6b6f4d Mon Sep 17 00:00:00 2001 From: techninja Date: Wed, 24 Jun 2020 11:26:08 -0700 Subject: [PATCH 004/147] Adjust for linting, add project TODOs --- .../core/comms/api/cncserver.api.projects.js | 11 +++++++++-- src/components/core/control/cncserver.projects.js | 2 ++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/components/core/comms/api/cncserver.api.projects.js b/src/components/core/comms/api/cncserver.api.projects.js index 85da409..a39897c 100644 --- a/src/components/core/comms/api/cncserver.api.projects.js +++ b/src/components/core/comms/api/cncserver.api.projects.js @@ -55,7 +55,10 @@ module.exports = (cncserver) => { projects.setPrintingState(printing); } else { // Nothing sent right, let em know. - return [406, 'Patch either "current" hash to open, or new "rendering" or "printing" state.']; + return [ + 406, + 'Patch either "current" hash to open, or new "rendering" or "printing" state.', + ]; } // Return the full new projects return body. @@ -104,7 +107,11 @@ module.exports = (cncserver) => { // Remove item. if (req.route.method === 'delete') { projects.removeItem(hash).then(() => { - res.status(200).send({ status: `Project identified by "${hash}" moved to trash directory` }); + res + .status(200) + .send({ + status: `Project identified by "${hash}" moved to trash directory`, + }); }).catch((err) => { const errBody = { status: 'error', diff --git a/src/components/core/control/cncserver.projects.js b/src/components/core/control/cncserver.projects.js index 8ce5ed4..a9d84c3 100644 --- a/src/components/core/control/cncserver.projects.js +++ b/src/components/core/control/cncserver.projects.js @@ -50,6 +50,7 @@ function loadProjects() { function initProject() { const now = new Date(); // Create a temp project or load last. + // TODO: When deleting an open project, default to this. projects.addItem({ title: 'New Project', description: `Automatic project created ${now.toLocaleDateString()}`, @@ -273,6 +274,7 @@ module.exports = (cncserver) => { // Remove a project. projects.removeItem = hash => new Promise((resolve, reject) => { + // TODO: When deleting an open project, default to this. const project = projects.items.get(hash); const trashDir = path.resolve(utils.getUserDir('trash'), `project-${project.name}-${hash}`); fs.rename(project.dir, trashDir, (err) => { From becad26c7e1b0e72bc64c9a00b0c1c57a010ac18 Mon Sep 17 00:00:00 2001 From: techninja Date: Wed, 24 Jun 2020 14:43:32 -0700 Subject: [PATCH 005/147] Initial implementation of new colorset API with schema --- .../core/comms/api/cncserver.api.colors.js | 56 ++-- .../core/drawing/cncserver.drawing.colors.js | 258 +++++++++++++----- .../cncserver.schemas.color.implement.js | 45 +++ .../core/schemas/cncserver.schemas.color.js | 41 +++ .../core/schemas/cncserver.schemas.colors.js | 38 +++ .../core/schemas/cncserver.schemas.js | 10 +- src/components/core/schemas/index.js | 15 +- 7 files changed, 352 insertions(+), 111 deletions(-) create mode 100644 src/components/core/schemas/cncserver.schemas.color.implement.js create mode 100644 src/components/core/schemas/cncserver.schemas.color.js create mode 100644 src/components/core/schemas/cncserver.schemas.colors.js diff --git a/src/components/core/comms/api/cncserver.api.colors.js b/src/components/core/comms/api/cncserver.api.colors.js index cdce1fc..6bbd093 100644 --- a/src/components/core/comms/api/cncserver.api.colors.js +++ b/src/components/core/comms/api/cncserver.api.colors.js @@ -4,49 +4,41 @@ const handlers = {}; module.exports = (cncserver) => { - handlers['/v2/colors'] = (req) => { + handlers['/v2/colors'] = (req, res) => { const { drawing: { colors } } = cncserver; - if (req.route.method === 'get') { // Get current colorset and + // Standard post resolve for colors requests. + function postResolve() { + res.status(200).send({ set: colors.getCurrentSet(), presets: colors.presets }); + } + + // Get current colorset and presets. + if (req.route.method === 'get') { return { code: 200, body: { - set: colors.set, + set: colors.getCurrentSet(), presets: colors.listPresets(req.t), }, }; } - if (req.route.method === 'post') { // Set color/preset + // Add color/set from preset. + if (req.route.method === 'post') { + // Adding preset, only err here is 404 preset not found. if (req.body.preset) { - if (!colors.applyPreset(req.body.preset)) { - return { - code: 404, - body: { - status: `Preset with id of '${req.body.preset}' not found in preset list.`, - validOptions: Object.keys(colors.presets), - }, - }; - } - } else if (!req.body.id || !req.body.name || !req.body.color) { - return [ - 406, - 'Must include valid id, name and color, see color documentation API docs', - ]; - } else if (!colors.add(req.body)) { - return [ - 406, - `Color with id ${req.body.id} already exists, update it directly or change id`, - ]; + colors.applyPreset(req.body.preset, req.t) + .then(postResolve) + .catch(cncserver.rest.err(res, 404)); + } else { + // Validate data and add color item, or error out. + cncserver.schemas.validateData('color', req.body, true) + .then(colors.add) + .then(postResolve) + .catch(cncserver.rest.err(res)); } - return { - code: 200, - body: { - set: colors.set, - presets: colors.presets, - }, - }; + return true; // Tell endpoint wrapper we'll handle the POST response. } return false; @@ -74,13 +66,13 @@ module.exports = (cncserver) => { } // Update color info - if (req.route.method === 'put') { + if (req.route.method === 'patch') { return { code: 200, body: colors.update(colorID, req.body) }; } // Delete color if (req.route.method === 'delete') { - colors.delete(color); + colors.delete(colorID); return { code: 200, body: { set: colors.set, presets: colors.presets } }; } diff --git a/src/components/core/drawing/cncserver.drawing.colors.js b/src/components/core/drawing/cncserver.drawing.colors.js index 20251e1..2cd25be 100644 --- a/src/components/core/drawing/cncserver.drawing.colors.js +++ b/src/components/core/drawing/cncserver.drawing.colors.js @@ -1,34 +1,85 @@ /** - * @file Code for drawing color and "tool" change management. - */ +* @file Code for drawing color and "tool" change management. +*/ + +// Paper.js is all about that parameter reassignment life. +/* eslint-disable no-param-reassign */ + const glob = require('glob'); const path = require('path'); const nc = require('nearest-color'); const { Color } = require('paper'); -const defaultColor = { id: 'color0', name: 'Black', color: '#000000' }; -const ignoreWhite = { id: 'ignore', name: 'White', color: '#FFFFFF' }; +// Default assumed color. +const defaultColor = { + id: 'color0', + name: 'Black', + color: '#000000', + weight: 0, + implement: { + type: 'inherit', + width: 1, + length: 0, + stiffness: 1, + }, +}; + +// Default ignored white. +const ignoreWhite = { + id: 'ignore', + name: 'White', + color: '#FFFFFF', + weight: -0.5, // Let other colors select before this. + implement: { + type: 'inherit', + width: 0, + length: 0, + stiffness: 1, + }, +}; + +// Default internal preset. const defaultPreset = { manufacturer: 'default', media: 'pen', machineName: 'default', weight: -10, + width: 1, // 1mm implement size. colors: { black: '#000000' }, }; -const colors = { id: 'drawing.colors', presets: { default: defaultPreset }, set: [] }; + +// Final export object. +const colors = { + id: 'drawing.colors', + presets: { default: defaultPreset }, + set: { // Set via initial preset or colorset loader. + name: '', // Machine name for loading/folder storage + title: '', // Clean name. + description: '', // Description of what it is beyond title. + implement: { + type: 'pen', + width: 1, + length: 0, + stiffness: 1, + }, + items: new Map(), // Items mapped by id. + }, +}; + +// Location of internal colorset presets. const presetDir = path.resolve( global.__basedir, 'components', 'core', 'drawing', 'colorsets' ); -module.exports = (cncserver, drawing) => { +module.exports = (cncserver) => { // What is this and why? // // When we draw, we assume a color: color0. It's assumed this is "black", but // if there's only one color in use, no color switching will occur so this // definition is moot unless we have more than one color in our set. // - // A "colorset" is a set of colors that can be applied to the available colors - // in `cncserver.drawing.colors.set` + // A "colorset" is a set of colors/implements that can be applied to the available items + // in `cncserver.drawing.colors.set.items` // Load all color presets into the presets key. const files = glob.sync(path.join(presetDir, '*.json')); @@ -72,54 +123,58 @@ module.exports = (cncserver, drawing) => { return out; }; - colors.getIDs = () => { - const items = []; - colors.set.forEach(({ id }) => { - items.push(id); - }); - return items; - }; + /** + * Get a flat list of valid colorset key ids. + * + * @returns {array} + * Array of colorset item keys, empty array if none. + */ + colors.getIDs = () => Array.from(colors.set.items.keys()); - colors.getIndex = (findID) => { - let findIndex = null; - colors.set.forEach(({ id }, index) => { - if (id === findID) { - findIndex = index; - } - }); - return findIndex; - }; - colors.delete = ({ id }) => { - const index = colors.getIndex(id); - colors.set.splice(index, 1); + // Delete a color by id. + colors.delete = (id) => { + colors.set.items.delete(id); - if (colors.set.length === 0) { - colors.set.push(defaultColor); + if (colors.set.items.size === 0) { + colors.set.items.set(defaultColor.id, defaultColor); } cncserver.sockets.sendPaperUpdate(); }; - colors.add = ({ id, name, color }) => { - if (colors.getIndex(id) === null) { - colors.set.push({ id, name, color }); + // Add a color by all of its info, appended to the end. + colors.add = ({ id, ...item }) => new Promise((resolve, reject) => { + if (!colors.getColor(id)) { + colors.set.items.set(id, { id, ...item }); cncserver.sockets.sendPaperUpdate(); - return true; + resolve(); + } else { + reject( + new Error(`Color with id "${id}" already exists, update it directly or change id`) + ); } - return null; - }; + }); - colors.update = (id, { name, color }) => { - const index = colors.getIndex(id); - colors.set[index] = { id, name, color }; + // Update a color by id with all its info. + colors.update = (id, { name, color, size }) => { + colors.set.items.set(id, { + id, name, color, size, + }); cncserver.sockets.sendPaperUpdate(); - return colors.set[index]; + return colors.set.items.get(id); }; - colors.getColor = (findID) => { - let color = colors.getIndex(findID); - if (color !== null) color = colors.set[color]; - return color; + // Get non-reference copy of colorset item by id. + colors.getColor = (id, applyInheritance = false) => { + const item = { ...colors.set.items.get(id) }; + item.implement = { ...colors.set.items.get(id).implement }; + + // If the implementor wants it, and the item wants inheritance... + if (applyInheritance && item.implement.type === 'inherit') { + item.implement = { ...colors.set.implement }; + } + + return item; }; /** @@ -130,15 +185,20 @@ module.exports = (cncserver, drawing) => { * @returns {boolean} * Null for failure, true if success. */ - colors.applyPreset = (presetName) => { - const preset = colors.setFromPreset(presetName); + colors.applyPreset = (presetName, t) => new Promise((resolve, reject) => { + const preset = colors.setFromPreset(presetName, t); if (preset) { colors.set = preset; cncserver.sockets.sendPaperUpdate(); - return true; + resolve(); + } else { + const validOptions = Object.keys(colors.presets).join(', '); + reject(new Error( + cncserver.utils.singleLineString`Preset with id of '${presetName}' not found + in preset list. Must be one of [${validOptions}]` + )); } - return null; - }; + }); /** * Get colorset array from a preset name. @@ -148,34 +208,66 @@ module.exports = (cncserver, drawing) => { * @returns {array} * Colorset style array with default toolnames */ - colors.setFromPreset = (presetName) => { - if (colors.presets[presetName]) { - const set = []; - Object.entries(colors.presets[presetName].colors).forEach(([name, color]) => { - set.push({ - id: `color${set.length}`, - name, // cncserver.i18n.t(`colorsets:colors.${name}`), + colors.setFromPreset = (presetName, t = s => s) => { + const preset = colors.presets[presetName]; + const { schemas } = cncserver; + if (preset) { + const presetInfo = colors.listPresets(t)[presetName]; + const colorset = schemas.getDataDefault('colors', { + name: presetName, + title: presetInfo.name, + description: presetInfo.description, + implement: { + type: preset.media === 'pen' ? 'pen' : 'brush', + width: preset.media === 'pen' ? 1 : 3, // Size 3 crayola brush. + length: preset.media === 'pen' ? 0 : 10.75, // Size 3 crayola brush. + stiffness: preset.media === 'pen' ? 1 : 0.25, // Soft brush! + }, + }); + colorset.items = new Map(); + + Object.entries(preset.colors).forEach(([name, color]) => { + const id = `color${colorset.items.size}`; + colorset.items.set(id, schemas.getDataDefault('color', { + id, + name: t(`colorsets:colors.${name}`), color, - }); + })); }); // TODO: Allow this to be set somewhere? - set.push({ - ...ignoreWhite, - // name: cncserver.i18n.t('colorsets:colors.white'), - }); - return set; + colorset.items.set(ignoreWhite.id, { ...ignoreWhite }); + return colorset; } return null; }; + // Get the current colorset as a JSON ready object. + colors.getCurrentSet = () => ({ + ...colors.set, items: Array.from(colors.set.items.values()), + }); + /** * Run at setup, allows machine specific colorset defaults. */ colors.setDefault = () => { - const defaultSet = cncserver.binder.trigger('colors.setDefault', [defaultColor, ignoreWhite]); - colors.set = defaultSet; + // Trigger on schema loaded for schema & validation defaults. + cncserver.binder.bindTo('schemas.loaded', colors.id, () => { + let defaultSet = cncserver.schemas.getDataDefault('colors', { + name: 'default', + title: 'Default Set', + description: '', + implement: { + type: 'pen', + width: defaultPreset.width, + }, + }); + defaultSet.items = new Map([['color0', defaultColor], ['ignore', ignoreWhite]]); + + defaultSet = cncserver.binder.trigger('colors.setDefault', defaultSet); + colors.set = defaultSet; + }); }; // Bind to when bot/controller is configured and setup, set default. @@ -195,7 +287,7 @@ module.exports = (cncserver, drawing) => { }; // Get a luminosity sorted list of colors. - colors.getSortedSet = () => colors.set.sort( + colors.getSortedSet = () => Array.from(colors.set.items.values()).sort( (a, b) => new Color(b.color).gray - new Color(a.color).gray ); @@ -213,31 +305,49 @@ module.exports = (cncserver, drawing) => { }; /** - * Snap all the paths in the given layer to a particular color. + * Snap all the paths in the given layer to a particular colorset item. * - * @param {*} layer + * @param {paper.Layer} layer */ colors.snapPathColors = (layer) => { // Build Nearest Color matcher const c = {}; - colors.set.forEach(({ id, color }) => { + colors.set.items.forEach(({ id, color }) => { c[id] = color; }); const nearestColor = nc.from(c); layer.children.forEach((group) => { - group.children.forEach((path) => { - if (path.strokeColor) { + group.children.forEach((item) => { + if (item.strokeColor) { // If we've never touched this path before, save the original color. - if (!path.data.originalColor) { - path.data.originalColor = path.strokeColor; + if (!item.data.originalColor) { + item.data.originalColor = item.strokeColor; } // Find nearest color. - const nearest = nearestColor(path.data.originalColor.toCSS(true)); + const nearest = nearestColor(item.data.originalColor.toCSS(true)); + const colorsetItem = colors.getColor(nearest.name, true); - path.data.colorID = nearest.name; - path.strokeColor = nearest.value; + // If item matched to "ignore", hide it. + if (nearest.name === 'ignore') { + item.strokeWidth = 0; + } else { + // Match colorset item effective implement size. + item.strokeWidth = colorsetItem.implement.width; + + // Save/set new color and matched ID. + // IMPORTANT: This is how tool set swaps are rendered. + item.data.colorID = nearest.name; + item.strokeColor = nearest.value; + + // Assume less than full opacity with brush/watercolor paintings. + item.opacity = colorsetItem.implement.type === 'brush' ? 0.8 : 1; + + // Prevent + item.strokeCap = 'round'; + item.strokeJoin = 'round'; + } } }); }); diff --git a/src/components/core/schemas/cncserver.schemas.color.implement.js b/src/components/core/schemas/cncserver.schemas.color.implement.js new file mode 100644 index 0000000..e303d07 --- /dev/null +++ b/src/components/core/schemas/cncserver.schemas.color.implement.js @@ -0,0 +1,45 @@ +/** + * @file Colorset item implement schema. + * + */ +/* eslint-disable max-len */ +module.exports = allowInherit => ({ + type: 'object', + title: 'Implement Details', + properties: { + type: { + type: 'string', + title: 'Type', + description: 'Type of implement', + enum: allowInherit ? ['inherit', 'brush', 'pen', 'other'] : ['brush', 'pen', 'other'], + default: allowInherit ? 'inherit' : 'pen', + }, + width: { + type: 'number', + title: 'Width', + description: 'Calculated maximum effective width in mm of the implement being used.', + format: 'range', + minimum: 0.01, + maximum: 35, + default: 1, + }, + length: { + type: 'number', + title: 'Length', + description: 'Dynamic fulcrum length for the implement. Set to bristle length in mm for brushes, 0 for pens.', + format: 'range', + minimum: 0, + maximum: 35, + default: 0, + }, + stiffness: { + type: 'number', + title: 'Stiffness', + description: 'Stiffness of dynamic fulcrum length. Set to 1 for fully stiff (same as length 0), 1 for fully loose (soft bristles).', + format: 'range', + minimum: 0, + maximum: 1, + default: 1, + }, + }, +}); diff --git a/src/components/core/schemas/cncserver.schemas.color.js b/src/components/core/schemas/cncserver.schemas.color.js new file mode 100644 index 0000000..2c1de3f --- /dev/null +++ b/src/components/core/schemas/cncserver.schemas.color.js @@ -0,0 +1,41 @@ +/** + * @file Colorset item settings schema. + * + */ +/* eslint-disable max-len */ +const properties = { + id: { + type: 'string', + title: 'ID', + description: 'Tool or machine name for the color.', + }, + name: { + type: 'string', + title: 'Name of colorset item', + description: 'Description of this color & implement', + }, + color: { + type: 'string', + format: 'color', + title: 'Hex color', + description: 'Color that this item represents. Will be used to select areas to be printed with it.', + }, + weight: { + type: 'number', + title: 'Color Weighting', + description: 'Amount to adjust for color selection preference. Smaller than 0 selects less often, larger than 0 selects more often.', + format: 'range', + minimum: -1, + maximum: 1, + default: 0, + }, + // eslint-disable-next-line global-require + implement: require('./cncserver.schemas.color.implement')(true), +}; + +module.exports = () => ({ + type: 'object', + title: 'Colorset Item', + required: ['color', 'name', 'id'], + properties, +}); diff --git a/src/components/core/schemas/cncserver.schemas.colors.js b/src/components/core/schemas/cncserver.schemas.colors.js new file mode 100644 index 0000000..3cdcdb5 --- /dev/null +++ b/src/components/core/schemas/cncserver.schemas.colors.js @@ -0,0 +1,38 @@ +/** + * @file Colorset settings schema. + * + */ +/* eslint-disable max-len */ +const properties = { + name: { + type: 'string', + title: 'Machine Name', + description: 'Machine name for the colorset, set from title.', + }, + title: { + type: 'string', + title: 'Title', + description: 'Human readable name for the colorset.', + }, + description: { + type: 'string', + title: 'Description', + description: 'Extra information about this colorset', + default: '', + }, + // eslint-disable-next-line global-require + implement: require('./cncserver.schemas.color.implement')(false), + items: { + type: 'array', + title: 'Items', + description: 'Colorset items representing all the colors/implements used.', + items: {}, // Set in indexer as color schema. + }, +}; + +module.exports = () => ({ + type: 'object', + required: ['title'], + title: 'Colors', + properties, +}); diff --git a/src/components/core/schemas/cncserver.schemas.js b/src/components/core/schemas/cncserver.schemas.js index bd4d274..a3e0146 100644 --- a/src/components/core/schemas/cncserver.schemas.js +++ b/src/components/core/schemas/cncserver.schemas.js @@ -23,8 +23,7 @@ module.exports = (cncserver) => { acc[e.dataPath] = [e.message]; } return acc; - }, - {}); + }, {}); return { fields }; } @@ -35,10 +34,12 @@ module.exports = (cncserver) => { function processNode(schemaNode, dataNode) { switch (schemaNode.type) { case 'object': - return processObject(schemaNode, dataNode); // eslint-disable-line no-use-before-define + // eslint-disable-next-line no-use-before-define + return processObject(schemaNode, dataNode); case 'array': - return processArray(schemaNode, dataNode); // eslint-disable-line no-use-before-define + // eslint-disable-next-line no-use-before-define + return processArray(schemaNode, dataNode); default: if (dataNode !== undefined) return dataNode; @@ -55,6 +56,7 @@ module.exports = (cncserver) => { const result = {}; // If no properties, pick the first oneOf. if (!schemaNode.properties) { + // eslint-disable-next-line no-param-reassign schemaNode.properties = schemaNode.oneOf[0].properties; } forOwn(schemaNode.properties, (propertySchema, propertyName) => { diff --git a/src/components/core/schemas/index.js b/src/components/core/schemas/index.js index bfb872a..781dec7 100644 --- a/src/components/core/schemas/index.js +++ b/src/components/core/schemas/index.js @@ -5,7 +5,17 @@ /* eslint-disable global-require, import/no-dynamic-require */ module.exports = (cncserver) => { // TODO: Schemas to finalize: stroke, vectorize, text - const items = ['projects', 'content', 'fill', 'stroke', 'text', 'vectorize', 'path']; + const items = [ + 'projects', + 'content', + 'fill', + 'stroke', + 'text', + 'vectorize', + 'path', + 'color', + 'colors', + ]; const settingsKeys = ['fill', 'stroke', 'text', 'vectorize', 'path']; const schemas = {}; @@ -21,6 +31,9 @@ module.exports = (cncserver) => { './cncserver.schemas.content.settings.js' )(settingsKeySchemas); + // Add the color schema to the colors.items schema. + schemas.colors.properties.items.items = schemas.color; + // Attach to content and projects schemas. schemas.content.properties.settings = schemas.settings; schemas.projects.properties.settings = schemas.settings; From 41a500582bce88ffeb2939620ae86214cf3d2315 Mon Sep 17 00:00:00 2001 From: techninja Date: Wed, 24 Jun 2020 14:49:48 -0700 Subject: [PATCH 006/147] Switch from title to text prop to not override hover text --- src/interface/index.html | 21 +++++++++++-------- .../modules/components/elements/tab-group.mjs | 4 ++-- .../modules/components/elements/tab-item.mjs | 2 +- .../components/widgets/content-importer.mjs | 4 ++-- 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/interface/index.html b/src/interface/index.html index d2a43db..c272ff8 100644 --- a/src/interface/index.html +++ b/src/interface/index.html @@ -26,17 +26,20 @@ - +
- + - + - + + + + @@ -48,22 +51,22 @@
- +
- + - + - + - + diff --git a/src/interface/modules/components/elements/tab-group.mjs b/src/interface/modules/components/elements/tab-group.mjs index 243c548..9cbfc04 100644 --- a/src/interface/modules/components/elements/tab-group.mjs +++ b/src/interface/modules/components/elements/tab-group.mjs @@ -33,11 +33,11 @@ export default styles => ({ ${styles}
- +
Upload ${labelType(type)}: @@ -286,7 +286,7 @@ export default styles => ({
- +
From 60d260a6af90ad3a1c433f38d984b23399b10396 Mon Sep 17 00:00:00 2001 From: techninja Date: Fri, 26 Jun 2020 11:02:05 -0700 Subject: [PATCH 007/147] Properly translate internally configured colorsets When the preset is configured early, there's no language context. This change ensures that the internal translate key gets a chance to be converted. --- .../core/comms/api/cncserver.api.colors.js | 2 +- .../core/drawing/cncserver.drawing.colors.js | 20 ++++++++++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/components/core/comms/api/cncserver.api.colors.js b/src/components/core/comms/api/cncserver.api.colors.js index 6bbd093..e5ba9c0 100644 --- a/src/components/core/comms/api/cncserver.api.colors.js +++ b/src/components/core/comms/api/cncserver.api.colors.js @@ -17,7 +17,7 @@ module.exports = (cncserver) => { return { code: 200, body: { - set: colors.getCurrentSet(), + set: colors.getCurrentSet(req.t), presets: colors.listPresets(req.t), }, }; diff --git a/src/components/core/drawing/cncserver.drawing.colors.js b/src/components/core/drawing/cncserver.drawing.colors.js index 2cd25be..e6e9565 100644 --- a/src/components/core/drawing/cncserver.drawing.colors.js +++ b/src/components/core/drawing/cncserver.drawing.colors.js @@ -244,9 +244,23 @@ module.exports = (cncserver) => { }; // Get the current colorset as a JSON ready object. - colors.getCurrentSet = () => ({ - ...colors.set, items: Array.from(colors.set.items.values()), - }); + colors.getCurrentSet = (t = s => s) => { + const set = { + ...colors.set, + title: t(colors.set.title), + description: t(colors.set.description), + items: [], + }; + + Array.from(colors.set.items.values()).forEach(item => { + set.items.push({ + ...item, + name: t(item.name), + }); + }); + + return set; + }; /** * Run at setup, allows machine specific colorset defaults. From 281ef75a880939a541b739add29afea3348df943 Mon Sep 17 00:00:00 2001 From: techninja Date: Fri, 26 Jun 2020 14:29:41 -0700 Subject: [PATCH 008/147] Another "title" attribute swap. Don't do that again. --- src/interface/modules/components/elements/button-single.mjs | 6 +++--- src/interface/modules/components/panels/toolbar-bottom.mjs | 6 +++--- src/interface/modules/components/panels/toolbar-top.mjs | 4 ++-- .../modules/components/widgets/content-importer.mjs | 4 ++-- src/interface/modules/components/widgets/height-presets.mjs | 6 +++--- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/interface/modules/components/elements/button-single.mjs b/src/interface/modules/components/elements/button-single.mjs index 3d0cc3c..1dd1a03 100644 --- a/src/interface/modules/components/elements/button-single.mjs +++ b/src/interface/modules/components/elements/button-single.mjs @@ -4,7 +4,7 @@ import { html } from '/modules/hybrids.js'; export default styles => ({ - title: '', + text: '', icon: '', style: 'plain', loading: false, @@ -14,7 +14,7 @@ export default styles => ({ disabled: false, render: ({ - style, icon, title, desc, fullwidth, active, disabled, loading + style, icon, text, desc, fullwidth, active, disabled, loading }) => { const linkClasses = { button: true, 'is-active': active, 'is-loading': loading }; if (style) linkClasses[`is-${style}`] = true; @@ -23,7 +23,7 @@ export default styles => ({ ${styles} ${icon && html``} - ${title && html`${title}`} + ${text && html`${text}`} `; }, diff --git a/src/interface/modules/components/panels/toolbar-bottom.mjs b/src/interface/modules/components/panels/toolbar-bottom.mjs index 413103b..c601888 100644 --- a/src/interface/modules/components/panels/toolbar-bottom.mjs +++ b/src/interface/modules/components/panels/toolbar-bottom.mjs @@ -9,14 +9,14 @@ export default styles => ({
({ > ({ ${styles} ({
${message}
@@ -310,7 +310,7 @@ export default styles => ({
({ ${styles} Height presets: ${presets.map( - name => html` + name => html` ` - )} + )} ${init} `, }); From 477c2ebde559b4dfaa8f3533416980ecc7778419 Mon Sep 17 00:00:00 2001 From: techninja Date: Fri, 26 Jun 2020 14:30:02 -0700 Subject: [PATCH 009/147] Upgrade hybrids --- package.json | 4 ++-- yarn.lock | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index f8968e4..eccd81d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cncserver", - "version": "3.0.0-beta1", + "version": "3.0.0-beta.1", "description": "A web based a web based CNC Server/Controller to drive @makersylvia's WaterColorBot and beyond", "main": "cncserver.js", "author": "techninja", @@ -21,7 +21,7 @@ "font-list": "^1.2.9", "glob": "^7.1.6", "hersheytext": "1", - "hybrids": "^4.0.3", + "hybrids": "^4.2.1", "i18next": "^19.0.1", "i18next-express-middleware": "^1.8.2", "i18next-node-fs-backend": "^2.1.3", diff --git a/yarn.lock b/yarn.lock index c7e8f89..0a864f3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2678,10 +2678,10 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" -hybrids@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/hybrids/-/hybrids-4.0.3.tgz#e256289e2b118bf8a3bd134e3e626b2005ec1128" - integrity sha512-rOQNUaUQu4NhbO0d/L0ga1BSqh63a7AWTpGxp5v2Wjd3p3k7JTgpGa/zKyUgSK17KywFKybdWUPe5t/pCPZk6g== +hybrids@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/hybrids/-/hybrids-4.2.1.tgz#df690f9d2247c00859dfd0def4bf6e8b21eaf98c" + integrity sha512-FduPhvyKaEK1gKoMrecxM7aKXHvkPq3e8wKaINFliTZcuhttstTDpWFXUXIl/doxLAZeVtRm+ptBpgU7aKLfSA== i18next-express-middleware@^1.8.2: version "1.8.2" From 2ce00bf471d9595ce0228a184fa6dc7678bf3dc4 Mon Sep 17 00:00:00 2001 From: techninja Date: Tue, 30 Jun 2020 20:43:43 -0700 Subject: [PATCH 010/147] Allowing viewing only the workspace on paper canvases --- .../components/elements/paper-canvas.mjs | 27 +++++++++++++------ .../widgets/canvas/canvas-compose.mjs | 1 + 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/interface/modules/components/elements/paper-canvas.mjs b/src/interface/modules/components/elements/paper-canvas.mjs index 0fc2a32..47ddad9 100644 --- a/src/interface/modules/components/elements/paper-canvas.mjs +++ b/src/interface/modules/components/elements/paper-canvas.mjs @@ -109,15 +109,25 @@ function paperInit(host, { size, layers = [], workspace }) { if (host.layers[layer]) host.layers[layer].importJSON(json); }); - // Add workspace overlay. host.layers.overlay.activate(); - Path.Rectangle({ - point: workspace.point, - size: workspace.size, - strokeWidth: 2, - strokeColor: 'red', - name: 'workspace', - }); + + // Add workspace overlay. + if (!host.workspaceOnly) { + Path.Rectangle({ + point: workspace.point, + size: workspace.size, + strokeWidth: 2, + strokeColor: 'red', + name: 'workspace', + }); + } else { + // Resize the canvas to just the workspace. + scope.project.view.translate([-workspace.point.x, -workspace.point.y]); + scope.project.view.viewSize = [ + (size.width - workspace.point.x) * viewScale, + (size.height - workspace.point.y) * viewScale, + ]; + } // Draw underlay grid. drawGrid(host.layers.underlay, workspace); @@ -172,6 +182,7 @@ export default () => ({ initialized: false, viewScale: 3, // Scale to add to the given size, sets raster volume. workspace: {}, // Workspace area rectangle. + workspaceOnly: false, // If true, visible area is limited to workspace. scope: {}, // Final initialized PaperScope object. layers: {}, // Keyed object of all Paper.Layer objects. scale: 1, // How much to scale points, set by resizing algo. diff --git a/src/interface/modules/components/widgets/canvas/canvas-compose.mjs b/src/interface/modules/components/widgets/canvas/canvas-compose.mjs index 22e9e63..324c1bf 100644 --- a/src/interface/modules/components/widgets/canvas/canvas-compose.mjs +++ b/src/interface/modules/components/widgets/canvas/canvas-compose.mjs @@ -122,6 +122,7 @@ export default styles => ({ name="compose" onpaperinit=${init} onlayerupdate=${bubbleUpdate} + workspace-only > `, }); From d61d88ddebef25c04c4d62b191fee42466acc0ff Mon Sep 17 00:00:00 2001 From: techninja Date: Wed, 8 Jul 2020 17:09:51 -0700 Subject: [PATCH 011/147] Move to bot placeholders with position and width --- machine_types/watercolorbot.ini | 188 ++---------------- .../core/control/cncserver.control.js | 70 +------ .../core/control/cncserver.tools.js | 170 ++++++++++++++++ src/components/index.js | 1 + 4 files changed, 195 insertions(+), 234 deletions(-) create mode 100644 src/components/core/control/cncserver.tools.js diff --git a/machine_types/watercolorbot.ini b/machine_types/watercolorbot.ini index 7b1b470..d577a18 100755 --- a/machine_types/watercolorbot.ini +++ b/machine_types/watercolorbot.ini @@ -93,182 +93,36 @@ max = 1023 min = 0 [tools] +[tools.pan] +x = 22 +y = -2.5 +width = 38.4 +height = 214 +type = swappable +group = Placeholder + [tools.water0] -x = 0 -y = 0 -wiggleAxis = y -wiggleTravel = 300 -wiggleIterations = 4 +x = -12.2 +y = 14.8 +width = 57.85 +height = 57.85 +type = wash group = Water -[tools.water0dip] -x = 0 -y = 0 -wiggleAxis = y -wiggleTravel = 5 -wiggleIterations = 2 -group = Water Dip - [tools.water1] -x = 0 -y = 1650 -wiggleAxis = y -wiggleTravel = 300 -wiggleIterations = 4 +x = -12.2 +y = 90 +width = 57.85 +height = 57.85 group = Water -[tools.water1dip] -x = 0 -y = 1650 -wiggleAxis = y -wiggleTravel = 5 -wiggleIterations = 2 -group = Water Dip - [tools.water2] -x = 0 -y = 3000 -wiggleAxis = y -wiggleTravel = 300 -wiggleIterations = 4 +x = -12.2 +y = 168 +width = 57.85 +height = 57.85 group = Water -[tools.water2dip] -x = 0 -y = 3000 -wiggleAxis = y -wiggleTravel = 5 -wiggleIterations = 2 -group = Water Dip - -[tools.color0] -x = 775 -y = 250 -wiggleAxis = xy -wiggleTravel = 300 -wiggleIterations = 8 -group = Colors - -[tools.color1] -x = 775 -y = 715 -wiggleAxis = xy -wiggleTravel = 300 -wiggleIterations = 8 -group = Colors - -[tools.color2] -x = 775 -y = 1135 -wiggleAxis = xy -wiggleTravel = 300 -wiggleIterations = 8 -group = Colors - -[tools.color3] -x = 775 -y = 1625 -wiggleAxis = xy -wiggleTravel = 300 -wiggleIterations = 8 -group = Colors - -[tools.color4] -x = 775 -y = 2035 -wiggleAxis = xy -wiggleTravel = 300 -wiggleIterations = 8 -group = Colors - -[tools.color5] -x = 775 -y = 2502 -wiggleAxis = xy -wiggleTravel = 300 -wiggleIterations = 8 -group = Colors - -[tools.color6] -x = 775 -y = 2955 -wiggleAxis = xy -wiggleTravel = 300 -wiggleIterations = 8 -group = Colors - -[tools.color7] -x = 775 -y = 3405 -wiggleAxis = xy -wiggleTravel = 300 -wiggleIterations = 8 -group = Colors - -[tools.color0dip] -x = 775 -y = 250 -wiggleAxis = xy -wiggleTravel = 5 -wiggleIterations = 2 -group = Colors Dip - -[tools.color1dip] -x = 775 -y = 715 -wiggleAxis = xy -wiggleTravel = 5 -wiggleIterations = 2 -group = Colors Dip - -[tools.color2dip] -x = 775 -y = 1135 -wiggleAxis = xy -wiggleTravel = 5 -wiggleIterations = 2 -group = Colors Dip - -[tools.color3dip] -x = 775 -y = 1625 -wiggleAxis = xy -wiggleTravel = 5 -wiggleIterations = 2 -group = Colors Dip - -[tools.color4dip] -x = 775 -y = 2035 -wiggleAxis = xy -wiggleTravel = 5 -wiggleIterations = 2 -group = Colors Dip - -[tools.color5dip] -x = 775 -y = 2502 -wiggleAxis = xy -wiggleTravel = 5 -wiggleIterations = 2 -group = Colors Dip - -[tools.color6dip] -x = 775 -y = 2955 -wiggleAxis = xy -wiggleTravel = 5 -wiggleIterations = 2 -group = Colors Dip - -[tools.color7dip] -x = 775 -y = 3405 -wiggleAxis = xy -wiggleTravel = 5 -wiggleIterations = 2 -group = Colors Dip - [tools.manualswap] x = 0 y = 0 diff --git a/src/components/core/control/cncserver.control.js b/src/components/core/control/cncserver.control.js index da66350..a263cb7 100644 --- a/src/components/core/control/cncserver.control.js +++ b/src/components/core/control/cncserver.control.js @@ -5,69 +5,6 @@ const control = {}; // Exposed export. module.exports = (cncserver) => { - /** - * Run the operation to set the current tool (and any aggregate operations - * required) into the buffer - * - * @param name - * The machine name of the tool (as defined in the bot config file). - * @param index - * Index for notifying user of what the manual tool change is for. - * @param callback - * Triggered when the full tool change is to have been completed, or on - * failure. - * @param waitForCompletion - * Pass false to call callback immediately after calculation, true to - * callback only after physical movement is complete. - * - * @returns {boolean} - * True if success, false on failure. - */ - control.setTool = (name, index = null, callback = () => {}, waitForCompletion = false) => { - // Get the matching tool object from the bot configuration. - const tool = cncserver.settings.botConf.get(`tools:${name}`); - - // No tool found with that name? Augh! Run AWAY! - if (!tool) { - cncserver.run('callback', callback); - return false; - } - - // For wait=false/"resume" tools, we really just resume the buffer. - // It should be noted, this is obviously NOT a queable toolchange. - // This should ONLY be called to restart the queue after a swap. - if (tool.wait !== undefined && tool.wait === false) { - cncserver.buffer.resume(); - callback(1); - return true; - } - - // Pen Up - cncserver.pen.setHeight('up'); - - // Move to the tool - cncserver.control.movePenAbs(tool); - - // Set the tip of state pen to the tool now that the change is done. - cncserver.pen.forceState({ tool: name }); - - // Trigger the binder event. - cncserver.binder.trigger('tool.change', { - ...tool, - index, - name, - }); - - // Finish up. - if (waitForCompletion) { // Run inside the buffer - cncserver.run('callback', callback); - } else { // Run as soon as items have been buffered - callback(1); - } - - return true; - }; - /** * "Move" the pen (tip of the buffer) to an absolute point inside the maximum * available bot area. Includes cutoffs and sanity checks. @@ -191,8 +128,8 @@ module.exports = (cncserver) => { // Adjust the distance counter based on movement amount, not if we're off // the canvas though. if (cncserver.utils.penDown() - && !cncserver.pen.state.offCanvas - && cncserver.settings.bot.inWorkArea(point)) { + && !cncserver.pen.state.offCanvas + && cncserver.settings.bot.inWorkArea(point)) { cncserver.pen.forceState({ distanceCounter: parseFloat( Number(distance) + Number(cncserver.pen.state.distanceCounter) @@ -434,7 +371,7 @@ module.exports = (cncserver) => { // Do we have a tool for this colorID? If not, use manualswap. if (colors.doColorParsing()) { const changeTool = botConf.get(`tools:${colorID}`) ? colorID : 'manualswap'; - control.setTool(changeTool, colorID); + tools.set(changeTool, colorID); } let pathIndex = 0; @@ -535,7 +472,6 @@ module.exports = (cncserver) => { // Exports... control.exports = { - setTool: control.setTool, movePenAbs: control.movePenAbs, }; diff --git a/src/components/core/control/cncserver.tools.js b/src/components/core/control/cncserver.tools.js new file mode 100644 index 0000000..3523062 --- /dev/null +++ b/src/components/core/control/cncserver.tools.js @@ -0,0 +1,170 @@ +/** + * @file Abstraction module for tool state and helper methods. + */ +const { Path, Group, PointText } = require('paper'); + +const tools = { + id: 'tools', +}; + +module.exports = (cncserver) => { + + // Get a flat array of tools. + tools.items = () => [ + ...tools.getBotTools(), + ...cncserver.drawing.colors.set.tools, + ]; + + tools.getNames = () => tools.items().map(({ id }) => id); + + // Flatten bot tools to array. + tools.getBotTools = () => { + const botTools = cncserver.settings.botConf.get('tools'); + const out = []; + + Object.entries(botTools).forEach(([id, tool]) => { + out.push({ + id, + parent: '', + ...tool, + x: parseFloat(tool.x), + y: parseFloat(tool.y), + width: tool.width ? parseFloat(tool.width) : 0, + height: tool.height ? parseFloat(tool.height) : 0, + }); + }); + return out; + }; + + // Get a single item, undefined if invalid. + tools.getItem = name => tools.items().find(({ id }) => id === name); + + /** + * Run the operation to set the current tool (and any aggregate operations + * required) into the buffer + * + * @param name + * The machine name of the tool (as defined in the bot config file). + * @param index + * Index for notifying user of what the manual tool change is for. + * @param callback + * Triggered when the full tool change is to have been completed, or on + * failure. + * @param waitForCompletion + * Pass false to call callback immediately after calculation, true to + * callback only after physical movement is complete. + * + * @returns {boolean} + * True if success, false on failure. + */ + tools.set = (name, index = null, callback = () => { }, waitForCompletion = false) => { + // Get the matching tool object from the bot configuration. + const tool = tools.getItem(name); + + // No tool found with that name? Augh! Run AWAY! + if (!tool) { + cncserver.run('callback', callback); + return false; + } + + // For wait=false/"resume" tools, we really just resume the buffer. + // It should be noted, this is obviously NOT a queable toolchange. + // This should ONLY be called to restart the queue after a swap. + if (tool.wait !== undefined && tool.wait === false) { + cncserver.buffer.resume(); + callback(1); + return true; + } + + // Pen Up + cncserver.pen.setHeight('up'); + + // Figure out the final position: + let toolPos = { x: tool.x, y: tool.y }; + + // Is there a parent? Offset for that. + const parent = tools.getItem(tool.parent); + if (parent) { + toolPos.x += parseFloat(parent.x); + toolPos.y += parseFloat(parent.y); + } + + // Convert MM to Abs steps. + toolPos = cncserver.utils.absToSteps(toolPos, 'mm', true); + + // Prevent out of bounds moves. + toolPos = cncserver.utils.sanityCheckAbsoluteCoord(toolPos); + + // Move to the tool + cncserver.control.movePenAbs(toolPos); + + // Set the tip of state pen to the tool now that the change is done. + cncserver.pen.forceState({ tool: name }); + + // Trigger the binder event. + cncserver.binder.trigger('tool.change', { + ...tool, + index, + name, + }); + + // Finish up. + if (waitForCompletion) { // Run inside the buffer + cncserver.run('callback', callback); + } else { // Run as soon as items have been buffered + callback(1); + } + + return true; + }; + + // Bind to tools.update to redraw the tools layer. + cncserver.binder.bindTo('tools.update', tools.id, () => { + const { layers } = cncserver.drawing.base; + const toolGroup = new Group(); + const items = tools.items(); + + layers.tools.removeChildren(); + + // Create a representation path for each tool. + items.forEach((tool) => { + const toolPos = { x: tool.x, y: tool.y }; + + // Offset for center positions. + if (tool.position === 'center') { + toolPos.x -= tool.width / 2; + toolPos.y -= tool.height / 2; + } + + // Apply parent offset. + const parent = tools.getItem(tool.parent); + if (parent) { + toolPos.x += parent.x; + toolPos.y += parent.y; + } + + // Don't try to display tools without size. + if (tool.width && tool.height) { + const path = new Path.Rectangle({ + ...toolPos, + width: tool.width, + height: tool.height, + radius: tool.radius, + name: tool.id, + strokeWidth: 1, + strokeColor: 'black', + fillColor: cncserver.drawing.colors.getToolColor(tool.id), + }); + + const label = new PointText({ fontSize: 8, content: tool.id, opacity: 0.5 }); + label.fitBounds(path.bounds); + toolGroup.addChild(new Group([path, label])); + } + }); + + layers.tools.addChild(toolGroup); + cncserver.sockets.sendPaperUpdate('tools'); + }); + + return tools; +}; diff --git a/src/components/index.js b/src/components/index.js index 639024d..dc1e378 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -16,6 +16,7 @@ module.exports = { pen: './components/core/control/cncserver.pen', actualPen: './components/core/control/cncserver.actualpen', control: './components/core/control/cncserver.control', + tools: './components/core/control/cncserver.tools', buffer: './components/core/utils/cncserver.buffer', run: './components/core/utils/cncserver.run', scratch: './components/third_party/scratch/cncserver.scratch', From ff06b5b0f1af546ffdc7e4bb7e53caf200ad3461 Mon Sep 17 00:00:00 2001 From: techninja Date: Wed, 8 Jul 2020 17:12:36 -0700 Subject: [PATCH 012/147] Initial implementation of locked size "slide" interface component --- .../modules/components/elements/index.mjs | 4 + .../components/elements/slide-group.mjs | 89 +++++++++++++++++++ .../components/elements/slide-item.mjs | 22 +++++ src/interface/styles/interface.css | 71 ++------------- 4 files changed, 120 insertions(+), 66 deletions(-) create mode 100644 src/interface/modules/components/elements/slide-group.mjs create mode 100644 src/interface/modules/components/elements/slide-item.mjs diff --git a/src/interface/modules/components/elements/index.mjs b/src/interface/modules/components/elements/index.mjs index 200791c..0c6680c 100644 --- a/src/interface/modules/components/elements/index.mjs +++ b/src/interface/modules/components/elements/index.mjs @@ -3,6 +3,8 @@ */ import TabGroup from './tab-group.mjs'; import TabItem from './tab-item.mjs'; +import SlideGroup from './slide-group.mjs'; +import SlideItem from './slide-item.mjs'; import ButtonToggle from './button-toggle.mjs'; import ButtonSingle from './button-single.mjs'; import LabelTitle from './label-title.mjs'; @@ -12,6 +14,8 @@ import PaperCanvas from './paper-canvas.mjs'; export default styles => ({ 'tab-item': TabItem(styles), 'tab-group': TabGroup(styles), + 'slide-item': SlideItem(styles), + 'slide-group': SlideGroup(styles), 'button-toggle': ButtonToggle(styles), 'button-single': ButtonSingle(styles), 'label-title': LabelTitle(styles), diff --git a/src/interface/modules/components/elements/slide-group.mjs b/src/interface/modules/components/elements/slide-group.mjs new file mode 100644 index 0000000..c7373c6 --- /dev/null +++ b/src/interface/modules/components/elements/slide-group.mjs @@ -0,0 +1,89 @@ +/** + * @file Slide group element definition. + */ +import SlideItem from './slide-item.mjs'; + +import { html, children, dispatch } from '/modules/hybrids.js'; + +function getIndex(host, name) { + let index = -1; + let home = 0; + + let i = 0; + for (const slide of host.children) { + if (slide.name === host.home) home = i; + if (slide.name === name) index = i; + i++; + } + + // Default to home if none found. + if (index === -1) index = home; + + return index; +} + +/** + * Factory for managing host item change/value, takes name, changes what's selected. + * + * @param {string} [defaultItem=''] + * Hash value from hybrids factory handler. + * + * @returns {hybrids factory} + */ +function itemChangeFactory() { + return { + set: (host, value) => { + const index = getIndex(host, value); + + // Actually set the index based on the name (if we have a value/home). + if (value && host.home) { + host.index = index; + // console.log('Active change:', value, index, host.home, host.index); + } + + + // Dispatch the change up from the host. + dispatch(host, 'change', { detail: { name: host.children[index].name } }); + return host.children[index].name; + }, + connect: (host) => { + host.activeItem = ''; + }, + }; +} + +export default styles => ({ + // Children defined in 'tab-item.js' + items: children(SlideItem), + height: 100, + width: 100, + initialized: false, + + // Sets and returns active item by name + activeItem: itemChangeFactory(), + home: '', + index: 0, + + render: ({ height, width, items, index }) => html` + ${styles} + +
+
+ +
+
+ `, +}); diff --git a/src/interface/modules/components/elements/slide-item.mjs b/src/interface/modules/components/elements/slide-item.mjs new file mode 100644 index 0000000..2a62a4b --- /dev/null +++ b/src/interface/modules/components/elements/slide-item.mjs @@ -0,0 +1,22 @@ +/** + * @file Slide item element definition. + */ +import { html } from '/modules/hybrids.js'; + +export default styles => ({ + text: '', + name: '', + icon: '', + active: false, + + // Renders all children () with active class. + render: ({ active }) => html` + ${styles} + +
+ `, +}); diff --git a/src/interface/styles/interface.css b/src/interface/styles/interface.css index 80f7d50..44be464 100644 --- a/src/interface/styles/interface.css +++ b/src/interface/styles/interface.css @@ -111,73 +111,11 @@ input.switch:checked:after { } /* Colorset editor */ -#color-editor .select2-selection--single { - height: 43px; -} - -.select2-container span.preset-color, -.preset .colors { - display: flex; - align-items: center; -} - -.select2-container span.preset-color b, -.preset .colors b { - content: " "; - display: block; - height: 2em; - width: 2em; - border-radius: 0.6em; - margin: 5px; -} - -/* Colorset viewer */ -.colorset-item.card b { - content: " "; - display: block; - width: 5.5em; - background: linear-gradient(90deg, rgba(255,255,255,0) 65%, rgba(255,255,255,1) 100%); - margin-right: -0.3em; - padding-top: 0.7em; - padding-left: 0.4em; - font-family: monospace; - color: white; - text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000; -} - -.colorset-item.card { - cursor: pointer; -} - -.colorset-item.card:hover { - background-color: rgba(240,240,240,1); -} - -.colorset-item.card a { - color: rgba(255,0,0,0.5); - transition: all 0.25s; -} - -.colorset-item.card a:hover { - color: rgba(255,0,0,1); - transform: scale(1.4); -} - -.colorset-item.card.active { - background-color: #3273dc; -} - -.colorset-item.card:hover b { - background: linear-gradient(90deg, rgba(240,240,240,0) 65%, rgba(240,240,240,1) 100%); -} - -.colorset-item.card.active b, -.colorset-item.card.active:hover b { - background: linear-gradient(90deg, rgba(50,115,220,0) 65%, rgba(50,115,220,1) 100%); +slide-item { + overflow-x: hidden; } - -.colorset-item.card.active .card-header-title { - color: white; +slide-item h1 { + font-size: 5em; } /* Project loader */ @@ -281,3 +219,4 @@ input.switch:checked:after { #source-type { font-size: 1.2em; } + From bc5c8048b38689e675ac9cde17493dbe2b019797 Mon Sep 17 00:00:00 2001 From: techninja Date: Wed, 8 Jul 2020 17:17:46 -0700 Subject: [PATCH 013/147] Initial work towards cleaning up and upgrading the tool API --- .../core/comms/api/cncserver.api.tools.js | 82 +++++++++++-------- .../components/widgets/tools-basic.mjs | 40 ++++----- 2 files changed, 65 insertions(+), 57 deletions(-) diff --git a/src/components/core/comms/api/cncserver.api.tools.js b/src/components/core/comms/api/cncserver.api.tools.js index 70491e1..26b6760 100644 --- a/src/components/core/comms/api/cncserver.api.tools.js +++ b/src/components/core/comms/api/cncserver.api.tools.js @@ -4,13 +4,25 @@ const handlers = {}; module.exports = (cncserver) => { + + // Unified item not found. + function notFound(name) { + return [ + 404, + cncserver.utils.singleLineString` + Tool: '${name}' not found. + Must be one of: '${cncserver.tools.getNames().join("', '")} + '.`, + ]; + } + handlers['/v2/tools'] = function toolsGet(req) { + const { tools } = cncserver; if (req.route.method === 'get') { // Get list of tools return { code: 200, body: { - tools: Object.keys(cncserver.settings.botConf.get('tools')), - toolData: cncserver.settings.botConf.get('tools'), + tools: tools.items(), }, }; } @@ -22,23 +34,24 @@ module.exports = (cncserver) => { // Standard toolchanges. // TODO: Prevent manual swap "wait" toolchanges on this endpoint? handlers['/v2/tools/:tool'] = function toolsMain(req, res) { - const toolName = req.params.tool; + const { tools } = cncserver; + const tool = tools.getItem(req.params.tool); + + // Sanity check tool. + if (!tool) return notFound(req.params.tool); + // TODO: Support other tool methods... (needs API design!) if (req.route.method === 'put') { // Set Tool - if (cncserver.settings.botConf.get(`tools:${toolName}`)) { - cncserver.control.setTool(toolName, null, () => { - res.status(200).send(JSON.stringify({ - status: `Tool changed to ${toolName}`, - })); - - if (cncserver.settings.gConf.get('debug')) { - console.log('>RESP', req.route.path, 200, `Tool:${toolName}`); - } - }, req.body.waitForCompletion); - return true; // Tell endpoint wrapper we'll handle the response - } - - return [404, `Tool: "${toolName}" not found`]; + cncserver.tools.set(tool.id, null, () => { + res.status(200).send(JSON.stringify({ + status: `Tool changed to ${tool.id}`, + })); + + if (cncserver.settings.gConf.get('debug')) { + console.log('>RESP', req.route.path, 200, `Tool:${tool.id}`); + } + }, req.body.waitForCompletion); + return true; // Tell endpoint wrapper we'll handle the response } // Error to client for unsupported request types. @@ -47,25 +60,28 @@ module.exports = (cncserver) => { // "wait" manual swap toolchanges with index handlers['/v2/tools/:tool/:index'] = function toolsMain(req, res) { - const toolName = req.params.tool; const toolIndex = req.params.index; + const { tools } = cncserver; + const tool = tools.getItem(req.params.tool); + + // Sanity check tool. + if (!tool) return notFound(req.params.tool); + if (req.route.method === 'put') { // Set Tool - if (cncserver.settings.botConf.get(`tools:${toolName}`)) { - cncserver.control.setTool(toolName, toolIndex, () => { - cncserver.pen.forceState({ tool: toolName }); - res.status(200).send(JSON.stringify({ - status: `Tool changed to ${toolName}, for index ${toolIndex}`, - })); - - if (cncserver.settings.gConf.get('debug')) { - console.log('>RESP', req.route.path, 200, `Tool:${toolName}, Index:${toolIndex}`); - } - }, req.body.waitForCompletion); - return true; // Tell endpoint wrapper we'll handle the response - } - - return [404, `Tool: "${toolName}" not found`]; + tools.set(tool.id, toolIndex, () => { + // TODO: Is this force state needed? + cncserver.pen.forceState({ tool: tool.id }); + res.status(200).send(JSON.stringify({ + status: `Tool changed to ${tool.id}, for index ${toolIndex}`, + })); + + if (cncserver.settings.gConf.get('debug')) { + console.log('>RESP', req.route.path, 200, `Tool:${toolName}, Index:${toolIndex}`); + } + }, req.body.waitForCompletion); + return true; // Tell endpoint wrapper we'll handle the response + } // Error to client for unsupported request types. diff --git a/src/interface/modules/components/widgets/tools-basic.mjs b/src/interface/modules/components/widgets/tools-basic.mjs index d4be9e5..4666c95 100644 --- a/src/interface/modules/components/widgets/tools-basic.mjs +++ b/src/interface/modules/components/widgets/tools-basic.mjs @@ -7,16 +7,13 @@ import apiInit from '/modules/utils/api-init.mjs'; // Load and group all tools from the API. function loadTools(host) { - cncserver.api.tools.list().then(response => { - if (response.data) { + cncserver.api.tools.list().then(({ data: { tools: toolArray } }) => { + if (toolArray.length) { const toolGroups = {}; - response.data.tools.forEach(tool => { - const td = response.data.toolData[tool]; - const group = td.group || 'Default'; + toolArray.forEach((tool) => { + const group = tool.group || 'Default'; - if (!toolGroups[group]) { - toolGroups[group] = []; - } + if (!toolGroups[group]) toolGroups[group] = []; toolGroups[group].push(tool); }); @@ -62,30 +59,25 @@ export default styles => ({ Tools:
${toolGroups.map( - ({ name, tools }) => html` + ({ name, tools }) => html`

${name}

- ${tools.map(toolName => { - if (toolName === currentTool) { - return html` - ${toolName} - `; - } - return html` - ${toolName} { + if (tool.id === currentTool) { + return html`${tool.id}`; + } + return html` + + ${tool.id} + `; - })} + })}
` - )} + )}
${init} `, From e9bd793506f6f625517083bef6a28e3ced5bfb81 Mon Sep 17 00:00:00 2001 From: techninja Date: Wed, 8 Jul 2020 17:18:23 -0700 Subject: [PATCH 014/147] Adjust water positions and radius --- machine_types/watercolorbot.ini | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/machine_types/watercolorbot.ini b/machine_types/watercolorbot.ini index d577a18..6ed6ecc 100755 --- a/machine_types/watercolorbot.ini +++ b/machine_types/watercolorbot.ini @@ -98,6 +98,8 @@ x = 22 y = -2.5 width = 38.4 height = 214 +position = topleft +radius = 4 type = swappable group = Placeholder @@ -106,7 +108,8 @@ x = -12.2 y = 14.8 width = 57.85 height = 57.85 -type = wash +position = center +radius = 35 group = Water [tools.water1] @@ -114,6 +117,8 @@ x = -12.2 y = 90 width = 57.85 height = 57.85 +position = center +radius = 35 group = Water [tools.water2] @@ -121,6 +126,8 @@ x = -12.2 y = 168 width = 57.85 height = 57.85 +position = center +radius = 35 group = Water [tools.manualswap] From ce15e9a0dfa57a0e79f2463b40680ba02cccf16a Mon Sep 17 00:00:00 2001 From: techninja Date: Wed, 8 Jul 2020 17:20:51 -0700 Subject: [PATCH 015/147] Add Display only synced "tools" layer --- src/components/core/comms/cncserver.sockets.js | 1 + src/components/core/drawing/cncserver.drawing.base.js | 10 ++++++++-- .../modules/components/widgets/canvas/canvas-print.mjs | 4 ++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/components/core/comms/cncserver.sockets.js b/src/components/core/comms/cncserver.sockets.js index cc65c37..35b2d28 100644 --- a/src/components/core/comms/cncserver.sockets.js +++ b/src/components/core/comms/cncserver.sockets.js @@ -113,6 +113,7 @@ module.exports = (cncserver) => { // Send this second. sockets.sendPaperUpdate('preview'); sockets.sendPaperUpdate('stage'); + sockets.sendPaperUpdate('tools'); }; /** diff --git a/src/components/core/drawing/cncserver.drawing.base.js b/src/components/core/drawing/cncserver.drawing.base.js index 0110c62..48c8a63 100644 --- a/src/components/core/drawing/cncserver.drawing.base.js +++ b/src/components/core/drawing/cncserver.drawing.base.js @@ -24,9 +24,15 @@ module.exports = (cncserver) => { base.project = new Project(base.size); - // Setup layers: temp, working + // Setup layers: // Whatever the last layer added was, will be default. - const layers = ['import', 'temp', 'stage', 'preview']; + const layers = [ + 'import', // Raw content, cleared on each import. + 'temp', // Temporary working space, cleared before each operation. + 'stage', // Project imported groups of items. + 'tools', // Helper visualization of tool positions. + 'preview', // Final render, item groups of lines w/color data only (no fills). + ]; layers.forEach((name) => { base.layers[name] = new Layer({ name }); }); diff --git a/src/interface/modules/components/widgets/canvas/canvas-print.mjs b/src/interface/modules/components/widgets/canvas/canvas-print.mjs index 44d08bb..f164375 100644 --- a/src/interface/modules/components/widgets/canvas/canvas-print.mjs +++ b/src/interface/modules/components/widgets/canvas/canvas-print.mjs @@ -128,7 +128,7 @@ function init(host, { detail }) { bindSocketPosition(host); // Tell the canvas to keep the preview layer updated. - host.canvas.scope.watchUpdates(['preview']); + host.canvas.scope.watchUpdates(['preview', 'tools']); // Get the bot size details and initialize the canvas with it. cncserver.api.settings.bot().then(({ data: bot }) => { @@ -148,7 +148,7 @@ function init(host, { detail }) { // Initialize the paper-canvas with bot size details. host.canvas.scope.paperInit({ size: new paper.Size(bot.maxAreaMM), - layers: ['preview'], + layers: ['preview', 'tools'], workspace, }).then(() => { initPrint(host); From 5895f51f36a2ff5b82688ee579d4450173747501 Mon Sep 17 00:00:00 2001 From: techninja Date: Wed, 8 Jul 2020 17:21:19 -0700 Subject: [PATCH 016/147] Tools handler refactors for scratch support, WCB --- .../cncserver.bots.watercolorbot.js | 10 +++++----- .../third_party/scratch/cncserver.scratch.js | 16 ++++++++-------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/components/machine_support/cncserver.bots.watercolorbot.js b/src/components/machine_support/cncserver.bots.watercolorbot.js index f0ab048..56ff814 100644 --- a/src/components/machine_support/cncserver.bots.watercolorbot.js +++ b/src/components/machine_support/cncserver.bots.watercolorbot.js @@ -74,15 +74,15 @@ module.exports = (cncserver) => { // Run a full wash in all the waters. watercolorbot.fullWash = () => { // TODO: Fix water0 overreach - cncserver.control.setTool('water0dip'); - cncserver.control.setTool('water1'); - cncserver.control.setTool('water2'); + cncserver.tools.set('water0dip'); + cncserver.tools.set('water1'); + cncserver.tools.set('water2'); }; // Reink with a water dip. watercolorbot.reink = (tool = cncserver.pen.state.tool) => { - cncserver.control.setTool('water0dip'); - cncserver.control.setTool(tool); + cncserver.tools.set('water0dip'); + cncserver.tools.set(tool); }; // Bind the wiggle to the toolchange event. diff --git a/src/components/third_party/scratch/cncserver.scratch.js b/src/components/third_party/scratch/cncserver.scratch.js index ac50b16..a103297 100644 --- a/src/components/third_party/scratch/cncserver.scratch.js +++ b/src/components/third_party/scratch/cncserver.scratch.js @@ -14,7 +14,7 @@ module.exports = (cncserver) => { const { arg2 } = req.params; let { arg } = req.params; if (arg2 && !arg) { - [,, arg] = url; + [, , arg] = url; } // Do nothing if sleeping @@ -159,12 +159,12 @@ module.exports = (cncserver) => { // If reink initialized, check distance and initiate reink! if (turtle.reinkDistance > 0 - && turtle.distanceCounter > turtle.reinkDistance) { + && turtle.distanceCounter > turtle.reinkDistance) { turtle.distanceCounter = 0; // Reink procedure! - cncserver.control.setTool('water0dip'); // Dip in the water - cncserver.control.setTool(turtle.media); // Apply the last saved media + cncserver.tools.set('water0dip'); // Dip in the water + cncserver.tools.set(turtle.media); // Apply the last saved media cncserver.control.movePenAbs(turtle); // Move back to "current" position cncserver.pen.setHeight('draw'); // Set the position back to draw } @@ -214,9 +214,9 @@ module.exports = (cncserver) => { // Run simple wash if (op === 'wash') { - cncserver.control.setTool('water0'); - cncserver.control.setTool('water1'); - cncserver.control.setTool('water2'); + cncserver.tools.set('water0'); + cncserver.tools.set('water1'); + cncserver.tools.set('water2'); } // Turn off motors and zero to park pos @@ -246,7 +246,7 @@ module.exports = (cncserver) => { // Set by ID (water/color) if (type) { const tool = type + parseInt(req.params.id, 10); - cncserver.control.setTool(tool); + cncserver.tools.set(tool); turtle.media = tool; } From f0409439aa3b45eb131c38e421b5dc2cdc6cc0f6 Mon Sep 17 00:00:00 2001 From: techninja Date: Wed, 8 Jul 2020 17:39:22 -0700 Subject: [PATCH 017/147] Continuing adventures in "What's the right number?" --- src/components/core/drawing/cncserver.drawing.accell.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/core/drawing/cncserver.drawing.accell.js b/src/components/core/drawing/cncserver.drawing.accell.js index 4109e7d..5acbf78 100644 --- a/src/components/core/drawing/cncserver.drawing.accell.js +++ b/src/components/core/drawing/cncserver.drawing.accell.js @@ -8,11 +8,11 @@ const accell = { id: 'drawing.accell', state: 'idle' }; // Path planning Settings const s = { - accelRate: 10, // Percentage increase over distance. - speedMultiplyer: 0.55, // Conversion of moment length to velocity. - minSpeed: 5, + accelRate: 25, // 10, // Percentage increase over distance. + speedMultiplyer: 0.75, // 0.55 // Conversion of moment length to velocity. + minSpeed: 15, // 5, resolution: 0.5, // Steps to check along path by - maxDeflection: 5, + maxDeflection: 10, // 5, // Time before work is sent to the callback for long operations. splitTimeout: 2500, }; From b001c95243a5ebb234ebb239d021d0a93f9e606f Mon Sep 17 00:00:00 2001 From: techninja Date: Wed, 8 Jul 2020 18:01:33 -0700 Subject: [PATCH 018/147] Include drawLength and handleWidth in implement schema This should be enough to handle reinking, and offset for center draw positions --- .../cncserver.schemas.color.implement.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/components/core/schemas/cncserver.schemas.color.implement.js b/src/components/core/schemas/cncserver.schemas.color.implement.js index e303d07..5750689 100644 --- a/src/components/core/schemas/cncserver.schemas.color.implement.js +++ b/src/components/core/schemas/cncserver.schemas.color.implement.js @@ -41,5 +41,23 @@ module.exports = allowInherit => ({ maximum: 1, default: 1, }, + drawLength: { + type: 'number', + title: 'Draw Length', + description: 'Distance in mm the implement can draw before it needs to be refreshed, 0 for pens.', + format: 'range', + minimum: 0, + maximum: 3000, + default: 0, + }, + handleWidth: { + type: 'number', + title: 'Handle Width', + description: 'Measured width of the handle, to account for center draw position offset.', + format: 'range', + minimum: 2, + maximum: 20, + default: 10, + }, }, }); From ac2355a741064477063c9e6f7ba8f41275761e79 Mon Sep 17 00:00:00 2001 From: techninja Date: Fri, 24 Jul 2020 09:26:02 -0700 Subject: [PATCH 019/147] New element: Schema form! Turns any JSON schema (or a subpath of it) into a clean, customizable form with full diff changes and data management --- package.json | 1 + .../modules/components/elements/index.mjs | 2 + .../components/elements/schema-form.mjs | 399 ++++++++++++++++++ 3 files changed, 402 insertions(+) create mode 100644 src/interface/modules/components/elements/schema-form.mjs diff --git a/package.json b/package.json index eccd81d..3bc464f 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "jquery": "^3.4.1", "js-angusj-clipper": "^1.0.3", "jsonform": "^2.1.6", + "jsonpath": "^1.0.2", "merge-deep": "^3.0.2", "nconf": "0.8.4", "nearest-color": "^0.4.4", diff --git a/src/interface/modules/components/elements/index.mjs b/src/interface/modules/components/elements/index.mjs index 0c6680c..7a4071f 100644 --- a/src/interface/modules/components/elements/index.mjs +++ b/src/interface/modules/components/elements/index.mjs @@ -10,6 +10,7 @@ import ButtonSingle from './button-single.mjs'; import LabelTitle from './label-title.mjs'; import MainTitle from './main-title.mjs'; import PaperCanvas from './paper-canvas.mjs'; +import SchemaForm from './schema-form.mjs'; export default styles => ({ 'tab-item': TabItem(styles), @@ -21,4 +22,5 @@ export default styles => ({ 'label-title': LabelTitle(styles), 'main-title': MainTitle(styles), 'paper-canvas': PaperCanvas(), + 'schema-form': SchemaForm(styles), }); diff --git a/src/interface/modules/components/elements/schema-form.mjs b/src/interface/modules/components/elements/schema-form.mjs new file mode 100644 index 0000000..1db5bab --- /dev/null +++ b/src/interface/modules/components/elements/schema-form.mjs @@ -0,0 +1,399 @@ +/** + * @file JSON Schema Form element. + * + * Renders a valid JSON Schema into a form with accompanying events. + */ +/* globals document, Event, JSONEditor, cncserver, _ */ +import { html, dispatch } from '/modules/hybrids.js'; +import jsonPath from '/modules/jsonpath.js'; +import apiInit from '/modules/utils/api-init.mjs'; + +// Initial configuration object for JSONEditor. +const globalJSONEditorSettings = { + iconlib: 'fontawesome5', + theme: 'bootstrap4', + disable_properties: true, + disable_edit_json: true, + required_by_default: true, + disable_array_add: true, + disable_array_delete: true, + no_additional_properties: true, +}; + +/** + * Callback for post-JSONEditor build form adjustments. + * + * @param {hybrids} host + * Host item to operate on. + */ +function customizeForm(host) { + // Customize the range sliders. + const inputs = host.shadowRoot.querySelectorAll('input[type=range]'); + inputs.forEach((item) => { + // Initial build setup value pulled from initial output. + const out = item.parentNode.querySelector('output'); + + // Hide it. + out.style.display = 'none'; + + // Force the step value to something more precise, assuming float. + const type = item.closest('[data-schematype]').getAttribute('data-schematype'); + item.step = type === 'integer' ? 1 : 0.01; + item.value = parseFloat(out.textContent); + + // Add a number input option. + const num = document.createElement('input'); + num.type = 'number'; + num.min = item.min; + num.max = item.max; + num.step = item.step; + num.value = item.value; + + // Lock the two together through change/input events. + num.addEventListener('change', () => { + item.value = num.value; + item.dispatchEvent(new Event('change')); + }); + item.addEventListener('input', () => { num.value = item.value; }); + + // Insert it above the slider. + item.parentNode.insertBefore(num, item); + }); +} + +/** + * Callback for external data post build form updates. + * + * @param {hybrids} host + * Host item to operate on. + */ +function externalUpdateForm(host) { + // Update the number input for the range sliders. + const inputs = host.shadowRoot.querySelectorAll('input[type=range]'); + inputs.forEach((item) => { + item.previousSibling.value = item.value; + }); +} + +/** + * Get only the parts that are different from a complete/deep JSON object. + * + * @param {object} object + * Input object to check for diff against the base. + * @param {object} base + * Base object to diff against the input. + * + * @returns {object} + * Only the differences between the two objects in full tree form. + */ +function dataDiff(object, base) { + const result = _.pick( + _.mapObject(object, (value, key) => ( + // eslint-disable-next-line no-nested-ternary + (!_.isEqual(value, base[key])) + // eslint-disable-next-line max-len + ? ((_.isObject(value) && _.isObject(base[key])) ? dataDiff(value, base[key]) : value) + : null + )), + value => (value !== null) + ); + + // Trim out empty keys + const entries = Object.entries(result); + entries.forEach(([key, val]) => { + if (typeof val === 'object' && Object.keys(val).length === 0) { + delete result[key]; + } + }); + + // TODO: Trim deeper changed objects. + return result; +} + +/** + * Direct callback for JSONEditor changes. + * + * @param {hybrids} host + * Host item to operate on. + */ +function dataChange(host) { + // Sync data pointer. + host.editor.data.current = host.editor.getValue(); + + if (!host.editor.data.last) { + host.editor.data.last = { ...host.editor.data.current }; + if (host.debug) console.log('host.editor.data.last SET'); + } else { + // Don't run a change event for external data push. + if (host.editor.data.setExternally) { + host.editor.data.setExternally = false; + + // Update values. + externalUpdateForm(host); + + // Dispatch host event for build customization update. + dispatch(host, 'build-update', { + detail: { form: host.shadowRoot.querySelector('form') }, + }); + + host.editor.data.last = { ...host.editor.data.current }; + if (host.debug) console.log('EXTERNAL, last data set', host.editor.data.last); + return; + } + + const diffObject = dataDiff(host.editor.data.current, host.editor.data.last); + + // Are there any actual changes? + if (Object.keys(diffObject).length) { + // Dispatch change with changed values. + dispatch(host, 'change', { detail: { diffObject } }); + if (host.debug) console.log('Diff', diffObject); + host.editor.data.last = { ...host.editor.data.current }; + } else if (host.debug) { + console.log('NO CHANGES'); + } + } +} + +/** + * Set the host schema by querying a specific API endpoint. + * + * @param {hybrids} host + * Host item to operate on. + * @param {string} endpoint + * Endpoint available via cncserver.api.[endpoint]. + * + * @returns {boolean} + * True if it worked, false otherwise. + */ +function setSchemaFromEndpoint(host, endpoint) { + // Is this a valid endpoint? + if (cncserver.api[endpoint]) { + host.loading = true; + cncserver.api[endpoint].schema().then(({ data }) => { + // With the schema, select only the JSON Path element. + if (host.jsonPath) { + const select = jsonPath.query(data, host.jsonPath); + if (select[0]) { + [host.schema] = select; + return true; + } + } + host.schema = data; + }); + return true; + } + + return false; +} + +/** + * Factory for managing host schema API path change/value. + * + * @param {string} [defaultAPI={}] + * Hash value from hybrids factory handler. + * + * @returns {hybrids factory} + */ +function apiChangeFactory(defaultAPI = '') { + return { + set: (host, value) => { + console.log('Api change...', value); + if (host.shadowRoot && host.initialized) { + // If it worked, set value. + if (setSchemaFromEndpoint(host, value)) { + console.log('Good API!'); + return value; + } + + console.log('Bad API!'); + // Bad Endpoint, cancel the value. + return ''; + } + + console.log('Not ready', value); + + // We're not ready to test, let it save; + return value; + }, + connect: (host, key) => { + if (host[key] === undefined) { + host[key] = defaultAPI; + } + }, + }; +} + + +/** + * Factory for managing host schema change/value. + * + * @param {string} [defaultSchema={}] + * Hash value from hybrids factory handler. + * + * @returns {hybrids factory} + */ +function schemaChangeFactory(defaultSchema = {}) { + return { + set: (host, value) => { + if (host.shadowRoot) { + const editorSettings = { + ...globalJSONEditorSettings, + object_layout: host.layout, + schema: value, + }; + + // Actually render the editor based on the schema. + const form = host.shadowRoot.querySelector('form'); + host.editor = new JSONEditor(form, editorSettings); + + // Setup base data store. + host.editor.data = { + setExternally: false, + last: null, + current: null, + }; + + // Form is built, run form customization. + customizeForm(host); + + // Dispatch host event for build customization. + dispatch(host, 'build', { detail: { form } }); + + // Set initial data of form. + host.editor.data.current = host.editor.getValue(); + + // Trigger an initial change to set values. + host.editor.on('change', () => { dataChange(host); }); + host.loading = false; + + return value; + } + + return {}; + }, + connect: (host, key) => { + if (host[key] === undefined) { + host[key] = defaultSchema; + } + }, + }; +} + +/** + * Factory for managing data change/value. + * + * @returns {hybrids factory} + */ +function dataChangeFactory() { + return { + get: host => host.editor.data.current, + set: (host, value, lastValue) => { + if (host.debug) console.log('EXTERNAL DATA SET', value, host.id); + const setData = host.appendData ? { ...host.editor.data.current, ...value } : value; + + // Only set the value if the data is any different. + const completeNew = { ...host.editor.data.current, ...value }; + const setDiff = dataDiff(completeNew, host.editor.data.current); + if (Object.entries(setDiff).length) { + if (host.debug) console.log('External Set diff is', setDiff); + host.editor.data.setExternally = true; + if (host.editor.setValue(setData)) { + host.editor.data.current = host.editor.getValue(); + return host.editor.data.current; + } + } else if (host.debug) { + console.log('No difference', host.id); + } + + return lastValue; + }, + connect: () => () => { }, + }; +} + +/** + * Initialize the element. + * + * @param {Hybrids} host + */ +function init(host) { + apiInit(() => { + if (!host.initialized) { + host.initialized = true; + + // Handle changes that couldn't happen until actual API init. + setSchemaFromEndpoint(host, host.api); + } + }); +} + +export default styles => ({ + initialized: false, + loading: true, + debug: false, + + // Either "Normal" or "grid". + layout: 'normal', + + // Whether setting data will append to existing data. False will apply to default data. + appendData: false, + + // Style selector and pixel height of "content area". + contentSelector: '', + contentHeight: 250, + + // Any extra styles to apply to the form. + extraStyles: '', + editor: {}, + data: dataChangeFactory(), + schema: schemaChangeFactory(), + + // TODO: Implement factory to manage changes of these two. + api: '', + jsonPath: '', + + render: ({ + loading, contentSelector, contentHeight, extraStyles, + }) => html` + ${styles} + + + + ${loading && html`
LOADING...
`} +
+ ${init} + `, +}); From 0dc2e9603be1892f71ed33d4312d7d12610d76b0 Mon Sep 17 00:00:00 2001 From: techninja Date: Fri, 24 Jul 2020 09:28:04 -0700 Subject: [PATCH 020/147] Move draw settings widget to schema form --- .../components/widgets/draw-settings.mjs | 182 +++++------------- src/interface/styles/interface.css | 41 ---- 2 files changed, 53 insertions(+), 170 deletions(-) diff --git a/src/interface/modules/components/widgets/draw-settings.mjs b/src/interface/modules/components/widgets/draw-settings.mjs index aad13d3..ed27d3e 100644 --- a/src/interface/modules/components/widgets/draw-settings.mjs +++ b/src/interface/modules/components/widgets/draw-settings.mjs @@ -1,33 +1,10 @@ /** * @file Main draw render settings widget definition with bindings. */ -/* globals document, paper, cncserver, JSONEditor, _ */ +/* globals document, cncserver */ import { html } from '/modules/hybrids.js'; import apiInit from '/modules/utils/api-init.mjs'; -// Globalize the content schema, to be filled on init. -let contentSchema = {}; - -// Settings & bounds JSONEditor objects once initialized. -let renderSettings = {}; -let boundsSettings = {}; - -// Hold last settings adjustments to figure out diff changes. -let lastRenderSettings = null; -let lastBoundsSettings = null; - -// Initial configuration object for JSONEditor. -const globalJSONEditorSettings = { - iconlib: 'fontawesome5', - theme: 'bootstrap4', - disable_properties: true, - disable_edit_json: true, - required_by_default: true, - disable_array_add: true, - disable_array_delete: true, - no_additional_properties: true, -}; - // Select the canvas-compose DOM element so we can attach to its update events. const canvasCompose = document.querySelector('canvas-compose'); @@ -46,47 +23,6 @@ function getItem({ contentItems }, hash) { return contentItems.filter(res => res.hash === hash)[0]; } -/** - * Get only the parts that are different from a complete/deep settings object. - * - * @param {object} inObject - * Input object to check for diff against the base. - * @param {object} inBase - * Base object to diff against the input. - * - * @returns {object} - * Only the differences between the two objects in full tree form. - */ -function settingsDiff(inObject, inBase) { - const changes = (object, base) => ( - _.pick( - _.mapObject(object, (value, key) => ( - // eslint-disable-next-line no-nested-ternary - (!_.isEqual(value, base[key])) - ? ((_.isObject(value) && _.isObject(base[key])) ? changes(value, base[key]) : value) - : null - )), - value => (value !== null) - ) - ); - - let finalChanges = changes(inObject, inBase) - - // When changing a fill/vectorize method, we don't want anything else. - if (finalChanges.fill) { - if (finalChanges.fill.method) { - finalChanges = { fill: { method: finalChanges.fill.method } }; - } - } - - if (finalChanges.vectorize) { - if (finalChanges.vectorize.method) { - finalChanges = { vectorize: { method: finalChanges.vectorize.method } }; - } - } - return finalChanges; -} - /** * Event trigger on display preview layer update from socket. * Used to update the list of available content items. @@ -134,43 +70,33 @@ function contentSelectChange(host, event) { } /** - * Callback for JSONEditor change on bounds settings. + * Callback for schema form change on bounds settings. * * @param {hybrids} host * Host item to operate on. + * @param {Event} event + * Event containing diffObject. */ -function boundsChange(host) { - if (!lastBoundsSettings) { - lastBoundsSettings = boundsSettings.getValue(); - } else { - const changed = boundsSettings.getValue(); - const diffObject = settingsDiff(changed, lastBoundsSettings); - if (Object.keys(diffObject).length) { - cncserver.api.content.item.update(host.item, { bounds: diffObject }); - lastBoundsSettings = boundsSettings.getValue(); - } +function boundsChange(host, { detail: { diffObject } }) { + if (Object.keys(diffObject).length) { + cncserver.api.content.item.update(host.item, { bounds: diffObject }); } } /** - * Callback for JSONEditor change on draw settings. + * Callback for schema form change on draw settings. * * @param {hybrids} host * Host item to operate on. + * @param {Event} event + * Event containing diffObject. */ -function settingsChange(host) { - if (!lastRenderSettings) { - lastRenderSettings = renderSettings.getValue(); - } else { - const changed = renderSettings.getValue(); - const diffObject = settingsDiff(changed, lastRenderSettings); - if (Object.keys(diffObject).length) { - if (host.item === 'project') { - cncserver.api.projects.current.update({ settings: diffObject }); - } else { - cncserver.api.content.item.update(host.item, { settings: diffObject }); - } - lastRenderSettings = renderSettings.getValue(); +function settingsChange(host, { detail: { diffObject } }) { + if (Object.keys(diffObject).length) { + if (host.item === 'project') { + cncserver.api.projects.current.update({ settings: diffObject }); + } else { + cncserver.api.content.item.update(host.item, { settings: diffObject }); } } } @@ -191,15 +117,8 @@ function itemChangeFactory(defaultItem = '') { // Does the item exist? if (item) { // Reset and set values of form fields - lastBoundsSettings = null; - boundsSettings.setValue(item.bounds); - - // Only update render settings if there are differences. - const diffSettings = settingsDiff(renderSettings.getValue(), lastRenderSettings); - if (Object.entries(diffSettings).length || value !== host.item) { - lastRenderSettings = null; - renderSettings.setValue(item.settings); - } + host.boundsForm.data = item.bounds; + host.settingsForm.data = item.settings; return value; } @@ -225,31 +144,10 @@ function init(host) { if (!host.initialized) { host.initialized = true; - // Get the full content schema for making forms. + // Get the schema forms attached to the host. apiInit(() => { - cncserver.api.content.schema().then(({ data: schema }) => { - contentSchema = schema; - - // With schema initialized, fill in the JSONEditor objects. - const settingsData = { - ...globalJSONEditorSettings, - // object_layout: 'grid', // TODO: Get this setup. - schema: contentSchema.properties.settings, - }; - const settingsForm = host.shadowRoot.querySelector('form#settings'); - renderSettings = new JSONEditor(settingsForm, settingsData); - renderSettings.on('change', () => settingsChange(host)); - - // Same for bounds - const boundsData = { - ...globalJSONEditorSettings, - object_layout: 'grid', - schema: contentSchema.properties.bounds, - }; - const boundsForm = host.shadowRoot.querySelector('form#bounds'); - boundsSettings = new JSONEditor(boundsForm, boundsData); - boundsSettings.on('change', () => boundsChange(host)); - }); + host.boundsForm = host.shadowRoot.querySelector('schema-form#bounds'); + host.settingsForm = host.shadowRoot.querySelector('schema-form#settings'); }); // Bind to display preview selection change to switch settings item. @@ -269,21 +167,47 @@ function init(host) { // Export the full widget definition. export default styles => ({ item: itemChangeFactory(''), + boundsForm: {}, + settingsForm: {}, initialized: false, contentItems: [], render: ({ item, contentItems }) => html` ${styles} - - ${contentItems.map(({ hash, title }) => html` `)} -
-
-
-
+ + + + ${init} `, }); diff --git a/src/interface/styles/interface.css b/src/interface/styles/interface.css index 44be464..8b9d629 100644 --- a/src/interface/styles/interface.css +++ b/src/interface/styles/interface.css @@ -149,47 +149,6 @@ slide-item h1 { border: 2px solid blueviolet; } -/* Render Settings */ -#content-select { - font-size: 1.5em; - font-weight: bold; - width: 100%; - margin-bottom: 1em; -} - -#settings-forms .tab-pane > div > div > h3.card-title { - display: none !important; -} - -#settings-forms > form > div > div.card-body { - padding: 0; - border-width: 0; -} - -#settings-forms > form > div div.card-body { - margin-bottom: 0 !important; -} - -#settings-forms > form > div > div.card-body > div.card > div.card-body { - padding: 0; - max-height: 200px; - overflow-y: auto; - overflow-x: hidden; -} - -#settings-forms > form > div > div.card-body.bg-light { - background-color: transparent !important; - border: 0 none; - box-shadow: none; - margin: 0 !important; -} - -#settings-forms span.btn-group, -#settings-forms #settings > div > h3, -#settings-forms #settings > div > p { - display: none !important; -} - /* Content Importer */ #content-importer { position: relative; From d070667239526c2746646a9312ce296e5c766690 Mon Sep 17 00:00:00 2001 From: techninja Date: Fri, 24 Jul 2020 10:52:55 -0700 Subject: [PATCH 021/147] Update lock file --- yarn.lock | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index 0a864f3..50da480 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2096,6 +2096,18 @@ escodegen@^1.11.1: optionalDependencies: source-map "~0.6.1" +escodegen@^1.8.1: + version "1.14.3" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" + integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw== + dependencies: + esprima "^4.0.1" + estraverse "^4.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + eslint-config-airbnb-base@^13.2.0: version "13.2.0" resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-13.2.0.tgz#f6ea81459ff4dec2dda200c35f1d8f7419d57943" @@ -2209,12 +2221,17 @@ espree@^6.1.2: acorn-jsx "^5.1.0" eslint-visitor-keys "^1.1.0" +esprima@1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-1.2.2.tgz#76a0fd66fcfe154fd292667dc264019750b1657b" + integrity sha1-dqD9Zvz+FU/SkmZ9wmQBl1CxZXs= + esprima@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" integrity sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM= -esprima@^4.0.0: +esprima@^4.0.0, esprima@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== @@ -3131,6 +3148,15 @@ jsonform@^2.1.6: resolved "https://registry.yarnpkg.com/jsonform/-/jsonform-2.1.6.tgz#f4f04300a1afc43bdf994f19c1ea353ff64dac63" integrity sha512-wezMsOZxJ5PZReR+PbunC/BNRB4RaQy0o/06mvd9to1mQIfOFIm67VOA973AwfO6fmrFMlPb4jTsJ6D27b3g0w== +jsonpath@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/jsonpath/-/jsonpath-1.0.2.tgz#e6aae681d03e9a77b4651d5d96eac5fc63b1fd13" + integrity sha512-rmzlgFZiQPc6q4HDyK8s9Qb4oxBnI5sF61y/Co5PV0lc3q2bIuRsNdueVbhoSHdKM4fxeimphOAtfz47yjCfeA== + dependencies: + esprima "1.2.2" + static-eval "2.0.2" + underscore "1.7.0" + jsonschema@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.2.5.tgz#bab69d97fa28946aec0a56a9cc266d23fe80ae61" @@ -4852,6 +4878,13 @@ sshpk@^1.7.0: safer-buffer "^2.0.2" tweetnacl "~0.14.0" +static-eval@2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/static-eval/-/static-eval-2.0.2.tgz#2d1759306b1befa688938454c546b7871f806a42" + integrity sha512-N/D219Hcr2bPjLxPiV+TQE++Tsmrady7TqAJugLy7Xk1EumfDWS/f5dtBbkRCGE7wKKXuYockQoj8Rm2/pVKyg== + dependencies: + escodegen "^1.8.1" + "statuses@>= 1.5.0 < 2": version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" @@ -5219,6 +5252,11 @@ typedarray-pool@^1.0.0, typedarray-pool@^1.1.0: bit-twiddle "^1.0.0" dup "^1.0.0" +underscore@1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.7.0.tgz#6bbaf0877500d36be34ecaa584e0db9fef035209" + integrity sha1-a7rwh3UA02vjTsqlhODbn+8DUgk= + underscore@^1.9.2: version "1.9.2" resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.2.tgz#0c8d6f536d6f378a5af264a72f7bec50feb7cf2f" From 77a26ff0023646fb29d34711df3b78dbe9af3360 Mon Sep 17 00:00:00 2001 From: techninja Date: Fri, 24 Jul 2020 16:55:43 -0700 Subject: [PATCH 022/147] Add support for hiding specific controls on a schema form --- .../components/elements/schema-form.mjs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/interface/modules/components/elements/schema-form.mjs b/src/interface/modules/components/elements/schema-form.mjs index 1db5bab..fd9c916 100644 --- a/src/interface/modules/components/elements/schema-form.mjs +++ b/src/interface/modules/components/elements/schema-form.mjs @@ -27,8 +27,10 @@ const globalJSONEditorSettings = { * Host item to operate on. */ function customizeForm(host) { + const form = host.shadowRoot.querySelector('form'); + // Customize the range sliders. - const inputs = host.shadowRoot.querySelectorAll('input[type=range]'); + const inputs = form.querySelectorAll('input[type=range]'); inputs.forEach((item) => { // Initial build setup value pulled from initial output. const out = item.parentNode.querySelector('output'); @@ -59,6 +61,17 @@ function customizeForm(host) { // Insert it above the slider. item.parentNode.insertBefore(num, item); }); + + // Hide items if the host requests it. + if (host.hidePaths) { + const paths = host.hidePaths.split(','); + const pathItems = form.querySelectorAll('[data-schemapath]'); + pathItems.forEach((item) => { + if (paths.includes(item.getAttribute('data-schemapath'))) { + item.style.display = 'none'; + } + }); + } } /** @@ -339,6 +352,9 @@ export default styles => ({ // Whether setting data will append to existing data. False will apply to default data. appendData: false, + // Comma separated list of schema paths to hide. + hidePaths: '', + // Style selector and pixel height of "content area". contentSelector: '', contentHeight: 250, From 838857840ba02941fecf6b3f1d12d919f14b94df Mon Sep 17 00:00:00 2001 From: techninja Date: Thu, 30 Jul 2020 23:20:46 -0700 Subject: [PATCH 023/147] Add handle colors to schema for users --- .../core/schemas/cncserver.schemas.color.implement.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/components/core/schemas/cncserver.schemas.color.implement.js b/src/components/core/schemas/cncserver.schemas.color.implement.js index 5750689..c72201b 100644 --- a/src/components/core/schemas/cncserver.schemas.color.implement.js +++ b/src/components/core/schemas/cncserver.schemas.color.implement.js @@ -6,6 +6,7 @@ module.exports = allowInherit => ({ type: 'object', title: 'Implement Details', + required: ['type'], properties: { type: { type: 'string', @@ -59,5 +60,15 @@ module.exports = allowInherit => ({ maximum: 20, default: 10, }, + handleColors: { + type: 'array', + title: 'Handle Color(s)', + description: 'Color of the handle for the implement, helps with physical user selection.', + items: { + type: 'string', + format: 'color', + title: 'Color', + }, + }, }, }); From 5f024e4de0523f3df6c30062cb1f75d3107f6b94 Mon Sep 17 00:00:00 2001 From: techninja Date: Thu, 30 Jul 2020 23:27:37 -0700 Subject: [PATCH 024/147] WIP update for Colorset/Tools API --- .../core/comms/api/cncserver.api.colors.js | 35 ++++++++- .../core/drawing/cncserver.drawing.colors.js | 71 ++++++++++++++++-- .../core/schemas/cncserver.schemas.color.js | 54 ++++++++++++- .../core/schemas/cncserver.schemas.colors.js | 9 ++- .../core/schemas/cncserver.schemas.tools.js | 75 +++++++++++++++++++ src/components/core/schemas/index.js | 1 + src/components/core/utils/cncserver.utils.js | 13 ++-- src/interface/lib/cncserver.client.api.js | 1 + 8 files changed, 240 insertions(+), 19 deletions(-) create mode 100644 src/components/core/schemas/cncserver.schemas.tools.js diff --git a/src/components/core/comms/api/cncserver.api.colors.js b/src/components/core/comms/api/cncserver.api.colors.js index e5ba9c0..ea1ae00 100644 --- a/src/components/core/comms/api/cncserver.api.colors.js +++ b/src/components/core/comms/api/cncserver.api.colors.js @@ -23,9 +23,9 @@ module.exports = (cncserver) => { }; } - // Add color/set from preset. + // Add color, or replace set from preset or custom set. if (req.route.method === 'post') { - // Adding preset, only err here is 404 preset not found. + // Set via preset, only err here is 404 preset not found. if (req.body.preset) { colors.applyPreset(req.body.preset, req.t) .then(postResolve) @@ -41,6 +41,37 @@ module.exports = (cncserver) => { return true; // Tell endpoint wrapper we'll handle the POST response. } + // Change set options directly. + if (req.route.method === 'patch') { + // If item data is attempted to be changed here, give a specific message for it. + if (req.body.items) { + cncserver.rest.err(res, 406)( + new Error('Patching the colors endpoint can only edit the current set details, not individual color items. Patch to /v2/colors/[ID].') + ); + } + + // TODO: + // - Colorsets need to be able to define size and relative position of tools + // - Provide templates for position and size based off crayola. + // - Colorsets items should allow for default selection criteria, and selectable + // options like: Color proximity (with weight), Transparency range + // - Re-ink distance for each implement + // - Machine defines a place for a holder to go relative to movable area. + // - Probably don't have to remove tools, but they make less sense here. + // - Edit below to update the set only + // - Get all of it to save to JSON + // - Load from JSON based on name (with reference in project) + // - As long as you save everything, you can take stuff away from users. + + // Validate data then edit. + cncserver.schemas.validateData('colors', req.body, true) + .then(colors.updateSet) + .then(postResolve) + .catch(cncserver.rest.err(res)); + + return true; // Tell endpoint wrapper we'll handle the PATCH response. + } + return false; }; diff --git a/src/components/core/drawing/cncserver.drawing.colors.js b/src/components/core/drawing/cncserver.drawing.colors.js index e6e9565..1c524c9 100644 --- a/src/components/core/drawing/cncserver.drawing.colors.js +++ b/src/components/core/drawing/cncserver.drawing.colors.js @@ -5,12 +5,13 @@ // Paper.js is all about that parameter reassignment life. /* eslint-disable no-param-reassign */ +const fs = require('fs'); const glob = require('glob'); const path = require('path'); const nc = require('nearest-color'); const { Color } = require('paper'); -// Default assumed color. +// Default assumed color (pen). const defaultColor = { id: 'color0', name: 'Black', @@ -21,6 +22,7 @@ const defaultColor = { width: 1, length: 0, stiffness: 1, + drawLength: 0, }, }; @@ -35,9 +37,25 @@ const ignoreWhite = { width: 0, length: 0, stiffness: 1, + drawLength: 0, }, }; +// Colorset Tool positions for Crayola default set. +const crayolaPositions = [14.5, 41.5, 66, 91, 116.5, 143, 169, 194]; +const crayolaDefaultTools = crayolaPositions.map((y, index) => ({ + id: `color${index}`, + title: `Color pan position ${index + 1}`, + x: 18.5, + y, + width: 27, + height: 19, + radius: 15, + parent: 'pan', + group: 'Colors', + position: 'center', +})); + // Default internal preset. const defaultPreset = { manufacturer: 'default', @@ -61,8 +79,11 @@ const colors = { width: 1, length: 0, stiffness: 1, + drawLength: 0, + handleWidth: 10, }, items: new Map(), // Items mapped by id. + tools: [], // Tools that get added with parentage. }, }; @@ -155,6 +176,13 @@ module.exports = (cncserver) => { } }); + + // Get a renderable color from a tool name. 'transparent' if no match. + colors.getToolColor = (name) => { + const item = colors.set.items.get(name); + return item ? item.color : 'transparent'; + }; + // Update a color by id with all its info. colors.update = (id, { name, color, size }) => { colors.set.items.set(id, { @@ -190,6 +218,9 @@ module.exports = (cncserver) => { if (preset) { colors.set = preset; cncserver.sockets.sendPaperUpdate(); + + // Trigger tools.update as colorset based tools update the list. + cncserver.binder.trigger('tools.update'); resolve(); } else { const validOptions = Object.keys(colors.presets).join(', '); @@ -212,20 +243,29 @@ module.exports = (cncserver) => { const preset = colors.presets[presetName]; const { schemas } = cncserver; if (preset) { + const isPen = preset.media === 'pen'; const presetInfo = colors.listPresets(t)[presetName]; const colorset = schemas.getDataDefault('colors', { name: presetName, title: presetInfo.name, description: presetInfo.description, implement: { - type: preset.media === 'pen' ? 'pen' : 'brush', - width: preset.media === 'pen' ? 1 : 3, // Size 3 crayola brush. - length: preset.media === 'pen' ? 0 : 10.75, // Size 3 crayola brush. - stiffness: preset.media === 'pen' ? 1 : 0.25, // Soft brush! + type: isPen ? 'pen' : 'brush', + width: isPen ? 1 : 3, // Size 3 crayola brush. + length: isPen ? 0 : 10.75, // Size 3 crayola brush. + stiffness: isPen ? 1 : 0.25, // Soft brush! + drawLength: isPen ? 0 : 482, // 48.2cm medium brush distance. + handleWidth: isPen ? 10 : 4.5, // Size 3 crayola brush handle. }, }); - colorset.items = new Map(); + // Load in the default expected tools for watercolors if not a pen. + if (!isPen) { + colorset.tools = [...crayolaDefaultTools]; + } + + // Build colorset item map. No implement overrides needed. + colorset.items = new Map(); Object.entries(preset.colors).forEach(([name, color]) => { const id = `color${colorset.items.size}`; colorset.items.set(id, schemas.getDataDefault('color', { @@ -243,6 +283,24 @@ module.exports = (cncserver) => { return null; }; + // Load custom from given machine name. + colors.loadCustom = (name) => { + + }; + + // Get the path to the json file for a given custom colorset. + colors.getCustomSetPath = (name) => { + const sets = cncserver.utils.getUserDir('colorsets'); + return path.join(sets, `${name}.json`); + }; + + // Save custom from set + colors.saveCustom = () => { + const dest = colors.getCustomSetPath(colors.set.name); + // We should really have a version number in here. + fs.writeFileSync(dest, JSON.stringify(colors.getCurrentSet(), null, 2)); + }; + // Get the current colorset as a JSON ready object. colors.getCurrentSet = (t = s => s) => { const set = { @@ -281,6 +339,7 @@ module.exports = (cncserver) => { defaultSet = cncserver.binder.trigger('colors.setDefault', defaultSet); colors.set = defaultSet; + cncserver.binder.trigger('tools.update'); }); }; diff --git a/src/components/core/schemas/cncserver.schemas.color.js b/src/components/core/schemas/cncserver.schemas.color.js index 2c1de3f..8d1ceba 100644 --- a/src/components/core/schemas/cncserver.schemas.color.js +++ b/src/components/core/schemas/cncserver.schemas.color.js @@ -17,10 +17,17 @@ const properties = { color: { type: 'string', format: 'color', - title: 'Hex color', + title: 'Color', description: 'Color that this item represents. Will be used to select areas to be printed with it.', }, - weight: { + selectionMethod: { + type: 'string', + title: 'Selection Method', + description: 'When parsing the drawing, how should this be selected?', + enum: ['color', 'opacity', 'strokeWidth'], + default: 'color', + }, + colorWeight: { type: 'number', title: 'Color Weighting', description: 'Amount to adjust for color selection preference. Smaller than 0 selects less often, larger than 0 selects more often.', @@ -28,6 +35,47 @@ const properties = { minimum: -1, maximum: 1, default: 0, + options: { dependencies: { selectionMethod: 'color' } }, + }, + opacityMin: { + type: 'number', + title: 'Minimum Opacity', + description: 'Opacity must be ABOVE this for opacity selection method.', + format: 'range', + minimum: 0, + maximum: 0.99, + default: 0, + options: { dependencies: { selectionMethod: 'opacity' } }, + }, + opacityMax: { + type: 'number', + title: 'Maximum Opacity', + description: 'Opacity must be BELOW this for opacity selection method.', + format: 'range', + minimum: 0.01, + maximum: 1, + default: 0.75, + options: { dependencies: { selectionMethod: 'opacity' } }, + }, + strokeWidthMin: { + type: 'number', + title: 'Minimum Stroke Width', + description: 'Stroke width must be ABOVE this for stroke width selection method.', + format: 'range', + minimum: 0, + maximum: 39.99, + default: 0, + options: { dependencies: { selectionMethod: 'strokeWidth' } }, + }, + strokeWidthMax: { + type: 'number', + title: 'Maximum Stroke Width', + description: 'Stroke width must be BELOW this for stroke width selection method.', + format: 'range', + minimum: 0.01, + maximum: 40, + default: 2, + options: { dependencies: { selectionMethod: 'strokeWidth' } }, }, // eslint-disable-next-line global-require implement: require('./cncserver.schemas.color.implement')(true), @@ -36,6 +84,6 @@ const properties = { module.exports = () => ({ type: 'object', title: 'Colorset Item', - required: ['color', 'name', 'id'], + required: ['color', 'name', 'id', 'selectionMethod'], properties, }); diff --git a/src/components/core/schemas/cncserver.schemas.colors.js b/src/components/core/schemas/cncserver.schemas.colors.js index 3cdcdb5..214e52d 100644 --- a/src/components/core/schemas/cncserver.schemas.colors.js +++ b/src/components/core/schemas/cncserver.schemas.colors.js @@ -2,7 +2,7 @@ * @file Colorset settings schema. * */ -/* eslint-disable max-len */ +/* eslint-disable max-len, global-require */ const properties = { name: { type: 'string', @@ -20,7 +20,6 @@ const properties = { description: 'Extra information about this colorset', default: '', }, - // eslint-disable-next-line global-require implement: require('./cncserver.schemas.color.implement')(false), items: { type: 'array', @@ -28,6 +27,12 @@ const properties = { description: 'Colorset items representing all the colors/implements used.', items: {}, // Set in indexer as color schema. }, + tools: { + type: 'array', + title: 'Colorset tools', + description: 'Tools defined by the colorset, usually relative to a placeholder tool position.', + items: require('./cncserver.schemas.tools')(), + }, }; module.exports = () => ({ diff --git a/src/components/core/schemas/cncserver.schemas.tools.js b/src/components/core/schemas/cncserver.schemas.tools.js new file mode 100644 index 0000000..e366c05 --- /dev/null +++ b/src/components/core/schemas/cncserver.schemas.tools.js @@ -0,0 +1,75 @@ +/** + * @file Individual Tool schema. + * + */ +/* eslint-disable max-len */ +module.exports = () => ({ + type: 'object', + title: 'Tool', + description: 'A tool defines a specific location (and optionally area) outside of the workspace to change a property of the current drawing implement.', + required: ['id', 'x', 'y'], + properties: { + id: { + type: 'string', + title: 'ID', + description: 'Machine name for colorset tool.', + }, + title: { + type: 'string', + title: 'Title', + description: 'Human readable name for the tool.', + }, + x: { + type: 'number', + format: 'number', + title: 'X Point', + description: 'X coordinate of the position of the tool in mm.', + }, + y: { + type: 'number', + format: 'number', + title: 'Y Point', + description: 'Y coordinate of top left position of the tool in mm away from parent top left.', + }, + position: { + type: 'string', + title: 'Position Type', + description: 'Height of the usable tool area in mm.', + enum: ['center', 'topleft'], + default: 'center', + }, + width: { + type: 'number', + format: 'number', + title: 'Width', + description: 'Width of the usable tool area in mm.', + minimum: 1, + }, + height: { + type: 'number', + format: 'number', + title: 'Height', + description: 'Height of the usable tool area in mm.', + minimum: 1, + }, + radius: { + type: 'number', + format: 'number', + title: 'Radius', + description: 'Amount in mm to curve corners of tool area. Set to 0 for square corners.', + minimum: 0, + }, + parent: { + type: 'string', + title: 'Parent tool', + description: 'Parent tool ID to base X and Y off of.', + default: '', + }, + group: { + type: 'string', + title: 'Tool Group', + description: 'Arbitrary grouping tag for tools', + default: '', + }, + }, +}); diff --git a/src/components/core/schemas/index.js b/src/components/core/schemas/index.js index 781dec7..61ba6d7 100644 --- a/src/components/core/schemas/index.js +++ b/src/components/core/schemas/index.js @@ -15,6 +15,7 @@ module.exports = (cncserver) => { 'path', 'color', 'colors', + 'tools', ]; const settingsKeys = ['fill', 'stroke', 'text', 'vectorize', 'path']; diff --git a/src/components/core/utils/cncserver.utils.js b/src/components/core/utils/cncserver.utils.js index d7007ed..84b75bb 100644 --- a/src/components/core/utils/cncserver.utils.js +++ b/src/components/core/utils/cncserver.utils.js @@ -16,15 +16,16 @@ module.exports = (cncserver) => { /** * Sanity check a given coordinate within the absolute area. * @param {object} point - * The point to be checked and operated on by reference. - * // TODO: It's an antipattern to operate by ref, refactor. - * @return {null} + * The point in absolute steps to be checked and operated on by reference. + * + * @return {object} + * The final sanitized coordinate. */ utils.sanityCheckAbsoluteCoord = ({ x, y }) => { const { maxArea } = cncserver.settings.bot; return { - x: Math.max(0, x > maxArea.width ? maxArea.width : x), - y: Math.max(0, y > maxArea.height ? maxArea.height : y), + x: Math.round(Math.max(0, x > maxArea.width ? maxArea.width : x)), + y: Math.round(Math.max(0, y > maxArea.height ? maxArea.height : y)), }; }; @@ -79,7 +80,7 @@ module.exports = (cncserver) => { const botHome = utils.getDir(path.resolve(home, cncserver.settings.gConf.get('botType'))); // Home base dir? or bot specific? - if (['projects'].includes(name)) { + if (['projects', 'colorsets'].includes(name)) { return utils.getDir(path.resolve(botHome, name)); } return utils.getDir(path.resolve(home, name)); diff --git a/src/interface/lib/cncserver.client.api.js b/src/interface/lib/cncserver.client.api.js index 9c7f9a0..d280742 100644 --- a/src/interface/lib/cncserver.client.api.js +++ b/src/interface/lib/cncserver.client.api.js @@ -169,6 +169,7 @@ cncserver.api = { add: data => _post('colors', { data }), save: data => _put(`colors/${data.id}`, { data }), delete: id => _delete(`colors/${id}`), + schema: () => _options('colors'), }, pen: { /** From bcc711f2ada8bb92b0bc0333446ef3afd538e2a9 Mon Sep 17 00:00:00 2001 From: techninja Date: Thu, 30 Jul 2020 23:28:01 -0700 Subject: [PATCH 025/147] Deliver the full data object with the diff --- src/interface/modules/components/elements/schema-form.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interface/modules/components/elements/schema-form.mjs b/src/interface/modules/components/elements/schema-form.mjs index fd9c916..06705f4 100644 --- a/src/interface/modules/components/elements/schema-form.mjs +++ b/src/interface/modules/components/elements/schema-form.mjs @@ -159,7 +159,7 @@ function dataChange(host) { // Are there any actual changes? if (Object.keys(diffObject).length) { // Dispatch change with changed values. - dispatch(host, 'change', { detail: { diffObject } }); + dispatch(host, 'change', { detail: { diffObject, data: host.editor.data.current } }); if (host.debug) console.log('Diff', diffObject); host.editor.data.last = { ...host.editor.data.current }; } else if (host.debug) { From 3bea6ce9fb6577ff8957b912d6f49f01e75614ed Mon Sep 17 00:00:00 2001 From: techninja Date: Thu, 30 Jul 2020 23:29:44 -0700 Subject: [PATCH 026/147] WIP colorset editor --- .../components/widgets/colorset-editor.mjs | 248 ------------------ .../components/widgets/colorset-item.mjs | 15 -- .../widgets/colorsets/colorset-color-item.mjs | 16 ++ .../widgets/colorsets/colorset-colors.mjs | 96 +++++++ .../widgets/colorsets/colorset-edit-color.mjs | 38 +++ .../colorsets/colorset-edit-implement.mjs | 36 +++ .../widgets/colorsets/colorset-edit-set.mjs | 38 +++ .../widgets/colorsets/colorset-editor.mjs | 98 +++++++ .../widgets/colorsets/colorset-presets.mjs | 18 ++ .../widgets/colorsets/pane-utils.mjs | 10 + .../modules/components/widgets/index.mjs | 23 +- 11 files changed, 369 insertions(+), 267 deletions(-) delete mode 100644 src/interface/modules/components/widgets/colorset-editor.mjs delete mode 100644 src/interface/modules/components/widgets/colorset-item.mjs create mode 100644 src/interface/modules/components/widgets/colorsets/colorset-color-item.mjs create mode 100644 src/interface/modules/components/widgets/colorsets/colorset-colors.mjs create mode 100644 src/interface/modules/components/widgets/colorsets/colorset-edit-color.mjs create mode 100644 src/interface/modules/components/widgets/colorsets/colorset-edit-implement.mjs create mode 100644 src/interface/modules/components/widgets/colorsets/colorset-edit-set.mjs create mode 100644 src/interface/modules/components/widgets/colorsets/colorset-editor.mjs create mode 100644 src/interface/modules/components/widgets/colorsets/colorset-presets.mjs create mode 100644 src/interface/modules/components/widgets/colorsets/pane-utils.mjs diff --git a/src/interface/modules/components/widgets/colorset-editor.mjs b/src/interface/modules/components/widgets/colorset-editor.mjs deleted file mode 100644 index f3d0dda..0000000 --- a/src/interface/modules/components/widgets/colorset-editor.mjs +++ /dev/null @@ -1,248 +0,0 @@ -/** - * @file Tab group element definition. - */ -/* globals window */ -import ColorsetItem from './colorset-item.mjs'; -import apiInit from '/modules/utils/api-init.mjs'; -import { - html, children, dispatch, property -} from '/modules/hybrids.js'; - -function findItem(items, name) { - return items.filter(item => (item.active = item.name === name))[0]; -} - -// Trigger deletion of the colorset item. -function removeItem(title, name) { - return (host, event) => { - event.stopPropagation(); - - // eslint-disable-next-line no-alert - if (window.confirm(`Delete Color: ${name} - "${title}"?`)) { - dispatch(host, 'remove', { detail: { name } }); - } - }; -} - -// Trigger editing of the item -function editItem(name) { - return (host) => { - host.activeItem = name; - host.isEditing = !!findItem(host.items, name); - - // After change custom event is dispatched - // for the user of tab-group element - dispatch(host, 'change'); - }; -} - -// Trigger editing of the item -function cancelEdit(host) { - // console.log('Edit', name); - host.activeName = ''; - host.activeColor = '#000000'; - host.activeTitle = ''; - host.activeItem = ''; - host.isEditing = !!findItem(host.items, ''); - host.errorName = false; - host.errorTitle = false; - - // After change custom event is dispatched - // for the user of tab-group element - dispatch(host, 'change'); -} - -function saveItem(op) { - return (host) => { - // First, check that we have all the info we need. - if (!host.activeName.trim()) { - host.errorName = true; - host.render(); - return; - } - - if (!host.activeTitle.trim()) { - host.errorTitle = true; - host.render(); - return; - } - - // Actually dispatch the change, then clear. - dispatch(host, op, { - detail: { - name: host.activeName, - color: host.activeColor, - title: host.activeTitle, - }, - }); - - cancelEdit(host); - }; -} - -// Catch edits to the name field, that changes what we're editing. -function updateActiveName(host, event) { - host.activeName = event.target.value; - host.isEditing = !!findItem(host.items, host.activeName); -} - -// Catch the first render of a host element, and dispatch refresh. -function init(host) { - apiInit(() => { - if (!host.initialized) { - host.initialized = true; - dispatch(host, 'init'); - } - }); -} - -export default styles => ({ - // Children defined in 'colorset-item.js' - items: children(ColorsetItem), - initialized: false, - activeName: '', - activeColor: '#000000', - activeTitle: '', - isEditing: false, - errorName: false, - errorTitle: false, - - // Sets and returns active item by name, which can be - // used by the user of tab-group element - activeItem: { - set: (host, name) => { - const item = findItem(host.items, name); - - if (item) { - host.activeName = item.name; - host.activeTitle = item.title; - host.activeColor = item.color; - } - return item ? item.name : ''; - }, - }, - render: ({ - items, - activeName, - activeTitle, - activeColor, - isEditing, - errorName, - errorTitle, - }) => html` - ${styles} - -
-
-
- ${items.map(({ - name, title, color, active, - }) => html` -
-
- ${name} -

${title}

- - - - - -
-
- `)} -
-
-
-
-
-
- -
- -
-

- Unique identifier for the color, match to a tool to change to it -

-
- -
- -
- -
-

- Path fill and stroke nearest this color will snap to the - matching tool/id -

-
-
-
-
- -
- -
-

- Human readable label for the color and/or implement -

-
- -
- - ${!isEditing - && html` - - `} - ${isEditing - && html` - ${isEditing} - `} -
-
-
-
-
- ${init} - `, -}); diff --git a/src/interface/modules/components/widgets/colorset-item.mjs b/src/interface/modules/components/widgets/colorset-item.mjs deleted file mode 100644 index d265aa7..0000000 --- a/src/interface/modules/components/widgets/colorset-item.mjs +++ /dev/null @@ -1,15 +0,0 @@ -/** - * @file Colorset single color item element definition. - */ -import { html } from '/modules/hybrids.js'; - -export default { - title: 'Color', - name: 'color0', - color: '#000000', - active: false, - - render: ({ title, name, color }) => html` -
${title} - ${name} - ${color}
- `, -}; diff --git a/src/interface/modules/components/widgets/colorsets/colorset-color-item.mjs b/src/interface/modules/components/widgets/colorsets/colorset-color-item.mjs new file mode 100644 index 0000000..2990f0b --- /dev/null +++ b/src/interface/modules/components/widgets/colorsets/colorset-color-item.mjs @@ -0,0 +1,16 @@ +/** + * @file Colorset Editor: single colorset item element definition. + */ +import { html } from '/modules/hybrids.js'; + +export default { + id: '', + name: '', + color: '#000000', + usesTool: false, + type: 'pen', + + render: () => html` +
This element holds data to be rendered in the host.
+ `, +}; diff --git a/src/interface/modules/components/widgets/colorsets/colorset-colors.mjs b/src/interface/modules/components/widgets/colorsets/colorset-colors.mjs new file mode 100644 index 0000000..90ab0d3 --- /dev/null +++ b/src/interface/modules/components/widgets/colorsets/colorset-colors.mjs @@ -0,0 +1,96 @@ +/** + * @file Colorset Editor: view colors element definition. + */ +import ColorsetColorItem from './colorset-color-item.mjs'; +import { handleSwitch } from './pane-utils.mjs'; +import { html, children } from '/modules/hybrids.js'; + +// TODO: +// - Use Schema form to build out the rest of the controls here. +// - Figure out some way to manage what the back button does based on how it was switched to. +// - Figure out some way to get data into the forms when you land there +// - Build a new slide for tool management +// - Build a preset loader (including getting back to custom ones). +// - Customize the implement form and add a display for brush/pen visualization. +// - Finish building the custom colorset saver. +// - Fix the incredibly ugly padding issue. General styles? +export default styles => ({ + items: children(ColorsetColorItem), + name: '', + description: '', + + render: ({ items, name, description }) => html` + ${styles} + + + +

${name}

+

${description}

+
+ ${items.map(({ id, name, color, type }) => html` +
+ + + +
+

${name}

+ + +
+
+ `)} +
+ + `, +}); + +//name=${colorset.title} +//description = ${ colorset.description } diff --git a/src/interface/modules/components/widgets/colorsets/colorset-edit-color.mjs b/src/interface/modules/components/widgets/colorsets/colorset-edit-color.mjs new file mode 100644 index 0000000..a7ec658 --- /dev/null +++ b/src/interface/modules/components/widgets/colorsets/colorset-edit-color.mjs @@ -0,0 +1,38 @@ +/** + * @file Colorset Editor: edit single color element definition. + */ +import { html } from '/modules/hybrids.js'; +import { handleSwitch } from './pane-utils.mjs'; +/* globals document */ + +function customizeForm(host, { detail: { form } }) { + // Hide the implement form, we put this on a different slide. + const implement = form.querySelector('div[data-schemapath="root.implement"]'); + implement.style.display = 'none'; + + const button = document.createElement('button-single'); + button.text = 'Override Implement'; + button.icon = 'pencil-alt'; + button.desc = 'Override the colorset implement'; + button.onclick = () => { handleSwitch('edit-implement')(host); }; + + implement.parentNode.insertBefore(button, implement); +} + +export default styles => ({ + initialized: false, + + render: () => html` + ${styles} + + + + `, +}); diff --git a/src/interface/modules/components/widgets/colorsets/colorset-edit-implement.mjs b/src/interface/modules/components/widgets/colorsets/colorset-edit-implement.mjs new file mode 100644 index 0000000..316a12a --- /dev/null +++ b/src/interface/modules/components/widgets/colorsets/colorset-edit-implement.mjs @@ -0,0 +1,36 @@ +/** + * @file Colorset Editor: edit implement element definition. + */ +import { html } from '/modules/hybrids.js'; +import { handleSwitch } from './pane-utils.mjs'; + + +function implementChange(host, { detail: { data } }) { + const item = document.querySelector('tool-implement'); + + // Assign all data to the object. + item.width = data.width; + item.type = data.type; + item.length = data.length; + item.stiffness = data.stiffness; + item.type = data.type; + item.type = data.type; + item.handleWidth = data.handleWidth; +} + +export default styles => ({ + initialized: false, + + render: () => html` + ${styles} + + + `, +}); diff --git a/src/interface/modules/components/widgets/colorsets/colorset-edit-set.mjs b/src/interface/modules/components/widgets/colorsets/colorset-edit-set.mjs new file mode 100644 index 0000000..4163252 --- /dev/null +++ b/src/interface/modules/components/widgets/colorsets/colorset-edit-set.mjs @@ -0,0 +1,38 @@ +/** + * @file Colorset Editor: edit colorset element definition. + */ +/* globals cncserver */ +import { html } from '/modules/hybrids.js'; +import { handleSwitch } from './pane-utils.mjs'; + + +function customizeForm(host, { detail: { form } }) { + +} + +export default styles => ({ + initialized: false, + + render: () => html` + ${styles} + + + +
+ Replace Current: + + +
+ `, +}); diff --git a/src/interface/modules/components/widgets/colorsets/colorset-editor.mjs b/src/interface/modules/components/widgets/colorsets/colorset-editor.mjs new file mode 100644 index 0000000..2a2c341 --- /dev/null +++ b/src/interface/modules/components/widgets/colorsets/colorset-editor.mjs @@ -0,0 +1,98 @@ +/** + * @file Colorset editor slide group element definition. + */ +/* globals cncserver */ + +import apiInit from '/modules/utils/api-init.mjs'; +import { html, dispatch } from '/modules/hybrids.js'; + + +// Catch the first render of a host element, and dispatch refresh. +function init(host) { + apiInit(() => { + if (!host.initialized) { + host.initialized = true; + dispatch(host, 'init'); + + cncserver.api.colors.stat().then(({ data }) => { + host.setTitle = data.set.title; + host.setDescription = data.set.description; + + host.colors = [...data.set.items]; + + host.colors.forEach((item) => { + item.type = item.implement.type === 'inherit' + ? data.set.implement.type : item.implement.type; + }); + + }); + } + }); +} + +// TODO: +// - Implement paged editor interface +// - Slide Grid under window. +// - Colorset title/desc/default implement editor +// - Colorset item viewer, with bot tool matched positions. +// - Colorset item editor for all fields except... +// - Implement editor from colorset level/item level +// - Implement preset loader for both internal and custom presets. + +function switchPane(host, { detail: { destination, options } }) { + host.slide = destination; + console.log('Switch to', destination, options); +} + +export default styles => ({ + colors: [], + setTitle: '', + setDescription: '', + initialized: false, + slide: 'colors', + + render: ({ setTitle, setDescription, colors, slide }) => html` + ${styles} + + + + + + + + + + + ${colors.map(({ id, name, color, type }) => html` + + + `)} + + + + + + + + + + ${init} + `, +}); diff --git a/src/interface/modules/components/widgets/colorsets/colorset-presets.mjs b/src/interface/modules/components/widgets/colorsets/colorset-presets.mjs new file mode 100644 index 0000000..b900ac5 --- /dev/null +++ b/src/interface/modules/components/widgets/colorsets/colorset-presets.mjs @@ -0,0 +1,18 @@ +/** + * @file Colorset Editor: preset loader element definition. + */ +import { html } from '/modules/hybrids.js'; +import { handleSwitch } from './pane-utils.mjs'; + +export default styles => ({ + initialized: false, + + render: () => html` + ${styles} + +
Presets
+ `, +}); diff --git a/src/interface/modules/components/widgets/colorsets/pane-utils.mjs b/src/interface/modules/components/widgets/colorsets/pane-utils.mjs new file mode 100644 index 0000000..0d434f3 --- /dev/null +++ b/src/interface/modules/components/widgets/colorsets/pane-utils.mjs @@ -0,0 +1,10 @@ +/** + * @file Colorset Editor: Shared slide/pane management utils. + */ +import { dispatch } from '/modules/hybrids.js'; + +export function handleSwitch(destination = '', options = {}) { + return (host) => { + dispatch(host, 'switchpane', { detail: { destination, options } }); + }; +} diff --git a/src/interface/modules/components/widgets/index.mjs b/src/interface/modules/components/widgets/index.mjs index e0789f2..9514619 100644 --- a/src/interface/modules/components/widgets/index.mjs +++ b/src/interface/modules/components/widgets/index.mjs @@ -2,8 +2,7 @@ * @file Index for all widgets (groups of elements that go in panels), * allows for binding and initialization with styles. */ -import ColorsetEditor from './colorset-editor.mjs'; -import ColorsetItem from './colorset-item.mjs'; + import HeightSettings from './height-settings.mjs'; import HeightPresets from './height-presets.mjs'; import ToolsBasic from './tools-basic.mjs'; @@ -14,9 +13,16 @@ import CanvasCompose from './canvas/canvas-compose.mjs'; import CanvasPrint from './canvas/canvas-print.mjs'; import DrawSettings from './draw-settings.mjs'; +// Colorset Editor and associated components. +import ColorsetEditor from './colorsets/colorset-editor.mjs'; +import ColorsetColors from './colorsets/colorset-colors.mjs'; +import ColorsetColorItem from './colorsets/colorset-color-item.mjs'; +import ColorsetEditColor from './colorsets/colorset-edit-color.mjs'; +import ColorsetEditImplement from './colorsets/colorset-edit-implement.mjs'; +import ColorsetEditSet from './colorsets/colorset-edit-set.mjs'; +import ColorsetPresets from './colorsets/colorset-presets.mjs'; + export default styles => ({ - 'colorset-item': ColorsetItem, - 'colorset-editor': ColorsetEditor(styles), 'height-settings': HeightSettings(styles), 'tools-basic': ToolsBasic(styles), 'scratch-controls': ScratchControls(styles), @@ -26,4 +32,13 @@ export default styles => ({ 'canvas-print': CanvasPrint(styles), 'draw-settings': DrawSettings(styles), 'content-importer': ContentImporter(styles), + + // Colorset editor components. + 'colorset-editor': ColorsetEditor(styles), + 'colorset-colors': ColorsetColors(styles), + 'colorset-color-item': ColorsetColorItem, + 'colorset-edit-color': ColorsetEditColor(styles), + 'colorset-edit-implement': ColorsetEditImplement(styles), + 'colorset-edit-set': ColorsetEditSet(styles), + 'colorset-presets': ColorsetPresets(styles), }); From e17062318d3220845aea75c4ac934361470e0af5 Mon Sep 17 00:00:00 2001 From: techninja Date: Thu, 30 Jul 2020 23:30:08 -0700 Subject: [PATCH 027/147] WIP SKETCH: tool implement renderer. Most of this should be removed :/ --- src/interface/index.html | 45 ++- .../modules/components/elements/index.mjs | 2 + .../elements/tool-implement-copy.mjs | 235 +++++++++++++ .../components/elements/tool-implement.mjs | 315 ++++++++++++++++++ 4 files changed, 592 insertions(+), 5 deletions(-) create mode 100644 src/interface/modules/components/elements/tool-implement-copy.mjs create mode 100644 src/interface/modules/components/elements/tool-implement.mjs diff --git a/src/interface/index.html b/src/interface/index.html index c272ff8..bf78691 100644 --- a/src/interface/index.html +++ b/src/interface/index.html @@ -33,14 +33,49 @@ - + - - + + + + + + @@ -60,8 +95,8 @@ - - + +

TODO

diff --git a/src/interface/modules/components/elements/index.mjs b/src/interface/modules/components/elements/index.mjs index 7a4071f..18196ad 100644 --- a/src/interface/modules/components/elements/index.mjs +++ b/src/interface/modules/components/elements/index.mjs @@ -11,6 +11,7 @@ import LabelTitle from './label-title.mjs'; import MainTitle from './main-title.mjs'; import PaperCanvas from './paper-canvas.mjs'; import SchemaForm from './schema-form.mjs'; +import ToolImplement from './tool-implement.mjs'; export default styles => ({ 'tab-item': TabItem(styles), @@ -23,4 +24,5 @@ export default styles => ({ 'main-title': MainTitle(styles), 'paper-canvas': PaperCanvas(), 'schema-form': SchemaForm(styles), + 'tool-implement': ToolImplement(styles), }); diff --git a/src/interface/modules/components/elements/tool-implement-copy.mjs b/src/interface/modules/components/elements/tool-implement-copy.mjs new file mode 100644 index 0000000..8b5624e --- /dev/null +++ b/src/interface/modules/components/elements/tool-implement-copy.mjs @@ -0,0 +1,235 @@ +/** + * @file Implement display element definition. + */ +import { html } from '/modules/hybrids.js'; + +function renderImplement(host) { + if (!host || !host.shadowRoot) return; + const canvas = host.shadowRoot.querySelector('canvas'); + const ctx = canvas.getContext('2d'); + ctx.clearRect(0, 0, canvas.width, canvas.height); + + const { + scale, + handleHeight, + bracketSize, + bracketNotch, + bracketPadding, + + // Implement specifics. + handleWidth, + width, + length, + type, + stiffness, + color, + } = host; + + const handleColors = host.handleColors.split(','); + + let x; + let y; + + const handle = { + left: 40, + top: 30, + width: handleWidth * scale, + height: handleHeight, + }; + + const brush = { + width: width * scale, + height: length * scale, + }; + + brush.top = handle.top + handle.height; + brush.left = (handle.left + (handle.width / 2)) - brush.width / 2; + + // Draw Handle: | + if (handleColors.length > 0) { + const height = handle.height / handleColors.length; + handleColors.forEach((cColor, index) => { + ctx.fillStyle = cColor; + ctx.fillRect(handle.left, handle.top + height * index, handle.width, height); + }); + } else { + ctx.fillStyle = color; + ctx.fillRect(handle.left, handle.top, handle.width, handle.height); + } + + // Round and specular highlight. + const penColor = ctx.createLinearGradient(handle.left, 0, handle.left + handle.width, 0); + penColor.addColorStop(0, 'rgba(0, 0, 0, 0.7)'); + penColor.addColorStop(0.6, 'rgba(0, 0, 0, 0.3)'); + penColor.addColorStop(0.7, 'rgba(255, 255, 255, 1)'); + penColor.addColorStop(0.8, 'rgba(0, 0, 0, 0.1)'); + penColor.addColorStop(1, 'rgba(0, 0, 0, 0)'); + ctx.fillStyle = penColor; + ctx.fillRect(handle.left, handle.top, handle.width, handle.height); + + // Pen or brush. + const center = brush.left + brush.width / 2; + const largerWidth = brush.width > handle.width ? brush.width / 2 : handle.width / 2; + const minLength = type === 'pen' ? 5 : 2; + const drawLength = length < minLength ? minLength * scale : length * scale; + if (type === 'pen') { + // Draw a trapezoidal representation of the pen. + y = handle.top + handle.height; + ctx.beginPath(); + ctx.moveTo(handle.left, y); + ctx.lineTo(handle.left + handle.width, y); + ctx.lineTo(center + ((width * scale) / 2), y + drawLength); + ctx.lineTo(center - ((width * scale) / 2), y + drawLength); + ctx.fillStyle = color; + ctx.fill(); + } else { + // Draw Brush: / + // - We know where it starts (center bottom of handle) + // - It should end the curve in a ratio from handle left less the brush width + const curve = 1 - stiffness; + const bendMax = 20; + brush.height = drawLength - (curve * bendMax); + //brush.height -= curve * bendMax; + const maxDeflection = center - (handle.left - (width * scale)); + const deflectionLength = maxDeflection * curve; + const curveEnd = center - deflectionLength; + + ctx.beginPath(); + ctx.moveTo(center, brush.top); + ctx.bezierCurveTo( + center, brush.top + (maxDeflection - deflectionLength), // First Control (Start) + curveEnd + deflectionLength, brush.top + brush.height - (stiffness * bendMax), // Second Control (End) + curveEnd, brush.top + brush.height // End Point. + ); + ctx.lineWidth = width * scale; + + const brushColor = ctx.createLinearGradient(center, brush.top, curveEnd, brush.top + brush.height); + brushColor.addColorStop('0', 'gray'); + brushColor.addColorStop('1', color); + ctx.strokeStyle = brushColor; + ctx.stroke(); + } + + // Draw Measures (1px black) + ctx.fillStyle = 'black'; + ctx.lineWidth = 1; + ctx.strokeStyle = 'black'; + + // Only draw left and right measures with brush/other. + if (type !== 'pen') { + // Draw left measure: [ + x = center - largerWidth - bracketPadding; + ctx.beginPath(); + ctx.moveTo(x, brush.top); + ctx.lineTo(x - bracketSize, brush.top); + ctx.lineTo(x - bracketSize, brush.top + brush.height); + ctx.lineTo(x, brush.top + brush.height); + + // Notch. + ctx.moveTo(x - bracketSize, brush.top + (brush.height / 2)); + ctx.lineTo(x - bracketSize - bracketNotch, brush.top + (brush.height / 2)); + ctx.textAlign = 'right'; + ctx.fillText( + `${Math.round(stiffness * 100)}%`, + x - bracketSize - bracketNotch - 2, brush.top + (brush.height / 2) + 3.5 + ); + ctx.stroke(); + + // Draw right measure: ] + x = center + largerWidth + bracketPadding; + ctx.beginPath(); + ctx.moveTo(x, brush.top); + ctx.lineTo(x + bracketSize, brush.top); + ctx.lineTo(x + bracketSize, brush.top + brush.height); + ctx.lineTo(x, brush.top + brush.height); + + // Notch. + ctx.moveTo(x + bracketSize, brush.top + (brush.height / 2)); + ctx.lineTo(x + bracketSize + bracketNotch, brush.top + (brush.height / 2)); + ctx.textAlign = 'left'; + ctx.fillText( + `${length}mm`, + x + bracketSize + bracketNotch + 2, brush.top + (brush.height / 2) + 3.5 + ); + + ctx.stroke(); + } + + // Draw top measure bracket: + y = handle.top - bracketPadding; + ctx.beginPath(); + ctx.moveTo(handle.left, y); + ctx.lineTo(handle.left, y - bracketSize); + ctx.lineTo(handle.left + handle.width, y - bracketSize); + ctx.lineTo(handle.left + handle.width, y); + + // Notch. + ctx.moveTo(handle.left + (handle.width / 2), y - bracketSize); + ctx.lineTo(handle.left + (handle.width / 2), y - bracketSize - bracketNotch); + ctx.textAlign = 'center'; + ctx.fillText( + `${handleWidth}mm`, + handle.left + (handle.width / 2), y - bracketSize - bracketNotch - 3.5 + ); + ctx.stroke(); + + // Draw bottom measure bracket: + y = brush.top + drawLength + bracketPadding; + ctx.beginPath(); + ctx.moveTo(brush.left, y); + ctx.lineTo(brush.left, y + bracketSize); + ctx.lineTo(brush.left + brush.width, y + bracketSize); + ctx.lineTo(brush.left + brush.width, y); + + // Notch. + ctx.moveTo(brush.left + (brush.width / 2), y + bracketSize); + ctx.lineTo(brush.left + (brush.width / 2), y + bracketSize + bracketNotch); + ctx.textAlign = 'center'; + ctx.fillText( + `${width}mm`, + brush.left + (brush.width / 2), y + bracketSize + bracketNotch + 9 + ); + ctx.stroke(); +} + +function valueObserveFactory(defValue) { + return { + set: (host, value) => { + // console.log('Value set...', value); + renderImplement(host); + return value; + }, + connect: (host, key) => { + if (host[key] === undefined) { + host[key] = defValue; + } + }, + }; +} + +export default styles => ({ + // UI configuration specifics. + scale: 4.5, + handleHeight: 40, + bracketSize: 8, + bracketNotch: 5, + bracketPadding: 5, + + // Implement specifics. + handleColors: 'yellow', + handleWidth: 10, + width: 1, + length: 0, + type: 'pen', + stiffness: 1, + color: '#000000', + + render: host => html` + ${styles} + + + + + ${renderImplement} + `, +}); diff --git a/src/interface/modules/components/elements/tool-implement.mjs b/src/interface/modules/components/elements/tool-implement.mjs new file mode 100644 index 0000000..188b4d2 --- /dev/null +++ b/src/interface/modules/components/elements/tool-implement.mjs @@ -0,0 +1,315 @@ +/** + * @file Implement display element definition. + */ +import { html, svg } from '/modules/hybrids.js'; + +function renderImplement(host) { + if (!host || !host.shadowRoot) return; + const canvas = host.shadowRoot.querySelector('canvas'); + const ctx = canvas.getContext('2d'); + ctx.clearRect(0, 0, canvas.width, canvas.height); + + const { + scale, + handleHeight, + bracketSize, + bracketNotch, + bracketPadding, + + // Implement specifics. + handleWidth, + width, + length, + type, + stiffness, + color, + } = host; + + const handleColors = host.handleColors.split(','); + + let x; + let y; + + const handle = { + left: 40, + top: 30, + width: handleWidth * scale, + height: handleHeight, + }; + + const brush = { + width: width * scale, + height: length * scale, + }; + + brush.top = handle.top + handle.height; + brush.left = (handle.left + (handle.width / 2)) - brush.width / 2; + + // Draw Handle: | + if (handleColors.length > 0) { + const height = handle.height / handleColors.length; + handleColors.forEach((cColor, index) => { + ctx.fillStyle = cColor; + ctx.fillRect(handle.left, handle.top + height * index, handle.width, height); + }); + } else { + ctx.fillStyle = color; + ctx.fillRect(handle.left, handle.top, handle.width, handle.height); + } + + // Round and specular highlight. + const penColor = ctx.createLinearGradient(handle.left, 0, handle.left + handle.width, 0); + penColor.addColorStop(0, 'rgba(0, 0, 0, 0.7)'); + penColor.addColorStop(0.6, 'rgba(0, 0, 0, 0.3)'); + penColor.addColorStop(0.7, 'rgba(255, 255, 255, 1)'); + penColor.addColorStop(0.8, 'rgba(0, 0, 0, 0.1)'); + penColor.addColorStop(1, 'rgba(0, 0, 0, 0)'); + ctx.fillStyle = penColor; + ctx.fillRect(handle.left, handle.top, handle.width, handle.height); + + // Pen or brush. + const center = brush.left + brush.width / 2; + const largerWidth = brush.width > handle.width ? brush.width / 2 : handle.width / 2; + const minLength = type === 'pen' ? 5 : 2; + const drawLength = length < minLength ? minLength * scale : length * scale; + if (type === 'pen') { + // Draw a trapezoidal representation of the pen. + y = handle.top + handle.height; + ctx.beginPath(); + ctx.moveTo(handle.left, y); + ctx.lineTo(handle.left + handle.width, y); + ctx.lineTo(center + ((width * scale) / 2), y + drawLength); + ctx.lineTo(center - ((width * scale) / 2), y + drawLength); + ctx.fillStyle = color; + ctx.fill(); + } else { + // Draw Brush: / + // - We know where it starts (center bottom of handle) + // - It should end the curve in a ratio from handle left less the brush width + const curve = 1 - stiffness; + const bendMax = 20; + brush.height = drawLength - (curve * bendMax); + //brush.height -= curve * bendMax; + const maxDeflection = center - (handle.left - (width * scale)); + const deflectionLength = maxDeflection * curve; + const curveEnd = center - deflectionLength; + + ctx.beginPath(); + ctx.moveTo(center, brush.top); + ctx.bezierCurveTo( + center, brush.top + (maxDeflection - deflectionLength), // First Control (Start) + curveEnd + deflectionLength, brush.top + brush.height - (stiffness * bendMax), // Second Control (End) + curveEnd, brush.top + brush.height // End Point. + ); + ctx.lineWidth = width * scale; + + const brushColor = ctx.createLinearGradient(center, brush.top, curveEnd, brush.top + brush.height); + brushColor.addColorStop('0', 'gray'); + brushColor.addColorStop('1', color); + ctx.strokeStyle = brushColor; + ctx.stroke(); + } + + // Draw Measures (1px black) + ctx.fillStyle = 'black'; + ctx.lineWidth = 1; + ctx.strokeStyle = 'black'; + + // Only draw left and right measures with brush/other. + if (type !== 'pen') { + // Draw left measure: [ + x = center - largerWidth - bracketPadding; + ctx.beginPath(); + ctx.moveTo(x, brush.top); + ctx.lineTo(x - bracketSize, brush.top); + ctx.lineTo(x - bracketSize, brush.top + brush.height); + ctx.lineTo(x, brush.top + brush.height); + + // Notch. + ctx.moveTo(x - bracketSize, brush.top + (brush.height / 2)); + ctx.lineTo(x - bracketSize - bracketNotch, brush.top + (brush.height / 2)); + ctx.textAlign = 'right'; + ctx.fillText( + `${Math.round(stiffness * 100)}%`, + x - bracketSize - bracketNotch - 2, brush.top + (brush.height / 2) + 3.5 + ); + ctx.stroke(); + + // Draw right measure: ] + x = center + largerWidth + bracketPadding; + ctx.beginPath(); + ctx.moveTo(x, brush.top); + ctx.lineTo(x + bracketSize, brush.top); + ctx.lineTo(x + bracketSize, brush.top + brush.height); + ctx.lineTo(x, brush.top + brush.height); + + // Notch. + ctx.moveTo(x + bracketSize, brush.top + (brush.height / 2)); + ctx.lineTo(x + bracketSize + bracketNotch, brush.top + (brush.height / 2)); + ctx.textAlign = 'left'; + ctx.fillText( + `${length}mm`, + x + bracketSize + bracketNotch + 2, brush.top + (brush.height / 2) + 3.5 + ); + + ctx.stroke(); + } + + // Draw top measure bracket: + y = handle.top - bracketPadding; + ctx.beginPath(); + ctx.moveTo(handle.left, y); + ctx.lineTo(handle.left, y - bracketSize); + ctx.lineTo(handle.left + handle.width, y - bracketSize); + ctx.lineTo(handle.left + handle.width, y); + + // Notch. + ctx.moveTo(handle.left + (handle.width / 2), y - bracketSize); + ctx.lineTo(handle.left + (handle.width / 2), y - bracketSize - bracketNotch); + ctx.textAlign = 'center'; + ctx.fillText( + `${handleWidth}mm`, + handle.left + (handle.width / 2), y - bracketSize - bracketNotch - 3.5 + ); + ctx.stroke(); + + // Draw bottom measure bracket: + y = brush.top + drawLength + bracketPadding; + ctx.beginPath(); + ctx.moveTo(brush.left, y); + ctx.lineTo(brush.left, y + bracketSize); + ctx.lineTo(brush.left + brush.width, y + bracketSize); + ctx.lineTo(brush.left + brush.width, y); + + // Notch. + ctx.moveTo(brush.left + (brush.width / 2), y + bracketSize); + ctx.lineTo(brush.left + (brush.width / 2), y + bracketSize + bracketNotch); + ctx.textAlign = 'center'; + ctx.fillText( + `${width}mm`, + brush.left + (brush.width / 2), y + bracketSize + bracketNotch + 9 + ); + ctx.stroke(); +} + +function valueObserveFactory(defValue) { + return { + set: (host, value) => { + // console.log('Value set...', value); + renderImplement(host); + return value; + }, + connect: (host, key) => { + if (host[key] === undefined) { + host[key] = defValue; + } + }, + }; +} + +export default styles => ({ + // UI configuration specifics. + scale: 4.5, + handleHeight: 40, + bracketSize: 8, + bracketNotch: 5, + bracketPadding: 5, + + // Implement specifics. + handleColors: 'yellow', + handleWidth: 10, + width: 1, + length: 0, + type: 'pen', + stiffness: 1, + color: '#000000', + + render: ({ + scale, + handleHeight, + bracketSize, + bracketNotch, + bracketPadding, + + // Implement specifics. + handleColors, + handleWidth, + width, + length, + type, + stiffness, + color, + }) => { + const handle = { + left: 40, + top: 30, + width: handleWidth * scale, + height: handleHeight, + }; + + const brush = { + width: width * scale, + height: length * scale, + }; + + const center = brush.left + brush.width / 2; + const largerWidth = brush.width > handle.width ? brush.width / 2 : handle.width / 2; + const minLength = type === 'pen' ? 5 : 2; + const drawLength = length < minLength ? minLength * scale : length * scale; + + return html` + ${styles} + + ${svg` + + + + + + + + + + + + ${handleWidth}mm + + + + + + + ${type !== 'pen' && svg` + + + + + + + `} + + + + `} + `; + }, +}); From 8b28ef7145308b4b2798c31b039f11043b289a59 Mon Sep 17 00:00:00 2001 From: techninja Date: Sat, 1 Aug 2020 16:40:42 -0700 Subject: [PATCH 028/147] Update tool implement rendering to SVG --- .../components/elements/tool-implement.mjs | 337 ++++++------------ 1 file changed, 115 insertions(+), 222 deletions(-) diff --git a/src/interface/modules/components/elements/tool-implement.mjs b/src/interface/modules/components/elements/tool-implement.mjs index 188b4d2..bbd96e3 100644 --- a/src/interface/modules/components/elements/tool-implement.mjs +++ b/src/interface/modules/components/elements/tool-implement.mjs @@ -3,211 +3,7 @@ */ import { html, svg } from '/modules/hybrids.js'; -function renderImplement(host) { - if (!host || !host.shadowRoot) return; - const canvas = host.shadowRoot.querySelector('canvas'); - const ctx = canvas.getContext('2d'); - ctx.clearRect(0, 0, canvas.width, canvas.height); - - const { - scale, - handleHeight, - bracketSize, - bracketNotch, - bracketPadding, - - // Implement specifics. - handleWidth, - width, - length, - type, - stiffness, - color, - } = host; - - const handleColors = host.handleColors.split(','); - - let x; - let y; - - const handle = { - left: 40, - top: 30, - width: handleWidth * scale, - height: handleHeight, - }; - - const brush = { - width: width * scale, - height: length * scale, - }; - - brush.top = handle.top + handle.height; - brush.left = (handle.left + (handle.width / 2)) - brush.width / 2; - - // Draw Handle: | - if (handleColors.length > 0) { - const height = handle.height / handleColors.length; - handleColors.forEach((cColor, index) => { - ctx.fillStyle = cColor; - ctx.fillRect(handle.left, handle.top + height * index, handle.width, height); - }); - } else { - ctx.fillStyle = color; - ctx.fillRect(handle.left, handle.top, handle.width, handle.height); - } - - // Round and specular highlight. - const penColor = ctx.createLinearGradient(handle.left, 0, handle.left + handle.width, 0); - penColor.addColorStop(0, 'rgba(0, 0, 0, 0.7)'); - penColor.addColorStop(0.6, 'rgba(0, 0, 0, 0.3)'); - penColor.addColorStop(0.7, 'rgba(255, 255, 255, 1)'); - penColor.addColorStop(0.8, 'rgba(0, 0, 0, 0.1)'); - penColor.addColorStop(1, 'rgba(0, 0, 0, 0)'); - ctx.fillStyle = penColor; - ctx.fillRect(handle.left, handle.top, handle.width, handle.height); - - // Pen or brush. - const center = brush.left + brush.width / 2; - const largerWidth = brush.width > handle.width ? brush.width / 2 : handle.width / 2; - const minLength = type === 'pen' ? 5 : 2; - const drawLength = length < minLength ? minLength * scale : length * scale; - if (type === 'pen') { - // Draw a trapezoidal representation of the pen. - y = handle.top + handle.height; - ctx.beginPath(); - ctx.moveTo(handle.left, y); - ctx.lineTo(handle.left + handle.width, y); - ctx.lineTo(center + ((width * scale) / 2), y + drawLength); - ctx.lineTo(center - ((width * scale) / 2), y + drawLength); - ctx.fillStyle = color; - ctx.fill(); - } else { - // Draw Brush: / - // - We know where it starts (center bottom of handle) - // - It should end the curve in a ratio from handle left less the brush width - const curve = 1 - stiffness; - const bendMax = 20; - brush.height = drawLength - (curve * bendMax); - //brush.height -= curve * bendMax; - const maxDeflection = center - (handle.left - (width * scale)); - const deflectionLength = maxDeflection * curve; - const curveEnd = center - deflectionLength; - - ctx.beginPath(); - ctx.moveTo(center, brush.top); - ctx.bezierCurveTo( - center, brush.top + (maxDeflection - deflectionLength), // First Control (Start) - curveEnd + deflectionLength, brush.top + brush.height - (stiffness * bendMax), // Second Control (End) - curveEnd, brush.top + brush.height // End Point. - ); - ctx.lineWidth = width * scale; - - const brushColor = ctx.createLinearGradient(center, brush.top, curveEnd, brush.top + brush.height); - brushColor.addColorStop('0', 'gray'); - brushColor.addColorStop('1', color); - ctx.strokeStyle = brushColor; - ctx.stroke(); - } - - // Draw Measures (1px black) - ctx.fillStyle = 'black'; - ctx.lineWidth = 1; - ctx.strokeStyle = 'black'; - - // Only draw left and right measures with brush/other. - if (type !== 'pen') { - // Draw left measure: [ - x = center - largerWidth - bracketPadding; - ctx.beginPath(); - ctx.moveTo(x, brush.top); - ctx.lineTo(x - bracketSize, brush.top); - ctx.lineTo(x - bracketSize, brush.top + brush.height); - ctx.lineTo(x, brush.top + brush.height); - - // Notch. - ctx.moveTo(x - bracketSize, brush.top + (brush.height / 2)); - ctx.lineTo(x - bracketSize - bracketNotch, brush.top + (brush.height / 2)); - ctx.textAlign = 'right'; - ctx.fillText( - `${Math.round(stiffness * 100)}%`, - x - bracketSize - bracketNotch - 2, brush.top + (brush.height / 2) + 3.5 - ); - ctx.stroke(); - - // Draw right measure: ] - x = center + largerWidth + bracketPadding; - ctx.beginPath(); - ctx.moveTo(x, brush.top); - ctx.lineTo(x + bracketSize, brush.top); - ctx.lineTo(x + bracketSize, brush.top + brush.height); - ctx.lineTo(x, brush.top + brush.height); - - // Notch. - ctx.moveTo(x + bracketSize, brush.top + (brush.height / 2)); - ctx.lineTo(x + bracketSize + bracketNotch, brush.top + (brush.height / 2)); - ctx.textAlign = 'left'; - ctx.fillText( - `${length}mm`, - x + bracketSize + bracketNotch + 2, brush.top + (brush.height / 2) + 3.5 - ); - - ctx.stroke(); - } - - // Draw top measure bracket: - y = handle.top - bracketPadding; - ctx.beginPath(); - ctx.moveTo(handle.left, y); - ctx.lineTo(handle.left, y - bracketSize); - ctx.lineTo(handle.left + handle.width, y - bracketSize); - ctx.lineTo(handle.left + handle.width, y); - - // Notch. - ctx.moveTo(handle.left + (handle.width / 2), y - bracketSize); - ctx.lineTo(handle.left + (handle.width / 2), y - bracketSize - bracketNotch); - ctx.textAlign = 'center'; - ctx.fillText( - `${handleWidth}mm`, - handle.left + (handle.width / 2), y - bracketSize - bracketNotch - 3.5 - ); - ctx.stroke(); - - // Draw bottom measure bracket: - y = brush.top + drawLength + bracketPadding; - ctx.beginPath(); - ctx.moveTo(brush.left, y); - ctx.lineTo(brush.left, y + bracketSize); - ctx.lineTo(brush.left + brush.width, y + bracketSize); - ctx.lineTo(brush.left + brush.width, y); - - // Notch. - ctx.moveTo(brush.left + (brush.width / 2), y + bracketSize); - ctx.lineTo(brush.left + (brush.width / 2), y + bracketSize + bracketNotch); - ctx.textAlign = 'center'; - ctx.fillText( - `${width}mm`, - brush.left + (brush.width / 2), y + bracketSize + bracketNotch + 9 - ); - ctx.stroke(); -} - -function valueObserveFactory(defValue) { - return { - set: (host, value) => { - // console.log('Value set...', value); - renderImplement(host); - return value; - }, - connect: (host, key) => { - if (host[key] === undefined) { - host[key] = defValue; - } - }, - }; -} - -export default styles => ({ +export default () => ({ // UI configuration specifics. scale: 4.5, handleHeight: 40, @@ -241,7 +37,7 @@ export default styles => ({ color, }) => { const handle = { - left: 40, + left: 50, top: 30, width: handleWidth * scale, height: handleHeight, @@ -250,6 +46,8 @@ export default styles => ({ const brush = { width: width * scale, height: length * scale, + top: handle.top + handle.height, + left: handle.left + handle.width / 2 - (width * scale) / 2, }; const center = brush.left + brush.width / 2; @@ -257,16 +55,54 @@ export default styles => ({ const minLength = type === 'pen' ? 5 : 2; const drawLength = length < minLength ? minLength * scale : length * scale; + + const curve = 1 - stiffness; + const bendMax = 20; + const maxDeflection = handle.left - (width * scale); + const deflectionLength = maxDeflection * curve; + const curveEnd = center - deflectionLength; + + let brushShape = ''; + let y; + let x; + + if (type === 'pen') { + // Draw a trapezoidal representation of the pen. + brushShape = ` + M0,0 + l${handle.width},0 + l${-handle.width / 2 + (width * scale) / 2},${drawLength} + l${-width * scale},0 + `; + } else { + brush.height = drawLength - (curve * bendMax); + + // Draw a brush shape. + brushShape = ` + M${handle.width / 2},0 + c + 0, ${maxDeflection - deflectionLength} + ${deflectionLength}, ${brush.height - (stiffness * bendMax)} + ${-handle.width / 2 + deflectionLength}, ${brush.height} + `; + } + return html` - ${styles} - - ${svg` + + + ${svg` @@ -277,8 +113,15 @@ export default styles => ({ - - ${handleWidth}mm + + + ${handleWidth}mm + - - + + + ${type !== 'pen' && svg` - + + + ${Math.round(stiffness * 100)}% + - + + + ${length}mm + - - `} - + `} + + + ${width}mm + - `} - `; + `} + + `; }, }); From 6594fa72f8bf49641ede4f24efa642d95ac9c562 Mon Sep 17 00:00:00 2001 From: techninja Date: Sun, 2 Aug 2020 09:46:55 -0700 Subject: [PATCH 029/147] Complete initial work for implement display component. --- .../elements/tool-implement-copy.mjs | 235 ------------------ .../components/elements/tool-implement.mjs | 147 ++++++----- 2 files changed, 88 insertions(+), 294 deletions(-) delete mode 100644 src/interface/modules/components/elements/tool-implement-copy.mjs diff --git a/src/interface/modules/components/elements/tool-implement-copy.mjs b/src/interface/modules/components/elements/tool-implement-copy.mjs deleted file mode 100644 index 8b5624e..0000000 --- a/src/interface/modules/components/elements/tool-implement-copy.mjs +++ /dev/null @@ -1,235 +0,0 @@ -/** - * @file Implement display element definition. - */ -import { html } from '/modules/hybrids.js'; - -function renderImplement(host) { - if (!host || !host.shadowRoot) return; - const canvas = host.shadowRoot.querySelector('canvas'); - const ctx = canvas.getContext('2d'); - ctx.clearRect(0, 0, canvas.width, canvas.height); - - const { - scale, - handleHeight, - bracketSize, - bracketNotch, - bracketPadding, - - // Implement specifics. - handleWidth, - width, - length, - type, - stiffness, - color, - } = host; - - const handleColors = host.handleColors.split(','); - - let x; - let y; - - const handle = { - left: 40, - top: 30, - width: handleWidth * scale, - height: handleHeight, - }; - - const brush = { - width: width * scale, - height: length * scale, - }; - - brush.top = handle.top + handle.height; - brush.left = (handle.left + (handle.width / 2)) - brush.width / 2; - - // Draw Handle: | - if (handleColors.length > 0) { - const height = handle.height / handleColors.length; - handleColors.forEach((cColor, index) => { - ctx.fillStyle = cColor; - ctx.fillRect(handle.left, handle.top + height * index, handle.width, height); - }); - } else { - ctx.fillStyle = color; - ctx.fillRect(handle.left, handle.top, handle.width, handle.height); - } - - // Round and specular highlight. - const penColor = ctx.createLinearGradient(handle.left, 0, handle.left + handle.width, 0); - penColor.addColorStop(0, 'rgba(0, 0, 0, 0.7)'); - penColor.addColorStop(0.6, 'rgba(0, 0, 0, 0.3)'); - penColor.addColorStop(0.7, 'rgba(255, 255, 255, 1)'); - penColor.addColorStop(0.8, 'rgba(0, 0, 0, 0.1)'); - penColor.addColorStop(1, 'rgba(0, 0, 0, 0)'); - ctx.fillStyle = penColor; - ctx.fillRect(handle.left, handle.top, handle.width, handle.height); - - // Pen or brush. - const center = brush.left + brush.width / 2; - const largerWidth = brush.width > handle.width ? brush.width / 2 : handle.width / 2; - const minLength = type === 'pen' ? 5 : 2; - const drawLength = length < minLength ? minLength * scale : length * scale; - if (type === 'pen') { - // Draw a trapezoidal representation of the pen. - y = handle.top + handle.height; - ctx.beginPath(); - ctx.moveTo(handle.left, y); - ctx.lineTo(handle.left + handle.width, y); - ctx.lineTo(center + ((width * scale) / 2), y + drawLength); - ctx.lineTo(center - ((width * scale) / 2), y + drawLength); - ctx.fillStyle = color; - ctx.fill(); - } else { - // Draw Brush: / - // - We know where it starts (center bottom of handle) - // - It should end the curve in a ratio from handle left less the brush width - const curve = 1 - stiffness; - const bendMax = 20; - brush.height = drawLength - (curve * bendMax); - //brush.height -= curve * bendMax; - const maxDeflection = center - (handle.left - (width * scale)); - const deflectionLength = maxDeflection * curve; - const curveEnd = center - deflectionLength; - - ctx.beginPath(); - ctx.moveTo(center, brush.top); - ctx.bezierCurveTo( - center, brush.top + (maxDeflection - deflectionLength), // First Control (Start) - curveEnd + deflectionLength, brush.top + brush.height - (stiffness * bendMax), // Second Control (End) - curveEnd, brush.top + brush.height // End Point. - ); - ctx.lineWidth = width * scale; - - const brushColor = ctx.createLinearGradient(center, brush.top, curveEnd, brush.top + brush.height); - brushColor.addColorStop('0', 'gray'); - brushColor.addColorStop('1', color); - ctx.strokeStyle = brushColor; - ctx.stroke(); - } - - // Draw Measures (1px black) - ctx.fillStyle = 'black'; - ctx.lineWidth = 1; - ctx.strokeStyle = 'black'; - - // Only draw left and right measures with brush/other. - if (type !== 'pen') { - // Draw left measure: [ - x = center - largerWidth - bracketPadding; - ctx.beginPath(); - ctx.moveTo(x, brush.top); - ctx.lineTo(x - bracketSize, brush.top); - ctx.lineTo(x - bracketSize, brush.top + brush.height); - ctx.lineTo(x, brush.top + brush.height); - - // Notch. - ctx.moveTo(x - bracketSize, brush.top + (brush.height / 2)); - ctx.lineTo(x - bracketSize - bracketNotch, brush.top + (brush.height / 2)); - ctx.textAlign = 'right'; - ctx.fillText( - `${Math.round(stiffness * 100)}%`, - x - bracketSize - bracketNotch - 2, brush.top + (brush.height / 2) + 3.5 - ); - ctx.stroke(); - - // Draw right measure: ] - x = center + largerWidth + bracketPadding; - ctx.beginPath(); - ctx.moveTo(x, brush.top); - ctx.lineTo(x + bracketSize, brush.top); - ctx.lineTo(x + bracketSize, brush.top + brush.height); - ctx.lineTo(x, brush.top + brush.height); - - // Notch. - ctx.moveTo(x + bracketSize, brush.top + (brush.height / 2)); - ctx.lineTo(x + bracketSize + bracketNotch, brush.top + (brush.height / 2)); - ctx.textAlign = 'left'; - ctx.fillText( - `${length}mm`, - x + bracketSize + bracketNotch + 2, brush.top + (brush.height / 2) + 3.5 - ); - - ctx.stroke(); - } - - // Draw top measure bracket: - y = handle.top - bracketPadding; - ctx.beginPath(); - ctx.moveTo(handle.left, y); - ctx.lineTo(handle.left, y - bracketSize); - ctx.lineTo(handle.left + handle.width, y - bracketSize); - ctx.lineTo(handle.left + handle.width, y); - - // Notch. - ctx.moveTo(handle.left + (handle.width / 2), y - bracketSize); - ctx.lineTo(handle.left + (handle.width / 2), y - bracketSize - bracketNotch); - ctx.textAlign = 'center'; - ctx.fillText( - `${handleWidth}mm`, - handle.left + (handle.width / 2), y - bracketSize - bracketNotch - 3.5 - ); - ctx.stroke(); - - // Draw bottom measure bracket: - y = brush.top + drawLength + bracketPadding; - ctx.beginPath(); - ctx.moveTo(brush.left, y); - ctx.lineTo(brush.left, y + bracketSize); - ctx.lineTo(brush.left + brush.width, y + bracketSize); - ctx.lineTo(brush.left + brush.width, y); - - // Notch. - ctx.moveTo(brush.left + (brush.width / 2), y + bracketSize); - ctx.lineTo(brush.left + (brush.width / 2), y + bracketSize + bracketNotch); - ctx.textAlign = 'center'; - ctx.fillText( - `${width}mm`, - brush.left + (brush.width / 2), y + bracketSize + bracketNotch + 9 - ); - ctx.stroke(); -} - -function valueObserveFactory(defValue) { - return { - set: (host, value) => { - // console.log('Value set...', value); - renderImplement(host); - return value; - }, - connect: (host, key) => { - if (host[key] === undefined) { - host[key] = defValue; - } - }, - }; -} - -export default styles => ({ - // UI configuration specifics. - scale: 4.5, - handleHeight: 40, - bracketSize: 8, - bracketNotch: 5, - bracketPadding: 5, - - // Implement specifics. - handleColors: 'yellow', - handleWidth: 10, - width: 1, - length: 0, - type: 'pen', - stiffness: 1, - color: '#000000', - - render: host => html` - ${styles} - - - - - ${renderImplement} - `, -}); diff --git a/src/interface/modules/components/elements/tool-implement.mjs b/src/interface/modules/components/elements/tool-implement.mjs index bbd96e3..a3cf077 100644 --- a/src/interface/modules/components/elements/tool-implement.mjs +++ b/src/interface/modules/components/elements/tool-implement.mjs @@ -54,17 +54,12 @@ export default () => ({ const largerWidth = brush.width > handle.width ? brush.width / 2 : handle.width / 2; const minLength = type === 'pen' ? 5 : 2; const drawLength = length < minLength ? minLength * scale : length * scale; - + handleColors = handleColors.split(','); const curve = 1 - stiffness; - const bendMax = 20; - const maxDeflection = handle.left - (width * scale); - const deflectionLength = maxDeflection * curve; - const curveEnd = center - deflectionLength; + const bendMax = 15; let brushShape = ''; - let y; - let x; if (type === 'pen') { // Draw a trapezoidal representation of the pen. @@ -77,16 +72,80 @@ export default () => ({ } else { brush.height = drawLength - (curve * bendMax); + // Position of start control point. + const startBend = { + // No x offset ensures straight down bristles. + x: 0, + + // Pushes bristles down by this amount. + y: ((length * scale) / 2) * curve, + }; + + // Relative position of tip from top/center. + const tip = { + x: -(handle.width / 2) * curve, + y: brush.height, + }; + + // Position of end control point that controls bezier bend. + const bend = { + // Position of tip, bent to the right depending on stiffness. + x: tip.x + ((length * scale) / 2) * curve, + + // Position of tip, less proportional to the curve to angle properly. + y: tip.y - (((length * scale) / 4) * stiffness), + }; + // Draw a brush shape. brushShape = ` M${handle.width / 2},0 c - 0, ${maxDeflection - deflectionLength} - ${deflectionLength}, ${brush.height - (stiffness * bendMax)} - ${-handle.width / 2 + deflectionLength}, ${brush.height} + ${startBend.x}, ${startBend.y} + ${bend.x}, ${bend.y} + ${tip.x}, ${tip.y} `; } + // Setup the bracket attribute values. + const brackets = { + top: { + transform: `translate(${handle.left}, ${handle.top - bracketPadding})`, + d: `M0,0 + l0,${-bracketSize} + l${handle.width},0 + l0,${bracketSize} + m${-handle.width / 2},${-bracketSize} + l0,${-bracketNotch}`, + }, + left: { + transform: `translate(${center - largerWidth - bracketPadding}, ${brush.top})`, + d: `M0,0 + l${-bracketSize},0 + l0,${brush.height} + l${bracketSize},0 + m${-bracketSize},${-brush.height / 2} + l${-bracketNotch},0`, + }, + right: { + transform: `translate(${center + largerWidth + bracketPadding}, ${brush.top})`, + d: `M0,0 + l${bracketSize},0 + l0,${brush.height} + l${-bracketSize},0 + m${bracketSize},${-brush.height / 2} + l${bracketNotch},0`, + }, + bottom: { + transform: `translate(${brush.left}, ${brush.top + drawLength + bracketPadding})`, + d: `M0,0 + l0,${bracketSize} + l${brush.width},0 + l0,${-bracketSize} + m${-brush.width / 2},${bracketSize} + l0,${bracketNotch}`, + }, + }; + return html` @@ -112,31 +171,29 @@ export default () => ({ + + + + - + ${handleWidth}mm - + + ${handleColors.map((handleColor, index) => svg` + `)} ({ transform="translate(${handle.left}, ${brush.top})" /> ${type !== 'pen' && svg` - + ${Math.round(stiffness * 100)}% - + - + ${length}mm - + `} - + ${width}mm - + `} From 1d7c0b807f92903c9435cfde3820b36d3292a8eb Mon Sep 17 00:00:00 2001 From: techninja Date: Sun, 2 Aug 2020 10:04:00 -0700 Subject: [PATCH 030/147] Fix for SVG rendering bug that hides gradient? Weird. --- src/interface/modules/components/elements/tool-implement.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interface/modules/components/elements/tool-implement.mjs b/src/interface/modules/components/elements/tool-implement.mjs index a3cf077..3c50d82 100644 --- a/src/interface/modules/components/elements/tool-implement.mjs +++ b/src/interface/modules/components/elements/tool-implement.mjs @@ -83,7 +83,7 @@ export default () => ({ // Relative position of tip from top/center. const tip = { - x: -(handle.width / 2) * curve, + x: -(handle.width / 2) * curve - 0.1, y: brush.height, }; From 5ba5f4e5041f39228099c3c1299570797ceb7ccc Mon Sep 17 00:00:00 2001 From: techninja Date: Sun, 2 Aug 2020 10:44:16 -0700 Subject: [PATCH 031/147] Remove various testing cruft --- src/interface/index.html | 35 ------------------------------ src/interface/styles/interface.css | 8 ------- 2 files changed, 43 deletions(-) diff --git a/src/interface/index.html b/src/interface/index.html index bf78691..5ee74b4 100644 --- a/src/interface/index.html +++ b/src/interface/index.html @@ -40,41 +40,6 @@ - - - - diff --git a/src/interface/styles/interface.css b/src/interface/styles/interface.css index 8b9d629..dba6b8f 100644 --- a/src/interface/styles/interface.css +++ b/src/interface/styles/interface.css @@ -110,14 +110,6 @@ input.switch:checked:after { border-width: 2px; } -/* Colorset editor */ -slide-item { - overflow-x: hidden; -} -slide-item h1 { - font-size: 5em; -} - /* Project loader */ #projects .list { height: 230px; From c84bf1fb20551f2243669beda66c3c22c5732754 Mon Sep 17 00:00:00 2001 From: techninja Date: Sun, 2 Aug 2020 10:47:10 -0700 Subject: [PATCH 032/147] Add basic array editing to schema form --- .../modules/components/elements/schema-form.mjs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/interface/modules/components/elements/schema-form.mjs b/src/interface/modules/components/elements/schema-form.mjs index 06705f4..3ce1a8d 100644 --- a/src/interface/modules/components/elements/schema-form.mjs +++ b/src/interface/modules/components/elements/schema-form.mjs @@ -159,7 +159,9 @@ function dataChange(host) { // Are there any actual changes? if (Object.keys(diffObject).length) { // Dispatch change with changed values. - dispatch(host, 'change', { detail: { diffObject, data: host.editor.data.current } }); + dispatch(host, 'change', { + detail: { diffObject, data: host.editor.data.current } } + ); if (host.debug) console.log('Diff', diffObject); host.editor.data.last = { ...host.editor.data.current }; } else if (host.debug) { @@ -256,6 +258,12 @@ function schemaChangeFactory(defaultSchema = {}) { schema: value, }; + // Enable array editing. + if (host.arrays) { + editorSettings.disable_array_add = false; + editorSettings.disable_array_delete = false; + } + // Actually render the editor based on the schema. const form = host.shadowRoot.querySelector('form'); host.editor = new JSONEditor(form, editorSettings); @@ -352,6 +360,9 @@ export default styles => ({ // Whether setting data will append to existing data. False will apply to default data. appendData: false, + // Whether to enable JSONEditor array add/delete. + arrays: false, + // Comma separated list of schema paths to hide. hidePaths: '', @@ -375,7 +386,7 @@ export default styles => ({ ${styles} diff --git a/src/interface/modules/components/widgets/draw-settings.mjs b/src/interface/modules/components/widgets/draw-settings.mjs index ed27d3e..6f01902 100644 --- a/src/interface/modules/components/widgets/draw-settings.mjs +++ b/src/interface/modules/components/widgets/draw-settings.mjs @@ -194,10 +194,8 @@ export default styles => ({ api="content" json-path="$.properties.bounds" layout="grid" - extra-styles="form > div > div.card-body { - background-color: transparent !important; box-shadow: none; - }" onchange=${boundsChange} + plain > Date: Tue, 4 Aug 2020 15:51:51 -0700 Subject: [PATCH 037/147] Reformat eslint and upgrade to babel-lint --- .eslintrc.js | 41 +-- package.json | 7 +- yarn.lock | 709 ++++++++++++++++++++++++++++----------------------- 3 files changed, 425 insertions(+), 332 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 054ec9e..da095d3 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,18 +1,27 @@ module.exports = { - "extends": "airbnb-base", - "rules": { - "no-restricted-syntax": "off", - "no-underscore-dangle": "off", - "no-plusplus": "off", - "max-len": ["error", { "code": 90 }], - "comma-dangle": ["error", { - "exports": "never", - "functions": "never", - "arrays": "always-multiline", - "objects": "always-multiline" - }], - "class-methods-use-this": "off", - "no-console": "off", // TODO: Move to some other logging method. - "no-multi-str": "off", - } + parser: 'babel-eslint', + extends: 'airbnb-base', + rules: { + 'no-restricted-syntax': 'off', + 'no-underscore-dangle': 'off', + 'no-plusplus': 'off', + 'max-len': [ + 'error', + { + code: 90, + }, + ], + 'comma-dangle': [ + 'error', + { + exports: 'never', + functions: 'never', + arrays: 'always-multiline', + objects: 'always-multiline', + }, + ], + 'class-methods-use-this': 'off', + 'no-console': 'off', + 'no-multi-str': 'off', + }, }; diff --git a/package.json b/package.json index 3bc464f..5e0f88a 100644 --- a/package.json +++ b/package.json @@ -50,10 +50,11 @@ "zodiac-ts": "^1.0.3" }, "devDependencies": { + "babel-eslint": "^10.1.0", "chai": "*", - "eslint": "^6.0.1", - "eslint-config-airbnb-base": "^13.2.0", - "eslint-plugin-import": "^2.18.0", + "eslint": "^7.2.0", + "eslint-config-airbnb-base": "^14.2.0", + "eslint-plugin-import": "^2.22.0", "mocha": "*" }, "engines": { diff --git a/yarn.lock b/yarn.lock index 50da480..86dd64b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,11 +2,6 @@ # yarn lockfile v1 -"@a11y/focus-trap@~1.0.4": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@a11y/focus-trap/-/focus-trap-1.0.5.tgz#0748189c37ca21255c702aa5de3ee00ed4821d22" - integrity sha512-3JOd6g+BALysWS8LNf0qdB8ltR651H/RCLAvUmfS0LIHwHO579XfjZUIZbURYiAZrcbp1CBAq4QZ2YwKNQZ1hw== - "@babel/code-frame@^7.0.0": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.5.5.tgz#bc0782f6d69f7b7d49531219699b988f669a8f9d" @@ -14,6 +9,13 @@ dependencies: "@babel/highlight" "^7.0.0" +"@babel/code-frame@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a" + integrity sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg== + dependencies: + "@babel/highlight" "^7.10.4" + "@babel/code-frame@^7.5.5", "@babel/code-frame@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e" @@ -51,6 +53,15 @@ semver "^5.4.1" source-map "^0.5.0" +"@babel/generator@^7.11.0": + version "7.11.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.11.0.tgz#4b90c78d8c12825024568cbe83ee6c9af193585c" + integrity sha512-fEm3Uzw7Mc9Xi//qU20cBKatTfs2aOtKqmvy/Vm7RkJEGFQ4xc9myCfbXxqK//ZS8MR/ciOHw6meGASJuKmDfQ== + dependencies: + "@babel/types" "^7.11.0" + jsesc "^2.5.1" + source-map "^0.5.0" + "@babel/generator@^7.8.4": version "7.8.4" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.8.4.tgz#35bbc74486956fe4251829f9f6c48330e8d0985e" @@ -121,6 +132,15 @@ "@babel/traverse" "^7.8.3" "@babel/types" "^7.8.3" +"@babel/helper-function-name@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz#d2d3b20c59ad8c47112fa7d2a94bc09d5ef82f1a" + integrity sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ== + dependencies: + "@babel/helper-get-function-arity" "^7.10.4" + "@babel/template" "^7.10.4" + "@babel/types" "^7.10.4" + "@babel/helper-function-name@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz#eeeb665a01b1f11068e9fb86ad56a1cb1a824cca" @@ -130,6 +150,13 @@ "@babel/template" "^7.8.3" "@babel/types" "^7.8.3" +"@babel/helper-get-function-arity@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz#98c1cbea0e2332f33f9a4661b8ce1505b2c19ba2" + integrity sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A== + dependencies: + "@babel/types" "^7.10.4" + "@babel/helper-get-function-arity@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz#b894b947bd004381ce63ea1db9f08547e920abd5" @@ -218,6 +245,13 @@ "@babel/template" "^7.8.3" "@babel/types" "^7.8.3" +"@babel/helper-split-export-declaration@^7.11.0": + version "7.11.0" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz#f8a491244acf6a676158ac42072911ba83ad099f" + integrity sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg== + dependencies: + "@babel/types" "^7.11.0" + "@babel/helper-split-export-declaration@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz#31a9f30070f91368a7182cf05f831781065fc7a9" @@ -225,6 +259,11 @@ dependencies: "@babel/types" "^7.8.3" +"@babel/helper-validator-identifier@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz#a78c7a7251e01f616512d31b10adcf52ada5e0d2" + integrity sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw== + "@babel/helper-wrap-function@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.8.3.tgz#9dbdb2bb55ef14aaa01fe8c99b629bd5352d8610" @@ -253,6 +292,15 @@ esutils "^2.0.2" js-tokens "^4.0.0" +"@babel/highlight@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.4.tgz#7d1bdfd65753538fabe6c38596cdb76d9ac60143" + integrity sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA== + dependencies: + "@babel/helper-validator-identifier" "^7.10.4" + chalk "^2.0.0" + js-tokens "^4.0.0" + "@babel/highlight@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.8.3.tgz#28f173d04223eaaa59bc1d439a3836e6d1265797" @@ -262,6 +310,11 @@ esutils "^2.0.2" js-tokens "^4.0.0" +"@babel/parser@^7.10.4", "@babel/parser@^7.11.0", "@babel/parser@^7.7.0": + version "7.11.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.11.0.tgz#a9d7e11aead25d3b422d17b2c6502c8dddef6a5d" + integrity sha512-qvRvi4oI8xii8NllyEc4MDJjuZiNaRzyb7Y7lup1NqJV8TZHF4O27CcP+72WPn/k1zkgJ6WJfnIbk4jTsVAZHw== + "@babel/parser@^7.6.4", "@babel/parser@^7.8.3", "@babel/parser@^7.8.4": version "7.8.4" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.8.4.tgz#d1dbe64691d60358a974295fa53da074dd2ce8e8" @@ -710,6 +763,15 @@ dependencies: regenerator-runtime "^0.13.2" +"@babel/template@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278" + integrity sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/parser" "^7.10.4" + "@babel/types" "^7.10.4" + "@babel/template@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.3.tgz#e02ad04fe262a657809327f578056ca15fd4d1b8" @@ -734,6 +796,30 @@ globals "^11.1.0" lodash "^4.17.13" +"@babel/traverse@^7.7.0": + version "7.11.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.11.0.tgz#9b996ce1b98f53f7c3e4175115605d56ed07dd24" + integrity sha512-ZB2V+LskoWKNpMq6E5UUCrjtDUh5IOTAyIl0dTjIEoXum/iKWkoIEKIRDnUucO6f+2FzNkE0oD4RLKoPIufDtg== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/generator" "^7.11.0" + "@babel/helper-function-name" "^7.10.4" + "@babel/helper-split-export-declaration" "^7.11.0" + "@babel/parser" "^7.11.0" + "@babel/types" "^7.11.0" + debug "^4.1.0" + globals "^11.1.0" + lodash "^4.17.19" + +"@babel/types@^7.10.4", "@babel/types@^7.11.0", "@babel/types@^7.7.0": + version "7.11.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.11.0.tgz#2ae6bf1ba9ae8c3c43824e5861269871b206e90d" + integrity sha512-O53yME4ZZI0jO1EVGtF1ePGl0LHirG4P1ibcD80XyzZcKhcMFeCXmh4Xb1ifGBIV233Qg12x4rBfQgA+tmOukA== + dependencies: + "@babel/helper-validator-identifier" "^7.10.4" + lodash "^4.17.19" + to-fast-properties "^2.0.0" + "@babel/types@^7.6.3", "@babel/types@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.8.3.tgz#5a383dffa5416db1b73dedffd311ffd0788fb31c" @@ -879,6 +965,11 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= + "@types/node@*": version "13.7.4" resolved "https://registry.yarnpkg.com/@types/node/-/node-13.7.4.tgz#76c3cb3a12909510f52e5dc04a6298cdf9504ffd" @@ -922,10 +1013,10 @@ acorn-globals@^4.3.2: acorn "^6.0.1" acorn-walk "^6.0.1" -acorn-jsx@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.1.0.tgz#294adb71b57398b0680015f0a38c563ee1db5384" - integrity sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw== +acorn-jsx@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.2.0.tgz#4c66069173d6fdd68ed85239fc256226182b2ebe" + integrity sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ== acorn-walk@^6.0.1: version "6.2.0" @@ -942,6 +1033,11 @@ acorn@^7.1.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.0.tgz#949d36f2c292535da602283586c2477c57eb2d6c" integrity sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ== +acorn@^7.3.1: + version "7.4.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.0.tgz#e1ad486e6c54501634c6c397c5c121daa383607c" + integrity sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w== + after@0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" @@ -980,12 +1076,10 @@ ansi-colors@3.2.3: resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.3.tgz#57d35b8686e851e2cc04c403f1c00203976a1813" integrity sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw== -ansi-escapes@^4.2.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.0.tgz#a4ce2b33d6b214b7950d8595c212f12ac9cc569d" - integrity sha512-EiYhwo0v255HUL6eDyuLrXEkTi7WwVCLAw+SeOQ7M7qdun1z1pum4DEm/nuqIVbPvi9RPPc9k9LbyBv6H0DwVg== - dependencies: - type-fest "^0.8.1" +ansi-colors@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" + integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== ansi-regex@^2.0.0: version "2.1.1" @@ -1052,13 +1146,22 @@ array-equal@^1.0.0: resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93" integrity sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM= -array-includes@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.0.3.tgz#184b48f62d92d7452bb31b323165c7f8bd02266d" - integrity sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0= +array-includes@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.1.tgz#cdd67e6852bdf9c1215460786732255ed2459348" + integrity sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ== dependencies: - define-properties "^1.1.2" - es-abstract "^1.7.0" + define-properties "^1.1.3" + es-abstract "^1.17.0" + is-string "^1.0.5" + +array.prototype.flat@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz#0de82b426b0318dbfdb940089e38b043d37f6c7b" + integrity sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" arraybuffer.slice@~0.0.7: version "0.0.7" @@ -1113,6 +1216,18 @@ axios@^0.19.0: follow-redirects "1.5.10" is-buffer "^2.0.2" +babel-eslint@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.1.0.tgz#6968e568a910b78fb3779cdd8b6ac2f479943232" + integrity sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/parser" "^7.7.0" + "@babel/traverse" "^7.7.0" + "@babel/types" "^7.7.0" + eslint-visitor-keys "^1.0.0" + resolve "^1.12.0" + babel-plugin-dynamic-import-node@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz#f00f507bdaa3c3e3ff6e7e5e98d90a7acab96f7f" @@ -1385,7 +1500,7 @@ chai@*: pathval "^1.1.0" type-detect "^4.0.5" -chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.2: +chalk@^2.0.0, chalk@^2.0.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -1402,10 +1517,13 @@ chalk@^3.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chardet@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" - integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== +chalk@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" + integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" check-error@^1.0.2: version "1.0.2" @@ -1452,23 +1570,11 @@ cli-cursor@^2.1.0: dependencies: restore-cursor "^2.0.0" -cli-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" - integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== - dependencies: - restore-cursor "^3.1.0" - cli-spinners@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.2.0.tgz#e8b988d9206c692302d8ee834e7a85c0144d8f77" integrity sha512-tgU3fKwzYjiLEQgPMD9Jt+JjHVL9kW93FiIMX/l7rivvOD4/LL0Mf7gda3+4U2KJBloybwgj5KEoQgGRioMiKQ== -cli-width@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" - integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk= - cliui@^3.0.3: version "3.2.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" @@ -1578,9 +1684,10 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -confusing-browser-globals@^1.0.5: - version "1.0.7" - resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.7.tgz#5ae852bd541a910e7ffb2dbb864a2d21a36ad29b" +confusing-browser-globals@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.9.tgz#72bc13b483c0276801681871d4898516f8f54fdd" + integrity sha512-KbS1Y0jMtyPgIxjO7ZzMAuUpAKMt1SzCL9fsrKsX6b0zJPTaT0SiSPmewwVZg9UAO83HVIlEhZF84LIjZ0lmAw== connect-slashes@techninja/connect-slashes#ignore-files: version "1.3.1" @@ -1665,16 +1772,14 @@ cosmiconfig@^6.0.0: path-type "^4.0.0" yaml "^1.7.2" -cross-spawn@^6.0.5: - version "6.0.5" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" - integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== +cross-spawn@^7.0.2: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== dependencies: - nice-try "^1.0.4" - path-key "^2.0.1" - semver "^5.5.0" - shebang-command "^1.2.0" - which "^1.2.9" + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" css-select@~1.0.0: version "1.0.0" @@ -1746,7 +1851,7 @@ debug@*, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@~4.1.0: dependencies: ms "^2.1.1" -debug@2.6.9, debug@^2.6.8, debug@^2.6.9: +debug@2.6.9, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" dependencies: @@ -1794,7 +1899,7 @@ deep-extend@^0.6.0: resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== -deep-is@~0.1.3: +deep-is@^0.1.3, deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= @@ -1957,11 +2062,6 @@ emoji-regex@^7.0.1: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - end-of-stream@^1.0.0: version "1.4.1" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" @@ -2016,6 +2116,13 @@ engine.io@~3.4.0: engine.io-parser "~2.2.0" ws "^7.1.2" +enquirer@^2.3.5: + version "2.3.6" + resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" + integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== + dependencies: + ansi-colors "^4.1.1" + entities@1.0: version "1.0.0" resolved "https://registry.yarnpkg.com/entities/-/entities-1.0.0.tgz#b2987aa3821347fcde642b24fdfc9e4fb712bf26" @@ -2038,18 +2145,24 @@ error-ex@^1.2.0, error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.12.0: - version "1.13.0" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9" +es-abstract@^1.17.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.5: + version "1.17.6" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.6.tgz#9142071707857b2cacc7b89ecb670316c3e2d52a" + integrity sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw== dependencies: - es-to-primitive "^1.2.0" + es-to-primitive "^1.2.1" function-bind "^1.1.1" has "^1.0.3" - is-callable "^1.1.4" - is-regex "^1.0.4" - object-keys "^1.0.12" + has-symbols "^1.0.1" + is-callable "^1.2.0" + is-regex "^1.1.0" + object-inspect "^1.7.0" + object-keys "^1.1.1" + object.assign "^4.1.0" + string.prototype.trimend "^1.0.1" + string.prototype.trimstart "^1.0.1" -es-abstract@^1.5.1, es-abstract@^1.7.0: +es-abstract@^1.5.1: version "1.16.2" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.16.2.tgz#4e874331645e9925edef141e74fc4bd144669d34" integrity sha512-jYo/J8XU2emLXl3OLwfwtuFfuF2w6DYPs+xy9ZfVyPkDcrauu6LYrw/q2TyCtrbc/KUdCiC5e9UajRhgNkVopA== @@ -2065,7 +2178,7 @@ es-abstract@^1.5.1, es-abstract@^1.7.0: string.prototype.trimleft "^2.1.0" string.prototype.trimright "^2.1.0" -es-to-primitive@^1.2.0, es-to-primitive@^1.2.1: +es-to-primitive@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== @@ -2108,83 +2221,92 @@ escodegen@^1.8.1: optionalDependencies: source-map "~0.6.1" -eslint-config-airbnb-base@^13.2.0: - version "13.2.0" - resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-13.2.0.tgz#f6ea81459ff4dec2dda200c35f1d8f7419d57943" +eslint-config-airbnb-base@^14.2.0: + version "14.2.0" + resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.2.0.tgz#fe89c24b3f9dc8008c9c0d0d88c28f95ed65e9c4" + integrity sha512-Snswd5oC6nJaevs3nZoLSTvGJBvzTfnBqOIArkf3cbyTyq9UD79wOk8s+RiL6bhca0p/eRO6veczhf6A/7Jy8Q== dependencies: - confusing-browser-globals "^1.0.5" + confusing-browser-globals "^1.0.9" object.assign "^4.1.0" - object.entries "^1.1.0" + object.entries "^1.1.2" -eslint-import-resolver-node@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz#58f15fb839b8d0576ca980413476aab2472db66a" - integrity sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q== +eslint-import-resolver-node@^0.3.3: + version "0.3.4" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz#85ffa81942c25012d8231096ddf679c03042c717" + integrity sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA== dependencies: debug "^2.6.9" - resolve "^1.5.0" + resolve "^1.13.1" -eslint-module-utils@^2.4.0: - version "2.4.1" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.4.1.tgz#7b4675875bf96b0dbf1b21977456e5bb1f5e018c" - integrity sha512-H6DOj+ejw7Tesdgbfs4jeS4YMFrT8uI8xwd1gtQqXssaR0EQ26L+2O/w6wkYFy2MymON0fTwHmXBvvfLNZVZEw== +eslint-module-utils@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz#579ebd094f56af7797d19c9866c9c9486629bfa6" + integrity sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA== dependencies: - debug "^2.6.8" + debug "^2.6.9" pkg-dir "^2.0.0" -eslint-plugin-import@^2.18.0: - version "2.18.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.18.2.tgz#02f1180b90b077b33d447a17a2326ceb400aceb6" - integrity sha512-5ohpsHAiUBRNaBWAF08izwUGlbrJoJJ+W9/TBwsGoR1MnlgfwMIKrFeSjWbt6moabiXW9xNvtFz+97KHRfI4HQ== +eslint-plugin-import@^2.22.0: + version "2.22.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.22.0.tgz#92f7736fe1fde3e2de77623c838dd992ff5ffb7e" + integrity sha512-66Fpf1Ln6aIS5Gr/55ts19eUuoDhAbZgnr6UxK5hbDx6l/QgQgx61AePq+BV4PP2uXQFClgMVzep5zZ94qqsxg== dependencies: - array-includes "^3.0.3" + array-includes "^3.1.1" + array.prototype.flat "^1.2.3" contains-path "^0.1.0" debug "^2.6.9" doctrine "1.5.0" - eslint-import-resolver-node "^0.3.2" - eslint-module-utils "^2.4.0" + eslint-import-resolver-node "^0.3.3" + eslint-module-utils "^2.6.0" has "^1.0.3" minimatch "^3.0.4" - object.values "^1.1.0" + object.values "^1.1.1" read-pkg-up "^2.0.0" - resolve "^1.11.0" + resolve "^1.17.0" + tsconfig-paths "^3.9.0" -eslint-scope@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.0.0.tgz#e87c8887c73e8d1ec84f1ca591645c358bfc8fb9" - integrity sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw== +eslint-scope@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.0.tgz#d0f971dfe59c69e0cada684b23d49dbf82600ce5" + integrity sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w== dependencies: esrecurse "^4.1.0" estraverse "^4.1.1" -eslint-utils@^1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f" - integrity sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q== +eslint-utils@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" + integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== dependencies: eslint-visitor-keys "^1.1.0" +eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" + integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== + eslint-visitor-keys@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz#e2a82cea84ff246ad6fb57f9bde5b46621459ec2" integrity sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A== -eslint@^6.0.1: - version "6.7.1" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.7.1.tgz#269ccccec3ef60ab32358a44d147ac209154b919" - integrity sha512-UWzBS79pNcsDSxgxbdjkmzn/B6BhsXMfUaOHnNwyE8nD+Q6pyT96ow2MccVayUTV4yMid4qLhMiQaywctRkBLA== +eslint@^7.2.0: + version "7.6.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.6.0.tgz#522d67cfaea09724d96949c70e7a0550614d64d6" + integrity sha512-QlAManNtqr7sozWm5TF4wIH9gmUm2hE3vNRUvyoYAa4y1l5/jxD/PQStEjBMQtCqZmSep8UxrcecI60hOpe61w== dependencies: "@babel/code-frame" "^7.0.0" ajv "^6.10.0" - chalk "^2.1.0" - cross-spawn "^6.0.5" + chalk "^4.0.0" + cross-spawn "^7.0.2" debug "^4.0.1" doctrine "^3.0.0" - eslint-scope "^5.0.0" - eslint-utils "^1.4.3" - eslint-visitor-keys "^1.1.0" - espree "^6.1.2" - esquery "^1.0.1" + enquirer "^2.3.5" + eslint-scope "^5.1.0" + eslint-utils "^2.1.0" + eslint-visitor-keys "^1.3.0" + espree "^7.2.0" + esquery "^1.2.0" esutils "^2.0.2" file-entry-cache "^5.0.1" functional-red-black-tree "^1.0.1" @@ -2193,33 +2315,31 @@ eslint@^6.0.1: ignore "^4.0.6" import-fresh "^3.0.0" imurmurhash "^0.1.4" - inquirer "^7.0.0" is-glob "^4.0.0" js-yaml "^3.13.1" json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.3.0" - lodash "^4.17.14" + levn "^0.4.1" + lodash "^4.17.19" minimatch "^3.0.4" - mkdirp "^0.5.1" natural-compare "^1.4.0" - optionator "^0.8.3" + optionator "^0.9.1" progress "^2.0.0" - regexpp "^2.0.1" - semver "^6.1.2" - strip-ansi "^5.2.0" - strip-json-comments "^3.0.1" + regexpp "^3.1.0" + semver "^7.2.1" + strip-ansi "^6.0.0" + strip-json-comments "^3.1.0" table "^5.2.3" text-table "^0.2.0" v8-compile-cache "^2.0.3" -espree@^6.1.2: - version "6.1.2" - resolved "https://registry.yarnpkg.com/espree/-/espree-6.1.2.tgz#6c272650932b4f91c3714e5e7b5f5e2ecf47262d" - integrity sha512-2iUPuuPP+yW1PZaMSDM9eyVf8D5P0Hi8h83YtZ5bPc/zHYjII5khoixIUTMO794NOY8F/ThF1Bo8ncZILarUTA== +espree@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-7.2.0.tgz#1c263d5b513dbad0ac30c4991b93ac354e948d69" + integrity sha512-H+cQ3+3JYRMEIOl87e7QdHX70ocly5iW4+dttuR8iYSPr/hXKFb+7dBsZ7+u1adC4VrnPlTkv0+OwuPnDop19g== dependencies: - acorn "^7.1.0" - acorn-jsx "^5.1.0" - eslint-visitor-keys "^1.1.0" + acorn "^7.3.1" + acorn-jsx "^5.2.0" + eslint-visitor-keys "^1.3.0" esprima@1.2.2: version "1.2.2" @@ -2236,12 +2356,12 @@ esprima@^4.0.0, esprima@^4.0.1: resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esquery@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.1.tgz#406c51658b1f5991a5f9b62b1dc25b00e3e5c708" - integrity sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA== +esquery@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.3.1.tgz#b78b5828aa8e214e29fb74c4d5b752e1c033da57" + integrity sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ== dependencies: - estraverse "^4.0.0" + estraverse "^5.1.0" esrecurse@^4.1.0: version "4.2.1" @@ -2250,11 +2370,16 @@ esrecurse@^4.1.0: dependencies: estraverse "^4.1.0" -estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0: +estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0: version "4.3.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== +estraverse@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.1.0.tgz#374309d39fd935ae500e7b92e8a6b4c720e59642" + integrity sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw== + estree-walker@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.1.tgz#53049143f40c6eb918b23671d1fe3219f3a1b362" @@ -2304,15 +2429,6 @@ extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" -external-editor@^3.0.3: - version "3.1.0" - resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" - integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== - dependencies: - chardet "^0.7.0" - iconv-lite "^0.4.24" - tmp "^0.0.33" - extsprintf@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" @@ -2336,18 +2452,11 @@ fast-json-stable-stringify@^2.0.0: resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= -fast-levenshtein@~2.0.4, fast-levenshtein@~2.0.6: +fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.4: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= -figures@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-3.1.0.tgz#4b198dd07d8d71530642864af2d45dd9e459c4ec" - integrity sha512-ravh8VRXqHuMvZt/d8GblBeqDMkdJMBdv/2KntFH+ra5MXkO7nxNKpzQ3n6QD/2da1kH0aWmNISdvhM7gl2gVg== - dependencies: - escape-string-regexp "^1.0.5" - file-entry-cache@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" @@ -2722,7 +2831,7 @@ i18next@^19.0.1: dependencies: "@babel/runtime" "^7.3.1" -iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4: +iconv-lite@0.4.24, iconv-lite@^0.4.4: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" dependencies: @@ -2786,25 +2895,6 @@ ini@^1.3.0, ini@~1.3.0: version "1.3.5" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" -inquirer@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.0.0.tgz#9e2b032dde77da1db5db804758b8fea3a970519a" - integrity sha512-rSdC7zelHdRQFkWnhsMu2+2SO41mpv2oF2zy4tMhmiLWkcKbOAs87fWAJhVXttKVwhdZvymvnuM95EyEXg2/tQ== - dependencies: - ansi-escapes "^4.2.1" - chalk "^2.4.2" - cli-cursor "^3.1.0" - cli-width "^2.0.0" - external-editor "^3.0.3" - figures "^3.0.0" - lodash "^4.17.15" - mute-stream "0.0.8" - run-async "^2.2.0" - rxjs "^6.4.0" - string-width "^4.1.0" - strip-ansi "^5.1.0" - through "^2.3.6" - interval-tree-1d@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/interval-tree-1d/-/interval-tree-1d-1.0.3.tgz#8fdbde02b6b2c7dbdead636bcbed8e08710d85c1" @@ -2874,6 +2964,11 @@ is-callable@^1.1.4: resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75" integrity sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA== +is-callable@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.0.tgz#83336560b54a38e35e3a2df7afd0454d691468bb" + integrity sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw== + is-date-object@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" @@ -2901,11 +2996,6 @@ is-fullwidth-code-point@^2.0.0: resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - is-function@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-function/-/is-function-1.0.1.tgz#12cfb98b65b57dd3d193a3121f5f6e2f437602b5" @@ -2930,11 +3020,6 @@ is-plain-object@^2.0.1: dependencies: isobject "^3.0.1" -is-promise@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" - integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= - is-reference@^1.1.2: version "1.1.4" resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.1.4.tgz#3f95849886ddb70256a3e6d062b1a68c13c51427" @@ -2949,11 +3034,23 @@ is-regex@^1.0.4: dependencies: has "^1.0.1" +is-regex@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.1.tgz#c6f98aacc546f6cec5468a07b7b153ab564a57b9" + integrity sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg== + dependencies: + has-symbols "^1.0.1" + is-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== +is-string@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" + integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ== + is-symbol@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" @@ -3136,6 +3233,13 @@ json5@2.0.0: dependencies: minimist "^1.2.0" +json5@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" + integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== + dependencies: + minimist "^1.2.0" + json5@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.1.tgz#81b6cb04e9ba496f1c7005d07b4368a2638f90b6" @@ -3218,7 +3322,15 @@ levenary@^1.1.1: dependencies: leven "^3.1.0" -levn@^0.3.0, levn@~0.3.0: +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +levn@~0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= @@ -3231,18 +3343,6 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= -lit-element@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/lit-element/-/lit-element-2.2.1.tgz#79c94d8cfdc2d73b245656e37991bd1e4811d96f" - integrity sha512-ipDcgQ1EpW6Va2Z6dWm79jYdimVepO5GL0eYkZrFvdr0OD/1N260Q9DH+K5HXHFrRoC7dOg+ZpED2XE0TgGdXw== - dependencies: - lit-html "^1.0.0" - -lit-html@^1.0.0, lit-html@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/lit-html/-/lit-html-1.1.2.tgz#2e3560a7075210243649c888ad738eaf0daa8374" - integrity sha512-FFlUMKHKi+qG1x1iHNZ1hrtc/zHmfYTyrSvs3/wBTvaNtpZjOZGWzU7efGYVpgp6KvWeKF6ql9/KsCq6Z/mEDA== - load-bmfont@^1.2.3: version "1.4.0" resolved "https://registry.yarnpkg.com/load-bmfont/-/load-bmfont-1.4.0.tgz#75f17070b14a8c785fe7f5bee2e6fd4f98093b6b" @@ -3298,6 +3398,11 @@ lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== +lodash@^4.17.19: + version "4.17.19" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" + integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== + log-symbols@2.2.0, log-symbols@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" @@ -3370,11 +3475,6 @@ mimic-fn@^1.0.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - mimic-response@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" @@ -3487,11 +3587,6 @@ ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -mute-stream@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" - integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== - nan@^2.13.2, nan@^2.14.0: version "2.14.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" @@ -3557,11 +3652,6 @@ nextafter@^1.0.0: dependencies: double-bits "^1.1.0" -nice-try@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" - integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== - node-abi@^2.7.0: version "2.10.0" resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.10.0.tgz#894bc6625ee042627ed9b5e9270d80bb63ef5045" @@ -3709,13 +3799,13 @@ object.assign@4.1.0, object.assign@^4.1.0: has-symbols "^1.0.0" object-keys "^1.0.11" -object.entries@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.0.tgz#2024fc6d6ba246aee38bdb0ffd5cfbcf371b7519" +object.entries@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.2.tgz#bc73f00acb6b6bb16c203434b10f9a7e797d3add" + integrity sha512-BQdB9qKmb/HyNdMNWVr7O3+z5MUIx3aiegEIJqjMBbBf0YT9RRxTJSim4mzFqtyr7PDAHigq0N9dO0m0tRakQA== dependencies: define-properties "^1.1.3" - es-abstract "^1.12.0" - function-bind "^1.1.1" + es-abstract "^1.17.5" has "^1.0.3" object.getownpropertydescriptors@^2.0.3: @@ -3726,13 +3816,13 @@ object.getownpropertydescriptors@^2.0.3: define-properties "^1.1.2" es-abstract "^1.5.1" -object.values@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.0.tgz#bf6810ef5da3e5325790eaaa2be213ea84624da9" - integrity sha512-8mf0nKLAoFX6VlNVdhGj31SVYpaNFtUnuoOXWyFEstsWRgU837AK+JYM0iAxwkSzGRbwn8cbFmgbyxj1j4VbXg== +object.values@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.1.tgz#68a99ecde356b7e9295a3c5e0ce31dc8c953de5e" + integrity sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA== dependencies: define-properties "^1.1.3" - es-abstract "^1.12.0" + es-abstract "^1.17.0-next.1" function-bind "^1.1.1" has "^1.0.3" @@ -3756,13 +3846,6 @@ onetime@^2.0.0: dependencies: mimic-fn "^1.0.0" -onetime@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.0.tgz#fff0f3c91617fe62bb50189636e99ac8a6df7be5" - integrity sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q== - dependencies: - mimic-fn "^2.1.0" - optionator@^0.8.1: version "0.8.2" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" @@ -3774,17 +3857,17 @@ optionator@^0.8.1: type-check "~0.3.2" wordwrap "~1.0.0" -optionator@^0.8.3: - version "0.8.3" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" - integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== +optionator@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" + integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== dependencies: - deep-is "~0.1.3" - fast-levenshtein "~2.0.6" - levn "~0.3.0" - prelude-ls "~1.1.2" - type-check "~0.3.2" - word-wrap "~1.2.3" + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.3" ora@^3.1.0: version "3.4.0" @@ -3809,7 +3892,7 @@ os-locale@^1.4.0: dependencies: lcid "^1.0.0" -os-tmpdir@^1.0.0, os-tmpdir@~1.0.2: +os-tmpdir@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" @@ -3952,10 +4035,10 @@ path-is-absolute@^1.0.0: resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= -path-key@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" - integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== path-parse@^1.0.6: version "1.0.6" @@ -4095,6 +4178,11 @@ prebuild-install@^5.2.1: tunnel-agent "^0.6.0" which-pm-runs "^1.0.0" +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" @@ -4265,10 +4353,10 @@ regenerator-transform@^0.14.0: dependencies: private "^0.1.6" -regexpp@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" - integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== +regexpp@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2" + integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q== regexpu-core@^4.6.0: version "4.6.0" @@ -4381,13 +4469,20 @@ resolve-from@^5.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== -resolve@^1.10.0, resolve@^1.11.0, resolve@^1.5.0: +resolve@^1.10.0, resolve@^1.11.0: version "1.13.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.13.1.tgz#be0aa4c06acd53083505abb35f4d66932ab35d16" integrity sha512-CxqObCX8K8YtAhOBRg+lrcdn+LK+WYOS8tSjqSFbjtrI5PnS63QPhZl4+yKfrU9tdsbMu9Anr/amegT87M9Z6w== dependencies: path-parse "^1.0.6" +resolve@^1.12.0, resolve@^1.13.1, resolve@^1.17.0: + version "1.17.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" + integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== + dependencies: + path-parse "^1.0.6" + resolve@^1.14.2, resolve@^1.3.2: version "1.15.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8" @@ -4403,14 +4498,6 @@ restore-cursor@^2.0.0: onetime "^2.0.0" signal-exit "^3.0.2" -restore-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" - integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== - dependencies: - onetime "^5.1.0" - signal-exit "^3.0.2" - rimraf@2.6.3: version "2.6.3" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" @@ -4520,20 +4607,6 @@ rollup@^1.2.3: "@types/node" "*" acorn "^7.1.0" -run-async@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" - integrity sha1-A3GrSuC91yDUFm19/aZP96RFpsA= - dependencies: - is-promise "^2.1.0" - -rxjs@^6.4.0: - version "6.5.3" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.3.tgz#510e26317f4db91a7eb1de77d9dd9ba0a4899a3a" - integrity sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA== - dependencies: - tslib "^1.9.0" - safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2: version "5.2.0" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" @@ -4583,11 +4656,16 @@ semver@^5.4.1: version "5.7.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b" -semver@^6.1.2, semver@^6.3.0: +semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== +semver@^7.2.1: + version "7.3.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" + integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== + send@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/send/-/send-0.1.0.tgz#cfb08ebd3cec9b7fc1a37d9ff9e875a971cf4640" @@ -4636,17 +4714,17 @@ shallow-clone@^0.1.2: lazy-cache "^0.2.3" mixin-object "^2.0.1" -shebang-command@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" - integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== dependencies: - shebang-regex "^1.0.0" + shebang-regex "^3.0.0" -shebang-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" - integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.2" @@ -4932,14 +5010,13 @@ string-width@^3.0.0, string-width@^3.1.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" -string-width@^4.1.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" - integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg== +string.prototype.trimend@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913" + integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g== dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.0" + define-properties "^1.1.3" + es-abstract "^1.17.5" string.prototype.trimleft@^2.1.0: version "2.1.0" @@ -4957,6 +5034,14 @@ string.prototype.trimright@^2.1.0: define-properties "^1.1.3" function-bind "^1.1.1" +string.prototype.trimstart@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54" + integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + string_decoder@~0.10.x: version "0.10.31" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" @@ -5006,10 +5091,10 @@ strip-json-comments@2.0.1, strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" -strip-json-comments@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7" - integrity sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw== +strip-json-comments@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== supports-color@6.0.0: version "6.0.0" @@ -5113,23 +5198,11 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= -through@^2.3.6: - version "2.3.8" - resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" - integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= - tinycolor2@^1.1.2: version "1.4.1" resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.1.tgz#f4fad333447bc0b07d4dc8e9209d8f39a8ac77e8" integrity sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g= -tmp@^0.0.33: - version "0.0.33" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" - integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== - dependencies: - os-tmpdir "~1.0.2" - to-array@0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890" @@ -5195,10 +5268,15 @@ triangulate-polyline@^1.0.0: dependencies: cdt2d "^1.0.0" -tslib@^1.9.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" - integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== +tsconfig-paths@^3.9.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz#098547a6c4448807e8fcb8eae081064ee9a3c90b" + integrity sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.1" + minimist "^1.2.0" + strip-bom "^3.0.0" tunnel-agent@^0.6.0: version "0.6.0" @@ -5221,6 +5299,13 @@ two-sum@^1.0.0: resolved "https://registry.yarnpkg.com/two-sum/-/two-sum-1.0.0.tgz#31d3f32239e4f731eca9df9155e2b297f008ab64" integrity sha1-MdPzIjnk9zHsqd+RVeKyl/AIq2Q= +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + type-check@~0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" @@ -5396,15 +5481,6 @@ webidl-conversions@^4.0.2: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== -weightless@^0.0.37: - version "0.0.37" - resolved "https://registry.yarnpkg.com/weightless/-/weightless-0.0.37.tgz#155b020c27ec1d210055f09c31682f32ae785c09" - integrity sha512-WxzpPc2VlD/PFWwOxMhQ6dPOmM4ugepXn6D8Qo7vUNSWWRIANqBvkQlo4TsjYiEpM4rAXCBGvkwMpfpxad14mA== - dependencies: - "@a11y/focus-trap" "~1.0.4" - lit-element "^2.2.1" - lit-html "^1.1.2" - whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" @@ -5436,12 +5512,19 @@ which-pm-runs@^1.0.0: resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb" integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs= -which@1.3.1, which@^1.2.9: +which@1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" dependencies: isexe "^2.0.0" +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + wide-align@1.1.3, wide-align@^1.1.0: version "1.1.3" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" @@ -5452,7 +5535,7 @@ window-size@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.4.tgz#f8e1aa1ee5a53ec5bf151ffa09742a6ad7697876" -word-wrap@~1.2.3: +word-wrap@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== From 104aa4d3ab8503706772abad7eb4ddab8b3ab245 Mon Sep 17 00:00:00 2001 From: techninja Date: Tue, 4 Aug 2020 15:52:22 -0700 Subject: [PATCH 038/147] Get rid of weightless. Sorry guys! --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 5e0f88a..e783702 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,6 @@ "socket.io": "^2.3.0", "underscore": "^1.9.2", "vectorize-text": "^3.2.1", - "weightless": "^0.0.37", "zodiac-ts": "^1.0.3" }, "devDependencies": { From a40a411e9b7168e0b162e5cfefba9ba274d3704e Mon Sep 17 00:00:00 2001 From: techninja Date: Tue, 4 Aug 2020 16:10:37 -0700 Subject: [PATCH 039/147] Set bounds for automatic SVG sizing --- .../modules/components/elements/tool-implement.mjs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/interface/modules/components/elements/tool-implement.mjs b/src/interface/modules/components/elements/tool-implement.mjs index 71f285c..222ff8f 100644 --- a/src/interface/modules/components/elements/tool-implement.mjs +++ b/src/interface/modules/components/elements/tool-implement.mjs @@ -61,6 +61,8 @@ export default () => ({ left: handle.left + handle.width / 2 - (width * scale) / 2, }; + // TODO: Support larger scales without hard coded values. + const textHeight = 2.2 * scale; const center = brush.left + brush.width / 2; const largerWidth = brush.width > handle.width ? brush.width / 2 : handle.width / 2; const minLength = type === 'pen' ? 5 : 2; @@ -157,17 +159,25 @@ export default () => ({ }, }; + // Set SVG viewbox. + const viewBox = [ + 0, 0, + 160, // Width. + handle.top + handle.height + brush.height + bracketSize * 2 + + bracketNotch * 2 + bracketPadding + textHeight * 2, // Height. + ]; + return html` - + ${svg`
This element holds data to be rendered in the host.
- `, -}; diff --git a/src/interface/modules/components/widgets/colorsets/colorset-colors.mjs b/src/interface/modules/components/widgets/colorsets/colorset-colors.mjs index 90ab0d3..d295ef2 100644 --- a/src/interface/modules/components/widgets/colorsets/colorset-colors.mjs +++ b/src/interface/modules/components/widgets/colorsets/colorset-colors.mjs @@ -1,9 +1,8 @@ /** * @file Colorset Editor: view colors element definition. */ -import ColorsetColorItem from './colorset-color-item.mjs'; import { handleSwitch } from './pane-utils.mjs'; -import { html, children } from '/modules/hybrids.js'; +import { html } from '/modules/hybrids.js'; // TODO: // - Use Schema form to build out the rest of the controls here. @@ -15,11 +14,15 @@ import { html, children } from '/modules/hybrids.js'; // - Finish building the custom colorset saver. // - Fix the incredibly ugly padding issue. General styles? export default styles => ({ - items: children(ColorsetColorItem), + colors: [], + implement: {}, // Parent level implement. + set: {}, name: '', description: '', - render: ({ items, name, description }) => html` + render: ({ + colors, name, description, implement, set, + }) => html` ${styles} ${loading && html`
LOADING...
`} -
+
${init} `, }); From 7d5a4bd306130e38051fac43a0a910f35c8d4aa8 Mon Sep 17 00:00:00 2001 From: techninja Date: Fri, 25 Sep 2020 15:20:19 -0700 Subject: [PATCH 059/147] Lots of schema form updates and improvements --- .../components/elements/schema-form.mjs | 124 ++++++++++++------ 1 file changed, 86 insertions(+), 38 deletions(-) diff --git a/src/interface/modules/components/elements/schema-form.mjs b/src/interface/modules/components/elements/schema-form.mjs index 84841c5..644c5b2 100644 --- a/src/interface/modules/components/elements/schema-form.mjs +++ b/src/interface/modules/components/elements/schema-form.mjs @@ -7,6 +7,7 @@ import { html, dispatch } from '/modules/hybrids.js'; import jsonPath from '/modules/jsonpath.js'; import apiInit from '/modules/utils/api-init.mjs'; +import dataDiff from '/modules/utils/data-diff.mjs'; // Initial configuration object for JSONEditor. const globalJSONEditorSettings = { @@ -62,13 +63,85 @@ function customizeForm(host) { item.parentNode.insertBefore(num, item); }); + // Insert forms inside preset keys. + matchingItems(host, host.presetPaths, item => { + if (item.getAttribute('data-schemapath') === 'root.implement') { + addPresetSelect(host, item, 'implement'); + } + + if (item.getAttribute('data-schemapath') === 'root.toolset') { + addPresetSelect(host, item, 'toolset'); + } + }); + // Hide items if the host requests it. - if (host.hidePaths) { - const paths = host.hidePaths.split(','); + matchingItems(host, host.hidePaths, item => { item.style.display = 'none'; }); + + // Disable items if the host requests it. + matchingItems(host, host.disablePaths, item => { + item.querySelector('.form-control').disabled = true; + }); +} + +/** + * Fold in a replacement form item to select a preset based on type. + * + * @param {*} host + * @param {*} item + * @param {*} type + */ +function addPresetSelect(host, item, type) { + // Create selector based on type. + const input = item.querySelector('input'); + const select = document.createElement(`preset-select-${type}`); + item.appendChild(select); + + select.classList.add('preset-selector'); + select.selected = input.value; + + // Switch for allow inherit. + if (type === 'implement') { + console.log('Implement preset...', host.editor.schema.properties?.implement?.default); + if (host.editor.schema.properties?.implement?.default === '[inherit]') { + select.allowInherit = true; + } + } + + // Bind change to insert value. + select.addEventListener('change', () => { + input.value = select.selected + input.dispatchEvent(new Event('change')); + }); + + // Bind to changes from content. + input.addEventListener('input', () => { + console.log('Change From Input', input.value); + select.selected = input.value; + }); + + // Hide elements. + item.querySelector('input').style.display = 'none'; + item.querySelector('small').style.display = 'none'; +} + +/** + * Callback runner for modifying forms. + * + * @param {Element} host + * The parent host element. + * @param {string} pathStrings + * Comma separated string from host property, to be parsed as schemapath matches. + * @param {function} [cb=(item) => item] + * Callback called on each matching form element wrapper. + */ +function matchingItems(host, pathStrings, cb = (item) => item) { + const form = host.shadowRoot.querySelector('form'); + if (pathStrings) { + const paths = pathStrings.split(','); const pathItems = form.querySelectorAll('[data-schemapath]'); pathItems.forEach((item) => { if (paths.includes(item.getAttribute('data-schemapath'))) { - item.style.display = 'none'; + cb(item); } }); } @@ -86,41 +159,11 @@ function externalUpdateForm(host) { inputs.forEach((item) => { item.previousSibling.value = item.value; }); -} -/** - * Get only the parts that are different from a complete/deep JSON object. - * - * @param {object} object - * Input object to check for diff against the base. - * @param {object} base - * Base object to diff against the input. - * - * @returns {object} - * Only the differences between the two objects in full tree form. - */ -function dataDiff(object, base) { - const result = _.pick( - _.mapObject(object, (value, key) => ( - // eslint-disable-next-line no-nested-ternary - (!_.isEqual(value, base[key])) - // eslint-disable-next-line max-len - ? ((_.isObject(value) && _.isObject(base[key])) ? dataDiff(value, base[key]) : value) - : null - )), - value => (value !== null) - ); - - // Trim out empty keys - const entries = Object.entries(result); - entries.forEach(([key, val]) => { - if (typeof val === 'object' && Object.keys(val).length === 0) { - delete result[key]; - } + // Ensure presets have correct selections. + matchingItems(host, host.presetPaths, item => { + item.querySelector('.preset-selector').selected = item.querySelector('input').value; }); - - // TODO: Trim deeper changed objects. - return result; } /** @@ -308,6 +351,7 @@ function schemaChangeFactory(defaultSchema = {}) { */ function dataChangeFactory() { return { + // TODO: This seems to be caching and can be behind, even though it shouldn't. get: host => (host.editor ? host.editor.data.current : {}), set: (host, value, lastValue) => { if (!host.editor) return {}; @@ -318,6 +362,8 @@ function dataChangeFactory() { const completeNew = { ...host.editor.data.current, ...value }; const setDiff = dataDiff(completeNew, host.editor.data.current); if (Object.entries(setDiff).length) { + // TODO: This fails as dependent keys are set to undefined! + // @see https://github.com/json-editor/json-editor/issues/819 if (host.debug) console.log('External Set diff is', setDiff); host.editor.data.setExternally = true; if (host.editor.setValue(setData)) { @@ -370,8 +416,10 @@ export default styles => ({ // Remove immediate box styles. plain: false, - // Comma separated list of schema paths to hide. - hidePaths: '', + // Comma separated list of schema paths to modify. + hidePaths: '', // Entirely hide input. + disablePaths: '', // Lock & Disable (but display). + presetPaths: '', // Replace with preset selector. // Style selector and pixel height of "content area". contentSelector: '', From 3d489655c67ef2bd21967e1936508b6562a37eaf Mon Sep 17 00:00:00 2001 From: techninja Date: Sun, 27 Sep 2020 17:11:32 -0700 Subject: [PATCH 060/147] Finish function if we're just cleaning up a process --- src/components/core/drawing/cncserver.drawing.spawner.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/core/drawing/cncserver.drawing.spawner.js b/src/components/core/drawing/cncserver.drawing.spawner.js index 1cd1ee5..e56e53e 100644 --- a/src/components/core/drawing/cncserver.drawing.spawner.js +++ b/src/components/core/drawing/cncserver.drawing.spawner.js @@ -32,6 +32,7 @@ module.exports = (cncserver, drawing) => { // throw new Error(`Spawn data item mismatch: ${spawnKey}`); console.error(`Spawn data item mismatch: ${spawnKey}, killing process`); ipc.server.emit(socket, 'cancel'); + return; } const timeTaken = Math.round((new Date() - workingQueueItem.start) / 100) / 10; From 8b5ca7227b74861fac77abd1964abeed9e1d7bb1 Mon Sep 17 00:00:00 2001 From: techninja Date: Thu, 1 Oct 2020 14:41:38 -0700 Subject: [PATCH 061/147] Pre-final working massive Colorset API rework - Adds print layer rendering - Adds endpoint for colorset tools - CRUD for colorset items - CRUD for toolsets/custom tools - Implement & toolset as preset string - Adjusts Final Draw rendering to use print layer --- .../core/comms/api/cncserver.api.colors.js | 66 ++- .../core/comms/api/cncserver.api.tools.js | 52 +- .../core/comms/cncserver.sockets.js | 1 + .../core/control/cncserver.content.js | 1 + .../core/control/cncserver.control.js | 24 +- src/components/core/control/cncserver.pen.js | 5 +- .../core/control/cncserver.tools.js | 131 ++++- .../core/drawing/cncserver.drawing.base.js | 19 +- .../core/drawing/cncserver.drawing.colors.js | 459 +++++++----------- .../drawing/cncserver.drawing.implements.js | 15 +- .../core/drawing/colorsets/crayola.json | 52 -- .../core/drawing/colorsets/generic.json | 16 - .../core/drawing/colorsets/prang.json | 48 -- .../core/drawing/colorsets/sargent.json | 16 - src/components/core/drawing/index.js | 1 + .../cncserver.bots.watercolorbot.js | 16 +- .../third_party/scratch/cncserver.scratch.js | 12 +- src/interface/lib/cncserver.client.api.js | 5 +- 18 files changed, 443 insertions(+), 496 deletions(-) delete mode 100644 src/components/core/drawing/colorsets/crayola.json delete mode 100644 src/components/core/drawing/colorsets/generic.json delete mode 100644 src/components/core/drawing/colorsets/prang.json delete mode 100644 src/components/core/drawing/colorsets/sargent.json diff --git a/src/components/core/comms/api/cncserver.api.colors.js b/src/components/core/comms/api/cncserver.api.colors.js index 65a2347..429b460 100644 --- a/src/components/core/comms/api/cncserver.api.colors.js +++ b/src/components/core/comms/api/cncserver.api.colors.js @@ -6,51 +6,70 @@ const handlers = {}; module.exports = (cncserver) => { // Primary Colorset group handler. handlers['/v2/colors'] = (req, res) => { - const { drawing: { colors }, utils, schemas } = cncserver; + const { drawing: { colors }, utils, schemas, rest } = cncserver; // Standard post resolve for colors requests. function postResolve() { - res.status(200).send({ set: colors.getCurrentSet(), presets: colors.presets }); + res.status(200).send({ + set: colors.getCurrentSet(req.t), // The current colorset. + presets: colors.listPresets(req.t), // List all presets. + customs: colors.customKeys(), // List of custom preset machine names. + internals: colors.internalKeys(), // List of internal preset machine names. + invalidSets: colors.invalidPresets(), // Keys of invalid colorsets. + }); } // Get current colorset and presets. if (req.route.method === 'get') { - return { - code: 200, - body: { - set: colors.getCurrentSet(req.t), - presets: colors.listPresets(req.t), - }, - }; + postResolve(); + return true; // Tell endpoint wrapper we'll handle the GET response. } - // Add color, or replace set from preset or custom set. + // Add color, or replace set from preset. if (req.route.method === 'post') { // Set via preset, only err here is 404 preset not found. if (req.body.preset) { colors.applyPreset(req.body.preset, req.t) .then(postResolve) - .catch(cncserver.rest.err(res, 404)); + .catch(rest.err(res, 404)); } else { // Validate data and add color item, or error out. schemas.validateData('color', req.body, true) + .then(utils.isValidPreset('implement', true)) .then(colors.add) .then(postResolve) - .catch(cncserver.rest.err(res)); + .catch(rest.err(res)); } return true; // Tell endpoint wrapper we'll handle the POST response. } + // Allow deleting of custom presets. + if (req.route.method === 'delete' && req.body.preset) { + const customKeys = colors.customKeys(); + if (!customKeys.includes(req.body.preset)) { + return { + code: 406, + body: { + status: `Only custom or overridden presets can be deleted.`, + allowedValues: customKeys, + }, + }; + } else { + utils.deletePreset('colorsets', req.body.preset); + postResolve(); + return true; // Tell endpoint wrapper we'll handle the GET response. + } + } + // Change set options directly. if (req.route.method === 'patch') { // If item data is attempted to be changed here, give a specific message for it. - if (req.body.items || req.body.tools) { - cncserver.rest.err(res, 406)( + if (req.body.items) { + rest.err(res, 406)( new Error(utils.singleLineString`Patching the colors endpoint can only edit the - current set details, not individual color items or tools. - Patch to /v2/colors/[ID] to edit a color item, /v2/tools/[ID] to edit a - colorset tool.` + current set details, not individual colorset items. + Patch to /v2/colors/[ID] to edit a colorset item.` ) ); } @@ -58,12 +77,13 @@ module.exports = (cncserver) => { // Merge with current set, validate data, then edit. const set = colors.getCurrentSet(); delete set.items; - delete set.tools; const mergedItem = utils.merge(set, req.body); schemas.validateData('colors', mergedItem, true) + .then(utils.isValidPreset('implement')) + .then(utils.isValidPreset('toolset')) .then(colors.editSet) .then(postResolve) - .catch(cncserver.rest.err(res)); + .catch(rest.err(res)); return true; // Tell endpoint wrapper we'll handle the PATCH response. } @@ -85,7 +105,7 @@ module.exports = (cncserver) => { code: 404, body: { status: `Color with id of '${colorID}' not found in color set.`, - validOptions: colors.getIDs(), + allowedValues: colors.getIDs(), }, }; } @@ -97,7 +117,8 @@ module.exports = (cncserver) => { // Patch item. if (req.route.method === 'patch') { - if (req.body.id) { + // Error out if client is trying to change the ID in the request. + if (req.body.id && req.body.id !== colorID) { return { code: 406, body: { @@ -112,6 +133,7 @@ module.exports = (cncserver) => { // Validate the request data against the schema before continuing. cncserver.schemas.validateData('color', mergedItem, true) + .then(utils.isValidPreset('implement', true)) .then(colors.edit) .then((finalItem) => { res.status(200).send(finalItem); }) .catch(cncserver.rest.err(res)); @@ -122,7 +144,7 @@ module.exports = (cncserver) => { // Delete color if (req.route.method === 'delete') { colors.delete(colorID); - return { code: 200, body: { set: colors.set, presets: colors.presets } }; + return { code: 200, body: { status: 'success' } }; } // Error to client for unsupported request types. diff --git a/src/components/core/comms/api/cncserver.api.tools.js b/src/components/core/comms/api/cncserver.api.tools.js index 3b78431..b8dbee3 100644 --- a/src/components/core/comms/api/cncserver.api.tools.js +++ b/src/components/core/comms/api/cncserver.api.tools.js @@ -13,17 +13,27 @@ module.exports = (cncserver) => { status: 'error', message: `Tool: '${name}' not found.`, validOptions: cncserver.tools.getNames(), - } + }, }; } // Primary tools endpoint handler. List, create. - handlers['/v2/tools'] = function toolsGet(req, res) { - const { tools, schemas } = cncserver; + handlers['/v2/tools'] = function toolsMain(req, res) { + const { tools, schemas, utils, rest } = cncserver; // Get list of tools if (req.route.method === 'get') { - return { code: 200, body: { tools: tools.items() } }; + return { + code: 200, + body: { + set: tools.getResponseSet(res.t), + tools: tools.items(), + presets: tools.listPresets(), + customs: tools.customKeys(), // List of custom preset machine names. + internals: tools.internalKeys(), // List of internal preset machine names. + invalidSets: tools.invalidPresets(), + } + }; } // Add new custom tool. @@ -32,17 +42,41 @@ module.exports = (cncserver) => { schemas.validateData('tools', req.body, true) .then(tools.add) .then((tool) => { res.status(200).send(tool); }) - .catch(cncserver.rest.err(res)); + .catch(rest.err(res)); return true; // Tell endpoint wrapper we'll handle the POST response. } + // Change toolset options directly. + if (req.route.method === 'patch') { + // If item data is attempted to be changed here, give a specific message for it. + if (req.body.items) { + rest.err(res, 406)( + new Error(utils.singleLineString`Patching the tools endpoint can only edit the + current toolset details, not individual tool items. + Patch to /v2/tools/[ID] to edit a custom toolset item.` + ) + ); + } + + // Merge with current set, validate data, then edit. + const set = { ...tools.set }; + delete set.items; + const mergedItem = utils.merge(set, req.body); + schemas.validateData('toolsets', mergedItem, true) + .then(tools.editSet) + .then((set) => { res.status(200).send(set); }) + .catch(rest.err(res)); + + return true; // Tell endpoint wrapper we'll handle the PATCH response. + } + // Error to client for unsupported request types. return false; }; // Tool specific enpoint. - handlers['/v2/tools/:toolID'] = function toolsMain(req, res) { + handlers['/v2/tools/:toolID'] = function toolsItemMain(req, res) { const { toolID } = req.params; const { tools, utils } = cncserver; const tool = tools.getItem(toolID); @@ -52,7 +86,7 @@ module.exports = (cncserver) => { // Set current end of buffer tool to ID. if (req.route.method === 'put') { - cncserver.tools.set(tool.id, null, () => { + cncserver.tools.changeTo(tool.id, null, () => { res.status(200).send(JSON.stringify({ status: `Tool changed to ${tool.id}`, })); @@ -84,7 +118,7 @@ module.exports = (cncserver) => { body: { status: 'error', message: 'This is a bot level tool, you can only edit colorset level tools via the API.', - validOptions: tools.canEdit(), + allowedValues: tools.canEdit(), }, }; } @@ -134,7 +168,7 @@ module.exports = (cncserver) => { if (req.route.method === 'put') { // Set Tool - tools.set(tool.id, toolIndex, () => { + tools.changeTo(tool.id, toolIndex, () => { // TODO: Is this force state needed? cncserver.pen.forceState({ tool: tool.id }); res.status(200).send(JSON.stringify({ diff --git a/src/components/core/comms/cncserver.sockets.js b/src/components/core/comms/cncserver.sockets.js index 35b2d28..f8db392 100644 --- a/src/components/core/comms/cncserver.sockets.js +++ b/src/components/core/comms/cncserver.sockets.js @@ -161,6 +161,7 @@ module.exports = (cncserver) => { cncserver.drawing.colors.snapPathColors( cncserver.drawing.base.layers.preview ); + sockets.sendPaperUpdate('print'); } io.emit('paper layer', { diff --git a/src/components/core/control/cncserver.content.js b/src/components/core/control/cncserver.content.js index f7a8384..8fd9461 100644 --- a/src/components/core/control/cncserver.content.js +++ b/src/components/core/control/cncserver.content.js @@ -179,6 +179,7 @@ module.exports = (cncserver) => { source content and try again.` )); } else { + cncserver.drawing.base.validateFills(item); success({ ...payload, source, item }); } } catch (error) { diff --git a/src/components/core/control/cncserver.control.js b/src/components/core/control/cncserver.control.js index a263cb7..e25c2ec 100644 --- a/src/components/core/control/cncserver.control.js +++ b/src/components/core/control/cncserver.control.js @@ -4,6 +4,8 @@ */ const control = {}; // Exposed export. +const { Group, Path, CompoundPath } = require('paper'); + module.exports = (cncserver) => { /** * "Move" the pen (tip of the buffer) to an absolute point inside the maximum @@ -330,8 +332,9 @@ module.exports = (cncserver) => { * @param {object} source * Source paper object containing the children, defaults to preview layer. */ - control.renderPathsToMoves = (source = cncserver.drawing.base.layers.preview, reqSettings = {}) => { - const { settings: { botConf }, drawing: { colors } } = cncserver; + control.renderPathsToMoves = (reqSettings = {}) => { + const { settings: { botConf }, drawing: { base, colors }, tools } = cncserver; + const source = cncserver.drawing.base.layers.print; const settings = { parkAfter: true, ...reqSettings, @@ -345,16 +348,9 @@ module.exports = (cncserver) => { // Store work for all paths grouped by color const workGroups = colors.getWorkGroups(); const validColors = Object.keys(workGroups); - const allPaths = cncserver.drawing.base.getPaths(source); - allPaths.forEach((path) => { - const colorID = cncserver.drawing.base.getColorID(path); - - if (path.length && colorID && validColors.includes(colorID)) { - workGroups[colorID].push(path); - // Allow implementing triggers to modify current list of paths. - workGroups[colorID] = cncserver.binder.trigger('control.render.path.select', workGroups[colorID]); - } else if (colorID !== 'ignore') { - console.log(`DEBUG: Invalid Draw path ${colorID} ${path.name} ==================`); + source.children.forEach((colorGroup) => { + if (workGroups[colorGroup.name]) { + workGroups[colorGroup.name] = base.getPaths(colorGroup); } }); @@ -371,14 +367,14 @@ module.exports = (cncserver) => { // Do we have a tool for this colorID? If not, use manualswap. if (colors.doColorParsing()) { const changeTool = botConf.get(`tools:${colorID}`) ? colorID : 'manualswap'; - tools.set(changeTool, colorID); + tools.changeTo(changeTool, colorID); } let pathIndex = 0; const nextPath = () => { if (paths[pathIndex]) { control.accelMoveOnPath(paths[pathIndex]).then(() => { - // Trigger implementors for plath render complete. + // Trigger implementors for path render complete. cncserver.binder.trigger('control.render.path.finish', paths[pathIndex]); // Path in this group done, move to the next. diff --git a/src/components/core/control/cncserver.pen.js b/src/components/core/control/cncserver.pen.js index d65a177..8c7cd44 100644 --- a/src/components/core/control/cncserver.pen.js +++ b/src/components/core/control/cncserver.pen.js @@ -18,6 +18,7 @@ module.exports = (cncserver) => { power: 0, busy: false, tool: 'color0', // TODO: This seems wrong and assuming. + implement: '', // Implement string. offCanvas: false, bufferHash: '', // Holds the last pen buffer hash. lastDuration: 0, // Holds the last movement timing in milliseconds @@ -38,7 +39,7 @@ module.exports = (cncserver) => { * @param {number} speedOverride * Percent of speed to set for this movement only. */ - pen.setPen = (inPenState, callback = () => {}, speedOverride = null) => { + pen.setPen = (inPenState, callback = () => { }, speedOverride = null) => { // What can happen here? // We're changing state, and what we need is the ability to find all the // passed, changed state from either the actual pen state, OR the tip of @@ -280,7 +281,7 @@ module.exports = (cncserver) => { * @param {function} callback * Ya know. */ - pen.park = (direct = false, callback = () => {}) => { + pen.park = (direct = false, callback = () => { }) => { pen.setHeight('up', () => { pen.setPen({ x: cncserver.settings.bot.park.x, diff --git a/src/components/core/control/cncserver.tools.js b/src/components/core/control/cncserver.tools.js index f33a854..966db33 100644 --- a/src/components/core/control/cncserver.tools.js +++ b/src/components/core/control/cncserver.tools.js @@ -5,6 +5,7 @@ const { Path, Group, PointText } = require('paper'); const tools = { id: 'tools', + set: {}, // Set as part of colorset tool update. }; module.exports = (cncserver) => { @@ -12,48 +13,107 @@ module.exports = (cncserver) => { // Get a flat array of tools. tools.items = () => [ ...tools.getBotTools(), - ...Array.from(cncserver.drawing.colors.set.tools.values()), + ...tools.colorsetTools(), ]; + // List all tool presets and their data. + tools.listPresets = (t, customOnly) => { + const { utils } = cncserver; + // TODO: Translate title/description. + return customOnly + ? utils.getCustomPresets('toolsets') + : utils.getPresets('toolsets'); + }; + + // List custom/overridden machine names. + tools.customKeys = () => + Object.keys(cncserver.utils.getCustomPresets('toolsets')); + + // List internal machine names. + tools.internalKeys = () => + Object.keys(cncserver.utils.getInternalPresets('toolsets')); + + // Object of preset keys with set tools parents unavailable to this bot. + tools.invalidPresets = () => { + const out = {}; + const sets = tools.listPresets(); + const botTools = cncserver.settings.botConf.get('tools'); + const botName = cncserver.settings.botConf.get('name'); + + // Move through all sets, check the toolset for missing parents + Object.entries(sets).forEach(([name, { items }]) => { + items.forEach(item => { + if (item.parent && !(item.parent in botTools)) { + if (!(name in out)) out[name] = {}; + out[name][item.parent] = `'${botName}' does not supply required parent tool '${item.parent}'`; + } + }); + }); + + return out; + }; + + // Get the current toolset as a translated, array based object. + tools.getResponseSet = (t) => { + const { utils } = cncserver; + // TODO: Translate title/description. + return { + ...tools.set, + items: utils.mapToArray(tools.set.items), + }; + } + + // Convert a colorset toolset to a flat array + tools.colorsetTools = (asMap = false) => { + const { utils } = cncserver; + return asMap ? tools.set.items : utils.mapToArray(tools.set.items); + } + // Can we edit the given tool id? tools.canEdit = (id) => { - const editableList = Array.from(cncserver.drawing.colors.set.tools.keys()); - if (id) { - return editableList.includes(id); - } - - return editableList; + const editableList = Array.from(tools.set.items.keys()); + return id ? editableList.includes(id) : editableList; }; // Function to run after the tools have been changed (add, delete, edit). tools.sendUpdate = () => { - cncserver.drawing.colors.saveCustom(); + tools.saveCustom(); cncserver.sockets.sendPaperUpdate(); cncserver.binder.trigger('tools.update'); }; - // Function for editing tools (from the colorset only). + // Function for editing tools (from the toolset only). tools.edit = (tool) => new Promise((resolve, reject) => { - const { colors } = cncserver.drawing; - colors.set.tools.set(tool.id, tool); + tools.set.items.set(tool.id, tool); tools.sendUpdate(); - resolve(colors.set.tools.get(tool.id)); + resolve(tools.set.items.get(tool.id)); + }); + + // Function for editing toolset base properties (name, manufacturer, title, desc). + tools.editSet = (toolset) => new Promise((resolve, reject) => { + toolset.name = cncserver.utils.getMachineName(toolset.name, 64); + // Name change: Update in colorset. + if (tools.set.name != toolset.name) { + cncserver.drawing.colors.set.toolset = toolset.name; + cncserver.drawing.colors.saveCustom(); + } + tools.set = { ...toolset, items: tools.set.items }; + tools.sendUpdate(); + resolve(tools.getResponseSet()); }); // Delete the a verified ID. tools.delete = (id) => { - const { colors } = cncserver.drawing; - colors.set.tools.delete(id); + tools.set.items.delete(id); tools.sendUpdate(); }; // Add a validated tool. tools.add = (tool) => new Promise((resolve, reject) => { - const { colors } = cncserver.drawing; - if (!colors.set.tools.get(tool.id)) { - colors.set.tools.set(tool.id, tool); + if (!tools.set.items.get(tool.id)) { + tools.set.items.set(tool.id, tool); tools.sendUpdate(); - resolve(colors.set.tools.get(tool.id)); + resolve(tools.set.items.get(tool)); } else { reject( new Error(cncserver.utils.singleLineString`Custom colorset tool with id @@ -87,8 +147,36 @@ module.exports = (cncserver) => { // Get a single item, undefined if invalid. tools.getItem = name => tools.items().find(({ id }) => id === name); + // Automatically set the internal "tools.set" from "colors.set.toolset". + tools.setFromColors = () => { + const { utils } = cncserver; + const set = utils.getPreset('toolsets', cncserver.drawing.colors.set.toolset); + + tools.set = { + ...set, + items: cncserver.utils.arrayToIDMap(set.items), + }; + }; + + // Save changes to the current toolset + tools.saveCustom = () => { + const { drawing: { colors }, utils } = cncserver; + + // Special failover to prevent squashing empty "default". + if (tools.set.name === 'default') { + tools.set.name = 'default-custom'; + colors.set.toolset = tools.set.name; + colors.saveCustom(); + } + + utils.savePreset('toolsets', { + ...tools.set, + items: utils.mapToArray(tools.set.items), + }) + }; + /** - * Run the operation to set the current tool (and any aggregate operations + * Run the operation to change the current tool (and any aggregate operations * required) into the buffer * * @param name @@ -105,7 +193,7 @@ module.exports = (cncserver) => { * @returns {boolean} * True if success, false on failure. */ - tools.set = (name, index = null, callback = () => { }, waitForCompletion = false) => { + tools.changeTo = (name, index = null, callback = () => { }, waitForCompletion = false) => { // Get the matching tool object from the bot configuration. const tool = tools.getItem(name); @@ -170,6 +258,9 @@ module.exports = (cncserver) => { cncserver.binder.bindTo('tools.update', tools.id, () => { const { layers } = cncserver.drawing.base; const toolGroup = new Group(); + + tools.setFromColors(); + const items = tools.items(); layers.tools.removeChildren(); diff --git a/src/components/core/drawing/cncserver.drawing.base.js b/src/components/core/drawing/cncserver.drawing.base.js index 48c8a63..40bf1cd 100644 --- a/src/components/core/drawing/cncserver.drawing.base.js +++ b/src/components/core/drawing/cncserver.drawing.base.js @@ -31,7 +31,8 @@ module.exports = (cncserver) => { 'temp', // Temporary working space, cleared before each operation. 'stage', // Project imported groups of items. 'tools', // Helper visualization of tool positions. - 'preview', // Final render, item groups of lines w/color data only (no fills). + 'preview', // Render destination, item groups of lines w/color data only (no fills). + 'print', // Final print source, grouped by colorset work groupings. ]; layers.forEach((name) => { base.layers[name] = new Layer({ name }); @@ -53,7 +54,7 @@ module.exports = (cncserver) => { // Get a list of all simple paths from all children as an array. base.getPaths = (parent = base.layers.preview, items = []) => { - if (parent.children && parent.children.length && !(parent instanceof CompoundPath)) { + if (parent.children) { let moreItems = []; parent.children.forEach((child) => { moreItems = base.getPaths(child, moreItems); @@ -171,6 +172,20 @@ module.exports = (cncserver) => { } }; + // SVG content can have paths with NO fill or strokes, they're assumed to be black fill. + base.validateFills = (item) => { + item.children.forEach(child => { + if (!child.fillColor && !child.strokeColor) { + if (child.children && child.children.length) { + base.validateFills(child); + } else { + // TODO: This likely needs more rules for cleanup. + child.fillColor = 'black'; + } + } + }) + }; + // Standardize path names to ensure everything has one. base.setName = (item) => { // eslint-disable-next-line no-param-reassign diff --git a/src/components/core/drawing/cncserver.drawing.colors.js b/src/components/core/drawing/cncserver.drawing.colors.js index dba5f9a..0be7a6c 100644 --- a/src/components/core/drawing/cncserver.drawing.colors.js +++ b/src/components/core/drawing/cncserver.drawing.colors.js @@ -6,105 +6,23 @@ /* eslint-disable no-param-reassign */ const fs = require('fs'); -const glob = require('glob'); const path = require('path'); const nc = require('nearest-color'); -const { Color } = require('paper'); - -// TODO: Store this in presets... -const defaultPenImplement = { - type: 'pen', - manufacturer: 'Sharpie', - name: 'Fine Ultra Fine Point #37000', - width: 0.5, - length: 6.1, - stiffness: 1, - drawLength: 0, - handleWidth: 11, - handleColors: ['#b7b9b8'], -}; - -const defaultBrushImplement = { - type: 'brush', - manufacturer: 'Crayola', - name: 'Size 3 #1127', - width: 3, - length: 10.75, - stiffness: 0.25, - drawLength: 482, - handleWidth: 11, - handleColors: ['#ffff00'], -}; - -// Default assumed color (pen). -const defaultColor = { - id: 'color0', - name: 'Black', - color: '#000000', - weight: 0, - implement: { ...defaultPenImplement, type: 'inherit' }, -}; - -// Default ignored white. -const ignoreWhite = { - id: 'ignore', - name: 'White', - color: '#FFFFFF', - weight: -0.5, // Let other colors select before this. - implement: { - type: 'inherit', - width: 0, - length: 0, - stiffness: 1, - drawLength: 0, - handleColors: ['#000080'], - }, -}; +const { Color, Group } = require('paper'); -// Colorset Tool positions for Crayola default set. -const crayolaPositions = [14.5, 41.5, 66, 91, 116.5, 143, 169, 194]; -const crayolaDefaultTools = crayolaPositions.map((y, index) => ({ - id: `color${index}`, - title: `Color pan position ${index + 1}`, - x: 18.5, - y, - width: 27, - height: 19, - radius: 15, - parent: 'pan', - group: 'Colors', - position: 'center', -})); - -// Default internal preset. -const defaultPreset = { - manufacturer: 'default', - media: 'pen', - machineName: 'default', - weight: -10, - width: 1, // 1mm implement size. - colors: { black: '#000000' }, -}; +const DEFAULT_PRESET = 'default-single-pen'; +const IMPLEMENT_PARENT = '[inherit]'; // Final export object. const colors = { id: 'drawing.colors', - presets: { default: defaultPreset }, set: { // Set via initial preset or colorset loader. name: '', // Machine name for loading/folder storage title: '', // Clean name. description: '', // Description of what it is beyond title. - implement: { - type: 'pen', - width: 1, - length: 0, - stiffness: 1, - drawLength: 0, - handleWidth: 10, - handleColors: ['#000080'], - }, + implement: '', // Implement preset string. items: new Map(), // Items mapped by id. - tools: new Map(), // Tools mapped by id. + toolset: '', // Extra Toolset by machine name. }, }; @@ -114,57 +32,59 @@ const presetDir = path.resolve( ); module.exports = (cncserver) => { - // What is this and why? - // - // When we draw, we assume a color: color0. It's assumed this is "black", but - // if there's only one color in use, no color switching will occur so this - // definition is moot unless we have more than one color in our set. - // - // A "colorset" is a set of colors/implements that can be applied to the available items - // in `cncserver.drawing.colors.set.items` - - // Load all color presets into the presets key. - const files = glob.sync(path.join(presetDir, '*.json')); - files.forEach((setPath) => { - try { - // eslint-disable-next-line global-require, import/no-dynamic-require - const sets = require(setPath); - sets.forEach((set) => { - const key = `${set.manufacturer}-${set.media}-${set.machineName}`; - colors.presets[key] = set; - }); - } catch (error) { - console.error(`Problem loading color preset: '${setPath}'`); - } - }); - - // Function to render presets for the API, translating human readable strings. + // Render presets for the API, translating human readable strings. colors.listPresets = (t) => { + const sets = Object.values(cncserver.utils.getPresets('colorsets')); + const sorted = sets.sort((a, b) => a.sortWeight > b.sortWeight ? 1 : -1); const out = {}; - Object.entries(colors.presets).forEach(([key, p]) => { - const basekey = `colorsets:${p.manufacturer}.${p.machineName}`; - out[key] = { - manufacturer: p.manufacturer, - media: p.media, - machineName: p.machineName, - manufacturerName: t(`${basekey}.manufacturer`), - name: t(`${basekey}.name`), - description: t(`${basekey}.description`), - mediaName: t(`colorsets:media.${p.media}`), - colors: {}, - }; - - Object.entries(p.colors).forEach(([id, color]) => { - out[key].colors[id] = { - color, - name: t(`colorsets:colors.${id}`), - }; - }); + + // Translate strings. + sorted.forEach((set) => { + out[set.name] = colors.translateSet(set, t); }); return out; }; + // List custom/overridden machine names. + colors.customKeys = () => + Object.keys(cncserver.utils.getCustomPresets('colorsets')); + + // List internal machine names. + colors.internalKeys = () => + Object.keys(cncserver.utils.getInternalPresets('colorsets')); + + // Object of preset keys with colorset toolset tool parents unavailable to this bot. + colors.invalidPresets = () => { + const out = {}; + const sets = colors.listPresets(); + const invalidToolsets = cncserver.tools.invalidPresets(); + + // Move through all sets, report invalid toolsets + Object.entries(sets).forEach(([name, { toolset }]) => { + if (toolset in invalidToolsets) { + out[name] = invalidToolsets[toolset]; + } + }); + + return out; + }; + + // Fully translate a given non-map based colorset. + colors.translateSet = (set, t = t => t) => { + if (set) { + set.title = t(set.title); + set.description = t(set.description); + set.manufacturer = t(set.manufacturer); + + set.items.forEach((item) => { + item.name = t(item.name); + }); + } + + return set; + }; + /** * Get a flat list of valid colorset key ids. * @@ -173,15 +93,39 @@ module.exports = (cncserver) => { */ colors.getIDs = () => Array.from(colors.set.items.keys()); + // Make changes to the colorset object. colors.editSet = (set) => new Promise((resolve, reject) => { delete set.items; - delete set.tools; + + // Enforce clean machine name. + set.name = cncserver.utils.getMachineName(set.name, 64); colors.set = cncserver.utils.merge(colors.set, set); colors.saveCustom(); resolve(); }); + // Take in a colorset and convert the items/tools to a map + colors.setAsMap = (set = {}) => ({ + ...set, + items: cncserver.utils.arrayToIDMap(set.items), + }); + + // Return a flat array version of the set. + colors.setAsArray = (set = colors.set) => ({ + ...set, + items: Array.from(set.items.values()), + }); + + // Load and return a correctly mapped colorset from a preset name. + // Pass translate function to rewrite translatable fields. + colors.getPreset = (presetName, t) => { + const { utils } = cncserver; + const set = colors.translateSet(utils.getPreset('colorsets', presetName), t); + return set ? colors.setAsMap(set) : set; + }; + + // Edit an item. colors.edit = (item) => new Promise((resolve, reject) => { const { id } = item; colors.set.items.set(id, item); @@ -195,7 +139,9 @@ module.exports = (cncserver) => { colors.set.items.delete(id); if (colors.set.items.size === 0) { - colors.set.items.set(defaultColor.id, defaultColor); + // Load in the default 1 color preset. + const color = colors.getPreset(DEFAULT_PRESET).items.get('color0'); + colors.set.items.set(color.id, color); } cncserver.sockets.sendPaperUpdate(); colors.saveCustom(); @@ -203,6 +149,7 @@ module.exports = (cncserver) => { // Add a color by all of its info, appended to the end. colors.add = ({ id, ...item }) => new Promise((resolve, reject) => { + id = cncserver.utils.getMachineName(id, 64); if (!colors.getColor(id)) { colors.set.items.set(id, { id, ...item }); cncserver.sockets.sendPaperUpdate(); @@ -215,25 +162,36 @@ module.exports = (cncserver) => { } }); - // Get a renderable color from a tool name. 'transparent' if no match. colors.getToolColor = (name) => { const item = colors.set.items.get(name); return item ? item.color : 'transparent'; }; + // Get the full implement object for a given colorset item id. + colors.getItemImplement = (id) => { + const item = colors.set.items.get(id); + const preset = item.implement === IMPLEMENT_PARENT + ? colors.set.implement : item.implement; + return cncserver.drawing.implements.get(preset); + }; + // Get non-reference copy of colorset item by id. - colors.getColor = (id, applyInheritance = false) => { + colors.getColor = (id, applyInheritance = false, withImplement = false) => { + const { implements } = cncserver.drawing; const rawItem = colors.set.items.get(id); let item = null; if (rawItem) { item = { ...rawItem }; - item.implement = { ...rawItem.implement }; // If the implementor wants it, and the item wants inheritance... - if (applyInheritance && item.implement.type === 'inherit') { - item.implement = { ...colors.set.implement }; + if (item.implement === IMPLEMENT_PARENT) { + if (applyInheritance) { + item.implement = withImplement ? implements.get(colors.set.implement) : colors.set.implement; + } + } else if (withImplement) { + item.implement = implements.get(item.implement); } } @@ -241,7 +199,7 @@ module.exports = (cncserver) => { }; /** - * Mutate the set array to match a preset by machine name. + * Mutate the set object to match a preset by machine name. * * @param {string} presetName * @@ -249,132 +207,40 @@ module.exports = (cncserver) => { * Null for failure, true if success. */ colors.applyPreset = (presetName, t) => new Promise((resolve, reject) => { - const preset = colors.setFromPreset(presetName, t); - if (preset) { - colors.set = preset; + const { utils } = cncserver; + const set = colors.getPreset(presetName); + if (set) { + colors.set = set; cncserver.tools.sendUpdate(); resolve(); } else { - const validOptions = Object.keys(colors.presets).join(', '); - reject(new Error( - cncserver.utils.singleLineString`Preset with id of '${presetName}' not found - in preset list. Must be one of [${validOptions}]` - )); + const err = new Error( + cncserver.utils.singleLineString`Preset with machine name ID '${presetName}' not + found or failed to load.` + ); + err.allowedValues = Object.keys(utils.getPresets('colorsets')); + reject(err); } }); - /** - * Get colorset array from a preset name. - * - * @param {string} presetName - * - * @returns {array} - * Colorset style array with default toolnames - */ - colors.setFromPreset = (presetName, t = s => s) => { - const preset = colors.presets[presetName]; - const { schemas } = cncserver; - if (preset) { - const isPen = preset.media === 'pen'; - const presetInfo = colors.listPresets(t)[presetName]; - const colorset = schemas.getDataDefault('colors', { - name: presetName, - title: presetInfo.name, - description: presetInfo.description, - implement: { ...isPen ? defaultPenImplement : defaultBrushImplement }, - }); - - // Load in the default expected tools for watercolors if not a pen. - colorset.tools = new Map(); - if (!isPen) { - crayolaDefaultTools.forEach((tool) => { - colorset.tools.set(tool.id, tool); - }); - } - - // Build colorset item map. No implement overrides needed. - colorset.items = new Map(); - Object.entries(preset.colors).forEach(([name, color]) => { - const id = `color${colorset.items.size}`; - colorset.items.set(id, schemas.getDataDefault('color', { - id, - name: t(`colorsets:colors.${name}`), - color, - })); - }); - - // TODO: Allow this to be set somewhere? - colorset.items.set(ignoreWhite.id, { ...ignoreWhite }); - return colorset; - } - - return null; - }; - - // Load custom from given machine name. - colors.loadCustom = (name) => { - - }; - - // Get the path to the json file for a given custom colorset. - colors.getCustomSetPath = (name) => { - const sets = cncserver.utils.getUserDir('colorsets'); - return path.join(sets, `${name}.json`); - }; - // Save custom from set. colors.saveCustom = () => { - const dest = colors.getCustomSetPath(colors.set.name); - - // Write the file with version header. - fs.writeFileSync( - dest, - JSON.stringify({ cncserverColorset: 'v3', ...colors.getCurrentSet() }, null, 2) - ); + cncserver.utils.savePreset('colorsets', colors.setAsArray()); }; // Get the current colorset as a JSON ready object. - colors.getCurrentSet = (t = s => s) => { - const set = { - ...colors.set, - title: t(colors.set.title), - description: t(colors.set.description), - items: [], - tools: [], - }; - - // Convert items map to array. - Array.from(colors.set.items.values()).forEach(item => { - set.items.push({ ...item, name: t(item.name) }); - }); - - // Convert tools map to array. - Array.from(colors.set.tools.values()).forEach(tool => { - set.tools.push({ ...tool }); - }); - - return set; - }; + colors.getCurrentSet = t => colors.translateSet(colors.setAsArray(), t); /** * Run at setup, allows machine specific colorset defaults. */ colors.setDefault = () => { + const { utils, binder } = cncserver; // Trigger on schema loaded for schema & validation defaults. - cncserver.binder.bindTo('schemas.loaded', colors.id, () => { - let defaultSet = cncserver.schemas.getDataDefault('colors', { - name: 'default', - title: 'Default Set', - description: '', - implement: { - type: 'pen', - width: defaultPreset.width, - }, - }); - defaultSet.items = new Map([['color0', defaultColor], ['ignore', ignoreWhite]]); - - defaultSet = cncserver.binder.trigger('colors.setDefault', defaultSet); - colors.set = defaultSet; + binder.bindTo('schemas.loaded', colors.id, () => { + let defaultSet = utils.getPreset('colorsets', 'default-single-pen'); + defaultSet = binder.trigger('colors.setDefault', defaultSet); + colors.set = colors.setAsMap(defaultSet); cncserver.binder.trigger('tools.update'); }); }; @@ -385,9 +251,10 @@ module.exports = (cncserver) => { }); // Figure out if we should even parse color work + // TODO: this kinda sucks. colors.doColorParsing = () => { const items = {}; - colors.set.forEach((item) => { + colors.set.items.forEach((item) => { if (item.id !== 'ignore') { items[item.id] = true; } @@ -395,19 +262,32 @@ module.exports = (cncserver) => { return Object.keys(items).length > 1; }; - // Get a luminosity sorted list of colors. - colors.getSortedSet = () => Array.from(colors.set.items.values()).sort( - (a, b) => new Color(b.color).gray - new Color(a.color).gray + // Apply luminosity sorting (for presets without weighting info). + colors.applyDefaultPrintSorting = (set) => { + const luminositySorted = Array.from(set.items.values()).sort((a, b) => + new Color(b.color).gray - new Color(a.color).gray + ); + + let weight = -15; + luminositySorted.forEach(({ id }) => { + weight = weight + 3; + set.items.get(id).printWeight = weight; + }); + }; + + // Get the print weight sorted list of colors. + colors.getSortedSet = (set = colors.set) => Array.from(set.items.values()).sort( + (a, b) => b.printWeight - a.printWeight ); - // Get an object keyed by color work ID, ordered by luminosity light->dark. + // Get an object keyed by color work ID, ordered by print weight of empty arrays. colors.getWorkGroups = () => { const groups = {}; - const sorted = colors.getSortedSet(); + const sorted = colors.getSortedSet().reverse(); - sorted.forEach((color) => { - if (color.id !== 'ignore') { - groups[color.id] = []; + sorted.forEach(({ id }) => { + if (id !== 'ignore') { + groups[id] = []; } }); return groups; @@ -424,10 +304,26 @@ module.exports = (cncserver) => { colors.set.items.forEach(({ id, color }) => { c[id] = color; }); - + const { layers } = cncserver.drawing.base; const nearestColor = nc.from(c); + + // Remove everything on print, rebuild it from here. + layers.print.removeChildren(); + + // Setup all the destination groups within layers. + const sorted = colors.getSortedSet().reverse(); + const printGroups = {}; // ID keyed set of Paper Groups. + const colorsetItems = {}; // Static cache of items in groups. + sorted.forEach(({ id }) => { + printGroups[id] = new Group({ name: id }); + layers.print.addChild(printGroups[id]); + colorsetItems[id] = colors.getColor(id, true, true); + }); + + // Move through all preview groups, then all items within them. layer.children.forEach((group) => { group.children.forEach((item) => { + if (item.strokeColor) { // If we've never touched this path before, save the original color. if (!item.data.originalColor) { @@ -436,31 +332,36 @@ module.exports = (cncserver) => { // Find nearest color. const nearest = nearestColor(item.data.originalColor.toCSS(true)); - const colorsetItem = colors.getColor(nearest.name, true); - - // If item matched to "ignore", hide it. - if (nearest.name === 'ignore') { - item.strokeWidth = 0; - } else { - // Match colorset item effective implement size. - item.strokeWidth = colorsetItem.implement.width; - - // Save/set new color and matched ID. - // IMPORTANT: This is how tool set swaps are rendered. - item.data.colorID = nearest.name; - item.strokeColor = nearest.value; - - // Assume less than full opacity with brush/watercolor paintings. - item.opacity = colorsetItem.implement.type === 'brush' ? 0.8 : 1; - - // Prevent - item.strokeCap = 'round'; - item.strokeJoin = 'round'; - } + colors.applyPreview(item, colorsetItems[nearest.name]); + printGroups[nearest.name].addChild(item.clone()); } }); }); }; + // Apply preview styles to an item. + colors.applyPreview = (item, color) => { + // If item matched to "ignore", hide it. + if (color.id === 'ignore') { + item.strokeWidth = 0; + } else { + // Match colorset item effective implement size. + item.strokeWidth = color.implement.width; + + // Save/set new color and matched ID. + // IMPORTANT: This is how tool set swaps are rendered. + // TODO: Set swaps from print groupings. + //item.data.colorID = colorID; + item.strokeColor = color.color; + + // Assume less than full opacity with brush/watercolor paintings. + item.opacity = color.implement.type === 'brush' ? 0.8 : 1; + + // Prevent sharp corners messing up render. + item.strokeCap = 'round'; + item.strokeJoin = 'round'; + } + }; + return colors; }; diff --git a/src/components/core/drawing/cncserver.drawing.implements.js b/src/components/core/drawing/cncserver.drawing.implements.js index 102e8d0..e559834 100644 --- a/src/components/core/drawing/cncserver.drawing.implements.js +++ b/src/components/core/drawing/cncserver.drawing.implements.js @@ -8,10 +8,13 @@ const implements = { }; module.exports = (cncserver) => { - // List Existing Presets. implements.listPresets = (t, customOnly) => { - const items = Object.values(cncserver.utils.getPresets('implements', customOnly)); + const { utils } = cncserver; + const sets = customOnly + ? utils.getCustomPresets('implements') + : utils.getPresets('implements'); + const items = Object.values(sets); const sorted = items.sort((a, b) => a.sortWeight > b.sortWeight ? 1 : -1); const out = {}; @@ -22,6 +25,14 @@ module.exports = (cncserver) => { return out; }; + // List custom/overridden machine names. + implements.customKeys = () => + Object.keys(cncserver.utils.getCustomPresets('implements')); + + // List internal machine names. + implements.internalKeys = () => + Object.keys(cncserver.utils.getInternalPresets('implements')); + // Get a single preset directly from the files. implements.get = (name, customOnly) => cncserver.utils.getPreset('implements', name, customOnly); diff --git a/src/components/core/drawing/colorsets/crayola.json b/src/components/core/drawing/colorsets/crayola.json deleted file mode 100644 index 7b4b589..0000000 --- a/src/components/core/drawing/colorsets/crayola.json +++ /dev/null @@ -1,52 +0,0 @@ -[{ - "manufacturer": "crayola", - "media": "watercolor", - "machineName": "secondary", - "weight": 1, - "colors": { - "maroon": "#bb39b9", - "orangered": "#e14a35", - "tangerine": "#f3e730", - "yellowgreen": "#b5d02e", - "teal": "#2ed07e", - "skyblue": "#53bee7", - "indigo": "#535ee7", - "white": "#EEE" - } -}, - -{ - "manufacturer": "crayola", - "media":"watercolor", - "machineName": "tertiary", - "weight": 5, - "colors": { - "grey": "#716d78", - "pink": "#ff61bc", - "salmon": "#ffb260", - "lemon": "#ffff30", - "lime": "#b2f743", - "turquoise": "#00c0a3", - "fuchsia": "#d55ce9", - "beige": "#de896d" - } -}, - -{ - "manufacturer": "crayola", - "media":"pen", - "machineName": "classic", - "weight": 5, - "colors": { - "brown":"#8B4513", - "purple":"#800080", - "red":"#FF0000", - "orange":"#FFA500", - "yellow":"#FFFF00", - "green":"#008000", - "blue":"#0000FF", - "black":"#000000", - "pink": "#ff61bc", - "grey": "#716d78" - } -}] diff --git a/src/components/core/drawing/colorsets/generic.json b/src/components/core/drawing/colorsets/generic.json deleted file mode 100644 index aac34b8..0000000 --- a/src/components/core/drawing/colorsets/generic.json +++ /dev/null @@ -1,16 +0,0 @@ -[{ - "manufacturer": "generic", - "media": "watercolor", - "machineName": "generic", - "weight": -10, - "colors": { - "black":"#000000", - "red":"#FF0000", - "orange":"#FFA500", - "yellow":"#FFFF00", - "green":"#008000", - "blue":"#0000FF", - "purple":"#800080", - "brown":"#8B4513" - } -}] diff --git a/src/components/core/drawing/colorsets/prang.json b/src/components/core/drawing/colorsets/prang.json deleted file mode 100644 index e6bddd1..0000000 --- a/src/components/core/drawing/colorsets/prang.json +++ /dev/null @@ -1,48 +0,0 @@ -[{ - "manufacturer": "prang", - "media": "watercolor", - "machineName": "glitter", - "weight": 11, - "colors": { - "gold":"#aba66d", - "silver":"#b1bcac", - "lilac":"#cdb2d0", - "sapphire":"#32c0c4", - "emerald":"#2ca440", - "canary":"#dede5d", - "tangerine":"#efa038", - "ruby":"#f0846c" - } -}, -{ - "manufacturer": "prang", - "media": "watercolor", - "machineName": "metallic", - "weight": 12, - "colors": { - "gold":"#d9bf81", - "silver":"#569aaa", - "copper":"#a74837", - "blue":"#0082ad", - "green":"#00ae4d", - "yellow":"#ffe30c", - "orange":"#f35400", - "red":"#c0080b" - } -}, -{ - "manufacturer": "prang", - "media": "watercolor", - "machineName": "secondary", - "weight": 10, - "colors": { - "vermilion":"#ff5a59", - "yelloworange":"#ffa300", - "yellowgreen":"#9aea24", - "bluegreen":"#009aa6", - "turquoiseblue":"#00aadf", - "blueviolet":"#00aadf", - "redviolet":"#d842ce", - "white":"#eee" - } -}] diff --git a/src/components/core/drawing/colorsets/sargent.json b/src/components/core/drawing/colorsets/sargent.json deleted file mode 100644 index 64936e9..0000000 --- a/src/components/core/drawing/colorsets/sargent.json +++ /dev/null @@ -1,16 +0,0 @@ -[{ - "manufacturer": "sargent", - "media": "watercolor", - "machineName": "secondary", - "weight": 20, - "colors": { - "yelloworange":"#ffbe2c", - "vermilion":"#ff451f", - "redviolet":"#c02987", - "blueviolet":"#212aa9", - "green":"#a3d130", - "teal":"#00a6b8", - "teal2":"#00a6b8", - "white":"#eee" - } -}] diff --git a/src/components/core/drawing/index.js b/src/components/core/drawing/index.js index 6b82cba..57710a3 100644 --- a/src/components/core/drawing/index.js +++ b/src/components/core/drawing/index.js @@ -15,6 +15,7 @@ module.exports = (cncserver) => { drawing.text = require('./cncserver.drawing.text.js')(cncserver, drawing); drawing.accell = require('./cncserver.drawing.accell.js')(cncserver, drawing); drawing.colors = require('./cncserver.drawing.colors.js')(cncserver, drawing); + drawing.implements = require('./cncserver.drawing.implements.js')(cncserver, drawing); drawing.stage = require('./cncserver.drawing.stage.js')(cncserver, drawing); drawing.preview = require('./cncserver.drawing.preview.js')(cncserver, drawing); drawing.temp = require('./cncserver.drawing.temp.js')(cncserver, drawing); diff --git a/src/components/machine_support/cncserver.bots.watercolorbot.js b/src/components/machine_support/cncserver.bots.watercolorbot.js index 56ff814..e1645ef 100644 --- a/src/components/machine_support/cncserver.bots.watercolorbot.js +++ b/src/components/machine_support/cncserver.bots.watercolorbot.js @@ -74,15 +74,15 @@ module.exports = (cncserver) => { // Run a full wash in all the waters. watercolorbot.fullWash = () => { // TODO: Fix water0 overreach - cncserver.tools.set('water0dip'); - cncserver.tools.set('water1'); - cncserver.tools.set('water2'); + cncserver.tools.changeTo('water0dip'); + cncserver.tools.changeTo('water1'); + cncserver.tools.changeTo('water2'); }; // Reink with a water dip. watercolorbot.reink = (tool = cncserver.pen.state.tool) => { - cncserver.tools.set('water0dip'); - cncserver.tools.set(tool); + cncserver.tools.changeTo('water0dip'); + cncserver.tools.changeTo(tool); }; // Bind the wiggle to the toolchange event. @@ -166,7 +166,11 @@ module.exports = (cncserver) => { }); // Bind to color setdefault to set watercolors - cncserver.binder.bindTo('colors.setDefault', watercolorbot.id, passthroughSet => (watercolorbot.inUse ? cncserver.drawing.colors.setFromPreset('generic-watercolor-generic') : passthroughSet)); + cncserver.binder.bindTo('colors.setDefault', watercolorbot.id, passthroughSet => ( + watercolorbot.inUse + ? cncserver.utils.getPreset('colorsets', 'generic-watercolor-generic') + : passthroughSet + )); return watercolorbot; diff --git a/src/components/third_party/scratch/cncserver.scratch.js b/src/components/third_party/scratch/cncserver.scratch.js index a103297..c2932cc 100644 --- a/src/components/third_party/scratch/cncserver.scratch.js +++ b/src/components/third_party/scratch/cncserver.scratch.js @@ -163,8 +163,8 @@ module.exports = (cncserver) => { turtle.distanceCounter = 0; // Reink procedure! - cncserver.tools.set('water0dip'); // Dip in the water - cncserver.tools.set(turtle.media); // Apply the last saved media + cncserver.tools.changeTo('water0dip'); // Dip in the water + cncserver.tools.changeTo(turtle.media); // Apply the last saved media cncserver.control.movePenAbs(turtle); // Move back to "current" position cncserver.pen.setHeight('draw'); // Set the position back to draw } @@ -214,9 +214,9 @@ module.exports = (cncserver) => { // Run simple wash if (op === 'wash') { - cncserver.tools.set('water0'); - cncserver.tools.set('water1'); - cncserver.tools.set('water2'); + cncserver.tools.changeTo('water0'); + cncserver.tools.changeTo('water1'); + cncserver.tools.changeTo('water2'); } // Turn off motors and zero to park pos @@ -246,7 +246,7 @@ module.exports = (cncserver) => { // Set by ID (water/color) if (type) { const tool = type + parseInt(req.params.id, 10); - cncserver.tools.set(tool); + cncserver.tools.changeTo(tool); turtle.media = tool; } diff --git a/src/interface/lib/cncserver.client.api.js b/src/interface/lib/cncserver.client.api.js index 28f87e7..3880b4b 100644 --- a/src/interface/lib/cncserver.client.api.js +++ b/src/interface/lib/cncserver.client.api.js @@ -167,9 +167,10 @@ cncserver.api = { stat: () => _get('colors'), preset: preset => _post('colors', { data: { preset } }), deletePreset: preset => _delete('colors', { data: { preset } }), + get: id => _get(`colors/${id}`), add: data => _post('colors', { data }), - edit: data => _patch('colors', { data }), - save: data => _put(`colors/${data.id}`, { data }), + editSet: data => _patch('colors', { data }), + save: data => _patch(`colors/${data.id}`, { data }), delete: id => _delete(`colors/${id}/`), schema: () => _options('colors'), }, From 5699244d3441d031be91ba2b23ebce8284b67f59 Mon Sep 17 00:00:00 2001 From: techninja Date: Thu, 1 Oct 2020 15:44:53 -0700 Subject: [PATCH 062/147] WIP working Colorset UI components for new colorset API work - Full colorset management ui using sliders and slide pages - Select/edit presets, implements, and more - Special schema form selection widgets for toolset and implement - --- .../modules/components/elements/color-set.mjs | 104 +++++++++++++ .../modules/components/elements/index.mjs | 2 + .../components/elements/schema-form.mjs | 9 +- .../components/elements/tool-implement.mjs | 46 +++++- .../components/panels/toolbar-bottom.mjs | 4 +- .../widgets/canvas/canvas-compose.mjs | 6 +- .../widgets/canvas/canvas-print.mjs | 20 ++- .../widgets/colorsets/colorset-colors.mjs | 67 ++++----- .../widgets/colorsets/colorset-edit-color.mjs | 65 +++----- .../colorsets/colorset-edit-implement.mjs | 10 +- .../widgets/colorsets/colorset-edit-set.mjs | 51 ++++--- .../widgets/colorsets/colorset-editor.mjs | 59 ++++---- .../widgets/colorsets/colorset-presets.mjs | 141 +++++++++++++++++- .../modules/components/widgets/index.mjs | 8 + .../preset-select/preset-select-implement.mjs | 98 ++++++++++++ .../preset-select/preset-select-toolset.mjs | 81 ++++++++++ 16 files changed, 624 insertions(+), 147 deletions(-) create mode 100644 src/interface/modules/components/elements/color-set.mjs create mode 100644 src/interface/modules/components/widgets/preset-select/preset-select-implement.mjs create mode 100644 src/interface/modules/components/widgets/preset-select/preset-select-toolset.mjs diff --git a/src/interface/modules/components/elements/color-set.mjs b/src/interface/modules/components/elements/color-set.mjs new file mode 100644 index 0000000..8a3672d --- /dev/null +++ b/src/interface/modules/components/elements/color-set.mjs @@ -0,0 +1,104 @@ +/** + * @file Color set/preset display element definition. + */ +import { html, svg } from '/modules/hybrids.js'; + +export default () => ({ + colors: 'black', + labels: 'Black', + display: 'round', // 'round', or 'line'. + width: 55, + + render: ({ colors, labels, width, display }) => { + colors = colors.split(','); + labels = labels.split(','); + + const spots = []; + let spotWidth = 0; + if (display === 'round') { + // Build out positions on a circle. + const radius = width / 2; + spotWidth = colors.length === 1 ? radius : radius * ((Math.PI * 2) / colors.length); + colors.forEach((color, index) => { + const angle = (Math.PI * 2) * (index / colors.length) - Math.PI; + + const x = Math.round(Math.cos(angle) * radius); + const y = Math.round(Math.sin(angle) * radius); + const style = { + backgroundColor: color, + left: `${x + radius - spotWidth / 2}px`, + top: `${y + radius - spotWidth / 2}px`, + }; + + spots.push(html` +
+ `); + }); + } else if (display === 'line') { + spotWidth = width / colors.length; + colors.forEach((color, index) => { + const style = { backgroundColor: color }; + spots.push(html` +
+ `); + }); + } + + return html` + +
+ +
+ ${spots.map(spot => spot)} +
+
+ `; + }, +}); diff --git a/src/interface/modules/components/elements/index.mjs b/src/interface/modules/components/elements/index.mjs index 18196ad..993c845 100644 --- a/src/interface/modules/components/elements/index.mjs +++ b/src/interface/modules/components/elements/index.mjs @@ -12,6 +12,7 @@ import MainTitle from './main-title.mjs'; import PaperCanvas from './paper-canvas.mjs'; import SchemaForm from './schema-form.mjs'; import ToolImplement from './tool-implement.mjs'; +import ColorSet from './color-set.mjs'; export default styles => ({ 'tab-item': TabItem(styles), @@ -25,4 +26,5 @@ export default styles => ({ 'paper-canvas': PaperCanvas(), 'schema-form': SchemaForm(styles), 'tool-implement': ToolImplement(styles), + 'color-set': ColorSet(styles), }); diff --git a/src/interface/modules/components/elements/schema-form.mjs b/src/interface/modules/components/elements/schema-form.mjs index 644c5b2..ed353dd 100644 --- a/src/interface/modules/components/elements/schema-form.mjs +++ b/src/interface/modules/components/elements/schema-form.mjs @@ -162,7 +162,14 @@ function externalUpdateForm(host) { // Ensure presets have correct selections. matchingItems(host, host.presetPaths, item => { - item.querySelector('.preset-selector').selected = item.querySelector('input').value; + const selector = item.querySelector('.preset-selector') + selector.selected = item.querySelector('input').value; + + // Try to get parent preset option (currently only for implements). + if (selector.allowInherit) { + selector.parentPreset = host.parentNode.host.parentImplement || ''; + selector.color = host.parentNode.host?.data?.color || '#000000'; + } }); } diff --git a/src/interface/modules/components/elements/tool-implement.mjs b/src/interface/modules/components/elements/tool-implement.mjs index 222ff8f..30d2ba2 100644 --- a/src/interface/modules/components/elements/tool-implement.mjs +++ b/src/interface/modules/components/elements/tool-implement.mjs @@ -1,7 +1,27 @@ /** * @file Implement display element definition. */ +/* globals cncserver */ import { html, svg } from '/modules/hybrids.js'; +import apiInit from '/modules/utils/api-init.mjs'; + + +function setHostFromPreset(host, preset) { + apiInit(() => { + cncserver.api.implements.get(preset).then(({ data }) => { + data.title = `${data.manufacturer} ${data.title}`; + delete data.sortWeight; + delete data.manufacturer; + Object.entries(data).forEach(([key, value]) => { + if (key === 'handleColors') data[key] = data[key].join(','); + host[key] = data[key]; + }); + }).catch((err) => { + // Reset the preset if there was any error. + host.preset = ''; + }); + }); +} export default () => ({ // UI configuration specifics. @@ -10,6 +30,13 @@ export default () => ({ bracketSize: 8, bracketNotch: 5, bracketPadding: 5, + initialized: false, + + // Switch to true for no labels/brackets. + plain: false, + + // Text name of preset to load dynamically. + preset: { observe: setHostFromPreset }, // Implement specifics. handleColors: 'yellow', @@ -26,6 +53,7 @@ export default () => ({ bracketSize, bracketNotch, bracketPadding, + plain, // Implement specifics. handleColors, @@ -48,8 +76,8 @@ export default () => ({ // Setup basic layout. const handle = { - left: 50, - top: 30, + left: plain ? 10 : 50, + top: plain ? 10 : 30, width: handleWidth * scale, height: handleHeight, }; @@ -62,7 +90,7 @@ export default () => ({ }; // TODO: Support larger scales without hard coded values. - const textHeight = 2.2 * scale; + const textHeight = plain ? 0 : 2.2 * scale; const center = brush.left + brush.width / 2; const largerWidth = brush.width > handle.width ? brush.width / 2 : handle.width / 2; const minLength = type === 'pen' ? 5 : 2; @@ -160,11 +188,13 @@ export default () => ({ }; // Set SVG viewbox. + // TODO: Do a better job at figuring out how much space is being used up. + const extraWidth = plain ? 0 : 50; const viewBox = [ 0, 0, - 160, // Width. + handle.left + largerWidth + extraWidth + bracketSize * 2, // Width. handle.top + handle.height + brush.height + bracketSize * 2 - + bracketNotch * 2 + bracketPadding + textHeight * 2, // Height. + + bracketNotch * 2 + bracketPadding + textHeight * 2, // Height. ]; return html` @@ -197,6 +227,7 @@ export default () => ({ + ${!plain && svg` ({ + `} ${handleColors.map((handleColor, index) => svg` ({ - ${type !== 'pen' && svg` + ${type !== 'pen' && !plain && svg` ({ `} + ${!plain && svg` ({ + `} `} `; diff --git a/src/interface/modules/components/panels/toolbar-bottom.mjs b/src/interface/modules/components/panels/toolbar-bottom.mjs index c601888..b782ef4 100644 --- a/src/interface/modules/components/panels/toolbar-bottom.mjs +++ b/src/interface/modules/components/panels/toolbar-bottom.mjs @@ -12,14 +12,14 @@ export default styles => ({ text="Render Stage" icon="hammer" style="secondary" - onclick="cncserver.api.projects.renderStage(); document.querySelector('draw-preview').layer = 'preview'" + onclick="cncserver.api.projects.renderStage()" >
{ // Tell the paper canvas to watch for updates on these layers. - host.canvas.scope.watchUpdates(['stage', 'preview']); + host.canvas.scope.watchUpdates(['stage', 'preview', 'print']); // Get the bot size to apply to the canvas. cncserver.api.settings.bot().then(({ data: bot }) => { @@ -76,7 +76,7 @@ function init(host, { detail }) { // Initialize the paper-canvas with bot size details. host.canvas.scope.paperInit({ size: new paper.Size(bot.maxAreaMM), - layers: ['draw', 'stage', 'preview'], + layers: ['draw', 'stage', 'preview', 'print'], workspace, }).then(() => { // Bind the tools for the canvas. @@ -114,6 +114,8 @@ export default (styles) => ({ + + { @@ -148,11 +148,25 @@ function init(host, { detail }) { // Initialize the paper-canvas with bot size details. host.canvas.scope.paperInit({ size: new paper.Size(bot.maxAreaMM), - layers: ['preview', 'tools'], + layers: ['print', 'tools'], workspace, }).then(() => { initPrint(host); }); + + // TODO: Find out where this lives. + // Catch when it's time to manually swap pen over. + cncserver.socket.on('manualswap trigger', ({ index }) => { + + cncserver.api.colors.get(index).then(({ data: { name } }) => { + const message = `We are now ready to draw with ${name}. When it's in and ready, click ok.`; + + // eslint-disable-next-line no-alert + if (window.confirm(message)) { + cncserver.api.tools.change('manualresume'); + } + }); + }); }); }); } diff --git a/src/interface/modules/components/widgets/colorsets/colorset-colors.mjs b/src/interface/modules/components/widgets/colorsets/colorset-colors.mjs index d295ef2..67c3ce6 100644 --- a/src/interface/modules/components/widgets/colorsets/colorset-colors.mjs +++ b/src/interface/modules/components/widgets/colorsets/colorset-colors.mjs @@ -4,24 +4,15 @@ import { handleSwitch } from './pane-utils.mjs'; import { html } from '/modules/hybrids.js'; -// TODO: -// - Use Schema form to build out the rest of the controls here. -// - Figure out some way to manage what the back button does based on how it was switched to. -// - Figure out some way to get data into the forms when you land there -// - Build a new slide for tool management -// - Build a preset loader (including getting back to custom ones). -// - Customize the implement form and add a display for brush/pen visualization. -// - Finish building the custom colorset saver. -// - Fix the incredibly ugly padding issue. General styles? export default styles => ({ - colors: [], - implement: {}, // Parent level implement. set: {}, + items: [], + parentImplement: '', // Default parent level implement. name: '', description: '', render: ({ - colors, name, description, implement, set, + items, name, description, parentImplement, set, }) => html` ${styles} + -
- Default Implement: - - - - -
+ + `, }); diff --git a/src/interface/modules/components/widgets/colorsets/colorset-editor.mjs b/src/interface/modules/components/widgets/colorsets/colorset-editor.mjs index d953531..2d65f73 100644 --- a/src/interface/modules/components/widgets/colorsets/colorset-editor.mjs +++ b/src/interface/modules/components/widgets/colorsets/colorset-editor.mjs @@ -7,38 +7,35 @@ import apiInit from '/modules/utils/api-init.mjs'; import { html, dispatch } from '/modules/hybrids.js'; import { applyProps } from './pane-utils.mjs'; +function loadColors(host) { + cncserver.api.colors.stat().then(({ data }) => { + host.set = data.set; + host.setTitle = data.set.title; + host.setDescription = data.set.description; + host.parentImplement = data.set.implement; + host.items = [...data.set.items]; + }); +} + // Catch the first render of a host element, and dispatch refresh. function init(host) { apiInit(() => { if (!host.initialized) { host.initialized = true; dispatch(host, 'init'); - - cncserver.api.colors.stat().then(({ data }) => { - host.set = data.set; - host.setTitle = data.set.title; - host.setDescription = data.set.description; - - host.colors = [...data.set.items]; - host.implement = data.set.implement; - - host.colors.forEach((item) => { - item.type = item.implement.type === 'inherit' - ? data.set.implement.type : item.implement.type; - }); - - }); + loadColors(host); } }); } -// TODO: -// - Implement paged editor interface -// - Colorset item viewer, with bot tool matched positions. -// - Colorset item editor for all fields except... -// - Implement editor from colorset level/item level -// - Implement preset loader for both internal and custom presets. - +/** + * Individual sub-element host onSwitchPane event callback. + * + * @param {Hybrids} host + * This host element. + * @param {Event} { detail: { destination, options } } + * DOM Event object including "event.target" of source element. + */ function switchPane(host, { detail: { destination, options } }) { // Actually switch to the detination slide. host.slide = destination; @@ -46,21 +43,27 @@ function switchPane(host, { detail: { destination, options } }) { // Find the first child of the destination to set its data. const dest = host.shadowRoot.querySelector(`[name=${destination}] > *`); + // Reload colors? + if (destination === 'colors' && options.reload) { + loadColors(host); + } + + console.log('Switch', options); // Apply and destination props set in switch options. if (options.destProps) applyProps(dest, options.destProps); } export default styles => ({ - colors: [], - implement: {}, set: {}, + parentImplement: '', + items: [], setTitle: '', setDescription: '', initialized: false, slide: 'colors', render: ({ - setTitle, setDescription, colors, slide, implement, set, + setTitle, setDescription, items, slide, parentImplement, set, }) => html` ${styles} @@ -77,12 +80,12 @@ export default styles => ({ diff --git a/src/interface/modules/components/widgets/colorsets/colorset-presets.mjs b/src/interface/modules/components/widgets/colorsets/colorset-presets.mjs index b900ac5..90b43e0 100644 --- a/src/interface/modules/components/widgets/colorsets/colorset-presets.mjs +++ b/src/interface/modules/components/widgets/colorsets/colorset-presets.mjs @@ -1,18 +1,155 @@ /** * @file Colorset Editor: preset loader element definition. */ +/* globals cncserver */ +import apiInit from '/modules/utils/api-init.mjs'; import { html } from '/modules/hybrids.js'; import { handleSwitch } from './pane-utils.mjs'; +function selectPreset(name) { + return (host) => { + cncserver.api.colors.preset(name).then(() => { + handleSwitch('colors', { reload: true })(host); + refreshData(host); + }); + }; +} + +/** + * Delete a given preset. + * + * @param {string} name + * Name of preset to delete. Will only work on valid custom presets. + */ +function deletePreset(name) { + // TODO: confirm before deletion. + return (host, event) => { + event.stopPropagation(); + cncserver.api.colors.deletePreset(name).then(() => { + refreshData(host); + }); + }; +} + +function getRenderedPresets(host, { set, presets, customs, internals, invalidSets }) { + const validColorsets = []; + const invalidColorsets = []; + Object.entries(presets).forEach(([presetName, preset]) => { + const colors = []; + const labels = []; + const imps = [preset.implement]; + + preset.items.forEach(({ color, name, implement }) => { + colors.push(color); + labels.push(name); + if (implement !== '[inherit]') imps.push(implement); + }); + + const presetInvalid = presetName in invalidSets; + const presetCustom = customs.includes(presetName); + const presetOverridden = internals.includes(presetName) && presetCustom; + const classes = { preset: true, invalid: presetInvalid, active: set.name === presetName }; + const desc = presetInvalid ? Object.values(invalidSets[presetName]).join(', ') : ''; + const build = html` +
+ ${presetCustom && html` + + `} + ${imps.map(implement => html` + + `)} + ${preset.title} + +
+ `; + + // Add to final output. + if (presetInvalid) { + invalidColorsets.push(build); + } else { + validColorsets.push(build) + } + }); + + host.presets = [...validColorsets]; + host.invalidPresets = [...invalidColorsets]; +} + +function refreshData(host) { + cncserver.api.colors.stat().then(({ data }) => { + getRenderedPresets(host, data); + }); +} + +// Catch the first render of a host element, and dispatch refresh. +function init(host) { + apiInit(() => { + if (!host.initialized) { + host.initialized = true; + + // Get all the presets. + refreshData(host); + } + }); +} + export default styles => ({ initialized: false, + presets: [], + invalidPresets: [], - render: () => html` + render: ({ presets, invalidPresets }) => html` ${styles} + -
Presets
+ Internal & Custom Presets +
+ ${presets.map(colorset => colorset)} +
+ + ${invalidPresets.length && html` + Invalid Presets +
+ ${invalidPresets.map(colorset => colorset)} +
+ `} + ${init} `, }); diff --git a/src/interface/modules/components/widgets/index.mjs b/src/interface/modules/components/widgets/index.mjs index ed5b0b2..f64debf 100644 --- a/src/interface/modules/components/widgets/index.mjs +++ b/src/interface/modules/components/widgets/index.mjs @@ -21,6 +21,10 @@ import ColorsetEditImplement from './colorsets/colorset-edit-implement.mjs'; import ColorsetEditSet from './colorsets/colorset-edit-set.mjs'; import ColorsetPresets from './colorsets/colorset-presets.mjs'; +// Preset selection interfaces and associated components. +import PresetSelectImplement from './preset-select/preset-select-implement.mjs'; +import PresetSelectToolset from './preset-select/preset-select-toolset.mjs'; + export default styles => ({ 'height-settings': HeightSettings(styles), 'tools-basic': ToolsBasic(styles), @@ -39,4 +43,8 @@ export default styles => ({ 'colorset-edit-implement': ColorsetEditImplement(styles), 'colorset-edit-set': ColorsetEditSet(styles), 'colorset-presets': ColorsetPresets(styles), + + // Preset Selection widgets. + 'preset-select-implement': PresetSelectImplement(styles), + 'preset-select-toolset': PresetSelectToolset(styles), }); diff --git a/src/interface/modules/components/widgets/preset-select/preset-select-implement.mjs b/src/interface/modules/components/widgets/preset-select/preset-select-implement.mjs new file mode 100644 index 0000000..458cefa --- /dev/null +++ b/src/interface/modules/components/widgets/preset-select/preset-select-implement.mjs @@ -0,0 +1,98 @@ +/** + * @file Preset Selector: Show all presets for implements, allow selection. + */ +/* globals cncserver */ +import { html, dispatch } from '/modules/hybrids.js'; +import apiInit from '/modules/utils/api-init.mjs'; + +function refreshPresets(host) { + cncserver.api.implements.stat().then(({ data }) => { + host.presets = [...Object.values(data.presets)]; + }); +} + +function init(host) { + apiInit(() => { + if (!host.initialized) { + host.initialized = true; + refreshPresets(host); + } + }); +} + +function selectItem(name) { + return (host) => { + host.selected = name; + dispatch(host, 'change'); + }; +} + +export default (styles) => ({ + initialized: false, + allowInherit: false, + selected: '', + color: '#000000', + presets: [], + parentPreset: '', + + render: ({ selected, presets, allowInherit, parentPreset, color }) => html` + ${styles} + + +
+ +
+
+ ${allowInherit && html` +
+ ${parentPreset && html` + + `} +

[Parent Implement]

+
+ `} + ${presets.map((preset) => html` +
+ +

${preset.manufacturer}

${preset.title}
+
+ `)} +
+ ${init} + `, +}); diff --git a/src/interface/modules/components/widgets/preset-select/preset-select-toolset.mjs b/src/interface/modules/components/widgets/preset-select/preset-select-toolset.mjs new file mode 100644 index 0000000..7dd7518 --- /dev/null +++ b/src/interface/modules/components/widgets/preset-select/preset-select-toolset.mjs @@ -0,0 +1,81 @@ +/** + * @file Preset Selector: Show all presets for toolsets, allow selection. + */ +/* globals cncserver */ +import { html, dispatch } from '/modules/hybrids.js'; +import apiInit from '/modules/utils/api-init.mjs'; + +function refreshPresets(host) { + cncserver.api.tools.list().then(({ data }) => { + const presets = Object.values(data.presets); + + presets.forEach(preset => { + if (preset.name in data.invalidSets) { + preset.invalid = Object.values(data.invalidSets[preset.name]).join(', '); + } + }); + + host.presets = [...presets]; + + }); +} + +function init(host) { + apiInit(() => { + if (!host.initialized) { + host.initialized = true; + refreshPresets(host); + } + }); +} + +function selectItem(name) { + return (host) => { + host.selected = name; + dispatch(host, 'change'); + }; +} + +export default (styles) => ({ + initialized: false, + selected: '', + presets: [], + + render: ({ selected, presets }) => html` + ${styles} + + +
+ ${presets.map((preset) => html` +
+

${preset.manufacturer}

+ ${preset.title} +

${preset.description}

+
+ `)} +
+ ${init} + `, +}); From 6f8e903fa7e60fc7bb32702c61c0cad8addee9eb Mon Sep 17 00:00:00 2001 From: techninja Date: Tue, 6 Oct 2020 15:16:52 -0700 Subject: [PATCH 063/147] Add loading modal notifier component --- src/interface/images/loading.svg | 55 ++++++++++++++++++ .../modules/components/elements/index.mjs | 2 + .../components/elements/notify-loading.mjs | 56 +++++++++++++++++++ .../components/elements/tool-implement.mjs | 9 ++- .../components/widgets/content-importer.mjs | 8 +++ 5 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 src/interface/images/loading.svg create mode 100644 src/interface/modules/components/elements/notify-loading.mjs diff --git a/src/interface/images/loading.svg b/src/interface/images/loading.svg new file mode 100644 index 0000000..9cf91cd --- /dev/null +++ b/src/interface/images/loading.svg @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/interface/modules/components/elements/index.mjs b/src/interface/modules/components/elements/index.mjs index 993c845..35bd5bd 100644 --- a/src/interface/modules/components/elements/index.mjs +++ b/src/interface/modules/components/elements/index.mjs @@ -13,6 +13,7 @@ import PaperCanvas from './paper-canvas.mjs'; import SchemaForm from './schema-form.mjs'; import ToolImplement from './tool-implement.mjs'; import ColorSet from './color-set.mjs'; +import NotifyLoading from './notify-loading.mjs'; export default styles => ({ 'tab-item': TabItem(styles), @@ -27,4 +28,5 @@ export default styles => ({ 'schema-form': SchemaForm(styles), 'tool-implement': ToolImplement(styles), 'color-set': ColorSet(styles), + 'notify-loading': NotifyLoading(styles), }); diff --git a/src/interface/modules/components/elements/notify-loading.mjs b/src/interface/modules/components/elements/notify-loading.mjs new file mode 100644 index 0000000..8313954 --- /dev/null +++ b/src/interface/modules/components/elements/notify-loading.mjs @@ -0,0 +1,56 @@ +/** + * @file Generic loading notifier component. + */ +import { html } from '/modules/hybrids.js'; + +export default styles => ({ + icon: '', + style: '', + text: '', + desc: 'Please wait...', + color: '', + opacity: 0.8, + active: false, + debug: false, + + render: ({ icon, desc, text, active, color, opacity }) => html` + ${styles} + + +
+ +
+ `, +}); diff --git a/src/interface/modules/components/elements/tool-implement.mjs b/src/interface/modules/components/elements/tool-implement.mjs index 30d2ba2..233e279 100644 --- a/src/interface/modules/components/elements/tool-implement.mjs +++ b/src/interface/modules/components/elements/tool-implement.mjs @@ -8,6 +8,7 @@ import apiInit from '/modules/utils/api-init.mjs'; function setHostFromPreset(host, preset) { apiInit(() => { + host.loading = true; cncserver.api.implements.get(preset).then(({ data }) => { data.title = `${data.manufacturer} ${data.title}`; delete data.sortWeight; @@ -16,9 +17,11 @@ function setHostFromPreset(host, preset) { if (key === 'handleColors') data[key] = data[key].join(','); host[key] = data[key]; }); + host.loading = false; }).catch((err) => { // Reset the preset if there was any error. host.preset = ''; + host.loading = false; }); }); } @@ -30,7 +33,7 @@ export default () => ({ bracketSize: 8, bracketNotch: 5, bracketPadding: 5, - initialized: false, + loading: false, // Switch to true for no labels/brackets. plain: false, @@ -54,6 +57,7 @@ export default () => ({ bracketNotch, bracketPadding, plain, + loading, // Implement specifics. handleColors, @@ -201,8 +205,11 @@ export default () => ({ + ${svg`
From b4053fd239d806b8f4204b107e64bb49afba1021 Mon Sep 17 00:00:00 2001 From: techninja Date: Tue, 6 Oct 2020 15:17:12 -0700 Subject: [PATCH 064/147] Add missing object diff util --- src/interface/modules/utils/data-diff.mjs | 43 +++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/interface/modules/utils/data-diff.mjs diff --git a/src/interface/modules/utils/data-diff.mjs b/src/interface/modules/utils/data-diff.mjs new file mode 100644 index 0000000..d135eb5 --- /dev/null +++ b/src/interface/modules/utils/data-diff.mjs @@ -0,0 +1,43 @@ +/** + * @file Util to get only the differences between two objects. + */ +/* globals _ */ + +/** + * Get only the parts that are different from a complete/deep JSON object. + * + * @param {object} object + * Input object to check for diff against the base. + * @param {object} base + * Base object to diff against the input. + * + * @returns {object} + * Only the differences between the two objects in full tree form. + */ +function dataDiff(object, base = {}) { + const result = _.pick( + _.mapObject(object, (value, key) => ( + // eslint-disable-next-line no-nested-ternary + (!_.isEqual(value, base[key])) + // eslint-disable-next-line max-len + ? ((_.isObject(value) && _.isObject(base[key])) ? dataDiff(value, base[key]) : value) + : null + )), + value => (value !== null) + ); + + // Trim out empty keys + const entries = Object.entries(result); + entries.forEach(([key, val]) => { + if (typeof val === 'object' && Object.keys(val).length === 0) { + delete result[key]; + } else if (val === undefined) { + delete result[key]; + } + }); + + // TODO: Trim deeper changed objects. + return result; +} + +export default dataDiff; From 711e04f15e255639da420cf4bc89cad2b00bcb54 Mon Sep 17 00:00:00 2001 From: techninja Date: Tue, 6 Oct 2020 15:17:36 -0700 Subject: [PATCH 065/147] Change project name based on title given --- src/interface/modules/components/widgets/project-loader.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/interface/modules/components/widgets/project-loader.mjs b/src/interface/modules/components/widgets/project-loader.mjs index 800421b..cdc90d3 100644 --- a/src/interface/modules/components/widgets/project-loader.mjs +++ b/src/interface/modules/components/widgets/project-loader.mjs @@ -34,6 +34,7 @@ function currentProjectChangeFactory() { function infoChange(host) { const inputs = host.shadowRoot.querySelectorAll('input'); cncserver.api.projects.item.update(host.current, { + name: inputs.item(0).value.trim() || inputs.item(0).placeholder, title: inputs.item(0).value.trim() || inputs.item(0).placeholder, description: inputs.item(1).value.trim() || inputs.item(1).placeholder, }).catch(() => { }); From 3520a685083a5de91896ed9e5af357946cc5c68d Mon Sep 17 00:00:00 2001 From: techninja Date: Tue, 6 Oct 2020 15:21:53 -0700 Subject: [PATCH 066/147] Add a 20 sec timeout for content requests --- src/interface/lib/cncserver.client.api.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/interface/lib/cncserver.client.api.js b/src/interface/lib/cncserver.client.api.js index 3880b4b..addf18d 100644 --- a/src/interface/lib/cncserver.client.api.js +++ b/src/interface/lib/cncserver.client.api.js @@ -18,6 +18,7 @@ let axios = {}; // Placeholder. // Initialize wrapper object is this library is being used elsewhere const cncserver = { + contentTimeout: 20000, init: ({ domain = 'localhost', port = 4242, @@ -146,12 +147,14 @@ cncserver.api = { content: { stat: () => _get('content'), add: { - direct: data => _post('content', { data }), + direct: data => _post('content', { data, timeout: cncserver.contentTimeout }), local: (type, content, options = {}) => _post('content', { data: { ...options, source: { type, content } }, + timeout: cncserver.contentTimeout, }), remote: (type, url, options = {}) => _post('content', { data: { ...options, source: { type, url } }, + timeout: cncserver.contentTimeout, }), }, From ab8d7b934639824b15506b6633c06e3b0a71a19b Mon Sep 17 00:00:00 2001 From: techninja Date: Tue, 6 Oct 2020 16:01:19 -0700 Subject: [PATCH 067/147] Fix XML issue --- src/interface/images/loading.svg | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/interface/images/loading.svg b/src/interface/images/loading.svg index 9cf91cd..997f091 100644 --- a/src/interface/images/loading.svg +++ b/src/interface/images/loading.svg @@ -1,5 +1,3 @@ - - From d378c529d5cc5a2394b49cd4fabc4727776c2984 Mon Sep 17 00:00:00 2001 From: techninja Date: Tue, 6 Oct 2020 17:29:46 -0700 Subject: [PATCH 068/147] Add new modal notify component! --- .../modules/components/elements/index.mjs | 2 + .../components/elements/notify-modal.mjs | 76 +++++++++++++++++++ .../components/widgets/content-importer.mjs | 58 +++++++++----- src/interface/styles/interface.css | 30 -------- 4 files changed, 116 insertions(+), 50 deletions(-) create mode 100644 src/interface/modules/components/elements/notify-modal.mjs diff --git a/src/interface/modules/components/elements/index.mjs b/src/interface/modules/components/elements/index.mjs index 35bd5bd..5a8e364 100644 --- a/src/interface/modules/components/elements/index.mjs +++ b/src/interface/modules/components/elements/index.mjs @@ -14,6 +14,7 @@ import SchemaForm from './schema-form.mjs'; import ToolImplement from './tool-implement.mjs'; import ColorSet from './color-set.mjs'; import NotifyLoading from './notify-loading.mjs'; +import NotifyModal from './notify-modal.mjs'; export default styles => ({ 'tab-item': TabItem(styles), @@ -29,4 +30,5 @@ export default styles => ({ 'tool-implement': ToolImplement(styles), 'color-set': ColorSet(styles), 'notify-loading': NotifyLoading(styles), + 'notify-modal': NotifyModal(styles), }); diff --git a/src/interface/modules/components/elements/notify-modal.mjs b/src/interface/modules/components/elements/notify-modal.mjs new file mode 100644 index 0000000..1721011 --- /dev/null +++ b/src/interface/modules/components/elements/notify-modal.mjs @@ -0,0 +1,76 @@ +/** + * @file Generic modal notifier component. + */ +import { html, dispatch } from '/modules/hybrids.js'; + +function dispatchClose(host) { + dispatch(host, 'close'); +} + +export default styles => ({ + icon: '', + style: 'primary', + message: '', + header: 'Alert', + active: false, + limit: false, + + render: ({ style, icon, header, desc, active, message, limit }) => { + const messageClasses = { + message: true, + }; + messageClasses[`is-${style}`] = true; + + const modalClasses = { + modal: true, + 'is-active': active, + limit, + }; + + return html` + ${styles} + + +
+ + +
+ ` + }, +}); diff --git a/src/interface/modules/components/widgets/content-importer.mjs b/src/interface/modules/components/widgets/content-importer.mjs index 7ac1e4f..82beca7 100644 --- a/src/interface/modules/components/widgets/content-importer.mjs +++ b/src/interface/modules/components/widgets/content-importer.mjs @@ -246,28 +246,46 @@ export default styles => ({ position: relative; overflow: hidden; } + + section { + position: relative; + } + + #source-type-wrapper { + position: absolute; + right: 1em; + } + + @media only screen and (max-width: 1230px) { + #source-type-wrapper { + position: relative; + right: auto; + margin-bottom: 1em; + } + } + + #source-type { + font-size: 1.2em; + } + -
-
- - -
- + + + +