Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[24.2] Show message for mixed extensions in collection creator #19404

Draft
wants to merge 4 commits into
base: release_24.2
Choose a base branch
from
Draft
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
26 changes: 26 additions & 0 deletions client/src/components/Collections/ListCollectionCreator.vue
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ const datatypesMapper = computed(() => datatypesMapperStore.datatypesMapper);
/** Are we filtering by datatype? */
const filterExtensions = computed(() => !!datatypesMapper.value && !!props.extensions?.length);

/** Does `inListElements` have elements with different extensions? */
const listHasMixedExtensions = computed(() => {
const extensions = new Set(inListElements.value.map((e) => e.extension));
return extensions.size > 1;
});

// ----------------------------------------------------------------------- process raw list
/** set up main data */
function _elementsSetUp() {
Expand Down Expand Up @@ -173,6 +179,21 @@ function _isElementInvalid(element: HistoryItemSummary): string | null {
return null;
}

/** Show the element's extension next to its name:
* 1. If there are no required extensions, so users can avoid creating mixed extension lists.
* 2. If the extension is not in the list of required extensions but is a subtype of one of them,
* so users can see that those elements were still included as they are implicitly convertible.
*/
function showElementExtension(element: HDASummary) {
return (
!props.extensions?.length ||
(filterExtensions.value &&
element.extension &&
!props.extensions?.includes(element.extension) &&
datatypesMapper.value?.isSubTypeOfAny(element.extension, props.extensions!))
);
}

// /** mangle duplicate names using a mac-like '(counter)' addition to any duplicates */
function _mangleDuplicateNames() {
var counter = 1;
Expand Down Expand Up @@ -523,6 +544,10 @@ function renameElement(element: any, name: string) {
</template>

<template v-slot:middle-content>
<BAlert v-if="listHasMixedExtensions" show variant="warning">
{{ localize("The selected datasets have mixed extensions.") }}
{{ localize("You can still create the list but its elements will have different extensions.") }}
</BAlert>
<div v-if="noInitialElements">
<BAlert show variant="warning" dismissible>
{{ localize("No datasets were selected") }}
Expand Down Expand Up @@ -660,6 +685,7 @@ function renameElement(element: any, name: string) {
<DatasetCollectionElementView
class="w-100"
:element="value"
:hide-extension="!showElementExtension(value)"
@onRename="(name) => renameElement(value, name)" />
</template>
</FormSelectMany>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ interface Props {
selected?: boolean;
hasActions?: boolean;
notEditable?: boolean;
hideExtension?: boolean;
}

const props = defineProps<Props>();
Expand Down Expand Up @@ -48,7 +49,7 @@ function clickDiscard() {
<ClickToEdit v-if="!notEditable" v-model="elementName" :title="localize('Click to rename')" />
<span v-else>{{ elementName }}</span>
</strong>
<i v-if="element.extension"> ({{ element.extension }}) </i>
<i v-if="!hideExtension && element.extension"> ({{ element.extension }}) </i>
</span>

<div v-if="hasActions" class="float-right">
Expand Down
140 changes: 103 additions & 37 deletions client/src/components/Collections/PairCollectionCreator.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ import { BAlert, BButton } from "bootstrap-vue";
import { computed, ref, watch } from "vue";

import type { HDASummary, HistoryItemSummary } from "@/api";
import { useAnimationFrameResizeObserver } from "@/composables/sensors/animationFrameResizeObserver";
import { useAnimationFrameScroll } from "@/composables/sensors/animationFrameScroll";
import { Toast } from "@/composables/toast";
import STATES from "@/mvc/dataset/states";
import { useDatatypesMapperStore } from "@/stores/datatypesMapperStore";
import localize from "@/utils/localization";

import type { DatasetPair } from "../History/adapters/buildCollectionModal";

import DelayedInput from "../Common/DelayedInput.vue";
import DatasetCollectionElementView from "./ListDatasetCollectionElementView.vue";
import CollectionCreator from "@/components/Collections/common/CollectionCreator.vue";

Expand Down Expand Up @@ -42,6 +45,13 @@ const removeExtensions = ref(true);
const initialSuggestedName = ref("");
const invalidElements = ref<string[]>([]);
const workingElements = ref<HDASummary[]>([]);
const filterText = ref("");

const filteredElements = computed(() => {
return workingElements.value.filter((element) => {
return `${element.hid}: ${element.name}`.toLowerCase().includes(filterText.value.toLowerCase());
});
});

/** If not `fromSelection`, the manually added elements that will become the pair */
const inListElements = ref<SelectedDatasetPair>({ forward: undefined, reverse: undefined });
Expand All @@ -66,6 +76,13 @@ const pairElements = computed<SelectedDatasetPair>(() => {
return inListElements.value;
}
});
const pairHasMixedExtensions = computed(() => {
return (
pairElements.value.forward?.extension &&
pairElements.value.reverse?.extension &&
pairElements.value.forward.extension !== pairElements.value.reverse.extension
);
});

// variables for datatype mapping and then filtering
const datatypesMapperStore = useDatatypesMapperStore();
Expand All @@ -74,6 +91,16 @@ const datatypesMapper = computed(() => datatypesMapperStore.datatypesMapper);
/** Are we filtering by datatype? */
const filterExtensions = computed(() => !!datatypesMapper.value && !!props.extensions?.length);

// check if we have scrolled to the top or bottom of the scrollable div
const scrollableDiv = ref<HTMLDivElement | null>(null);
const { arrived } = useAnimationFrameScroll(scrollableDiv);
const isScrollable = ref(false);
useAnimationFrameResizeObserver(scrollableDiv, ({ clientSize, scrollSize }) => {
isScrollable.value = scrollSize.height >= clientSize.height + 1;
});
const scrolledTop = computed(() => !isScrollable.value || arrived.top);
const scrolledBottom = computed(() => !isScrollable.value || arrived.bottom);

watch(
() => props.initialElements,
() => {
Expand Down Expand Up @@ -355,17 +382,6 @@ function _naiveStartingAndEndingLCS(s1: string, s2: string) {
</ul>
</BAlert>
</div>
<div v-if="!exactlyTwoValidElements">
<BAlert show variant="warning" dismissible>
{{ localize("Exactly two elements are needed for the pair.") }}
<span v-if="fromSelection">
<a class="cancel-text" href="javascript:void(0)" role="button" @click="emit('on-cancel')">
{{ localize("Cancel") }}
</a>
{{ localize("and reselect new elements.") }}
</span>
</BAlert>
</div>

<CollectionCreator
:oncancel="() => emit('on-cancel')"
Expand Down Expand Up @@ -474,19 +490,48 @@ function _naiveStartingAndEndingLCS(s1: string, s2: string) {
</BAlert>
</div>
<div v-else>
<div class="collection-elements-controls">
<BButton
class="swap"
size="sm"
:disabled="!exactlyTwoValidElements"
:title="localize('Swap forward and reverse datasets')"
@click="swapButton">
<FontAwesomeIcon :icon="faArrowsAltV" fixed-width />
{{ localize("Swap") }}
</BButton>
<div class="collection-elements-controls flex-gapx-1">
<div>
<BButton
class="swap"
size="sm"
:disabled="!exactlyTwoValidElements"
:title="localize('Swap forward and reverse datasets')"
@click="swapButton">
<FontAwesomeIcon :icon="faArrowsAltV" fixed-width />
{{ localize("Swap") }}
</BButton>
</div>
<div class="flex-grow-1">
<BAlert v-if="!exactlyTwoValidElements" show variant="warning">
{{ localize("Exactly two elements are needed for the pair.") }}
<span v-if="fromSelection">
<a
class="cancel-text"
href="javascript:void(0)"
role="button"
@click="emit('on-cancel')">
{{ localize("Cancel") }}
</a>
{{ localize("and reselect new elements.") }}
</span>
</BAlert>
<BAlert v-else-if="pairHasMixedExtensions" show variant="warning">
{{ localize("The selected datasets have mixed extensions.") }}
{{
localize(
"You can still create the pair but its elements will have different extensions."
)
}}
</BAlert>
<BAlert v-else show variant="success">
{{ localize("The Dataset Pair is ready to be created.") }}
{{ localize("Provide a name and click the button below to create the pair.") }}
</BAlert>
</div>
</div>

<div class="collection-elements flex-row mb-3">
<div class="flex-row mb-3">
<div v-for="dataset in ['forward', 'reverse']" :key="dataset">
{{ localize(dataset) }}:
<DatasetCollectionElementView
Expand All @@ -502,20 +547,33 @@ function _naiveStartingAndEndingLCS(s1: string, s2: string) {
</div>

<div v-if="!fromSelection">
{{ localize("Manually select a forward and reverse dataset to create a pair collection:") }}
<div class="collection-elements">
<DatasetCollectionElementView
v-for="element in workingElements"
:key="element.id"
:class="{
selected: [pairElements.forward, pairElements.reverse].includes(element),
}"
:element="element"
not-editable
:selected="[pairElements.forward, pairElements.reverse].includes(element)"
@element-is-selected="selectElement"
@onRename="(name) => (element.name = name)" />
<DelayedInput v-model="filterText" placeholder="search datasets" :delay="800" />
<strong>
{{
localize("Manually select a forward and reverse dataset to create a dataset pair:")
}}
</strong>
<div
v-if="filteredElements.length"
class="scroll-list-container"
:class="{ 'scrolled-top': scrolledTop, 'scrolled-bottom': scrolledBottom }">
<div ref="scrollableDiv" class="collection-elements">
<DatasetCollectionElementView
v-for="element in filteredElements"
:key="element.id"
:class="{
selected: [pairElements.forward, pairElements.reverse].includes(element),
}"
:element="element"
not-editable
:selected="[pairElements.forward, pairElements.reverse].includes(element)"
@element-is-selected="selectElement"
@onRename="(name) => (element.name = name)" />
</div>
</div>
<BAlert v-else show variant="info">
{{ localize(`No datasets found${filterText ? " matching '" + filterText + "'" : ""}`) }}
</BAlert>
</div>
</div>
</template>
Expand All @@ -531,11 +589,19 @@ function _naiveStartingAndEndingLCS(s1: string, s2: string) {
}

.collection-elements-controls {
margin-bottom: 8px;
display: flex;
justify-content: space-between;
align-items: center;

.alert {
padding: 0.25rem 0.5rem;
margin: 0;
text-align: center;
}
}

.collection-elements {
max-height: 400px;
max-height: 30vh;
border: 0px solid lightgrey;
overflow-y: auto;
overflow-x: hidden;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ const props = withDefaults(defineProps<Props>(), {
extensions: undefined,
extensionsToggle: false,
showUpload: true,
collectionType: undefined,
});

const emit = defineEmits<{
Expand Down
Loading