diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 539feb52..e9245230 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -4,6 +4,7 @@ module.exports = { 'jsx-a11y/click-events-have-key-events': 0, 'jsx-a11y/interactive-supports-focus': 0, 'jsx-a11y/control-has-associated-label': 0, + 'react/no-unused-prop-types': 0, }, overrides: [ diff --git a/package.json b/package.json index 8bfb2579..50ec9a34 100644 --- a/package.json +++ b/package.json @@ -40,10 +40,14 @@ "license": "GPL-2.0-only", "devDependencies": { "@adamscybot/react-leaflet-component-marker": "^2.0.0", + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/modifiers": "^9.0.0", + "@dnd-kit/sortable": "^10.0.0", "@fontsource/open-sans": "^4.5.14", "@radix-ui/colors": "^3.0.0", "@radix-ui/react-checkbox": "^1.2.3", "@radix-ui/react-switch": "^1.2.2", + "@tanstack/react-virtual": "^3.13.10", "@tsconfig/node18": "^18.2.2", "@tsconfig/node20": "^20.1.4", "@tsconfig/strictest": "^2.0.2", @@ -110,7 +114,6 @@ "react-icons": "^4.1.0", "react-leaflet": "^4.2.1", "react-lottie-player": "^1.5.0", - "react-sortablejs": "^6.1.4", "react-syntax-highlighter": "^15.4.3", "react-use": "^17.4.0", "rimraf": "^5.0.5", diff --git a/src/renderer/src/SegmentList.tsx b/src/renderer/src/SegmentList.tsx index f537a8d6..6949efc1 100644 --- a/src/renderer/src/SegmentList.tsx +++ b/src/renderer/src/SegmentList.tsx @@ -1,12 +1,13 @@ -import { memo, useMemo, useRef, useCallback, useState, SetStateAction, Dispatch, ReactNode, MouseEventHandler } from 'react'; +import { memo, useMemo, useRef, useCallback, useState, SetStateAction, Dispatch, ReactNode, MouseEventHandler, CSSProperties, useEffect } from 'react'; import { FaYinYang, FaSave, FaPlus, FaMinus, FaTag, FaSortNumericDown, FaAngleRight, FaRegCheckCircle, FaRegCircle } from 'react-icons/fa'; import { AiOutlineSplitCells } from 'react-icons/ai'; -import { MotionStyle, motion } from 'framer-motion'; +import { motion } from 'framer-motion'; import { useTranslation, Trans } from 'react-i18next'; -import { ReactSortable } from 'react-sortablejs'; -import isEqual from 'lodash/isEqual'; -import useDebounce from 'react-use/lib/useDebounce'; -import scrollIntoView from 'scroll-into-view-if-needed'; +import { DndContext, closestCenter, PointerSensor, useSensor, useSensors, DragEndEvent, DragStartEvent, DragOverlay, UniqueIdentifier } from '@dnd-kit/core'; +import { SortableContext, verticalListSortingStrategy, arrayMove, useSortable } from '@dnd-kit/sortable'; +import { restrictToVerticalAxis } from '@dnd-kit/modifiers'; +import { useVirtualizer } from '@tanstack/react-virtual'; +import { CSS } from '@dnd-kit/utilities'; import Dialog, { ConfirmButton } from './components/Dialog'; import Swal from './swal'; @@ -32,7 +33,8 @@ const neutralButtonColor = 'var(--gray-9)'; const Segment = memo(({ seg, index, - currentSegIndex, + isActive, + dragging, formatTimecode, getFrameCount, updateSegOrder, @@ -62,7 +64,8 @@ const Segment = memo(({ }: { seg: StateSegment | InverseCutSegment, index: number, - currentSegIndex: number, + isActive?: boolean | undefined, + dragging?: boolean | undefined, formatTimecode: FormatTimecode, getFrameCount: GetFrameCount, updateSegOrder: UseSegments['updateSegOrder'], @@ -72,7 +75,7 @@ const Segment = memo(({ onLabelSelectedSegments: UseSegments['labelSelectedSegments'], onReorderPress: (i: number) => Promise, onLabelPress: UseSegments['labelSegment'], - selected: boolean, + selected: boolean | undefined, onSelectSingleSegment: UseSegments['selectOnlySegment'], onToggleSegmentSelected: UseSegments['toggleSegmentSelected'], onDeselectAllSegments: UseSegments['deselectAllSegments'], @@ -94,7 +97,7 @@ const Segment = memo(({ const { t } = useTranslation(); const { getSegColor } = useSegColors(); - const ref = useRef(null); + const ref = useRef(null); const contextMenuTemplate = useMemo(() => { if (invertCutSegments) return []; @@ -152,12 +155,6 @@ const Segment = memo(({ : `${formatTimecode({ seconds: seg.start })} - ${formatTimecode({ seconds: seg.end })}` ), [formatTimecode, seg]); - const isActive = !invertCutSegments && currentSegIndex === index; - - useDebounce(() => { - if (isActive && ref.current) scrollIntoView(ref.current, { behavior: 'smooth', scrollMode: 'if-needed' }); - }, 300, [isActive]); - function renderNumber() { if (invertCutSegments || !('segColorIndex' in seg)) { return ; @@ -169,7 +166,7 @@ const Segment = memo(({ const borderColor = darkMode ? color.lighten(0.5) : color.darken(0.3); return ( - + {index + 1} ); @@ -187,28 +184,64 @@ const Segment = memo(({ onToggleSegmentSelected(seg); }, [onToggleSegmentSelected, seg]); - const cursor = invertCutSegments ? undefined : 'grab'; + const cursor = invertCutSegments ? undefined : (dragging ? 'grabbing' : 'grab'); const tags = useMemo(() => getSegmentTags('tags' in seg ? seg : {}), [seg]); const maybeOnClick = useCallback(() => !invertCutSegments && onClick(index), [index, invertCutSegments, onClick]); - const motionStyle = useMemo(() => ({ originY: 0, margin: '5px 0', background: 'var(--gray-2)', border: isActive ? '1px solid var(--gray-10)' : '1px solid transparent', padding: 5, borderRadius: 5, position: 'relative' }), [isActive]); + const sortable = useSortable({ + id: seg.segId, + transition: { + duration: 150, + easing: 'ease-in-out', + }, + disabled: invertCutSegments, + }); + + const style = useMemo(() => { + const transitions = [ + ...(sortable.transition ? [sortable.transition] : []), + 'opacity 100ms ease-out', + ]; + return { + visibility: sortable.isDragging ? 'hidden' : undefined, + padding: '3px 5px', + margin: '1px 0', + boxSizing: 'border-box', + originY: 0, + position: 'relative', + transform: CSS.Transform.toString(sortable.transform), + transition: transitions.length > 0 ? transitions.join(', ') : undefined, + background: 'var(--gray-2)', + border: `1px solid ${isActive ? 'var(--gray-10)' : 'transparent'}`, + borderRadius: 5, + opacity: !selected && !invertCutSegments ? 0.5 : undefined, + }; + }, [invertCutSegments, isActive, selected, sortable.isDragging, sortable.transform, sortable.transition]); + + const setRef = useCallback((node: HTMLDivElement | null) => { + sortable.setNodeRef(node); + ref.current = node; + }, [sortable]); return ( - -
+
{renderNumber()} {timeStr}
@@ -229,12 +262,12 @@ const Segment = memo(({ )} - {!invertCutSegments && ( + {!invertCutSegments && selected != null && (
- +
)} - +
); }); @@ -321,6 +354,7 @@ function SegmentList({ }) { const { t } = useTranslation(); const { getSegColor } = useSegColors(); + const [draggingId, setDraggingId] = useState(); const { invertCutSegments, simpleMode, darkMode } = useUserSettings(); @@ -334,11 +368,6 @@ function SegmentList({ const sortableList = useMemo(() => segmentsOrInverse.map((seg) => ({ id: seg.segId, seg })), [segmentsOrInverse]); - const setSortableList = useCallback((newList: typeof sortableList) => { - if (isEqual(segmentsOrInverse.map((s) => s.segId), newList.map((l) => l.id))) return; // No change - updateSegOrders(newList.map((list) => list.id)); - }, [segmentsOrInverse, updateSegOrders]); - let header: ReactNode = t('Segments to export:'); if (segmentsOrInverse.length === 0) { header = invertCutSegments ? ( @@ -458,6 +487,88 @@ function SegmentList({ onSegmentTagsCloseComplete(); }, [editingSegmentTags, editingSegmentTagsSegmentIndex, onSegmentTagsCloseComplete, updateSegAtIndex]); + const scrollerRef = useRef(null); + + const sensors = useSensors(useSensor(PointerSensor, { + activationConstraint: { + distance: 10, + }, + })); + + const rowVirtualizer = useVirtualizer({ + count: sortableList.length, + getScrollElement: () => scrollerRef.current, + estimateSize: () => 66, // todo this probably needs to be changed if the segment height changes + overscan: 5, + getItemKey: (index) => sortableList[index]!.id, + }); + + useEffect(() => { + if (invertCutSegments) return; + rowVirtualizer.scrollToIndex(currentSegIndex, { behavior: 'smooth', align: 'auto' }); + }, [currentSegIndex, invertCutSegments, rowVirtualizer]); + + const handleDragStart = (event: DragStartEvent) => { + setDraggingId(event.active.id); + }; + + const handleDragEnd = (event: DragEndEvent) => { + setDraggingId(undefined); + const { active, over } = event; + if (over != null && active.id !== over?.id) { + const ids = sortableList.map((s) => s.id); + const oldIndex = ids.indexOf(active.id as string); + const newIndex = ids.indexOf(over.id as string); + const newList = arrayMove(sortableList, oldIndex, newIndex); + updateSegOrders(newList.map((item) => item.id)); + } + }; + + const draggingSeg = useMemo(() => sortableList.find((s) => s.id === draggingId), [sortableList, draggingId]); + + function renderSegment({ seg, index, selected, isActive, dragging }: { + seg: StateSegment | InverseCutSegment, + index: number, + selected?: boolean, + isActive?: boolean, + dragging?: boolean, + }) { + return ( + + ); + } + return ( <> {editingSegmentTagsSegmentIndex != null && ( @@ -471,7 +582,7 @@ function SegmentList({ )} -
- - {sortableList.map(({ id, seg }, index) => { - const selected = 'selected' in seg ? seg.selected : true; - return ( - - ); - })} - -
+ + +
+
+ {rowVirtualizer.getVirtualItems().map((virtualRow) => { + const { id, seg } = sortableList[virtualRow.index]!; + const selected = 'selected' in seg ? seg.selected : true; + const isActive = !invertCutSegments && currentSegIndex === virtualRow.index; + + return ( +
+ {renderSegment({ seg, index: virtualRow.index, selected, isActive })} +
+ ); + })} +
+
+
+ + + {draggingSeg ? renderSegment({ seg: draggingSeg.seg, index: sortableList.indexOf(draggingSeg), dragging: true }) : null} + +
{renderFooter()}
diff --git a/src/renderer/src/components/BatchFile.tsx b/src/renderer/src/components/BatchFile.tsx index 196b8e56..7bf06290 100644 --- a/src/renderer/src/components/BatchFile.tsx +++ b/src/renderer/src/components/BatchFile.tsx @@ -1,33 +1,73 @@ -import { memo, useRef, useMemo } from 'react'; +import { memo, useRef, useMemo, useCallback, CSSProperties } from 'react'; import { useTranslation } from 'react-i18next'; import { FaAngleRight, FaFile } from 'react-icons/fa'; +import { useSortable } from '@dnd-kit/sortable'; +import { CSS } from '@dnd-kit/utilities'; import useContextMenu from '../hooks/useContextMenu'; import { primaryTextColor } from '../colors'; -function BatchFile({ path, index, isOpen, isSelected, name, onSelect, onDelete }: { +function BatchFile({ path, index, isOpen, isSelected, name, onSelect, onDelete, dragging }: { path: string, index: number, - isOpen: boolean, - isSelected: boolean, + isOpen?: boolean, + isSelected?: boolean, name: string, - onSelect: (a: string) => void, - onDelete: (a: string) => void, + onSelect?: (a: string) => void, + onDelete?: (a: string) => void, + dragging?: boolean, }) { - const ref = useRef(null); + const ref = useRef(null); const { t } = useTranslation(); const contextMenuTemplate = useMemo(() => [ - { label: t('Remove'), click: () => onDelete(path) }, + { label: t('Remove'), click: () => onDelete?.(path) }, ], [t, onDelete, path]); useContextMenu(ref, contextMenuTemplate); + const sortable = useSortable({ + id: path, + transition: { + duration: 150, + easing: 'ease-in-out', + }, + }); + + const setRef = useCallback((node: HTMLDivElement | null) => { + sortable.setNodeRef(node); + ref.current = node; + }, [sortable]); + + const style = useMemo(() => ({ + visibility: sortable.isDragging ? 'hidden' : undefined, + opacity: dragging ? 0.6 : 1, + transform: CSS.Transform.toString(sortable.transform), + transition: sortable.transition, + background: isSelected ? 'var(--gray-7)' : undefined, + cursor: dragging ? 'grabbing' : 'pointer', + fontSize: 13, + padding: '3px 6px', + display: 'flex', + alignItems: 'center', + alignContent: 'flex-start', + }), [sortable.isDragging, sortable.transform, sortable.transition, isSelected, dragging]); + return ( -
onSelect(path)}> +
onSelect?.(path)} + >
-
{index + 1}. {name}
+
{index + 1}. {name}
{isOpen && }
diff --git a/src/renderer/src/components/BatchFilesList.tsx b/src/renderer/src/components/BatchFilesList.tsx index ce4f3cdc..6f90d458 100644 --- a/src/renderer/src/components/BatchFilesList.tsx +++ b/src/renderer/src/components/BatchFilesList.tsx @@ -1,10 +1,12 @@ -import { DragEventHandler, memo, useCallback, useState } from 'react'; +import { DragEventHandler, memo, useCallback, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { motion } from 'framer-motion'; import { FaTimes, FaHatWizard } from 'react-icons/fa'; import { AiOutlineMergeCells } from 'react-icons/ai'; -import { ReactSortable } from 'react-sortablejs'; import { SortAlphabeticalIcon, SortAlphabeticalDescIcon } from 'evergreen-ui'; +import { DndContext, closestCenter, PointerSensor, useSensor, useSensors, DragEndEvent, DragStartEvent, DragOverlay, UniqueIdentifier } from '@dnd-kit/core'; +import { SortableContext, verticalListSortingStrategy, arrayMove } from '@dnd-kit/sortable'; +import { restrictToVerticalAxis } from '@dnd-kit/modifiers'; import BatchFile from './BatchFile'; import { controlsBackground, darkModeTransition, primaryColor } from '../colors'; @@ -37,13 +39,10 @@ function BatchFilesList({ selectedBatchFiles, filePath, width, batchFiles, setBa const { t } = useTranslation(); const [sortDesc, setSortDesc] = useState(); + const [draggingId, setDraggingId] = useState(); const sortableList = batchFiles.map((batchFile) => ({ id: batchFile.path, batchFile })); - const setSortableList = useCallback((newList: { batchFile: BatchFileType }[]) => { - setBatchFiles(newList.map(({ batchFile }) => batchFile)); - }, [setBatchFiles]); - const onSortClick = useCallback(() => { const newSortDesc = sortDesc == null ? false : !sortDesc; const sortedFiles = [...batchFiles]; @@ -56,6 +55,30 @@ function BatchFilesList({ selectedBatchFiles, filePath, width, batchFiles, setBa const SortIcon = sortDesc ? SortAlphabeticalDescIcon : SortAlphabeticalIcon; + const sensors = useSensors(useSensor(PointerSensor, { + activationConstraint: { + distance: 10, + }, + })); + + const handleDragStart = (event: DragStartEvent) => { + setDraggingId(event.active.id); + }; + + const handleDragEnd = (event: DragEndEvent) => { + setDraggingId(undefined); + const { active, over } = event; + if (over != null && active.id !== over?.id) { + const ids = sortableList.map((s) => s.id); + const oldIndex = ids.indexOf(active.id as string); + const newIndex = ids.indexOf(over.id as string); + const newList = arrayMove(sortableList, oldIndex, newIndex); + setBatchFiles(newList.map((item) => item.batchFile)); + } + }; + + const draggingFile = useMemo(() => sortableList.find((s) => s.id === draggingId), [sortableList, draggingId]); + return (
-
- - {sortableList.map(({ batchFile: { path, name } }, index) => ( - - ))} - -
+ + +
+ {sortableList.map(({ batchFile: { path, name } }, index) => ( + + ))} +
+
+ + + {draggingFile ? : null} + +
); } diff --git a/src/renderer/src/dialogs/index.tsx b/src/renderer/src/dialogs/index.tsx index 32d9a5ff..20d32de8 100644 --- a/src/renderer/src/dialogs/index.tsx +++ b/src/renderer/src/dialogs/index.tsx @@ -208,8 +208,8 @@ async function askForNumSegments() { const { value } = await Swal.fire({ input: 'number', inputAttributes: { - min: 0 as unknown as string, - max: maxSegments as unknown as string, + min: String(0), + max: String(maxSegments), }, showCancelButton: true, inputValue: '2', @@ -226,13 +226,13 @@ async function askForNumSegments() { return parseInt(value, 10); } -export async function createNumSegments(fileDuration: number) { +export async function createNumSegments(totalDuration: number) { const numSegments = await askForNumSegments(); if (numSegments == null) return undefined; const edl: { start: number, end: number }[] = []; - const segDuration = fileDuration / numSegments; + const segDuration = totalDuration / numSegments; for (let i = 0; i < numSegments; i += 1) { - edl.push({ start: i * segDuration, end: i === numSegments - 1 ? fileDuration : (i + 1) * segDuration }); + edl.push({ start: i * segDuration, end: i === numSegments - 1 ? totalDuration : (i + 1) * segDuration }); } return edl; } diff --git a/src/renderer/src/main.css b/src/renderer/src/main.css index 4ed81a62..cf2c260a 100644 --- a/src/renderer/src/main.css +++ b/src/renderer/src/main.css @@ -22,6 +22,7 @@ https://www.radix-ui.com/docs/colors/palette-composition/understanding-the-scale html { font-family: 'Open Sans', 'Noto Sans SemiCondensed', 'Noto Sans', sans-serif; font-size: 16px; + overflow: hidden; } body { @@ -82,11 +83,11 @@ code.highlighted { text-align: left; } -.segment-list-entry .enabled { +.segment-list-entry .selected { display: none; } -.segment-list-entry:hover .enabled { +.segment-list-entry:hover .selected { display: inherit; } diff --git a/yarn.lock b/yarn.lock index de3bf4ed..d93ab2b0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -290,6 +290,68 @@ __metadata: languageName: node linkType: hard +"@dnd-kit/accessibility@npm:^3.1.1": + version: 3.1.1 + resolution: "@dnd-kit/accessibility@npm:3.1.1" + dependencies: + tslib: "npm:^2.0.0" + peerDependencies: + react: ">=16.8.0" + checksum: 10/961000456a36700a9cd13be51147a818bc100f7dfabb332b80438d02e06f3b556aa0ff46ddf13bdff3b70bc8f9b63dd5a392cc285597ab1f7026e672660c54b6 + languageName: node + linkType: hard + +"@dnd-kit/core@npm:^6.3.1": + version: 6.3.1 + resolution: "@dnd-kit/core@npm:6.3.1" + dependencies: + "@dnd-kit/accessibility": "npm:^3.1.1" + "@dnd-kit/utilities": "npm:^3.2.2" + tslib: "npm:^2.0.0" + peerDependencies: + react: ">=16.8.0" + react-dom: ">=16.8.0" + checksum: 10/a5ae6fa8404765712aa80e308f58cb79bac9a306c274ec8272c405c2a59dd277d24b966348fe8ca6340bb3f0d75f90b8a021fa781edcf65255114d3cf2bef891 + languageName: node + linkType: hard + +"@dnd-kit/modifiers@npm:^9.0.0": + version: 9.0.0 + resolution: "@dnd-kit/modifiers@npm:9.0.0" + dependencies: + "@dnd-kit/utilities": "npm:^3.2.2" + tslib: "npm:^2.0.0" + peerDependencies: + "@dnd-kit/core": ^6.3.0 + react: ">=16.8.0" + checksum: 10/2ae238a1b787029e95d92319d7e4a0e2ffba8fceed56c4b58dfee7ed6890df207bf89ce522d4126411051121954222bd8e1444fae321485b594ae518c7c4397d + languageName: node + linkType: hard + +"@dnd-kit/sortable@npm:^10.0.0": + version: 10.0.0 + resolution: "@dnd-kit/sortable@npm:10.0.0" + dependencies: + "@dnd-kit/utilities": "npm:^3.2.2" + tslib: "npm:^2.0.0" + peerDependencies: + "@dnd-kit/core": ^6.3.0 + react: ">=16.8.0" + checksum: 10/bc61c25e76905204a53f91294b8116bf106fa27247eebca2c66478450b2051d7177115a384054e7e5639e6c4430083ade63056f79ee45f549da537cf05bc5288 + languageName: node + linkType: hard + +"@dnd-kit/utilities@npm:^3.2.2": + version: 3.2.2 + resolution: "@dnd-kit/utilities@npm:3.2.2" + dependencies: + tslib: "npm:^2.0.0" + peerDependencies: + react: ">=16.8.0" + checksum: 10/6cfe46a5fcdaced943982e7ae66b08b89235493e106eb5bc833737c25905e13375c6ecc3aa0c357d136cb21dae3966213dba063f19b7a60b1235a29a7b05ff84 + languageName: node + linkType: hard + "@electron/asar@npm:^3.2.1": version: 3.2.4 resolution: "@electron/asar@npm:3.2.4" @@ -1849,6 +1911,25 @@ __metadata: languageName: node linkType: hard +"@tanstack/react-virtual@npm:^3.13.10": + version: 3.13.10 + resolution: "@tanstack/react-virtual@npm:3.13.10" + dependencies: + "@tanstack/virtual-core": "npm:3.13.10" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10/3585a8ae112669b88268f47e8c78d17ac37c5a1eebccec98691d8254c53d32ee0ed3fc7baabeca7daf6a777e45898c9ca327295cb9c7f8408547d54de9e9e5ce + languageName: node + linkType: hard + +"@tanstack/virtual-core@npm:3.13.10": + version: 3.13.10 + resolution: "@tanstack/virtual-core@npm:3.13.10" + checksum: 10/75be98270bb4f689f5938ac875ed566de5324bc6c1e945cf750a7afeec226e338224d97448448a3f8b06ace8fafdfe09a535cfd3bd0f2c63e6ea43e1213e6d5d + languageName: node + linkType: hard + "@tokenizer/token@npm:^0.3.0": version: 0.3.0 resolution: "@tokenizer/token@npm:0.3.0" @@ -3626,13 +3707,6 @@ __metadata: languageName: node linkType: hard -"classnames@npm:2.3.1": - version: 2.3.1 - resolution: "classnames@npm:2.3.1" - checksum: 10/28fec94a815d5f570fa6cb4baaa4a7ae1466db3c8f704802f1330180db45d3b85ef8ae612f521fb37ce2cab1c3040d1d78061697b62987bc2909f26d1ad4321f - languageName: node - linkType: hard - "classnames@npm:^2.3.0": version: 2.3.2 resolution: "classnames@npm:2.3.2" @@ -7696,12 +7770,16 @@ __metadata: resolution: "lossless-cut@workspace:." dependencies: "@adamscybot/react-leaflet-component-marker": "npm:^2.0.0" + "@dnd-kit/core": "npm:^6.3.1" + "@dnd-kit/modifiers": "npm:^9.0.0" + "@dnd-kit/sortable": "npm:^10.0.0" "@electron/remote": "npm:^2.1.2" "@fontsource/open-sans": "npm:^4.5.14" "@octokit/core": "npm:5" "@radix-ui/colors": "npm:^3.0.0" "@radix-ui/react-checkbox": "npm:^1.2.3" "@radix-ui/react-switch": "npm:^1.2.2" + "@tanstack/react-virtual": "npm:^3.13.10" "@tsconfig/node18": "npm:^18.2.2" "@tsconfig/node20": "npm:^20.1.4" "@tsconfig/strictest": "npm:^2.0.2" @@ -7782,7 +7860,6 @@ __metadata: react-icons: "npm:^4.1.0" react-leaflet: "npm:^4.2.1" react-lottie-player: "npm:^1.5.0" - react-sortablejs: "npm:^6.1.4" react-syntax-highlighter: "npm:^15.4.3" react-use: "npm:^17.4.0" rimraf: "npm:^5.0.5" @@ -9255,21 +9332,6 @@ __metadata: languageName: node linkType: hard -"react-sortablejs@npm:^6.1.4": - version: 6.1.4 - resolution: "react-sortablejs@npm:6.1.4" - dependencies: - classnames: "npm:2.3.1" - tiny-invariant: "npm:1.2.0" - peerDependencies: - "@types/sortablejs": 1 - react: ">=16.9.0" - react-dom: ">=16.9.0" - sortablejs: 1 - checksum: 10/44e7ed04b437ab1f3636070ed65bcca237c0a4f6425a9c6cb5a0aa2d2a9a82b8e5e66d3d9995834adb49a65c828d86fca9a9909436f15be039aa0a09c2ae31b3 - languageName: node - linkType: hard - "react-syntax-highlighter@npm:^15.4.3": version: 15.4.5 resolution: "react-syntax-highlighter@npm:15.4.5" @@ -10777,13 +10839,6 @@ __metadata: languageName: node linkType: hard -"tiny-invariant@npm:1.2.0": - version: 1.2.0 - resolution: "tiny-invariant@npm:1.2.0" - checksum: 10/e09a718a7c4a499ba592cdac61f015d87427a0867ca07f50c11fd9b623f90cdba18937b515d4a5e4f43dac92370498d7bdaee0d0e7a377a61095e02c4a92eade - languageName: node - linkType: hard - "tiny-invariant@npm:^1.3.3": version: 1.3.3 resolution: "tiny-invariant@npm:1.3.3" @@ -10963,7 +11018,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.1.0, tslib@npm:^2.3.1, tslib@npm:^2.4.0": +"tslib@npm:^2.0.0, tslib@npm:^2.1.0, tslib@npm:^2.3.1, tslib@npm:^2.4.0": version: 2.8.1 resolution: "tslib@npm:2.8.1" checksum: 10/3e2e043d5c2316461cb54e5c7fe02c30ef6dccb3384717ca22ae5c6b5bc95232a6241df19c622d9c73b809bea33b187f6dbc73030963e29950c2141bc32a79f7