Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/renderer/src/components/Storage/BurstCluster.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const props = defineProps<Props>();
const emit = defineEmits<{
(e: 'keep', clipId: number): void;
(e: 'delete', clips: Clip[]): void;
(e: 'renamed', clip: Clip): void;
}>();

const { formatBytes } = useFormat();
Expand Down Expand Up @@ -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)"
/>
</div>

Expand Down
2 changes: 2 additions & 0 deletions src/renderer/src/components/Storage/BurstSection.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ defineProps<Props>();
const emit = defineEmits<{
(e: 'keep', clusterIndex: number, clipId: number): void;
(e: 'delete', clips: Clip[]): void;
(e: 'renamed', clip: Clip): void;
}>();
</script>

Expand Down Expand Up @@ -73,6 +74,7 @@ const emit = defineEmits<{
:deleting="deleting"
@keep="emit('keep', index, $event)"
@delete="emit('delete', $event)"
@renamed="emit('renamed', $event)"
/>
</div>
</template>
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/src/components/Storage/GraveyardSection.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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)"
/>
</div>
</div>
Expand Down
31 changes: 19 additions & 12 deletions src/renderer/src/components/Storage/StorageClipTile.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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';

/**
Expand All @@ -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.
Expand All @@ -49,7 +50,11 @@ interface Props {
}

const props = withDefaults(defineProps<Props>(), { 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();
Expand Down Expand Up @@ -235,19 +240,21 @@ const frame = computed(() => [
</span>
</button>

<!-- The caption is part of the choice too: the name and the size, pressed, pick it. -->
<button
type="button"
:class="['block w-full px-2 py-1.5 text-left', FOCUS_RING]"
:aria-label="choiceLabel"
@click="emit('toggle')"
>
<div class="truncate text-xs text-foreground">{{ title }}</div>
<!--
The name can be changed right here, with the same field a library card
uses: it saves on Enter or on leaving it, never while somebody is typing.
Picking is the corner box's job alone, so a click on the name edits it
rather than ticking the clip behind somebody's back.
-->
<div class="px-2 py-1.5">
<div class="flex text-xs">
<ClipNameInput :clip="clip" @updated="emit('renamed', $event)" />
</div>
<div class="flex items-center gap-1.5 font-mono text-[11px] text-muted-400">
<span class="tabular-nums">{{ formatBytes(clip.sizeBytes) }}</span>
<span aria-hidden="true">路</span>
<span class="truncate">{{ age }}</span>
</div>
</button>
</div>
</div>
</template>
31 changes: 31 additions & 0 deletions src/renderer/src/composables/library/useStorageSaver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> {
await Promise.all([loadUnreviewed(), loadBursts()]);
}

return {
unreviewed,
refresh,
patch,
refreshAll,
bursts,
loadingUnreviewed,
loadingBursts,
Expand Down
23 changes: 23 additions & 0 deletions src/renderer/src/views/StorageSaverPage.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script setup lang="ts">
import { computed, onMounted, watch } from 'vue';
import { useClipDetail } from '@renderer/composables/clips/useClipDetail';
import { useToastStore } from '@renderer/stores/toast';
import { useStorageSaver } from '@renderer/composables/library/useStorageSaver';
import { useAppSettings } from '@renderer/composables/app/useAppSettings';
import GraveyardSection from '@renderer/components/Storage/GraveyardSection.vue';
Expand Down Expand Up @@ -46,6 +47,26 @@ watch(openClipId, (now, before) => {
if (now === null && before !== null && before !== undefined) void saver.refresh(before);
});

/*
* A clip renamed on its tile keeps its place until the reader says otherwise.
*
* Refreshing on save would pull the tile away from somebody who pressed Enter
* on a typo and was about to fix it. So the new name shows at once, the list
* stays still, and the toast says why the clip will go and offers to do it.
*/
const toast = useToastStore();
function renamed(clip: Clip): void {
saver.patch(clip);
if (!clip.displayName?.trim()) return;
toast.show({
type: 'success',
title: 'Renamed',
description: 'A clip with a name is one you want, so it leaves this list when you refresh it.',
duration: 8000,
action: { label: 'Refresh list', onClick: () => void saver.refreshAll() },
});
}

function deleteSelected(): void {
void saver.remove(saver.selectedClips.value, 'Delete these clips?');
}
Expand Down Expand Up @@ -73,6 +94,7 @@ function toggleGroup(game: string): void {
@toggle="saver.toggle"
@toggle-group="toggleGroup"
@delete="deleteSelected"
@renamed="renamed"
/>

<BurstSection
Expand All @@ -83,6 +105,7 @@ function toggleGroup(game: string): void {
:window-sec="windowSec"
@keep="saver.keep"
@delete="deleteCluster"
@renamed="renamed"
/>
</div>
</template>
Loading