diff --git a/taxonium_component/package.json b/taxonium_component/package.json index 45c5626e..7e4da8b8 100644 --- a/taxonium_component/package.json +++ b/taxonium_component/package.json @@ -34,11 +34,13 @@ "classnames": "^2.3.2", "deck.gl": "=8.6.6", "react-circular-progressbar": "^2.1.0", + "react-color": "^2.19.3", "react-debounce-input": "^3.3.0", "react-hot-toast": "^2.4.1", "react-icons": "^4.8.0", "react-modal": "^3.16.1", "react-spinners": "^0.13.8", + "react-tabs": "4", "react-tooltip": "^4.2.21", "readable-web-to-node-stream": "^3.0.2", "sb": "^7.0.14", diff --git a/taxonium_component/src/Deck.jsx b/taxonium_component/src/Deck.jsx index 70528789..a9c6f1cf 100644 --- a/taxonium_component/src/Deck.jsx +++ b/taxonium_component/src/Deck.jsx @@ -20,6 +20,7 @@ import { TreenomeButtons } from "./components/TreenomeButtons"; import TreenomeModal from "./components/TreenomeModal"; import FirefoxWarning from "./components/FirefoxWarning"; import { JBrowseErrorBoundary } from "./components/JBrowseErrorBoundary"; +import ColorSettingModal from "./components/ColorSettingModal"; import Key from "./components/Key"; const MemoizedKey = React.memo(Key); @@ -42,10 +43,14 @@ function Deck({ isCurrentlyOutsideBounds, deckRef, jbrowseRef, + setAdditionalColorMapping, }) { const zoomReset = view.zoomReset; const snapshot = useSnapshot(deckRef); const [deckSettingsOpen, setDeckSettingsOpen] = useState(false); + const [colorSettingOpen, setColorSettingOpen] = useState(false); + const [currentColorSettingKey, setCurrentColorSettingKey] = useState("a"); + const [hoveredKey, setHoveredKey] = useState(null); const [treenomeSettingsOpen, setTreenomeSettingsOpen] = useState(false); //console.log("DATA is ", data); @@ -187,6 +192,7 @@ function Deck({ treenomeState, treenomeReferenceInfo, setTreenomeReferenceInfo, + hoveredKey, }); // console.log("deck refresh"); @@ -254,6 +260,65 @@ function Deck({ deckSettingsOpen={deckSettingsOpen} setDeckSettingsOpen={setDeckSettingsOpen} settings={settings} + noneColor={colorHook.toRGB("None")} + setNoneColor={(color) => { + setAdditionalColorMapping((x) => { + return { ...x, None: color }; + }); + }} + /> + { + setAdditionalColorMapping((x) => { + return { ...x, [currentColorSettingKey]: color }; + }); + }} + title={currentColorSettingKey} + /> + + + + setDeckSettingsOpen(true)} + settings={settings} /> - - - - - setDeckSettingsOpen(true)} - settings={settings} - /> - + ); diff --git a/taxonium_component/src/Taxonium.jsx b/taxonium_component/src/Taxonium.jsx index 79e9905c..e974c874 100644 --- a/taxonium_component/src/Taxonium.jsx +++ b/taxonium_component/src/Taxonium.jsx @@ -96,9 +96,11 @@ function Taxonium({ configUrl ); const colorBy = useColorBy(config, query, updateQuery); + const [additionalColorMapping, setAdditionalColorMapping] = useState({}); const colorMapping = useMemo(() => { - return config.colorMapping ? config.colorMapping : {}; - }, [config.colorMapping]); + const initial = config.colorMapping ? config.colorMapping : {}; + return { ...initial, ...additionalColorMapping }; + }, [config.colorMapping, additionalColorMapping]); const colorHook = useColor(colorMapping); //TODO: this is always true for now @@ -193,6 +195,7 @@ function Taxonium({ treenomeState={treenomeState} deckRef={deckRef} jbrowseRef={jbrowseRef} + setAdditionalColorMapping={setAdditionalColorMapping} /> diff --git a/taxonium_component/src/components/ColorPicker.jsx b/taxonium_component/src/components/ColorPicker.jsx new file mode 100644 index 00000000..cd329808 --- /dev/null +++ b/taxonium_component/src/components/ColorPicker.jsx @@ -0,0 +1,84 @@ +import React, { useState } from "react"; +import { SketchPicker } from "react-color"; + +const rgbToList = (rgb) => { + return [rgb.r, rgb.g, rgb.b]; +}; +const listToRgb = (list) => { + const color = { r: list[0], g: list[1], b: list[2] }; + return color; +}; + +function ColorPicker({ color, setColor }) { + const rgbColor = listToRgb(color); + const [showPicker, setShowPicker] = useState(false); + + const togglePicker = () => { + setShowPicker(!showPicker); + }; + + const handleClose = () => { + setShowPicker(false); + }; + + const handleColorChange = (newColor) => { + setColor(rgbToList(newColor.rgb)); + }; + + if (!showPicker) { + return ( +
+
+
+
+
+ ); + } + + return ( +
+
+ +
+ ); +} + +export default ColorPicker; diff --git a/taxonium_component/src/components/ColorSettingModal.jsx b/taxonium_component/src/components/ColorSettingModal.jsx new file mode 100644 index 00000000..f904bfb5 --- /dev/null +++ b/taxonium_component/src/components/ColorSettingModal.jsx @@ -0,0 +1,44 @@ +import React from "react"; +import Modal from "react-modal"; +import ColorPicker from "./ColorPicker"; + +const modalStyle = { + content: { + top: "50%", + left: "50%", + transform: "translate(-50%, -50%)", + backgroundColor: "#fafafa", + border: "1px solid #e2e8f0", + borderRadius: "8px", + padding: "20px", + maxWidth: "400px", + }, + overlay: { + backgroundColor: "rgba(100, 100, 100, 0.3)", + zIndex: 1000, + }, +}; + +const ColorSettingModal = ({ isOpen, setIsOpen, color, setColor, title }) => { + return ( + setIsOpen(false)} + contentLabel={title} + > +

{title}

+ +
+
+ +
+
+
+ ); +}; + +export default ColorSettingModal; diff --git a/taxonium_component/src/components/DeckButtons.jsx b/taxonium_component/src/components/DeckButtons.jsx index ae7572f4..b1fa96cb 100644 --- a/taxonium_component/src/components/DeckButtons.jsx +++ b/taxonium_component/src/components/DeckButtons.jsx @@ -19,6 +19,9 @@ const TaxButton = ({ children, onClick, title }) => { shadow-md " onClick={onClick} title={title} + style={{ + pointerEvents: "auto", + }} > {children} @@ -41,7 +44,8 @@ export const DeckButtons = ({ position: "absolute", right: "0em", bottom: "0em", - zIndex: 10, + zIndex: 2, + pointerEvents: "none", }} className="flex flex-col items-end" > @@ -63,6 +67,7 @@ export const DeckButtons = ({ " style={{ fontSize: ".7em", + pointerEvents: "auto", boxShadow: "0px -3px 4px 4px rgba(255, 255, 255, 1)", }} > diff --git a/taxonium_component/src/components/DeckSettingsModal.jsx b/taxonium_component/src/components/DeckSettingsModal.jsx index aa30281c..fd6d4e36 100644 --- a/taxonium_component/src/components/DeckSettingsModal.jsx +++ b/taxonium_component/src/components/DeckSettingsModal.jsx @@ -1,14 +1,26 @@ +import React from "react"; import Modal from "react-modal"; +import ColorPicker from "./ColorPicker"; +import { Tab, Tabs, TabList, TabPanel } from "react-tabs"; +import "react-tabs/style/react-tabs.css"; + const settingsModalStyle = { content: { top: "50%", left: "50%", - right: "auto", - bottom: "auto", - marginRight: "-50%", transform: "translate(-50%, -50%)", - //width: '50%', backgroundColor: "#fafafa", + border: "1px solid #e2e8f0", + borderRadius: "8px", + padding: "20px", + maxWidth: "700px", + maxHeight: "80vh", + minWidth: "400px", + minHeight: "400px", + }, + overlay: { + backgroundColor: "rgba(100, 100, 100, 0.3)", + zIndex: 1000, }, }; @@ -21,116 +33,215 @@ const DeckSettingsModal = ({ settings, deckSettingsOpen, setDeckSettingsOpen, + noneColor, + setNoneColor, }) => { return ( setDeckSettingsOpen(false)} - contentLabel="Example Modal" + contentLabel="Deck Settings Modal" > -

Settings

-
-
- -
-
- -
-
- -
+

Settings

+ + + Toggle + Appearance -
- -
-
- -
+ Search + Color +
-

Mutation types enabled

-
- {Object.keys(settings.mutationTypesEnabled).map((key) => ( -
-
+
+ + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ Tip: to modify the color of nodes colored by metadata, + click the specific metadata in the key at the bottom left. +
+
+
+
); }; + export default DeckSettingsModal; diff --git a/taxonium_component/src/components/Key.jsx b/taxonium_component/src/components/Key.jsx index 719ea42d..26f1f448 100644 --- a/taxonium_component/src/components/Key.jsx +++ b/taxonium_component/src/components/Key.jsx @@ -7,6 +7,10 @@ const Key = ({ colorByGene, colorByPosition, config, + setCurrentColorSettingKey, + setColorSettingOpen, + hoveredKey, + setHoveredKey, }) => { const numLegendEntries = 10; const [collapsed, setCollapsed] = useState(window.innerWidth < 800); @@ -29,6 +33,12 @@ const Key = ({ "px-2 border-right border bg-white opacity-90 absolute bottom-2 left-2 pt-1 pb-2", collapsed ? "w-20" : "w-32" )} + // z index big + style={{ + zIndex: 10, + + cursor: "default", + }} >

{ + setCurrentColorSettingKey(item.value); + setColorSettingOpen(true); + }} + onMouseEnter={() => { + setHoveredKey(item.value); + }} + onMouseLeave={() => setHoveredKey(null)} + title="Edit color" >
{item.value}
); })} + {isTruncated &&
...
} )} diff --git a/taxonium_component/src/components/MyColorPicker b/taxonium_component/src/components/MyColorPicker new file mode 100644 index 00000000..e69de29b diff --git a/taxonium_component/src/components/SearchDisplayToggle.jsx b/taxonium_component/src/components/SearchDisplayToggle.jsx new file mode 100644 index 00000000..eba16fb5 --- /dev/null +++ b/taxonium_component/src/components/SearchDisplayToggle.jsx @@ -0,0 +1,26 @@ +import React from "react"; +import { toast } from "react-hot-toast"; +import { FaCircle, FaRegCircle } from "react-icons/fa"; + +const SearchDisplayToggle = ({ settings }) => { + const { displaySearchesAsPoints, setDisplaySearchesAsPoints } = settings; + const toggleDisplay = () => { + // Toggle the displaySearchesAsPoints value + setDisplaySearchesAsPoints(!displaySearchesAsPoints); + + // Show a toast message based on the new value + if (!displaySearchesAsPoints) { + toast.success("Displaying searches as points"); + } else { + toast.success("Displaying searches as circles"); + } + }; + + return ( + + ); +}; + +export default SearchDisplayToggle; diff --git a/taxonium_component/src/components/SearchPanel.jsx b/taxonium_component/src/components/SearchPanel.jsx index 35733736..8f263dad 100644 --- a/taxonium_component/src/components/SearchPanel.jsx +++ b/taxonium_component/src/components/SearchPanel.jsx @@ -16,6 +16,8 @@ import { useState, useMemo, useEffect } from "react"; import classNames from "classnames"; +import SearchDisplayToggle from "./SearchDisplayToggle"; + const prettify_x_types = { x_dist: "Distance", x_time: "Time" }; const formatNumber = (num) => { @@ -372,9 +374,12 @@ function SearchPanel({ )}

-

- - Search +

+
+ + Search +
+

{search.searchSpec.map((item) => ( diff --git a/taxonium_component/src/hooks/useColor.jsx b/taxonium_component/src/hooks/useColor.jsx index 2c08a208..d5fcb755 100644 --- a/taxonium_component/src/hooks/useColor.jsx +++ b/taxonium_component/src/hooks/useColor.jsx @@ -140,7 +140,7 @@ const useColor = (colorMapping) => { const toRGB = useCallback( (string) => { - if (rgb_cache[string]) { + if (rgb_cache[string] && !colorMapping[string]) { return rgb_cache[string]; } else { const result = toRGB_uncached(string); diff --git a/taxonium_component/src/hooks/useLayers.jsx b/taxonium_component/src/hooks/useLayers.jsx index 71959326..b3367c84 100644 --- a/taxonium_component/src/hooks/useLayers.jsx +++ b/taxonium_component/src/hooks/useLayers.jsx @@ -44,8 +44,9 @@ const useLayers = ({ treenomeState, treenomeReferenceInfo, setTreenomeReferenceInfo, + hoveredKey, }) => { - const lineColor = [150, 150, 150]; + const lineColor = settings.lineColor; const getNodeColorField = colorBy.getNodeColorField; const colorByField = colorBy.colorByField; @@ -158,7 +159,8 @@ const useLayers = ({ getFillColor: (d) => toRGB(getNodeColorField(d, detailed_data)), // radius in pixels - getRadius: 3, + getRadius: (d) => + getNodeColorField(d, detailed_data) === hoveredKey ? 4 : 3, getLineColor: [100, 100, 100], opacity: 0.6, stroked: data.data.nodes && data.data.nodes.length < 3000, @@ -169,7 +171,8 @@ const useLayers = ({ onHover: (info) => setHoverInfo(info), modelMatrix: modelMatrix, updateTriggers: { - getFillColor: [detailed_data, getNodeColorField], + getFillColor: [detailed_data, getNodeColorField, colorHook], + getRadius: [hoveredKey, getNodeColorField], getPosition: [xType], }, }; @@ -304,7 +307,7 @@ const useLayers = ({ getPosition: (d) => [getX(d), d.y], getText: (d) => d.clades[clade_accessor], - getColor: [100, 100, 100], + getColor: settings.cladeLabelColor, getAngle: 0, fontFamily: "Roboto, sans-serif", fontWeight: 700, @@ -352,7 +355,7 @@ const useLayers = ({ getPosition: (d) => [getX(d), d.y], getText: (d) => d[config.name_accessor], - getColor: [180, 180, 180], + getColor: settings.terminalNodeLabelColor, getAngle: 0, billboard: true, @@ -455,8 +458,10 @@ const useLayers = ({ data: data, id: "main-search-scatter-" + spec.key, getPosition: (d) => [d[xType], d.y], - getLineColor: lineColor, - getRadius: 5 + 2 * i, + getLineColor: settings.displaySearchesAsPoints ? [0, 0, 0] : lineColor, + getRadius: settings.displaySearchesAsPoints + ? settings.searchPointSize + : 5 + 2 * i, radiusUnits: "pixels", lineWidthUnits: "pixels", stroked: true, @@ -464,7 +469,9 @@ const useLayers = ({ wireframe: true, getLineWidth: 1, filled: true, - getFillColor: [255, 0, 0, 0], + getFillColor: settings.displaySearchesAsPoints + ? lineColor + : [255, 0, 0, 0], modelMatrix: modelMatrix, updateTriggers: { getPosition: [xType], diff --git a/taxonium_component/src/hooks/useSettings.jsx b/taxonium_component/src/hooks/useSettings.jsx index 90eeba46..aff52936 100644 --- a/taxonium_component/src/hooks/useSettings.jsx +++ b/taxonium_component/src/hooks/useSettings.jsx @@ -10,6 +10,17 @@ export const useSettings = ({ query, updateQuery }) => { const [thresholdForDisplayingText, setThresholdForDisplayingText] = useState(2.9); + const [displaySearchesAsPoints, setDisplaySearchesAsPoints] = useState(false); + + const [searchPointSize, setSearchPointSize] = useState(3); + + const [terminalNodeLabelColor, setTerminalNodeLabelColor] = useState([ + 180, 180, 180, + ]); + + const [lineColor, setLineColor] = useState([150, 150, 150]); + const [cladeLabelColor, setCladeLabelColor] = useState([100, 100, 100]); + const [displayPointsForInternalNodes, setDisplayPointsForInternalNodes] = useState(false); const toggleMinimapEnabled = () => { @@ -123,5 +134,15 @@ export const useSettings = ({ query, updateQuery }) => { isCov2Tree, chromosomeName, setChromosomeName, + displaySearchesAsPoints, + setDisplaySearchesAsPoints, + searchPointSize, + setSearchPointSize, + terminalNodeLabelColor, + setTerminalNodeLabelColor, + lineColor, + setLineColor, + cladeLabelColor, + setCladeLabelColor, }; }; diff --git a/taxonium_component/yarn.lock b/taxonium_component/yarn.lock index ef116a78..f3e712d0 100644 --- a/taxonium_component/yarn.lock +++ b/taxonium_component/yarn.lock @@ -1553,6 +1553,11 @@ resolved "https://registry.yarnpkg.com/@iarna/toml/-/toml-2.2.5.tgz#b32366c89b43c6f8cefbdefac778b9c828e3ba8c" integrity sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg== +"@icons/material@^0.2.4": + version "0.2.4" + resolved "https://registry.yarnpkg.com/@icons/material/-/material-0.2.4.tgz#e90c9f71768b3736e76d7dd6783fc6c2afa88bc8" + integrity sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw== + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -7643,6 +7648,11 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" +lodash-es@^4.17.15: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" + integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== + lodash.capitalize@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz#f826c9b4e2a8511d84e3aca29db05e1a4f3b72a9" @@ -7678,7 +7688,7 @@ lodash.uniqby@^4.7.0: resolved "https://registry.yarnpkg.com/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz#d99c07a669e9e6d24e1362dfe266c67616af1302" integrity sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww== -lodash@4.17.21, lodash@^4.17.15, lodash@^4.17.21: +lodash@4.17.21, lodash@^4.0.1, lodash@^4.17.15, lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -7789,6 +7799,11 @@ markdown-to-jsx@^7.1.8: resolved "https://registry.yarnpkg.com/markdown-to-jsx/-/markdown-to-jsx-7.2.0.tgz#e7b46b65955f6a04d48a753acd55874a14bdda4b" integrity sha512-3l4/Bigjm4bEqjCR6Xr+d4DtM1X6vvtGsMGSjJYyep8RjjIvcWtrXBS8Wbfe1/P+atKNMccpsraESIaWVplzVg== +material-colors@^1.2.1: + version "1.2.6" + resolved "https://registry.yarnpkg.com/material-colors/-/material-colors-1.2.6.tgz#6d1958871126992ceecc72f4bcc4d8f010865f46" + integrity sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg== + material-ui-popup-state@^5.0.0: version "5.0.8" resolved "https://registry.yarnpkg.com/material-ui-popup-state/-/material-ui-popup-state-5.0.8.tgz#b35f7878b994590a55bf1e7a5a1ed4687f75fdc4" @@ -8884,7 +8899,7 @@ prompts@^2.4.0: kleur "^3.0.3" sisteransi "^1.0.5" -prop-types@^15.0.0, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: +prop-types@^15.0.0, prop-types@^15.5.0, prop-types@^15.5.10, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -9126,6 +9141,19 @@ react-circular-progressbar@^2.1.0: resolved "https://registry.yarnpkg.com/react-circular-progressbar/-/react-circular-progressbar-2.1.0.tgz#99e5ae499c21de82223b498289e96f66adb8fa3a" integrity sha512-xp4THTrod4aLpGy68FX/k1Q3nzrfHUjUe5v6FsdwXBl3YVMwgeXYQKDrku7n/D6qsJA9CuunarAboC2xCiKs1g== +react-color@^2.19.3: + version "2.19.3" + resolved "https://registry.yarnpkg.com/react-color/-/react-color-2.19.3.tgz#ec6c6b4568312a3c6a18420ab0472e146aa5683d" + integrity sha512-LEeGE/ZzNLIsFWa1TMe8y5VYqr7bibneWmvJwm1pCn/eNmrabWDh659JSPn9BuaMpEfU83WTOJfnCcjDZwNQTA== + dependencies: + "@icons/material" "^0.2.4" + lodash "^4.17.15" + lodash-es "^4.17.15" + material-colors "^1.2.1" + prop-types "^15.5.10" + reactcss "^1.2.0" + tinycolor2 "^1.4.1" + react-colorful@^5.1.2: version "5.6.1" resolved "https://registry.yarnpkg.com/react-colorful/-/react-colorful-5.6.1.tgz#7dc2aed2d7c72fac89694e834d179e32f3da563b" @@ -9278,6 +9306,14 @@ react-spinners@^0.13.8: resolved "https://registry.yarnpkg.com/react-spinners/-/react-spinners-0.13.8.tgz#5262571be0f745d86bbd49a1e6b49f9f9cb19acc" integrity sha512-3e+k56lUkPj0vb5NDXPVFAOkPC//XyhKPJjvcGjyMNPWsBKpplfeyialP74G7H7+It7KzhtET+MvGqbKgAqpZA== +react-tabs@4: + version "4.3.0" + resolved "https://registry.yarnpkg.com/react-tabs/-/react-tabs-4.3.0.tgz#9f4db0fd209ba4ab2c1e78993ff964435f84af62" + integrity sha512-2GfoG+f41kiBIIyd3gF+/GRCCYtamC8/2zlAcD8cqQmqI9Q+YVz7fJLHMmU9pXDVYYHpJeCgUSBJju85vu5q8Q== + dependencies: + clsx "^1.1.0" + prop-types "^15.5.0" + react-tooltip@^4.2.21: version "4.5.1" resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-4.5.1.tgz#77eccccdf16adec804132e558ec20ca5783b866a" @@ -9325,6 +9361,13 @@ react@^17.0.2: loose-envify "^1.1.0" object-assign "^4.1.1" +reactcss@^1.2.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/reactcss/-/reactcss-1.2.3.tgz#c00013875e557b1cf0dfd9a368a1c3dab3b548dd" + integrity sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A== + dependencies: + lodash "^4.0.1" + read-cache@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" @@ -10401,6 +10444,11 @@ timers-browserify@^2.0.4: dependencies: setimmediate "^1.0.4" +tinycolor2@^1.4.1: + version "1.6.0" + resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.6.0.tgz#f98007460169b0263b97072c5ae92484ce02d09e" + integrity sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw== + titleize@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/titleize/-/titleize-3.0.0.tgz#71c12eb7fdd2558aa8a44b0be83b8a76694acd53"