diff --git a/client/src/api/datasets.ts b/client/src/api/datasets.ts index c6096ea18f65..4c2a463394d3 100644 --- a/client/src/api/datasets.ts +++ b/client/src/api/datasets.ts @@ -95,3 +95,6 @@ export async function fetchDatasetAttributes(datasetId: string) { return data; } + +export type HistoryContentType = components["schemas"]["HistoryContentType"]; +export type HistoryContentSource = components["schemas"]["HistoryContentSource"]; diff --git a/client/src/api/index.ts b/client/src/api/index.ts index d1dbc8854bbb..bd17dc514d52 100644 --- a/client/src/api/index.ts +++ b/client/src/api/index.ts @@ -128,6 +128,14 @@ export interface DCECollection extends DCESummary { object: DCObject; } +/** + * DatasetCollectionElement specific type for datasets. + */ +export interface DCEDataset extends DCESummary { + element_type: "hda"; + object: HDAObject; +} + /** * Contains summary information about a HDCA (HistoryDatasetCollectionAssociation). * @@ -148,6 +156,8 @@ export type HDCADetailed = components["schemas"]["HDCADetailed"]; */ export type DCObject = components["schemas"]["DCObject"]; +export type HDAObject = components["schemas"]["HDAObject"]; + export type DatasetCollectionAttributes = components["schemas"]["DatasetCollectionAttributesResult"]; export type ConcreteObjectStoreModel = components["schemas"]["ConcreteObjectStoreModel"]; @@ -187,6 +197,13 @@ export function isCollectionElement(element: DCESummary): element is DCECollecti return element.element_type === "dataset_collection"; } +/** + * Returns true if the given element of a collection is a Dataset. + */ +export function isDatasetElement(element: DCESummary): element is DCEDataset { + return element.element_type === "hda"; +} + /** * Returns true if the given dataset entry is an instance of DatasetDetails. */ @@ -215,6 +232,10 @@ export function isHistoryItem(item: object): item is HistoryItemSummary { return item && "history_content_type" in item; } +export function isDCE(item: object): item is DCESummary { + return item && "element_type" in item; +} + type QuotaUsageResponse = components["schemas"]["UserQuotaUsage"]; /** Represents a registered user.**/ diff --git a/client/src/components/Form/Elements/FormData/FormData.vue b/client/src/components/Form/Elements/FormData/FormData.vue index b5f71c8cb2d1..e370339e6cb5 100644 --- a/client/src/components/Form/Elements/FormData/FormData.vue +++ b/client/src/components/Form/Elements/FormData/FormData.vue @@ -6,6 +6,7 @@ import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"; import { BAlert, BButton, BButtonGroup, BCollapse, BFormCheckbox, BTooltip } from "bootstrap-vue"; import { computed, onMounted, type Ref, ref, watch } from "vue"; +import { isDatasetElement, isDCE } from "@/api"; import { getGalaxyInstance } from "@/app"; import { useDatatypesMapper } from "@/composables/datatypesMapper"; import { useUid } from "@/composables/utils/uid"; @@ -314,11 +315,21 @@ function handleIncoming(incoming: Record, partial = true) { const incomingValues: Array = []; values.forEach((v) => { // Map incoming objects to data option values + let newSrc; + if (isDCE(v)) { + if (isDatasetElement(v)) { + newSrc = SOURCE.DATASET; + v = v.object; + } else { + newSrc = SOURCE.COLLECTION_ELEMENT; + } + } else { + newSrc = + v.src || (v.history_content_type === "dataset_collection" ? SOURCE.COLLECTION : SOURCE.DATASET); + } const newHid = v.hid; const newId = v.id; const newName = v.name ? v.name : newId; - const newSrc = - v.src || (v.history_content_type === "dataset_collection" ? SOURCE.COLLECTION : SOURCE.DATASET); const newValue: DataOption = { id: newId, src: newSrc, diff --git a/client/src/components/History/CurrentCollection/CollectionPanel.vue b/client/src/components/History/CurrentCollection/CollectionPanel.vue index 47ca7a497827..058c05430110 100644 --- a/client/src/components/History/CurrentCollection/CollectionPanel.vue +++ b/client/src/components/History/CurrentCollection/CollectionPanel.vue @@ -8,6 +8,7 @@ import { canMutateHistory, isCollectionElement, isHDCA } from "@/api"; import ExpandedItems from "@/components/History/Content/ExpandedItems"; import { updateContentFields } from "@/components/History/model/queries"; import { useCollectionElementsStore } from "@/stores/collectionElementsStore"; +import { setItemDragstart } from "@/utils/setDrag"; import { errorMessageAsString } from "@/utils/simple-error"; import CollectionDetails from "./CollectionDetails.vue"; @@ -164,6 +165,7 @@ watch( :expand-dataset="isExpanded(item)" :is-dataset="item.element_type == 'hda'" :filterable="filterable" + @drag-start="setItemDragstart(item, $event)" @update:expand-dataset="setExpanded(item, $event)" @view-collection="onViewDatasetCollectionElement(item)" /> diff --git a/client/src/components/History/CurrentHistory/HistoryPanel.vue b/client/src/components/History/CurrentHistory/HistoryPanel.vue index 869548649a95..d3245eb45cde 100644 --- a/client/src/components/History/CurrentHistory/HistoryPanel.vue +++ b/client/src/components/History/CurrentHistory/HistoryPanel.vue @@ -3,21 +3,20 @@ import { BAlert } from "bootstrap-vue"; import { storeToRefs } from "pinia"; import { computed, onMounted, type Ref, ref, set as VueSet, unref, watch } from "vue"; -import { type HistoryItemSummary, type HistorySummaryExtended, isHistoryItem, userOwnsHistory } from "@/api"; -import { copyDataset } from "@/api/datasets"; +import { type HistoryItemSummary, type HistorySummaryExtended, userOwnsHistory } from "@/api"; import ExpandedItems from "@/components/History/Content/ExpandedItems"; import SelectedItems from "@/components/History/Content/SelectedItems"; import { HistoryFilters } from "@/components/History/HistoryFilters"; import { deleteContent, updateContentFields } from "@/components/History/model/queries"; -import { Toast } from "@/composables/toast"; import { useActiveElement } from "@/composables/useActiveElement"; import { startWatchingHistory } from "@/store/historyStore/model/watchHistory"; -import { useEventStore } from "@/stores/eventStore"; import { useHistoryItemsStore } from "@/stores/historyItemsStore"; import { useHistoryStore } from "@/stores/historyStore"; import { useUserStore } from "@/stores/userStore"; import { type Alias, getOperatorForAlias } from "@/utils/filtering"; -import { setDrag } from "@/utils/setDrag"; +import { setItemDragstart } from "@/utils/setDrag"; + +import { useHistoryDragDrop } from "../../../composables/historyDragDrop"; import HistoryCounter from "./HistoryCounter.vue"; import HistoryDetails from "./HistoryDetails.vue"; @@ -70,11 +69,9 @@ const isLoading = ref(false); const offsetQueryParam = ref(0); const searchError = ref(undefined); const showAdvanced = ref(false); -const showDropZone = ref(false); const operationRunning = ref(null); const operationError = ref(null); const querySelectionBreak = ref(false); -const dragTarget = ref(null); const contentItemRefs = computed(() => { return historyItems.value.reduce((acc: ContentItemRef, item) => { acc[itemUniqueKey(item)] = ref(null); @@ -91,6 +88,9 @@ const historyStore = useHistoryStore(); const historyItemsStore = useHistoryItemsStore(); const { currentUser } = storeToRefs(useUserStore()); +const historyIdComputed = computed(() => props.history.id); +const { showDropZone, onDragEnter, onDragLeave, onDragOver, onDrop } = useHistoryDragDrop(historyIdComputed); + const currentUserOwnsHistory = computed(() => { return userOwnsHistory(currentUser.value, props.history); }); @@ -219,19 +219,6 @@ watch( } ); -function dragSameHistory() { - return getDragData().sameHistory; -} - -function getDragData() { - const eventStore = useEventStore(); - const dragItems = eventStore.getDragItems(); - // Filter out any non-history items - const historyItems = dragItems?.filter((item: any) => isHistoryItem(item)) as HistoryItemSummary[]; - const historyId = historyItems?.[0]?.history_id; - return { data: historyItems, sameHistory: historyId === props.history.id, multiple: historyItems?.length > 1 }; -} - function getHighlight(item: HistoryItemSummary) { if (unref(isLoading)) { return undefined; @@ -345,70 +332,6 @@ function onOperationError(error: any) { operationError.value = error; } -function onDragEnter(e: DragEvent) { - if (dragSameHistory()) { - return; - } - dragTarget.value = e.target; - showDropZone.value = true; -} - -function onDragOver(e: DragEvent) { - if (dragSameHistory()) { - return; - } - e.preventDefault(); -} - -function onDragLeave(e: DragEvent) { - if (dragSameHistory()) { - return; - } - if (dragTarget.value === e.target) { - showDropZone.value = false; - } -} - -async function onDrop() { - showDropZone.value = false; - const { data, sameHistory, multiple } = getDragData(); - if (!data || sameHistory) { - return; - } - - let datasetCount = 0; - let collectionCount = 0; - try { - // iterate over the data array and copy each item to the current history - for (const item of data) { - const dataSource = item.history_content_type === "dataset" ? "hda" : "hdca"; - await copyDataset(item.id, props.history.id, item.history_content_type, dataSource); - - if (item.history_content_type === "dataset") { - datasetCount++; - if (!multiple) { - Toast.info("Dataset copied to history"); - } - } else { - collectionCount++; - if (!multiple) { - Toast.info("Collection copied to history"); - } - } - } - - if (multiple && datasetCount > 0) { - Toast.info(`${datasetCount} dataset${datasetCount > 1 ? "s" : ""} copied to new history`); - } - if (multiple && collectionCount > 0) { - Toast.info(`${collectionCount} collection${collectionCount > 1 ? "s" : ""} copied to new history`); - } - historyStore.loadHistoryById(props.history.id); - } catch (error) { - Toast.error(`${error}`); - } -} - function updateFilterValue(filterKey: string, newValue: any) { const currentFilterText = filterText.value; filterText.value = filterClass.setFilterValue(currentFilterText, filterKey, newValue); @@ -451,24 +374,6 @@ function arrowNavigate(item: HistoryItemSummary, eventKey: string) { } return nextItem; } - -function setItemDragstart( - item: HistoryItemSummary, - itemIsSelected: boolean, - selectedItems: Map, - selectionSize: number, - event: DragEvent -) { - if (itemIsSelected && selectionSize > 1) { - const selectedItemsObj: any = {}; - for (const [key, value] of selectedItems) { - selectedItemsObj[key] = value; - } - setDrag(event, selectedItemsObj, true); - } else { - setDrag(event, item as any); - } -}