mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-21 13:50:20 +08:00
use PageDisplayToolbar in editor view as well
This commit is contained in:
@@ -23,6 +23,7 @@ const localVue = getLocalVue();
|
||||
|
||||
const WORKFLOW_ID = "workflow-abc123";
|
||||
const WORKFLOW_NAME = "My Test Workflow";
|
||||
const INPUT_SELECTOR = "[data-description='workflow name input']";
|
||||
|
||||
async function mountRenameModal(name = WORKFLOW_NAME) {
|
||||
const wrapper = mount(RenameModal as object, {
|
||||
@@ -52,7 +53,7 @@ describe("RenameModal tested for renaming workflows", () => {
|
||||
|
||||
const wrapper = await mountRenameModal();
|
||||
|
||||
await createWrapper(document.body).find("#workflow-name-input").setValue("Renamed Workflow");
|
||||
await createWrapper(document.body).find(INPUT_SELECTOR).setValue("Renamed Workflow");
|
||||
wrapper.findComponent(GModal).vm.$emit("ok");
|
||||
await flushPromises();
|
||||
|
||||
@@ -65,7 +66,7 @@ describe("RenameModal tested for renaming workflows", () => {
|
||||
|
||||
const wrapper = await mountRenameModal();
|
||||
|
||||
await createWrapper(document.body).find("#workflow-name-input").setValue("Attempted New Name");
|
||||
await createWrapper(document.body).find(INPUT_SELECTOR).setValue("Attempted New Name");
|
||||
wrapper.findComponent(GModal).vm.$emit("ok");
|
||||
await flushPromises();
|
||||
|
||||
@@ -77,7 +78,7 @@ describe("RenameModal tested for renaming workflows", () => {
|
||||
// Simulate parent closing and reopening the modal (destroys old instance)
|
||||
wrapper.destroy();
|
||||
await mountRenameModal();
|
||||
expect((createWrapper(document.body).find("#workflow-name-input").element as HTMLInputElement).value).toBe(
|
||||
expect((createWrapper(document.body).find(INPUT_SELECTOR).element as HTMLInputElement).value).toBe(
|
||||
WORKFLOW_NAME,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -63,15 +63,15 @@ async function onRename(newName: string) {
|
||||
show
|
||||
:ok-text="localize('Rename')"
|
||||
:ok-disabled="nameInvalid || renaming"
|
||||
:title="`Rename ${props.itemType}: ${props.name}`"
|
||||
:title="`Rename ${capitalizeFirstLetter(props.itemType)}: ${props.name}`"
|
||||
confirm
|
||||
:close-on-ok="false"
|
||||
@ok="onRename(nameModel)"
|
||||
@close="emit('close')"
|
||||
@cancel="emit('close')">
|
||||
<GFormInput
|
||||
:id="`${props.itemType}-name-input`"
|
||||
v-model="nameModel"
|
||||
:data-description="`${props.itemType} name input`"
|
||||
class="w-100"
|
||||
:disabled="renaming"
|
||||
type="text"
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
<script setup lang="ts">
|
||||
import { faArrowLeft, faEye, faHistory, faSave, faSpinner, faUsers } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faSpinner, faUsers } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||
import { BAlert, BBadge, BButton } from "bootstrap-vue";
|
||||
import { BAlert } from "bootstrap-vue";
|
||||
import { computed, onMounted, onUnmounted, ref, watch } from "vue";
|
||||
import { useRouter } from "vue-router/composables";
|
||||
|
||||
import { getGalaxyInstance } from "@/app";
|
||||
import type { RouterPushOptions } from "@/components/History/Content/router-push-options";
|
||||
// import { getGalaxyInstance } from "@/app";
|
||||
// import type { RouterPushOptions } from "@/components/History/Content/router-push-options";
|
||||
import { PAGE_LABELS } from "@/components/Page/constants";
|
||||
import { useWindowAwareNavigation } from "@/composables/windowAwareNavigation";
|
||||
import { useHistoryStore } from "@/stores/historyStore";
|
||||
import { type PageEditorMode, usePageEditorStore } from "@/stores/pageEditorStore";
|
||||
|
||||
import GButton from "../BaseComponents/GButton.vue";
|
||||
import ObjectPermissionsModal from "./ObjectPermissionsModal.vue";
|
||||
import PageDisplayOnly from "./PageDisplayOnly.vue";
|
||||
import PageDisplayToolbar from "./PageDisplayToolbar.vue";
|
||||
import PageRevisionList from "./PageRevisionList.vue";
|
||||
import PageRevisionView from "./PageRevisionView.vue";
|
||||
import ClickToEdit from "@/components/ClickToEdit.vue";
|
||||
import MarkdownEditor from "@/components/Markdown/MarkdownEditor.vue";
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -125,39 +126,33 @@ function handleEdit() {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSave() {
|
||||
await store.savePage();
|
||||
}
|
||||
|
||||
async function handleSaveAndView() {
|
||||
await store.savePage();
|
||||
if (props.invocationId) {
|
||||
router.push(`/workflows/invocations/${props.invocationId}/reports?id=${props.pageId}`);
|
||||
return;
|
||||
}
|
||||
if (store.currentPage) {
|
||||
const Galaxy = getGalaxyInstance();
|
||||
const isWmActive = Galaxy?.frame?.active;
|
||||
if (isWmActive) {
|
||||
const url = `/published/page?id=${props.pageId}&embed=true`;
|
||||
const options: RouterPushOptions = {
|
||||
title: `${labels.value.entityName}: ${store.currentTitle || labels.value.defaultTitle}`,
|
||||
preventWindowManager: false,
|
||||
};
|
||||
// @ts-ignore - monkeypatched router
|
||||
router.push(url, options);
|
||||
} else {
|
||||
const data = store.currentPage as any;
|
||||
if (data.username && data.slug) {
|
||||
window.location.href = `/u/${data.username}/p/${data.slug}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleTitleChange(newTitle: string) {
|
||||
store.updateTitle(newTitle);
|
||||
}
|
||||
// TODO: Uncomment when router guards with unsaved changes protection are implemented
|
||||
// Before, we had a Save & View button that is now removed.
|
||||
// async function handleSaveAndView() {
|
||||
// await store.savePage();
|
||||
// if (props.invocationId) {
|
||||
// router.push(`/workflows/invocations/${props.invocationId}/reports?id=${props.pageId}`);
|
||||
// return;
|
||||
// }
|
||||
// if (store.currentPage) {
|
||||
// const Galaxy = getGalaxyInstance();
|
||||
// const isWmActive = Galaxy?.frame?.active;
|
||||
// if (isWmActive) {
|
||||
// const url = `/published/page?id=${props.pageId}&embed=true`;
|
||||
// const options: RouterPushOptions = {
|
||||
// title: `${labels.value.entityName}: ${store.currentTitle || labels.value.defaultTitle}`,
|
||||
// preventWindowManager: false,
|
||||
// };
|
||||
// // @ts-ignore - monkeypatched router
|
||||
// router.push(url, options);
|
||||
// } else {
|
||||
// const data = store.currentPage as any;
|
||||
// if (data.username && data.slug) {
|
||||
// window.location.href = `/u/${data.username}/p/${data.slug}`;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
function handleContentUpdate(newContent: string) {
|
||||
store.updateContent(newContent);
|
||||
@@ -208,78 +203,24 @@ function handleRevisionRestore(revisionId: string) {
|
||||
|
||||
<!-- Edit mode: toolbar + editor + optional chat/revision panels -->
|
||||
<template v-else-if="store.hasCurrentPage">
|
||||
<div class="d-flex align-items-center p-2 border-bottom" data-description="page editor toolbar">
|
||||
<BButton variant="link" size="sm" data-description="page back button" @click="handleBack">
|
||||
<FontAwesomeIcon :icon="faArrowLeft" />
|
||||
{{ labels.editorBackLabel }}
|
||||
</BButton>
|
||||
<ClickToEdit
|
||||
:value="store.currentTitle || labels.defaultTitle"
|
||||
tag-name="span"
|
||||
:placeholder="labels.defaultTitle"
|
||||
class="flex-grow-1 text-center font-weight-bold"
|
||||
data-description="page editor title"
|
||||
@input="handleTitleChange" />
|
||||
<BButton
|
||||
variant="outline-primary"
|
||||
size="sm"
|
||||
class="mr-2"
|
||||
data-description="page revisions button"
|
||||
@click="store.toggleRevisions">
|
||||
<FontAwesomeIcon :icon="faHistory" />
|
||||
Revisions
|
||||
<BBadge v-if="store.revisionCount > 0" variant="light" class="ml-1">
|
||||
{{ store.revisionCount }}
|
||||
</BBadge>
|
||||
</BButton>
|
||||
<BButton
|
||||
variant="outline-primary"
|
||||
size="sm"
|
||||
class="mr-2"
|
||||
data-description="page preview button"
|
||||
@click="handlePreview">
|
||||
<FontAwesomeIcon :icon="faEye" />
|
||||
Preview
|
||||
</BButton>
|
||||
<template v-if="isStandalone">
|
||||
<PageDisplayToolbar :labels="labels" mode="editor" @preview="handlePreview" @back="handleBack">
|
||||
<template v-if="isStandalone" v-slot:extra-actions>
|
||||
<ObjectPermissionsModal
|
||||
id="object-permissions-modal"
|
||||
v-model="showPermissions"
|
||||
:markdown-content="store.currentContent" />
|
||||
<BButton
|
||||
<GButton
|
||||
v-b-modal:object-permissions-modal
|
||||
variant="outline-primary"
|
||||
size="sm"
|
||||
class="mr-2"
|
||||
data-description="page permissions button">
|
||||
color="blue"
|
||||
outline
|
||||
size="small"
|
||||
data-description="page permissions button"
|
||||
@click="showPermissions = true">
|
||||
<FontAwesomeIcon :icon="faUsers" />
|
||||
Permissions
|
||||
</BButton>
|
||||
</GButton>
|
||||
</template>
|
||||
<BButton
|
||||
variant="primary"
|
||||
size="sm"
|
||||
:class="{ 'mr-2': isStandalone }"
|
||||
data-description="page save button"
|
||||
:disabled="!store.canSave"
|
||||
@click="handleSave">
|
||||
<FontAwesomeIcon :icon="store.isSaving ? faSpinner : faSave" :spin="store.isSaving" />
|
||||
Save
|
||||
</BButton>
|
||||
<BButton
|
||||
v-if="isStandalone"
|
||||
variant="primary"
|
||||
size="sm"
|
||||
data-description="page save-view button"
|
||||
:disabled="!store.canSave"
|
||||
@click="handleSaveAndView">
|
||||
<FontAwesomeIcon :icon="faEye" />
|
||||
Save & View
|
||||
</BButton>
|
||||
<span v-if="store.isDirty" class="ml-2 text-warning small" data-description="page unsaved indicator">
|
||||
Unsaved
|
||||
</span>
|
||||
</div>
|
||||
</PageDisplayToolbar>
|
||||
|
||||
<div class="page-body d-flex flex-grow-1 overflow-hidden">
|
||||
<div class="page-content flex-grow-1 overflow-auto">
|
||||
|
||||
@@ -644,7 +644,7 @@ pages:
|
||||
toolbar:
|
||||
type: data-description
|
||||
selector: 'page editor toolbar'
|
||||
toolbar_title: '[data-description="page editor title"] > span'
|
||||
toolbar_title: '[data-description="page editor title"]'
|
||||
save_button:
|
||||
type: data-description
|
||||
selector: 'page save button'
|
||||
@@ -694,16 +694,15 @@ pages:
|
||||
edit_button:
|
||||
type: data-description
|
||||
selector: 'page edit button'
|
||||
toolbar_title_input: '[data-description="page editor title"] input'
|
||||
rename_button:
|
||||
type: data-description
|
||||
selector: 'page rename button'
|
||||
back_button:
|
||||
type: data-description
|
||||
selector: 'page back button'
|
||||
permissions_button:
|
||||
type: data-description
|
||||
selector: 'page permissions button'
|
||||
save_view_button:
|
||||
type: data-description
|
||||
selector: 'page save-view button'
|
||||
revision_compare_current_button:
|
||||
type: data-description
|
||||
selector: 'revision compare current button'
|
||||
|
||||
@@ -1961,10 +1961,7 @@ class NavigatesGalaxy(HasDriverProxy[WaitType]):
|
||||
action_chains = self.action_chains()
|
||||
action_chains.move_to_element(workflow).perform()
|
||||
workflow.find_element(By.CSS_SELECTOR, ".g-card-rename").click()
|
||||
self.components.workflows.rename_input.wait_for_visible().clear()
|
||||
self.components.workflows.rename_input.wait_for_and_send_keys(new_name)
|
||||
self.components.workflows.rename_input.wait_for_and_send_keys(Keys.ENTER)
|
||||
self.wait_for_selector_absent_or_hidden(".g-modal")
|
||||
self.rename_modal_rename("workflow", new_name)
|
||||
|
||||
def workflow_delete_by_name(self, name):
|
||||
self.workflow_index_search_for(name)
|
||||
@@ -2330,14 +2327,25 @@ class NavigatesGalaxy(HasDriverProxy[WaitType]):
|
||||
items = self.components.pages.history.revision_item.all()
|
||||
assert len(items) == n, f"Expected {n} revision items, found {len(items)}"
|
||||
|
||||
def history_page_rename(self, new_name):
|
||||
"""Rename page via ClickToEdit in toolbar."""
|
||||
self.components.pages.history.toolbar_title.wait_for_and_click()
|
||||
title_input = self.components.pages.history.toolbar_title_input.wait_for_visible()
|
||||
self.aggressive_clear(title_input)
|
||||
title_input.send_keys(new_name)
|
||||
self.send_enter(title_input)
|
||||
self.sleep_for(self.wait_types.UX_RENDER)
|
||||
def history_page_rename(self, page_type: str, new_name: str):
|
||||
"""Rename page via RenameModal in toolbar."""
|
||||
self.components.pages.history.rename_button.wait_for_and_click()
|
||||
self.rename_modal_rename(page_type, new_name)
|
||||
|
||||
def rename_modal_rename(self, item_type: str, new_name: str):
|
||||
"""Rename via RenameModal: opens the rename modal, types new name, and submits."""
|
||||
# Clear via JS + dispatch Vue-compatible input event so nameModel is updated to empty,
|
||||
# then type the new name so each keystroke fires input events and updates nameModel.
|
||||
item_name_input = self.wait_for_selector(f"[data-description='{item_type} name input']")
|
||||
self.execute_script(
|
||||
"arguments[0].value = ''; arguments[0].dispatchEvent(new Event('input', {bubbles: true}));",
|
||||
item_name_input,
|
||||
)
|
||||
item_name_input.send_keys(new_name)
|
||||
|
||||
self.wait_for_and_click_selector(".g-modal-confirm-buttons button:last-child")
|
||||
# Wait for the rename promise to resolve (modal closes in the finally block)
|
||||
self.wait_for_selector_absent_or_hidden(f"#{item_type}-name-input")
|
||||
|
||||
@retry_during_transitions
|
||||
def click_history_options(self):
|
||||
|
||||
@@ -65,19 +65,7 @@ class TestSavedHistories(SharedStateSeleniumTestCase):
|
||||
|
||||
self.select_history_card_operation("Unnamed history", '[id^="g-card-rename-history-"]')
|
||||
|
||||
# Rename the history using the RenameModal.
|
||||
# Clear via JS + dispatch Vue-compatible input event so nameModel is updated to empty,
|
||||
# then type the new name so each keystroke fires input events and updates nameModel.
|
||||
history_name_input = self.wait_for_selector("#history-name-input")
|
||||
self.execute_script(
|
||||
"arguments[0].value = ''; arguments[0].dispatchEvent(new Event('input', {bubbles: true}));",
|
||||
history_name_input,
|
||||
)
|
||||
history_name_input.send_keys(self.history1_name)
|
||||
|
||||
self.wait_for_and_click_selector(".g-modal-confirm-buttons button:last-child")
|
||||
# Wait for the rename API call to complete (modal closes in the finally block)
|
||||
self.wait_for_selector_absent_or_hidden("#history-name-input")
|
||||
self.rename_modal_rename("history", self.history1_name)
|
||||
|
||||
self.navigate_to_histories_page()
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ class TestHistoryPages(SeleniumTestCase):
|
||||
self.components.pages.history.editor.wait_for_visible()
|
||||
|
||||
save_button = self.components.pages.history.save_button
|
||||
save_button.assert_disabled()
|
||||
assert save_button.has_class("g-disabled")
|
||||
|
||||
self.components.pages.history.unsaved_indicator.assert_absent_or_hidden()
|
||||
|
||||
@@ -112,7 +112,7 @@ class TestHistoryPages(SeleniumTestCase):
|
||||
|
||||
@retry_assertion_during_transitions
|
||||
def assert_save_enabled():
|
||||
assert not save_button.has_class("disabled")
|
||||
assert not save_button.has_class("g-disabled")
|
||||
|
||||
assert_save_enabled()
|
||||
|
||||
@@ -120,7 +120,7 @@ class TestHistoryPages(SeleniumTestCase):
|
||||
|
||||
@retry_assertion_during_transitions
|
||||
def assert_save_disabled_again():
|
||||
save_button.assert_disabled()
|
||||
assert save_button.has_class("g-disabled")
|
||||
|
||||
assert_save_disabled_again()
|
||||
self.screenshot("history_page_save_disabled")
|
||||
@@ -439,8 +439,8 @@ class TestHistoryPages(SeleniumTestCase):
|
||||
|
||||
@selenium_test
|
||||
@managed_history
|
||||
def test_inline_rename_page(self):
|
||||
"""Rename page title via ClickToEdit, save, verify persistence."""
|
||||
def test_rename_page(self):
|
||||
"""Rename page title via the RenameModal, save, verify persistence."""
|
||||
history_id = self.current_history_id()
|
||||
self.dataset_populator.new_history_page(history_id, title="Original Name", content="# Content")
|
||||
|
||||
@@ -448,7 +448,7 @@ class TestHistoryPages(SeleniumTestCase):
|
||||
self.components.pages.history.item_edit.wait_for_and_click()
|
||||
self.components.pages.history.editor.wait_for_visible()
|
||||
|
||||
self.history_page_rename("Renamed Page")
|
||||
self.history_page_rename("galaxy notebook", "Renamed Page")
|
||||
|
||||
self.components.pages.history.unsaved_indicator.wait_for_visible()
|
||||
self.screenshot("history_page_renamed_unsaved")
|
||||
@@ -557,7 +557,6 @@ class TestHistoryPages(SeleniumTestCase):
|
||||
|
||||
# Standalone-only controls absent
|
||||
self.components.pages.history.permissions_button.assert_absent_or_hidden()
|
||||
self.components.pages.history.save_view_button.assert_absent_or_hidden()
|
||||
|
||||
# Back button says "This History's Pages" not "Back to Pages"
|
||||
back_text = self.components.pages.history.back_button.wait_for_text()
|
||||
|
||||
@@ -213,7 +213,6 @@ class TestPages(SeleniumTestCase):
|
||||
self.components.pages.history.revisions_button.wait_for_visible()
|
||||
self.components.pages.history.preview_button.wait_for_visible()
|
||||
self.components.pages.history.permissions_button.wait_for_visible()
|
||||
self.components.pages.history.save_view_button.wait_for_visible()
|
||||
|
||||
# Back button says "Back to Reports" not "This History's Notebooks"
|
||||
back_text = self.components.pages.history.back_button.wait_for_text()
|
||||
|
||||
Reference in New Issue
Block a user