Let users pick and order their workbench module tiles.

Per-user layout lives in the new app_user.prefs JSON column, read via
/v1/auth/me and written via PUT /v1/auth/me/prefs/workbench. The
workbench gets an in-place edit mode (gear -> drag to reorder, x to
remove, tray to add back, reset/done). Reset stores empty prefs so
registry changes still apply; ids of modules the user can't currently
see are kept on save.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
oliver 2026-10-13 08:31:51 +08:00
parent 313706595d
commit 2307f19779
16 changed files with 906 additions and 23 deletions

View file

@ -29,6 +29,53 @@
/* Modules: one row of primary tiles, then a quiet row of admin links. */
.wb-modules { padding: var(--wb-pad); display: flex; flex-direction: column; gap: 14px; }
.wb-modules__toolbar { display: flex; justify-content: flex-end; margin: -4px 0 -6px; min-height: 28px; }
.app--shell .app-main .wb-modules__gear {
width: 28px; height: 28px; min-height: 0; padding: 0; margin: 0;
display: inline-flex; align-items: center; justify-content: center;
border: 0; border-radius: var(--nx-radius-sm); background: transparent;
color: var(--nx-text-faint); box-shadow: none;
}
.app--shell .app-main .wb-modules__gear:hover { background: rgb(255 255 255 / .06); color: var(--nx-text); }
.wb-modules__gear svg { width: 16px; height: 16px; }
.wb-modules__empty {
grid-column: 1 / -1; min-height: var(--wb-tile-h);
display: flex; align-items: center; justify-content: center;
}
.app--shell .app-main .wb-modules__empty-btn {
height: auto; min-height: 40px; padding: 8px 16px; border: 1px dashed var(--nx-line);
border-radius: 12px; background: transparent; color: var(--nx-text-muted); box-shadow: none;
}
.app--shell .app-main .wb-modules__empty-btn:hover { background: rgb(255 255 255 / .04); color: var(--nx-text); border-color: var(--nx-line-strong); }
.wb-tile-wrap { position: relative; min-width: 0; }
.wb-modules--editing .wb-tile-wrap--edit .wb-tile { border-color: var(--nx-line); cursor: grab; }
.wb-modules--editing .wb-tile-wrap--edit.is-dragging .wb-tile { opacity: .55; cursor: grabbing; }
.wb-modules--editing .wb-tile-wrap--edit .wb-tile:hover { transform: none; }
.app--shell .app-main .wb-tile__remove {
position: absolute; top: 4px; right: 4px; z-index: 2;
width: 22px; height: 22px; padding: 0; margin: 0;
display: inline-flex; align-items: center; justify-content: center;
border: 1px solid var(--nx-line); border-radius: 999px;
background: rgb(15 23 42 / .72); color: var(--nx-text-muted); cursor: pointer;
}
.app--shell .app-main .wb-tile__remove:hover { color: var(--nx-text); border-color: var(--nx-line-strong); }
.wb-tile__remove svg { width: 12px; height: 12px; }
.wb-modules__tray { display: flex; flex-direction: column; gap: 8px; padding-top: 4px; }
.wb-modules__tray-label { font-size: 12px; font-weight: 500; color: var(--nx-text-muted); }
.wb-modules__tray-grid {
display: grid; grid-template-columns: repeat(auto-fill, minmax(max(88px, 16%), 1fr));
grid-auto-rows: calc(var(--wb-tile-h) * .85); gap: 10px;
}
.app--shell .app-main .wb-tile--available {
opacity: .55; border-style: dashed; border-color: var(--nx-line); min-height: 0;
background: rgb(255 255 255 / .02);
}
.app--shell .app-main .wb-tile--available:hover {
opacity: .85; transform: none;
border-color: var(--nx-line-strong);
background: rgb(255 255 255 / .05);
}
.wb-modules__actions { display: flex; justify-content: flex-end; gap: 8px; padding-top: 4px; }
/* Fixed-size tiles fill rows in order; the panel's spare height is room for new modules. */
.wb-section--operations { display: grid; grid-template-columns: repeat(auto-fill, minmax(max(88px, 16%), 1fr)); grid-auto-rows: var(--wb-tile-h); gap: 12px; }
.app--shell .app-main .wb-tile {