From 5dee76ecd16a0dc311640a27c90f8710d9fe93b7 Mon Sep 17 00:00:00 2001 From: Saberlve Date: Wed, 15 Apr 2026 15:21:07 +0800 Subject: [PATCH] docs: add import floating window design spec Co-Authored-By: Claude Opus 4.6 --- ...026-04-15-import-floating-window-design.md | 249 ++++++++++++++++++ 1 file changed, 249 insertions(+) create mode 100644 docs/superpowers/specs/2026-04-15-import-floating-window-design.md diff --git a/docs/superpowers/specs/2026-04-15-import-floating-window-design.md b/docs/superpowers/specs/2026-04-15-import-floating-window-design.md new file mode 100644 index 0000000..fe740fa --- /dev/null +++ b/docs/superpowers/specs/2026-04-15-import-floating-window-design.md @@ -0,0 +1,249 @@ +# Import Floating Window Design + +## Overview + +This design replaces the current full-screen import modal with a **draggable, resizable, minimizable floating window**. The user can freely position the literature selector over the main page, minimizing it when not in use, maximizing it when focusing on selection, and closing it without losing context. + +**What changes:** The import experience shifts from a full-screen modal overlay to an independent floating panel. + +**What stays the same:** Collection tree navigation, item browsing, checkbox multi-selection across collections, abstract preview behavior, and batch import flow all remain identical. + +--- + +## Window Attributes + +| Attribute | Value | +|-----------|-------| +| Default size | `width: 90vw; height: 75vh` | +| Default position | Horizontally and vertically centered | +| Minimum size | `min-width: 40rem; min-height: 30rem` | +| z-index | `40` (same as current modal) | +| Border radius | `16px` (rounded corners to distinguish from full-screen modal) | +| Shadow | Enhanced shadow: `0 24px 64px rgba(35, 31, 21, 0.22)` | +| Background | `var(--surface)` | + +--- + +## Window Structure + +``` +┌─────────────────────────────────────────────────────────┐ +│ [≡] Import From Zotero 当前项目:xxx [_][□][×] │ ← toolbar (draggable) +├──────────────────────┬──────────────────────────────────┤ +│ Collections │ Collection Items │ +│ ├─ Collection A (5) │ ☑ Title 1 2023 [i] │ +│ ├─ Collection B (3) │ ☑ Title 2 2021 [i] │ +│ └─ Collection C (8) │ ☐ Title 3 2019 [i] │ +│ │ ... │ +├──────────────────────┴──────────────────────────────────┤ +│ 已选 3 篇 [清空选择] [导入所选] │ ← bottom action bar +└─────────────────────────────────────────────────────────┘ +``` + +### Toolbar (Draggable Header) + +- Left: grab handle icon `[≡]` + title "Import From Zotero" + project label +- Right: window control buttons `[_]` `[□]` `[×]` +- Cursor: `grab` on toolbar, `grabbing` while dragging +- Dragging moves the window by delta from pointer movement + +### Window Controls + +- **`_` (Minimize):** Reduces window to a small bar at bottom-right of viewport (`height: 2.5rem; width: 14rem`). The bar shows "导入文献" label + `[□]` button. Clicking the bar restores the window. +- **`□` (Maximize):** Expands window to `100vw × 100vh` (same as current modal). Clicking again restores to previous size/position. +- **`×` (Close):** Closes the window and clears session state for the window (position/size/isMinimized/isMaximized). + +### Bottom Action Bar + +- Left: selected count ("已选 N 篇") +- Right: "清空选择" + "导入所选" +- Same behavior as current modal + +### Internal Layout + +- `display: grid; grid-template-columns: 22rem minmax(0, 1fr); min-height: 0;` +- Same two-column layout as current modal (collection tree left, items right) + +--- + +## Dragging Behavior + +- **Drag handle:** Only the toolbar area is draggable +- **Implementation:** `pointerdown` on toolbar → `pointermove` globally to update `left`/`top` → `pointerup` to end +- **Boundary constraint:** `left >= 0`, `top >= 0`, `left + width <= viewport.width`, `top + height <= viewport.height` +- **Restore on first open:** If session state has no saved position, center the window + +--- + +## Minimized State + +- Window becomes a thin bar fixed to bottom-right corner of viewport +- Bar contents: "导入文献" text + `[□]` restore button +- Bar `z-index: 50` (above main window and main page elements) +- Clicking the bar restores window to last saved size/position +- When restored, window returns to last non-minimized state (position, size, maximized or not) + +--- + +## Maximized State + +- Window expands to fill the entire viewport (`width: 100vw; height: 100vh; left: 0; top: 0`) +- Same layout and behavior as the original full-screen modal +- The `[□]` button toggles between maximized and restored state + +--- + +## Session State + +Key: `zotero-kb.import-window` in `sessionStorage` + +```json +{ + "x": 120, + "y": 80, + "width": 1100, + "height": 700, + "isMinimized": false, + "isMaximized": false +} +``` + +**Rules:** +- Read on page load; if empty, use default centered position +- Write on every state change (move, resize, minimize, maximize, restore) +- Clear on browser session end +- Window opening behavior: if `isMinimized`, show minimized bar; if `isMaximized`, show maximized; otherwise restore `x, y, width, height` + +--- + +## CSS Changes Summary + +### New Classes + +```css +.window-frame { + position: fixed; + z-index: 40; + width: var(--fw-width, 90vw); + height: var(--fw-height, 75vh); + left: var(--fw-left, calc((100vw - var(--fw-width, 90vw)) / 2)); + top: var(--fw-top, calc((100vh - var(--fw-height, 75vh)) / 2)); + min-width: 40rem; + min-height: 30rem; + border-radius: 16px; + box-shadow: 0 24px 64px rgba(35, 31, 21, 0.22); + display: grid; + grid-template-rows: auto 1fr auto; + overflow: hidden; + background: var(--surface); +} + +.window-frame.maximized { + width: 100vw; + height: 100vh; + left: 0; + top: 0; + border-radius: 0; +} + +.window-frame.minimized { + display: none; +} + +.window-minimized-bar { + position: fixed; + bottom: 1rem; + right: 1rem; + z-index: 50; + height: 2.5rem; + width: 14rem; + border-radius: 8px; + background: var(--surface); + border: 1px solid var(--line); + box-shadow: 0 8px 24px rgba(35, 31, 21, 0.14); + display: flex; + align-items: center; + padding: 0 0.75rem; + gap: 0.5rem; + cursor: pointer; +} + +.window-minimized-bar:hover { + background: var(--panel); +} +``` + +### Removed / Modified + +- Remove `.modal-shell`, `.modal-overlay`, `.modal-card` full-screen overlay styles +- Replace with `.window-frame` for the floating import window + +--- + +## State Keys + +| Key | Type | Description | +|-----|------|-------------| +| `isImportWindowOpen` | boolean | Whether the floating window is open (not minimized) | +| `windowX`, `windowY` | number | Pixel position of window top-left | +| `windowWidth`, `windowHeight` | number | Pixel dimensions | +| `isWindowMinimized` | boolean | Whether window is in minimized bar state | +| `isWindowMaximized` | boolean | Whether window is in full-screen state | + +All persisted to `sessionStorage` key `zotero-kb.import-window`. + +--- + +## Interaction Changes + +| Action | New Behavior | +|--------|-------------| +| Click `导入文献` | Open floating window at saved or default position | +| Drag toolbar | Move window freely within viewport | +| Click `_` | Minimize to bottom-right bar | +| Click minimized bar | Restore window | +| Click `□` | Toggle maximized (full-screen ↔ restored) | +| Click `×` | Close window, clear import window session state | +| Press `Esc` | Same as clicking `×` (close) | + +--- + +## What Stays The Same + +- Collection tree rendering and expand/collapse +- Item list rendering with checkbox, title, year, details button +- Cross-collection selection persistence +- Abstract hover preview (3s delay) +- Selection count, clear, and import actions +- Import API call and success handling (refresh cards, close window) +- Disabled import button when no project is selected + +--- + +## Constraints and Tradeoffs + +### Why Not Actual `window.open`? + +A native browser window would create separate JavaScript context, requiring `postMessage` for state synchronization with the main page. A DOM-based floating panel keeps all state in one JavaScript context and is lighter weight. + +### Why Minimize to Bar, Not Just Hide? + +The user wants the literature selector to be accessible without disrupting the main page. A minimize-to-bar pattern (like VSCode) keeps the entry point visible without cluttering the viewport. + +### Why 90vw × 75vh Default? + +This is large enough to browse a collection tree and item list comfortably, while leaving the main page visible on large monitors — supporting the user's workflow of referencing the main page while selecting. + +--- + +## Acceptance Criteria + +- [ ] Floating window opens at centered 90vw × 75vh on first open +- [ ] Window position/size persists across page refreshes in the same session +- [ ] Toolbar dragging moves the window freely within viewport bounds +- [ ] `_` button minimizes to bottom-right bar; bar click restores +- [ ] `□` button maximizes to full-screen and restores to previous size +- [ ] `×` button closes and clears window session state +- [ ] `Esc` key closes the window +- [ ] Main page remains interactive when the floating window is minimized +- [ ] All existing import behaviors (collection tree, item selection, import flow) are unchanged