mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Merge pull request #20342 from dannon/tab-icons
[25.0] DatasetView and Card Polish
This commit is contained in:
@@ -1,3 +1,26 @@
|
||||
import type { components } from "@/api";
|
||||
import { GalaxyApi } from "@/api";
|
||||
import { rethrowSimple } from "@/utils/simple-error";
|
||||
|
||||
export type CompositeFileInfo = components["schemas"]["CompositeFileInfo"];
|
||||
export type DatatypeDetails = components["schemas"]["DatatypeDetails"];
|
||||
|
||||
/**
|
||||
* Get details about a specific datatype
|
||||
*/
|
||||
export async function fetchDatatypeDetails(extension: string): Promise<DatatypeDetails> {
|
||||
try {
|
||||
const { GET } = GalaxyApi();
|
||||
const { data } = await GET("/api/datatypes/{datatype}", {
|
||||
params: {
|
||||
path: { datatype: extension },
|
||||
},
|
||||
});
|
||||
if (!data) {
|
||||
throw new Error(`Failed to fetch datatype details for ${extension}`);
|
||||
}
|
||||
return data as DatatypeDetails;
|
||||
} catch (error) {
|
||||
rethrowSimple(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9301,6 +9301,11 @@ export interface components {
|
||||
* @description The URL to a detailed description for this datatype
|
||||
*/
|
||||
description_url: string | null;
|
||||
/**
|
||||
* Display behavior
|
||||
* @description How this datatype behaves when displayed with preview=True: 'inline' (can be displayed in browser) or 'download' (triggers download)
|
||||
*/
|
||||
display_behavior?: string | null;
|
||||
/**
|
||||
* Display in upload
|
||||
* @description If True, the associated file extension will be displayed in the `File Format` select list in the `Upload File from your computer` tool in the `Get Data` tool section of the tool panel
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import { BNav, BNavItem } from "bootstrap-vue";
|
||||
import { faBug, faChartBar, faEye, faFileAlt, faInfoCircle, faPen } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||
import { BLink, BNav, BNavItem } from "bootstrap-vue";
|
||||
import { computed, ref, watch } from "vue";
|
||||
|
||||
import { usePersistentToggle } from "@/composables/persistentToggle";
|
||||
import { useDatasetStore } from "@/stores/datasetStore";
|
||||
import { useDatatypeVisualizationsStore } from "@/stores/datatypeVisualizationsStore";
|
||||
import { useDatatypeStore } from "@/stores/datatypeStore";
|
||||
import { bytesToString } from "@/utils/utils";
|
||||
|
||||
import DatasetError from "../DatasetInformation/DatasetError.vue";
|
||||
import LoadingSpan from "../LoadingSpan.vue";
|
||||
@@ -17,12 +20,12 @@ import VisualizationFrame from "@/components/Visualizations/VisualizationFrame.v
|
||||
import CenterFrame from "@/entry/analysis/modules/CenterFrame.vue";
|
||||
|
||||
const datasetStore = useDatasetStore();
|
||||
const datatypeVisualizationsStore = useDatatypeVisualizationsStore();
|
||||
const datatypeStore = useDatatypeStore();
|
||||
const { toggled: headerCollapsed, toggle: toggleHeaderCollapse } = usePersistentToggle("dataset-header-collapsed");
|
||||
|
||||
interface Props {
|
||||
datasetId: string;
|
||||
tab?: "details" | "edit" | "error" | "preview" | "visualize";
|
||||
tab?: "details" | "edit" | "error" | "preview" | "raw" | "visualize";
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
@@ -30,37 +33,40 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
});
|
||||
|
||||
const iframeLoading = ref(true);
|
||||
const preferredVisualization = ref<string>();
|
||||
|
||||
const dataset = computed(() => datasetStore.getDataset(props.datasetId));
|
||||
const headerState = computed(() => (headerCollapsed.value ? "closed" : "open"));
|
||||
const isLoading = computed(() => datasetStore.isLoadingDataset(props.datasetId));
|
||||
|
||||
// Track datatype loading state
|
||||
const isDatatypeLoading = ref(false);
|
||||
|
||||
// Consolidated loading state
|
||||
const isLoading = computed(() => {
|
||||
return datasetStore.isLoadingDataset(props.datasetId) || isDatatypeLoading.value;
|
||||
});
|
||||
|
||||
const showError = computed(
|
||||
() => dataset.value && (dataset.value.state === "error" || dataset.value.state === "failed_metadata")
|
||||
);
|
||||
const isAutoDownloadType = computed(
|
||||
() => dataset.value && datatypeStore.isDatatypeAutoDownload(dataset.value.file_ext)
|
||||
);
|
||||
const preferredVisualization = computed(
|
||||
() => dataset.value && datatypeStore.getPreferredVisualization(dataset.value.file_ext)
|
||||
);
|
||||
|
||||
// Check if the dataset has a preferred visualization by datatype
|
||||
async function checkPreferredVisualization() {
|
||||
if (dataset.value && dataset.value.file_ext) {
|
||||
try {
|
||||
const mapping = await datatypeVisualizationsStore.getPreferredVisualizationForDatatype(
|
||||
dataset.value.file_ext
|
||||
);
|
||||
if (mapping) {
|
||||
preferredVisualization.value = mapping.visualization;
|
||||
} else {
|
||||
preferredVisualization.value = undefined;
|
||||
}
|
||||
} catch (error) {
|
||||
preferredVisualization.value = undefined;
|
||||
// Watch for changes to the dataset to fetch datatype info
|
||||
watch(
|
||||
() => dataset.value?.file_ext,
|
||||
async () => {
|
||||
if (dataset.value && dataset.value.file_ext) {
|
||||
isDatatypeLoading.value = true;
|
||||
await datatypeStore.fetchDatatypeDetails(dataset.value.file_ext);
|
||||
isDatatypeLoading.value = false;
|
||||
}
|
||||
} else {
|
||||
preferredVisualization.value = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Watch for changes to the dataset to check for preferred visualizations
|
||||
watch(() => dataset.value?.file_ext, checkPreferredVisualization, { immediate: true });
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -76,7 +82,8 @@ watch(() => dataset.value?.file_ext, checkPreferredVisualization, { immediate: t
|
||||
class="flex-grow-1"
|
||||
:collapse="headerState"
|
||||
@click="toggleHeaderCollapse">
|
||||
{{ dataset?.hid }}: <span class="font-weight-bold">{{ dataset?.name }}</span>
|
||||
<span class="dataset-hid">{{ dataset?.hid }}:</span>
|
||||
<span class="dataset-name font-weight-bold">{{ dataset?.name }}</span>
|
||||
<span class="dataset-state-header">
|
||||
<DatasetState :dataset-id="datasetId" />
|
||||
</span>
|
||||
@@ -100,29 +107,52 @@ watch(() => dataset.value?.file_ext, checkPreferredVisualization, { immediate: t
|
||||
{{ dataset.genome_build }}
|
||||
</BLink>
|
||||
</span>
|
||||
<div v-if="dataset.misc_info" class="info">
|
||||
<span class="value">{{ dataset.misc_info }}</span>
|
||||
</div>
|
||||
<span v-if="dataset.file_size" class="filesize">
|
||||
<span v-localize class="prompt">size</span>
|
||||
<span class="value font-weight-bold" v-html="bytesToString(dataset.file_size, false)" />
|
||||
</span>
|
||||
</div>
|
||||
</transition>
|
||||
</header>
|
||||
<BNav pills class="my-2 p-2 bg-light border-bottom">
|
||||
<BNavItem title="Preview" :active="tab === 'preview'" :to="`/datasets/${datasetId}/preview`">
|
||||
Preview
|
||||
<BNavItem
|
||||
title="View a preview of the dataset contents"
|
||||
:active="tab === 'preview'"
|
||||
:to="`/datasets/${datasetId}/preview`">
|
||||
<FontAwesomeIcon :icon="faEye" class="mr-1" /> Preview
|
||||
</BNavItem>
|
||||
<BNavItem
|
||||
v-if="preferredVisualization"
|
||||
title="View raw dataset contents"
|
||||
:active="tab === 'raw'"
|
||||
:to="`/datasets/${datasetId}/raw`">
|
||||
<FontAwesomeIcon :icon="faFileAlt" class="mr-1" /> Raw
|
||||
</BNavItem>
|
||||
<BNavItem
|
||||
v-if="!showError"
|
||||
title="Visualize"
|
||||
title="Explore available visualizations for this dataset"
|
||||
:active="tab === 'visualize'"
|
||||
:to="`/datasets/${datasetId}/visualize`">
|
||||
Visualize
|
||||
<FontAwesomeIcon :icon="faChartBar" class="mr-1" /> Visualize
|
||||
</BNavItem>
|
||||
<BNavItem title="Details" :active="tab === 'details'" :to="`/datasets/${datasetId}/details`">
|
||||
Details
|
||||
<BNavItem
|
||||
title="View detailed information about this dataset"
|
||||
:active="tab === 'details'"
|
||||
:to="`/datasets/${datasetId}/details`">
|
||||
<FontAwesomeIcon :icon="faInfoCircle" class="mr-1" /> Details
|
||||
</BNavItem>
|
||||
<BNavItem title="Edit" :active="tab === 'edit'" :to="`/datasets/${datasetId}/edit`">Edit</BNavItem>
|
||||
<BNavItem v-if="showError" title="Error" :active="tab === 'error'" :to="`/datasets/${datasetId}/error`">
|
||||
Error
|
||||
<BNavItem
|
||||
title="Edit dataset attributes and metadata"
|
||||
:active="tab === 'edit'"
|
||||
:to="`/datasets/${datasetId}/edit`">
|
||||
<FontAwesomeIcon :icon="faPen" class="mr-1" /> Edit
|
||||
</BNavItem>
|
||||
<BNavItem
|
||||
v-if="showError"
|
||||
title="View error information for this dataset"
|
||||
:active="tab === 'error'"
|
||||
:to="`/datasets/${datasetId}/error`">
|
||||
<FontAwesomeIcon :icon="faBug" class="mr-1" /> Error
|
||||
</BNavItem>
|
||||
</BNav>
|
||||
<div v-if="tab === 'preview'" class="h-100">
|
||||
@@ -131,22 +161,49 @@ watch(() => dataset.value?.file_ext, checkPreferredVisualization, { immediate: t
|
||||
:dataset-id="datasetId"
|
||||
:visualization="preferredVisualization"
|
||||
@load="iframeLoading = false" />
|
||||
<div v-else-if="isAutoDownloadType" class="auto-download-message p-4">
|
||||
<div class="alert alert-info">
|
||||
<h4>Download Required</h4>
|
||||
<p>This file type ({{ dataset.file_ext }}) will download automatically when accessed directly.</p>
|
||||
<p>File size: <strong v-html="bytesToString(dataset.file_size || 0, false)" /></p>
|
||||
<a :href="`/datasets/${datasetId}/display`" class="btn btn-primary mt-2" download>
|
||||
<FontAwesomeIcon :icon="faFileAlt" class="mr-1" /> Download File
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<CenterFrame
|
||||
v-else
|
||||
:src="`/datasets/${datasetId}/display/?preview=True`"
|
||||
:is_preview="true"
|
||||
:is-preview="true"
|
||||
@load="iframeLoading = false" />
|
||||
</div>
|
||||
<div v-else-if="tab === 'visualize'" class="d-flex flex-column overflow-hidden overflow-y">
|
||||
<div v-else-if="tab === 'raw'" class="h-100">
|
||||
<div v-if="isAutoDownloadType" class="auto-download-message p-4">
|
||||
<div class="alert alert-info">
|
||||
<h4>Download Required</h4>
|
||||
<p>This file type ({{ dataset.file_ext }}) will download automatically when accessed directly.</p>
|
||||
<p>File size: <strong v-html="bytesToString(dataset.file_size || 0, false)" /></p>
|
||||
<a :href="`/datasets/${datasetId}/display`" class="btn btn-primary mt-2" download>
|
||||
<FontAwesomeIcon :icon="faFileAlt" class="mr-1" /> Download File
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<CenterFrame
|
||||
v-else
|
||||
:src="`/datasets/${datasetId}/display/?preview=True`"
|
||||
:is-preview="true"
|
||||
@load="iframeLoading = false" />
|
||||
</div>
|
||||
<div v-else-if="tab === 'visualize'" class="tab-content-panel">
|
||||
<VisualizationsList :dataset-id="datasetId" />
|
||||
</div>
|
||||
<div v-else-if="tab === 'edit'" class="d-flex flex-column overflow-hidden overflow-y mt-2">
|
||||
<div v-else-if="tab === 'edit'" class="tab-content-panel mt-2">
|
||||
<DatasetAttributes :dataset-id="datasetId" />
|
||||
</div>
|
||||
<div v-else-if="tab === 'details'" class="d-flex flex-column overflow-hidden overflow-y mt-2">
|
||||
<div v-else-if="tab === 'details'" class="tab-content-panel mt-2">
|
||||
<DatasetDetails :dataset-id="datasetId" />
|
||||
</div>
|
||||
<div v-else-if="tab === 'error'" class="d-flex flex-column overflow-hidden overflow-y mt-2">
|
||||
<div v-else-if="tab === 'error'" class="tab-content-panel mt-2">
|
||||
<DatasetError :dataset-id="datasetId" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -161,6 +218,21 @@ watch(() => dataset.value?.file_ext, checkPreferredVisualization, { immediate: t
|
||||
opacity: 1;
|
||||
transition: all 0.25s ease;
|
||||
overflow: hidden;
|
||||
|
||||
.datatype,
|
||||
.dbkey,
|
||||
.filesize {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.prompt {
|
||||
color: $text-muted;
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.blurb {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.header-enter, /* change to header-enter-from with Vue 3 */
|
||||
@@ -172,8 +244,41 @@ watch(() => dataset.value?.file_ext, checkPreferredVisualization, { immediate: t
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.dataset-hid,
|
||||
.dataset-state-header {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.dataset-hid {
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.dataset-name {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.dataset-state-header {
|
||||
font-size: $h5-font-size;
|
||||
vertical-align: middle;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.tab-content-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.auto-download-message {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
|
||||
.alert {
|
||||
max-width: 600px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { faStop } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||
import axios from "axios";
|
||||
import { BButton, BDropdown } from "bootstrap-vue";
|
||||
import { faStop } from "font-awesome-6";
|
||||
//@ts-ignore deprecated package without types (vue 2, remove this comment on vue 3 migration)
|
||||
import { ScanEye } from "lucide-vue";
|
||||
import { computed, type Ref, ref } from "vue";
|
||||
|
||||
import { getAppRoot } from "@/onload/loadConfig";
|
||||
@@ -112,14 +114,14 @@ function onDisplay($event: MouseEvent) {
|
||||
<BButton
|
||||
v-if="isDataset"
|
||||
v-b-tooltip.hover
|
||||
title="Display"
|
||||
title="View"
|
||||
tabindex="0"
|
||||
class="display-btn px-1"
|
||||
size="sm"
|
||||
variant="link"
|
||||
:href="displayUrl"
|
||||
@click.prevent.stop="onDisplay($event)">
|
||||
<icon icon="eye" />
|
||||
<ScanEye absolute-stroke-width :size="16" />
|
||||
</BButton>
|
||||
<BButton
|
||||
v-if="writable && isHistoryItem"
|
||||
|
||||
@@ -39,12 +39,24 @@ const showDownloads = computed(() => {
|
||||
const showError = computed(() => {
|
||||
return props.item.state === "error" || props.item.state === "failed_metadata";
|
||||
});
|
||||
const showInfo = computed(() => {
|
||||
return props.item.state !== "noPermission";
|
||||
});
|
||||
const showVisualizations = computed(() => {
|
||||
return !props.item.purged && ["ok", "failed_metadata", "error"].includes(props.item.state);
|
||||
});
|
||||
const showRerun = computed(() => {
|
||||
return props.item.accessible && props.item.rerunnable && props.item.creating_job && props.item.state != "upload";
|
||||
});
|
||||
const reportErrorUrl = computed(() => {
|
||||
return prependPath(props.itemUrls.reportError!);
|
||||
});
|
||||
const showDetailsUrl = computed(() => {
|
||||
return prependPath(props.itemUrls.showDetails!);
|
||||
});
|
||||
const visualizeUrl = computed(() => {
|
||||
return prependPath(props.itemUrls.visualize!);
|
||||
});
|
||||
const rerunUrl = computed(() => {
|
||||
return prependPath(props.itemUrls.rerun!);
|
||||
});
|
||||
@@ -69,6 +81,14 @@ function onError() {
|
||||
window.location.href = reportErrorUrl.value;
|
||||
}
|
||||
|
||||
function onInfo() {
|
||||
router.push(`/datasets/${props.item.id}/details`);
|
||||
}
|
||||
|
||||
function onVisualize() {
|
||||
router.push(`/datasets/${props.item.id}/visualize`);
|
||||
}
|
||||
|
||||
function onRerun() {
|
||||
router.push(`/root?job_id=${props.item.creating_job}`);
|
||||
}
|
||||
@@ -103,6 +123,30 @@ function onRerun() {
|
||||
<FontAwesomeIcon :icon="faLink" />
|
||||
</BButton>
|
||||
|
||||
<BButton
|
||||
v-if="showInfo"
|
||||
v-b-tooltip.hover
|
||||
class="info-btn px-1"
|
||||
title="Dataset Details"
|
||||
size="sm"
|
||||
variant="link"
|
||||
:href="showDetailsUrl"
|
||||
@click.prevent.stop="onInfo">
|
||||
<FontAwesomeIcon :icon="faInfoCircle" />
|
||||
</BButton>
|
||||
|
||||
<BButton
|
||||
v-if="showVisualizations"
|
||||
v-b-tooltip.hover
|
||||
class="visualize-btn px-1"
|
||||
title="Visualize"
|
||||
size="sm"
|
||||
variant="link"
|
||||
:href="visualizeUrl"
|
||||
@click.prevent.stop="onVisualize">
|
||||
<FontAwesomeIcon :icon="faChartBar" />
|
||||
</BButton>
|
||||
|
||||
<BButton
|
||||
v-if="showHighlight"
|
||||
v-b-tooltip.hover
|
||||
|
||||
@@ -1,14 +1,28 @@
|
||||
import UploadUtils from "components/Upload/utils";
|
||||
import { defineStore } from "pinia";
|
||||
|
||||
import { fetchDatatypeDetails } from "@/api/datatypes";
|
||||
|
||||
export const useDatatypeStore = defineStore("datatypeStore", {
|
||||
state: () => ({
|
||||
uploadDatatypes: [],
|
||||
datatypeDetails: {},
|
||||
}),
|
||||
getters: {
|
||||
getUploadDatatypes: (state) => {
|
||||
return state.uploadDatatypes;
|
||||
},
|
||||
getDatatypeDetails: (state) => (extension) => {
|
||||
return state.datatypeDetails[extension];
|
||||
},
|
||||
isDatatypeAutoDownload: (state) => (extension) => {
|
||||
const details = state.datatypeDetails[extension];
|
||||
return details?.display_behavior === "download";
|
||||
},
|
||||
getPreferredVisualization: (state) => (extension) => {
|
||||
const details = state.datatypeDetails[extension];
|
||||
return details?.preferred_visualization?.visualization || null;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
async fetchUploadDatatypes() {
|
||||
@@ -19,5 +33,20 @@ export const useDatatypeStore = defineStore("datatypeStore", {
|
||||
console.log("Error: unable to load datatypes", err);
|
||||
}
|
||||
},
|
||||
async fetchDatatypeDetails(extension) {
|
||||
// Return cached details if available
|
||||
if (this.datatypeDetails[extension]) {
|
||||
return this.datatypeDetails[extension];
|
||||
}
|
||||
|
||||
try {
|
||||
const details = await fetchDatatypeDetails(extension);
|
||||
this.datatypeDetails[extension] = details;
|
||||
return details;
|
||||
} catch (err) {
|
||||
console.error(`Error: unable to load datatype details for ${extension}`, err);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -157,9 +157,9 @@ object_store_details:
|
||||
dataset_view:
|
||||
selectors:
|
||||
_: .dataset-view
|
||||
edit_tab: ".nav-item[title='Edit'] > a.nav-link"
|
||||
details_tab: ".nav-item[title='Details'] > a.nav-link"
|
||||
visualize_tab: ".nav-item[title='Visualize'] > a.nav-link"
|
||||
edit_tab: ".nav-item[title='Edit dataset attributes and metadata'] > a.nav-link"
|
||||
details_tab: ".nav-item[title='View detailed information about this dataset'] > a.nav-link"
|
||||
visualize_tab: ".nav-item[title='Explore available visualizations for this dataset'] > a.nav-link"
|
||||
|
||||
history_panel:
|
||||
menu:
|
||||
|
||||
@@ -66,6 +66,11 @@ class DatatypeDetails(BaseModel):
|
||||
title="Upload warning",
|
||||
description="End-user information regarding potential pitfalls with this upload type.",
|
||||
)
|
||||
display_behavior: Optional[str] = Field(
|
||||
default=None,
|
||||
title="Display behavior",
|
||||
description="How this datatype behaves when displayed with preview=True: 'inline' (can be displayed in browser) or 'download' (triggers download)",
|
||||
)
|
||||
|
||||
|
||||
class DatatypesMap(BaseModel):
|
||||
|
||||
@@ -414,6 +414,7 @@ class CompressedZipArchive(CompressedArchive):
|
||||
"""
|
||||
|
||||
file_ext = "zip"
|
||||
display_behavior = "download" # Archive files trigger downloads
|
||||
|
||||
def set_peek(self, dataset: DatasetProtocol, **kwd) -> None:
|
||||
if not dataset.dataset.purged:
|
||||
@@ -3198,6 +3199,7 @@ class Xlsx(Binary):
|
||||
|
||||
file_ext = "xlsx"
|
||||
compressed = True
|
||||
display_behavior = "download" # Office documents trigger downloads
|
||||
|
||||
def sniff_prefix(self, file_prefix: FilePrefix) -> bool:
|
||||
# Xlsx is compressed in zip format and must not be uncompressed in Galaxy.
|
||||
@@ -3210,6 +3212,7 @@ class ExcelXls(Binary):
|
||||
|
||||
file_ext = "excel.xls"
|
||||
edam_format = "format_3468"
|
||||
display_behavior = "download" # Office documents trigger downloads
|
||||
|
||||
def sniff_prefix(self, file_prefix: FilePrefix) -> bool:
|
||||
return file_prefix.mime_type == self.get_mime()
|
||||
|
||||
@@ -240,6 +240,10 @@ class Data(metaclass=DataMeta):
|
||||
# A per datatype setting (inherited): max file size (in bytes) for setting optional metadata
|
||||
_max_optional_metadata_filesize = None
|
||||
|
||||
# Display behavior when preview=True: "inline" (can be displayed in browser),
|
||||
# "download" (always triggers download), or None (default behavior)
|
||||
display_behavior: Optional[Literal["inline", "download"]] = None
|
||||
|
||||
# Trackster track type.
|
||||
track_type: Optional[str] = None
|
||||
|
||||
@@ -264,6 +268,23 @@ class Data(metaclass=DataMeta):
|
||||
return cls.allow_datatype_change
|
||||
return cls.composite_type is None
|
||||
|
||||
@classmethod
|
||||
def get_display_behavior(cls) -> str:
|
||||
"""
|
||||
Returns the display behavior for this datatype.
|
||||
|
||||
If display_behavior is set on the class, returns that value.
|
||||
Otherwise, returns "inline" for text-based types and "download" for binary types.
|
||||
"""
|
||||
if cls.display_behavior is not None:
|
||||
return cls.display_behavior
|
||||
|
||||
# Default behavior based on whether the datatype is binary
|
||||
if cls.is_binary is True:
|
||||
return "download"
|
||||
else:
|
||||
return "inline"
|
||||
|
||||
def dataset_content_needs_grooming(self, file_name: str) -> bool:
|
||||
"""This function is called on an output dataset file after the content is initially generated."""
|
||||
return False
|
||||
|
||||
@@ -228,6 +228,7 @@ class Png(Image):
|
||||
class Tiff(Image):
|
||||
edam_format = "format_3591"
|
||||
file_ext = "tiff"
|
||||
display_behavior = "download" # TIFF files trigger browser downloads
|
||||
MetadataElement(
|
||||
name="offsets",
|
||||
desc="Offsets File",
|
||||
@@ -505,6 +506,7 @@ class Rast(Image):
|
||||
class Pdf(Image):
|
||||
edam_format = "format_3508"
|
||||
file_ext = "pdf"
|
||||
display_behavior = "download" # PDFs often trigger downloads depending on browser settings
|
||||
|
||||
def sniff(self, filename: str) -> bool:
|
||||
"""Determine if the file is in pdf format."""
|
||||
|
||||
@@ -343,6 +343,11 @@ class Registry:
|
||||
"description": description,
|
||||
"description_url": description_url,
|
||||
"upload_warning": upload_warning(upload_warning_template),
|
||||
"display_behavior": (
|
||||
datatype_instance.get_display_behavior()
|
||||
if hasattr(datatype_instance, "get_display_behavior")
|
||||
else None
|
||||
),
|
||||
}
|
||||
composite_files = datatype_instance.get_composite_files()
|
||||
if composite_files:
|
||||
|
||||
@@ -1995,7 +1995,9 @@ class NavigatesGalaxy(HasDriver):
|
||||
|
||||
# Find and click the Edit tab - using a more reliable selector
|
||||
# BVue generates '.nav-item a' elements with a title attribute matching the tab title
|
||||
edit_tab_button = self.wait_for_selector_clickable(".nav-item[title='Edit'] > a.nav-link")
|
||||
edit_tab_button = self.wait_for_selector_clickable(
|
||||
".nav-item[title='Edit dataset attributes and metadata'] > a.nav-link"
|
||||
)
|
||||
edit_tab_button.click()
|
||||
|
||||
# Wait for the edit attributes panel to be visible
|
||||
@@ -2010,14 +2012,18 @@ class NavigatesGalaxy(HasDriver):
|
||||
def show_dataset_details(self, hid):
|
||||
self.display_dataset(hid)
|
||||
# Find and click the Details tab
|
||||
details_tab_button = self.wait_for_selector_clickable(".nav-item[title='Details'] > a.nav-link")
|
||||
details_tab_button = self.wait_for_selector_clickable(
|
||||
".nav-item[title='View detailed information about this dataset'] > a.nav-link"
|
||||
)
|
||||
details_tab_button.click()
|
||||
self.components.dataset_details._.wait_for_visible()
|
||||
|
||||
def show_dataset_visualizations(self, hid):
|
||||
self.display_dataset(hid)
|
||||
# Find and click the Visualize tab
|
||||
visualize_tab_button = self.wait_for_selector_clickable(".nav-item[title='Visualize'] > a.nav-link")
|
||||
visualize_tab_button = self.wait_for_selector_clickable(
|
||||
".nav-item[title='Explore available visualizations for this dataset'] > a.nav-link"
|
||||
)
|
||||
visualize_tab_button.click()
|
||||
|
||||
def history_panel_item_view_dataset_details(self, hid):
|
||||
|
||||
@@ -229,6 +229,9 @@ class FastAPIDatatypes:
|
||||
"display_in_upload": datatype in self.datatypes_registry.upload_file_formats,
|
||||
"mimetype": self.datatypes_registry.get_mimetype_by_extension(datatype),
|
||||
"is_binary": getattr(dt_object, "is_binary", False),
|
||||
"display_behavior": (
|
||||
dt_object.get_display_behavior() if hasattr(dt_object, "get_display_behavior") else None
|
||||
),
|
||||
}
|
||||
|
||||
# Add composite files if applicable
|
||||
|
||||
+2
-1
@@ -19,5 +19,6 @@
|
||||
"dependencies": {
|
||||
"@galaxyproject/galaxy-client": "^24.0.0",
|
||||
"cpy-cli": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user