diff --git a/client/src/components/History/CurrentHistory/HistoryPanel.vue b/client/src/components/History/CurrentHistory/HistoryPanel.vue index 7393243ef45a..1e233e7f1404 100644 --- a/client/src/components/History/CurrentHistory/HistoryPanel.vue +++ b/client/src/components/History/CurrentHistory/HistoryPanel.vue @@ -3,29 +3,21 @@ import { BAlert } from "bootstrap-vue"; import { storeToRefs } from "pinia"; import { computed, onMounted, type Ref, ref, set as VueSet, unref, watch } from "vue"; -import { - type DCEDataset, - type HistoryItemSummary, - type HistorySummaryExtended, - isDatasetElement, - isHistoryItem, - userOwnsHistory, -} from "@/api"; -import { copyDataset, type HistoryContentSource, type HistoryContentType } 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 { setItemDragstart } from "@/utils/setDrag"; +import { useHistoryDragDrop } from "../../../composables/historyDragDrop"; + import HistoryCounter from "./HistoryCounter.vue"; import HistoryDetails from "./HistoryDetails.vue"; import HistoryDropZone from "./HistoryDropZone.vue"; @@ -61,8 +53,6 @@ interface Props { isMultiViewItem?: boolean; } -type DraggableHistoryItem = HistoryItemSummary | DCEDataset; // TODO: DCESummary instead of DCEDataset - type ContentItemRef = Record | null>>; const props = withDefaults(defineProps(), { @@ -79,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); @@ -100,6 +88,8 @@ const historyStore = useHistoryStore(); const historyItemsStore = useHistoryItemsStore(); const { currentUser } = storeToRefs(useUserStore()); +const { showDropZone, onDragEnter, onDragLeave, onDragOver, onDrop } = useHistoryDragDrop(props.history.id); + const currentUserOwnsHistory = computed(() => { return userOwnsHistory(currentUser.value, props.history); }); @@ -228,28 +218,6 @@ watch( } ); -function dragSameHistory() { - return getDragData().sameHistory; -} - -function getDragData() { - const eventStore = useEventStore(); - const dragItems = eventStore.getDragItems(); - // Filter out any non-history items - // TODO: `isDCE` instead of `isDatasetElement` - const historyItems = dragItems?.filter( - (item: any) => isHistoryItem(item) || isDatasetElement(item) - ) as DraggableHistoryItem[]; - - // TODO: handle historyId === null || historyItems.length === 0 - const historyId = historyItems[0] - ? isHistoryItem(historyItems[0]) - ? historyItems[0].history_id - : historyItems[0].object?.history_id - : null; - return { data: historyItems, sameHistory: historyId === props.history.id, multiple: historyItems?.length > 1 }; -} - function getHighlight(item: HistoryItemSummary) { if (unref(isLoading)) { return undefined; @@ -363,95 +331,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) { - let dataSource: HistoryContentSource; - let type: HistoryContentType; - let id: string; - if (isHistoryItem(item)) { - dataSource = item.history_content_type === "dataset" ? "hda" : "hdca"; - type = item.history_content_type; - id = item.id; - } - // TEMPORARY: fix this when DCEs are handled correctly, unify like commented out code below - else if (isDatasetElement(item) && item.object) { - dataSource = "hda"; - type = "dataset"; - id = item.object.id; - } - /** TODO: Handle DCE, `DCEDataset`s work fine as they are HDAs, - * `DCECollection`s are `dataset_collection`s and need to be HDCAs... - */ - // else if (isDCE(item) && (item as DCESummary).object) { - // const collectionElement = item as DCESummary; - // dataSource = collectionElement.element_type === "dataset_collection" ? "hdca" : "hda"; // incorrect... - // type = collectionElement.element_type === "dataset_collection" ? "dataset_collection" : "dataset"; - // id = collectionElement.object.id as string; - // } - else { - throw new Error(`Invalid item type${item.element_type ? `: ${item.element_type}` : ""}`); - } - await copyDataset(id, props.history.id, type, dataSource); - - if (dataSource === "hda") { - 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); diff --git a/client/src/components/History/Multiple/MultipleViewList.vue b/client/src/components/History/Multiple/MultipleViewList.vue index 36eaac0e68cb..e84a89b0eadc 100644 --- a/client/src/components/History/Multiple/MultipleViewList.vue +++ b/client/src/components/History/Multiple/MultipleViewList.vue @@ -6,16 +6,15 @@ import { computed, type Ref, ref } from "vue"; //@ts-ignore missing typedefs import VirtualList from "vue-virtual-scroll-list"; -import { HistoryItemSummary, isHistoryItem } from "@/api"; -import { copyDataset } from "@/api/datasets"; import { useAnimationFrameResizeObserver } from "@/composables/sensors/animationFrameResizeObserver"; import { useAnimationFrameScroll } from "@/composables/sensors/animationFrameScroll"; import { Toast } from "@/composables/toast"; -import { useEventStore } from "@/stores/eventStore"; import { useHistoryStore } from "@/stores/historyStore"; import localize from "@/utils/localization"; import { errorMessageAsString } from "@/utils/simple-error"; +import { useHistoryDragDrop } from "../../../composables/historyDragDrop"; + import HistoryDropZone from "../CurrentHistory/HistoryDropZone.vue"; import MultipleViewItem from "./MultipleViewItem.vue"; @@ -65,67 +64,7 @@ async function createAndPin() { } } -const showDropZone = ref(false); -const processingDrop = ref(false); -async function onDrop(evt: any) { - const eventStore = useEventStore(); - if (processingDrop.value) { - showDropZone.value = false; - return; - } - processingDrop.value = true; - showDropZone.value = false; - const dragItems = eventStore.getDragItems(); - // Filter out any non-history items - const historyItems = dragItems?.filter((item: any) => isHistoryItem(item)) as HistoryItemSummary[]; - const multiple = historyItems.length > 1; - const originalHistoryId = historyItems?.[0]?.history_id; - - if (historyItems && originalHistoryId) { - await historyStore.createNewHistory(); - const currentHistoryId = historyStore.currentHistoryId; - - let datasetCount = 0; - let collectionCount = 0; - if (currentHistoryId) { - // iterate over the data array and copy each item to the new history - for (const item of historyItems) { - const dataSource = item.history_content_type === "dataset" ? "hda" : "hdca"; - await copyDataset(item.id, currentHistoryId, item.history_content_type, dataSource) - .then(() => { - if (item.history_content_type === "dataset") { - datasetCount++; - if (!multiple) { - Toast.info(localize("Dataset copied to new history")); - } - } else { - collectionCount++; - if (!multiple) { - Toast.info(localize("Collection copied to new history")); - } - } - }) - .catch((error) => { - Toast.error(errorMessageAsString(error)); - }); - } - 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`); - } - - if (historyStore.pinnedHistories.length > 0) { - // pin the newly created history via the drop - historyStore.pinHistory(currentHistoryId); - // also pin the original history where the item came from - historyStore.pinHistory(originalHistoryId); - } - } - processingDrop.value = false; - } -} +const { showDropZone, onDragEnter, onDragLeave, onDragOver, onDrop } = useHistoryDragDrop(undefined, true, true); async function onKeyDown(evt: KeyboardEvent) { if (evt.key === "Enter" || evt.key === " ") { @@ -159,9 +98,9 @@ async function onKeyDown(evt: KeyboardEvent) {
+ @dragenter.prevent="onDragEnter" + @dragover="onDragOver" + @dragleave.prevent="onDragLeave">
| string, createNew = false, pinHistories = false) { + const destinationHistoryId = unref(targetHistoryId); + const eventStore = useEventStore(); + const historyStore = useHistoryStore(); + + const fromHistoryId = computed(() => { + const dragItems = getDragItems(); + return dragItems[0] + ? isHistoryItem(dragItems[0]) + ? dragItems[0].history_id + : dragItems[0].object?.history_id + : null; + }); + + const showDropZone = ref(false); + const dragTarget = ref(null); + const processingDrop = ref(false); + + const operationDisabled = computed( + () => + !fromHistoryId.value || + (destinationHistoryId && fromHistoryId.value === destinationHistoryId) || + (!createNew && !destinationHistoryId) || + !getDragItems().length || + processingDrop.value + ); + + function getDragItems() { + const storeItems = eventStore.getDragItems(); + // Filter out any non-history or `DatasetCollectionElement` items + return storeItems?.filter( + (item) => isHistoryItem(item) || isDatasetElement(item as DCESummary) + ) as DraggableHistoryItem[]; + } + + function onDragEnter(e: DragEvent) { + if (operationDisabled.value) { + return; + } + dragTarget.value = e.target; + showDropZone.value = true; + } + + function onDragOver(e: DragEvent) { + if (operationDisabled.value) { + return; + } + e.preventDefault(); + } + + function onDragLeave(e: DragEvent) { + if (operationDisabled.value) { + return; + } + if (dragTarget.value === e.target) { + showDropZone.value = false; + } + } + + async function onDrop() { + showDropZone.value = false; + if (operationDisabled.value) { + return; + } + processingDrop.value = true; + + let datasetCount = 0; + let collectionCount = 0; + + try { + const dragItems = getDragItems(); + const multiple = dragItems.length > 1; + const originalHistoryId = fromHistoryId.value as string; + + let historyId; + if (destinationHistoryId) { + historyId = destinationHistoryId; + } else if (createNew) { + await historyStore.createNewHistory(); + historyId = historyStore.currentHistoryId; + } else { + historyId = null; + } + if (!historyId) { + throw new Error("Destination history not found or created"); + } + + // iterate over the data array and copy each item to the current history + for (const item of dragItems) { + let dataSource: HistoryContentSource; + let type: HistoryContentType; + let id: string; + if (isHistoryItem(item)) { + dataSource = item.history_content_type === "dataset" ? "hda" : "hdca"; + type = item.history_content_type; + id = item.id; + } + // For DCEs, only `DCEDataset`s are droppable, `DCEDatasetCollection`s are not + else if (isDatasetElement(item) && item.object) { + dataSource = "hda"; + type = "dataset"; + id = item.object.id; + } else { + throw new Error(`Invalid item type${item.element_type ? `: ${item.element_type}` : ""}`); + } + await copyDataset(id, historyId, type, dataSource); + + if (dataSource === "hda") { + datasetCount++; + if (!multiple) { + Toast.info(`Dataset copied to ${createNew ? "new" : ""} history`); + } + } else { + collectionCount++; + if (!multiple) { + Toast.info(`Collection copied to ${createNew ? "new" : ""} history`); + } + } + } + + if (multiple && datasetCount > 0) { + Toast.info( + `${datasetCount} dataset${datasetCount > 1 ? "s" : ""} copied to ${createNew ? "new" : ""} history` + ); + } + if (multiple && collectionCount > 0) { + Toast.info( + `${collectionCount} collection${collectionCount > 1 ? "s" : ""} copied to ${ + createNew ? "new" : "" + } history` + ); + } + + if (pinHistories && historyStore.pinnedHistories.length > 0) { + // pin the target history + historyStore.pinHistory(historyId); + // also pin the original history where the item came from + historyStore.pinHistory(originalHistoryId); + } else { + historyStore.loadHistoryById(historyId); + } + } catch (error) { + Toast.error(`${error}`); + } finally { + processingDrop.value = false; + } + } + + return { + showDropZone, + onDragEnter, + onDragOver, + onDragLeave, + onDrop, + }; +}