diff --git a/src/renderer/src/components/Storage/BurstCluster.vue b/src/renderer/src/components/Storage/BurstCluster.vue index 84a4eaf..06033a1 100644 --- a/src/renderer/src/components/Storage/BurstCluster.vue +++ b/src/renderer/src/components/Storage/BurstCluster.vue @@ -31,6 +31,7 @@ const props = defineProps(); const emit = defineEmits<{ (e: 'keep', clipId: number): void; (e: 'delete', clips: Clip[]): void; + (e: 'renamed', clip: Clip): void; }>(); const { formatBytes } = useFormat(); @@ -70,6 +71,7 @@ const publishedDoomed = computed(() => doomed.value.filter((clip) => clip.publis mode="keep" :keeper="clip.id === keeperId" @toggle="emit('keep', clip.id)" + @renamed="emit('renamed', $event)" /> diff --git a/src/renderer/src/components/Storage/BurstSection.vue b/src/renderer/src/components/Storage/BurstSection.vue index 63d96e7..9feb6a4 100644 --- a/src/renderer/src/components/Storage/BurstSection.vue +++ b/src/renderer/src/components/Storage/BurstSection.vue @@ -27,6 +27,7 @@ defineProps(); const emit = defineEmits<{ (e: 'keep', clusterIndex: number, clipId: number): void; (e: 'delete', clips: Clip[]): void; + (e: 'renamed', clip: Clip): void; }>(); @@ -73,6 +74,7 @@ const emit = defineEmits<{ :deleting="deleting" @keep="emit('keep', index, $event)" @delete="emit('delete', $event)" + @renamed="emit('renamed', $event)" /> diff --git a/src/renderer/src/components/Storage/GraveyardSection.vue b/src/renderer/src/components/Storage/GraveyardSection.vue index 265172d..c4b2406 100644 --- a/src/renderer/src/components/Storage/GraveyardSection.vue +++ b/src/renderer/src/components/Storage/GraveyardSection.vue @@ -35,6 +35,7 @@ const emit = defineEmits<{ (e: 'toggle', clipId: number): void; (e: 'toggle-group', game: string): void; (e: 'delete'): void; + (e: 'renamed', clip: Clip): void; }>(); const { formatBytes } = useFormat(); @@ -110,6 +111,7 @@ function groupSelected(clips: Clip[]): boolean { :clip="clip" :selected="selected.has(clip.id)" @toggle="emit('toggle', clip.id)" + @renamed="emit('renamed', $event)" /> diff --git a/src/renderer/src/components/Storage/StorageClipTile.vue b/src/renderer/src/components/Storage/StorageClipTile.vue index ad4bd45..0a194f0 100644 --- a/src/renderer/src/components/Storage/StorageClipTile.vue +++ b/src/renderer/src/components/Storage/StorageClipTile.vue @@ -12,6 +12,7 @@ import ClipGoodBitPips from '@renderer/components/Library/ClipGoodBitPips.vue'; import { clipGoodBitRanges } from '@renderer/utils/goodBits'; import { useClipDetail } from '@renderer/composables/clips/useClipDetail'; import { formatRelativeTime } from '@renderer/helpers/dateFormat'; +import ClipNameInput from '@renderer/components/Library/ClipNameInput.vue'; import type { Clip } from '@renderer/types/clip'; /** @@ -33,8 +34,8 @@ import type { Clip } from '@renderer/types/clip'; * library card, from the same `useHoverScrub` and the same setting, and * pressing it opens the clip itself, full size, where it plays and can be * trimmed or deleted. - * - **The box and the caption are the choice.** The checkbox in the corner and - * the name and size underneath tick it, or in a burst, keep it. + * - **The corner box is the choice.** It ticks the clip, or in a burst, keeps + * it. The name under the picture is a field: it renames the clip in place. * * The size is the loudest number on it, deliberately: it is the reason * somebody is looking at this list. @@ -49,7 +50,11 @@ interface Props { } const props = withDefaults(defineProps(), { mode: 'select' }); -const emit = defineEmits<{ (e: 'toggle'): void }>(); +const emit = defineEmits<{ + (e: 'toggle'): void; + /** Renamed in place. The row is handed up so the tile shows the new name at once. */ + (e: 'renamed', clip: Clip): void; +}>(); const { formatBytes } = useFormat(); const config = useConfiguration(); @@ -235,19 +240,21 @@ const frame = computed(() => [ - - + diff --git a/src/renderer/src/composables/library/useStorageSaver.ts b/src/renderer/src/composables/library/useStorageSaver.ts index d387e55..4225d7a 100644 --- a/src/renderer/src/composables/library/useStorageSaver.ts +++ b/src/renderer/src/composables/library/useStorageSaver.ts @@ -230,9 +230,40 @@ export function useStorageSaver() { } } + /** + * Put a changed row in place of the old one, and nothing else. + * + * For a rename typed on a tile: the tile has to show the new name, and the + * list must not move under somebody who may be about to fix a typo in it. + * A named clip does not belong on this screen, but taking it away is the + * reader's call, made from the toast, not something that happens mid-edit. + */ + function patch(changed: Clip): void { + const swap = (clips: Clip[]): Clip[] => clips.map((clip) => (clip.id === changed.id ? { ...clip, ...changed } : clip)); + if (unreviewed.value) { + unreviewed.value = { + ...unreviewed.value, + groups: unreviewed.value.groups.map((group) => ({ ...group, clips: swap(group.clips) })), + }; + } + if (bursts.value) { + bursts.value = { + ...bursts.value, + clusters: bursts.value.clusters.map((cluster) => ({ ...cluster, clips: swap(cluster.clips) })), + }; + } + } + + /** Take every clip that has since been named or noted off the screen. */ + async function refreshAll(): Promise { + await Promise.all([loadUnreviewed(), loadBursts()]); + } + return { unreviewed, refresh, + patch, + refreshAll, bursts, loadingUnreviewed, loadingBursts, diff --git a/src/renderer/src/views/StorageSaverPage.vue b/src/renderer/src/views/StorageSaverPage.vue index d28033d..e5aa186 100644 --- a/src/renderer/src/views/StorageSaverPage.vue +++ b/src/renderer/src/views/StorageSaverPage.vue @@ -1,6 +1,7 @@