Remove legacy upload components from client UI

This commit is contained in:
davelopez
2026-06-08 17:38:48 +02:00
parent 757c4b6136
commit 56395148c7
11 changed files with 0 additions and 1799 deletions
@@ -1,52 +0,0 @@
import { getLocalVue } from "@tests/vitest/helpers";
import { mount } from "@vue/test-utils";
import { describe, expect, it } from "vitest";
import mountTarget from "./CompositeBox.vue";
const localVue = getLocalVue();
function getWrapper() {
return mount(mountTarget, {
propsData: {
defaultDbKey: "?",
effectiveExtensions: [
{
id: "affybatch",
text: "affybatch",
composite_files: [
{
name: "%s.pheno",
optional: false,
description: "Phenodata tab text file",
},
{
name: "%s.affybatch",
optional: false,
description: "AffyBatch R object saved to file",
},
],
},
],
fileSourcesConfigured: true,
ftpUploadSite: null,
historyId: "historyId",
listDbKeys: [],
},
localVue,
});
}
describe("Composite", () => {
it("rendering", async () => {
const wrapper = getWrapper();
expect(wrapper.find("#btn-start").classes()).toEqual(expect.arrayContaining(["disabled"]));
expect(wrapper.vm.showHelper).toBe(true);
expect(wrapper.vm.enableStart).toBe(false);
const extensions = wrapper.vm.listExtensions;
expect(extensions.length).toBe(2);
expect(extensions[0].id).toBe(null);
expect(extensions[0].text).toBe("Select");
expect(extensions[1].id).toBe("affybatch");
});
});
@@ -1,227 +0,0 @@
<script setup>
import { BButton } from "bootstrap-vue";
import Vue, { computed, ref } from "vue";
import { buildLegacyPayload, submitUpload } from "@/utils/upload";
import { defaultModel } from "./model";
import CompositeRow from "./CompositeRow.vue";
import UploadSelect from "./UploadSelect.vue";
const props = defineProps({
defaultDbKey: {
type: String,
required: true,
},
effectiveExtensions: {
type: Array,
required: true,
},
fileSourcesConfigured: {
type: Boolean,
required: true,
},
ftpUploadSite: {
type: String,
default: null,
},
hasCallback: {
type: Boolean,
default: false,
},
historyId: {
type: String,
default: null,
},
listDbKeys: {
type: Array,
required: true,
},
});
const extension = ref(null);
const dbKey = ref(props.defaultDbKey);
const uploadItems = ref({});
const enableStart = computed(() => {
const incomplete = uploadValues.value.find((v) => v.status === "init" && !v.optional && v.fileSize === 0);
return !isRunning.value && uploadValues.value.length > 0 && !incomplete;
});
const hasRemoteFiles = computed(() => props.fileSourcesConfigured || !!props.ftpUploadSite);
const isRunning = computed(() => {
const model = uploadKeys.value[0];
return model && model.status === "running";
});
const listExtensions = computed(() => {
const result = props.effectiveExtensions.filter((ext) => ext.composite_files);
result.unshift({ id: null, text: "Select" });
return result;
});
const showHelper = computed(() => uploadKeys.value.length === 0);
const uploadValues = computed(() => Object.values(uploadItems.value));
const uploadKeys = computed(() => Object.keys(uploadItems.value));
/** Refresh error state */
function eventError(message) {
uploadValues.value.forEach((model) => {
model.info = message;
model.status = "error";
});
}
/** Update model */
function eventInput(index, newData) {
const it = uploadItems.value[index];
Object.entries(newData).forEach(([key, value]) => {
it[key] = value;
});
restoreStatus();
}
/** Refresh progress state */
function eventProgress(percentage) {
uploadValues.value.forEach((model) => {
model.percentage = percentage;
});
}
/** Remove all */
function eventReset() {
if (!uploadValues.value.find((v) => v.status === "running")) {
uploadItems.value = {};
dbKey.value = props.defaultDbKey;
}
}
/** Start upload process */
function eventStart() {
uploadValues.value.forEach((model) => {
model.dbKey = dbKey.value;
model.extension = extension.value;
});
try {
submitUpload({
data: buildLegacyPayload(uploadValues.value, props.historyId, true),
error: eventError,
progress: eventProgress,
success: eventSuccess,
isComposite: true,
});
} catch (e) {
eventError(String(e));
}
}
/** Refresh success state */
function eventSuccess() {
uploadValues.value.forEach((model) => {
model.percentage = 100;
model.status = "success";
});
}
function inputDbkey(newDbkey) {
dbKey.value = newDbkey;
restoreStatus();
}
function inputExtension(newExtension) {
extension.value = newExtension;
uploadItems.value = {};
let uploadCount = 0;
const extensionDetails = listExtensions.value.find((v) => v.id === newExtension);
if (extensionDetails && extensionDetails.composite_files) {
extensionDetails.composite_files.forEach((item) => {
const index = String(uploadCount++);
const uploadModel = {
...defaultModel,
description: item.description || item.name,
optional: item.optional,
};
Vue.set(uploadItems.value, index, uploadModel);
});
}
restoreStatus();
}
/** Refresh init state if any user changes attributes */
function restoreStatus() {
uploadValues.value.forEach((model) => {
model.percentage = 0;
model.status = "init";
});
}
defineExpose({
enableStart,
listExtensions,
showHelper,
});
</script>
<template>
<div class="upload-wrapper">
<div class="upload-header">&nbsp;</div>
<div class="upload-box">
<div v-show="showHelper" v-localize class="upload-helper">Select a composite type</div>
<div v-show="!showHelper">
<CompositeRow
v-for="(uploadItem, uploadIndex) in uploadItems"
:key="uploadIndex"
:index="uploadIndex"
:file-description="uploadItem.description"
:file-content="uploadItem.fileContent"
:file-mode="uploadItem.fileMode"
:file-name="uploadItem.fileName"
:file-size="uploadItem.fileSize"
:info="uploadItem.info"
:has-remote-files="hasRemoteFiles"
:optional="uploadItem.optional"
:percentage="uploadItem.percentage"
:space-to-tab="uploadItem.spaceToTab"
:status="uploadItem.status"
:to-posix-lines="uploadItem.toPosixLines"
@input="eventInput" />
</div>
</div>
<div class="upload-footer">
<span v-localize class="upload-footer-title">Composite Type:</span>
<UploadSelect
class="upload-footer-extension"
:value="null"
:options="listExtensions"
:disabled="isRunning"
what="file type"
@input="inputExtension" />
<span v-localize class="upload-footer-title">Reference:</span>
<UploadSelect
what="reference"
:value="dbKey"
:options="listDbKeys"
:disabled="isRunning"
@input="inputDbkey" />
</div>
<div class="upload-buttons d-flex justify-content-end">
<BButton
id="btn-start"
:disabled="!enableStart"
title="Start"
:variant="enableStart ? 'primary' : null"
@click="eventStart">
<span v-localize>Start</span>
</BButton>
<BButton id="btn-reset" title="Reset" @click="eventReset">
<span v-localize>Reset</span>
</BButton>
<BButton id="btn-close" title="Close" @click="$emit('dismiss')">
<span v-if="hasCallback" v-localize>Close</span>
<span v-else v-localize>Cancel</span>
</BButton>
</div>
</div>
</template>
@@ -1,225 +0,0 @@
<script setup>
import {
faCheck,
faEdit,
faExclamation,
faExclamationTriangle,
faFolderOpen,
faLaptop,
faSpinner,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { BDropdown, BDropdownItem } from "bootstrap-vue";
import { computed, ref } from "vue";
import { filesDialog } from "@/utils/dataModals";
import { bytesToString } from "@/utils/utils";
import { DEFAULT_FILE_NAME } from "./utils";
import UploadSettings from "./UploadSettings.vue";
const props = defineProps({
fileContent: {
type: String,
required: true,
},
fileDescription: {
type: String,
default: null,
},
fileMode: {
type: String,
required: true,
},
fileName: {
type: String,
required: true,
},
fileSize: {
type: Number,
required: true,
},
index: {
type: String,
required: true,
},
info: {
type: String,
default: null,
},
hasRemoteFiles: {
type: Boolean,
default: false,
},
optional: {
type: Boolean,
required: true,
},
percentage: {
type: Number,
required: true,
},
spaceToTab: {
type: Boolean,
required: true,
},
status: {
type: String,
required: true,
},
toPosixLines: {
type: Boolean,
required: true,
},
});
const emit = defineEmits(["input"]);
const isDisabled = computed(() => props.status === "running");
const isDragging = ref(false);
const uploadFile = ref(null);
function inputFileContent(newFileContent) {
emit("input", props.index, {
fileContent: newFileContent,
fileSize: newFileContent.length,
});
}
function inputDialog(files) {
if (files && files.length > 0) {
emit("input", props.index, {
fileData: files[0],
fileMode: "local",
fileName: files[0].name,
filePath: null,
fileSize: files[0].size,
});
}
}
function inputPaste() {
emit("input", props.index, {
fileData: null,
fileMode: "new",
fileName: DEFAULT_FILE_NAME,
filePath: null,
fileSize: 0,
});
}
/** Show remote files dialog or FTP files */
function inputRemoteFiles() {
filesDialog(
(item) => {
emit("input", props.index, {
fileData: null,
fileMode: "url",
fileName: item.label,
filePath: item.url,
fileSize: item.size,
});
},
{ multiple: false },
);
}
function inputSettings(settingId) {
const newSettings = {};
newSettings[settingId] = !props[settingId];
emit("input", props.index, newSettings);
}
/** Handle files dropped into the upload row **/
function onDrop(evt) {
isDragging.value = false;
const droppedFile = evt.dataTransfer && evt.dataTransfer.files && evt.dataTransfer.files[0];
if (droppedFile) {
emit("input", props.index, {
fileData: droppedFile,
fileMode: "local",
fileName: droppedFile.name,
filePath: null,
fileSize: droppedFile.size,
});
}
}
</script>
<template>
<div
:id="`upload-row-${index}`"
class="upload-row rounded my-1 p-2"
:class="[`upload-${status}`, isDragging && 'bg-warning']"
@dragover.prevent="isDragging = true"
@dragleave.prevent="isDragging = false"
@drop.prevent="onDrop">
<div class="d-flex justify-content-around">
<div>
<BDropdown
:id="`upload-type-${index}`"
class="upload-source"
:disabled="isDisabled"
text="Select"
:variant="fileSize > 0 ? 'secondary' : 'primary'">
<BDropdownItem @click="uploadFile.click()">
<FontAwesomeIcon :icon="faLaptop" />
<span v-localize>Choose local file</span>
</BDropdownItem>
<BDropdownItem v-if="hasRemoteFiles" @click="inputRemoteFiles">
<FontAwesomeIcon :icon="faFolderOpen" />
<span v-localize>Choose from repository</span>
</BDropdownItem>
<BDropdownItem @click="inputPaste">
<FontAwesomeIcon :icon="faEdit" />
<span v-localize>Paste/Fetch data</span>
</BDropdownItem>
</BDropdown>
</div>
<div class="upload-title">
{{ fileDescription }}
</div>
<div class="upload-title">
{{ fileName || "-" }}
</div>
<div class="upload-size">
{{ bytesToString(fileSize) }}
</div>
<UploadSettings
:disabled="isDisabled"
:to-posix-lines="toPosixLines"
:space-to-tab="spaceToTab"
@input="inputSettings" />
<div class="upload-progress">
<div class="progress">
<div
class="upload-progress-bar progress-bar progress-bar-success"
:style="{ width: `${percentage}%` }" />
<div class="upload-percentage">{{ percentage }}%</div>
</div>
</div>
<div>
<FontAwesomeIcon v-if="['running', 'queued'].includes(status)" :icon="faSpinner" spin fixed-width />
<FontAwesomeIcon v-else-if="status === 'error'" :icon="faExclamationTriangle" fixed-width />
<FontAwesomeIcon v-else-if="fileSize > 0" :icon="faCheck" fixed-width />
<FontAwesomeIcon v-else-if="optional" class="text-info" :icon="faCheck" fixed-width />
<FontAwesomeIcon v-else :icon="faExclamation" fixed-width />
</div>
</div>
<div v-if="info" v-localize class="upload-text-message font-weight-bold">
{{ info }}
</div>
<div v-if="fileMode == 'new'">
<div class="upload-text-message">
Download data from the web by entering URLs (one per line) or directly paste content.
</div>
<b-textarea
:value="fileContent"
class="upload-text-content form-control"
:disabled="isDisabled"
@input="inputFileContent" />
</div>
<input ref="uploadFile" type="file" @change="inputDialog($event.target.files)" />
</div>
</template>
@@ -1,101 +0,0 @@
import { createTestingPinia } from "@pinia/testing";
import { getLocalVue } from "@tests/vitest/helpers";
import { mount } from "@vue/test-utils";
import flushPromises from "flush-promises";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import DefaultBox from "./DefaultBox.vue";
const localVue = getLocalVue();
type IntersectionObserverType = {
new (callback: IntersectionObserverCallback, options?: IntersectionObserverInit): IntersectionObserver;
prototype: IntersectionObserver;
};
function getWrapper() {
return mount(DefaultBox as object, {
propsData: {
chunkUploadSize: 100,
defaultDbKey: "?",
defaultExtension: "auto",
effectiveExtensions: [{ id: "ab1" }],
fileSourcesConfigured: true,
ftpUploadSite: null,
historyId: "historyId",
lazyLoad: 3,
listDbKeys: [],
},
localVue,
stubs: {
FontAwesomeIcon: true,
},
pinia: createTestingPinia({ createSpy: vi.fn }),
});
}
describe("Default", () => {
let UnpatchedIntersectionObserver: IntersectionObserverType;
beforeEach(() => {
UnpatchedIntersectionObserver = global.IntersectionObserver;
// The use of b-textarea in this DefaultRow causes the following warning:
// [Vue warn]: Error in directive b-visible unbind hook: "TypeError: this.observer.disconnect is not a function"
// I don't think there is a problem with the usage so I think this a bug in bootstrap vue, it can be worked around
// with the following code - but just suppressing the warning is probably better?
const observerMock = vi.fn(function IntersectionObserver(this: any, callback: IntersectionObserverCallback) {
this.observe = vi.fn();
this.disconnect = vi.fn();
this.trigger = (mockedMutationsList: IntersectionObserverEntry[]) => {
callback(mockedMutationsList, this);
};
});
global.IntersectionObserver = observerMock as unknown as IntersectionObserverType;
});
afterEach(() => {
global.IntersectionObserver = UnpatchedIntersectionObserver;
});
it("rendering", async () => {
const wrapper = getWrapper();
expect((wrapper.vm as any).counterAnnounce).toBe(0);
expect((wrapper.vm as any).showHelper).toBe(true);
expect((wrapper.vm as any).listExtensions[0].id).toBe("ab1");
expect(wrapper.find("#btn-reset").classes()).toEqual(expect.arrayContaining(["g-disabled"]));
expect(wrapper.find("#btn-start").classes()).toEqual(expect.arrayContaining(["g-disabled"]));
expect(wrapper.find("#btn-stop").classes()).toEqual(expect.arrayContaining(["g-disabled"]));
await flushPromises();
});
it("resets properly", async () => {
const wrapper = getWrapper();
expect((wrapper.vm as any).showHelper).toBe(true);
await wrapper.find("#btn-new").trigger("click");
expect((wrapper.vm as any).showHelper).toBe(false);
expect((wrapper.vm as any).counterAnnounce).toBe(1);
await wrapper.find("#btn-reset").trigger("click");
expect((wrapper.vm as any).showHelper).toBe(true);
await flushPromises();
});
it("does render remote files / repository button", async () => {
const wrapper = getWrapper();
expect(wrapper.find("#btn-remote-files").exists()).toBeTruthy();
await wrapper.setProps({ fileSourcesConfigured: false });
expect(wrapper.find("#btn-remote-files").exists()).toBeFalsy();
await flushPromises();
});
it("renders a limited set", async () => {
const wrapper = getWrapper();
for (let i = 0; i < 5; i++) {
await wrapper.find("#btn-new").trigger("click");
}
expect(wrapper.findAll(".upload-row").length).toBe(3);
const textMessage = wrapper.find("[data-description='lazyload message']");
expect(textMessage.text()).toBe("Only showing first 3 of 5 entries.");
await flushPromises();
});
});
-600
View File
@@ -1,600 +0,0 @@
<script setup lang="ts">
import { faCopy, faEdit, faFolderOpen, faLaptop } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { BBadge } from "bootstrap-vue";
import Vue, { computed, type Ref, ref } from "vue";
import { useRouter } from "vue-router/composables";
import type { HDASummary } from "@/api";
import type { CollectionBuilderType } from "@/components/Collections/common/buildCollectionModal";
import type { SelectionItem } from "@/components/SelectionDialog/selectionTypes";
import { monitorUploadedHistoryItems } from "@/composables/monitorUploadedHistoryItems";
import type { DbKey, ExtensionDetails } from "@/composables/uploadConfigurations";
import { archiveExplorerEventBus, type ArchiveSource } from "@/composables/zipExplorer";
import { useActivityStore } from "@/stores/activityStore";
import { filesDialog } from "@/utils/dataModals";
import { UploadQueue } from "@/utils/upload-queue.js";
import type { ComponentSize } from "../BaseComponents/componentVariants";
import type { UploadFile, UploadRowModel } from "./model";
import { defaultModel, isLocalFile } from "./model";
import { COLLECTION_TYPES, DEFAULT_FILE_NAME, hasBrowserSupport } from "./utils";
import GButton from "../BaseComponents/GButton.vue";
import DefaultRow from "./DefaultRow.vue";
import UploadBox from "./UploadBox.vue";
import UploadSelect from "./UploadSelect.vue";
import UploadSelectExtension from "./UploadSelectExtension.vue";
import CollectionCreatorIndex from "@/components/Collections/CollectionCreatorIndex.vue";
const router = useRouter();
const activityStore = useActivityStore("default");
interface Props {
chunkUploadSize: number;
defaultDbKey: string;
defaultExtension: string;
effectiveExtensions: ExtensionDetails[];
fileSourcesConfigured: boolean;
ftpUploadSite?: string;
historyId: string;
multiple?: boolean;
hasCallback?: boolean;
lazyLoad?: number;
listDbKeys: DbKey[];
isCollection?: boolean;
disableFooter?: boolean;
emitUploaded?: boolean;
size?: ComponentSize;
showUploadActivity?: boolean;
showBetaUpload?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
ftpUploadSite: undefined,
multiple: true,
lazyLoad: 150,
size: "medium",
isCollection: false,
showUploadActivity: true,
showBetaUpload: true,
});
const emit = defineEmits<{
(e: "dismiss"): void;
(e: "progress", value: number | null, variant?: string): void;
(e: "uploaded", value: HDASummary[]): void;
}>();
const collectionModalShow = ref(false);
const collectionType = ref<CollectionBuilderType>("list");
const counterAnnounce = ref(0);
const counterError = ref(0);
const counterRunning = ref(0);
const counterSuccess = ref(0);
const extension = ref(props.defaultExtension);
const dbKey = ref(props.defaultDbKey);
const queueStopping = ref(false);
const uploadCompleted = ref(0);
const uploadFile = ref<HTMLInputElement | null>(null);
const uploadItems = ref<Record<string, UploadRowModel>>({});
const uploadSize = ref(0);
const queue = ref(createUploadQueue());
const selectedItemsForModal = ref<HDASummary[]>([]);
const counterNonRunning = computed(() => counterAnnounce.value + counterSuccess.value + counterError.value);
const creatingPairedType = computed(() => props.isCollection && collectionType.value === "list:paired");
const enableBuild = computed(
() =>
!isRunning.value &&
counterAnnounce.value == 0 &&
counterSuccess.value > 0 &&
uploadedHistoryItemsReady.value &&
uploadedHistoryItemsOk.value.length > 0 &&
(!creatingPairedType.value || uploadedHistoryItemsOk.value.length % 2 === 0),
);
const enableReset = computed(() => !isRunning.value && counterNonRunning.value > 0);
const enableStart = computed(() => !isRunning.value && counterAnnounce.value > 0);
const enableSources = computed(() => !isRunning.value && (props.multiple || counterNonRunning.value == 0));
const isRunning = computed(() => counterRunning.value > 0);
const hasRemoteFiles = computed(() => props.fileSourcesConfigured || !!props.ftpUploadSite);
const historyId = computed(() => props.historyId);
const listExtensions = computed(() => props.effectiveExtensions.filter((ext) => !ext.composite_files));
const showHelper = computed(() => Object.keys(uploadItems.value).length === 0);
const uploadValues = computed(() => Object.values(uploadItems.value));
const { uploadedHistoryItemsOk, uploadedHistoryItemsReady, historyItemsStateInfo } = monitorUploadedHistoryItems(
uploadValues as Ref<UploadRowModel[]>,
historyId,
enableStart,
creatingPairedType,
);
function createUploadQueue() {
return new UploadQueue({
announce: eventAnnounce,
chunkSize: props.chunkUploadSize,
complete: eventComplete,
error: eventError,
get: (index: string) => uploadItems.value[index],
multiple: props.multiple,
progress: eventProgress,
success: eventSuccess,
warning: eventWarning,
});
}
/** Add files to queue */
function addFiles(files: FileList, immediate = false) {
if (!isRunning.value) {
if (immediate || !props.multiple) {
eventReset();
}
if (props.multiple) {
queue.value.add(files);
} else if (files.length > 0) {
queue.value.add([files[0]]);
}
}
}
function addFileFromInput(eventTarget: EventTarget | null) {
if (!eventTarget) {
return;
}
const { files } = eventTarget as HTMLInputElement;
if (files) {
addFiles(files);
}
}
/** A new file has been announced to the upload queue */
function eventAnnounce(index: string, file: UploadFile) {
counterAnnounce.value++;
const mode = file.mode || "local";
let deferred: boolean | undefined = false;
if (mode === "local") {
deferred = undefined;
}
const uploadModel = {
...defaultModel,
id: index,
dbKey: dbKey.value,
extension: extension.value,
fileData: file,
fileMode: mode,
deferred: deferred,
fileName: file.name,
filePath: file.path,
fileSize: file.size,
fileUri: file.uri,
};
Vue.set(uploadItems.value, index, uploadModel);
}
/** Populates and opens collection builder with uploaded files, or emits uploads */
async function eventBuild(openModal = false) {
if (openModal) {
selectedItemsForModal.value = uploadedHistoryItemsOk.value;
collectionModalShow.value = true;
} else {
emit("uploaded", uploadedHistoryItemsOk.value);
counterRunning.value = 0;
eventReset();
emit("dismiss");
}
}
/** Queue is done */
function eventComplete() {
uploadValues.value.forEach((model) => {
if (model.status === "queued") {
model.status = "init";
}
});
counterRunning.value = 0;
queueStopping.value = false;
}
/** Create a new file */
function eventCreate() {
queue.value.add([{ name: DEFAULT_FILE_NAME, size: 0, mode: "new" }]);
}
/** Error */
function eventError(index: string, message: string) {
const it = uploadItems.value[index];
if (it) {
it.percentage = 100;
it.status = "error";
it.info = message;
uploadCompleted.value += it.fileSize * 100;
counterAnnounce.value--;
counterError.value++;
emit("progress", uploadPercentage(100, it.fileSize), "danger");
}
}
/** Update model */
function eventInput(index: string, newData: Partial<UploadRowModel>) {
const it = uploadItems.value[index];
if (it) {
Object.entries(newData).forEach(([key, value]) => {
(it as any)[key] = value;
});
}
}
/** Reflect upload progress */
function eventProgress(index: string, percentage: number) {
const it = uploadItems.value[index];
if (it) {
it.percentage = percentage;
emit("progress", uploadPercentage(percentage, it.fileSize));
}
}
/** Remove model from upload list */
function eventRemove(index: string) {
const it = uploadItems.value[index];
if (it) {
var status = it.status;
if (status == "success") {
counterSuccess.value--;
} else if (status == "error") {
counterError.value--;
} else {
counterAnnounce.value--;
}
Vue.delete(uploadItems.value, index);
queue.value.remove(index);
}
}
async function eventExplore(archiveSource: ArchiveSource) {
await router.push({ name: "ZipImportWizard" });
archiveExplorerEventBus.emit("set-archive-source", archiveSource);
emit("dismiss");
}
/** Show remote files dialog or FTP files */
function eventRemoteFiles() {
filesDialog(
(items: SelectionItem[]) => {
queue.value.add(
items.map((item) => {
const rval = {
mode: "url",
name: item.label,
size: item.entry.size,
path: item.url,
hashes: item.entry.hashes,
};
return rval;
}),
);
},
{ multiple: true },
(route: string) => {
router.push(route);
emit("dismiss");
},
);
}
/** Remove all */
function eventReset() {
if (!isRunning.value) {
counterAnnounce.value = 0;
counterSuccess.value = 0;
counterError.value = 0;
queue.value.reset();
uploadItems.value = {};
extension.value = props.defaultExtension;
dbKey.value = props.defaultDbKey;
emit("progress", 0);
}
}
/** Success */
function eventSuccess(index: string, incoming: any) {
var it = uploadItems.value[index];
if (it) {
it.percentage = 100;
it.status = "success";
it.outputs = incoming.outputs || incoming.data.outputs || {};
emit("progress", uploadPercentage(100, it.fileSize));
uploadCompleted.value += it.fileSize * 100;
counterAnnounce.value--;
counterSuccess.value++;
}
}
/** Start upload process */
function eventStart() {
if (!isRunning.value && counterAnnounce.value > 0) {
uploadSize.value = 0;
uploadCompleted.value = 0;
uploadValues.value.forEach((model) => {
if (model.status === "init") {
model.status = "queued";
if (!model.targetHistoryId) {
// Associate with current history once upload starts
// This will not change if the current history is changed during upload
model.targetHistoryId = historyId.value;
}
uploadSize.value += model.fileSize;
}
});
emit("progress", 0, "success");
counterRunning.value = counterAnnounce.value;
queue.value.start();
}
}
/** Pause upload process */
function eventStop() {
if (isRunning.value) {
emit("progress", null, "info");
queueStopping.value = true;
queue.value.stop();
}
}
/** Display warning */
function eventWarning(index: string, message: string) {
const it = uploadItems.value[index];
if (it) {
it.status = "warning";
it.info = message;
}
}
/** Update collection type */
function updateCollectionType(newCollectionType: CollectionBuilderType) {
collectionType.value = newCollectionType;
}
/* Update extension type for all entries */
function updateExtension(newExtension: string) {
extension.value = newExtension;
uploadValues.value.forEach((model) => {
if (model.status === "init" && model.extension === props.defaultExtension) {
model.extension = newExtension;
}
});
}
/** Update reference dataset for all entries */
function updateDbKey(newDbKey: string) {
dbKey.value = newDbKey;
uploadValues.value.forEach((model) => {
if (model.status === "init" && model.dbKey === props.defaultDbKey) {
model.dbKey = newDbKey;
}
});
}
/** Calculate percentage of all queued uploads */
function uploadPercentage(percentage: number, size: number) {
return (uploadCompleted.value + percentage * size) / uploadSize.value;
}
function openUploadActivity() {
const uploadActivity = activityStore.findById("upload");
if (uploadActivity) {
if (!uploadActivity.visible) {
activityStore.ensureVisible(uploadActivity.id);
activityStore.setPosition(uploadActivity.id, 0);
}
activityStore.ensureSideBarOpen(uploadActivity.id);
emit("dismiss");
}
}
defineExpose({
addFiles,
counterAnnounce,
listExtensions,
showHelper,
});
</script>
<template>
<div class="upload-wrapper">
<div class="upload-header">
<div v-if="queueStopping" v-localize>Queue will pause after completing the current file...</div>
<div v-else-if="counterAnnounce === 0">
<div v-if="!!hasBrowserSupport">&nbsp;</div>
<div v-else>
Browser does not support Drag & Drop. Try Firefox 4+, Chrome 7+, IE 10+, Opera 12+ or Safari 6+.
</div>
</div>
<div v-else>
<div v-if="!isRunning">
You added {{ counterAnnounce }} file(s) to the queue. Add more files or click 'Start' to proceed.
</div>
<div v-else>Please wait...{{ counterAnnounce }} out of {{ counterRunning }} remaining...</div>
</div>
</div>
<UploadBox @add="addFiles">
<div v-show="showHelper" class="upload-helper">
<FontAwesomeIcon class="mr-1" :icon="faCopy" />
<span v-localize>Drop files here</span>
</div>
<div v-show="!showHelper">
<DefaultRow
v-for="[uploadIndex, uploadItem] in Object.entries(uploadItems).slice(0, lazyLoad)"
:key="uploadIndex"
:index="uploadIndex"
:db-key="uploadItem.dbKey"
:deferred="uploadItem.deferred"
:extension="uploadItem.extension"
:file-data="isLocalFile(uploadItem.fileData) ? uploadItem.fileData : undefined"
:file-content="uploadItem.fileContent"
:file-mode="uploadItem.fileMode"
:file-name="uploadItem.fileName"
:file-size="uploadItem.fileSize"
:info="uploadItem.info || undefined"
:list-extensions="!isCollection && listExtensions.length > 1 ? listExtensions : undefined"
:list-db-keys="!isCollection && listDbKeys.length > 1 ? listDbKeys : undefined"
:percentage="uploadItem.percentage"
:space-to-tab="uploadItem.spaceToTab"
:status="uploadItem.status"
:to-posix-lines="uploadItem.toPosixLines"
@remove="eventRemove"
@input="eventInput"
@explore="eventExplore" />
<div
v-if="uploadValues.length > lazyLoad"
v-localize
class="upload-text-message"
data-description="lazyload message">
Only showing first {{ lazyLoad }} of {{ uploadValues.length }} entries.
</div>
</div>
<label class="sr-only" for="upload-file">Uploaded File</label>
<input
id="upload-file"
ref="uploadFile"
type="file"
:multiple="multiple"
@change="addFileFromInput($event.target)" />
</UploadBox>
<div v-if="!disableFooter" class="upload-footer text-center">
<span v-if="isCollection" v-localize class="upload-footer-title">Collection:</span>
<UploadSelect
v-if="isCollection"
class="upload-footer-collection-type"
:value="collectionType"
:disabled="isRunning"
:options="COLLECTION_TYPES"
:searchable="false"
placeholder="Select Type"
@input="updateCollectionType" />
<span v-localize class="upload-footer-title">Type (set all):</span>
<UploadSelectExtension
class="upload-footer-extension"
:value="extension"
:disabled="isRunning"
:list-extensions="listExtensions"
@input="updateExtension">
</UploadSelectExtension>
<span v-localize class="upload-footer-title">Reference (set all):</span>
<UploadSelect
class="upload-footer-genome"
:value="dbKey"
:disabled="isRunning"
:options="listDbKeys"
what="reference"
placeholder="Select Reference"
@input="updateDbKey" />
</div>
<slot name="footer" />
<div
class="d-flex justify-content-between flex-wrap"
:class="{
'upload-buttons': !disableFooter,
'flex-gapx-1': disableFooter,
}">
<div class="d-flex">
<GButton
v-if="props.showUploadActivity ?? props.showBetaUpload"
id="btn-upload-activity"
size="small"
title="Use the upload activity"
@click="openUploadActivity">
<span v-localize>Upload activity</span>
</GButton>
</div>
<div class="d-flex justify-content-end flex-wrap">
<GButton id="btn-local" :size="size" :disabled="!enableSources" @click="uploadFile?.click()">
<FontAwesomeIcon :icon="faLaptop" />
<span v-localize>Choose local file</span>
</GButton>
<GButton
v-if="hasRemoteFiles"
id="btn-remote-files"
:size="size"
:disabled="!enableSources"
@click="eventRemoteFiles">
<FontAwesomeIcon :icon="faFolderOpen" />
<span v-localize>Choose from repository</span>
</GButton>
<GButton
id="btn-new"
:size="size"
title="Paste/Fetch data"
:disabled="!enableSources"
@click="eventCreate">
<FontAwesomeIcon :icon="faEdit" />
<span v-localize>Paste/Fetch data</span>
</GButton>
<GButton
id="btn-start"
:size="size"
:disabled="!enableStart"
title="Start"
:variant="enableStart ? 'primary' : null"
@click="eventStart">
<span v-localize>Start</span>
</GButton>
<GButton
v-if="isCollection && !collectionModalShow"
id="btn-build"
:size="size"
:disabled="!enableBuild"
:tooltip="!enableBuild && Boolean(historyItemsStateInfo?.message)"
:disabled-title="historyItemsStateInfo?.message || 'Build is not available'"
title="Build"
:color="historyItemsStateInfo?.color ? historyItemsStateInfo.color : undefined"
@click="() => eventBuild(true)">
<FontAwesomeIcon
v-if="historyItemsStateInfo?.icon"
:icon="historyItemsStateInfo.icon"
:spin="historyItemsStateInfo.spin" />
<span v-localize>Build</span>
</GButton>
<GButton
v-if="emitUploaded"
id="btn-emit"
:size="size"
:disabled="!enableBuild"
:tooltip="Boolean(historyItemsStateInfo?.message)"
:disabled-title="historyItemsStateInfo?.message || 'Upload Valid Files to Use'"
:title="historyItemsStateInfo?.message || 'Use Uploaded Files'"
:color="historyItemsStateInfo?.color ? historyItemsStateInfo.color : undefined"
@click="() => eventBuild(false)">
<FontAwesomeIcon
v-if="historyItemsStateInfo?.icon"
:icon="historyItemsStateInfo.icon"
:spin="historyItemsStateInfo.spin" />
<span v-localize>Use Uploaded</span>
<span v-if="uploadedHistoryItemsOk.length < counterSuccess">
({{ uploadedHistoryItemsOk.length }}/{{ counterSuccess }})
</span>
<span v-else> ({{ counterSuccess }}) </span>
</GButton>
<GButton id="btn-stop" :size="size" title="Pause" :disabled="!isRunning" @click="eventStop">
<span v-localize>Pause</span>
</GButton>
<GButton id="btn-reset" :size="size" title="Reset" :disabled="!enableReset" @click="eventReset">
<span v-localize>Reset</span>
</GButton>
<GButton id="btn-close" :size="size" title="Close" @click="$emit('dismiss')">
<span v-if="hasCallback" v-localize>Cancel</span>
<span v-else v-localize>Close</span>
</GButton>
</div>
</div>
<CollectionCreatorIndex
v-if="isCollection && historyId"
:history-id="historyId"
:collection-type="collectionType"
:extended-collection-type="{}"
:selected-items="selectedItemsForModal"
:show.sync="collectionModalShow"
default-hide-source-items
@on-hide="emit('dismiss')" />
</div>
</template>
-234
View File
@@ -1,234 +0,0 @@
<script setup lang="ts">
import {
faCheck,
faEdit,
faExclamation,
faExclamationTriangle,
faFolderOpen,
faLaptop,
faSpinner,
faTrash,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { useDebounceFn } from "@vueuse/core";
import { storeToRefs } from "pinia";
import { computed, onMounted, type Ref, ref } from "vue";
import type { DbKey, ExtensionDetails } from "@/composables/uploadConfigurations";
import { type ArchiveSource, isLocalZipFile, isRemoteZipFile } from "@/composables/zipExplorer";
import { useUserStore } from "@/stores/userStore";
import { bytesToString } from "@/utils/utils";
import type { UploadRowModel } from "./model";
import { isLocalFile } from "./model";
import GButton from "../BaseComponents/GButton.vue";
import UploadExtension from "./UploadExtension.vue";
import UploadSelect from "./UploadSelect.vue";
import UploadSettings from "./UploadSettings.vue";
const { isAnonymous } = storeToRefs(useUserStore());
const fileField: Ref<HTMLInputElement | null> = ref(null);
interface Props {
deferred?: boolean;
extension: string;
fileContent: string;
fileMode: string;
fileName: string;
fileSize: number;
fileData?: File;
dbKey: string;
index: string;
info?: string;
listDbKeys?: DbKey[];
listExtensions?: ExtensionDetails[];
percentage: number;
spaceToTab: boolean;
status: string;
toPosixLines: boolean;
}
const props = withDefaults(defineProps<Props>(), {
deferred: undefined,
info: "",
listDbKeys: undefined,
listExtensions: undefined,
fileData: undefined,
});
const emit = defineEmits<{
(e: "input", index: string, value: Partial<UploadRowModel>): void;
(e: "remove", index: string): void;
(e: "explore", archiveSource: ArchiveSource): void;
}>();
const isExplorable = ref(false);
const isDisabled = computed(() => props.status !== "init");
function inputExtension(newExtension: string) {
emit("input", props.index, { extension: newExtension });
}
async function inputFileContent(newFileContent: string) {
emit("input", props.index, { fileContent: newFileContent, fileSize: newFileContent.length });
isExplorable.value = await isRemoteExplorableArchiveDebounced(newFileContent);
}
function inputFileName(newFileName: string) {
emit("input", props.index, { fileName: newFileName });
}
function inputDbKey(newDbKey: string) {
emit("input", props.index, { dbKey: newDbKey });
}
function inputSettings(settingId: string) {
const newSettings: Record<string, any> = {};
newSettings[settingId] = !(props as any)[settingId];
emit("input", props.index, newSettings);
}
function removeUpload() {
if (["init", "success", "error"].indexOf(props.status) !== -1) {
emit("remove", props.index);
}
}
onMounted(() => {
autoSelectFileInput();
});
function autoSelectFileInput() {
fileField.value?.select();
}
const isRemoteExplorableArchiveDebounced = useDebounceFn(async (url: string) => {
return isRemoteZipFile(url);
}, 1000);
function initializeExplorableArchive() {
if (props.fileMode === "local" && isLocalFile(props.fileData)) {
isExplorable.value = isLocalZipFile(props.fileData);
} else if (props.fileMode === "new" && props.fileContent) {
isRemoteZipFile(props.fileContent).then((result) => {
isExplorable.value = result;
});
} else {
// Remote File Source URIs are not explorable because they don't support byte range requests
isExplorable.value = false;
}
}
function exploreZipContents() {
if (props.fileMode === "local" && props.fileData) {
emit("explore", props.fileData);
} else if (props.fileMode === "new" && props.fileContent) {
emit("explore", props.fileContent);
}
}
initializeExplorableArchive();
</script>
<template>
<div :id="`upload-row-${index}`" class="upload-row rounded my-1 p-2" :class="`upload-${status}`">
<div class="d-flex justify-content-around align-items-center">
<div>
<FontAwesomeIcon v-if="fileMode == 'new'" :icon="faEdit" fixed-width />
<FontAwesomeIcon v-if="fileMode == 'local'" :icon="faLaptop" fixed-width />
<FontAwesomeIcon v-if="fileMode == 'url'" :icon="faFolderOpen" fixed-width />
</div>
<b-input
ref="fileField"
:value="fileName"
class="upload-title p-1 border rounded"
:disabled="isDisabled"
@input="inputFileName" />
<div class="upload-size">
{{ bytesToString(fileSize) }}
</div>
<UploadSelect
v-if="listExtensions"
class="upload-extension"
:value="extension"
:disabled="isDisabled"
:options="listExtensions"
placeholder="Select Type"
what="file type"
@input="inputExtension" />
<UploadExtension v-if="listExtensions" :extension="extension" :list-extensions="listExtensions" />
<UploadSelect
v-if="listDbKeys"
class="upload-genome"
:value="dbKey"
:disabled="isDisabled"
:options="listDbKeys"
placeholder="Select Reference"
what="reference"
@input="inputDbKey" />
<UploadSettings
class="upload-settings"
:deferred="deferred"
:disabled="isDisabled"
:to-posix-lines="toPosixLines"
:space-to-tab="spaceToTab"
@input="inputSettings" />
<div class="upload-progress">
<div class="progress">
<div
class="upload-progress-bar progress-bar progress-bar-success"
:style="{ width: `${percentage}%` }" />
<div class="upload-percentage">{{ percentage }}%</div>
</div>
</div>
<div>
<FontAwesomeIcon v-if="['running', 'queued'].includes(status)" :icon="faSpinner" spin />
<FontAwesomeIcon
v-else-if="status === 'error'"
class="cursor-pointer"
:icon="faExclamationTriangle"
fixed-width
@click="removeUpload" />
<FontAwesomeIcon
v-else-if="status === 'init'"
class="cursor-pointer"
:icon="faTrash"
fixed-width
@click="removeUpload" />
<FontAwesomeIcon
v-else-if="status === 'success'"
class="cursor-pointer"
:icon="faCheck"
fixed-width
@click="removeUpload" />
<FontAwesomeIcon v-else :icon="faExclamation" />
</div>
<GButton
v-if="isExplorable"
class="btn-explore-archive"
size="small"
title="Explore the contents of a remote or local compressed archive and upload individual files"
:disabled="isAnonymous"
disabled-title="You must be logged in to use this feature"
@click="exploreZipContents">
<span v-localize>Explore</span>
</GButton>
</div>
<div v-if="info" v-localize class="upload-text-message font-weight-bold">
{{ info }}
</div>
<div v-if="fileMode == 'new'">
<div v-localize class="upload-text-message">
Download data from the web by entering URLs (one per line) or directly paste content.
</div>
<b-textarea
:value="fileContent"
class="upload-text-content form-control"
:disabled="isDisabled"
@input="inputFileContent" />
</div>
</div>
</template>
@@ -1,33 +0,0 @@
import { getLocalVue } from "@tests/vitest/helpers";
import { mount } from "@vue/test-utils";
import { describe, expect, it } from "vitest";
import mountTarget from "./RulesInput.vue";
const localVue = getLocalVue();
function getWrapper() {
return mount(mountTarget, {
propsData: {
fileSourcesConfigured: true,
ftpUploadSite: null,
historyId: "historyId",
},
localVue,
});
}
describe("RulesInput", () => {
it("rendering and reset", async () => {
const wrapper = getWrapper();
expect(wrapper.find("#btn-reset").classes()).toEqual(expect.arrayContaining(["disabled"]));
const textInput = wrapper.find(".upload-rule-source-content");
expect(textInput.element.value).toBe("");
await textInput.setValue("a b c d");
expect(textInput.element.value).toBe("a b c d");
expect(wrapper.find("#btn-reset").classes()).not.toEqual(expect.arrayContaining(["disabled"]));
await wrapper.find("#btn-reset").trigger("click");
expect(textInput.element.value).toBe("");
expect(wrapper.find("#btn-reset").classes()).toEqual(expect.arrayContaining(["disabled"]));
});
});
-181
View File
@@ -1,181 +0,0 @@
<script setup>
import { faEdit, faFile, faFolderOpen, faLock } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { BAlert, BButton } from "bootstrap-vue";
import { computed, ref } from "vue";
import { getGalaxyInstance } from "@/app";
import { buildCollectionFromRules } from "@/components/Collections/common/buildCollectionModal";
import { getRemoteEntries, getRemoteEntriesAt } from "@/components/Upload/utils";
import { filesDialog } from "@/utils/dataModals";
import { urlData } from "@/utils/url";
import { RULES_TYPES } from "./utils.js";
import UploadSelect from "./UploadSelect.vue";
const props = defineProps({
hasCallback: {
type: Boolean,
default: false,
},
fileSourcesConfigured: {
type: Boolean,
required: true,
},
ftpUploadSite: {
type: String,
default: null,
},
historyId: {
type: String,
required: true,
},
});
const emit = defineEmits(["dismiss"]);
const dataType = ref("datasets");
const errorMessage = ref(null);
const ftpFiles = ref([]);
const selectedDatasetId = ref(null);
const selectionType = ref("raw");
const sourceContent = ref(null);
const uris = ref([]);
const isDisabled = computed(() => selectionType.value !== "raw");
function eventBuild() {
const entry = {
dataType: dataType.value,
selectionType: selectionType.value,
};
if (entry.selectionType == "ftp") {
entry.elements = ftpFiles.value;
entry.ftpUploadSite = props.ftpUploadSite;
} else if (entry.selectionType === "raw") {
entry.content = sourceContent.value;
} else if (entry.selectionType == "remote_files") {
entry.elements = uris.value;
}
buildCollectionFromRules(entry, null, true);
emit("dismiss");
}
function eventReset() {
selectedDatasetId.value = null;
selectionType.value = "raw";
sourceContent.value = null;
}
function inputDialog() {
const Galaxy = getGalaxyInstance();
Galaxy.data.dialog(
(response) => {
selectedDatasetId.value = response.id;
urlData({ url: `/api/histories/${props.historyId}/contents/${selectedDatasetId.value}/display` })
.then((newSourceContent) => {
selectionType.value = "raw";
sourceContent.value = newSourceContent;
})
.catch((error) => {
errorMessage.value = error;
});
},
{
multiple: false,
library: false,
format: null,
allowUpload: false,
},
);
}
function inputFtp() {
getRemoteEntries((ftp_files) => {
selectionType.value = "ftp";
sourceContent.value = ftp_files.map((file) => file["path"]).join("\n");
ftpFiles.value = ftp_files;
});
}
function inputPaste() {
selectionType.value = "raw";
selectedDatasetId.value = null;
sourceContent.value = null;
}
function inputRemote() {
function handleRemoteFilesUri(record) {
getRemoteEntriesAt(record.url).then((files) => {
files = files.filter((file) => file["class"] == "File");
selectionType.value = "remote_files";
sourceContent.value = files.map((file) => file["uri"]).join("\n");
uris.value = files;
});
}
filesDialog(handleRemoteFilesUri, { mode: "directory" });
}
</script>
<template>
<div class="upload-wrapper d-flex flex-column">
<BAlert v-if="errorMessage" variant="danger" show>{{ errorMessage }}</BAlert>
<div v-localize class="upload-header">Insert tabular source data to extract collection files and metadata.</div>
<textarea
v-model="sourceContent"
class="upload-box upload-rule-source-content"
:placeholder="localize('Insert tabular source data here.')"
:disabled="isDisabled" />
<FontAwesomeIcon v-if="isDisabled" class="upload-text-lock" :icon="faLock" />
<div class="upload-footer text-center">
<span v-localize class="upload-footer-title">Upload type:</span>
<UploadSelect v-model="dataType" class="rule-data-type" :options="RULES_TYPES" :searchable="false" />
</div>
<div class="upload-buttons d-flex justify-content-end">
<BButton @click="inputPaste">
<FontAwesomeIcon :icon="faEdit" />
<span v-localize>Paste data</span>
</BButton>
<BButton data-description="rules dataset dialog" @click="inputDialog">
<FontAwesomeIcon :icon="faFile" />
<span v-localize>Choose dataset</span>
</BButton>
<BButton v-if="ftpUploadSite" @click="inputFtp">
<FontAwesomeIcon :icon="faFolderOpen" />
<span v-localize>Import FTP files</span>
</BButton>
<BButton @click="inputRemote">
<FontAwesomeIcon :icon="faFolderOpen" />
<span v-localize>Choose from repository</span>
</BButton>
<BButton
id="btn-build"
:disabled="!sourceContent"
title="Build"
:variant="sourceContent ? 'primary' : ''"
@click="eventBuild">
<span v-localize>Build</span>
</BButton>
<BButton id="btn-reset" title="Reset" :disabled="!sourceContent" @click="eventReset">
<span v-localize>Reset</span>
</BButton>
<BButton id="btn-close" title="Close" @click="$emit('dismiss')">
<span v-localize>Close</span>
</BButton>
</div>
</div>
</template>
<style scoped>
.upload-rule-source-content {
resize: none;
}
.upload-text-lock {
bottom: 22%;
font-size: 1.275rem;
opacity: 0.2;
right: 3%;
position: absolute;
}
</style>
@@ -1,32 +0,0 @@
<script setup>
import { ref } from "vue";
const isDragging = ref(false);
const emit = defineEmits(["add"]);
/** Handle files dropped into the upload box **/
function onDrop(evt) {
isDragging.value = false;
if (evt.dataTransfer) {
emit("add", evt.dataTransfer.files);
}
}
</script>
<template>
<div
class="upload-box"
:class="{ highlight: isDragging }"
@dragover.prevent="isDragging = true"
@dragleave.prevent="isDragging = false"
@drop.prevent="onDrop">
<slot />
</div>
</template>
<style scoped>
.upload {
height: 300px;
}
</style>
@@ -1,30 +0,0 @@
<script setup>
import { faSquare } from "@fortawesome/free-regular-svg-icons";
import { faCheckSquare } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
defineProps({
title: {
type: String,
required: true,
},
value: {
type: Boolean,
required: true,
},
});
const emit = defineEmits("click");
</script>
<template>
<tr @click="emit('click')">
<td>
<FontAwesomeIcon v-if="value" class="px-2" :icon="faCheckSquare" fa-fw />
<FontAwesomeIcon v-else class="px-2" :icon="faSquare" fa-fw />
</td>
<td class="text-left">
<span v-localize class="pr-2">{{ title }}</span>
</td>
</tr>
</template>
@@ -1,84 +0,0 @@
<script setup>
import { faCog } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import UploadOption from "./UploadOption.vue";
import Popper from "@/components/Popper/Popper.vue";
defineProps({
deferred: {
type: Boolean,
default: undefined,
},
disabled: {
type: Boolean,
default: false,
},
spaceToTab: {
type: Boolean,
default: null,
},
toPosixLines: {
type: Boolean,
default: null,
},
});
const emit = defineEmits(["input"]);
</script>
<template>
<Popper placement="bottom" title="Upload Configuration" mode="primary-title" trigger="click">
<template v-slot:reference>
<FontAwesomeIcon class="cursor-pointer" :icon="faCog" />
</template>
<div class="upload-settings-content px-2 py-2 no-highlight">
<table class="upload-settings-table grid">
<tbody>
<UploadOption
class="upload-space-to-tab"
title="Convert spaces to tabs"
:value="spaceToTab"
@click="emit('input', 'spaceToTab')" />
<UploadOption
class="upload-to-posix-lines"
title="Use POSIX standard"
:value="toPosixLines"
@click="emit('input', 'toPosixLines')" />
<UploadOption
v-if="deferred !== undefined"
class="upload-deferred"
title="Defer dataset resolution"
:value="deferred"
@click="emit('input', 'deferred')" />
</tbody>
</table>
<div v-if="disabled" class="upload-settings-cover" />
</div>
</Popper>
</template>
<style lang="scss">
@import "@/style/scss/theme/blue.scss";
.upload-settings-content {
position: relative;
.upload-settings-cover {
background: $white;
cursor: no-drop;
height: 100%;
left: 0;
opacity: 0.25;
position: absolute;
top: 0;
width: 100%;
}
.upload-settings-table {
tr {
cursor: pointer;
}
tr:hover {
background-color: lighten($brand-success, 20%);
}
}
}
</style>