You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by wu...@apache.org on 2026/06/25 01:13:59 UTC

(skywalking-horizon-ui) branch fix/dashboard-editor-controls created (now 2b9c6f5)

This is an automated email from the ASF dual-hosted git repository.

wu-sheng pushed a change to branch fix/dashboard-editor-controls
in repository https://gitbox.apache.org/repos/asf/skywalking-horizon-ui.git


      at 2b9c6f5  fix(layer-dashboards): keep the widget editor's move / delete controls in view

This branch includes the following new commits:

     new 2b9c6f5  fix(layer-dashboards): keep the widget editor's move / delete controls in view

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(skywalking-horizon-ui) 01/01: fix(layer-dashboards): keep the widget editor's move / delete controls in view

Posted by wu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

wu-sheng pushed a commit to branch fix/dashboard-editor-controls
in repository https://gitbox.apache.org/repos/asf/skywalking-horizon-ui.git

commit 2b9c6f560956991a125200127549914c5673218c
Author: Wu Sheng <wu...@foxmail.com>
AuthorDate: Thu Jun 25 09:13:49 2026 +0800

    fix(layer-dashboards): keep the widget editor's move / delete controls in view
    
    The per-widget editor drawer kept Up / Down / Delete as the last block inside a
    scrolling body, and its fixed height (calc(100vh - 52px)) overshot because the
    drawer sits below the editor card's header — so the drawer's bottom, with the
    Delete button, fell below the viewport, hidden behind a scrollbar even on a
    short board with few widgets.
    
    Move Up / Down / Delete into a pinned footer outside the scroll region, and size
    the drawer in JS from its live top offset down to the viewport bottom (re-synced
    on open, scroll, and resize) so the footer is always visible regardless of pin
    state or board height. Also scroll a newly-added widget into view, next to the
    editor that opens for it, instead of leaving it off-screen at the canvas bottom.
