From bfbe65aee9c214b9787232e0646e3e569c7adaa4 Mon Sep 17 00:00:00 2001 From: anderstorstensson Date: Tue, 20 Jan 2026 22:21:08 +0100 Subject: [PATCH 1/4] Use diff 5 --- package.json | 3 +-- src/containers/App/Prefetch.js | 2 -- yarn.lock | 8 ++++---- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 76bd1e0..14473c1 100644 --- a/package.json +++ b/package.json @@ -47,8 +47,7 @@ } }, "resolutions": { - "@react-three/fiber/its-fine": "~1.1.4", - "diff": "^8.0.3" + "@react-three/fiber/its-fine": "~1.1.4" }, "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" } diff --git a/src/containers/App/Prefetch.js b/src/containers/App/Prefetch.js index 6f4f25d..8d7cdeb 100644 --- a/src/containers/App/Prefetch.js +++ b/src/containers/App/Prefetch.js @@ -9,10 +9,8 @@ const Prefetch = ({children}) => { const prefetchSynonyms = usePrefetchSynonyms(); prefetchTaxa(); - console.log('prefetching taxa'); prefetchSynonyms(); - console.log('prefetching synonyms'); return children; }; diff --git a/yarn.lock b/yarn.lock index 98f87dd..8189502 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2213,10 +2213,10 @@ detect-node@^2.0.4: resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== -diff@^5.0.0, diff@^8.0.3: - version "8.0.3" - resolved "https://registry.yarnpkg.com/diff/-/diff-8.0.3.tgz#c7da3d9e0e8c283bb548681f8d7174653720c2d5" - integrity sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ== +diff@^5.0.0: + version "5.2.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.2.tgz#0a4742797281d09cfa699b79ea32d27723623bad" + integrity sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A== dns-packet@^5.2.2: version "5.6.1" From 58ada684d8f0e0253998f4ce0b80d07df1444c5c Mon Sep 17 00:00:00 2001 From: anderstorstensson Date: Thu, 22 Jan 2026 07:50:14 +0100 Subject: [PATCH 2/4] Bump lodash --- package.json | 1 + yarn.lock | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/package.json b/package.json index 14473c1..127168f 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "@babel/runtime": "^7.8.4", "@react-three/fiber": "^8.10.0", "@reduxjs/toolkit": "^1.9.1", + "lodash": "^4.17.23", "match-sorter": "^6.3.1", "ol": "^9.0.0", "react": "^18.2.0", diff --git a/yarn.lock b/yarn.lock index 8189502..ba16954 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3060,6 +3060,11 @@ lodash@^4.17.20, lodash@^4.17.21: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +lodash@^4.17.23: + version "4.17.23" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.23.tgz#f113b0378386103be4f6893388c73d0bde7f2c5a" + integrity sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w== + loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" From 2b174f2a6da2bf367641ef4d5ad49f634149eb0f Mon Sep 17 00:00:00 2001 From: anderstorstensson Date: Thu, 22 Jan 2026 09:03:27 +0100 Subject: [PATCH 3/4] Redesign image labeling sidebar with plankton group hierarchy from user input MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace taxonomic tree with expandable plankton groups structure: Plankton Group → Taxon → Class names (titles) - Add expand all, collapse all, and reset buttons for navigation - Expand plankton groups by default on page load - Show taxa count instead of image count for "All taxa" button - Add visual separator between "All taxa" and plankton groups list - Change unknown taxon URL parameter from __no_taxon__ to "unknown" - Add RTK Query hook for new grouped_by_plankton endpoint --- .../ImageLabeling/ImageLabelingPage.js | 24 +- .../ImageLabeling/ImageLabelingPage.scss | 131 ++++++- .../ImageLabeling/ImageLabelingTaxonomy.js | 324 ++++++++++-------- src/slices/labeling.js | 9 +- 4 files changed, 328 insertions(+), 160 deletions(-) diff --git a/src/containers/ImageLabeling/ImageLabelingPage.js b/src/containers/ImageLabeling/ImageLabelingPage.js index a2aca45..be5d12d 100644 --- a/src/containers/ImageLabeling/ImageLabelingPage.js +++ b/src/containers/ImageLabeling/ImageLabelingPage.js @@ -135,7 +135,7 @@ const ImageLabelingPage = ({ location, history }) => { limit: 1000, fields: ['slug', 'renditions', 'related_taxon', 'taxon', 'attributes', 'file', 'priority'], }; - if (selectedTaxon && selectedTaxon !== '__no_taxon__') { + if (selectedTaxon && selectedTaxon !== 'unknown') { p.taxon = selectedTaxon; } return p; @@ -208,7 +208,7 @@ const ImageLabelingPage = ({ location, history }) => { name = taxonObj; } } else { - slug = '__no_taxon__'; + slug = 'unknown'; name = 'Unknown taxon'; } @@ -218,8 +218,8 @@ const ImageLabelingPage = ({ location, history }) => { }); return Array.from(map.values()).sort((a, b) => { - if (a.slug === '__no_taxon__') return 1; - if (b.slug === '__no_taxon__') return -1; + if (a.slug === 'unknown') return 1; + if (b.slug === 'unknown') return -1; return String(a.name).localeCompare(String(b.name)); }); }, [filteredAllImages, hasActiveFilters]); @@ -231,8 +231,8 @@ const ImageLabelingPage = ({ location, history }) => { } return [...(summary?.taxa || [])].sort((a, b) => { - if (a.slug === '__no_taxon__') return 1; - if (b.slug === '__no_taxon__') return -1; + if (a.slug === 'unknown') return 1; + if (b.slug === 'unknown') return -1; return String(a.name).localeCompare(String(b.name)); }); }, [hasActiveFilters, filteredTaxaMap, summary]); @@ -245,7 +245,7 @@ const ImageLabelingPage = ({ location, history }) => { const filteredImages = React.useMemo(() => { let result = images; - if (selectedTaxon === '__no_taxon__') { + if (selectedTaxon === 'unknown') { result = result.filter((img) => !img.relatedTaxon && !img.taxon); } @@ -316,7 +316,7 @@ const ImageLabelingPage = ({ location, history }) => { name = taxonObj; } } else { - slug = '__no_taxon__'; + slug = 'unknown'; name = 'Unknown taxon'; } } @@ -350,8 +350,8 @@ const ImageLabelingPage = ({ location, history }) => { }); return Array.from(taxonImages.values()).sort((a, b) => { - if (a.taxonSlug === '__no_taxon__') return 1; - if (b.taxonSlug === '__no_taxon__') return -1; + if (a.taxonSlug === 'unknown') return 1; + if (b.taxonSlug === 'unknown') return -1; return String(a.taxonName).localeCompare(String(b.taxonName)); }); }, [isLandingPage, hasActiveFilters, filteredAllImages, landingImages, taxaList]); @@ -405,7 +405,7 @@ const ImageLabelingPage = ({ location, history }) => { // Use image data if available, otherwise fall back to store data for taxa without images const relatedTaxon = relatedTaxonFromImages || ( - !isLandingPage && selectedTaxon && selectedTaxon !== '__no_taxon__' + !isLandingPage && selectedTaxon && selectedTaxon !== 'unknown' ? selectedTaxonFromStore : null ); @@ -455,7 +455,7 @@ const ImageLabelingPage = ({ location, history }) => { .

