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
135 changes: 128 additions & 7 deletions client/src/components/ImportData/zip/ZipFileSelector.vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
<script setup lang="ts">
import { BAlert } from "bootstrap-vue";
import { BAlert, BPagination } from "bootstrap-vue";
import { storeToRefs } from "pinia";
import { computed, ref } from "vue";
import { computed, ref, watch } from "vue";

import { usePagination } from "@/composables/pagination";
import type { ArchiveSource, ImportableFile, ImportableZipContents } from "@/composables/zipExplorer";
import { useUserStore } from "@/stores/userStore";

import DelayedInput from "@/components/Common/DelayedInput.vue";
import Heading from "@/components/Common/Heading.vue";
import ListHeader from "@/components/Common/ListHeader.vue";
import ZipFileEntryCard from "@/components/ImportData/zip/ZipFileEntryCard.vue";
Expand All @@ -27,6 +29,7 @@ const userStore = useUserStore();
const { isAnonymous } = storeToRefs(userStore);

const localSelectedItems = ref<ImportableFile[]>(props.selectedItems);
const searchQuery = ref("");

function toggleSelection(item: ImportableFile) {
const index = localSelectedItems.value.findIndex((selected) => selected.path === item.path);
Expand All @@ -38,26 +41,128 @@ function toggleSelection(item: ImportableFile) {
emit("update:selectedItems", localSelectedItems.value);
}

function matchesSearch(file: ImportableFile): boolean {
if (!searchQuery.value) {
return true;
}
const query = searchQuery.value.toLowerCase();
return (
file.name.toLowerCase().includes(query) ||
file.path.toLowerCase().includes(query) ||
Boolean(file.description && file.description.toLowerCase().includes(query))
);
}

const filteredWorkflows = computed(() => {
return props.zipContents.workflows.filter(matchesSearch);
});

const filteredFiles = computed(() => {
return props.zipContents.files.filter(matchesSearch);
});

// Combine workflows and files for unified pagination
const allFilteredItems = computed(() => [...filteredWorkflows.value, ...filteredFiles.value]);

const {
currentPage,
itemsPerPage,
paginatedItems: allPaginatedItems,
totalItems,
showPagination,
onPageChange,
resetPage,
} = usePagination(allFilteredItems);

// Split paginated items back into workflows and files
const paginatedWorkflows = computed(() => {
return allPaginatedItems.value.filter((item) => item.type === "workflow");
});

const paginatedFiles = computed(() => {
return allPaginatedItems.value.filter((item) => item.type === "file");
});

// Reset to page 1 when search changes
watch(searchQuery, resetPage);

const allSelectableFiles = computed(() => [...paginatedWorkflows.value, ...paginatedFiles.value]);

function onSelectAll() {
const allSelected = allSelectableFiles.value.every((file) =>
localSelectedItems.value.some((selected) => selected.path === file.path),
);

if (allSelected) {
// Deselect all visible items on current page
localSelectedItems.value = localSelectedItems.value.filter(
(selected) => !allSelectableFiles.value.some((file) => file.path === selected.path),
);
} else {
// Select all visible items on current page that aren't already selected
const newSelections = allSelectableFiles.value.filter(
(file) => !localSelectedItems.value.some((selected) => selected.path === file.path),
);
localSelectedItems.value = [...localSelectedItems.value, ...newSelections];
}
emit("update:selectedItems", localSelectedItems.value);
}

const allSelected = computed(() => {
if (allSelectableFiles.value.length === 0) {
return false;
}
return allSelectableFiles.value.every((file) =>
localSelectedItems.value.some((selected) => selected.path === file.path),
);
});

const indeterminateSelected = computed(() => {
const selectedCount = allSelectableFiles.value.filter((file) =>
localSelectedItems.value.some((selected) => selected.path === file.path),
).length;
return selectedCount > 0 && selectedCount < allSelectableFiles.value.length;
});

const currentListView = computed(() => userStore.currentListViewPreferences.zipFileSelector || "list");

const localFileSizeLimit = computed(() => {
return props.zipSource instanceof File ? props.bytesLimit : undefined;
});

function onSearch(value: string) {
searchQuery.value = value;
}
</script>

<template>
<div class="zip-file-selector w-100">
<ListHeader list-id="zipFileSelector" show-view-toggle />
<div class="zip-file-selector-search mb-3">
<DelayedInput
:value="searchQuery"
:delay="200"
placeholder="Search files by name or path"
@change="onSearch" />
</div>

<div v-if="props.zipContents.workflows.length > 0" class="d-flex flex-column w-100">
<ListHeader
list-id="zipFileSelector"
show-view-toggle
show-select-all
:all-selected="allSelected"
:indeterminate-selected="indeterminateSelected"
:select-all-disabled="allSelectableFiles.length === 0"
@select-all="onSelectAll" />

<div v-if="paginatedWorkflows.length > 0" class="d-flex flex-column w-100">
<Heading h3 separator> Workflows </Heading>

<BAlert v-if="isAnonymous" variant="warning" show fade>You must be logged in to import workflows</BAlert>
<p>Here you can select workflows compatible with Galaxy and import them into your account.</p>

<div class="d-flex flex-wrap">
<ZipFileEntryCard
v-for="workflow in props.zipContents.workflows"
v-for="workflow in paginatedWorkflows"
:key="workflow.path"
:file="workflow"
:grid-view="currentListView === 'grid'"
Expand All @@ -66,14 +171,14 @@ const localFileSizeLimit = computed(() => {
</div>
</div>

<div v-if="props.zipContents.files.length > 0" class="d-flex flex-column w-100">
<div v-if="paginatedFiles.length > 0" class="d-flex flex-column w-100">
<Heading h3 separator> Files </Heading>

<p>Here you can select individual files to import into your <b>current history</b>.</p>

<div class="d-flex flex-wrap">
<ZipFileEntryCard
v-for="dataset in props.zipContents.files"
v-for="dataset in paginatedFiles"
:key="dataset.path"
:file="dataset"
:bytes-limit="localFileSizeLimit"
Expand All @@ -82,5 +187,21 @@ const localFileSizeLimit = computed(() => {
@select="toggleSelection(dataset)" />
</div>
</div>

<BAlert v-if="searchQuery && filteredWorkflows.length === 0 && filteredFiles.length === 0" variant="info" show>
No files found matching "{{ searchQuery }}". Try a different search term.
</BAlert>

<div v-if="showPagination" class="d-flex justify-content-center py-3 mt-3">
<BPagination
:value="currentPage"
:total-rows="totalItems"
:per-page="itemsPerPage"
align="center"
size="sm"
first-number
last-number
@change="onPageChange" />
</div>
</div>
</template>
29 changes: 25 additions & 4 deletions client/src/components/ImportData/zip/ZipImportSummary.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
<script setup lang="ts">
import { faFile, faNetworkWired } from "@fortawesome/free-solid-svg-icons";
import { BPagination } from "bootstrap-vue";
import { computed } from "vue";

import type { CardBadge } from "@/components/Common/GCard.types";
import { usePagination } from "@/composables/pagination";
import type { ImportableFile } from "@/composables/zipExplorer";
import { bytesToString } from "@/utils/utils";

Expand All @@ -27,6 +29,13 @@ const totalFileSize = computed(() => {
return regularFiles.value.reduce((total, file) => total + file.size, 0);
});

const allItems = computed(() => [...workflowFiles.value, ...regularFiles.value]);

const { currentPage, itemsPerPage, paginatedItems, totalItems, showPagination, onPageChange } = usePagination(allItems);

const paginatedWorkflows = computed(() => paginatedItems.value.filter((item) => item.type === "workflow"));
const paginatedFiles = computed(() => paginatedItems.value.filter((item) => item.type === "file"));

const workflowBadges: CardBadge[] = [
{
id: "workflow-count",
Expand Down Expand Up @@ -54,7 +63,7 @@ const fileBadges: CardBadge[] = [
<div class="w-100">
<div class="d-flex flex-grow-1">
<GCard
v-if="workflowFiles.length > 0"
v-if="paginatedWorkflows.length > 0"
id="zip-workflows-summary"
title="Workflows to Import"
title-size="md"
Expand All @@ -63,13 +72,13 @@ const fileBadges: CardBadge[] = [
<template v-slot:description>
<p v-localize>The following workflows will be imported from the archive into your account.</p>
<div class="d-flex flex-wrap">
<ZipFileEntrySummaryCard v-for="file in workflowFiles" :key="file.path" :file="file" />
<ZipFileEntrySummaryCard v-for="file in paginatedWorkflows" :key="file.path" :file="file" />
</div>
</template>
</GCard>

<GCard
v-if="regularFiles.length > 0"
v-if="paginatedFiles.length > 0"
id="zip-files-summary"
title="Files to Import"
title-size="md"
Expand All @@ -81,10 +90,22 @@ const fileBadges: CardBadge[] = [
<b>currently active Galaxy history</b>.
</p>
<div class="d-flex flex-wrap">
<ZipFileEntrySummaryCard v-for="file in regularFiles" :key="file.path" :file="file" />
<ZipFileEntrySummaryCard v-for="file in paginatedFiles" :key="file.path" :file="file" />
</div>
</template>
</GCard>
</div>

<div v-if="showPagination" class="d-flex justify-content-center py-3 mt-3">
<BPagination
:value="currentPage"
:total-rows="totalItems"
:per-page="itemsPerPage"
align="center"
size="sm"
first-number
last-number
@change="onPageChange" />
</div>
</div>
</template>
27 changes: 25 additions & 2 deletions client/src/components/ImportData/zip/views/GalaxyZipView.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<script setup lang="ts">
import { BPagination } from "bootstrap-vue";
import { computed } from "vue";

import { usePagination } from "@/composables/pagination";
import { type GalaxyZipExplorer, getImportableFiles } from "@/composables/zipExplorer";

import Heading from "@/components/Common/Heading.vue";
Expand All @@ -11,12 +13,33 @@ const props = defineProps<{
}>();

const files = computed(() => getImportableFiles(props.explorer));

const {
currentPage,
itemsPerPage,
paginatedItems: paginatedFiles,
showPagination,
onPageChange,
} = usePagination(files);
</script>

<template>
<div class="rocrate-explorer">
<div class="galaxy-zip-explorer">
<Heading size="lg">Galaxy Export Archive Summary</Heading>
<Heading size="md">Files</Heading>
<ZipFileEntryCard v-for="file in files" :key="file.path" :file="file" />

<ZipFileEntryCard v-for="file in paginatedFiles" :key="file.path" :file="file" />

<div v-if="showPagination" class="d-flex justify-content-center py-3 mt-3">
<BPagination
:value="currentPage"
:total-rows="files.length"
:per-page="itemsPerPage"
align="center"
size="sm"
first-number
last-number
@change="onPageChange" />
</div>
</div>
</template>
46 changes: 37 additions & 9 deletions client/src/components/ImportData/zip/views/RegularZipView.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
<script setup lang="ts">
import { BPagination } from "bootstrap-vue";
import { computed } from "vue";

import type { CardBadge } from "@/components/Common/GCard.types";
import { usePagination } from "@/composables/pagination";
import { getImportableFiles, type IZipExplorer } from "@/composables/zipExplorer";

import GCard from "@/components/Common/GCard.vue";
Expand All @@ -13,6 +15,14 @@ const props = defineProps<{

const files = computed(() => getImportableFiles(props.explorer));

const {
currentPage,
itemsPerPage,
paginatedItems: paginatedFiles,
showPagination,
onPageChange,
} = usePagination(files);

const zipFileBadges: CardBadge[] = [
{
id: "file-count",
Expand All @@ -23,13 +33,31 @@ const zipFileBadges: CardBadge[] = [
</script>

<template>
<GCard
id="regular-zip-summary-card"
title="List of files contained in the archive"
title-size="md"
:badges="zipFileBadges">
<template v-slot:description>
<ZipFileEntrySummaryCard v-for="file in files" :key="file.path" :file="file" :selectable="false" />
</template>
</GCard>
<div>
<GCard
id="regular-zip-summary-card"
title="List of files contained in the archive"
title-size="md"
:badges="zipFileBadges">
<template v-slot:description>
<ZipFileEntrySummaryCard
v-for="file in paginatedFiles"
:key="file.path"
:file="file"
:selectable="false" />
</template>
</GCard>

<div v-if="showPagination" class="d-flex justify-content-center py-3 mt-3">
<BPagination
:value="currentPage"
:total-rows="files.length"
:per-page="itemsPerPage"
align="center"
size="sm"
first-number
last-number
@change="onPageChange" />
</div>
</div>
</template>
Loading
Loading