diff --git a/client/src/components/Panels/Upload/UploadMethodList.vue b/client/src/components/Panels/Upload/UploadMethodList.vue
index 008e2c4c327..2f40bbb495e 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,23 @@ 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",
+ handler: () => selectUploadMethod(method),
+ },
+ ];
+}
@@ -95,6 +115,7 @@ function updateQuery(newQuery: string) {
:full-description="true"
:description="method.description"
:title-icon="{ icon: method.icon, class: 'text-primary', size: 'lg' }"
+ :badges="getStagingBadges(method)"
:data-method-id="method.id"
@click="selectUploadMethod(method)" />
diff --git a/client/src/composables/upload/useUploadStaging.ts b/client/src/composables/upload/useUploadStaging.ts
index c14dab0bd60..eebf4bb4c67 100644
--- a/client/src/composables/upload/useUploadStaging.ts
+++ b/client/src/composables/upload/useUploadStaging.ts
@@ -1,7 +1,10 @@
-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";
+import type { PasteContentItem } from "@/components/Panels/Upload/types/uploadItem";
+import type { StagedUploadItem } from "@/stores/uploadStagingStore";
+import { useUploadStagingStore } from "@/stores/uploadStagingStore";
interface UseUploadStagingReturn {
clear: () => void;
@@ -46,3 +49,31 @@ 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)) {
+ 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;
+}
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 {