- ) : selectedTaxon === '__no_taxon__' ? ( + ) : selectedTaxon === 'unknown' ? (

Unknown taxon diff --git a/src/containers/ImageLabeling/ImageLabelingPage.scss b/src/containers/ImageLabeling/ImageLabelingPage.scss index ebcb92a..4d156dd 100644 --- a/src/containers/ImageLabeling/ImageLabelingPage.scss +++ b/src/containers/ImageLabeling/ImageLabelingPage.scss @@ -33,10 +33,34 @@ color: #666; } - .taxonomy-all-taxa { + .taxonomy-expand-collapse { + display: flex; + gap: 0.5rem; margin-bottom: 0.5rem; } + .taxonomy-expand-collapse-button { + flex: 1; + padding: 0.25rem 0.5rem; + font-size: 0.75rem; + color: #666; + background: transparent; + border: 1px solid #ddd; + border-radius: 0.25rem; + cursor: pointer; + + &:hover { + background: rgba(0, 0, 0, 0.05); + color: #333; + } + } + + .taxonomy-all-taxa { + margin-bottom: 1rem; + padding-bottom: 1rem; + border-bottom: 1px solid #e0e0e0; + } + .taxonomy-all-taxa-button, .taxonomy-unknown-button { display: block; @@ -78,6 +102,111 @@ margin-left: 0; } } + + // Plankton groups tree styles + .plankton-groups-tree { + margin-top: 0.5rem; + } + + .plankton-groups-list { + list-style: none; + padding: 0; + margin: 0; + } + + .plankton-group-item { + margin-bottom: 0.25rem; + } + + .plankton-group-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.5rem 0.75rem; + background: #f5f5f5; + border-radius: 0.25rem; + } + + .plankton-group-name { + font-weight: 500; + font-size: 0.875rem; + color: #333; + } + + .plankton-group-toggle, + .plankton-taxon-toggle { + display: flex; + align-items: center; + justify-content: center; + width: 1.5rem; + height: 1.5rem; + padding: 0; + background: transparent; + border: none; + cursor: pointer; + color: #666; + + &:hover { + color: #000; + } + + svg { + width: 0.75rem; + height: 0.75rem; + } + } + + .plankton-taxa-list { + list-style: none; + padding: 0; + margin: 0.25rem 0 0.5rem 1rem; + } + + .plankton-taxon-item { + margin-bottom: 0.125rem; + } + + .plankton-taxon-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.375rem 0.5rem; + border-radius: 0.25rem; + + &:hover { + background: rgba(0, 0, 0, 0.03); + } + } + + .plankton-taxon-link { + flex: 1; + font-size: 0.8125rem; + color: #444; + text-decoration: none; + + &:hover { + color: #0066cc; + } + + &.is-selected { + font-weight: 500; + color: #000; + } + } + + .plankton-titles-list { + list-style: none; + padding: 0; + margin: 0.125rem 0 0.375rem 1.5rem; + } + + .plankton-title-item { + padding: 0.25rem 0.5rem; + font-size: 0.75rem; + color: #666; + border-left: 2px solid #e0e0e0; + margin-bottom: 0.125rem; + } } // Filters toggle button (for taxonomy sidebar on mobile) diff --git a/src/containers/ImageLabeling/ImageLabelingTaxonomy.js b/src/containers/ImageLabeling/ImageLabelingTaxonomy.js index 9797f6d..11ecd7a 100644 --- a/src/containers/ImageLabeling/ImageLabelingTaxonomy.js +++ b/src/containers/ImageLabeling/ImageLabelingTaxonomy.js @@ -1,10 +1,9 @@ import React, { useEffect, useState, useMemo } from 'react'; import PropTypes from 'prop-types'; -import { Link, useHistory } from 'react-router-dom'; -import { useSelector } from 'react-redux'; +import { Link } from 'react-router-dom'; -import Tree from 'Components/Taxonomy/Tree'; -import { useGetAllTaxaQuery, selectById } from 'Slices/taxa'; +import { useGetImageLabelingGroupedByPlanktonQuery } from 'Slices/labeling'; +import { PlusIcon, DashIcon } from 'Components/Icons'; const propTypes = { selectedTaxon: PropTypes.string, @@ -17,6 +16,17 @@ const propTypes = { const ImageLabelingTaxonomy = ({ selectedTaxon, onTaxonSelect, imageLabelingTaxa }) => { const [navigationIsExpanded, setNavigationIsExpanded] = useState(false); + const [expandedGroups, setExpandedGroups] = useState(null); // null = not initialized yet + const [expandedTaxa, setExpandedTaxa] = useState(new Set()); + + const { data: groupedData = [], isLoading } = useGetImageLabelingGroupedByPlanktonQuery(); + + // Initialize expanded groups to all groups when data first loads + useEffect(() => { + if (groupedData.length > 0 && expandedGroups === null) { + setExpandedGroups(new Set(groupedData.map(g => g.group_name))); + } + }, [groupedData, expandedGroups]); useEffect(() => { if (navigationIsExpanded) { @@ -29,148 +39,77 @@ const ImageLabelingTaxonomy = ({ selectedTaxon, onTaxonSelect, imageLabelingTaxa }; }, [navigationIsExpanded]); - const query = useGetAllTaxaQuery(); - - const selectedTaxonData = useSelector( - state => selectById(state, selectedTaxon) - ); - - const getTaxonKey = ({ slug }) => slug; - const handleClickToggleTaxonomy = () => { setNavigationIsExpanded(!navigationIsExpanded); }; - // Build a map of taxa slugs to their image counts (stable reference) - const imageLabelingCounts = useMemo(() => { - if (!imageLabelingTaxa) return new Map(); - const map = new Map(); - imageLabelingTaxa.forEach(t => { - if (t.slug && t.slug !== '__no_taxon__') { - map.set(t.slug, t.count || 0); + const toggleGroup = (groupName) => { + setExpandedGroups(prev => { + // If prev is null, initialize with all groups expanded except the one being toggled + if (prev === null) { + const allGroups = new Set(groupedData.map(g => g.group_name)); + allGroups.delete(groupName); + return allGroups; } - }); - return map; - }, [imageLabelingTaxa]); - - // Ranks that should only show if they have images - const conditionalRanks = new Set(['Kingdom', 'Phylum', 'Order', 'Family', 'Genus']); - - // Filter to only show taxa with images and their ancestors - const filteredEntities = useMemo(() => { - if (!query.data?.entities || imageLabelingCounts.size === 0) { - return null; - } - - const entities = query.data.entities; - const includedSlugs = new Set(); - - // For each taxon with images, include it and ALL its ancestors - imageLabelingCounts.forEach((count, slug) => { - const taxon = entities[slug]; - if (taxon) { - includedSlugs.add(slug); - - if (taxon.classification) { - taxon.classification.forEach(ancestor => { - const ancestorSlug = typeof ancestor === 'string' ? ancestor : ancestor.slug; - if (ancestorSlug) { - includedSlugs.add(ancestorSlug); - } - }); - } + const next = new Set(prev); + if (next.has(groupName)) { + next.delete(groupName); + } else { + next.add(groupName); } + return next; }); + }; - // Helper to determine effective rank - const getEffectiveRank = (slug) => { - const taxon = entities[slug]; - if (!taxon) return null; - const imageCount = imageLabelingCounts.get(slug) || 0; - const isConditionalRank = conditionalRanks.has(taxon.rank); - const hasImages = imageCount > 0; - return (isConditionalRank && !hasImages) ? '_SkippedRank' : taxon.rank; - }; - - // Build filtered entities - const filtered = {}; - includedSlugs.forEach(slug => { - const taxon = entities[slug]; - if (taxon) { - const imageCount = imageLabelingCounts.get(slug) || 0; - - // Filter children to only those in includedSlugs - // AND update their rank in the child object for reduceChildren to read - const filteredChildren = (taxon.children || []) - .filter(child => { - const childSlug = typeof child === 'string' ? child : child.slug; - return includedSlugs.has(childSlug); - }) - .map(child => { - const childSlug = typeof child === 'string' ? child : child.slug; - const childObj = typeof child === 'object' ? child : { slug: childSlug }; - return { - ...childObj, - slug: childSlug, - rank: getEffectiveRank(childSlug), - }; - }); - - filtered[slug] = { - ...taxon, - rank: getEffectiveRank(slug), - children: filteredChildren, - scientificName: imageCount > 0 - ? `${taxon.scientificName} (${imageCount})` - : taxon.scientificName, - }; + const toggleTaxon = (taxonSlug) => { + setExpandedTaxa(prev => { + const next = new Set(prev); + if (next.has(taxonSlug)) { + next.delete(taxonSlug); + } else { + next.add(taxonSlug); } + return next; }); + }; - return filtered; - }, [query.data?.entities, imageLabelingCounts]); + const expandAll = () => { + setExpandedGroups(new Set(groupedData.map(g => g.group_name))); + // Also expand all taxa + const allTaxaSlugs = groupedData.flatMap(g => g.taxa.map(t => t.taxon_slug)); + setExpandedTaxa(new Set(allTaxaSlugs)); + }; - // Determine which ranks to display - const displayRanks = useMemo(() => { - const baseRanks = ['Domain', 'Class', 'Genus', 'Species', 'Subspecies', 'Variety', 'Form', 'Forma']; - - if (!query.data?.entities || imageLabelingCounts.size === 0) { - return baseRanks; - } + const collapseAll = () => { + setExpandedGroups(new Set()); + setExpandedTaxa(new Set()); + }; - const entities = query.data.entities; - const ranksToAdd = new Set(); - - // Check which conditional ranks have taxa with images - imageLabelingCounts.forEach((count, slug) => { - if (count > 0) { - const taxon = entities[slug]; - if (taxon?.rank && conditionalRanks.has(taxon.rank)) { - ranksToAdd.add(taxon.rank); - } + const resetToDefault = () => { + // Default: groups expanded, taxa collapsed + setExpandedGroups(new Set(groupedData.map(g => g.group_name))); + setExpandedTaxa(new Set()); + }; + + // Build a map of taxa slugs to their image counts from the summary data + const imageLabelingCounts = useMemo(() => { + if (!imageLabelingTaxa) return new Map(); + const map = new Map(); + imageLabelingTaxa.forEach(t => { + if (t.slug && t.slug !== 'unknown') { + map.set(t.slug, t.count || 0); } }); + return map; + }, [imageLabelingTaxa]); - return [...baseRanks, ...ranksToAdd]; - }, [query.data?.entities, imageLabelingCounts]); - - // Always keep tree fully expanded - const initialPath = useMemo(() => { - if (filteredEntities) { - return Object.keys(filteredEntities); - } - return []; - }, [filteredEntities]); - - const selectedKey = selectedTaxonData ? getTaxonKey(selectedTaxonData) : null; - - const hasTaxa = filteredEntities && Object.keys(filteredEntities).length > 0; - const hasUnknownTaxon = imageLabelingTaxa?.some(t => t.slug === '__no_taxon__'); - const unknownTaxonCount = imageLabelingTaxa?.find(t => t.slug === '__no_taxon__')?.count || 0; + const hasUnknownTaxon = imageLabelingTaxa?.some(t => t.slug === 'unknown'); + const unknownTaxonCount = imageLabelingTaxa?.find(t => t.slug === 'unknown')?.count || 0; - const totalImageCount = useMemo(() => { + // Count number of taxa (excluding unknown) + const totalTaxaCount = useMemo(() => { if (!imageLabelingTaxa) return 0; - return imageLabelingTaxa.reduce((sum, t) => sum + (t.count || 0), 0); + return imageLabelingTaxa.filter(t => t.slug !== 'unknown').length; }, [imageLabelingTaxa]); return ( @@ -193,9 +132,35 @@ const ImageLabelingTaxonomy = ({ selectedTaxon, onTaxonSelect, imageLabelingTaxa Image Labeling Guide

- +

Taxonomy

- + + {!isLoading && groupedData.length > 0 && ( +
+ + + +
+ )} +
- {hasTaxa && ( -
- ({ - to: `/image-labeling/?taxon=${slug}`, + {isLoading && ( +
Loading...
+ )} + + {!isLoading && groupedData.length > 0 && ( +
+
    + {groupedData.map((group) => { + const isGroupExpanded = expandedGroups?.has(group.group_name) ?? true; + const groupTaxonCount = group.taxa.reduce( + (sum, t) => sum + (imageLabelingCounts.get(t.taxon_slug) || 0), + 0 + ); + + return ( +
  • +
    + + {group.group_name} ({groupTaxonCount}) + + +
    + + {isGroupExpanded && ( +
      + {group.taxa.map((taxon) => { + const isTaxonExpanded = expandedTaxa.has(taxon.taxon_slug); + const taxonCount = imageLabelingCounts.get(taxon.taxon_slug) || 0; + const hasTitles = taxon.titles && taxon.titles.length > 0; + + return ( +
    • +
      + { + onTaxonSelect(taxon.taxon_slug); + setNavigationIsExpanded(false); + }} + > + {taxon.taxon_name} ({taxonCount}) + + {hasTitles && ( + + )} +
      + + {isTaxonExpanded && hasTitles && ( +
        + {taxon.titles.map((title) => ( +
      • + {title} +
      • + ))} +
      + )} +
    • + ); + })} +
    + )} +
  • + ); })} - /> +
)} @@ -228,10 +260,10 @@ const ImageLabelingTaxonomy = ({ selectedTaxon, onTaxonSelect, imageLabelingTaxa
@@ -245,4 +277,4 @@ const ImageLabelingTaxonomy = ({ selectedTaxon, onTaxonSelect, imageLabelingTaxa ImageLabelingTaxonomy.propTypes = propTypes; -export default ImageLabelingTaxonomy; \ No newline at end of file +export default ImageLabelingTaxonomy; diff --git a/src/slices/labeling.js b/src/slices/labeling.js index 0bfbe8f..1edacc8 100644 --- a/src/slices/labeling.js +++ b/src/slices/labeling.js @@ -31,12 +31,19 @@ export const extendedApiSlice = baseApi.injectEndpoints({ getImageLabelingSummary: builder.query({ query: () => 'media/image_labeling/summary', }), + + // Get taxa grouped by plankton groups with class names (titles) + getImageLabelingGroupedByPlankton: builder.query({ + query: () => 'media/image_labeling/grouped_by_plankton', + transformResponse: responseData => responseData.groups || [], + }), }), overrideExisting: false, }); -export const { +export const { useGetImageLabelingImagesQuery, useGetImageLabelingSummaryQuery, useGetImageLabelingFirstPerTaxonQuery, + useGetImageLabelingGroupedByPlanktonQuery, } = extendedApiSlice; \ No newline at end of file From 74feca9969eb45ead2f82c3bca3ea92e18d13a40 Mon Sep 17 00:00:00 2001 From: Anders Torstensson Date: Thu, 22 Jan 2026 11:11:24 +0100 Subject: [PATCH 4/4] Deduplicate lodash to a single version --- package.json | 1 + yarn.lock | 7 +------ 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 127168f..fa0a652 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ } }, "resolutions": { + "lodash": "4.17.23", "@react-three/fiber/its-fine": "~1.1.4" }, "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" diff --git a/yarn.lock b/yarn.lock index ba16954..d345016 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3055,12 +3055,7 @@ lodash.debounce@^4.0.8: resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== -lodash@^4.17.20, lodash@^4.17.21: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - -lodash@^4.17.23: +lodash@4.17.23, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.23: version "4.17.23" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.23.tgz#f113b0378386103be4f6893388c73d0bde7f2c5a" integrity sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==