Use modals instead of tabs for web history and changes #82

Closed
opened 2026-04-09 07:19:40 +00:00 by maxtkc · 0 comments
Owner

Summary

Replace the three right-panel DaisyUI radio tabs (Browse, Files, Changes) with a simpler layout: Browse becomes the permanent right-panel content (no tabs), Files and History are opened from the navbar via buttons. The "History" button (renamed from "Changes") sits between the undo and redo buttons. Undo/redo get grey-out state and descriptive hover tooltips. Mobile dock gets "Changes" renamed to "History" and wired to open the modal instead of switching a tab.

The approach avoids creating/destroying the modal DOM on each open (which would break CodeMirror/the table editor) — instead both modals are persistent <dialog> elements toggled open/closed, with the existing content divs moved inside them.

Relevant Context

  • src/index.html — all the HTML changes: right panel tab structure, navbar buttons, modal elements, mobile dock
  • src/modules/ui.tsinitializeTabs(), handleTabChange(), showFileList(), showFileEditor(), showFileInEditor() all need updates; these target #file-list-view and #file-editor-view by ID, which work as long as the IDs stay the same inside the modal
  • src/modules/history-controller.tsrender() targets #changes-panel; currently triggers on changes-tab-radio change event; needs to trigger on modal open instead
  • src/modules/tab-manager.tsswitchToTab() / getActiveTab() / onTabChange() all query input[name="main_tabs"]; after the tab radio inputs are removed these become no-ops. The class can stay but methods should be safe no-ops or stubs so callers don't break.
  • src/modules/bottom-sheet.tssetupDock() wires dock-files/dock-changes to tabManager.switchToTab(); needs to open modals instead
  • src/modules/keyboard-shortcuts.tsctrl+1/2/3/4 shortcuts call switchToTab(); should be removed (no longer meaningful)
  • src/index.tssetupNavigationTabSwitching() calls switchToTab('browse') and listens to tab change events; both become no-ops. Also imports humanLabel — reuse for undo/redo tooltips. The undo/redo buttons are wired via onclick in HTML; state update logic (grey-out, tooltip) goes here, listening to patchManager events.
  • src/modules/patch-manager.ts — has canUndo: boolean, canRedo: boolean, version: number, getHistory() (async). Events: 'change', 'undo', 'redo', 'jump'. Use getHistory() to find the next undo/redo patch for tooltip text.

Phases


Phase 1: HTML restructure

Goal: Strip the tab scaffolding from the right panel, add persistent modal elements for Files and History, and add the three new navbar buttons (Files icon, History, the existing undo/redo are already there but need wrapping adjustments).

  • Remove the <div class="tabs tabs-bordered …"> wrapper and all three <input type="radio" name="main_tabs" …> elements from the right panel
  • Remove the class="tab hidden md:flex" inputs and the class="tab-content …" wrapper divs — keep only the content divs that were inside them:
    • Browse content: #browse-list-view and #object-details-view become direct children of #right-panel (wrapped in a flex-1 overflow-hidden h-full div)
    • Files content: #file-list-view and #file-editor-view move into the Files modal (see below)
    • Changes content: #changes-panel moves into the History modal (see below)
  • Add a persistent Files modal <dialog id="files-modal" class="modal"> near the bottom of <body> containing:
    • A modal-box with w-11/12 max-w-2xl max-h-[90vh] flex flex-col p-0 (generous size)
    • A header row with title "Files" and a close button
    • #file-list-view and #file-editor-view moved inside the modal body (they keep their IDs — UIController targets them by ID and will work unchanged)
  • Add a persistent History modal <dialog id="history-modal" class="modal"> similarly:
    • modal-box with max-h-[80vh] overflow-y-auto
    • Title "History" and close button
    • #changes-panel inside the modal body
  • In the navbar, add a Files icon button (id="files-btn") in navbar-end, before the undo/redo group:
    • Use a folder/files SVG icon, btn btn-ghost btn-sm btn-square, title="Files"
  • Between undo and redo, add a History button (id="history-btn") labeled "History":
    • btn btn-ghost btn-sm, small text label "History" (no icon needed per issue)
  • Make the undo/redo buttons structurally tooltip-capable: wrap each in a <div class="tooltip" id="undo-tooltip"> and <div class="tooltip" id="redo-tooltip"> with data-tip set initially to "Nothing to undo" / "Nothing to redo". Keep existing button IDs.
  • In the mobile dock, rename <span class="dock-label">Changes</span>"History" and update id="dock-changes" → keep ID but the label changes (id is fine to keep; code references it)

Gotcha: #table-editor (the editor mount point) is inside #file-editor-view. Moving it into the modal is safe as long as it happens before Editor.initialize() runs — which it will, since both happen at page load. The Editor class mounts into #table-editor once on init.

