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:
Ahmed Awan
2024-12-02 16:08:24 -06:00
parent f53170f430
commit 35ff19da97
6 changed files with 1127 additions and 870 deletions
@@ -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>
-38
View File
@@ -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;
+45
View File
@@ -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;
}