mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-10-24 12:13:33 +08:00
Merge pull request #14905 from ElectronicBlueberry/dnd-upload
Add global drag and drop upload
This commit is contained in:
@@ -20,7 +20,6 @@ export { SweepsterVisualization, SweepsterVisualizationView } from "viz/sweepste
|
||||
export { createTabularDatasetChunkedView } from "mvc/dataset/data";
|
||||
export { default as LegacyGridView } from "legacy/grid/grid-view";
|
||||
export { create_chart, create_histogram } from "reports/run_stats";
|
||||
export { openGlobalUploadModal } from "components/Upload";
|
||||
export { runTour } from "components/Tour/runTour";
|
||||
export { Toast } from "ui/toast"; // TODO: remove when external consumers are updated/gone (IES right now)
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ import { UrlTracker } from "./utilities";
|
||||
import { Model } from "./model";
|
||||
import { Services } from "./services";
|
||||
import { getAppRoot } from "onload/loadConfig";
|
||||
import { mountUploadModal } from "components/Upload";
|
||||
import { useGlobalUploadModal } from "composables/globalUploadModal";
|
||||
|
||||
Vue.use(BootstrapVue);
|
||||
|
||||
@@ -78,6 +78,10 @@ export default {
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const { openGlobalUploadModal } = useGlobalUploadModal();
|
||||
return { openGlobalUploadModal };
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
errorMessage: null,
|
||||
@@ -133,7 +137,7 @@ export default {
|
||||
modalShow: true,
|
||||
selectable: true,
|
||||
};
|
||||
mountUploadModal(propsData);
|
||||
this.openGlobalUploadModal(propsData);
|
||||
this.modalShow = false;
|
||||
},
|
||||
/** Called when selection is complete, values are formatted and parsed to external callback **/
|
||||
|
||||
@@ -13,14 +13,17 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { openGlobalUploadModal } from "components/Upload";
|
||||
import { useGlobalUploadModal } from "composables/globalUploadModal";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
message: { type: String, default: "This history is empty." },
|
||||
},
|
||||
setup() {
|
||||
const { openGlobalUploadModal } = useGlobalUploadModal();
|
||||
return { openGlobalUploadModal };
|
||||
},
|
||||
methods: {
|
||||
openGlobalUploadModal,
|
||||
clickDataLink() {
|
||||
this.eventHub.$emit("openToolSection", "getext");
|
||||
},
|
||||
|
||||
@@ -70,7 +70,8 @@
|
||||
<script>
|
||||
import ToolSection from "./Common/ToolSection";
|
||||
import ToolSearch from "./Common/ToolSearch";
|
||||
import { UploadButton, openGlobalUploadModal } from "components/Upload";
|
||||
import { UploadButton } from "components/Upload";
|
||||
import { useGlobalUploadModal } from "composables/globalUploadModal";
|
||||
import FavoritesButton from "./Buttons/FavoritesButton";
|
||||
import PanelViewButton from "./Buttons/PanelViewButton";
|
||||
import { filterToolSections, filterTools, hasResults } from "./utilities";
|
||||
@@ -106,6 +107,10 @@ export default {
|
||||
default: _l("Workflows"),
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const { openGlobalUploadModal } = useGlobalUploadModal();
|
||||
return { openGlobalUploadModal };
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
query: null,
|
||||
@@ -171,7 +176,7 @@ export default {
|
||||
onOpen(tool, evt) {
|
||||
if (tool.id === "upload1") {
|
||||
evt.preventDefault();
|
||||
openGlobalUploadModal();
|
||||
this.openGlobalUploadModal();
|
||||
} else if (tool.form_style === "regular") {
|
||||
evt.preventDefault();
|
||||
// encode spaces in tool.id
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<script>
|
||||
import Vue from "vue";
|
||||
import infiniteScroll from "vue-infinite-scroll";
|
||||
import { openGlobalUploadModal } from "components/Upload";
|
||||
import { useGlobalUploadModal } from "composables/globalUploadModal";
|
||||
import { fetchData } from "./services";
|
||||
import ToolsListItem from "./ToolsListItem";
|
||||
|
||||
@@ -44,6 +44,10 @@ export default {
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const { openGlobalUploadModal } = useGlobalUploadModal();
|
||||
return { openGlobalUploadModal };
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
allLoaded: false,
|
||||
@@ -62,7 +66,7 @@ export default {
|
||||
methods: {
|
||||
onOpen(tool) {
|
||||
if (tool.id === "upload1") {
|
||||
openGlobalUploadModal();
|
||||
this.openGlobalUploadModal();
|
||||
} else if (tool.form_style === "regular") {
|
||||
// encode spaces in tool.id
|
||||
const toolId = tool.id;
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
<script setup>
|
||||
import { ref, computed } from "vue";
|
||||
import { useFileDrop } from "composables/fileDrop";
|
||||
import { useGlobalUploadModal } from "composables/globalUploadModal";
|
||||
|
||||
const modalContentElement = ref(null);
|
||||
const { isFileOverDocument, isFileOverDropZone } = useFileDrop(modalContentElement, onDrop, true);
|
||||
|
||||
const modalClass = computed(() => {
|
||||
if (isFileOverDropZone.value) {
|
||||
return "ui-drag-and-drop-modal drag-over";
|
||||
} else {
|
||||
return "ui-drag-and-drop-modal";
|
||||
}
|
||||
});
|
||||
|
||||
const { openGlobalUploadModal } = useGlobalUploadModal();
|
||||
|
||||
function onDrop(event) {
|
||||
console.debug(event.dataTransfer);
|
||||
|
||||
if (event.dataTransfer?.files?.length > 0) {
|
||||
openGlobalUploadModal({
|
||||
immediateUpload: true,
|
||||
immediateFiles: event.dataTransfer.files,
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<b-modal v-model="isFileOverDocument" :modal-class="modalClass" hide-header hide-footer centered>
|
||||
<div ref="modalContentElement" class="inner-content h-xl">Drop Files here to Upload</div>
|
||||
</b-modal>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import "theme/blue.scss";
|
||||
|
||||
.ui-drag-and-drop-modal {
|
||||
.modal-content {
|
||||
background-color: transparent;
|
||||
border-radius: 16px;
|
||||
border: 6px dashed;
|
||||
border-color: $brand-secondary;
|
||||
min-height: 40vh;
|
||||
|
||||
.modal-body {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.inner-content {
|
||||
flex: 1 1 auto;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: $brand-secondary;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
&.drag-over {
|
||||
.modal-content {
|
||||
border-color: lighten($brand-info, 30%);
|
||||
|
||||
.inner-content {
|
||||
color: lighten($brand-info, 30%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -289,6 +289,9 @@ export default {
|
||||
_eventCreate: function () {
|
||||
this.uploadbox.add([{ name: defaultNewFileName, size: 0, mode: "new" }]);
|
||||
},
|
||||
addFiles: function (files) {
|
||||
this.uploadbox.add(files);
|
||||
},
|
||||
/** Pause upload process */
|
||||
_eventStop: function () {
|
||||
if (this.counterRunning > 0) {
|
||||
|
||||
@@ -28,7 +28,8 @@ import { VBTooltip } from "bootstrap-vue";
|
||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||
import { library } from "@fortawesome/fontawesome-svg-core";
|
||||
import { faUpload } from "@fortawesome/free-solid-svg-icons";
|
||||
import { openGlobalUploadModal } from "./mount";
|
||||
import { useGlobalUploadModal } from "composables/globalUploadModal";
|
||||
|
||||
library.add(faUpload);
|
||||
|
||||
export default {
|
||||
@@ -39,6 +40,10 @@ export default {
|
||||
props: {
|
||||
title: { type: String, default: "Download from URL or upload files from disk" },
|
||||
},
|
||||
setup() {
|
||||
const { openGlobalUploadModal } = useGlobalUploadModal();
|
||||
return { openGlobalUploadModal };
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
status: "",
|
||||
@@ -58,7 +63,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
showUploadDialog() {
|
||||
openGlobalUploadModal();
|
||||
this.openGlobalUploadModal();
|
||||
},
|
||||
setStatus(val) {
|
||||
this.status = val;
|
||||
|
||||
@@ -2,12 +2,12 @@ import MockAdapter from "axios-mock-adapter";
|
||||
import axios from "axios";
|
||||
import UploadModal from "./UploadModal";
|
||||
import UploadModalContent from "./UploadModalContent";
|
||||
import store from "../../store";
|
||||
import { mount } from "@vue/test-utils";
|
||||
import { getLocalVue } from "tests/jest/helpers";
|
||||
|
||||
import MockCurrentUser from "../providers/MockCurrentUser";
|
||||
import MockCurrentHistory from "../providers/MockCurrentHistory";
|
||||
import { getLocalVue, mockModule } from "tests/jest/helpers";
|
||||
import { userStore } from "store/userStore";
|
||||
import { historyStore } from "store/historyStore";
|
||||
import { configStore } from "store/configStore";
|
||||
import Vuex from "vuex";
|
||||
|
||||
jest.mock("app");
|
||||
|
||||
@@ -17,6 +17,16 @@ const propsData = {
|
||||
fileSourcesConfigured: true,
|
||||
};
|
||||
|
||||
const createStore = () => {
|
||||
return new Vuex.Store({
|
||||
modules: {
|
||||
user: mockModule(userStore, { currentUser: { id: "fakeuser" } }),
|
||||
history: mockModule(historyStore, { currentHistoryId: "fakehistory", histories: { fakehistory: {} } }),
|
||||
config: mockModule(configStore, { config: {} }),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
describe("UploadModal.vue", () => {
|
||||
let wrapper;
|
||||
let axiosMock;
|
||||
@@ -47,17 +57,14 @@ describe("UploadModal.vue", () => {
|
||||
axiosMock.onGet(`/api/datatypes?extension_only=False`).reply(200, datatypesResponse);
|
||||
|
||||
const localVue = getLocalVue();
|
||||
const store = createStore();
|
||||
|
||||
wrapper = await mount(UploadModal, {
|
||||
store,
|
||||
provide: { store },
|
||||
propsData,
|
||||
localVue,
|
||||
stubs: {
|
||||
// Need to stub all this horrible-ness because of the last 2 tests
|
||||
// which need to dig into the first layer of the mount tree, will remove
|
||||
// all of this shortly with a PR that completely replaces Upload
|
||||
CurrentUser: MockCurrentUser({ id: "fakeuser" }),
|
||||
UserHistories: MockCurrentHistory({ id: "fakehistory" }),
|
||||
BTabs: true,
|
||||
BTab: true,
|
||||
Collection: true,
|
||||
@@ -66,6 +73,8 @@ describe("UploadModal.vue", () => {
|
||||
RulesInput: true,
|
||||
},
|
||||
});
|
||||
|
||||
await wrapper.vm.open();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -74,8 +83,9 @@ describe("UploadModal.vue", () => {
|
||||
});
|
||||
|
||||
it("should load with correct defaults", async () => {
|
||||
expect(wrapper.vm.auto.id).toBe("auto");
|
||||
expect(wrapper.vm.datatypesDisableAuto).toBe(false);
|
||||
const contentWrapper = wrapper.findComponent(UploadModalContent);
|
||||
expect(contentWrapper.vm.auto.id).toBe("auto");
|
||||
expect(contentWrapper.vm.datatypesDisableAuto).toBe(false);
|
||||
});
|
||||
|
||||
it("should fetch datatypes and parse them", async () => {
|
||||
|
||||
@@ -1,87 +1,118 @@
|
||||
<!--
|
||||
Temporary modal wrapper until I replace the entire UploadModal which desperately needs a rewrite.
|
||||
Provides user and current history to modal because it currently has initialization sequence issues
|
||||
-->
|
||||
<script setup>
|
||||
import UploadModalContent from "./UploadModalContent";
|
||||
|
||||
import { ref, watch } from "vue";
|
||||
import { getAppRoot } from "onload";
|
||||
|
||||
import { useCurrentUser } from "composables/user";
|
||||
import { useUserHistories } from "composables/userHistories";
|
||||
import { useConfig } from "composables/config";
|
||||
|
||||
const { currentUser } = useCurrentUser();
|
||||
const { currentHistoryId } = useUserHistories(currentUser);
|
||||
|
||||
const { config, isLoaded } = useConfig();
|
||||
|
||||
function getDefaultOptions() {
|
||||
const baseOptions = {
|
||||
title: "Upload from Disk or Web",
|
||||
modalStatic: true,
|
||||
callback: null,
|
||||
multiple: true,
|
||||
selectable: false,
|
||||
uploadPath: "",
|
||||
immediateUpload: false,
|
||||
immediateFiles: null,
|
||||
};
|
||||
|
||||
const configOptions = isLoaded.value
|
||||
? {
|
||||
uploadPath: config.value.nginx_upload_path ?? `${getAppRoot()}api/tools`,
|
||||
chunkUploadSize: config.value.chunk_upload_size,
|
||||
fileSourcesConfigured: config.value.file_sources_configured,
|
||||
ftpUploadSite: config.value.ftp_upload_site,
|
||||
defaultDbKey: config.value.default_genome,
|
||||
defaultExtension: config.value.default_extension,
|
||||
}
|
||||
: {};
|
||||
|
||||
return { ...baseOptions, ...configOptions };
|
||||
}
|
||||
|
||||
const options = ref(getDefaultOptions());
|
||||
const showModal = ref(false);
|
||||
const content = ref(null);
|
||||
|
||||
function dismiss(result) {
|
||||
if (result && options.value.callback) {
|
||||
options.value.callback(result);
|
||||
}
|
||||
|
||||
showModal.value = false;
|
||||
}
|
||||
|
||||
function wait(milliseconds) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => resolve(), milliseconds);
|
||||
});
|
||||
}
|
||||
|
||||
async function open(overrideOptions) {
|
||||
const newOptions = overrideOptions ?? {};
|
||||
|
||||
options.value = { ...getDefaultOptions(), ...newOptions };
|
||||
|
||||
if (options.value.callback) {
|
||||
options.value.hasCallback = true;
|
||||
}
|
||||
|
||||
showModal.value = true;
|
||||
await wait(100);
|
||||
|
||||
if (options.value.immediateUpload) {
|
||||
content.value.immediateUpload(options.value.immediateFiles);
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => showModal.value,
|
||||
(modalShown) => setIframeEvents(modalShown)
|
||||
);
|
||||
|
||||
function setIframeEvents(disableEvents) {
|
||||
const element = document.getElementById("galaxy_main");
|
||||
|
||||
if (element) {
|
||||
element.style["pointer-events"] = disableEvents ? "none" : "auto";
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
open,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CurrentUser v-slot="{ user }">
|
||||
<UserHistories v-if="user" v-slot="{ currentHistoryId }" :user="user">
|
||||
<b-modal
|
||||
v-model="modalShow"
|
||||
:static="modalStatic"
|
||||
header-class="no-separator"
|
||||
modal-class="ui-modal"
|
||||
dialog-class="upload-dialog"
|
||||
body-class="upload-dialog-body"
|
||||
no-enforce-focus
|
||||
hide-footer>
|
||||
<template v-slot:modal-header>
|
||||
<h2 class="title h-sm" tabindex="0">{{ title | localize }}</h2>
|
||||
</template>
|
||||
<b-modal
|
||||
v-model="showModal"
|
||||
:static="options.modalStatic"
|
||||
header-class="no-separator"
|
||||
modal-class="ui-modal"
|
||||
dialog-class="upload-dialog"
|
||||
body-class="upload-dialog-body"
|
||||
no-enforce-focus
|
||||
hide-footer>
|
||||
<template v-slot:modal-header>
|
||||
<h2 class="title h-sm" tabindex="0">{{ options.title }}</h2>
|
||||
</template>
|
||||
|
||||
<UploadModalContent
|
||||
v-if="currentHistoryId"
|
||||
:current-user-id="user.id"
|
||||
:current-history-id="currentHistoryId"
|
||||
v-bind="{ ...$props, ...$attrs }"
|
||||
@dismiss="dismiss" />
|
||||
</b-modal>
|
||||
</UserHistories>
|
||||
</CurrentUser>
|
||||
<UploadModalContent
|
||||
v-if="currentHistoryId"
|
||||
ref="content"
|
||||
:key="showModal"
|
||||
:currentUserId="currentUser.id"
|
||||
:currentHistoryId="currentHistoryId"
|
||||
v-bind="options"
|
||||
@dismiss="dismiss" />
|
||||
</b-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CurrentUser from "../providers/CurrentUser";
|
||||
import UserHistories from "components/providers/UserHistories";
|
||||
import UploadModalContent from "./UploadModalContent";
|
||||
import { commonProps } from "./helpers";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CurrentUser,
|
||||
UserHistories,
|
||||
UploadModalContent,
|
||||
},
|
||||
props: {
|
||||
title: { type: String, default: "Download from web or upload from disk" },
|
||||
modalStatic: { type: Boolean, default: true },
|
||||
...commonProps,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
modalShow: false,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
modalShow() {
|
||||
this.setIframeEvents(this.modalShow);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.show();
|
||||
// handles subsequent external requests to re-open a re-used modal
|
||||
this.$root.$on("openUpload", this.show);
|
||||
},
|
||||
methods: {
|
||||
show() {
|
||||
this.modalShow = true;
|
||||
},
|
||||
hide() {
|
||||
this.modalShow = false;
|
||||
},
|
||||
dismiss(result) {
|
||||
if (undefined !== result) {
|
||||
this.$root.$emit("uploadResult", result);
|
||||
}
|
||||
this.hide();
|
||||
},
|
||||
/** Disable mouse events in iframe to prevent interference with uploader drop box */
|
||||
setIframeEvents(disableEvents) {
|
||||
const element = document.getElementById("galaxy_main");
|
||||
if (element) {
|
||||
element.style["pointer-events"] = disableEvents ? "none" : "auto";
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<b-tabs v-if="ready">
|
||||
<b-tab v-if="showRegular" id="regular" title="Regular" button-id="tab-title-link-regular">
|
||||
<default
|
||||
ref="regular"
|
||||
:app="this"
|
||||
:lazy-load-max="50"
|
||||
:multiple="multiple"
|
||||
@@ -35,7 +36,6 @@ import RulesInput from "./RulesInput";
|
||||
import LoadingSpan from "components/LoadingSpan";
|
||||
import { uploadModelsToPayload } from "./helpers";
|
||||
import { BTabs, BTab } from "bootstrap-vue";
|
||||
import { commonProps } from "./helpers";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -50,7 +50,29 @@ export default {
|
||||
props: {
|
||||
currentHistoryId: { type: String, required: true },
|
||||
currentUserId: { type: String, default: "" },
|
||||
...commonProps,
|
||||
uploadPath: { type: String, required: true },
|
||||
chunkUploadSize: { type: Number, default: 1024 },
|
||||
fileSourcesConfigured: { type: Boolean, default: false },
|
||||
ftpUploadSite: { type: String, default: "" },
|
||||
defaultDbKey: { type: String, default: UploadUtils.DEFAULT_DBKEY },
|
||||
defaultExtension: { type: String, default: UploadUtils.DEFAULT_EXTENSION },
|
||||
datatypesDisableAuto: { type: Boolean, default: false },
|
||||
formats: { type: Array, default: null },
|
||||
multiple: {
|
||||
// Restrict the forms to a single dataset upload if false
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
hasCallback: {
|
||||
// Return uploads when done if supplied.
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
selectable: { type: Boolean, default: false },
|
||||
auto: {
|
||||
type: Object,
|
||||
default: () => UploadUtils.AUTO_EXTENSION,
|
||||
},
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
@@ -180,6 +202,10 @@ export default {
|
||||
toData: function (items, history_id, composite = false) {
|
||||
return uploadModelsToPayload(items, history_id, composite);
|
||||
},
|
||||
immediateUpload: function (files) {
|
||||
this.$refs.regular?.addFiles(files);
|
||||
this.$refs.regular?._eventStart();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import { getGalaxyInstance } from "app";
|
||||
import { getAppRoot } from "onload";
|
||||
|
||||
export function initializeUploadDefaults(propsData = {}) {
|
||||
const Galaxy = getGalaxyInstance();
|
||||
const appRoot = getAppRoot();
|
||||
const defaults = {
|
||||
multiple: true,
|
||||
uploadPath: Galaxy.config.nginx_upload_path || `${appRoot}api/tools`,
|
||||
chunkUploadSize: Galaxy.config.chunk_upload_size,
|
||||
fileSourcesConfigured: Galaxy.config.file_sources_configured,
|
||||
ftpUploadSite: Galaxy.config.ftp_upload_site,
|
||||
defaultDbKey: Galaxy.config.default_genome,
|
||||
defaultExtension: Galaxy.config.default_extension,
|
||||
selectable: false,
|
||||
};
|
||||
return Object.assign({}, defaults, propsData);
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
import UploadUtils from "mvc/upload/upload-utils";
|
||||
export const defaultNewFileName = "New File";
|
||||
|
||||
const URI_PREFIXES = ["http", "https", "ftp", "file", "gxfiles", "gximport", "gxuserimport", "gxftp"];
|
||||
@@ -109,58 +108,3 @@ export function uploadModelsToPayload(items, history_id, composite = false) {
|
||||
files: files,
|
||||
};
|
||||
}
|
||||
|
||||
export const commonProps = {
|
||||
uploadPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
chunkUploadSize: {
|
||||
type: Number,
|
||||
default: 1024,
|
||||
},
|
||||
fileSourcesConfigured: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
ftpUploadSite: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
defaultDbKey: {
|
||||
type: String,
|
||||
default: UploadUtils.DEFAULT_DBKEY,
|
||||
},
|
||||
defaultExtension: {
|
||||
type: String,
|
||||
default: UploadUtils.DEFAULT_EXTENSION,
|
||||
},
|
||||
datatypesDisableAuto: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
formats: {
|
||||
type: Array,
|
||||
default: null,
|
||||
},
|
||||
multiple: {
|
||||
// Restrict the forms to a single dataset upload if false
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
hasCallback: {
|
||||
// Return uploads when done if supplied.
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
selectable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
auto: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return UploadUtils.AUTO_EXTENSION;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -2,6 +2,4 @@
|
||||
* External entry point for Upload components (currently only the modal).
|
||||
*/
|
||||
export { default as UploadModal } from "./UploadModal";
|
||||
export { initializeUploadDefaults } from "./config";
|
||||
export { openGlobalUploadModal, mountUploadModal } from "./mount";
|
||||
export { default as UploadButton } from "./UploadButton";
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
import UploadModal from "./UploadModal";
|
||||
import { initializeUploadDefaults } from "./config";
|
||||
import { mountVueComponent } from "utils/mountVueComponent";
|
||||
|
||||
export function mountUploadModal(options = {}) {
|
||||
const props = initializeUploadDefaults(options);
|
||||
|
||||
// should use events insted of passing in functions
|
||||
const { callback, ...propsData } = props;
|
||||
if (callback) {
|
||||
// internal display characteristic
|
||||
propsData.hasCallback = true;
|
||||
}
|
||||
|
||||
const mounter = mountVueComponent(UploadModal);
|
||||
const container = document.createElement("div");
|
||||
document.body.appendChild(container);
|
||||
const uploadVm = mounter(propsData, container);
|
||||
|
||||
if (callback) {
|
||||
uploadVm.$once("uploadResult", callback);
|
||||
}
|
||||
|
||||
return uploadVm;
|
||||
}
|
||||
|
||||
// Global upload dialog instance
|
||||
let uploadVm = null;
|
||||
|
||||
export function openGlobalUploadModal(options) {
|
||||
if (!uploadVm) {
|
||||
uploadVm = mountUploadModal(options);
|
||||
}
|
||||
// re-open
|
||||
uploadVm.$emit("openUpload", uploadVm);
|
||||
return uploadVm;
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
import { ref, unref } from "vue";
|
||||
import { useEventListener } from "@vueuse/core";
|
||||
|
||||
/**
|
||||
* Custom File-Drop composable
|
||||
* @param dropZone Element which files should be dropped on
|
||||
* @param onDrop callback function called when drop occurs
|
||||
* @param solo when true, only reacts if no modal is open
|
||||
*/
|
||||
export function useFileDrop(dropZone, onDrop, solo) {
|
||||
const isFileOverDocument = ref(false);
|
||||
const isFileOverDropZone = ref(false);
|
||||
|
||||
const dragBlocked = ref(false);
|
||||
|
||||
// Don't react to page-internal drag events
|
||||
useEventListener(
|
||||
document.body,
|
||||
"dragstart",
|
||||
() => {
|
||||
dragBlocked.value = true;
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
useEventListener(
|
||||
document.body,
|
||||
"dragover",
|
||||
(event) => {
|
||||
if (!dragBlocked.value) {
|
||||
// prevent the browser from opening the file
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
useEventListener(
|
||||
document.body,
|
||||
"drop",
|
||||
(event) => {
|
||||
if (!dragBlocked.value) {
|
||||
// prevent the browser from opening the file
|
||||
event.preventDefault();
|
||||
|
||||
if (isFileOverDropZone.value && isFileOverDocument.value) {
|
||||
unref(onDrop)(event);
|
||||
}
|
||||
}
|
||||
isFileOverDocument.value = false;
|
||||
dragBlocked.value = false;
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
useEventListener(
|
||||
document.body,
|
||||
"dragend",
|
||||
() => {
|
||||
// reset on drag end
|
||||
isFileOverDocument.value = false;
|
||||
isFileOverDropZone.value = false;
|
||||
dragBlocked.value = false;
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
useEventListener(
|
||||
document.body,
|
||||
"dragenter",
|
||||
(event) => {
|
||||
// init values if drag is possible
|
||||
if (!dragBlocked.value && !(unref(solo) && isAnyModalOpen())) {
|
||||
isFileOverDocument.value = true;
|
||||
isFileOverDropZone.value = false;
|
||||
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
/** returns if any bootstrap modal is open */
|
||||
function isAnyModalOpen() {
|
||||
return document.querySelectorAll(".modal.show").length > 0;
|
||||
}
|
||||
|
||||
useEventListener(
|
||||
dropZone,
|
||||
"dragenter",
|
||||
() => {
|
||||
isFileOverDropZone.value = true;
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
useEventListener(
|
||||
dropZone,
|
||||
"dragleave",
|
||||
() => {
|
||||
isFileOverDropZone.value = false;
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
return { isFileOverDocument, isFileOverDropZone };
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { ref } from "vue";
|
||||
|
||||
let modal = ref(null);
|
||||
|
||||
export function setGlobalUploadModal(modalRef) {
|
||||
// eslint-disable-next-line vue/no-ref-as-operand
|
||||
modal = modalRef;
|
||||
}
|
||||
|
||||
export function useGlobalUploadModal() {
|
||||
const openGlobalUploadModal = (options) => {
|
||||
modal.value?.open(options);
|
||||
};
|
||||
|
||||
return { openGlobalUploadModal };
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { computed, watch, unref, inject } from "vue";
|
||||
|
||||
export function useUserHistories(user) {
|
||||
const store = inject("store");
|
||||
|
||||
watch(
|
||||
() => unref(user),
|
||||
(newUser, oldUser) => {
|
||||
if (newUser?.id !== oldUser?.id) {
|
||||
store.dispatch("history/loadHistories");
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
const currentHistoryId = computed(() => store.getters["history/currentHistoryId"]);
|
||||
|
||||
return { currentHistoryId };
|
||||
}
|
||||
@@ -36,6 +36,7 @@
|
||||
<div id="dd-helper" />
|
||||
<Toast ref="toastRef" />
|
||||
<ConfirmDialog ref="confirmDialogRef" />
|
||||
<UploadModal ref="uploadModal" />
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
@@ -49,23 +50,28 @@ import { WindowManager } from "layout/window-manager";
|
||||
import { safePath } from "utils/redirect";
|
||||
import Toast from "components/Toast";
|
||||
import ConfirmDialog from "components/ConfirmDialog";
|
||||
import UploadModal from "components/Upload/UploadModal.vue";
|
||||
import { ref } from "vue";
|
||||
import { setToastComponentRef } from "composables/toast";
|
||||
import { setConfirmDialogComponentRef } from "composables/confirmDialog";
|
||||
import { ref } from "vue";
|
||||
import { setGlobalUploadModal } from "composables/globalUploadModal";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Masthead,
|
||||
Toast,
|
||||
ConfirmDialog,
|
||||
UploadModal,
|
||||
},
|
||||
setup() {
|
||||
const toastRef = ref(null);
|
||||
setToastComponentRef(toastRef);
|
||||
const confirmDialogRef = ref(null);
|
||||
setConfirmDialogComponentRef(confirmDialogRef);
|
||||
const uploadModal = ref(null);
|
||||
setGlobalUploadModal(uploadModal);
|
||||
|
||||
return { toastRef, confirmDialogRef };
|
||||
return { toastRef, confirmDialogRef, uploadModal };
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<SidePanel v-if="showPanels" side="right" :current-panel="getHistoryIndex()" :current-panel-properties="{}" />
|
||||
<DragAndDropModal />
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
@@ -22,11 +23,13 @@ import HistoryIndex from "components/History/Index";
|
||||
import ToolBox from "components/Panels/ProviderAwareToolBox";
|
||||
import SidePanel from "components/Panels/SidePanel";
|
||||
import CenterFrame from "./CenterFrame";
|
||||
import DragAndDropModal from "components/Upload/DragAndDropModal";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CenterFrame,
|
||||
SidePanel,
|
||||
DragAndDropModal,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
@@ -4,12 +4,12 @@ import Vue from "vue";
|
||||
import DataDialog from "components/DataDialog/DataDialog.vue";
|
||||
import { FilesDialog } from "components/FilesDialog";
|
||||
import DatasetCollectionDialog from "components/SelectionDialog/DatasetCollectionDialog.vue";
|
||||
import { mountUploadModal } from "components/Upload";
|
||||
import { uploadModelsToPayload } from "components/Upload/helpers";
|
||||
import { getGalaxyInstance } from "app";
|
||||
import { getAppRoot } from "onload/loadConfig";
|
||||
import { submitUpload } from "utils/uploadbox";
|
||||
import { rewatchHistory } from "store/historyStore/model/watchHistory";
|
||||
import { useGlobalUploadModal } from "composables/globalUploadModal";
|
||||
|
||||
// This should be moved more centrally (though still hanging off Galaxy for
|
||||
// external use?), and populated from the store; just using this as a temporary
|
||||
@@ -44,7 +44,8 @@ export function dialog(callback, options = {}) {
|
||||
history: history_id,
|
||||
});
|
||||
if (options.new) {
|
||||
mountUploadModal(options);
|
||||
const { openGlobalUploadModal } = useGlobalUploadModal();
|
||||
openGlobalUploadModal(options);
|
||||
} else {
|
||||
_mountSelectionDialog(DataDialog, options);
|
||||
}
|
||||
|
||||
+3
-3
@@ -10486,9 +10486,9 @@ void-elements@^2.0.0:
|
||||
integrity sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung==
|
||||
|
||||
vue-demi@*:
|
||||
version "0.13.6"
|
||||
resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.13.6.tgz#f9433cbd75e68a970dec066647f4ba6c08ced48f"
|
||||
integrity sha512-02NYpxgyGE2kKGegRPYlNQSL1UWfA/+JqvzhGCOYjhfbLWXU5QQX0+9pAm/R2sCOPKr5NBxVIab7fvFU0B1RxQ==
|
||||
version "0.13.11"
|
||||
resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.13.11.tgz#7d90369bdae8974d87b1973564ad390182410d99"
|
||||
integrity sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==
|
||||
|
||||
vue-eslint-parser@^9.0.1:
|
||||
version "9.0.3"
|
||||
|
||||
Reference in New Issue
Block a user