Merge pull request #18638 from itisAliRH/libraries-modernization-1-directory-dataset-picker

Libraries Modernisation: Directory Dataset Picker
This commit is contained in:
David López
2024-10-07 18:17:47 +02:00
committed by GitHub
15 changed files with 510 additions and 1700 deletions
@@ -1,4 +1,5 @@
<script setup lang="ts">
import { BAlert, BFormCheckbox } from "bootstrap-vue";
import { computed, type ComputedRef } from "vue";
import { findDescendants, flattenValues, getAllValues, type Option, type Value } from "./utilities";
@@ -11,10 +12,12 @@ const props = withDefaults(
value?: Value;
options: Array<Option>;
multiple: boolean;
showIcons?: boolean;
}>(),
{
value: null,
multiple: true,
showIcons: false,
}
);
@@ -91,20 +94,27 @@ function setElementValues(oldArray: string[], newArray: string[], value: string)
</script>
<template>
<div v-if="hasOptions">
<b-form-checkbox
v-if="multiple"
v-localize
:checked="selectAllChecked"
:indeterminate="selectAllIndeterminate"
class="d-inline select-all-checkbox"
@change="onSelectAll">
Select / Deselect All
</b-form-checkbox>
<FormDrilldownList
:multiple="multiple"
:current-value="currentValue"
:options="options"
:handle-click="handleClick" />
<div>
<div v-if="hasOptions">
<BFormCheckbox
v-if="multiple"
v-localize
:checked="selectAllChecked"
:indeterminate="selectAllIndeterminate"
class="d-inline select-all-checkbox"
@change="onSelectAll">
Select / Deselect All
</BFormCheckbox>
<FormDrilldownList
:show-icons="showIcons"
:multiple="multiple"
:current-value="currentValue"
:options="options"
:handle-click="handleClick" />
</div>
<div v-else>
<BAlert show variant="info" class="mt-2"> No options available. </BAlert>
</div>
</div>
</template>
@@ -7,6 +7,7 @@ defineProps<{
currentValue: string[];
handleClick: Function;
multiple: boolean;
showIcons?: boolean;
options: Array<Option>;
}>();
</script>
@@ -15,6 +16,7 @@ defineProps<{
<div class="ui-drilldown">
<div v-for="option in options" :key="option.name" class="descendant-lines">
<FormDrilldownOption
:show-icons="showIcons"
:current-value="currentValue"
:handle-click="handleClick"
:multiple="multiple"
@@ -1,4 +1,6 @@
<script setup lang="ts">
import { faCaretDown, faCaretRight, faFile, faFolder, type IconDefinition } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { BFormCheckbox, BFormRadio } from "bootstrap-vue";
import { computed, type ComputedRef, onMounted, ref } from "vue";
@@ -6,12 +8,20 @@ import { getAllValues, type Option } from "./utilities";
import FormDrilldownList from "./FormDrilldownList.vue";
const props = defineProps<{
interface Props {
currentValue: string[];
option: Option;
handleClick: Function;
multiple: boolean;
}>();
showIcons?: boolean;
leafIcon?: IconDefinition;
branchIcon?: IconDefinition;
}
const props = withDefaults(defineProps<Props>(), {
leafIcon: () => faFile,
branchIcon: () => faFolder,
});
const showChildren = ref(false);
@@ -43,6 +53,11 @@ function toggleInitialization(): void {
}
}
}
function getOptionIcon(option: Option) {
return option.leaf ? props.leafIcon : props.branchIcon;
}
onMounted(() => {
toggleInitialization();
});
@@ -51,22 +66,26 @@ onMounted(() => {
<template>
<div>
<b-button v-if="hasOptions" variant="link" class="btn p-0" @click="toggleChildren">
<i v-if="showChildren" class="fa fa-caret-down align-checkbox" />
<i v-else class="fa fa-caret-right align-checkbox" />
<FontAwesomeIcon v-if="showChildren" :icon="faCaretDown" class="align-checkbox" />
<FontAwesomeIcon v-else :icon="faCaretRight" class="align-checkbox" />
</b-button>
<span v-if="!hasOptions" class="align-indent"></span>
<component
:is="isComponent"
:id="`drilldown-option-${option.name}`"
class="drilldown-option d-inline"
value="true"
:disabled="option.disabled"
:checked="isChecked"
@change="handleClick(option.value, $event)">
<FontAwesomeIcon v-if="props.showIcons" :icon="getOptionIcon(option)" />
{{ option.name }}
</component>
<FormDrilldownList
v-if="hasOptions"
v-show="showChildren"
class="indent"
:show-icons="props.showIcons"
:current-value="currentValue"
:multiple="multiple"
:options="option.options"
@@ -1,6 +1,9 @@
export interface Option {
name: string;
value: string;
leaf?: boolean;
fullPath?: string;
disabled?: boolean;
options: Array<Option>;
}
@@ -99,7 +99,7 @@
collection-name="Database/Builds"
:loading="loadingDbKeys"
:items="dbkeys"
:current-item-id="selectedDbKey"
:current-item="selectedDbKey"
@update:selected-item="onSelectedDbKey" />
</DbKeyProvider>
</b-modal>
@@ -116,7 +116,7 @@
collection-name="Data Types"
:loading="loadingDatatypes"
:items="datatypes"
:current-item-id="selectedDatatype"
:current-item="selectedDatatype"
@update:selected-item="onSelectedDatatype" />
</DatatypesProvider>
</b-modal>
@@ -186,8 +186,8 @@ export default {
},
data: function () {
return {
selectedDbKey: "?",
selectedDatatype: "auto",
selectedDbKey: { id: "?", text: "unspecified (?)" },
selectedDatatype: { id: "auto", text: "Auto-detect" },
selectedTags: [],
};
},
@@ -307,12 +307,12 @@ export default {
this.runOnSelection(purgeSelectedContent);
},
changeDbkeyOfSelected() {
this.runOnSelection(changeDbkeyOfSelectedContent, { dbkey: this.selectedDbKey });
this.selectedDbKey = "?";
this.runOnSelection(changeDbkeyOfSelectedContent, { dbkey: this.selectedDbKey.id });
this.selectedDbKey = { id: "?" };
},
changeDatatypeOfSelected() {
this.runOnSelection(changeDatatypeOfSelectedContent, { datatype: this.selectedDatatype });
this.selectedDatatype = "auto";
this.runOnSelection(changeDatatypeOfSelectedContent, { datatype: this.selectedDatatype.id });
this.selectedDatatype = { id: "auto", text: "Auto-detect" };
},
addTagsToSelected() {
this.runOnSelection(addTagsToSelectedContent, { tags: this.selectedTags });
@@ -354,10 +354,10 @@ export default {
this.$emit("operation-error", { errorMessage, result });
},
onSelectedDbKey(dbkey) {
this.selectedDbKey = dbkey.id;
this.selectedDbKey = dbkey;
},
onSelectedDatatype(datatype) {
this.selectedDatatype = datatype.id;
this.selectedDatatype = datatype;
},
// collection creation, fires up a modal
@@ -83,7 +83,7 @@
collection-name="Data Types"
:loading="loadingDatatypes"
:items="datatypes"
:current-item-id="dataset.file_ext"
:current-item="datatypes?.find((datatype) => datatype.id === dataset.file_ext)"
@update:selected-item="onSelectedDatatype" />
</DatatypesProvider>
<DbKeyProvider
@@ -93,7 +93,7 @@
collection-name="Database/Builds"
:loading="loadingDbKeys"
:items="dbkeys"
:current-item-id="dataset.genome_build"
:current-item="dbkeys?.find((dbkey) => dbkey.id === dataset.genome_build)"
@update:selected-item="onSelectedDbKey" />
</DbKeyProvider>
<b-form-input
@@ -0,0 +1,366 @@
<script setup lang="ts">
import {
BAlert,
BButton,
BFormCheckbox,
BFormCheckboxGroup,
BFormGroup,
BFormTextarea,
BModal,
BTab,
BTabs,
} from "bootstrap-vue";
import { computed, ref, watch } from "vue";
import { GalaxyApi } from "@/api";
import { type Option } from "@/components/Form/Elements/FormDrilldown/utilities";
import { type DetailedDatatypes, useDetailedDatatypes } from "@/composables/datatypes";
import { Toast } from "@/composables/toast";
import { useDbKeyStore } from "@/stores/dbKeyStore";
import { errorMessageAsString } from "@/utils/simple-error";
import FormDrilldown from "@/components/Form/Elements/FormDrilldown/FormDrilldown.vue";
import LoadingSpan from "@/components/LoadingSpan.vue";
import SingleItemSelector from "@/components/SingleItemSelector.vue";
const autoExtension = {
id: "auto",
extension: "auto",
text: "Auto-detect",
description: `This system will try to detect the file type automatically.
If your file is not detected properly as one of the known formats,
it most likely means that it has some format problems (e.g., different
number of columns on different rows). You can still coerce the system
to set your data to the format you think it should be.
You can also upload compressed files, which will automatically be decompressed`,
descriptionUrl: "",
};
type DbKey = { id: string; text: string };
type DbKeyList = DbKey[];
type RequestData = {
path: string;
source: string;
dbkey: string;
encoded_folder_id: string;
file_type?: string;
preserve_dirs?: boolean;
link_data: boolean;
space_to_tab: boolean;
to_posix_lines: boolean;
tag_using_filenames: boolean;
};
interface Props {
folderId: string;
target: "userdir" | "importdir" | "path";
}
const props = defineProps<Props>();
const emit = defineEmits<{
(e: "reload"): void;
(e: "onClose"): void;
(e: "onSelect", items: RequestData[]): void;
}>();
const dbKeyStore = useDbKeyStore();
const { datatypes, datatypesLoading } = useDetailedDatatypes();
const activeTab = ref(0);
const importing = ref(false);
const paths = ref<string>("");
const options = ref<Option[]>([]);
const optionsLoading = ref(false);
const selectedDbKey = ref<DbKey>();
const dbKeyList = ref<DbKeyList>([]);
const errorMessage = ref<string>("");
const currentValue = ref<string[]>([]);
const preserveOptions = ref<string[]>([]);
const extensionsList = ref<DetailedDatatypes[]>([]);
const selectedExtension = ref<DetailedDatatypes>(autoExtension);
const pathMode = computed(() => {
return props.target === "path";
});
const filesMode = computed(() => {
return activeTab.value === 0;
});
const title = computed(() => {
return pathMode.value ? "Please enter paths to import" : "Please select folders or files";
});
const importDisable = computed(() => {
if (importing.value) {
return true;
}
if (pathMode.value) {
return paths.value?.length === 0;
}
return currentValue.value?.length === 0;
});
const okButtonText = computed(() => {
const length = currentValue.value?.length || 0;
return length === 0 ? "Import" : `Import ${length} dataset${length > 1 ? "s" : ""}`;
});
async function fetchOptions() {
optionsLoading.value = true;
const { data, error } = await GalaxyApi().GET("/api/remote_files", {
params: {
query: {
format: "jstree",
target: props.target,
disable: filesMode.value ? "folders" : "files",
},
},
});
options.value = mapDataToOptions(data);
if (error) {
errorMessage.value = errorMessageAsString(error, "Failed to load directories.");
}
optionsLoading.value = false;
}
function mapDataToOptions(data: any): Option[] {
return data?.map((item: any) => {
const option: Option = {
name: item.text,
fullPath: item.li_attr.full_path,
value: item.li_attr.id,
leaf: item.type === "file",
disabled: item.state.disabled,
options: item.children ? mapDataToOptions(item.children) : [],
};
return option;
});
}
function getFullPathById(id: string): string {
function traverse(nodes: Option[], targetId: string): string {
for (const node of nodes) {
if (node.value === targetId) {
return node.fullPath as string;
}
if (node.options?.length > 0) {
const result = traverse(node.options, targetId);
if (result) {
return result;
}
}
}
return "";
}
return traverse(options.value, id);
}
async function fetchExtAndDbKey() {
extensionsList.value = datatypes.value;
extensionsList.value.sort((a, b) => (a.extension > b.extension ? 1 : a.extension < b.extension ? -1 : 0));
extensionsList.value = [autoExtension, ...extensionsList.value];
selectedExtension.value = autoExtension;
await dbKeyStore.fetchUploadDbKeys();
dbKeyList.value = dbKeyStore.uploadDbKeys as DbKeyList;
selectedDbKey.value = dbKeyStore.uploadDbKeys.find((item: DbKey) => item.id === "?");
dbKeyList.value.sort((a, b) => (a.id > b.id ? 1 : a.id < b.id ? -1 : 0));
}
async function onImport() {
if (pathMode.value) {
const tmp: string[] = paths.value.split("\n");
const validPaths: string[] = [];
for (let i = tmp.length - 1; i >= 0; i--) {
var trimmed = tmp?.[i]?.trim();
if (trimmed && trimmed?.length !== 0) {
validPaths.push(trimmed);
}
}
if (validPaths.length === 0) {
Toast.error("Please provide a valid path");
return;
} else {
importFileOrFolder(validPaths, "admin_path");
}
} else {
const source = `${props.target}_${filesMode.value ? "file" : "folder"}`;
const validPaths: string[] = currentValue.value.map((item) => getFullPathById(item));
importFileOrFolder(validPaths, source);
}
}
async function importFileOrFolder(validPaths: string[], source: string) {
const items: RequestData[] = [];
for (const path of validPaths) {
const reqData: RequestData = {
path: path,
source: source,
dbkey: selectedDbKey.value?.id || "?",
encoded_folder_id: props.folderId,
link_data: preserveOptions.value.includes("link_files"),
space_to_tab: preserveOptions.value.includes("space_to_tab"),
to_posix_lines: preserveOptions.value.includes("to_posix_lines"),
tag_using_filenames: preserveOptions.value.includes("tags_from_filenames"),
};
if (!pathMode.value) {
reqData.file_type = selectedExtension.value.extension;
}
if (!filesMode.value) {
reqData.preserve_dirs = preserveOptions.value.includes("preserve_directory_structure");
}
items.push(reqData);
}
emit("onSelect", items);
emit("onClose");
}
watch(
activeTab,
() => {
if (!pathMode.value) {
fetchOptions();
}
},
{ immediate: true }
);
function onSelectDbKey(item: DbKey) {
selectedDbKey.value = item;
}
function onSelectExtension(item: DetailedDatatypes) {
selectedExtension.value = item;
}
watch(
() => datatypesLoading.value,
() => {
if (!datatypesLoading.value) {
fetchExtAndDbKey();
}
}
);
</script>
<template>
<BModal :title="title" visible scrollable content-class="directory-dataset-picker" @hide="emit('onClose')">
<BTabs v-if="!pathMode" v-model="activeTab" fill pills>
<BTab title="Choose Files" />
<BTab title="Choose Folders" />
</BTabs>
<BAlert v-if="filesMode" show class="mt-2">
All files you select will be imported into the current folder ignoring their folder structure.
</BAlert>
<BAlert v-else-if="!filesMode" show class="mt-2">
All files within the selected folders and their sub-folders will be imported into the current folder.
</BAlert>
<BAlert v-else-if="pathMode" show class="mt-2">
All files within the given folders and their sub-folders will be imported into the current folder.
</BAlert>
<BFormCheckboxGroup v-model="preserveOptions" switches class="directory-dataset-picker-options">
<BFormCheckbox value="link_files">Link files instead of copying </BFormCheckbox>
<BFormCheckbox value="to_posix_lines">Convert line endings to POSIX</BFormCheckbox>
<BFormCheckbox value="space_to_tab">Convert spaces to tabs</BFormCheckbox>
<BFormCheckbox value="tags_from_filenames">Tag datasets based on file names</BFormCheckbox>
<BFormCheckbox v-if="!filesMode || pathMode" value="preserve_directory_structure">
Preserve directory structure
</BFormCheckbox>
</BFormCheckboxGroup>
<hr />
You can set database/build and extension type for all imported datasets at once:
<BFormGroup label="Database/Build">
<SingleItemSelector
:current-item="selectedDbKey"
collection-name="DB Keys"
:items="dbKeyList"
@update:selected-item="onSelectDbKey" />
</BFormGroup>
<BFormGroup label="Extension">
<SingleItemSelector
:current-item="selectedExtension"
collection-name="Extensions"
:items="extensionsList"
track-by="extension"
label="extension"
@update:selected-item="onSelectExtension" />
</BFormGroup>
<hr />
<BFormTextarea
v-if="pathMode"
id="import_paths"
v-model="paths"
placeholder="Absolute paths (or paths relative to Galaxy root) separated by newline"
rows="5" />
<BAlert v-else-if="optionsLoading" variant="info" show>
<LoadingSpan message="Loading directories" />
</BAlert>
<BAlert v-else-if="errorMessage" variant="danger" show>
{{ errorMessage }}
</BAlert>
<FormDrilldown
v-else
:id="filesMode ? 'files' : 'folders'"
v-model="currentValue"
class="directory-dataset-picker-list"
show-icons
:options="options"
multiple />
<template v-slot:modal-footer>
<BButton size="sm" variant="secondary" :disabled="importing" @click="emit('onClose')">Close</BButton>
<BButton size="sm" variant="primary" :disabled="importDisable" @click="onImport">
{{ okButtonText }}
</BButton>
</template>
</BModal>
</template>
<style scoped lang="scss">
.directory-dataset-picker {
display: grid;
grid-template-rows: max-content 1fr;
.directory-dataset-picker-options {
display: grid;
grid-template-columns: 1fr 1fr;
}
.directory-dataset-picker-list {
max-height: 100%;
overflow-y: auto;
}
}
</style>
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { library } from "@fortawesome/fontawesome-svg-core";
import { faBook, faCaretDown, faDownload, faHome, faPlus, faTrash } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import axios from "axios";
import {
BAlert,
BButton,
@@ -16,28 +16,23 @@ import { computed, reactive, ref } from "vue";
import { GalaxyApi } from "@/api";
import { Services } from "@/components/Libraries/LibraryFolder/services";
import mod_add_datasets from "@/components/Libraries/LibraryFolder/TopToolbar/add-datasets";
import { deleteSelectedItems } from "@/components/Libraries/LibraryFolder/TopToolbar/delete-selected";
import download from "@/components/Libraries/LibraryFolder/TopToolbar/download";
import mod_import_collection from "@/components/Libraries/LibraryFolder/TopToolbar/import-to-history/import-collection";
import mod_import_dataset from "@/components/Libraries/LibraryFolder/TopToolbar/import-to-history/import-dataset";
import { type SelectionItem } from "@/components/SelectionDialog/selectionTypes";
import { useConfig } from "@/composables/config";
import { type DetailedDatatypes, useDetailedDatatypes } from "@/composables/datatypes";
import { Toast } from "@/composables/toast";
import { useDbKeyStore } from "@/stores/dbKeyStore";
import { getAppRoot } from "@/onload";
import { useUserStore } from "@/stores/userStore";
import FolderDetails from "@/components/Libraries/LibraryFolder/FolderDetails/FolderDetails.vue";
import LibraryBreadcrumb from "@/components/Libraries/LibraryFolder/LibraryBreadcrumb.vue";
import SearchField from "@/components/Libraries/LibraryFolder/SearchField.vue";
import DirectoryDatasetPicker from "@/components/Libraries/LibraryFolder/TopToolbar/DirectoryDatasetPicker.vue";
import ProgressBar from "@/components/ProgressBar.vue";
import HistoryDatasetPicker from "@/components/SelectionDialog/HistoryDatasetPicker.vue";
library.add(faBook, faCaretDown, faDownload, faHome, faPlus, faTrash);
type GenomesList = { id: string; text: string }[];
interface Props {
metadata: any;
selected: any[];
@@ -68,13 +63,8 @@ const { config, isConfigLoaded } = useConfig();
const userStore = useUserStore();
const { isAdmin } = storeToRefs(userStore);
const { datatypes } = useDetailedDatatypes();
const dbKeyStore = useDbKeyStore();
const modalShow = ref("");
const genomesList = ref<GenomesList>([]);
const extensionsList = ref<DetailedDatatypes[]>([]);
type ImportSource = "history" | "userdir" | "importdir" | "path" | undefined;
const modalShow = ref<ImportSource>();
const progress = ref(false);
const progressNote = ref("");
const progressStatus = reactive({
@@ -83,18 +73,6 @@ const progressStatus = reactive({
errorCount: 0,
runningCount: 0,
});
const auto = ref({
id: "auto",
extension: "auto",
text: "Auto-detect",
description: `This system will try to detect the file type automatically.
If your file is not detected properly as one of the known formats,
it most likely means that it has some format problems (e.g., different
number of columns on different rows). You can still coerce the system
to set your data to the format you think it should be.
You can also upload compressed files, which will automatically be decompressed`,
description_url: "",
});
const services = new Services();
@@ -222,50 +200,10 @@ async function importToHistoryModal(isCollection: boolean) {
}
}
function onAddDatasets(source: string = "") {
function onAddDatasets(source?: ImportSource) {
modalShow.value = source;
}
// TODO: after replacing the selection dialog with the new component that is not using jquery
async function addDatasets(source: string) {
await fetchExtAndGenomes();
new mod_add_datasets.AddDatasets({
source: source,
id: props.folderId,
updateContent: updateContent,
list_genomes: genomesList.value,
list_extensions: extensionsList.value,
});
}
function updateContent() {
emit("fetchFolderContents");
}
// END TODO
async function fetchExtAndGenomes() {
try {
extensionsList.value = datatypes.value;
extensionsList.value.sort((a, b) => (a.extension > b.extension ? 1 : a.extension < b.extension ? -1 : 0));
extensionsList.value = [auto.value, ...extensionsList.value];
} catch (err) {
console.error(err);
}
try {
await dbKeyStore.fetchUploadDbKeys();
genomesList.value = dbKeyStore.uploadDbKeys;
genomesList.value.sort((a, b) => (a.id > b.id ? 1 : a.id < b.id ? -1 : 0));
} catch (err) {
console.error(err);
}
}
function resetProgress() {
progressStatus.total = 0;
progressStatus.okCount = 0;
@@ -273,7 +211,10 @@ function resetProgress() {
progressStatus.runningCount = 0;
}
async function onAddDatasetsFromHistory(selectedDatasets: SelectionItem[]) {
async function addDatasets(
selectedDatasets: SelectionItem[] | Record<string, string | boolean>[],
datasetApiCall: Function
) {
resetProgress();
progress.value = true;
@@ -286,22 +227,10 @@ async function onAddDatasetsFromHistory(selectedDatasets: SelectionItem[]) {
try {
progressStatus.runningCount++;
const { error } = await GalaxyApi().POST("/api/folders/{folder_id}/contents", {
params: {
path: { folder_id: props.folderId },
},
body: {
ldda_message: null,
from_hda_id: dataset.id,
},
});
if (error) {
throw new Error(error.err_msg);
}
await datasetApiCall(dataset);
progressStatus.okCount++;
} catch (err) {
} catch (e) {
progressStatus.errorCount++;
} finally {
progressStatus.runningCount--;
@@ -323,6 +252,34 @@ async function onAddDatasetsFromHistory(selectedDatasets: SelectionItem[]) {
emit("setBusy", false);
emit("fetchFolderContents");
}
function onAddDatasetsFromHistory(selectedDatasets: SelectionItem[]) {
const datasetApiCall = async (dataset: SelectionItem) => {
const { error } = await GalaxyApi().POST("/api/folders/{folder_id}/contents", {
params: {
path: { folder_id: props.folderId },
},
body: {
ldda_message: null,
from_hda_id: dataset.id,
},
});
if (error) {
throw new Error(error.err_msg);
}
};
addDatasets(selectedDatasets, datasetApiCall);
}
function onAddDatasetsDirectory(selectedDatasets: Record<string, string | boolean>[]) {
const datasetApiCall = async (dataset: Record<string, string | boolean>) => {
await axios.post(`${getAppRoot()}api/libraries/datasets`, dataset);
};
addDatasets(selectedDatasets, datasetApiCall);
}
</script>
<template>
@@ -364,18 +321,18 @@ async function onAddDatasetsFromHistory(selectedDatasets: SelectionItem[]) {
<BDropdownItem @click="onAddDatasets('history')"> from History </BDropdownItem>
<BDropdownItem v-if="userLibraryImportDirAvailable" @click="addDatasets('userdir')">
<BDropdownItem v-if="userLibraryImportDirAvailable" @click="onAddDatasets('userdir')">
from User Directory
</BDropdownItem>
<BDropdownDivider v-if="libraryImportDir || allowLibraryPathPaste" />
<BDropdownGroup v-if="libraryImportDir || allowLibraryPathPaste" header="Admins Only">
<BDropdownItem v-if="libraryImportDir" @click="addDatasets('importdir')">
<BDropdownItem v-if="libraryImportDir" @click="onAddDatasets('importdir')">
from Import Directory
</BDropdownItem>
<BDropdownItem v-if="allowLibraryPathPaste" @click="addDatasets('path')">
<BDropdownItem v-if="allowLibraryPathPaste" @click="onAddDatasets('path')">
from Path
</BDropdownItem>
</BDropdownGroup>
@@ -449,5 +406,11 @@ async function onAddDatasetsFromHistory(selectedDatasets: SelectionItem[]) {
:folder-id="props.folderId"
@onSelect="onAddDatasetsFromHistory"
@onClose="onAddDatasets" />
<DirectoryDatasetPicker
v-else-if="modalShow"
:target="modalShow"
:folder-id="props.folderId"
@onSelect="onAddDatasetsDirectory"
@onClose="onAddDatasets" />
</div>
</template>
@@ -1,579 +0,0 @@
import "libs/jquery/jstree";
import { getGalaxyInstance } from "app";
import Backbone from "backbone";
import { Toast } from "composables/toast";
import $ from "jquery";
import { getAppRoot } from "onload/loadConfig";
import _ from "underscore";
import _l from "utils/localization";
import { updateProgress } from "./delete-selected";
import mod_library_model from "./library-model";
import mod_select from "./ui-select";
var AddDatasets = Backbone.View.extend({
options: null,
initialize: function (options) {
this.options = options;
this.options.chain_call_control = {
total_number: 0,
failed_number: 0,
};
this.list_extensions = options.list_extensions;
this.list_genomes = options.list_genomes;
this.showImportModal(options);
},
/*
Slightly adopted Backbone code
*/
showImportModal: function (options) {
switch (options.source) {
case "importdir":
this.importFilesFromGalaxyFolderModal({
source: "importdir",
});
break;
case "path":
this.importFilesFromPathModal();
break;
case "userdir":
this.importFilesFromGalaxyFolderModal({
source: "userdir",
});
break;
default:
// Galaxy.libraries.library_router.back();
Toast.error("Invalid import source.");
break;
}
},
templateBrowserModal: function () {
return _.template(
`<div id="file_browser_modal">
<div style="margin-bottom:1em;">
<label title="Switch to selecting files" class="radio-inline import-type-switch">
<input type="radio" name="jstree-radio" value="jstree-disable-folders" checked="checked">
Choose Files
</label>
<label title="Switch to selecting folders" class="radio-inline import-type-switch">
<input type="radio" name="jstree-radio" value="jstree-disable-files">
Choose Folders
</label>
</div>
<div class="alert alert-info jstree-files-message">
All files you select will be imported into the current folder ignoring their folder structure.
</div>
<div class="alert alert-info jstree-folders-message" style="display:none;">
All files within the selected folders and their subfolders will be imported into the current folder.
</div>
<div style="margin-bottom:1em;">
<label class="checkbox-inline jstree-preserve-structure" style="display:none;">
<input class="preserve-checkbox" type="checkbox" value="preserve_directory_structure">
Preserve directory structure
</label>
<label class="checkbox-inline">
<input class="link-checkbox" type="checkbox" value="link_files">
Link files instead of copying
</label>
<label class="checkbox-inline">
<input class="posix-checkbox" type="checkbox" value="to_posix_lines" checked="checked">
Convert line endings to POSIX
</label>
<label class="checkbox-inline">
<input class="spacetab-checkbox" type="checkbox" value="space_to_tab">
Convert spaces to tabs
</label>
</div>
<button title="Select all files" type="button" class="button primary-button libimport-select-all">
Select all
</button>
<button title="Select no files" type="button" class="button primary-button libimport-select-none">
Unselect all
</button>
<hr /> <!-- append jstree object here -->
<div id="jstree_browser">
</div>
<hr />
<p>You can set extension type and genome for all imported datasets at once:</p>
<div>
Type: <span id="library_extension_select" class="library-extension-select" />
Genome: <span id="library_genome_select" class="library-genome-select" />
</div>
<br />
<div>
<label class="checkbox-inline tag-files">
Tag datasets based on file names
<input class="tag-files" type="checkbox" value="tag_using_filenames">
</label>
</div>
</div>`
);
},
templateImportPathModal: function () {
return _.template(
`<div id="file_browser_modal">
<div class="alert alert-info jstree-folders-message">
All files within the given folders and their subfolders will be imported into the current folder.
</div>
<div style="margin-bottom: 0.5em;">
<label class="checkbox-inline">
<input class="preserve-checkbox" type="checkbox" value="preserve_directory_structure">
Preserve directory structure
</label>
<label class="checkbox-inline">
<input class="link-checkbox" type="checkbox" value="link_files">
Link files instead of copying
</label>
<br>
<label class="checkbox-inline">
<input class="posix-checkbox" type="checkbox" value="to_posix_lines" checked="checked">
Convert line endings to POSIX
</label>
<label class="checkbox-inline">
<input class="spacetab-checkbox" type="checkbox" value="space_to_tab">
Convert spaces to tabs
</label>
</div>
<textarea id="import_paths" class="form-control" rows="5"
placeholder="Absolute paths (or paths relative to Galaxy root) separated by newline" autofocus>
</textarea>
<hr />
<p>You can set extension type and genome for all imported datasets at once:</p>
<div>
Type: <span id="library_extension_select" class="library-extension-select"></span>
Genome: <span id="library_genome_select" class="library-genome-select"></span>
</div>
<div>
<label class="checkbox-inline tag-files">
Tag datasets based on file names
<input class="tag-files" type="checkbox" value="tag_using_filenames">
</label>
</div>
</div>`
);
},
renderSelectBoxes: function () {
const Galaxy = getGalaxyInstance();
// This won't work properly unlesss we already have the data fetched.
// See this.fetchExtAndGenomes()
this.select_genome = new mod_select.View({
css: "library-genome-select",
data: this.list_genomes,
container: Galaxy.modal.$el.find("#library_genome_select"),
value: "?",
});
this.select_extension = new mod_select.View({
css: "library-extension-select",
data: this.list_extensions,
container: Galaxy.modal.$el.find("#library_extension_select"),
value: "auto",
});
},
/**
* Create modal for importing from given directory
* on Galaxy. Bind jQuery events.
*/
importFilesFromGalaxyFolderModal: function (options) {
var template_modal = this.templateBrowserModal();
const Galaxy = getGalaxyInstance();
this.modal = Galaxy.modal;
this.modal.show({
closing_events: true,
title: _l("Please select folders or files"),
body: template_modal({}),
buttons: {
Import: () => {
this.importFromJstreePath(this, options);
},
Close: () => {
Galaxy.modal.hide();
},
},
closing_callback: () => {
// TODO update table without fetching new content from the server
this.options.updateContent();
// Galaxy.libraries.library_router.navigate(`folders/${this.id}`, {
// trigger: true,
// });
},
});
$(".libimport-select-all").bind("click", () => {
$("#jstree_browser").jstree("check_all");
});
$(".libimport-select-none").bind("click", () => {
$("#jstree_browser").jstree("uncheck_all");
});
this.renderSelectBoxes();
options.disabled_jstree_element = "folders";
this.renderJstree(options);
$("input[type=radio]").change((event) => {
if (event.target.value === "jstree-disable-folders") {
options.disabled_jstree_element = "folders";
this.renderJstree(options);
$(".jstree-folders-message").hide();
$(".jstree-preserve-structure").hide();
$(".jstree-files-message").show();
} else if (event.target.value === "jstree-disable-files") {
$(".jstree-files-message").hide();
$(".jstree-folders-message").show();
$(".jstree-preserve-structure").show();
options.disabled_jstree_element = "files";
this.renderJstree(options);
}
});
},
/**
* Take the selected items from the jstree, create a request queue
* and send them one by one to the server for importing into
* the current folder.
*
* jstree.js has to be loaded before
* @see renderJstree
*/
importFromJstreePath: function (that, options) {
var all_nodes = $("#jstree_browser").jstree().get_selected(true);
// remove the disabled elements that could have been trigerred with the 'select all'
var selected_nodes = _.filter(all_nodes, (node) => node.state.disabled == false);
var preserve_dirs = this.modal.$el.find(".preserve-checkbox").is(":checked");
var link_data = this.modal.$el.find(".link-checkbox").is(":checked");
var space_to_tab = this.modal.$el.find(".spacetab-checkbox").is(":checked");
var to_posix_lines = this.modal.$el.find(".posix-checkbox").is(":checked");
var file_type = this.select_extension.value();
var dbkey = this.select_genome.value();
var tag_using_filenames = this.modal.$el.find(".tag-files").is(":checked");
var selection_type = selected_nodes[0].type;
var paths = [];
if (selected_nodes.length < 1) {
Toast.info("Please select some items first.");
} else {
this.modal.disableButton("Import");
for (let i = selected_nodes.length - 1; i >= 0; i--) {
if (selected_nodes[i].li_attr.full_path !== undefined) {
// should be always String
paths.push(`"${selected_nodes[i].li_attr.full_path}"`);
}
}
this.initChainCallControlAddingDatasets({
length: paths.length,
});
if (selection_type === "folder") {
const full_source = `${options.source}_folder`;
this.chainCallImportingFolders({
paths: paths,
preserve_dirs: preserve_dirs,
link_data: link_data,
space_to_tab: space_to_tab,
to_posix_lines: to_posix_lines,
source: full_source,
file_type: file_type,
dbkey: dbkey,
tag_using_filenames: tag_using_filenames,
});
} else if (selection_type === "file") {
const full_source = `${options.source}_file`;
this.chainCallImportingUserdirFiles({
paths: paths,
file_type: file_type,
dbkey: dbkey,
link_data: link_data,
space_to_tab: space_to_tab,
to_posix_lines: to_posix_lines,
source: full_source,
tag_using_filenames: tag_using_filenames,
});
}
}
},
/**
* Take the array of paths and create a request for each of them
* calling them in chain. Update the progress bar in between each.
* @param {array} paths paths relative to user folder on Galaxy
* @param {boolean} tag_using_filenames add tags to datasets using names of files
*/
chainCallImportingUserdirFiles: function (options) {
const Galaxy = getGalaxyInstance();
const popped_item = options.paths.pop();
if (typeof popped_item === "undefined") {
if (this.options.chain_call_control.failed_number === 0) {
Toast.success("Selected files imported into the current folder");
Galaxy.modal.hide();
} else {
Toast.error("An error occurred.");
}
return true;
}
const post_url = `${getAppRoot()}api/libraries/datasets`;
const post_data = {
encoded_folder_id: this.id,
source: options.source,
path: popped_item,
file_type: options.file_type,
link_data: options.link_data,
space_to_tab: options.space_to_tab,
to_posix_lines: options.to_posix_lines,
dbkey: options.dbkey,
tag_using_filenames: options.tag_using_filenames,
};
const promise = $.when($.post(post_url, post_data));
promise
.done((response) => {
updateProgress();
this.chainCallImportingUserdirFiles(options);
})
.fail(() => {
this.options.chain_call_control.failed_number += 1;
updateProgress();
this.chainCallImportingUserdirFiles(options);
});
},
/**
* Fetch the contents of user directory on Galaxy
* and render jstree component based on received
* data.
* @param {[type]} options [description]
*/
renderJstree: function (options) {
this.options = _.extend(this.options, options);
var target = options.source || "userdir";
var disabled_jstree_element = this.options.disabled_jstree_element;
this.jstree = new mod_library_model.Jstree();
this.jstree.url = `${this.jstree.urlRoot}?target=${target}&format=jstree&disable=${disabled_jstree_element}`;
this.jstree.fetch({
success: (model, response) => {
$("#jstree_browser").jstree("destroy");
$("#jstree_browser").jstree({
core: {
data: model,
},
plugins: ["types", "checkbox"],
types: {
folder: {
icon: "jstree-folder",
},
file: {
icon: "jstree-file",
},
},
checkbox: {
three_state: false,
},
});
},
error: (model, response) => {
if (typeof response.responseJSON !== "undefined") {
if (response.responseJSON.err_code === 404001) {
Toast.warning(response.responseJSON.err_msg);
getGalaxyInstance().modal.hide();
} else {
Toast.error(response.responseJSON.err_msg);
}
} else {
Toast.error("An error occurred.");
}
},
});
},
/**
* Create modal for importing from Galaxy path.
*/
importFilesFromPathModal: function () {
const Galaxy = getGalaxyInstance();
this.modal = Galaxy.modal;
var template_modal = this.templateImportPathModal();
this.modal.show({
closing_events: true,
title: _l("Please enter paths to import"),
body: template_modal({}),
buttons: {
Import: () => {
this.importFromPathsClicked(this);
},
Close: () => {
Galaxy.modal.hide();
},
},
closing_callback: () => {
// TODO update table without fetching new content from the server
this.options.updateContent();
// Galaxy.libraries.library_router.navigate(`folders/${this.id}`, {
// trigger: true,
// });
},
});
this.renderSelectBoxes();
},
/**
* Take the paths from the textarea, split it, create
* a request queue and call a function that starts sending
* one by one to be imported on the server.
*/
importFromPathsClicked: function () {
var preserve_dirs = this.modal.$el.find(".preserve-checkbox").is(":checked");
var link_data = this.modal.$el.find(".link-checkbox").is(":checked");
var space_to_tab = this.modal.$el.find(".spacetab-checkbox").is(":checked");
var to_posix_lines = this.modal.$el.find(".posix-checkbox").is(":checked");
var tag_using_filenames = this.modal.$el.find(".tag-files").is(":checked");
var file_type = this.select_extension.value();
var dbkey = this.select_genome.value();
var paths = $("textarea#import_paths").val();
var valid_paths = [];
if (!paths) {
Toast.info("Please enter a path relative to Galaxy root.");
} else {
this.modal.disableButton("Import");
paths = paths.split("\n");
for (let i = paths.length - 1; i >= 0; i--) {
var trimmed = paths[i].trim();
if (trimmed.length !== 0) {
valid_paths.push(trimmed);
}
}
this.initChainCallControlAddingDatasets({
length: valid_paths.length,
});
this.chainCallImportingFolders({
paths: valid_paths,
preserve_dirs: preserve_dirs,
link_data: link_data,
space_to_tab: space_to_tab,
to_posix_lines: to_posix_lines,
source: "admin_path",
file_type: file_type,
tag_using_filenames: tag_using_filenames,
dbkey: dbkey,
});
}
},
/**
* Take the array of paths and create a request for each of them
* calling them in series. Update the progress bar in between each.
* @param {array} paths paths relative to Galaxy root folder
* @param {boolean} preserve_dirs indicates whether to preserve folder structure
* @param {boolean} link_data copy files to Galaxy or link instead
* @param {boolean} to_posix_lines convert line endings to POSIX standard
* @param {boolean} space_to_tab convert spaces to tabs
* @param {str} source string representing what type of folder
* is the source of import
* @param {boolean} tag_using_filenames add tags to datasets using names of files
*/
chainCallImportingFolders: function (options) {
const Galaxy = getGalaxyInstance();
// TODO need to check which paths to call
const popped_item = options.paths.pop();
if (typeof popped_item == "undefined") {
if (this.options.chain_call_control.failed_number === 0) {
Toast.success("Selected folders and their contents imported into the current folder.");
Galaxy.modal.hide();
} else {
// TODO better error report
Toast.error("An error occurred.");
}
return true;
}
const post_url = `${getAppRoot()}api/libraries/datasets`;
const post_data = {
encoded_folder_id: this.id,
source: options.source,
path: popped_item,
preserve_dirs: options.preserve_dirs,
link_data: options.link_data,
to_posix_lines: options.to_posix_lines,
space_to_tab: options.space_to_tab,
file_type: options.file_type,
dbkey: options.dbkey,
tag_using_filenames: options.tag_using_filenames,
};
const promise = $.when($.post(post_url, post_data));
promise
.done((response) => {
updateProgress();
this.chainCallImportingFolders(options);
})
.fail(() => {
this.options.chain_call_control.failed_number += 1;
updateProgress();
this.chainCallImportingFolders(options);
});
},
templateAddingDatasetsProgressBar: function () {
return _.template(
`<div class="import_text">
Adding selected datasets to library folder <b><%= _.escape(folder_name) %></b>
</div>
<div class="progress">
<div class="progress-bar progress-bar-import" role="progressbar" aria-valuenow="0" aria-valuemin="0"
aria-valuemax="100" style="width: 00%;">
<span class="completion_span">0% Complete</span>
</div>
</div>`
);
},
initChainCallControlAddingDatasets: function (options) {
var template;
template = this.templateAddingDatasetsProgressBar();
this.modal.$el.find(".modal-body").html(
template({
folder_name: this.options.folder_name,
})
);
// var progress_bar_tmpl = this.templateAddingDatasetsProgressBar();
// this.modal.$el.find( '.modal-body' ).html( progress_bar_tmpl( { folder_name : this.options.folder_name } ) );
this.progress = 0;
this.progressStep = 100 / options.length;
this.options.chain_call_control.total_number = options.length;
this.options.chain_call_control.failed_number = 0;
},
/**
* Take the array of hdas and create a request for each.
* Call them in chain and update progress bar in between each.
* @param {array} hdas_set array of empty hda objects
*/
chainCallAddingHdas: function (hdas_set) {
const Galaxy = getGalaxyInstance();
this.added_hdas = new mod_library_model.Folder();
var popped_item = hdas_set.pop();
if (typeof popped_item == "undefined") {
if (this.options.chain_call_control.failed_number === 0) {
Toast.success("Selected datasets from history added to the folder");
} else if (this.options.chain_call_control.failed_number === this.options.chain_call_control.total_number) {
Toast.error("There was an error and no datasets were added to the folder.");
} else if (this.options.chain_call_control.failed_number < this.options.chain_call_control.total_number) {
Toast.warning("Some of the datasets could not be added to the folder");
}
this.options.updateContent();
Galaxy.modal.hide();
return this.added_hdas;
}
var promise = $.when(
popped_item.save({
from_hda_id: popped_item.get("from_hda_id"),
})
);
promise
.done((model) => {
// TODO add to lib
// Galaxy.libraries.folderListView.collection.add(model);
updateProgress();
this.chainCallAddingHdas(hdas_set);
})
.fail(() => {
this.options.chain_call_control.failed_number += 1;
updateProgress();
this.chainCallAddingHdas(hdas_set);
});
},
});
export default {
AddDatasets: AddDatasets,
};
+13 -5
View File
@@ -5,8 +5,8 @@
v-if="items"
v-model="selectedItem"
:deselect-label="null"
track-by="id"
label="text"
:track-by="trackBy"
:label="label"
:options="items"
:searchable="true"
:allow-empty="false"
@@ -54,9 +54,17 @@ export default {
default: null,
},
/** The initially selected item. */
currentItemId: {
currentItem: {
type: Object,
default: null,
},
label: {
type: String,
required: true,
default: "text",
},
trackBy: {
type: String,
default: "id",
},
},
data() {
@@ -72,7 +80,7 @@ export default {
},
watch: {
items: function () {
this.selectedItem = this.items.find((item) => item.id == this.currentItemId);
this.selectedItem = this.currentItem;
},
},
methods: {
-1
View File
@@ -31,7 +31,6 @@ $fa-font-path: "../../../node_modules/@fortawesome/fontawesome-free/webfonts/";
@import "library.scss";
@import "trackster.scss";
@import "toastr.scss";
@import "jstree.scss";
@import "tour.scss";
@import "flex.scss";
@import "charts.scss";
-990
View File
@@ -1,990 +0,0 @@
/* jsTree default theme */
.jstree-node,
.jstree-children,
.jstree-container-ul {
display: block;
margin: 0;
padding: 0;
list-style-type: none;
list-style-image: none;
}
.jstree-node {
white-space: nowrap;
}
.jstree-anchor {
display: inline-block;
color: black;
white-space: nowrap;
padding: 0 4px 0 1px;
margin: 0;
vertical-align: top;
}
.jstree-anchor:focus {
outline: 0;
}
.jstree-anchor,
.jstree-anchor:link,
.jstree-anchor:visited,
.jstree-anchor:hover,
.jstree-anchor:active {
text-decoration: none;
color: inherit;
}
.jstree-icon {
display: inline-block;
text-decoration: none;
margin: 0;
padding: 0;
vertical-align: top;
text-align: center;
}
.jstree-icon:empty {
display: inline-block;
text-decoration: none;
margin: 0;
padding: 0;
vertical-align: top;
text-align: center;
}
.jstree-ocl {
cursor: pointer;
}
.jstree-leaf > .jstree-ocl {
cursor: default;
}
.jstree .jstree-open > .jstree-children {
display: block;
}
.jstree .jstree-closed > .jstree-children,
.jstree .jstree-leaf > .jstree-children {
display: none;
}
.jstree-anchor > .jstree-themeicon {
margin-right: 2px;
}
.jstree-no-icons .jstree-themeicon,
.jstree-anchor > .jstree-themeicon-hidden {
display: none;
}
.jstree-rtl .jstree-anchor {
padding: 0 1px 0 4px;
}
.jstree-rtl .jstree-anchor > .jstree-themeicon {
margin-left: 2px;
margin-right: 0;
}
.jstree-rtl .jstree-node {
margin-left: 0;
}
.jstree-rtl .jstree-container-ul > .jstree-node {
margin-right: 0;
}
.jstree-wholerow-ul {
position: relative;
display: inline-block;
min-width: 100%;
}
.jstree-wholerow-ul .jstree-leaf > .jstree-ocl {
cursor: pointer;
}
.jstree-wholerow-ul .jstree-anchor,
.jstree-wholerow-ul .jstree-icon {
position: relative;
}
.jstree-wholerow-ul .jstree-wholerow {
width: 100%;
cursor: pointer;
position: absolute;
left: 0;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.vakata-context {
display: none;
}
.vakata-context,
.vakata-context ul {
margin: 0;
padding: 2px;
position: absolute;
background: #f5f5f5;
border: 1px solid #979797;
-moz-box-shadow: 5px 5px 4px -4px #666666;
-webkit-box-shadow: 2px 2px 2px #999999;
box-shadow: 2px 2px 2px #999999;
}
.vakata-context ul {
list-style: none;
left: 100%;
margin-top: -2.7em;
margin-left: -4px;
}
.vakata-context .vakata-context-right ul {
left: auto;
right: 100%;
margin-left: auto;
margin-right: -4px;
}
.vakata-context li {
list-style: none;
display: inline;
}
.vakata-context li > a {
display: block;
padding: 0 2em 0 2em;
text-decoration: none;
width: auto;
color: black;
white-space: nowrap;
line-height: 2.4em;
-moz-text-shadow: 1px 1px 0 white;
-webkit-text-shadow: 1px 1px 0 white;
text-shadow: 1px 1px 0 white;
-moz-border-radius: 1px;
-webkit-border-radius: 1px;
border-radius: 1px;
}
.vakata-context li > a:hover {
position: relative;
background-color: #e8eff7;
-moz-box-shadow: 0 0 2px #0a6aa1;
-webkit-box-shadow: 0 0 2px #0a6aa1;
box-shadow: 0 0 2px #0a6aa1;
}
.vakata-context li > a.vakata-context-parent {
background-image: url("data:image/gif;base64,R0lGODlhCwAHAIAAACgoKP///yH5BAEAAAEALAAAAAALAAcAAAIORI4JlrqN1oMSnmmZDQUAOw==");
background-position: right center;
background-repeat: no-repeat;
}
.vakata-context li > a:focus {
outline: 0;
}
.vakata-context .vakata-context-hover > a {
position: relative;
background-color: #e8eff7;
-moz-box-shadow: 0 0 2px #0a6aa1;
-webkit-box-shadow: 0 0 2px #0a6aa1;
box-shadow: 0 0 2px #0a6aa1;
}
.vakata-context .vakata-context-separator > a,
.vakata-context .vakata-context-separator > a:hover {
background: white;
border: 0;
border-top: 1px solid #e2e3e3;
height: 1px;
min-height: 1px;
max-height: 1px;
padding: 0;
margin: 0 0 0 2.4em;
border-left: 1px solid #e0e0e0;
-moz-text-shadow: 0 0 0 transparent;
-webkit-text-shadow: 0 0 0 transparent;
text-shadow: 0 0 0 transparent;
-moz-box-shadow: 0 0 0 transparent;
-webkit-box-shadow: 0 0 0 transparent;
box-shadow: 0 0 0 transparent;
-moz-border-radius: 0;
-webkit-border-radius: 0;
border-radius: 0;
}
.vakata-context .vakata-contextmenu-disabled a,
.vakata-context .vakata-contextmenu-disabled a:hover {
color: silver;
background-color: transparent;
border: 0;
box-shadow: 0 0 0;
}
.vakata-context li > a > i {
text-decoration: none;
display: inline-block;
width: 2.4em;
height: 2.4em;
background: transparent;
margin: 0 0 0 -2em;
vertical-align: top;
text-align: center;
line-height: 2.4em;
}
.vakata-context li > a > i:empty {
width: 2.4em;
line-height: 2.4em;
}
.vakata-context li > a .vakata-contextmenu-sep {
display: inline-block;
width: 1px;
height: 2.4em;
background: white;
margin: 0 0.5em 0 0;
border-left: 1px solid #e2e3e3;
}
.vakata-context .vakata-contextmenu-shortcut {
font-size: 0.8em;
color: silver;
opacity: 0.5;
display: none;
}
.vakata-context-rtl ul {
left: auto;
right: 100%;
margin-left: auto;
margin-right: -4px;
}
.vakata-context-rtl li > a.vakata-context-parent {
background-image: url("data:image/gif;base64,R0lGODlhCwAHAIAAACgoKP///yH5BAEAAAEALAAAAAALAAcAAAINjI+AC7rWHIsPtmoxLAA7");
background-position: left center;
background-repeat: no-repeat;
}
.vakata-context-rtl .vakata-context-separator > a {
margin: 0 2.4em 0 0;
border-left: 0;
border-right: 1px solid #e2e3e3;
}
.vakata-context-rtl .vakata-context-left ul {
right: auto;
left: 100%;
margin-left: -4px;
margin-right: auto;
}
.vakata-context-rtl li > a > i {
margin: 0 -2em 0 0;
}
.vakata-context-rtl li > a .vakata-contextmenu-sep {
margin: 0 0 0 0.5em;
border-left-color: white;
background: #e2e3e3;
}
#jstree-marker {
position: absolute;
top: 0;
left: 0;
margin: -5px 0 0 0;
padding: 0;
border-right: 0;
border-top: 5px solid transparent;
border-bottom: 5px solid transparent;
border-left: 5px solid;
width: 0;
height: 0;
font-size: 0;
line-height: 0;
}
#jstree-dnd {
line-height: 16px;
margin: 0;
padding: 4px;
}
#jstree-dnd .jstree-icon,
#jstree-dnd .jstree-copy {
display: inline-block;
text-decoration: none;
margin: 0 2px 0 0;
padding: 0;
width: 16px;
height: 16px;
}
#jstree-dnd .jstree-ok {
background: green;
}
#jstree-dnd .jstree-er {
background: red;
}
#jstree-dnd .jstree-copy {
margin: 0 2px 0 2px;
}
.jstree-default .jstree-node,
.jstree-default .jstree-icon {
background-repeat: no-repeat;
background-color: transparent;
}
.jstree-default .jstree-anchor,
.jstree-default .jstree-wholerow {
transition: background-color 0.15s, box-shadow 0.15s;
}
.jstree-default .jstree-hovered {
background: #e7f4f9;
border-radius: 2px;
box-shadow: inset 0 0 1px #ccc;
}
.jstree-default .jstree-clicked {
background: #beebff;
border-radius: 2px;
box-shadow: inset 0 0 1px #999;
}
.jstree-default .jstree-no-icons .jstree-anchor > .jstree-themeicon {
display: none;
}
.jstree-default .jstree-disabled {
background: transparent;
color: #666;
}
.jstree-default .jstree-disabled.jstree-hovered {
background: transparent;
box-shadow: none;
}
.jstree-default .jstree-disabled.jstree-clicked {
background: #efefef;
}
.jstree-default .jstree-disabled > .jstree-icon {
opacity: 0.8;
filter: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg'><filter id='jstree-grayscale'><feColorMatrix type='matrix' values='0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0 0 0 1 0'/></filter></svg>#jstree-grayscale");
/* Firefox 10+ */
filter: gray;
/* IE6-9 */
-webkit-filter: grayscale(100%);
/* Chrome 19+ & Safari 6+ */
}
.jstree-default .jstree-search {
font-style: italic;
color: #8b0000;
font-weight: bold;
}
.jstree-default .jstree-no-checkboxes .jstree-checkbox {
display: none !important;
}
.jstree-default.jstree-checkbox-no-clicked .jstree-clicked {
background: transparent;
box-shadow: none;
}
.jstree-default.jstree-checkbox-no-clicked .jstree-clicked.jstree-hovered {
background: #e7f4f9;
}
.jstree-default.jstree-checkbox-no-clicked > .jstree-wholerow-ul .jstree-wholerow-clicked {
background: transparent;
}
.jstree-default.jstree-checkbox-no-clicked > .jstree-wholerow-ul .jstree-wholerow-clicked.jstree-wholerow-hovered {
background: #e7f4f9;
}
#jstree-dnd.jstree-default .jstree-ok,
#jstree-dnd.jstree-default .jstree-er {
background-image: url("../../assets/images/jstree/32px.png");
background-repeat: no-repeat;
background-color: transparent;
}
#jstree-dnd.jstree-default i {
background: transparent;
width: 16px;
height: 16px;
}
#jstree-dnd.jstree-default .jstree-ok {
background-position: -9px -71px;
}
#jstree-dnd.jstree-default .jstree-er {
background-position: -39px -71px;
}
.jstree-default > .jstree-striped {
background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAkCAMAAAB/qqA+AAAABlBMVEUAAAAAAAClZ7nPAAAAAnRSTlMNAMM9s3UAAAAXSURBVHjajcEBAQAAAIKg/H/aCQZ70AUBjAATb6YPDgAAAABJRU5ErkJggg==")
left top repeat;
}
.jstree-default > .jstree-wholerow-ul .jstree-hovered,
.jstree-default > .jstree-wholerow-ul .jstree-clicked {
background: transparent;
box-shadow: none;
border-radius: 0;
}
.jstree-default .jstree-wholerow {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.jstree-default .jstree-wholerow-hovered {
background: #e7f4f9;
}
.jstree-default .jstree-wholerow-clicked {
background: #beebff;
background: -moz-linear-gradient(top, #beebff 0%, #a8e4ff 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #beebff), color-stop(100%, #a8e4ff));
background: -webkit-linear-gradient(top, #beebff 0%, #a8e4ff 100%);
background: -o-linear-gradient(top, #beebff 0%, #a8e4ff 100%);
background: -ms-linear-gradient(top, #beebff 0%, #a8e4ff 100%);
background: linear-gradient(to bottom, #beebff 0%, #a8e4ff 100%);
/*filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='$color1', endColorstr='$color2',GradientType=0 );*/
}
.jstree-default .jstree-node {
min-height: 24px;
line-height: 24px;
margin-left: 24px;
min-width: 24px;
}
.jstree-default .jstree-anchor {
line-height: 24px;
height: 24px;
}
.jstree-default .jstree-icon {
width: 24px;
height: 24px;
line-height: 24px;
}
.jstree-default .jstree-icon:empty {
width: 24px;
height: 24px;
line-height: 24px;
}
.jstree-default.jstree-rtl .jstree-node {
margin-right: 24px;
}
.jstree-default .jstree-wholerow {
height: 24px;
}
.jstree-default .jstree-node,
.jstree-default .jstree-icon {
background-image: url("../../assets/images/jstree/32px.png");
}
.jstree-default .jstree-node {
background-position: -292px -4px;
background-repeat: repeat-y;
}
.jstree-default .jstree-last {
background: transparent;
}
.jstree-default .jstree-open > .jstree-ocl {
background-position: -132px -4px;
}
.jstree-default .jstree-closed > .jstree-ocl {
background-position: -100px -4px;
}
.jstree-default .jstree-leaf > .jstree-ocl {
background-position: -68px -4px;
}
.jstree-default .jstree-themeicon {
background-position: -260px -4px;
}
.jstree-default > .jstree-no-dots .jstree-node,
.jstree-default > .jstree-no-dots .jstree-leaf > .jstree-ocl {
background: transparent;
}
.jstree-default > .jstree-no-dots .jstree-open > .jstree-ocl {
background-position: -36px -4px;
}
.jstree-default > .jstree-no-dots .jstree-closed > .jstree-ocl {
background-position: -4px -4px;
}
.jstree-default .jstree-disabled {
background: transparent;
}
.jstree-default .jstree-disabled.jstree-hovered {
background: transparent;
}
.jstree-default .jstree-disabled.jstree-clicked {
background: #efefef;
}
.jstree-default .jstree-checkbox {
background-position: -164px -4px;
}
.jstree-default .jstree-checkbox:hover {
background-position: -164px -36px;
}
.jstree-default.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox,
.jstree-default .jstree-checked > .jstree-checkbox {
background-position: -228px -4px;
}
.jstree-default.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox:hover,
.jstree-default .jstree-checked > .jstree-checkbox:hover {
background-position: -228px -36px;
}
.jstree-default .jstree-anchor > .jstree-undetermined {
background-position: -196px -4px;
}
.jstree-default .jstree-anchor > .jstree-undetermined:hover {
background-position: -196px -36px;
}
.jstree-default > .jstree-striped {
background-size: auto 48px;
}
.jstree-default.jstree-rtl .jstree-node {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg==");
background-position: 100% 1px;
background-repeat: repeat-y;
}
.jstree-default.jstree-rtl .jstree-last {
background: transparent;
}
.jstree-default.jstree-rtl .jstree-open > .jstree-ocl {
background-position: -132px -36px;
}
.jstree-default.jstree-rtl .jstree-closed > .jstree-ocl {
background-position: -100px -36px;
}
.jstree-default.jstree-rtl .jstree-leaf > .jstree-ocl {
background-position: -68px -36px;
}
.jstree-default.jstree-rtl > .jstree-no-dots .jstree-node,
.jstree-default.jstree-rtl > .jstree-no-dots .jstree-leaf > .jstree-ocl {
background: transparent;
}
.jstree-default.jstree-rtl > .jstree-no-dots .jstree-open > .jstree-ocl {
background-position: -36px -36px;
}
.jstree-default.jstree-rtl > .jstree-no-dots .jstree-closed > .jstree-ocl {
background-position: -4px -36px;
}
.jstree-default .jstree-themeicon-custom {
background-color: transparent;
background-image: none;
background-position: 0 0;
}
.jstree-default > .jstree-container-ul .jstree-loading > .jstree-ocl {
background: url("../../assets/images/jstree/throbber.gif") center center no-repeat;
}
.jstree-default .jstree-file {
background: url("../../assets/images/jstree/32px.png") -100px -68px no-repeat;
}
.jstree-default .jstree-folder {
background: url("../../assets/images/jstree/32px.png") -260px -4px no-repeat;
}
.jstree-default.jstree-rtl .jstree-node {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg==");
}
.jstree-default.jstree-rtl .jstree-last {
background: transparent;
}
.jstree-default-small .jstree-node {
min-height: 18px;
line-height: 18px;
margin-left: 18px;
min-width: 18px;
}
.jstree-default-small .jstree-anchor {
line-height: 18px;
height: 18px;
}
.jstree-default-small .jstree-icon {
width: 18px;
height: 18px;
line-height: 18px;
}
.jstree-default-small .jstree-icon:empty {
width: 18px;
height: 18px;
line-height: 18px;
}
.jstree-default-small.jstree-rtl .jstree-node {
margin-right: 18px;
}
.jstree-default-small .jstree-wholerow {
height: 18px;
}
.jstree-default-small .jstree-node,
.jstree-default-small .jstree-icon {
background-image: url("../../assets/images/jstree/32px.png");
}
.jstree-default-small .jstree-node {
background-position: -295px -7px;
background-repeat: repeat-y;
}
.jstree-default-small .jstree-last {
background: transparent;
}
.jstree-default-small .jstree-open > .jstree-ocl {
background-position: -135px -7px;
}
.jstree-default-small .jstree-closed > .jstree-ocl {
background-position: -103px -7px;
}
.jstree-default-small .jstree-leaf > .jstree-ocl {
background-position: -71px -7px;
}
.jstree-default-small .jstree-themeicon {
background-position: -263px -7px;
}
.jstree-default-small > .jstree-no-dots .jstree-node,
.jstree-default-small > .jstree-no-dots .jstree-leaf > .jstree-ocl {
background: transparent;
}
.jstree-default-small > .jstree-no-dots .jstree-open > .jstree-ocl {
background-position: -39px -7px;
}
.jstree-default-small > .jstree-no-dots .jstree-closed > .jstree-ocl {
background-position: -7px -7px;
}
.jstree-default-small .jstree-disabled {
background: transparent;
}
.jstree-default-small .jstree-disabled.jstree-hovered {
background: transparent;
}
.jstree-default-small .jstree-disabled.jstree-clicked {
background: #efefef;
}
.jstree-default-small .jstree-checkbox {
background-position: -167px -7px;
}
.jstree-default-small .jstree-checkbox:hover {
background-position: -167px -39px;
}
.jstree-default-small.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox,
.jstree-default-small .jstree-checked > .jstree-checkbox {
background-position: -231px -7px;
}
.jstree-default-small.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox:hover,
.jstree-default-small .jstree-checked > .jstree-checkbox:hover {
background-position: -231px -39px;
}
.jstree-default-small .jstree-anchor > .jstree-undetermined {
background-position: -199px -7px;
}
.jstree-default-small .jstree-anchor > .jstree-undetermined:hover {
background-position: -199px -39px;
}
.jstree-default-small > .jstree-striped {
background-size: auto 36px;
}
.jstree-default-small.jstree-rtl .jstree-node {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg==");
background-position: 100% 1px;
background-repeat: repeat-y;
}
.jstree-default-small.jstree-rtl .jstree-last {
background: transparent;
}
.jstree-default-small.jstree-rtl .jstree-open > .jstree-ocl {
background-position: -135px -39px;
}
.jstree-default-small.jstree-rtl .jstree-closed > .jstree-ocl {
background-position: -103px -39px;
}
.jstree-default-small.jstree-rtl .jstree-leaf > .jstree-ocl {
background-position: -71px -39px;
}
.jstree-default-small.jstree-rtl > .jstree-no-dots .jstree-node,
.jstree-default-small.jstree-rtl > .jstree-no-dots .jstree-leaf > .jstree-ocl {
background: transparent;
}
.jstree-default-small.jstree-rtl > .jstree-no-dots .jstree-open > .jstree-ocl {
background-position: -39px -39px;
}
.jstree-default-small.jstree-rtl > .jstree-no-dots .jstree-closed > .jstree-ocl {
background-position: -7px -39px;
}
.jstree-default-small .jstree-themeicon-custom {
background-color: transparent;
background-image: none;
background-position: 0 0;
}
.jstree-default-small > .jstree-container-ul .jstree-loading > .jstree-ocl {
background: url("../../assets/images/jstree/throbber.gif") center center no-repeat;
}
.jstree-default-small .jstree-file {
background: url("../../assets/images/jstree/32px.png") -103px -71px no-repeat;
}
.jstree-default-small .jstree-folder {
background: url("../../assets/images/jstree/32px.png") -263px -7px no-repeat;
}
.jstree-default-small.jstree-rtl .jstree-node {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAACAQMAAABv1h6PAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMHBgAAiABBI4gz9AAAAABJRU5ErkJggg==");
}
.jstree-default-small.jstree-rtl .jstree-last {
background: transparent;
}
.jstree-default-large .jstree-node {
min-height: 32px;
line-height: 32px;
margin-left: 32px;
min-width: 32px;
}
.jstree-default-large .jstree-anchor {
line-height: 32px;
height: 32px;
}
.jstree-default-large .jstree-icon {
width: 32px;
height: 32px;
line-height: 32px;
}
.jstree-default-large .jstree-icon:empty {
width: 32px;
height: 32px;
line-height: 32px;
}
.jstree-default-large.jstree-rtl .jstree-node {
margin-right: 32px;
}
.jstree-default-large .jstree-wholerow {
height: 32px;
}
.jstree-default-large .jstree-node,
.jstree-default-large .jstree-icon {
background-image: url("../../assets/images/jstree/32px.png");
}
.jstree-default-large .jstree-node {
background-position: -288px 0px;
background-repeat: repeat-y;
}
.jstree-default-large .jstree-last {
background: transparent;
}
.jstree-default-large .jstree-open > .jstree-ocl {
background-position: -128px 0px;
}
.jstree-default-large .jstree-closed > .jstree-ocl {
background-position: -96px 0px;
}
.jstree-default-large .jstree-leaf > .jstree-ocl {
background-position: -64px 0px;
}
.jstree-default-large .jstree-themeicon {
background-position: -256px 0px;
}
.jstree-default-large > .jstree-no-dots .jstree-node,
.jstree-default-large > .jstree-no-dots .jstree-leaf > .jstree-ocl {
background: transparent;
}
.jstree-default-large > .jstree-no-dots .jstree-open > .jstree-ocl {
background-position: -32px 0px;
}
.jstree-default-large > .jstree-no-dots .jstree-closed > .jstree-ocl {
background-position: 0px 0px;
}
.jstree-default-large .jstree-disabled {
background: transparent;
}
.jstree-default-large .jstree-disabled.jstree-hovered {
background: transparent;
}
.jstree-default-large .jstree-disabled.jstree-clicked {
background: #efefef;
}
.jstree-default-large .jstree-checkbox {
background-position: -160px 0px;
}
.jstree-default-large .jstree-checkbox:hover {
background-position: -160px -32px;
}
.jstree-default-large.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox,
.jstree-default-large .jstree-checked > .jstree-checkbox {
background-position: -224px 0px;
}
.jstree-default-large.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox:hover,
.jstree-default-large .jstree-checked > .jstree-checkbox:hover {
background-position: -224px -32px;
}
.jstree-default-large .jstree-anchor > .jstree-undetermined {
background-position: -192px 0px;
}
.jstree-default-large .jstree-anchor > .jstree-undetermined:hover {
background-position: -192px -32px;
}
.jstree-default-large > .jstree-striped {
background-size: auto 64px;
}
.jstree-default-large.jstree-rtl .jstree-node {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg==");
background-position: 100% 1px;
background-repeat: repeat-y;
}
.jstree-default-large.jstree-rtl .jstree-last {
background: transparent;
}
.jstree-default-large.jstree-rtl .jstree-open > .jstree-ocl {
background-position: -128px -32px;
}
.jstree-default-large.jstree-rtl .jstree-closed > .jstree-ocl {
background-position: -96px -32px;
}
.jstree-default-large.jstree-rtl .jstree-leaf > .jstree-ocl {
background-position: -64px -32px;
}
.jstree-default-large.jstree-rtl > .jstree-no-dots .jstree-node,
.jstree-default-large.jstree-rtl > .jstree-no-dots .jstree-leaf > .jstree-ocl {
background: transparent;
}
.jstree-default-large.jstree-rtl > .jstree-no-dots .jstree-open > .jstree-ocl {
background-position: -32px -32px;
}
.jstree-default-large.jstree-rtl > .jstree-no-dots .jstree-closed > .jstree-ocl {
background-position: 0px -32px;
}
.jstree-default-large .jstree-themeicon-custom {
background-color: transparent;
background-image: none;
background-position: 0 0;
}
.jstree-default-large > .jstree-container-ul .jstree-loading > .jstree-ocl {
background: url("../../assets/images/jstree/throbber.gif") center center no-repeat;
}
.jstree-default-large .jstree-file {
background: url("../../assets/images/jstree/32px.png") -96px -64px no-repeat;
}
.jstree-default-large .jstree-folder {
background: url("../../assets/images/jstree/32px.png") -256px 0px no-repeat;
}
.jstree-default-large.jstree-rtl .jstree-node {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAACAQMAAAAD0EyKAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjgIIGBgABCgCBvVLXcAAAAABJRU5ErkJggg==");
}
.jstree-default-large.jstree-rtl .jstree-last {
background: transparent;
}
@media (max-width: 768px) {
#jstree-dnd.jstree-dnd-responsive {
line-height: 40px;
font-weight: bold;
font-size: 1.1em;
text-shadow: 1px 1px white;
}
#jstree-dnd.jstree-dnd-responsive > i {
background: transparent;
width: 40px;
height: 40px;
}
#jstree-dnd.jstree-dnd-responsive > .jstree-ok {
background-image: url("../../assets/images/jstree/40px.png");
background-position: 0 -200px;
background-size: 120px 240px;
}
#jstree-dnd.jstree-dnd-responsive > .jstree-er {
background-image: url("../../assets/images/jstree/40px.png");
background-position: -40px -200px;
background-size: 120px 240px;
}
#jstree-marker.jstree-dnd-responsive {
border-left-width: 10px;
border-top-width: 10px;
border-bottom-width: 10px;
margin-top: -10px;
}
}
@media (max-width: 768px) {
.jstree-default-responsive {
/*
.jstree-open > .jstree-ocl,
.jstree-closed > .jstree-ocl { border-radius:20px; background-color:white; }
*/
}
.jstree-default-responsive .jstree-icon {
background-image: url("../../assets/images/jstree/40px.png");
}
.jstree-default-responsive .jstree-node,
.jstree-default-responsive .jstree-leaf > .jstree-ocl {
background: transparent;
}
.jstree-default-responsive .jstree-node {
min-height: 40px;
line-height: 40px;
margin-left: 40px;
min-width: 40px;
white-space: nowrap;
}
.jstree-default-responsive .jstree-anchor {
line-height: 40px;
height: 40px;
}
.jstree-default-responsive .jstree-icon,
.jstree-default-responsive .jstree-icon:empty {
width: 40px;
height: 40px;
line-height: 40px;
}
.jstree-default-responsive > .jstree-container-ul > .jstree-node {
margin-left: 0;
}
.jstree-default-responsive.jstree-rtl .jstree-node {
margin-left: 0;
margin-right: 40px;
}
.jstree-default-responsive.jstree-rtl .jstree-container-ul > .jstree-node {
margin-right: 0;
}
.jstree-default-responsive .jstree-ocl,
.jstree-default-responsive .jstree-themeicon,
.jstree-default-responsive .jstree-checkbox {
background-size: 120px 240px;
}
.jstree-default-responsive .jstree-leaf > .jstree-ocl {
background: transparent;
}
.jstree-default-responsive .jstree-open > .jstree-ocl {
background-position: 0 0px !important;
}
.jstree-default-responsive .jstree-closed > .jstree-ocl {
background-position: 0 -40px !important;
}
.jstree-default-responsive.jstree-rtl .jstree-closed > .jstree-ocl {
background-position: -40px 0px !important;
}
.jstree-default-responsive .jstree-themeicon {
background-position: -40px -40px;
}
.jstree-default-responsive .jstree-checkbox,
.jstree-default-responsive .jstree-checkbox:hover {
background-position: -40px -80px;
}
.jstree-default-responsive.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox,
.jstree-default-responsive.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox:hover,
.jstree-default-responsive .jstree-checked > .jstree-checkbox,
.jstree-default-responsive .jstree-checked > .jstree-checkbox:hover {
background-position: 0 -80px;
}
.jstree-default-responsive .jstree-anchor > .jstree-undetermined,
.jstree-default-responsive .jstree-anchor > .jstree-undetermined:hover {
background-position: 0 -120px;
}
.jstree-default-responsive .jstree-anchor {
font-weight: bold;
font-size: 1.1em;
text-shadow: 1px 1px white;
}
.jstree-default-responsive > .jstree-striped {
background: transparent;
}
.jstree-default-responsive .jstree-wholerow {
border-top: 1px solid rgba(255, 255, 255, 0.7);
border-bottom: 1px solid rgba(64, 64, 64, 0.2);
background: #ebebeb;
height: 40px;
}
.jstree-default-responsive .jstree-wholerow-hovered {
background: #e7f4f9;
}
.jstree-default-responsive .jstree-wholerow-clicked {
background: #beebff;
}
.jstree-default-responsive .jstree-children .jstree-last > .jstree-wholerow {
box-shadow: inset 0 -6px 3px -5px #666666;
}
.jstree-default-responsive .jstree-children .jstree-open > .jstree-wholerow {
box-shadow: inset 0 6px 3px -5px #666666;
border-top: 0;
}
.jstree-default-responsive .jstree-children .jstree-open + .jstree-open {
box-shadow: none;
}
.jstree-default-responsive .jstree-node,
.jstree-default-responsive .jstree-icon,
.jstree-default-responsive .jstree-node > .jstree-ocl,
.jstree-default-responsive .jstree-themeicon,
.jstree-default-responsive .jstree-checkbox {
background-image: url("../../assets/images/jstree/40px.png");
background-size: 120px 240px;
}
.jstree-default-responsive .jstree-node {
background-position: -80px 0;
background-repeat: repeat-y;
}
.jstree-default-responsive .jstree-last {
background: transparent;
}
.jstree-default-responsive .jstree-leaf > .jstree-ocl {
background-position: -40px -120px;
}
.jstree-default-responsive .jstree-last > .jstree-ocl {
background-position: -40px -160px;
}
.jstree-default-responsive .jstree-themeicon-custom {
background-color: transparent;
background-image: none;
background-position: 0 0;
}
.jstree-default-responsive .jstree-file {
background: url("../../assets/images/jstree/40px.png") 0 -160px no-repeat;
background-size: 120px 240px;
}
.jstree-default-responsive .jstree-folder {
background: url("../../assets/images/jstree/40px.png") -40px -40px no-repeat;
background-size: 120px 240px;
}
}
.jstree-default > .jstree-container-ul > .jstree-node {
margin-left: 0;
margin-right: 0;
}
+14 -5
View File
@@ -1032,7 +1032,7 @@ libraries:
add_to_history_collection: '.add-to-history-collection'
# TODO: Most of these aren't very good selectors but the same DOM elements
# are reused without adding specific classes, IDs, or roles to anything.
import_modal: '.modal'
import_modal: '.directory-dataset-picker'
import_datasets_from_history_modal: '.selection-dialog-modal'
import_datasets_from_history_modal_history_search: '.selection-dialog-modal .search-query'
import_datasets_from_history_modal_dataset_search: '.selection-dialog-modal input[placeholder="search datasets"]'
@@ -1041,8 +1041,15 @@ libraries:
import_datasets_from_history_modal_select_list_item_by_index: '.selection-dialog-modal table tbody tr[aria-rowindex="${row_index}"] td[aria-colindex="1"]'
import_datasets_from_history_modal_ok: '.selection-dialog-modal [data-description="selection dialog ok"]'
import_datasets_from_history_modal_cancel: '.selection-dialog-modal [data-description="selection dialog cancel"]'
import_datasets_ok_button: '.modal-footer .buttons #button-0'
import_datasets_cancel_button: '.modal-footer .buttons #button-1'
add_to_history_as_collection:
type: xpath
selector: '//button[contains(text(), "Continue")]'
import_datasets_ok_button:
type: xpath
selector: '//button[contains(text(), "Import")]'
import_datasets_cancel_button:
type: xpath
selector: '//button[contains(text(), "Close")]'
export_to_history_options: '#library-collection-type-select'
export_to_history_paired_option: 'option[value="${collection_option}"]'
export_to_history_collection_name: '.collection-name'
@@ -1066,8 +1073,10 @@ libraries:
download_button: '#download-btn'
delete_btn: '.toolbtn-bulk-delete'
toast_msg: '.b-toast'
toast_warning: '.b-toast-warning'
select_import_dir_item: 'li[full_path="${name}"] .jstree-anchor'
alert_not_exists_user_import_dir:
type: xpath
selector: //div[contains(@class, "alert-danger") and contains(normalize-space(text()), "Your user import directory does not exist")]
select_import_dir_item: '[for="drilldown-option-${name}"]'
import_dir_btn:
type: xpath
selector: '//button[contains(text(), "Import")]'
@@ -63,7 +63,7 @@ class TestLibraryToCollections(SeleniumTestCase, UsesLibraryAssertions):
collection_option=collection_option
).wait_for_and_click()
self.screenshot(f"libraries_to_collection_landing_is_new_history={is_new_history}")
self.components.libraries.folder.import_datasets_ok_button.wait_for_and_click()
self.components.libraries.folder.add_to_history_as_collection.wait_for_and_click()
self.build_collection_and_assert()
if is_new_history:
assert self.history_panel_name_element().text == random_name
@@ -78,7 +78,7 @@ class TestLibraryToCollections(SeleniumTestCase, UsesLibraryAssertions):
collection_option="list:paired"
).wait_for_and_click()
self.screenshot(f"test_export_pairs_list={is_new_history}")
self.components.libraries.folder.import_datasets_ok_button.wait_for_and_click()
self.components.libraries.folder.add_to_history_as_collection.wait_for_and_click()
self.components.libraries.folder.clear_filters.wait_for_and_click()
self.collection_builder_click_paired_item("forward", 0)
self.collection_builder_click_paired_item("reverse", 1)
@@ -57,7 +57,7 @@ class TestUserLibraryImport(SeleniumIntegrationTestCase):
self.wait_for_selector_absent_or_hidden(self.modal_body_selector())
# assert 'user import folder was not created' warning
self.components.libraries.folder.toast_warning.wait_for_visible()
self.components.libraries.folder.alert_not_exists_user_import_dir.wait_for_visible()
@selenium_test
def test_user_library_dataset_permissions(self):