mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Merge branch 'release_24.0' into dev
This commit is contained in:
@@ -82,11 +82,11 @@ const { showActivityBar, showToolbox } = usePanels();
|
||||
<h2 class="h-sm">Related Pages</h2>
|
||||
|
||||
<div>
|
||||
<router-link :to="urlAll">All published {{ plural }}.</router-link>
|
||||
<router-link :to="urlAll">All published {{ plural }}</router-link>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<router-link :to="publishedByUser"> Published {{ plural }} by {{ owner }}. </router-link>
|
||||
<router-link :to="publishedByUser"> Published {{ plural }} by {{ owner }}</router-link>
|
||||
</div>
|
||||
</div>
|
||||
<LoadingSpan v-else message="Loading item details" />
|
||||
|
||||
@@ -18,9 +18,10 @@ library.add(faPlus);
|
||||
|
||||
interface Props {
|
||||
activeList?: "my" | "shared" | "published";
|
||||
username?: string;
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
activeList: "my",
|
||||
});
|
||||
</script>
|
||||
@@ -66,6 +67,6 @@ withDefaults(defineProps<Props>(), {
|
||||
</BNav>
|
||||
<GridList v-if="activeList === 'my'" :grid-config="historiesGridConfig" embedded />
|
||||
<GridList v-else-if="activeList === 'shared'" :grid-config="historiesSharedGridConfig" embedded />
|
||||
<GridList v-else :grid-config="historiesPublishedGridConfig" embedded />
|
||||
<GridList v-else :grid-config="historiesPublishedGridConfig" :username-search="props.username" embedded />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -37,6 +37,8 @@ interface Props {
|
||||
embedded?: boolean;
|
||||
// rows per page to be shown
|
||||
limit?: number;
|
||||
// username for initial search
|
||||
usernameSearch?: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
@@ -213,6 +215,11 @@ function onSelectAll(current: boolean): void {
|
||||
* Initialize grid data
|
||||
*/
|
||||
onMounted(() => {
|
||||
if (props.usernameSearch) {
|
||||
const search_query = `user:${props.usernameSearch}`.trim();
|
||||
filterText.value = search_query;
|
||||
onSearch(search_query);
|
||||
}
|
||||
getGridData();
|
||||
eventBus.on(onRouterPush);
|
||||
displayInitialMessage();
|
||||
|
||||
@@ -17,9 +17,10 @@ library.add(faPlus);
|
||||
|
||||
interface Props {
|
||||
activeList?: "my" | "published";
|
||||
username?: string;
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
activeList: "my",
|
||||
});
|
||||
</script>
|
||||
@@ -47,6 +48,6 @@ withDefaults(defineProps<Props>(), {
|
||||
<BNavItem :active="activeList === 'published'" to="/pages/list_published"> Public Pages </BNavItem>
|
||||
</BNav>
|
||||
<GridList v-if="activeList === 'my'" :grid-config="pagesGridConfig" embedded />
|
||||
<GridList v-else :grid-config="pagesPublishedGridConfig" embedded />
|
||||
<GridList v-else :grid-config="pagesPublishedGridConfig" :username-search="props.username" embedded />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -185,41 +185,54 @@ function onKeyDown(event: KeyboardEvent) {
|
||||
|
||||
if (event.key === "Enter" || event.key === " ") {
|
||||
event.preventDefault();
|
||||
onClick(event);
|
||||
} else if ((event.key === "ArrowUp" || event.key === "ArrowDown") && event.shiftKey) {
|
||||
onClick();
|
||||
} else if ((event.key === "ArrowUp" || event.key === "ArrowDown") && !event.shiftKey) {
|
||||
event.preventDefault();
|
||||
emit("shift-select", event.key);
|
||||
} else if (event.key === "ArrowUp" || event.key === "ArrowDown") {
|
||||
event.preventDefault();
|
||||
emit("init-key-selection");
|
||||
emit("arrow-navigate", event.key);
|
||||
} else if (event.key === "Tab") {
|
||||
emit("init-key-selection");
|
||||
} else if (event.key === "Delete" && !props.selected && !props.item.deleted) {
|
||||
event.preventDefault();
|
||||
onDelete(event.shiftKey);
|
||||
} else if (event.key === "Escape") {
|
||||
event.preventDefault();
|
||||
emit("hide-selection");
|
||||
} else if (event.key === "a" && isSelectKey(event)) {
|
||||
event.preventDefault();
|
||||
emit("select-all");
|
||||
}
|
||||
|
||||
if (props.writable) {
|
||||
if (event.key === "Tab") {
|
||||
emit("init-key-selection");
|
||||
} else {
|
||||
event.preventDefault();
|
||||
if ((event.key === "ArrowUp" || event.key === "ArrowDown") && event.shiftKey) {
|
||||
emit("shift-select", event.key);
|
||||
} else if (event.key === "ArrowUp" || event.key === "ArrowDown") {
|
||||
emit("init-key-selection");
|
||||
} else if (event.key === "Delete" && !props.selected && !props.item.deleted) {
|
||||
onDelete(event.shiftKey);
|
||||
emit("arrow-navigate", "ArrowDown");
|
||||
} else if (event.key === "Escape") {
|
||||
emit("hide-selection");
|
||||
} else if (event.key === "a" && isSelectKey(event)) {
|
||||
emit("select-all");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onClick(e: Event) {
|
||||
function onClick(e?: Event) {
|
||||
const event = e as KeyboardEvent;
|
||||
if (event && event.shiftKey && isSelectKey(event)) {
|
||||
emit("selected-to", false);
|
||||
} else if (event && isSelectKey(event)) {
|
||||
emit("init-key-selection");
|
||||
emit("update:selected", !props.selected);
|
||||
} else if (event && event.shiftKey) {
|
||||
emit("selected-to", true);
|
||||
} else if (props.isPlaceholder) {
|
||||
emit("init-key-selection");
|
||||
} else if (props.isDataset) {
|
||||
emit("init-key-selection");
|
||||
if (event && props.writable) {
|
||||
if (event.shiftKey && isSelectKey(event)) {
|
||||
emit("selected-to", false);
|
||||
return;
|
||||
} else if (isSelectKey(event)) {
|
||||
emit("init-key-selection");
|
||||
emit("update:selected", !props.selected);
|
||||
return;
|
||||
} else if (event.shiftKey) {
|
||||
emit("selected-to", true);
|
||||
return;
|
||||
} else {
|
||||
emit("init-key-selection");
|
||||
}
|
||||
}
|
||||
if (props.isPlaceholder) {
|
||||
return;
|
||||
}
|
||||
if (props.isDataset) {
|
||||
emit("update:expand-dataset", !props.expandDataset);
|
||||
} else {
|
||||
emit("view-collection", props.item, props.name);
|
||||
|
||||
@@ -50,7 +50,6 @@ interface Props {
|
||||
history: HistorySummary;
|
||||
filter?: string;
|
||||
canEditHistory?: boolean;
|
||||
shouldShowControls?: boolean;
|
||||
filterable?: boolean;
|
||||
isMultiViewItem?: boolean;
|
||||
}
|
||||
@@ -61,7 +60,6 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
listOffset: 0,
|
||||
filter: "",
|
||||
canEditHistory: true,
|
||||
shouldShowControls: true,
|
||||
filterable: false,
|
||||
isMultiViewItem: false,
|
||||
});
|
||||
@@ -80,7 +78,7 @@ const querySelectionBreak = ref(false);
|
||||
const dragTarget = ref<EventTarget | null>(null);
|
||||
const contentItemRefs = computed(() => {
|
||||
return historyItems.value.reduce((acc: ContentItemRef, item) => {
|
||||
acc[`item-${item.id}`] = ref(null);
|
||||
acc[itemUniqueKey(item)] = ref(null);
|
||||
return acc;
|
||||
}, {});
|
||||
});
|
||||
@@ -405,6 +403,10 @@ function getItemKey(item: HistoryItem) {
|
||||
return item.type_id;
|
||||
}
|
||||
|
||||
function itemUniqueKey(item: HistoryItem) {
|
||||
return `${item.history_content_type}-${item.id}`;
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
// `filterable` here indicates if this is the current history panel
|
||||
if (props.filterable && !props.filter) {
|
||||
@@ -413,9 +415,11 @@ onMounted(async () => {
|
||||
await loadHistoryItems();
|
||||
// if there is a listOffset, we are coming from a collection view, so focus on item at that offset
|
||||
if (props.listOffset) {
|
||||
const itemId = historyItems.value[props.listOffset]?.id;
|
||||
const itemElement = contentItemRefs.value[`item-${itemId}`]?.value?.$el as HTMLElement;
|
||||
itemElement?.focus();
|
||||
const itemAtOffset = historyItems.value[props.listOffset];
|
||||
if (itemAtOffset) {
|
||||
const itemElement = contentItemRefs.value[itemUniqueKey(itemAtOffset)]?.value?.$el as HTMLElement;
|
||||
itemElement?.focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -427,7 +431,7 @@ function arrowNavigate(item: HistoryItem, eventKey: string) {
|
||||
nextItem = historyItems.value[historyItems.value.indexOf(item) - 1];
|
||||
}
|
||||
if (nextItem) {
|
||||
const itemElement = contentItemRefs.value[`item-${nextItem.id}`]?.value?.$el as HTMLElement;
|
||||
const itemElement = contentItemRefs.value[itemUniqueKey(nextItem)]?.value?.$el as HTMLElement;
|
||||
itemElement?.focus();
|
||||
}
|
||||
return nextItem;
|
||||
@@ -510,13 +514,13 @@ function setItemDragstart(
|
||||
:history="history"
|
||||
:is-watching="isWatching"
|
||||
:last-checked="lastCheckedTime"
|
||||
:show-controls="shouldShowControls"
|
||||
:show-controls="canEditHistory"
|
||||
:filter-text.sync="filterText"
|
||||
:hide-reload="isMultiViewItem"
|
||||
@reloadContents="reloadContents" />
|
||||
|
||||
<HistoryOperations
|
||||
v-if="shouldShowControls"
|
||||
v-if="canEditHistory"
|
||||
:history="history"
|
||||
:show-selection="showSelection"
|
||||
:expanded-count="expandedCount"
|
||||
@@ -586,7 +590,7 @@ function setItemDragstart(
|
||||
<template v-slot:item="{ item, currentOffset }">
|
||||
<ContentItem
|
||||
:id="item.hid"
|
||||
:ref="contentItemRefs[`item-${item.id}`]"
|
||||
:ref="contentItemRefs[itemUniqueKey(item)]"
|
||||
is-history-item
|
||||
:item="item"
|
||||
:name="item.name"
|
||||
|
||||
@@ -14,9 +14,8 @@ const props = defineProps<Props>();
|
||||
const history = ref({});
|
||||
|
||||
onMounted(async () => {
|
||||
const result = await historyFetcher({ history_id: props.id });
|
||||
|
||||
history.value = result;
|
||||
const { data } = await historyFetcher({ history_id: props.id });
|
||||
history.value = data;
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -94,12 +94,15 @@ describe("History center panel View", () => {
|
||||
expect(tags.text()).toContain("tag_1");
|
||||
expect(tags.text()).toContain("tag_2");
|
||||
// HistoryCounter
|
||||
expect(wrapper.find("[data-description='storage dashboard button']").attributes("disabled")).toBeTruthy();
|
||||
expect(wrapper.find("[data-description='show active items button']").text()).toEqual("8");
|
||||
expect(wrapper.find("[data-description='include deleted items button']").text()).toEqual("1");
|
||||
expect(wrapper.find("[data-description='include hidden items button']").text()).toEqual("2");
|
||||
}
|
||||
|
||||
function storageDashboardButtonDisabled(wrapper) {
|
||||
return wrapper.find("[data-description='storage dashboard button']").attributes("disabled");
|
||||
}
|
||||
|
||||
it("current user's current history", async () => {
|
||||
const history = create_history("history_1", "user_1", false);
|
||||
const wrapper = await createWrapper(localVue, "user_1", history);
|
||||
@@ -117,6 +120,9 @@ describe("History center panel View", () => {
|
||||
// parts of the layout that should be similar for all cases
|
||||
expectCorrectLayout(wrapper);
|
||||
|
||||
// storage dashboard button should be enabled
|
||||
expect(storageDashboardButtonDisabled(wrapper)).toBeFalsy();
|
||||
|
||||
// make sure all history items show up
|
||||
const historyItems = wrapper.findAllComponents(ContentItem);
|
||||
expect(historyItems.length).toBe(10);
|
||||
@@ -138,6 +144,9 @@ describe("History center panel View", () => {
|
||||
expect(switchButton.exists()).toBe(false);
|
||||
expect(importButton.attributes("disabled")).toBeFalsy();
|
||||
|
||||
// storage dashboard button should be disabled
|
||||
expect(storageDashboardButtonDisabled(wrapper)).toBeTruthy();
|
||||
|
||||
// parts of the layout that should be similar for all cases
|
||||
expectCorrectLayout(wrapper);
|
||||
});
|
||||
@@ -153,6 +162,9 @@ describe("History center panel View", () => {
|
||||
expect(switchButton.attributes("disabled")).toBeFalsy();
|
||||
expect(importButton.exists()).toBe(false);
|
||||
|
||||
// storage dashboard button should be enabled
|
||||
expect(storageDashboardButtonDisabled(wrapper)).toBeFalsy();
|
||||
|
||||
// parts of the layout that should be similar for all cases
|
||||
expectCorrectLayout(wrapper);
|
||||
});
|
||||
@@ -168,6 +180,9 @@ describe("History center panel View", () => {
|
||||
expect(switchButton.attributes("disabled")).toBeTruthy();
|
||||
expect(importButton.exists()).toBe(false);
|
||||
|
||||
// storage dashboard button should be disabled
|
||||
expect(storageDashboardButtonDisabled(wrapper)).toBeTruthy();
|
||||
|
||||
// instead we have an alert
|
||||
expect(wrapper.find("[data-description='history state info']").text()).toBe("This history has been purged.");
|
||||
});
|
||||
@@ -183,6 +198,9 @@ describe("History center panel View", () => {
|
||||
expect(importButton.exists()).toBe(true);
|
||||
expect(importButton.attributes("disabled")).toBeFalsy();
|
||||
|
||||
// storage dashboard button should be disabled
|
||||
expect(storageDashboardButtonDisabled(wrapper)).toBeTruthy();
|
||||
|
||||
expectCorrectLayout(wrapper);
|
||||
expect(wrapper.find("[data-description='history state info']").exists()).toBe(false);
|
||||
});
|
||||
@@ -198,6 +216,9 @@ describe("History center panel View", () => {
|
||||
expect(switchButton.attributes("disabled")).toBeTruthy();
|
||||
expect(importButton.exists()).toBe(false);
|
||||
|
||||
// storage dashboard button should be disabled
|
||||
expect(storageDashboardButtonDisabled(wrapper)).toBeTruthy();
|
||||
|
||||
expectCorrectLayout(wrapper);
|
||||
expect(wrapper.find("[data-description='history state info']").text()).toBe("This history has been archived.");
|
||||
});
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
v-else
|
||||
:history="history"
|
||||
:can-edit-history="canEditHistory"
|
||||
:should-show-controls="false"
|
||||
filterable
|
||||
@view-collection="onViewCollection" />
|
||||
|
||||
|
||||
@@ -281,9 +281,10 @@ export function getRouter(Galaxy) {
|
||||
{
|
||||
path: "histories/list_published",
|
||||
component: GridHistory,
|
||||
props: {
|
||||
props: (route) => ({
|
||||
activeList: "published",
|
||||
},
|
||||
username: route.query["f-username"],
|
||||
}),
|
||||
},
|
||||
{
|
||||
path: "histories/archived",
|
||||
@@ -381,9 +382,10 @@ export function getRouter(Galaxy) {
|
||||
{
|
||||
path: "pages/list_published",
|
||||
component: GridPage,
|
||||
props: {
|
||||
props: (route) => ({
|
||||
activeList: "published",
|
||||
},
|
||||
username: route.query["f-username"],
|
||||
}),
|
||||
},
|
||||
{
|
||||
path: "storage/history/:historyId",
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
id: news
|
||||
type:
|
||||
- masthead
|
||||
activate: true
|
||||
|
||||
icon: fa-bullhorn
|
||||
tooltip: See the Galaxy Release Notes
|
||||
@@ -1,111 +0,0 @@
|
||||
(function () {
|
||||
function hideNewsOverlay() {
|
||||
const container = document.getElementById("news-container");
|
||||
if (container) {
|
||||
container.style.visibility = "hidden";
|
||||
}
|
||||
}
|
||||
|
||||
function newsSeen(currentGalaxyVersion) {
|
||||
// When it's seen, remove the red indicator if it exists and store the current version.
|
||||
const newsIndicator = document.getElementById("news-indicator");
|
||||
if (newsIndicator) {
|
||||
newsIndicator.remove();
|
||||
}
|
||||
window.localStorage.setItem("galaxy-news-seen-release", currentGalaxyVersion);
|
||||
}
|
||||
|
||||
function newsUnseen() {
|
||||
// When there is news, add an red indicator to the icon.
|
||||
const newsIconSpan = document.querySelector("#news .nav-link");
|
||||
newsIconSpan.insertAdjacentHTML("beforeend", '<span id="news-indicator"></span>');
|
||||
}
|
||||
|
||||
/* The masthead icon may not exist yet when this webhook executes; we need this to wait for that to happen.
|
||||
* elementReady function from gist:
|
||||
* https://gist.github.com/jwilson8767/db379026efcbd932f64382db4b02853e
|
||||
*/
|
||||
function elementReadyNews(selector) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const el = document.querySelector(selector);
|
||||
if (el) {
|
||||
resolve(el);
|
||||
}
|
||||
new MutationObserver((mutationRecords, observer) => {
|
||||
// Query for elements matching the specified selector
|
||||
Array.from(document.querySelectorAll(selector)).forEach((element) => {
|
||||
resolve(element);
|
||||
//Once we have resolved we don't need the observer anymore.
|
||||
observer.disconnect();
|
||||
});
|
||||
}).observe(document.documentElement, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
elementReadyNews("#news a").then((el) => {
|
||||
// External stuff may also have attached a click handler here (vue-based masthead)
|
||||
// replace with a clean copy of the node to remove all that cruft.
|
||||
clean = el.cloneNode(true);
|
||||
el.parentNode.replaceChild(clean, el);
|
||||
|
||||
let currentGalaxyVersion = Galaxy.config.version_major;
|
||||
const lastSeenVersion = window.localStorage.getItem("galaxy-news-seen-release");
|
||||
|
||||
// If we're at a deployed release candidate, just mark it seen and show
|
||||
// the previous notes if someone clicks the link. RC notes won't exist.
|
||||
if (Galaxy.config.version_minor.startsWith("rc")) {
|
||||
// If we, for whatever reason, need to do this again just add
|
||||
// another case here. It's not worth parsing and doing version
|
||||
// math, and we should be able to drop preferring notifications
|
||||
// framework moving forward in 23.2
|
||||
if (currentGalaxyVersion == "23.1") {
|
||||
currentGalaxyVersion = "23.0";
|
||||
}
|
||||
newsSeen(currentGalaxyVersion);
|
||||
} else if (lastSeenVersion != currentGalaxyVersion) {
|
||||
newsUnseen();
|
||||
} else {
|
||||
newsSeen(currentGalaxyVersion);
|
||||
}
|
||||
|
||||
const releaseNotes = `https://docs.galaxyproject.org/en/latest/releases/${currentGalaxyVersion}_announce_user.html`;
|
||||
|
||||
clean.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// If element doesn't exist, add it.
|
||||
if (document.getElementById("news-container") == null) {
|
||||
document.querySelector("body.full-content").insertAdjacentHTML(
|
||||
"afterbegin",
|
||||
`
|
||||
<div id="news-container" style="visibility: hidden">
|
||||
<div id="news-screen-overlay"></div>
|
||||
<div id="news-screen">
|
||||
<div id="news-header">
|
||||
<iframe id="news-embed" src="${releaseNotes}" width="80%" height="80%"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
);
|
||||
// Clicking outside of GTN closes it
|
||||
document.getElementById("news-screen").addEventListener("click", () => {
|
||||
hideNewsOverlay();
|
||||
});
|
||||
}
|
||||
document.getElementById("news-container").style.visibility = "visible";
|
||||
newsSeen(currentGalaxyVersion);
|
||||
});
|
||||
});
|
||||
|
||||
// Remove the overlay on escape button click
|
||||
document.addEventListener("keydown", (e) => {
|
||||
// Check for escape button - "27"
|
||||
if (e.which === 27 || e.keyCode === 27) {
|
||||
hideNewsOverlay();
|
||||
}
|
||||
});
|
||||
})();
|
||||
@@ -1,38 +0,0 @@
|
||||
#news-screen {
|
||||
position: fixed;
|
||||
z-index: 5100;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#news-screen-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: rgba(224, 224, 224, 0.75);
|
||||
z-index: 5000;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 2;
|
||||
}
|
||||
|
||||
#news-header {
|
||||
position: fixed;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
z-index: 5200;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#news-indicator {
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
right: 0.2rem;
|
||||
width: 0.6rem;
|
||||
height: 0.6rem;
|
||||
border-radius: 50%;
|
||||
background: #e31a1e;
|
||||
}
|
||||
Reference in New Issue
Block a user