---
 CHANGELOG.md                                       |  4 ++
 .../admin/layer-templates/LayerDashboardsAdmin.vue | 53 +++++++++++++++++++---
 2 files changed, 51 insertions(+), 6 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1d547cb..cb9234e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -35,6 +35,10 @@ The version line is shared by every package in the monorepo (apps + shared packa
 
 - **Single-value metrics now render as cards, not flat lines, on several layer dashboards.** Widgets whose expression collapses the window to one number (a `latest(...)` total) had been mis-ported as line charts — drawn as a lone dot that misreads as a time series and shares one axis with an unrelated average trend. Each is now split into a proper single-value **card** (the total) plus a trend **line** (the average), matching the metric's shape, the way booster-ui rendered them. Affects  [...]
 
+### Layer dashboard editor
+
+- **The widget editor's move / delete controls stay reachable.** On the Layer dashboards admin, the per-widget editor's `Up` / `Down` / `Delete` row is now a pinned footer instead of the last thing in a scrolling panel, and the panel sizes itself to the visible area — so Delete no longer slips below the fold on a long form or a short board. Adding a widget also scrolls the new widget into view, next to the editor that opens for it, instead of leaving it off-screen at the bottom of the canvas.
+
 ## 0.7.0
 
 ### Browser errors & source maps
diff --git a/apps/ui/src/features/admin/layer-templates/LayerDashboardsAdmin.vue b/apps/ui/src/features/admin/layer-templates/LayerDashboardsAdmin.vue
index e3ac142..52288fe 100644
--- a/apps/ui/src/features/admin/layer-templates/LayerDashboardsAdmin.vue
+++ b/apps/ui/src/features/admin/layer-templates/LayerDashboardsAdmin.vue
@@ -600,9 +600,29 @@ watch(
 // without resizing the nav, so refresh on those too.
 watch([visibleScopes, activeScope], () => void nextTick(updateScopeScroll));
 function onWinResizeScope(): void { updateScopeScroll(); }
-onMounted(() => window.addEventListener('resize', onWinResizeScope));
+
+// ── Editor drawer height. The drawer is a sticky sidebar that sits below the
+// card header, so a fixed `100vh - 52px` overshoots (the header offsets the
+// drawer's top) and pushes its pinned footer — Up / Down / Delete — below the
+// fold; worst on a short board you can't scroll far enough to pin. Size it to
+// the live space from wherever it currently sits down to the viewport bottom
+// instead, so the footer is always visible. Scroll listens in the capture
+// phase so the inner content pane's scroll (not just window) re-syncs it.
+const drawerEl = ref<HTMLElement | null>(null);
+function syncDrawerHeight(): void {
+  const el = drawerEl.value;
+  if (!el) return;
+  el.style.height = `${Math.max(220, window.innerHeight - el.getBoundingClientRect().top - 8)}px`;
+}
+onMounted(() => {
+  window.addEventListener('resize', onWinResizeScope);
+  window.addEventListener('scroll', syncDrawerHeight, { capture: true, passive: true });
+  window.addEventListener('resize', syncDrawerHeight, { passive: true });
+});
 onBeforeUnmount(() => {
   window.removeEventListener('resize', onWinResizeScope);
+  window.removeEventListener('scroll', syncDrawerHeight, { capture: true });
+  window.removeEventListener('resize', syncDrawerHeight);
   scopeResizeObs?.disconnect();
   scopeResizeObs = null;
 });
@@ -688,6 +708,9 @@ function moveWidget(i: number, dir: -1 | 1): void {
  * ------------------------------------------------------------------- */
 
 const selectedIdx = ref<number | null>(null);
+/* Re-fit the drawer to the viewport when it opens / the target widget changes
+ * (content height differs); the scroll + resize listeners keep it fitted after. */
+watch(selectedIdx, () => void nextTick(syncDrawerHeight));
 
 /** When the user switches scope or layer we drop the selection so the
  *  drawer doesn't refer to a widget that no longer exists. */
@@ -910,6 +933,14 @@ async function addAndSelectWidget(): Promise<void> {
   addWidget();
   await nextTick();
   selectedIdx.value = currentWidgets.value.length - 1;
+  await nextTick();
+  /* The new widget is appended at the canvas bottom — on a tall board it lands
+   * below the fold. Scroll it into view so the operator sees the widget next to
+   * the editor that just opened, instead of an empty drawer over off-screen
+   * canvas. block:'center' keeps the editor header + footer in frame too. */
+  canvasEl.value
+    ?.querySelector('.canvas-widget.selected')
+    ?.scrollIntoView({ behavior: 'smooth', block: 'center' });
 }
 
 /* ------------------------------------------------------------------- *
@@ -3761,7 +3792,7 @@ const namingTest = computed<NamingTestResult>(() => {
               </div>
             </div>
 
-            <aside v-if="selectedWidget" class="drawer">
+            <aside v-if="selectedWidget" ref="drawerEl" class="drawer">
               <div class="drawer-head">
                 <h4>Edit widget</h4>
                 <span class="sub">{{ scopeLabel(activeScope) }} · #{{ (selectedIdx ?? 0) + 1 }}</span>
@@ -3947,6 +3978,10 @@ const namingTest = computed<NamingTestResult>(() => {
                     <span>Layer-scoped (run MQE across the whole layer, ignore selected service)</span>
                   </label>
                 </div>
+              </div>
+              <!-- Pinned footer: move/delete stay visible no matter how long
+                   the form scrolls (the body above owns the overflow). -->
+              <div class="drawer-foot">
                 <div class="d-actions">
                   <button
                     class="sw-btn"
@@ -5503,7 +5538,10 @@ const namingTest = computed<NamingTestResult>(() => {
   position: sticky;
   top: 0;
   align-self: start;
-  height: calc(100vh - 52px);
+  /* Height is set in JS (syncDrawerHeight) to the live space from the drawer's
+   * top to the viewport bottom, so the pinned footer never falls below the
+   * fold. This calc is just the pre-JS / no-JS fallback. */
+  height: calc(100vh - 96px);
   max-height: calc(100vh - 52px);
 }
 .drawer-head {
@@ -5696,12 +5734,15 @@ const namingTest = computed<NamingTestResult>(() => {
   margin-top: 2px;
   accent-color: var(--sw-accent);
 }
+.drawer-foot {
+  flex-shrink: 0;
+  padding: 8px 14px;
+  border-top: 1px solid var(--sw-line);
+  background: var(--sw-bg-1);
+}
 .d-actions {
   display: flex;
   gap: 6px;
-  padding-top: 4px;
-  border-top: 1px dashed var(--sw-line);
-  margin-top: 4px;
 }
 .d-actions .sw-btn { font-size: 11px; }
 .d-actions .sw-btn.danger { margin-left: auto; }