Skip to content

Commit

Permalink
Merge pull request #18699 from ahmedhamidawan/drag_drop_DCE
Browse files Browse the repository at this point in the history
[24.1] Allow drag and drop for collection elements
  • Loading branch information
mvdbeek authored Sep 16, 2024
2 parents c26b5d6 + c742333 commit aad1caf
Show file tree
Hide file tree
Showing 8 changed files with 273 additions and 173 deletions.
3 changes: 3 additions & 0 deletions client/src/api/datasets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,6 @@ export async function fetchDatasetAttributes(datasetId: string) {

return data;
}

export type HistoryContentType = components["schemas"]["HistoryContentType"];
export type HistoryContentSource = components["schemas"]["HistoryContentSource"];
21 changes: 21 additions & 0 deletions client/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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).
*
Expand All @@ -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"];
Expand Down Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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.**/
Expand Down
15 changes: 13 additions & 2 deletions client/src/components/Form/Elements/FormData/FormData.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -314,11 +315,21 @@ function handleIncoming(incoming: Record<string, unknown>, partial = true) {
const incomingValues: Array<DataOption> = [];
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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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)" />
</template>
Expand Down
113 changes: 9 additions & 104 deletions client/src/components/History/CurrentHistory/HistoryPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -70,11 +69,9 @@ const isLoading = ref(false);
const offsetQueryParam = ref(0);
const searchError = ref<BackendFilterError | undefined>(undefined);
const showAdvanced = ref(false);
const showDropZone = ref(false);
const operationRunning = ref<string | null>(null);
const operationError = ref(null);
const querySelectionBreak = ref(false);
const dragTarget = ref<EventTarget | null>(null);
const contentItemRefs = computed(() => {
return historyItems.value.reduce((acc: ContentItemRef, item) => {
acc[itemUniqueKey(item)] = ref(null);
Expand All @@ -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);
});
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -451,24 +374,6 @@ function arrowNavigate(item: HistoryItemSummary, eventKey: string) {
}
return nextItem;
}
function setItemDragstart(
item: HistoryItemSummary,
itemIsSelected: boolean,
selectedItems: Map<string, HistoryItemSummary>,
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);
}
}
</script>

<template>
Expand Down Expand Up @@ -629,10 +534,10 @@ function setItemDragstart(
@drag-start="
setItemDragstart(
item,
$event,
showSelection && isSelected(item),
selectedItems,
selectionSize,
$event
selectedItems
)
"
@hide-selection="setShowSelection(false)"
Expand Down
73 changes: 6 additions & 67 deletions client/src/components/History/Multiple/MultipleViewList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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 === " ") {
Expand Down Expand Up @@ -159,9 +98,9 @@ async function onKeyDown(evt: KeyboardEvent) {
<div
class="history-picker"
@drop.prevent="onDrop"
@dragenter.prevent="showDropZone = true"
@dragover.prevent
@dragleave.prevent="showDropZone = false">
@dragenter.prevent="onDragEnter"
@dragover="onDragOver"
@dragleave.prevent="onDragLeave">
<span v-if="!showDropZone" class="d-flex flex-column h-100">
<div
class="history-picker-box top-picker text-primary"
Expand Down
Loading

0 comments on commit aad1caf

Please sign in to comment.