Discoveries: Undo/redo buttons were given disabled attribute by default (since nothing is undoable on fresh load); Phase 3 will enable/disable them dynamically. Both modals include a <form method="dialog" class="modal-backdrop"> so clicking the backdrop closes them (standard DaisyUI pattern). Files button is hidden md:flex since mobile uses the dock instead. History button is also hidden md:flex for the same reason.


Phase 2: Wire Files and History modals in TypeScript

Goal: Make the new buttons open their respective modals, update UIController to work inside the modal, update HistoryController to render on modal open.

src/modules/ui.ts

  • Remove initializeTabs() and handleTabChange() entirely (no tab radios left)
  • Remove the initializeTabs() call from initialize()
  • showFileList() — no change needed (still toggles hidden on #file-list-view / #file-editor-view)
  • showFileEditor() — no change needed
  • showFileInEditor() — replaced the [data-tab-name="files"] click with files-modal showModal()
  • In loadGTFSFile(), added files-modal showModal() after this.showFileList()
  • Added event listener for #files-btn click in setupEventListeners() — opens Files modal + calls updateFileList()
  • #back-to-files — already wired to this.showFileList(), no change needed

src/modules/history-controller.ts

  • Removed the changes-tab-radio event listener from initialize()
  • index.ts wires #history-btn click → showModal() + historyController.render()

src/modules/tab-manager.ts

  • switchToTab() — safe no-op with deprecation warning
  • getActiveTab() — returns 'browse' always
  • onTabChange() — no-op

src/modules/bottom-sheet.ts

  • setupDock() now accepts openHistoryModal: (() => void) | null callback (passed via constructor)
  • dock-files click opens files modal directly
  • dock-changes click calls openHistoryModal?.()
  • dock-browse click keeps this.open('half') with local updateDockActive call
  • Removed tabManager.onTabChange(updateDockActive) — dock-active updated locally on each click

src/modules/keyboard-shortcuts.ts

  • Removed ctrl+1, ctrl+2, ctrl+3, ctrl+4 tab-switching shortcuts

src/index.ts

  • setupNavigationTabSwitching() — removed tabManager.switchToTab('browse') and tabManager.onTabChange() listener; only bottomSheet?.open('half') remains
  • #history-btn click → opens history modal + historyController.render()
  • Modal close buttons handled by <form method="dialog"> backdrop (no extra JS needed)

Phase 3: Undo/redo button state

Goal: Grey out undo/redo when unavailable; show a descriptive tooltip on hover with what will be undone/redone.

All changes in src/index.ts:

  • Add a method updateUndoRedoState() that:
    1. Reads patchManager.canUndo and patchManager.canRedo
    2. Toggles disabled attribute on #undo-btn / #redo-btn (no btn-disabled class needed — disabled attribute suffices)
    3. Calls patchManager.getHistory() async, finds:
      • Next undo patch: the entry with version === patchManager.version (highest applied)
      • Next redo patch: the entry with version === patchManager.version + 1 (first unapplied)
    4. Updates data-tip on #undo-tooltip / #redo-tooltip:
      • Undo available: "Undo: ${humanLabel(patch)}"; unavailable: "Nothing to undo"
      • Redo available: "Redo: ${humanLabel(patch)}"; unavailable: "Nothing to redo"
  • Call updateUndoRedoState() on these patchManager events: 'change', 'undo', 'redo', 'jump'
  • Call updateUndoRedoState() once on startup (after patchManager is initialized) so buttons start in the correct state (likely disabled since nothing has been done yet)

Gotcha: btn-disabled in DaisyUI is a CSS class that visually disables but doesn't prevent click. Also set the disabled HTML attribute on the <button> so the onclick handler is suppressed. The existing undo/redo buttons use onclick="window.gtfsEditor?.undoEdit()" — setting disabled on the button will suppress this naturally.


Original Issue

The tabs aren't that great of an idiom for this. Lets use buttons in the top bar to open the file view and the history.

  • For file view, just a files icon will work
  • For history, lets place it between the back and forward. We can rename it "History"
  • Lets take the opportunity to make back and forward show what they will do when you click them (through a hover) and go grey when they wont do anything.

Mobile will not change significantly, besides names and such.

## Summary Replace the three right-panel DaisyUI radio tabs (Browse, Files, Changes) with a simpler layout: Browse becomes the permanent right-panel content (no tabs), Files and History are opened from the navbar via buttons. The "History" button (renamed from "Changes") sits between the undo and redo buttons. Undo/redo get grey-out state and descriptive hover tooltips. Mobile dock gets "Changes" renamed to "History" and wired to open the modal instead of switching a tab. The approach avoids creating/destroying the modal DOM on each open (which would break CodeMirror/the table editor) — instead both modals are persistent `<dialog>` elements toggled open/closed, with the existing content divs moved inside them. ## Relevant Context - **`src/index.html`** — all the HTML changes: right panel tab structure, navbar buttons, modal elements, mobile dock - **`src/modules/ui.ts`** — `initializeTabs()`, `handleTabChange()`, `showFileList()`, `showFileEditor()`, `showFileInEditor()` all need updates; these target `#file-list-view` and `#file-editor-view` by ID, which work as long as the IDs stay the same inside the modal - **`src/modules/history-controller.ts`** — `render()` targets `#changes-panel`; currently triggers on `changes-tab-radio` change event; needs to trigger on modal open instead - **`src/modules/tab-manager.ts`** — `switchToTab()` / `getActiveTab()` / `onTabChange()` all query `input[name="main_tabs"]`; after the tab radio inputs are removed these become no-ops. The class can stay but methods should be safe no-ops or stubs so callers don't break. - **`src/modules/bottom-sheet.ts`** — `setupDock()` wires dock-files/dock-changes to `tabManager.switchToTab()`; needs to open modals instead - **`src/modules/keyboard-shortcuts.ts`** — `ctrl+1/2/3/4` shortcuts call `switchToTab()`; should be removed (no longer meaningful) - **`src/index.ts`** — `setupNavigationTabSwitching()` calls `switchToTab('browse')` and listens to tab change events; both become no-ops. Also imports `humanLabel` — reuse for undo/redo tooltips. The undo/redo buttons are wired via `onclick` in HTML; state update logic (grey-out, tooltip) goes here, listening to patchManager events. - **`src/modules/patch-manager.ts`** — has `canUndo: boolean`, `canRedo: boolean`, `version: number`, `getHistory()` (async). Events: `'change'`, `'undo'`, `'redo'`, `'jump'`. Use `getHistory()` to find the next undo/redo patch for tooltip text. ## Phases --- ### Phase 1: HTML restructure **Goal:** Strip the tab scaffolding from the right panel, add persistent modal elements for Files and History, and add the three new navbar buttons (Files icon, History, the existing undo/redo are already there but need wrapping adjustments). - [x] Remove the `<div class="tabs tabs-bordered …">` wrapper and all three `<input type="radio" name="main_tabs" …>` elements from the right panel - [x] Remove the `class="tab hidden md:flex"` inputs and the `class="tab-content …"` wrapper divs — keep only the *content* divs that were inside them: - Browse content: `#browse-list-view` and `#object-details-view` become direct children of `#right-panel` (wrapped in a `flex-1 overflow-hidden h-full` div) - Files content: `#file-list-view` and `#file-editor-view` move into the Files modal (see below) - Changes content: `#changes-panel` moves into the History modal (see below) - [x] Add a persistent **Files modal** `<dialog id="files-modal" class="modal">` near the bottom of `<body>` containing: - A `modal-box` with `w-11/12 max-w-2xl max-h-[90vh] flex flex-col p-0` (generous size) - A header row with title "Files" and a close button - `#file-list-view` and `#file-editor-view` moved inside the modal body (they keep their IDs — UIController targets them by ID and will work unchanged) - [x] Add a persistent **History modal** `<dialog id="history-modal" class="modal">` similarly: - `modal-box` with `max-h-[80vh] overflow-y-auto` - Title "History" and close button - `#changes-panel` inside the modal body - [x] In the navbar, add a **Files icon button** (`id="files-btn"`) in `navbar-end`, before the undo/redo group: - Use a folder/files SVG icon, `btn btn-ghost btn-sm btn-square`, `title="Files"` - [x] Between undo and redo, add a **History button** (`id="history-btn"`) labeled "History": - `btn btn-ghost btn-sm`, small text label "History" (no icon needed per issue) - [x] Make the undo/redo buttons structurally tooltip-capable: wrap each in a `<div class="tooltip" id="undo-tooltip">` and `<div class="tooltip" id="redo-tooltip">` with `data-tip` set initially to `"Nothing to undo"` / `"Nothing to redo"`. Keep existing button IDs. - [x] In the mobile dock, rename `<span class="dock-label">Changes</span>` → `"History"` and update `id="dock-changes"` → keep ID but the label changes (id is fine to keep; code references it) **Gotcha:** `#table-editor` (the editor mount point) is inside `#file-editor-view`. Moving it into the modal is safe as long as it happens before `Editor.initialize()` runs — which it will, since both happen at page load. The Editor class mounts into `#table-editor` once on init. **Discoveries:** Undo/redo buttons were given `disabled` attribute by default (since nothing is undoable on fresh load); Phase 3 will enable/disable them dynamically. Both modals include a `<form method="dialog" class="modal-backdrop">` so clicking the backdrop closes them (standard DaisyUI pattern). Files button is `hidden md:flex` since mobile uses the dock instead. History button is also `hidden md:flex` for the same reason. --- ### Phase 2: Wire Files and History modals in TypeScript **Goal:** Make the new buttons open their respective modals, update UIController to work inside the modal, update HistoryController to render on modal open. **`src/modules/ui.ts`** - [x] Remove `initializeTabs()` and `handleTabChange()` entirely (no tab radios left) - [x] Remove the `initializeTabs()` call from `initialize()` - [x] `showFileList()` — no change needed (still toggles hidden on `#file-list-view` / `#file-editor-view`) - [x] `showFileEditor()` — no change needed - [x] `showFileInEditor()` — replaced the `[data-tab-name="files"]` click with `files-modal` showModal() - [x] In `loadGTFSFile()`, added `files-modal` showModal() after `this.showFileList()` - [x] Added event listener for `#files-btn` click in `setupEventListeners()` — opens Files modal + calls `updateFileList()` - [x] `#back-to-files` — already wired to `this.showFileList()`, no change needed **`src/modules/history-controller.ts`** - [x] Removed the `changes-tab-radio` event listener from `initialize()` - [x] `index.ts` wires `#history-btn` click → `showModal()` + `historyController.render()` **`src/modules/tab-manager.ts`** - [x] `switchToTab()` — safe no-op with deprecation warning - [x] `getActiveTab()` — returns `'browse'` always - [x] `onTabChange()` — no-op **`src/modules/bottom-sheet.ts`** - [x] `setupDock()` now accepts `openHistoryModal: (() => void) | null` callback (passed via constructor) - [x] dock-files click opens files modal directly - [x] dock-changes click calls `openHistoryModal?.()` - [x] dock-browse click keeps `this.open('half')` with local `updateDockActive` call - [x] Removed `tabManager.onTabChange(updateDockActive)` — dock-active updated locally on each click **`src/modules/keyboard-shortcuts.ts`** - [x] Removed `ctrl+1`, `ctrl+2`, `ctrl+3`, `ctrl+4` tab-switching shortcuts **`src/index.ts`** - [x] `setupNavigationTabSwitching()` — removed `tabManager.switchToTab('browse')` and `tabManager.onTabChange()` listener; only `bottomSheet?.open('half')` remains - [x] `#history-btn` click → opens history modal + `historyController.render()` - [x] Modal close buttons handled by `<form method="dialog">` backdrop (no extra JS needed) --- ### Phase 3: Undo/redo button state **Goal:** Grey out undo/redo when unavailable; show a descriptive tooltip on hover with what will be undone/redone. All changes in **`src/index.ts`**: - [x] Add a method `updateUndoRedoState()` that: 1. Reads `patchManager.canUndo` and `patchManager.canRedo` 2. Toggles `disabled` attribute on `#undo-btn` / `#redo-btn` (no `btn-disabled` class needed — `disabled` attribute suffices) 3. Calls `patchManager.getHistory()` async, finds: - Next undo patch: the entry with `version === patchManager.version` (highest applied) - Next redo patch: the entry with `version === patchManager.version + 1` (first unapplied) 4. Updates `data-tip` on `#undo-tooltip` / `#redo-tooltip`: - Undo available: `"Undo: ${humanLabel(patch)}"`; unavailable: `"Nothing to undo"` - Redo available: `"Redo: ${humanLabel(patch)}"`; unavailable: `"Nothing to redo"` - [x] Call `updateUndoRedoState()` on these patchManager events: `'change'`, `'undo'`, `'redo'`, `'jump'` - [x] Call `updateUndoRedoState()` once on startup (after patchManager is initialized) so buttons start in the correct state (likely disabled since nothing has been done yet) **Gotcha:** `btn-disabled` in DaisyUI is a CSS class that visually disables but doesn't prevent click. Also set the `disabled` HTML attribute on the `<button>` so the `onclick` handler is suppressed. The existing undo/redo buttons use `onclick="window.gtfsEditor?.undoEdit()"` — setting `disabled` on the button will suppress this naturally. --- ## Original Issue The tabs aren't that great of an idiom for this. Lets use buttons in the top bar to open the file view and the history. - For file view, just a files icon will work - For history, lets place it between the back and forward. We can rename it "History" - Lets take the opportunity to make back and forward show what they will do when you click them (through a hover) and go grey when they wont do anything. Mobile will not change significantly, besides names and such.
maxtkc self-assigned this 2026-04-09 07:19:40 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
gtfs.zone/coloring-book#82
No description provided.