Browse Source

fix: polish overlay dismissal and job details

codex/vue-opencloud-design-system
Rain Mark 2 months ago
parent
commit
3681540c38
  1. 2
      app/web/dist/assets/app.css
  2. 42
      app/web/dist/assets/app.js
  3. 19
      frontend/src/App.vue
  4. 13
      frontend/src/styles/opencloud-app.css
  5. 29
      scripts/ui_playwright_smoke.py

2
app/web/dist/assets/app.css

File diff suppressed because one or more lines are too long

42
app/web/dist/assets/app.js

File diff suppressed because one or more lines are too long

19
frontend/src/App.vue

@ -187,6 +187,7 @@ watch([jobsPage, jobsTotalPages], () => {
onMounted(async () => { onMounted(async () => {
window.addEventListener("popstate", handlePopState); window.addEventListener("popstate", handlePopState);
window.addEventListener("keydown", handleGlobalKeydown); window.addEventListener("keydown", handleGlobalKeydown);
window.addEventListener("pointerdown", handleGlobalPointerDown, true);
if (!getAccessToken() && route.value.name !== "login") { if (!getAccessToken() && route.value.name !== "login") {
navigate("/app/login"); navigate("/app/login");
return; return;
@ -197,6 +198,7 @@ onMounted(async () => {
onUnmounted(() => { onUnmounted(() => {
window.removeEventListener("popstate", handlePopState); window.removeEventListener("popstate", handlePopState);
window.removeEventListener("keydown", handleGlobalKeydown); window.removeEventListener("keydown", handleGlobalKeydown);
window.removeEventListener("pointerdown", handleGlobalPointerDown, true);
}); });
function handlePopState() { function handlePopState() {
@ -206,6 +208,17 @@ function handlePopState() {
function handleGlobalKeydown(event: KeyboardEvent) { function handleGlobalKeydown(event: KeyboardEvent) {
if (event.key === "Escape") { if (event.key === "Escape") {
openMenuId.value = null; openMenuId.value = null;
dialogMode.value = null;
isCreateBackendOpen.value = false;
editingBackend.value = null;
isDetailDrawerOpen.value = false;
}
}
function handleGlobalPointerDown(event: PointerEvent) {
const target = event.target instanceof Element ? event.target : null;
if (!target?.closest(".menu-popover, .row-action-button")) {
openMenuId.value = null;
} }
} }
@ -953,7 +966,7 @@ function getPaginationItems(current: number, total: number): Array<number | "ell
<div v-if="toast" class="toast-item">{{ toast }}</div> <div v-if="toast" class="toast-item">{{ toast }}</div>
<div v-if="dialogMode" class="dialog-backdrop"> <div v-if="dialogMode" class="dialog-backdrop" @click.self="dialogMode = null">
<form class="dialog-card" role="dialog" aria-modal="true" @submit.prevent="confirmDialog"> <form class="dialog-card" role="dialog" aria-modal="true" @submit.prevent="confirmDialog">
<div class="dialog-header"> <div class="dialog-header">
<div><span class="eyebrow">Action</span><h3>{{ dialogMode === "delete" ? "Delete file" : dialogMode === "create" ? "New folder" : dialogMode === "move" ? "Move item" : "Rename" }}</h3></div> <div><span class="eyebrow">Action</span><h3>{{ dialogMode === "delete" ? "Delete file" : dialogMode === "create" ? "New folder" : dialogMode === "move" ? "Move item" : "Rename" }}</h3></div>
@ -970,7 +983,7 @@ function getPaginationItems(current: number, total: number): Array<number | "ell
</form> </form>
</div> </div>
<div v-if="isCreateBackendOpen" class="dialog-backdrop"> <div v-if="isCreateBackendOpen" class="dialog-backdrop" @click.self="isCreateBackendOpen = false">
<form class="dialog-card dialog-card-wide" role="dialog" aria-modal="true" @submit.prevent="createBackend"> <form class="dialog-card dialog-card-wide" role="dialog" aria-modal="true" @submit.prevent="createBackend">
<div class="dialog-header"> <div class="dialog-header">
<div><span class="eyebrow">Storage</span><h3>Add storage backend</h3></div> <div><span class="eyebrow">Storage</span><h3>Add storage backend</h3></div>
@ -989,7 +1002,7 @@ function getPaginationItems(current: number, total: number): Array<number | "ell
</form> </form>
</div> </div>
<div v-if="editingBackend" class="dialog-backdrop"> <div v-if="editingBackend" class="dialog-backdrop" @click.self="editingBackend = null">
<form class="dialog-card dialog-card-wide" role="dialog" aria-modal="true" @submit.prevent="saveBackend"> <form class="dialog-card dialog-card-wide" role="dialog" aria-modal="true" @submit.prevent="saveBackend">
<div class="dialog-header"> <div class="dialog-header">
<div><span class="eyebrow">Storage</span><h3>Edit storage backend</h3></div> <div><span class="eyebrow">Storage</span><h3>Edit storage backend</h3></div>

13
frontend/src/styles/opencloud-app.css

@ -857,11 +857,24 @@ textarea {
} }
.mono-text { .mono-text {
min-width: 0;
overflow-wrap: anywhere; overflow-wrap: anywhere;
word-break: break-word;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
font-size: 0.78rem; font-size: 0.78rem;
} }
.jobs-detail-meta > div {
grid-template-columns: 72px minmax(0, 1fr);
align-items: start;
}
.jobs-detail-meta strong {
min-width: 0;
justify-self: start;
text-align: left;
}
.notice-text { .notice-text {
color: #008b63; color: #008b63;
} }

29
scripts/ui_playwright_smoke.py

@ -211,6 +211,10 @@ def run_browser_flow(base_url: str) -> None:
page.get_by_role("menu").get_by_role("button", name="Download").wait_for() page.get_by_role("menu").get_by_role("button", name="Download").wait_for()
page.get_by_role("menu").get_by_role("button", name="Reconcile").wait_for() page.get_by_role("menu").get_by_role("button", name="Reconcile").wait_for()
page.screenshot(path=str(OUTPUT_DIR / "files-grid-menu.png"), full_page=True) page.screenshot(path=str(OUTPUT_DIR / "files-grid-menu.png"), full_page=True)
page.mouse.click(40, 120)
page.get_by_role("menu").wait_for(state="detached")
page.locator(".drive-grid .drive-item", has_text="playwright-preview.png").get_by_role("button", name="More").click()
page.get_by_role("menu").wait_for()
page.keyboard.press("Escape") page.keyboard.press("Escape")
page.get_by_role("menu").wait_for(state="detached") page.get_by_role("menu").wait_for(state="detached")
page.locator(".drive-grid .drive-item", has_text="playwright-preview.png").get_by_role("button", name="More").click() page.locator(".drive-grid .drive-item", has_text="playwright-preview.png").get_by_role("button", name="More").click()
@ -263,12 +267,14 @@ def run_browser_flow(base_url: str) -> None:
page.get_by_role("button", name="Edit").first.click() page.get_by_role("button", name="Edit").first.click()
page.get_by_role("dialog").get_by_text("Stored secrets").wait_for() page.get_by_role("dialog").get_by_text("Stored secrets").wait_for()
page.screenshot(path=str(OUTPUT_DIR / "storage-edit-dialog.png"), full_page=True) page.screenshot(path=str(OUTPUT_DIR / "storage-edit-dialog.png"), full_page=True)
page.get_by_role("dialog").get_by_role("button", name="Cancel").click() page.mouse.click(24, 24)
page.get_by_role("dialog").wait_for(state="detached")
page.screenshot(path=str(OUTPUT_DIR / "storage-page.png"), full_page=True) page.screenshot(path=str(OUTPUT_DIR / "storage-page.png"), full_page=True)
page.get_by_role("button", name="Add Backend").click() page.get_by_role("button", name="Add Backend").click()
page.get_by_role("dialog").get_by_label("Config JSON").wait_for() page.get_by_role("dialog").get_by_label("Config JSON").wait_for()
page.screenshot(path=str(OUTPUT_DIR / "storage-create-dialog.png"), full_page=True) page.screenshot(path=str(OUTPUT_DIR / "storage-create-dialog.png"), full_page=True)
page.get_by_role("dialog").get_by_role("button", name="Cancel").click() page.mouse.click(24, 24)
page.get_by_role("dialog").wait_for(state="detached")
page.goto(f"{base_url}/app/policies", wait_until="networkidle") page.goto(f"{base_url}/app/policies", wait_until="networkidle")
page.get_by_role("button", name="Save Policy").wait_for() page.get_by_role("button", name="Save Policy").wait_for()
@ -281,6 +287,7 @@ def run_browser_flow(base_url: str) -> None:
page.locator(".jobs-table-row").first.click() page.locator(".jobs-table-row").first.click()
page.locator(".jobs-detail-panel", has_text="Job Detail").wait_for() page.locator(".jobs-detail-panel", has_text="Job Detail").wait_for()
page.locator(".payload-preview").wait_for() page.locator(".payload-preview").wait_for()
assert_jobs_id_layout(page, failures)
page.screenshot(path=str(OUTPUT_DIR / "jobs-page.png"), full_page=True) page.screenshot(path=str(OUTPUT_DIR / "jobs-page.png"), full_page=True)
mobile_context = browser.new_context(viewport={"width": 390, "height": 844}) mobile_context = browser.new_context(viewport={"width": 390, "height": 844})
@ -370,5 +377,23 @@ def assert_pagination_controls(page, label: str, failures: list[str]) -> None:
failures.append(f"{label} page size selector missing") failures.append(f"{label} page size selector missing")
def assert_jobs_id_layout(page, failures: list[str]) -> None: # type: ignore[no-untyped-def]
overlaps = page.locator(".jobs-detail-meta > div").evaluate_all(
"""(nodes) => nodes
.filter((node) => node.textContent && node.textContent.includes("ID"))
.map((node) => {
const label = node.querySelector("span");
const value = node.querySelector("strong");
if (!label || !value) return false;
const left = label.getBoundingClientRect();
const right = value.getBoundingClientRect();
return !(left.right <= right.left || right.right <= left.left || left.bottom <= right.top || right.bottom <= left.top);
})
.some(Boolean)"""
)
if overlaps:
failures.append("Jobs detail ID overlaps its label")
if __name__ == "__main__": if __name__ == "__main__":
raise SystemExit(main()) raise SystemExit(main())

Loading…
Cancel
Save