mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
341 lines
13 KiB
Vue
341 lines
13 KiB
Vue
<template>
|
|
<selection-dialog
|
|
:error-message="errorMessage"
|
|
:options-show="optionsShow"
|
|
:modal-show="modalShow"
|
|
:hide-modal="() => (modalShow = false)">
|
|
<template v-slot:search>
|
|
<data-dialog-search v-model="filter" />
|
|
</template>
|
|
<template v-slot:options>
|
|
<data-dialog-table
|
|
v-if="optionsShow"
|
|
:items="items"
|
|
:multiple="multiple"
|
|
:filter="filter"
|
|
:select-all-icon="selectAllIcon"
|
|
:show-select-icon="undoShow && multiple"
|
|
:show-details="showDetails"
|
|
:show-time="showTime"
|
|
:is-busy="isBusy"
|
|
@clicked="clicked"
|
|
@open="open"
|
|
@toggleSelectAll="toggleSelectAll" />
|
|
</template>
|
|
<template v-slot:buttons>
|
|
<b-btn id="back-btn" size="sm" class="float-left" v-if="undoShow" @click="load()">
|
|
<font-awesome-icon :icon="['fas', 'caret-left']" />
|
|
Back
|
|
</b-btn>
|
|
<b-btn
|
|
v-if="multiple || !fileMode"
|
|
size="sm"
|
|
class="float-right ml-1 file-dialog-modal-ok"
|
|
variant="primary"
|
|
id="ok-btn"
|
|
@click="fileMode ? finalize() : selectLeaf(currentDirectory)"
|
|
:disabled="(fileMode && !hasValue) || isBusy || (!fileMode && urlTracker.atRoot())">
|
|
{{ fileMode ? "Ok" : "Select this folder" }}
|
|
</b-btn>
|
|
</template>
|
|
</selection-dialog>
|
|
</template>
|
|
|
|
<script>
|
|
import Vue from "vue";
|
|
import SelectionDialogMixin from "components/SelectionDialog/SelectionDialogMixin";
|
|
import { selectionStates } from "components/SelectionDialog/selectionStates";
|
|
import { UrlTracker } from "components/DataDialog/utilities";
|
|
import { isSubPath } from "components/FilesDialog/utilities";
|
|
import { Services } from "./services";
|
|
import { Model } from "./model";
|
|
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
|
import { library } from "@fortawesome/fontawesome-svg-core";
|
|
import { faCaretLeft } from "@fortawesome/free-solid-svg-icons";
|
|
|
|
library.add(faCaretLeft);
|
|
export default {
|
|
mixins: [SelectionDialogMixin],
|
|
props: {
|
|
multiple: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
mode: {
|
|
type: String,
|
|
default: "file",
|
|
validator: (prop) => ["file", "directory"].includes(prop),
|
|
},
|
|
requireWritable: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
},
|
|
components: {
|
|
FontAwesomeIcon,
|
|
},
|
|
data() {
|
|
return {
|
|
allSelected: false,
|
|
selectedDirectories: [],
|
|
errorMessage: null,
|
|
filter: null,
|
|
items: [],
|
|
modalShow: true,
|
|
optionsShow: false,
|
|
undoShow: false,
|
|
hasValue: false,
|
|
showTime: true,
|
|
showDetails: true,
|
|
isBusy: false,
|
|
currentDirectory: undefined,
|
|
selectAllIcon: selectionStates.unselected,
|
|
};
|
|
},
|
|
created: function () {
|
|
this.services = new Services();
|
|
this.urlTracker = new UrlTracker("");
|
|
this.model = new Model({ multiple: this.multiple });
|
|
this.load();
|
|
},
|
|
computed: {
|
|
fileMode() {
|
|
return this.mode == "file";
|
|
},
|
|
},
|
|
methods: {
|
|
/** Add highlighting for record variations, i.e. datasets vs. libraries/collections **/
|
|
formatRows() {
|
|
const getIcon = (isSelected, path) => {
|
|
if (isSelected) {
|
|
return selectionStates.selected;
|
|
} else {
|
|
return this.model.pathExists(path) ? selectionStates.mixed : selectionStates.unselected;
|
|
}
|
|
};
|
|
|
|
this.hasValue = this.model.count() > 0 || this.selectedDirectories.length > 0;
|
|
for (const item of this.items) {
|
|
let _rowVariant = "active";
|
|
if (item.isLeaf || !this.fileMode) {
|
|
_rowVariant = this.model.exists(item.id) ? "success" : "default";
|
|
}
|
|
// if directory
|
|
else if (!item.isLeaf) {
|
|
_rowVariant = getIcon(this.isDirectorySelected(item.id), item.url);
|
|
}
|
|
Vue.set(item, "_rowVariant", _rowVariant);
|
|
}
|
|
this.allSelected = this.checkIfAllSelected();
|
|
if (this.currentDirectory.url) {
|
|
this.selectAllIcon = getIcon(this.allSelected, this.currentDirectory.url);
|
|
}
|
|
},
|
|
/** Collects selected datasets in value array **/
|
|
clicked: function (record) {
|
|
// ignore the click during directory mode
|
|
if (!this.fileMode) {
|
|
return;
|
|
}
|
|
if (record.isLeaf) {
|
|
// record is file
|
|
this.selectLeaf(record);
|
|
} else {
|
|
// record is directory
|
|
// you cannot select entire root directory
|
|
this.urlTracker.atRoot() ? this.open(record) : this.selectDirectory(record);
|
|
}
|
|
this.formatRows();
|
|
},
|
|
unselectPath(path, unselectOnlyAboveDirectories = false, unselectId) {
|
|
// unselect directories
|
|
this.selectedDirectories = this.selectedDirectories.filter((directory) => {
|
|
if (unselectId === directory.id) {
|
|
return false;
|
|
}
|
|
|
|
let matched;
|
|
if (unselectOnlyAboveDirectories) {
|
|
matched = isSubPath(directory.url, path);
|
|
} else {
|
|
// unselect all folders under or above the current path
|
|
matched = isSubPath(directory.url, path) || isSubPath(path, directory.url);
|
|
}
|
|
// filter out those that matched
|
|
return !matched;
|
|
});
|
|
|
|
// unselect files
|
|
if (!unselectOnlyAboveDirectories) {
|
|
// unselect all files under this path
|
|
this.model.unselectUnderPath(path);
|
|
}
|
|
},
|
|
selectLeaf(file, selectOnly = false) {
|
|
const selected = this.model.exists(file.id);
|
|
if (selected) {
|
|
this.unselectPath(file.url, true);
|
|
}
|
|
if (selectOnly) {
|
|
if (!selected) {
|
|
this.model.add(file);
|
|
}
|
|
} else {
|
|
this.model.add(file);
|
|
}
|
|
|
|
this.hasValue = this.model.count() > 0;
|
|
if (this.multiple) {
|
|
this.formatRows();
|
|
} else {
|
|
this.finalize();
|
|
}
|
|
},
|
|
selectDirectory(record) {
|
|
// if directory is `selected` or `mixed` unselect everything
|
|
if (this.isDirectorySelected(record.id) || this.model.pathExists(record.url)) {
|
|
this.unselectPath(record.url, false, record.id);
|
|
} else {
|
|
this.selectedDirectories.push(record);
|
|
// look for subdirectories
|
|
const recursive = true;
|
|
this.isBusy = true;
|
|
this.services.list(record.url, recursive).then((items) => {
|
|
items.forEach((item) => {
|
|
// construct record
|
|
const sub_record = this.parseItemFileMode(item);
|
|
if (sub_record.isLeaf) {
|
|
// select file under this path
|
|
this.selectLeaf(sub_record, true);
|
|
} else {
|
|
// select subdirectory
|
|
this.selectedDirectories.push(sub_record);
|
|
}
|
|
});
|
|
this.isBusy = false;
|
|
});
|
|
}
|
|
},
|
|
isDirectorySelected(directoryId) {
|
|
return this.selectedDirectories.some(({ id }) => id === directoryId);
|
|
},
|
|
open: function (record) {
|
|
this.load(record);
|
|
},
|
|
/** Called when selection is complete, values are formatted and parsed to external callback **/
|
|
finalize: function () {
|
|
const results = this.model.finalize();
|
|
this.modalShow = false;
|
|
this.callback(results);
|
|
},
|
|
/** check if all objects in this folders are selected **/
|
|
checkIfAllSelected() {
|
|
const isAllSelected = this.items.every(({ id }) => this.model.exists(id) || this.isDirectorySelected(id));
|
|
|
|
if (isAllSelected && !this.isDirectorySelected(this.currentDirectory.id)) {
|
|
// if all selected, select current folder
|
|
this.selectedDirectories.push(this.currentDirectory);
|
|
}
|
|
|
|
return isAllSelected;
|
|
},
|
|
/** select all files in current folder**/
|
|
toggleSelectAll: function () {
|
|
const isUnselectAll = this.model.pathExists(this.currentDirectory.url);
|
|
|
|
for (const item of this.items) {
|
|
if (isUnselectAll) {
|
|
if (this.model.exists(item.id) || this.model.pathExists(item.id)) {
|
|
item.isLeaf ? this.model.add(item) : this.selectDirectory(item);
|
|
}
|
|
} else {
|
|
item.isLeaf ? this.model.add(item) : this.selectDirectory(item);
|
|
}
|
|
}
|
|
this.hasValue = this.model.count() > 0;
|
|
this.formatRows();
|
|
},
|
|
/** Performs server request to retrieve data records **/
|
|
load: function (record) {
|
|
this.currentDirectory = this.urlTracker.getUrl(record);
|
|
this.filter = null;
|
|
this.optionsShow = false;
|
|
this.undoShow = !this.urlTracker.atRoot();
|
|
if (this.urlTracker.atRoot()) {
|
|
this.services
|
|
.getFileSources()
|
|
.then((items) => {
|
|
items = items
|
|
.filter((item) => !this.requireWritable || item.writable)
|
|
.map((item) => {
|
|
return {
|
|
id: item.id,
|
|
label: item.label,
|
|
details: item.doc,
|
|
isLeaf: false,
|
|
url: item.uri_root,
|
|
labelTitle: item.uri_root,
|
|
};
|
|
});
|
|
this.items = items;
|
|
this.formatRows();
|
|
this.optionsShow = true;
|
|
this.showTime = false;
|
|
this.showDetails = true;
|
|
})
|
|
.catch((errorMessage) => {
|
|
this.errorMessage = errorMessage;
|
|
});
|
|
} else {
|
|
this.services
|
|
.list(this.currentDirectory.url)
|
|
.then((items) => {
|
|
this.items = this.parseItems(items);
|
|
this.formatRows();
|
|
this.optionsShow = true;
|
|
this.showTime = true;
|
|
this.showDetails = false;
|
|
})
|
|
.catch((errorMessage) => {
|
|
this.errorMessage = errorMessage;
|
|
});
|
|
}
|
|
},
|
|
parseItemFileMode(item) {
|
|
const result = {
|
|
id: item.uri,
|
|
label: item.name,
|
|
time: item.ctime,
|
|
isLeaf: item.class === "File",
|
|
size: item.size,
|
|
url: item.uri,
|
|
labelTitle: item.uri,
|
|
};
|
|
return result;
|
|
},
|
|
parseItems(items) {
|
|
if (this.fileMode) {
|
|
items = items.map((item) => {
|
|
return this.parseItemFileMode(item);
|
|
});
|
|
} else {
|
|
items = items
|
|
.filter((item) => item.class == "Directory")
|
|
.map((item) => {
|
|
return {
|
|
id: item.uri,
|
|
label: item.name,
|
|
time: item.ctime,
|
|
isLeaf: false,
|
|
size: item.size,
|
|
url: item.uri,
|
|
labelTitle: item.uri,
|
|
};
|
|
});
|
|
}
|
|
return items;
|
|
},
|
|
},
|
|
};
|
|
</script>
|