mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-01 15:37:32 +08:00
Update SelectionDialog component
- Remove deprecated BTable in favor of GTable for improved functionality. - Update field types from FieldEntry to TableField for consistency. - Enhance sorting and filtering capabilities with new computed properties. - Refactor event handling for row clicks and sorting changes. - Improve pagination reset logic when filters are applied.
This commit is contained in:
committed by
Alireza Heidari
parent
e7272a77d1
commit
5044379dc3
@@ -3,29 +3,30 @@ import type { IconDefinition } from "@fortawesome/fontawesome-svg-core";
|
||||
import { faCheckSquare, faMinusSquare, faSquare } from "@fortawesome/free-regular-svg-icons";
|
||||
import { faCaretLeft, faCheck, faFolder, faSpinner, faTimes } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||
import { BAlert, BButton, BLink, BPagination, BTable } from "bootstrap-vue";
|
||||
import { BAlert, BButton, BLink, BPagination, BSpinner } from "bootstrap-vue";
|
||||
import { computed, ref, watch } from "vue";
|
||||
|
||||
import type { RowClickEvent, TableField } from "@/components/Common/GTable.types";
|
||||
import { type ItemsProvider, SELECTION_STATES, type SelectionState } from "@/components/SelectionDialog/selectionTypes";
|
||||
import type Filtering from "@/utils/filtering";
|
||||
|
||||
import type { FieldEntry, SelectionItem } from "./selectionTypes";
|
||||
import type { SelectionItem } from "./selectionTypes";
|
||||
|
||||
import GModal from "../BaseComponents/GModal.vue";
|
||||
import Heading from "../Common/Heading.vue";
|
||||
import FilterMenu from "@/components/Common/FilterMenu.vue";
|
||||
import LoadingSpan from "@/components/LoadingSpan.vue";
|
||||
import GTable from "@/components/Common/GTable.vue";
|
||||
import DataDialogSearch from "@/components/SelectionDialog/DataDialogSearch.vue";
|
||||
import StatelessTags from "@/components/TagsMultiselect/StatelessTags.vue";
|
||||
|
||||
const LABEL_FIELD: FieldEntry = { key: "label", sortable: true };
|
||||
const SELECT_ICON_FIELD: FieldEntry = { key: "__select_icon__", label: "", sortable: false };
|
||||
const LABEL_FIELD: TableField = { key: "label", label: "Name", sortable: true };
|
||||
const SELECT_ICON_FIELD: TableField = { key: "__select_icon__", label: "", sortable: false };
|
||||
|
||||
interface Props {
|
||||
disableOk?: boolean;
|
||||
errorMessage?: string;
|
||||
fileMode?: boolean;
|
||||
fields?: FieldEntry[];
|
||||
fields?: TableField[];
|
||||
isBusy?: boolean;
|
||||
isEncoded?: boolean;
|
||||
items?: SelectionItem[];
|
||||
@@ -87,15 +88,22 @@ const currentPage = ref(1);
|
||||
const perPage = ref(25);
|
||||
const showAdvancedSearch = ref(false);
|
||||
|
||||
const providerRequestId = ref(0);
|
||||
const providerItems = ref<SelectionItem[]>([]);
|
||||
const sortBy = ref<string | undefined>(undefined);
|
||||
const sortDesc = ref<boolean | undefined>(undefined);
|
||||
|
||||
const usingProvider = computed(() => Boolean(props.itemsProvider));
|
||||
|
||||
const okButtonText = computed(() => {
|
||||
return props.okButtonText ? props.okButtonText : props.fileMode ? "Select" : "Select this folder";
|
||||
});
|
||||
|
||||
const fieldDetails = computed(() => {
|
||||
const fields = props.fields.slice().map((x) => {
|
||||
x.sortable = x.sortable === undefined ? true : x.sortable;
|
||||
return x;
|
||||
});
|
||||
const fieldDetails = computed<TableField[]>(() => {
|
||||
const fields: TableField[] = props.fields.slice().map((field) => ({
|
||||
...field,
|
||||
sortable: field.sortable ?? true,
|
||||
}));
|
||||
if (fields.length === 0) {
|
||||
fields.unshift(LABEL_FIELD);
|
||||
}
|
||||
@@ -105,7 +113,44 @@ const fieldDetails = computed(() => {
|
||||
return fields;
|
||||
});
|
||||
|
||||
function selectionIcon(variant: string) {
|
||||
const tableItems = computed(() => {
|
||||
return usingProvider.value ? providerItems.value : props.items;
|
||||
});
|
||||
|
||||
async function loadProviderItems() {
|
||||
if (!props.itemsProvider || !props.optionsShow) {
|
||||
return;
|
||||
}
|
||||
|
||||
const requestId = ++providerRequestId.value;
|
||||
const result = await props.itemsProvider({
|
||||
apiUrl: props.providerUrl,
|
||||
currentPage: currentPage.value,
|
||||
perPage: perPage.value,
|
||||
filter: filter.value || undefined,
|
||||
sortBy: sortBy.value,
|
||||
sortDesc: sortDesc.value,
|
||||
});
|
||||
|
||||
if (requestId === providerRequestId.value) {
|
||||
providerItems.value = result ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
function onSortChanged(newSortBy: string, newSortDesc: boolean) {
|
||||
sortBy.value = newSortBy || undefined;
|
||||
sortDesc.value = newSortDesc;
|
||||
}
|
||||
|
||||
function onRowClick(event: RowClickEvent<SelectionItem>) {
|
||||
emit("onClick", event.item);
|
||||
}
|
||||
|
||||
function onOpen(item: SelectionItem) {
|
||||
emit("onOpen", item);
|
||||
}
|
||||
|
||||
function selectionIcon(variant?: string) {
|
||||
switch (variant) {
|
||||
case SELECTION_STATES.SELECTED:
|
||||
return faCheckSquare;
|
||||
@@ -116,13 +161,6 @@ function selectionIcon(variant: string) {
|
||||
}
|
||||
}
|
||||
|
||||
/** Resets pagination when a filter/search word is entered **/
|
||||
function filtered(items: SelectionItem[]) {
|
||||
if (props.itemsProvider === undefined) {
|
||||
resetPagination();
|
||||
}
|
||||
}
|
||||
|
||||
/** Format time stamp */
|
||||
function formatTime(value: string) {
|
||||
if (value) {
|
||||
@@ -159,6 +197,30 @@ if (props.watchOnPageChanges) {
|
||||
}
|
||||
|
||||
const dialog = ref<InstanceType<typeof GModal> | null>(null);
|
||||
|
||||
watch(
|
||||
[
|
||||
() => props.itemsProvider,
|
||||
currentPage,
|
||||
perPage,
|
||||
filter,
|
||||
sortBy,
|
||||
sortDesc,
|
||||
() => props.providerUrl,
|
||||
() => props.optionsShow,
|
||||
],
|
||||
() => {
|
||||
if (props.itemsProvider && props.optionsShow) {
|
||||
void loadProviderItems();
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
watch(filter, () => {
|
||||
resetPagination();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => dialog.value,
|
||||
(newValue) => {
|
||||
@@ -208,19 +270,21 @@ defineExpose({
|
||||
</BAlert>
|
||||
<div v-else>
|
||||
<div v-if="optionsShow" data-description="selection dialog options">
|
||||
<BTable
|
||||
small
|
||||
hover
|
||||
<GTable
|
||||
class="selection-dialog-table"
|
||||
primary-key="id"
|
||||
:busy="isBusy"
|
||||
clickable-rows
|
||||
compact
|
||||
hover
|
||||
:current-page="currentPage"
|
||||
:items="itemsProvider ?? items"
|
||||
:fields="fieldDetails"
|
||||
:filter="filter"
|
||||
:items="tableItems"
|
||||
:loading="isBusy"
|
||||
:local-filtering="!usingProvider"
|
||||
:local-sorting="!usingProvider"
|
||||
:per-page="perPage"
|
||||
@filtered="filtered"
|
||||
@row-clicked="emit('onClick', $event)">
|
||||
@sort-changed="onSortChanged"
|
||||
@row-click="onRowClick">
|
||||
<template v-slot:head(__select_icon__)="">
|
||||
<FontAwesomeIcon
|
||||
class="select-checkbox cursor-pointer"
|
||||
@@ -228,9 +292,11 @@ defineExpose({
|
||||
:icon="selectionIcon(selectAllVariant)"
|
||||
@click="$emit('onSelectAll')" />
|
||||
</template>
|
||||
|
||||
<template v-slot:cell(__select_icon__)="data">
|
||||
<FontAwesomeIcon :icon="selectionIcon(data.item._rowVariant)" />
|
||||
</template>
|
||||
|
||||
<template v-slot:cell(label)="data">
|
||||
<div style="cursor: pointer">
|
||||
<pre
|
||||
@@ -241,34 +307,46 @@ defineExpose({
|
||||
<i :class="leafIcon" />
|
||||
<span :title="`label-${data.item.url}`">{{ data.value ? data.value : "-" }}</span>
|
||||
</div>
|
||||
<div v-else @click.stop="emit('onOpen', data.item)">
|
||||
<div
|
||||
v-else
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@click.stop="onOpen(data.item)"
|
||||
@keydown.enter.stop="onOpen(data.item)"
|
||||
@keydown.space.stop.prevent="onOpen(data.item)">
|
||||
<FontAwesomeIcon :icon="props.folderIcon" />
|
||||
<BLink :title="`label-${data.item.url}`">{{ data.value ? data.value : "-" }}</BLink>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-slot:cell(details)="data">
|
||||
<span :title="`details-${data.item.url}`">{{ data.value ? data.value : "-" }}</span>
|
||||
</template>
|
||||
|
||||
<template v-slot:cell(tags)="data">
|
||||
<StatelessTags v-if="data.value?.length > 0" :value="data.value" :disabled="true" />
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
|
||||
<template v-slot:cell(time)="data">
|
||||
{{ formatTime(data.value) }}
|
||||
</template>
|
||||
|
||||
<template v-slot:cell(update_time)="data">
|
||||
{{ formatTime(data.value) }}
|
||||
</template>
|
||||
</BTable>
|
||||
</GTable>
|
||||
|
||||
<div v-if="isBusy" class="text-center">
|
||||
<LoadingSpan />
|
||||
<BSpinner small type="grow" />
|
||||
<BSpinner small type="grow" />
|
||||
<BSpinner small type="grow" />
|
||||
</div>
|
||||
<div v-else-if="totalItems === 0">
|
||||
<div v-if="filter">
|
||||
No search results found for: <b>{{ filter }}</b
|
||||
>.
|
||||
No search results found for: <b> {{ filter }} </b>.
|
||||
</div>
|
||||
<div v-else>No entries.</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user