From 0e99609c3ab0c24e3f2f1419856409b85b98c0c8 Mon Sep 17 00:00:00 2001 From: davelopez <46503462+davelopez@users.noreply.github.com> Date: Fri, 6 Feb 2026 17:23:34 +0100 Subject: [PATCH 1/4] Adds badge indicators for staged uploads by method Enhances the upload method selection UI with dynamic badges showing the number of items staged for each method. Improves user awareness of pending uploads. --- .../Panels/Upload/UploadMethodList.vue | 20 +++++++++++++++++++ .../composables/upload/useUploadStaging.ts | 15 +++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/client/src/components/Panels/Upload/UploadMethodList.vue b/client/src/components/Panels/Upload/UploadMethodList.vue index d27950899d3..f6f55d6fe01 100644 --- a/client/src/components/Panels/Upload/UploadMethodList.vue +++ b/client/src/components/Panels/Upload/UploadMethodList.vue @@ -2,7 +2,9 @@ import { computed, ref } from "vue"; import { useRouter } from "vue-router/composables"; +import type { CardBadge } from "@/components/Common/GCard.types"; import { useConfig } from "@/composables/config"; +import { useUploadStagingCounts } from "@/composables/upload/useUploadStaging"; import type { UploadMethodConfig } from "./types"; import { useAllUploadMethods } from "./uploadMethodRegistry"; @@ -30,6 +32,7 @@ const query = ref(""); const searchInputClass = computed(() => (props.searchTeleportTarget ? "my-2" : props.inPanel ? "my-2" : "mb-3")); const allUploadMethods = useAllUploadMethods(); +const stagedCountsByMode = useUploadStagingCounts(); const availableMethods = computed(() => { if (!isConfigLoaded.value) { @@ -64,6 +67,22 @@ function selectUploadMethod(method: UploadMethodConfig) { function updateQuery(newQuery: string) { query.value = newQuery; } + +function getStagingBadges(method: UploadMethodConfig): CardBadge[] { + const count = stagedCountsByMode.value[method.id] ?? 0; + if (count === 0) { + return []; + } + return [ + { + id: `staged-${method.id}`, + label: String(count), + title: `${count} upload item${count === 1 ? "" : "s"} staged and pending submission`, + variant: "primary", + type: "badge", + }, + ]; +} diff --git a/client/src/composables/upload/useUploadStaging.ts b/client/src/composables/upload/useUploadStaging.ts index c14dab0bd60..4969c0a9d9d 100644 --- a/client/src/composables/upload/useUploadStaging.ts +++ b/client/src/composables/upload/useUploadStaging.ts @@ -1,4 +1,5 @@ -import { onMounted, type Ref, watch } from "vue"; +import { storeToRefs } from "pinia"; +import { computed, onMounted, type Ref, watch } from "vue"; import type { UploadMethod } from "@/components/Panels/Upload/types"; import { type StagedUploadItem, useUploadStagingStore } from "@/stores/uploadStagingStore"; @@ -46,3 +47,15 @@ export function useUploadStaging( clear, }; } + +export function useUploadStagingCounts() { + const { itemsByMode } = storeToRefs(useUploadStagingStore()); + + return computed>>(() => { + const counts: Partial> = {}; + for (const [mode, items] of Object.entries(itemsByMode.value)) { + counts[mode as UploadMethod] = items?.length ?? 0; + } + return counts; + }); +} From 21813445c12d8e96ac4104a702c34e1553b454d5 Mon Sep 17 00:00:00 2001 From: davelopez <46503462+davelopez@users.noreply.github.com> Date: Fri, 6 Feb 2026 17:27:30 +0100 Subject: [PATCH 2/4] Fixes Vue2 reactivity issue in staged upload items Addresses Vue2's limitations with object key reactivity by ensuring a new object reference is created when adding a new mode. Prevents stale data and ensures UI updates reliably when modifying staged upload item collections. --- client/src/stores/uploadStagingStore.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/client/src/stores/uploadStagingStore.ts b/client/src/stores/uploadStagingStore.ts index 23634159788..f670619d49a 100644 --- a/client/src/stores/uploadStagingStore.ts +++ b/client/src/stores/uploadStagingStore.ts @@ -27,12 +27,19 @@ export const useUploadStagingStore = defineStore("uploadStagingStore", () => { } function setItems(mode: UploadMethod, items: T[]) { - // always store a new array reference so reactivity triggers + // Ensure the key exists (Vue 2 key reactivity) with a one-time object replacement. + if (!(mode in itemsByMode.value)) { + itemsByMode.value = { ...itemsByMode.value, [mode]: [] }; + } + // Always store a new array reference so reactivity triggers. itemsByMode.value[mode] = [...items]; } function clearItems(mode: UploadMethod) { - delete itemsByMode.value[mode]; + if (!(mode in itemsByMode.value)) { + return; + } + itemsByMode.value[mode] = []; } return { From 6e51fde718cddc0296d9340bf00ee6fb11d12636 Mon Sep 17 00:00:00 2001 From: davelopez <46503462+davelopez@users.noreply.github.com> Date: Fri, 6 Feb 2026 17:34:51 +0100 Subject: [PATCH 3/4] Adds click handler to staging badge for method selection --- client/src/components/Panels/Upload/UploadMethodList.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/client/src/components/Panels/Upload/UploadMethodList.vue b/client/src/components/Panels/Upload/UploadMethodList.vue index f6f55d6fe01..5d154117387 100644 --- a/client/src/components/Panels/Upload/UploadMethodList.vue +++ b/client/src/components/Panels/Upload/UploadMethodList.vue @@ -80,6 +80,7 @@ function getStagingBadges(method: UploadMethodConfig): CardBadge[] { title: `${count} upload item${count === 1 ? "" : "s"} staged and pending submission`, variant: "primary", type: "badge", + handler: () => selectUploadMethod(method), }, ]; } From a395c2ac87ebef24068dd091199526955a781ad1 Mon Sep 17 00:00:00 2001 From: davelopez <46503462+davelopez@users.noreply.github.com> Date: Mon, 9 Feb 2026 13:16:41 +0100 Subject: [PATCH 4/4] Improves paste content item counting logic Ensures only non-empty paste content items are counted. --- .../composables/upload/useUploadStaging.ts | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/client/src/composables/upload/useUploadStaging.ts b/client/src/composables/upload/useUploadStaging.ts index 4969c0a9d9d..eebf4bb4c67 100644 --- a/client/src/composables/upload/useUploadStaging.ts +++ b/client/src/composables/upload/useUploadStaging.ts @@ -2,7 +2,9 @@ import { storeToRefs } from "pinia"; import { computed, onMounted, type Ref, watch } from "vue"; import type { UploadMethod } from "@/components/Panels/Upload/types"; -import { type StagedUploadItem, useUploadStagingStore } from "@/stores/uploadStagingStore"; +import type { PasteContentItem } from "@/components/Panels/Upload/types/uploadItem"; +import type { StagedUploadItem } from "@/stores/uploadStagingStore"; +import { useUploadStagingStore } from "@/stores/uploadStagingStore"; interface UseUploadStagingReturn { clear: () => void; @@ -54,8 +56,24 @@ export function useUploadStagingCounts() { return computed>>(() => { const counts: Partial> = {}; for (const [mode, items] of Object.entries(itemsByMode.value)) { - counts[mode as UploadMethod] = items?.length ?? 0; + const uploadMode = mode as UploadMethod; + counts[uploadMode] = countStagedItems(uploadMode, items ?? []); } return counts; }); } + +function countStagedItems(mode: UploadMethod, items: StagedUploadItem[]): number { + if (mode == "paste-content") { + return items.filter(isNonEmptyPasteContentItem).length; + } + return items.length; +} + +function isNonEmptyPasteContentItem(item: StagedUploadItem): item is PasteContentItem { + return isPasteContentItem(item) && item.content.trim().length > 0; +} + +export function isPasteContentItem(item: StagedUploadItem): item is PasteContentItem { + return "content" in item; +}