From a32a1a35de072671a70ccf03e07cb3d1e03ffb6d Mon Sep 17 00:00:00 2001 From: Mikael Finstad Date: Tue, 7 Nov 2023 14:19:26 +0900 Subject: [PATCH] improve segment tags editor closes #1766 --- src/App.jsx | 4 +- src/SegmentList.jsx | 172 ++++++++++++++++++++++------------- src/StreamsSelector.jsx | 109 +++------------------- src/components/TagEditor.jsx | 100 ++++++++++++++++++++ src/dialogs/index.jsx | 22 +---- src/hooks/useSegments.js | 22 +---- 6 files changed, 227 insertions(+), 202 deletions(-) create mode 100644 src/components/TagEditor.jsx diff --git a/src/App.jsx b/src/App.jsx index e5de8e60..fe5c5447 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -361,7 +361,7 @@ const App = memo(() => { }, [isFileOpened]); const { - cutSegments, cutSegmentsHistory, createSegmentsFromKeyframes, shuffleSegments, detectBlackScenes, detectSilentScenes, detectSceneChanges, removeCutSegment, invertAllSegments, fillSegmentsGaps, combineOverlappingSegments, combineSelectedSegments, shiftAllSegmentTimes, alignSegmentTimesToKeyframes, onViewSegmentTags, updateSegOrder, updateSegOrders, reorderSegsByStartTime, addSegment, setCutStart, setCutEnd, onLabelSegment, splitCurrentSegment, createNumSegments, createFixedDurationSegments, createRandomSegments, apparentCutSegments, haveInvalidSegs, currentSegIndexSafe, currentCutSeg, currentApparentCutSeg, inverseCutSegments, clearSegments, loadCutSegments, isSegmentSelected, setCutTime, setCurrentSegIndex, onLabelSelectedSegments, deselectAllSegments, selectAllSegments, selectOnlyCurrentSegment, toggleCurrentSegmentSelected, invertSelectedSegments, removeSelectedSegments, setDeselectedSegmentIds, onSelectSegmentsByLabel, onSelectSegmentsByTag, toggleSegmentSelected, selectOnlySegment, getApparentCutSegmentById, selectedSegments, selectedSegmentsOrInverse, nonFilteredSegmentsOrInverse, segmentsToExport, duplicateCurrentSegment, duplicateSegment, + cutSegments, cutSegmentsHistory, createSegmentsFromKeyframes, shuffleSegments, detectBlackScenes, detectSilentScenes, detectSceneChanges, removeCutSegment, invertAllSegments, fillSegmentsGaps, combineOverlappingSegments, combineSelectedSegments, shiftAllSegmentTimes, alignSegmentTimesToKeyframes, updateSegOrder, updateSegOrders, reorderSegsByStartTime, addSegment, setCutStart, setCutEnd, onLabelSegment, splitCurrentSegment, createNumSegments, createFixedDurationSegments, createRandomSegments, apparentCutSegments, haveInvalidSegs, currentSegIndexSafe, currentCutSeg, currentApparentCutSeg, inverseCutSegments, clearSegments, loadCutSegments, isSegmentSelected, setCutTime, setCurrentSegIndex, onLabelSelectedSegments, deselectAllSegments, selectAllSegments, selectOnlyCurrentSegment, toggleCurrentSegmentSelected, invertSelectedSegments, removeSelectedSegments, setDeselectedSegmentIds, onSelectSegmentsByLabel, onSelectSegmentsByTag, toggleSegmentSelected, selectOnlySegment, getApparentCutSegmentById, selectedSegments, selectedSegmentsOrInverse, nonFilteredSegmentsOrInverse, segmentsToExport, duplicateCurrentSegment, duplicateSegment, updateSegAtIndex, } = useSegments({ filePath, workingRef, setWorking, setCutProgress, mainVideoStream, duration, getRelevantTime, maxLabelLength, checkFileOpened, invertCutSegments, segmentsToChaptersOnly }); const jumpSegStart = useCallback((index) => userSeekAbs(apparentCutSegments[index].start), [apparentCutSegments, userSeekAbs]); @@ -2418,10 +2418,10 @@ const App = memo(() => { onExtractSegmentFramesAsImages={extractSegmentFramesAsImages} jumpSegStart={jumpSegStart} jumpSegEnd={jumpSegEnd} - onViewSegmentTags={onViewSegmentTags} onSelectSegmentsByLabel={onSelectSegmentsByLabel} onSelectSegmentsByTag={onSelectSegmentsByTag} onLabelSelectedSegments={onLabelSelectedSegments} + updateSegAtIndex={updateSegAtIndex} /> )} diff --git a/src/SegmentList.jsx b/src/SegmentList.jsx index eee6e748..aa228fb8 100644 --- a/src/SegmentList.jsx +++ b/src/SegmentList.jsx @@ -1,4 +1,4 @@ -import React, { memo, useMemo, useRef, useCallback } from 'react'; +import React, { memo, useMemo, useRef, useCallback, useState } from 'react'; import { FaYinYang, FaSave, FaPlus, FaMinus, FaTag, FaSortNumericDown, FaAngleRight, FaRegCheckCircle, FaRegCircle } from 'react-icons/fa'; import { AiOutlineSplitCells } from 'react-icons/ai'; import { motion } from 'framer-motion'; @@ -7,6 +7,7 @@ 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 { Dialog } from 'evergreen-ui'; import Swal from './swal'; import useContextMenu from './hooks/useContextMenu'; @@ -15,6 +16,7 @@ import { saveColor, controlsBackground, primaryTextColor, darkModeTransition } f import { useSegColors } from './contexts'; import { mySpring } from './animations'; import { getSegmentTags } from './segments'; +import TagEditor from './components/TagEditor'; const buttonBaseStyle = { margin: '0 3px', borderRadius: 3, color: 'white', cursor: 'pointer', @@ -23,7 +25,7 @@ const buttonBaseStyle = { const neutralButtonColor = 'var(--gray8)'; -const Segment = memo(({ darkMode, seg, index, currentSegIndex, formatTimecode, getFrameCount, updateOrder, invertCutSegments, onClick, onRemovePress, onRemoveSelected, onLabelSelectedSegments, onReorderPress, onLabelPress, selected, onSelectSingleSegment, onToggleSegmentSelected, onDeselectAllSegments, onSelectSegmentsByLabel, onSelectSegmentsByTag, onSelectAllSegments, jumpSegStart, jumpSegEnd, addSegment, onViewSegmentTags, onExtractSegmentFramesAsImages, onInvertSelectedSegments, onDuplicateSegmentClick }) => { +const Segment = memo(({ darkMode, seg, index, currentSegIndex, formatTimecode, getFrameCount, updateOrder, invertCutSegments, onClick, onRemovePress, onRemoveSelected, onLabelSelectedSegments, onReorderPress, onLabelPress, selected, onSelectSingleSegment, onToggleSegmentSelected, onDeselectAllSegments, onSelectSegmentsByLabel, onSelectSegmentsByTag, onSelectAllSegments, jumpSegStart, jumpSegEnd, addSegment, onEditSegmentTags, onExtractSegmentFramesAsImages, onInvertSelectedSegments, onDuplicateSegmentClick }) => { const { t } = useTranslation(); const { getSegColor } = useSegColors(); @@ -64,10 +66,10 @@ const Segment = memo(({ darkMode, seg, index, currentSegIndex, formatTimecode, g { type: 'separator' }, - { label: t('Segment tags'), click: () => onViewSegmentTags(index) }, + { label: t('Segment tags'), click: () => onEditSegmentTags(index) }, { label: t('Extract frames as image files'), click: () => onExtractSegmentFramesAsImages([seg.segId]) }, ]; - }, [invertCutSegments, t, jumpSegStart, jumpSegEnd, addSegment, onLabelPress, onRemovePress, onLabelSelectedSegments, onRemoveSelected, onReorderPress, onDuplicateSegmentClick, seg, onSelectSingleSegment, onSelectAllSegments, onDeselectAllSegments, onSelectSegmentsByLabel, onSelectSegmentsByTag, onInvertSelectedSegments, updateOrder, onViewSegmentTags, index, onExtractSegmentFramesAsImages]); + }, [invertCutSegments, t, jumpSegStart, jumpSegEnd, addSegment, onLabelPress, onRemovePress, onLabelSelectedSegments, onRemoveSelected, onReorderPress, onDuplicateSegmentClick, seg, onSelectSingleSegment, onSelectAllSegments, onDeselectAllSegments, onSelectSegmentsByLabel, onSelectSegmentsByTag, onInvertSelectedSegments, updateOrder, onEditSegmentTags, index, onExtractSegmentFramesAsImages]); useContextMenu(ref, contextMenuTemplate); @@ -159,7 +161,7 @@ const SegmentList = memo(({ updateSegOrder, updateSegOrders, addSegment, removeCutSegment, onRemoveSelected, onLabelSegment, currentCutSeg, segmentAtCursor, toggleSegmentsList, splitCurrentSegment, selectedSegments, isSegmentSelected, onSelectSingleSegment, onToggleSegmentSelected, onDeselectAllSegments, onSelectAllSegments, onSelectSegmentsByLabel, onSelectSegmentsByTag, onExtractSegmentFramesAsImages, onLabelSelectedSegments, onInvertSelectedSegments, onDuplicateSegmentClick, - jumpSegStart, jumpSegEnd, onViewSegmentTags, + jumpSegStart, jumpSegEnd, updateSegAtIndex, }) => { const { t } = useTranslation(); const { getSegColor } = useSegColors(); @@ -269,69 +271,111 @@ const SegmentList = memo(({ ); } + const [editingSegmentTagsSegmentIndex, setEditingSegmentTagsSegmentIndex] = useState(); + const [editingSegmentTags, setEditingSegmentTags] = useState(); + const [editingTag, setEditingTag] = useState(); + + const onTagChange = useCallback((tag, value) => setEditingSegmentTags((existingTags) => ({ + ...existingTags, + [tag]: value, + })), []); + + const onTagReset = useCallback((tag) => setEditingSegmentTags(({ [tag]: deleted, ...rest }) => rest), []); + + const onEditSegmentTags = useCallback((index) => { + setEditingSegmentTagsSegmentIndex(index); + setEditingSegmentTags(getSegmentTags(apparentCutSegments[index])); + }, [apparentCutSegments]); + + const onSegmentTagsCloseComplete = useCallback(() => { + setEditingSegmentTagsSegmentIndex(); + setEditingSegmentTags(); + }, []); + + const onSegmentTagsConfirm = useCallback(() => { + updateSegAtIndex(editingSegmentTagsSegmentIndex, { tags: editingSegmentTags }); + onSegmentTagsCloseComplete(); + }, [editingSegmentTags, editingSegmentTagsSegmentIndex, onSegmentTagsCloseComplete, updateSegAtIndex]); + return ( - -
- + <> + +
+ +
+
- {header} -
+ +
+ -
- - {sortableList.map(({ id, seg }, index) => { - const selected = !invertCutSegments && isSegmentSelected({ segId: seg.segId }); - return ( - removeCutSegment(index)} - onReorderPress={() => onReorderSegs(index)} - onLabelPress={() => onLabelSegment(index)} - jumpSegStart={() => jumpSegStart(index)} - jumpSegEnd={() => jumpSegEnd(index)} - updateOrder={(dir) => updateSegOrder(index, index + dir)} - getFrameCount={getFrameCount} - formatTimecode={formatTimecode} - currentSegIndex={currentSegIndex} - invertCutSegments={invertCutSegments} - onSelectSingleSegment={onSelectSingleSegment} - onToggleSegmentSelected={onToggleSegmentSelected} - onDeselectAllSegments={onDeselectAllSegments} - onSelectAllSegments={onSelectAllSegments} - onViewSegmentTags={onViewSegmentTags} - onSelectSegmentsByLabel={onSelectSegmentsByLabel} - onSelectSegmentsByTag={onSelectSegmentsByTag} - onExtractSegmentFramesAsImages={onExtractSegmentFramesAsImages} - onLabelSelectedSegments={onLabelSelectedSegments} - onInvertSelectedSegments={onInvertSelectedSegments} - onDuplicateSegmentClick={onDuplicateSegmentClick} - /> - ); - })} - -
+ {header} +
- {segments.length > 0 && renderFooter()} -
+
+ + {sortableList.map(({ id, seg }, index) => { + const selected = !invertCutSegments && isSegmentSelected({ segId: seg.segId }); + return ( + removeCutSegment(index)} + onReorderPress={() => onReorderSegs(index)} + onLabelPress={() => onLabelSegment(index)} + jumpSegStart={() => jumpSegStart(index)} + jumpSegEnd={() => jumpSegEnd(index)} + updateOrder={(dir) => updateSegOrder(index, index + dir)} + getFrameCount={getFrameCount} + formatTimecode={formatTimecode} + currentSegIndex={currentSegIndex} + invertCutSegments={invertCutSegments} + onSelectSingleSegment={onSelectSingleSegment} + onToggleSegmentSelected={onToggleSegmentSelected} + onDeselectAllSegments={onDeselectAllSegments} + onSelectAllSegments={onSelectAllSegments} + onEditSegmentTags={onEditSegmentTags} + onSelectSegmentsByLabel={onSelectSegmentsByLabel} + onSelectSegmentsByTag={onSelectSegmentsByTag} + onExtractSegmentFramesAsImages={onExtractSegmentFramesAsImages} + onLabelSelectedSegments={onLabelSelectedSegments} + onInvertSelectedSegments={onInvertSelectedSegments} + onDuplicateSegmentClick={onDuplicateSegmentClick} + /> + ); + })} + +
+ + {segments.length > 0 && renderFooter()} +
+ ); }); diff --git a/src/StreamsSelector.jsx b/src/StreamsSelector.jsx index 4b05babc..b7ac8f61 100644 --- a/src/StreamsSelector.jsx +++ b/src/StreamsSelector.jsx @@ -1,117 +1,28 @@ -import React, { memo, useState, useMemo, useCallback, useRef, useEffect } from 'react'; +import React, { memo, useState, useMemo, useCallback } from 'react'; import { FaImage, FaCheckCircle, FaPaperclip, FaVideo, FaVideoSlash, FaFileImport, FaVolumeUp, FaVolumeMute, FaBan, FaFileExport } from 'react-icons/fa'; import { GoFileBinary } from 'react-icons/go'; -import { FiEdit, FiCheck, FiTrash } from 'react-icons/fi'; import { MdSubtitles } from 'react-icons/md'; -import { Checkbox, BookIcon, TextInput, MoreIcon, Position, Popover, Menu, TrashIcon, EditIcon, InfoSignIcon, IconButton, Heading, SortAscIcon, SortDescIcon, Dialog, Button, PlusIcon, ForkIcon, WarningSignIcon } from 'evergreen-ui'; +import { Checkbox, BookIcon, MoreIcon, Position, Popover, Menu, TrashIcon, EditIcon, InfoSignIcon, IconButton, Heading, SortAscIcon, SortDescIcon, Dialog, Button, ForkIcon, WarningSignIcon } from 'evergreen-ui'; import { useTranslation } from 'react-i18next'; import prettyBytes from 'pretty-bytes'; import AutoExportToggler from './components/AutoExportToggler'; import Select from './components/Select'; -import { askForMetadataKey, showJson5Dialog } from './dialogs'; +import { showJson5Dialog } from './dialogs'; import { formatDuration } from './util/duration'; import { getStreamFps } from './ffmpeg'; import { deleteDispositionValue } from './util'; import { getActiveDisposition, attachedPicDisposition } from './util/streams'; +import TagEditor from './components/TagEditor'; -const activeColor = '#429777'; - const dispositionOptions = ['default', 'dub', 'original', 'comment', 'lyrics', 'karaoke', 'forced', 'hearing_impaired', 'visual_impaired', 'clean_effects', 'attached_pic', 'captions', 'descriptions', 'dependent', 'metadata']; const unchangedDispositionValue = 'llc_disposition_unchanged'; -const TagEditor = memo(({ existingTags, customTags, editingTag, setEditingTag, onTagChange, onTagReset }) => { + +const EditFileDialog = memo(({ editingFile, allFilesMeta, customTagsByFile, setCustomTagsByFile, editingTag, setEditingTag }) => { const { t } = useTranslation(); - const ref = useRef(); - - const [editingTagVal, setEditingTagVal] = useState(); - const [newTag, setNewTag] = useState(); - - const mergedTags = useMemo(() => ({ ...existingTags, ...customTags, ...(newTag ? { [newTag]: '' } : {}) }), [customTags, existingTags, newTag]); - - const onResetClick = useCallback(() => { - onTagReset(editingTag); - setEditingTag(); - setNewTag(); - }, [editingTag, onTagReset, setEditingTag]); - - function onEditClick(tag) { - if (newTag) { - onTagChange(editingTag, editingTagVal); - setEditingTag(); - setNewTag(); - } else if (editingTag != null) { - if (editingTagVal !== existingTags[editingTag]) { - onTagChange(editingTag, editingTagVal); - setEditingTag(); - } else { // If not actually changed, no need to update - onResetClick(); - } - } else { - setEditingTag(tag); - setEditingTagVal(mergedTags[tag]); - } - } - - function onSubmit(e) { - e.preventDefault(); - onEditClick(); - } - - const onAddPress = useCallback(async (e) => { - e.preventDefault(); - e.target.blur(); - const tag = await askForMetadataKey(); - if (!tag || Object.keys(mergedTags).includes(tag)) return; - setEditingTag(tag); - setEditingTagVal(''); - setNewTag(tag); - }, [mergedTags, setEditingTag]); - - useEffect(() => { - ref.current?.focus(); - }, [editingTag]); - - return ( - <> - - - {Object.keys(mergedTags).map((tag) => { - const editingThis = tag === editingTag; - const Icon = editingThis ? FiCheck : FiEdit; - const thisTagCustom = customTags[tag] != null; - const thisTagNew = existingTags[tag] == null; - - return ( - - - - - - ); - })} - -
{tag} - {editingThis ? ( -
- setEditingTagVal(e.target.value)} /> - - ) : ( - {mergedTags[tag]} - )} - {(editingTag == null || editingThis) && onEditClick(tag)} />} - {editingThis && } -
- - - - ); -}); - -const EditFileDialog = memo(({ editingFile, allFilesMeta, customTagsByFile, setCustomTagsByFile }) => { - const [editingTag, setEditingTag] = useState(); const { formatData } = allFilesMeta[editingFile]; const existingTags = formatData.tags || {}; @@ -128,7 +39,7 @@ const EditFileDialog = memo(({ editingFile, allFilesMeta, customTagsByFile, setC }); }, [editingFile, setCustomTagsByFile]); - return ; + return ; }); const getStreamDispositionsObj = (stream) => ((stream && stream.disposition) || {}); @@ -208,7 +119,7 @@ const EditStreamDialog = memo(({ editingStream: { streamId: editingStreamId, pat Parameters updateStreamParams(editingFile, editingStreamId, setter)} /> Tags - + ); @@ -387,6 +298,7 @@ const StreamsSelector = memo(({ const [editingFile, setEditingFile] = useState(); const [editingStream, setEditingStream] = useState(); const { t } = useTranslation(); + const [editingTag, setEditingTag] = useState(); function getFormatDuration(formatData) { if (!formatData || !formatData.duration) return undefined; @@ -507,8 +419,9 @@ const StreamsSelector = memo(({ hasCancel={false} confirmLabel={t('Done')} onCloseComplete={() => setEditingFile()} + isConfirmDisabled={editingTag != null} > - + {editingStream != null && ( diff --git a/src/components/TagEditor.jsx b/src/components/TagEditor.jsx new file mode 100644 index 00000000..de7df1dd --- /dev/null +++ b/src/components/TagEditor.jsx @@ -0,0 +1,100 @@ +import React, { memo, useRef, useState, useMemo, useCallback, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; +import { TextInput, TrashIcon, TickIcon, EditIcon, PlusIcon, Button, IconButton } from 'evergreen-ui'; + +import { askForMetadataKey } from '../dialogs'; + + +const activeColor = '#429777'; + +const emptyObject = {}; + +function TagEditor({ existingTags = emptyObject, customTags = emptyObject, editingTag, setEditingTag, onTagChange, onTagReset, addTagTitle, addTagText }) { + const { t } = useTranslation(); + const ref = useRef(); + + const [editingTagVal, setEditingTagVal] = useState(); + const [newTag, setNewTag] = useState(); + + const mergedTags = useMemo(() => ({ ...existingTags, ...customTags, ...(newTag ? { [newTag]: '' } : {}) }), [customTags, existingTags, newTag]); + + const onResetClick = useCallback(() => { + onTagReset(editingTag); + setEditingTag(); + setNewTag(); + }, [editingTag, onTagReset, setEditingTag]); + + function onEditClick(tag) { + if (newTag) { + onTagChange(editingTag, editingTagVal); + setEditingTag(); + setNewTag(); + } else if (editingTag != null) { + if (editingTagVal !== existingTags[editingTag]) { + onTagChange(editingTag, editingTagVal); + setEditingTag(); + } else { // If not actually changed, no need to update + onResetClick(); + } + } else { + setEditingTag(tag); + setEditingTagVal(mergedTags[tag]); + } + } + + function onSubmit(e) { + e.preventDefault(); + onEditClick(); + } + + const onAddPress = useCallback(async (e) => { + e.preventDefault(); + e.target.blur(); + const tag = await askForMetadataKey({ title: addTagTitle, text: addTagText }); + if (!tag || Object.keys(mergedTags).includes(tag)) return; + setEditingTag(tag); + setEditingTagVal(''); + setNewTag(tag); + }, [addTagText, addTagTitle, mergedTags, setEditingTag]); + + useEffect(() => { + ref.current?.focus(); + }, [editingTag]); + + return ( + <> + + + {Object.keys(mergedTags).map((tag) => { + const editingThis = tag === editingTag; + const Icon = editingThis ? TickIcon : EditIcon; + const thisTagCustom = customTags[tag] != null; + const thisTagNew = existingTags[tag] == null; + + return ( + + + + + + ); + })} + +
{tag} + {editingThis ? ( +
+ setEditingTagVal(e.target.value)} /> + + ) : ( + {mergedTags[tag] || `<${t('empty')}>`} + )} + {(editingTag == null || editingThis) && onEditClick(tag)} intent={editingThis ? 'success' : 'none'} />} + {editingThis && } +
+ + + + ); +} + +export default memo(TagEditor); diff --git a/src/dialogs/index.jsx b/src/dialogs/index.jsx index 9c696f90..426e80fe 100644 --- a/src/dialogs/index.jsx +++ b/src/dialogs/index.jsx @@ -310,13 +310,13 @@ export async function askForAlignSegments() { }; } -export async function askForMetadataKey() { +export async function askForMetadataKey({ title, text }) { const { value } = await Swal.fire({ - title: i18n.t('Add metadata'), - text: i18n.t('Enter metadata key'), + title, + text, input: 'text', showCancelButton: true, - inputPlaceholder: 'metadata_key', + inputPlaceholder: 'key', inputValidator: (v) => v.includes('=') && i18n.t('Invalid character(s) found in key'), }); return value; @@ -525,20 +525,6 @@ export async function selectSegmentsByTagDialog() { return { tagName: value1, tagValue: value2 }; } -export async function showEditableJsonDialog({ text, title, inputLabel, inputValue, inputValidator }) { - const { value } = await Swal.fire({ - input: 'textarea', - inputLabel, - text, - title, - inputPlaceholder: JSON5.stringify({ exampleTag: 'Example value' }, null, 2), - inputValue, - showCancelButton: true, - inputValidator, - }); - return value; -} - export function showJson5Dialog({ title, json }) { const html = ( diff --git a/src/hooks/useSegments.js b/src/hooks/useSegments.js index 14efe590..1b0741ed 100644 --- a/src/hooks/useSegments.js +++ b/src/hooks/useSegments.js @@ -1,7 +1,6 @@ import { useCallback, useRef, useMemo, useState } from 'react'; import { useStateWithHistory } from 'react-use/lib/useStateWithHistory'; import i18n from 'i18next'; -import JSON5 from 'json5'; import pMap from 'p-map'; import sortBy from 'lodash/sortBy'; @@ -10,7 +9,7 @@ import { detectSceneChanges as ffmpegDetectSceneChanges, readFrames, mapTimesToS import { handleError, shuffleArray } from '../util'; import { errorToast } from '../swal'; import { showParametersDialog } from '../dialogs/parameters'; -import { createNumSegments as createNumSegmentsDialog, createFixedDurationSegments as createFixedDurationSegmentsDialog, createRandomSegments as createRandomSegmentsDialog, labelSegmentDialog, showEditableJsonDialog, askForShiftSegments, askForAlignSegments, selectSegmentsByLabelDialog, selectSegmentsByTagDialog } from '../dialogs'; +import { createNumSegments as createNumSegmentsDialog, createFixedDurationSegments as createFixedDurationSegmentsDialog, createRandomSegments as createRandomSegmentsDialog, labelSegmentDialog, askForShiftSegments, askForAlignSegments, selectSegmentsByLabelDialog, selectSegmentsByTagDialog } from '../dialogs'; import { createSegment, findSegmentsAtCursor, sortSegments, invertSegments, getSegmentTags, combineOverlappingSegments as combineOverlappingSegments2, combineSelectedSegments as combineSelectedSegments2, isDurationValid, getSegApparentStart, getSegApparentEnd as getSegApparentEnd2 } from '../segments'; import * as ffmpegParameters from '../ffmpeg-parameters'; import { maxSegmentsAllowed } from '../util/constants'; @@ -273,22 +272,6 @@ export default ({ } }, [filePath, mainVideoStream, modifySelectedSegmentTimes, setWorking, workingRef]); - const onViewSegmentTags = useCallback(async (index) => { - const segment = cutSegments[index]; - function inputValidator(jsonStr) { - try { - const json = JSON5.parse(jsonStr); - if (!(typeof json === 'object' && Object.values(json).every((val) => typeof val === 'string'))) throw new Error(); - return undefined; - } catch (err) { - return i18n.t('Invalid JSON'); - } - } - const tags = getSegmentTags(segment); - const newTagsStr = await showEditableJsonDialog({ title: i18n.t('Segment tags'), text: i18n.t('View and edit segment tags in JSON5 format:'), inputValue: Object.keys(tags).length > 0 ? JSON5.stringify(tags, null, 2) : '', inputValidator }); - if (newTagsStr != null) updateSegAtIndex(index, { tags: JSON5.parse(newTagsStr) }); - }, [cutSegments, updateSegAtIndex]); - const updateSegOrder = useCallback((index, newOrder) => { if (newOrder > cutSegments.length - 1 || newOrder < 0) return; const newSegments = [...cutSegments]; @@ -465,7 +448,6 @@ export default ({ enableSegments(segmentsToEnable); }, [cutSegments, enableSegments]); - const onLabelSelectedSegments = useCallback(async () => { if (selectedSegmentsRaw.length < 1) return; const { name } = selectedSegmentsRaw[0]; @@ -517,7 +499,6 @@ export default ({ combineSelectedSegments, shiftAllSegmentTimes, alignSegmentTimesToKeyframes, - onViewSegmentTags, updateSegOrder, updateSegOrders, reorderSegsByStartTime, @@ -561,5 +542,6 @@ export default ({ toggleSegmentSelected, selectOnlySegment, setCutTime, + updateSegAtIndex, }; };