mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
modernize/refactor PairedListCollectionCreator for input forms
- Converted the file(s) to composition API and typescript - Improved styling of the modal and its components Still need to add `extensions` handling for cases where a certain extension is required for the collection.
This commit is contained in:
@@ -1,69 +1,58 @@
|
||||
<script setup lang="ts">
|
||||
import { library } from "@fortawesome/fontawesome-svg-core";
|
||||
import { faUnlink } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faArrowLeft, faArrowRight, faUnlink } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
import { BButton } from "bootstrap-vue";
|
||||
import { ref, watch } from "vue";
|
||||
|
||||
import type { HDASummary } from "@/api";
|
||||
import localize from "@/utils/localization";
|
||||
|
||||
import ClickToEdit from "@/components/Collections/common/ClickToEdit.vue";
|
||||
|
||||
library.add(faUnlink);
|
||||
|
||||
interface Props {
|
||||
// TODO: Define the type of the pair prop
|
||||
pair: {
|
||||
name: string;
|
||||
forward: { name: string };
|
||||
reverse: { name: string };
|
||||
forward: HDASummary;
|
||||
reverse: HDASummary;
|
||||
};
|
||||
unlinkFn: () => void;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: "onPairRename", name: string): void;
|
||||
(event: "onUnpair"): void;
|
||||
}>();
|
||||
|
||||
const name = ref("");
|
||||
const elementName = ref(props.pair.name);
|
||||
|
||||
watch(
|
||||
() => props.pair,
|
||||
() => {
|
||||
name.value = props.pair.name;
|
||||
}
|
||||
);
|
||||
|
||||
watch(name, () => {
|
||||
emit("onPairRename", name.value);
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
name.value = props.pair.name;
|
||||
watch(elementName, () => {
|
||||
emit("onPairRename", elementName.value);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<li class="dataset paired">
|
||||
<li class="d-flex align-items-center justify-content-between">
|
||||
<div class="dataset paired w-100">
|
||||
<span class="forward-dataset-name flex-column">
|
||||
{{ pair.forward.name }}
|
||||
<FontAwesomeIcon :icon="faArrowRight" fixed-width />
|
||||
</span>
|
||||
|
||||
<span class="pair-name-column flex-column">
|
||||
<span class="pair-name">
|
||||
<ClickToEdit v-model="name" :title="localize('Click to rename')" />
|
||||
<ClickToEdit v-model="elementName" :title="localize('Click to rename')" />
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span class="reverse-dataset-name flex-column">
|
||||
<FontAwesomeIcon :icon="faArrowLeft" fixed-width />
|
||||
{{ pair.reverse.name }}
|
||||
</span>
|
||||
</li>
|
||||
</div>
|
||||
|
||||
<button class="unpair-btn" @click="unlinkFn">
|
||||
<BButton class="unpair-btn" variant="link" @click="emit('onUnpair')">
|
||||
<FontAwesomeIcon :icon="faUnlink" :title="localize('Unpair')" />
|
||||
</button>
|
||||
</div>
|
||||
</BButton>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,19 @@
|
||||
import _l from "utils/localization";
|
||||
import Vue from "vue";
|
||||
|
||||
import { orList } from "@/utils/strings";
|
||||
|
||||
import { collectionCreatorModalSetup } from "./common/modal";
|
||||
|
||||
import PairedListCollectionCreator from "./PairedListCollectionCreator.vue";
|
||||
|
||||
function pairedListCollectionCreatorModal(elements, options) {
|
||||
options = options || {};
|
||||
options.title = _l("Create a collection of paired datasets");
|
||||
options.title = _l(
|
||||
`Create a collection of ${options.fromSelection ? "selected" : ""} ${
|
||||
options.extensions?.length ? orList(options.extensions) : ""
|
||||
} dataset pairs`
|
||||
);
|
||||
const { promise, showEl } = collectionCreatorModalSetup(options);
|
||||
var pairedListCollectionCreatorInstance = Vue.extend(PairedListCollectionCreator);
|
||||
var vm = document.createElement("div");
|
||||
@@ -19,6 +25,8 @@ function pairedListCollectionCreatorModal(elements, options) {
|
||||
oncancel: options.oncancel,
|
||||
oncreate: options.oncreate,
|
||||
defaultHideSourceItems: options.defaultHideSourceItems,
|
||||
fromSelection: options.fromSelection,
|
||||
extensions: options.extensions,
|
||||
},
|
||||
}).$mount(vm);
|
||||
return promise;
|
||||
@@ -27,10 +35,12 @@ function pairedListCollectionCreatorModal(elements, options) {
|
||||
/** Use a modal to create a list collection, then add it to the given history contents.
|
||||
* @returns {Promise} resolved when the collection is added to the history.
|
||||
*/
|
||||
function createPairedListCollection(contents, defaultHideSourceItems) {
|
||||
function createPairedListCollection(contents) {
|
||||
const elements = contents.toJSON();
|
||||
const promise = pairedListCollectionCreatorModal(elements, {
|
||||
defaultHideSourceItems: defaultHideSourceItems,
|
||||
defaultHideSourceItems: contents.defaultHideSourceItems,
|
||||
fromSelection: contents.fromSelection,
|
||||
extensions: contents.extensions,
|
||||
creationFn: function (elements, name, hideSourceItems) {
|
||||
elements = elements.map((pair) => ({
|
||||
collection_type: "paired",
|
||||
|
||||
@@ -1,21 +1,66 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from "vue";
|
||||
|
||||
import type { HDASummary } from "@/api";
|
||||
import { useAnimationFrameSize } from "@/composables/sensors/animationFrameSize";
|
||||
|
||||
interface Props {
|
||||
// TODO: Define the type of the element
|
||||
element: any;
|
||||
element: HDASummary;
|
||||
disabled?: boolean;
|
||||
showExtension?: boolean;
|
||||
showHid?: boolean;
|
||||
}
|
||||
|
||||
defineProps<Props>();
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
disabled: false,
|
||||
showExtension: true,
|
||||
showHid: true,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
// TODO: Define the type of the element
|
||||
(event: "element-is-selected", element: any): void;
|
||||
(event: "element-is-selected", element: HDASummary): void;
|
||||
}>();
|
||||
|
||||
const unpairedElement = ref<HTMLElement | null>(null);
|
||||
const elementName = ref<HTMLElement | null>(null);
|
||||
|
||||
const { width: parentWidth } = useAnimationFrameSize(unpairedElement);
|
||||
|
||||
const childWidth = computed(() => elementName.value?.clientWidth);
|
||||
const shouldMoveElement = computed(() => {
|
||||
if (childWidth.value && parentWidth.value && childWidth.value <= parentWidth.value) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
function getExtension(): string {
|
||||
return "extension" in props.element && props.element.extension ? props.element.extension : "";
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<li class="dataset unpaired" @click="emit('element-is-selected', element)">
|
||||
<label>
|
||||
{{ element.name }}
|
||||
</label>
|
||||
<li
|
||||
ref="unpairedElement"
|
||||
class="dataset unpaired"
|
||||
:class="{ disabled: props.disabled }"
|
||||
:aria-disabled="props.disabled"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@keydown.enter="emit('element-is-selected', props.element)"
|
||||
@click="emit('element-is-selected', props.element)">
|
||||
<span ref="elementName" class="element-name" :class="{ moves: shouldMoveElement }">
|
||||
<span v-if="props.showHid">{{ props.element.hid }}:</span>
|
||||
<strong>{{ props.element.name }}</strong>
|
||||
<i v-if="props.showExtension"> ({{ getExtension() }}) </i>
|
||||
</span>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.disabled {
|
||||
pointer-events: none;
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
// Alphanumeric/natural sort fn
|
||||
function naturalSort(a, b) {
|
||||
// setup temp-scope variables for comparison evauluation
|
||||
var re = /(-?[0-9.]+)/g;
|
||||
|
||||
var x = a.toString().toLowerCase() || "";
|
||||
var y = b.toString().toLowerCase() || "";
|
||||
var nC = String.fromCharCode(0);
|
||||
var xN = x.replace(re, `${nC}$1${nC}`).split(nC);
|
||||
var yN = y.replace(re, `${nC}$1${nC}`).split(nC);
|
||||
var xD = new Date(x).getTime();
|
||||
var yD = xD ? new Date(y).getTime() : null;
|
||||
// natural sorting of dates
|
||||
if (yD) {
|
||||
if (xD < yD) {
|
||||
return -1;
|
||||
} else if (xD > yD) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// natural sorting through split numeric strings and default strings
|
||||
var oFxNcL;
|
||||
|
||||
var oFyNcL;
|
||||
for (var cLoc = 0, numS = Math.max(xN.length, yN.length); cLoc < numS; cLoc++) {
|
||||
oFxNcL = parseFloat(xN[cLoc]) || xN[cLoc];
|
||||
oFyNcL = parseFloat(yN[cLoc]) || yN[cLoc];
|
||||
if (oFxNcL < oFyNcL) {
|
||||
return -1;
|
||||
} else if (oFxNcL > oFyNcL) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
export default naturalSort;
|
||||
@@ -0,0 +1,45 @@
|
||||
// Alphanumeric/natural sort fn
|
||||
export function naturalSort(a = "", b = ""): number {
|
||||
// setup temp-scope variables for comparison evauluation
|
||||
const re = /(-?[0-9.]+)/g;
|
||||
|
||||
const x = a.toString().toLowerCase() || "";
|
||||
const y = b.toString().toLowerCase() || "";
|
||||
const nC = String.fromCharCode(0);
|
||||
const xN = x.replace(re, `${nC}$1${nC}`).split(nC);
|
||||
const yN = y.replace(re, `${nC}$1${nC}`).split(nC);
|
||||
const xD = new Date(x).getTime();
|
||||
const yD = xD ? new Date(y).getTime() : null;
|
||||
// natural sorting of dates
|
||||
if (yD) {
|
||||
if (xD < yD) {
|
||||
return -1;
|
||||
} else if (xD > yD) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// natural sorting through split numeric strings and default strings
|
||||
let oFxNcL;
|
||||
|
||||
let oFyNcL;
|
||||
for (let cLoc = 0, numS = Math.max(xN.length, yN.length); cLoc < numS; cLoc++) {
|
||||
oFxNcL = parseFloat(xN[cLoc] || "") || xN[cLoc];
|
||||
oFyNcL = parseFloat(yN[cLoc] || "") || yN[cLoc];
|
||||
|
||||
// Check if either part is undefined
|
||||
if (oFxNcL === undefined) {
|
||||
return -1;
|
||||
}
|
||||
if (oFyNcL === undefined) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (oFxNcL < oFyNcL) {
|
||||
return -1;
|
||||
} else if (oFxNcL > oFyNcL) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user