Skip to content

Commit

Permalink
Merge pull request #17238 from ElectronicBlueberry/form-data-extensio…
Browse files Browse the repository at this point in the history
…n-view

Add accepted extensions to form data input
  • Loading branch information
martenson authored Jan 18, 2024
2 parents 684a038 + c54bea0 commit afd4cca
Show file tree
Hide file tree
Showing 5 changed files with 190 additions and 87 deletions.
199 changes: 134 additions & 65 deletions client/src/components/Form/Elements/FormData/FormData.vue
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
<script setup lang="ts">
import { library } from "@fortawesome/fontawesome-svg-core";
import { faCopy, faFile, faFolder } from "@fortawesome/free-regular-svg-icons";
import { faExclamation, faLink, faUnlink } from "@fortawesome/free-solid-svg-icons";
import { faCaretDown, faCaretUp, faExclamation, faLink, faUnlink } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { BButton, BButtonGroup, BFormCheckbox } from "bootstrap-vue";
import { BAlert, BButton, BButtonGroup, BCollapse, BFormCheckbox, BTooltip } from "bootstrap-vue";
import { computed, onMounted, type Ref, ref, watch } from "vue";
import { getGalaxyInstance } from "@/app";
import { useUid } from "@/composables/utils/uid";
import { type EventData, useEventStore } from "@/stores/eventStore";
import { orList } from "@/utils/strings";
import type { DataOption } from "./types";
import { BATCH, SOURCE, VARIANTS } from "./variants";
import FormSelect from "@/components/Form/Elements/FormSelect.vue";
library.add(faCopy, faExclamation, faFile, faFolder, faLink, faUnlink);
library.add(faCopy, faFile, faFolder, faCaretDown, faCaretUp, faExclamation, faLink, faUnlink);
interface SelectOption {
type SelectOption = {
label: string;
value: DataOption | null;
}
};
const props = withDefaults(
defineProps<{
Expand All @@ -39,11 +41,11 @@ const props = withDefaults(
loading: false,
multiple: false,
optional: false,
value: null,
value: undefined,
extensions: () => [],
type: "data",
flavor: null,
tag: null,
flavor: undefined,
tag: undefined,
}
);
Expand Down Expand Up @@ -106,7 +108,7 @@ const currentValue = computed({
return value;
}
}
return null;
return undefined;
},
set: (val) => {
$emit("input", createValue(val));
Expand Down Expand Up @@ -219,7 +221,7 @@ function clearHighlighting(timeout = 1000) {
/**
* Create final input element value
*/
function createValue(val: Array<DataOption> | DataOption | null) {
function createValue(val?: Array<DataOption> | DataOption | null) {
if (val) {
const values = Array.isArray(val) ? val : [val];
if (variant.value && values.length > 0 && values[0]) {
Expand Down Expand Up @@ -358,9 +360,7 @@ function onBrowse() {
}
}
/**
* Drag/Drop event handlers
*/
// Drag/Drop event handlers
function onDragEnter(evt: MouseEvent) {
const eventData = eventStore.getDragData();
if (eventData) {
Expand Down Expand Up @@ -426,68 +426,137 @@ watch(
$emit("input", createValue(currentValue.value));
}
);
const formatsVisible = ref(false);
const formatsButtonId = useUid("form-data-formats-");
const warningListAmount = 4;
const noOptionsWarningMessage = computed(() => {
if (!props.extensions || props.extensions.length === 0) {
return "No datasets available";
} else if (props.extensions.length <= warningListAmount) {
return `No ${orList(props.extensions)} datasets available`;
} else {
return "No compatible datasets available";
}
});
</script>

<template>
<!-- eslint-disable-next-line vuejs-accessibility/no-static-element-interactions -->
<div
class="form-data"
:class="currentHighlighting && `ui-dragover-${currentHighlighting}`"
@dragenter.prevent="onDragEnter"
@dragleave.prevent="onDragLeave"
@dragover.prevent="onDragOver"
@drop.prevent="onDrop">
<div>
<div class="d-flex">
<BButtonGroup v-if="variant.length > 1" buttons class="align-self-start mr-2">
<BButton
v-for="(v, index) in variant"
:key="index"
v-b-tooltip.hover.bottom
:pressed="currentField === index"
:title="v.tooltip"
@click="currentField = index">
<FontAwesomeIcon :icon="['far', v.icon]" />
</BButton>
<BButton
v-if="canBrowse"
v-b-tooltip.hover.bottom
title="Browse or Upload Datasets"
@click="onBrowse">
<FontAwesomeIcon v-if="loading" icon="fa-spinner" spin />
<span v-else class="font-weight-bold">...</span>
</BButton>
</BButtonGroup>
<FormSelect
v-if="currentVariant"
v-model="currentValue"
:multiple="currentVariant.multiple"
:optional="optional"
:options="formattedOptions"
:placeholder="`Select a ${placeholder}`" />
</div>
<div v-if="currentVariant.batch !== BATCH.DISABLED">
<BFormCheckbox
v-if="currentVariant.batch === BATCH.ENABLED"
v-model="currentLinked"
class="no-highlight my-2"
switch>
<span v-if="currentLinked">
<FontAwesomeIcon icon="fa-link" />
<b v-localize class="mr-1">Linked:</b>
<span v-localize>Datasets will be run in matched order with other datasets.</span>
</span>
<span v-else>
<FontAwesomeIcon icon="fa-unlink" />
<b v-localize class="mr-1">Unlinked:</b>
<span v-localize>Dataset will be run against *all* other datasets.</span>
</span>
</BFormCheckbox>
<div class="text-info my-2">
<FontAwesomeIcon icon="fa-exclamation" />
<span v-localize class="ml-1">
This is a batch mode input field. Individual jobs will be triggered for each dataset.
</span>
</div>
<div class="d-flex flex-column">
<BButtonGroup v-if="variant && variant.length > 1" buttons class="align-self-start">
<BButton
v-for="(v, index) in variant"
:key="index"
v-b-tooltip.hover.bottom
:pressed="currentField === index"
:title="v.tooltip"
@click="currentField = index">
<FontAwesomeIcon :icon="['far', v.icon]" />
</BButton>
<BButton v-if="canBrowse" v-b-tooltip.hover.bottom title="Browse or Upload Datasets" @click="onBrowse">
<FontAwesomeIcon v-if="loading" icon="fa-spinner" spin />
<span v-else class="font-weight-bold">...</span>
</BButton>
</BButtonGroup>
<div v-if="extensions && extensions.length > 0">
<BButton :id="formatsButtonId" class="ui-link" @click="formatsVisible = !formatsVisible">
accepted formats
<FontAwesomeIcon v-if="formatsVisible" icon="fa-caret-up" />
<FontAwesomeIcon v-else icon="fa-caret-down" />
</BButton>
<BCollapse v-model="formatsVisible">
<ul class="pl-3 m-0">
<li v-for="extension in extensions" :key="extension">{{ extension }}</li>
</ul>
</BCollapse>
<BTooltip :target="formatsButtonId" noninteractive placement="bottom" triggers="hover">
<div class="form-data-extensions-tooltip">
<span v-for="extension in extensions" :key="extension">{{ extension }}</span>
</div>
</BTooltip>
</div>
</div>

<FormSelect
v-if="currentVariant"
v-model="currentValue"
class="align-self-start"
:multiple="currentVariant.multiple"
:optional="optional"
:options="formattedOptions"
:placeholder="`Select a ${placeholder}`">
<template v-slot:no-options>
<BAlert variant="warning" show>
{{ noOptionsWarningMessage }}
</BAlert>
</template>
</FormSelect>

<template v-if="currentVariant && currentVariant.batch !== BATCH.DISABLED">
<BFormCheckbox
v-if="currentVariant.batch === BATCH.ENABLED"
v-model="currentLinked"
class="checkbox no-highlight"
switch>
<span v-if="currentLinked">
<FontAwesomeIcon icon="fa-link" />
<b v-localize class="mr-1">Linked:</b>
<span v-localize>Datasets will be run in matched order with other datasets.</span>
</span>
<span v-else>
<FontAwesomeIcon icon="fa-unlink" />
<b v-localize class="mr-1">Unlinked:</b>
<span v-localize>Dataset will be run against *all* other datasets.</span>
</span>
</BFormCheckbox>
<div class="info text-info">
<FontAwesomeIcon icon="fa-exclamation" />
<span v-localize class="ml-1">
This is a batch mode input field. Individual jobs will be triggered for each dataset.
</span>
</div>
</template>
</div>
</template>

<style scoped lang="scss">
.form-data {
display: grid;
grid-template-columns: auto 1fr;
gap: 0.5rem;
.checkbox {
grid-column: span 2;
}
.info {
grid-column: span 2;
}
}
</style>

<style lang="scss">
.form-data-extensions-tooltip {
display: flex;
flex-wrap: wrap;
column-gap: 0.25rem;
font-size: 0.8rem;
span::after {
content: ", ";
}
span:last-child::after {
content: none;
}
}
</style>
4 changes: 2 additions & 2 deletions client/src/components/Form/Elements/FormData/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export interface DataOption {
export type DataOption = {
id: string;
hid: number;
is_dataset?: boolean;
Expand All @@ -7,4 +7,4 @@ export interface DataOption {
name: string;
src: string;
tags: Array<string>;
}
};
42 changes: 23 additions & 19 deletions client/src/components/Form/Elements/FormSelect.vue
Original file line number Diff line number Diff line change
Expand Up @@ -154,23 +154,27 @@ onMounted(() => {
</script>

<template>
<Multiselect
v-if="hasOptions"
:id="id"
v-model="currentValue"
:allow-empty="optional"
:aria-expanded="ariaExpanded"
:close-on-select="!multiple"
:disabled="disabled"
:deselect-label="deselectLabel"
label="label"
:multiple="multiple"
:options="reorderedOptions"
:placeholder="placeholder"
:selected-label="selectedLabel"
select-label="Click to select"
track-by="value"
@open="onOpen"
@close="onClose" />
<b-alert v-else v-localize class="w-100" variant="warning" show> No options available. </b-alert>
<div>
<Multiselect
v-if="hasOptions"
:id="id"
v-model="currentValue"
:allow-empty="optional"
:aria-expanded="ariaExpanded"
:close-on-select="!multiple"
:disabled="disabled"
:deselect-label="deselectLabel"
label="label"
:multiple="multiple"
:options="reorderedOptions"
:placeholder="placeholder"
:selected-label="selectedLabel"
select-label="Click to select"
track-by="value"
@open="onOpen"
@close="onClose" />
<slot v-else name="no-options">
<b-alert v-localize class="w-100" variant="warning" show> No options available. </b-alert>
</slot>
</div>
</template>
2 changes: 1 addition & 1 deletion client/src/components/Form/FormElement.vue
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ const isOptional = computed(() => !isRequired.value && attrs.value["optional"] !
:id="id"
v-model="currentValue"
:loading="loading"
:extension="attrs.extension"
:extensions="attrs.extensions"
:flavor="attrs.flavor"
:multiple="attrs.multiple"
:optional="attrs.optional"
Expand Down
30 changes: 30 additions & 0 deletions client/src/utils/strings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* Converts an array of strings to a list, ending with "or"
*
* @example
* // returns the string "a, b or c"
* orList(["a", "b", "c"]);
* @param items array of strings to join
* @returns human readable comma + or separated list
*/
export function orList(items: string[]): string {
if (items.length === 0) {
return "";
} else if (items.length === 1) {
return items[0] as string;
}

return items
.reverse()
.flatMap((item, index) => {
if (index === 0) {
return [item, " or "];
} else if (index !== 1) {
return [", ", item];
} else {
return [item];
}
})
.reverse()
.join("");
}

0 comments on commit afd4cca

Please sign in to comment.