mirror of
https://github.com/bookorbit/bookorbit.git
synced 2026-11-03 00:11:37 -06:00
fix(books): restore metadata editing for multiple selected books
Routes multiple book selection to the edit tab instead of details. Fixes a bug where deactivated kept-alive sources would passively clobber the active navigation context by tracking component activation state. Converts route param computed refs to shallow refs to prevent reactivity issues during navigation. Closes #455
This commit is contained in:
@@ -206,7 +206,7 @@ describe('useBookTableShell', () => {
|
||||
handleEditIndividually()
|
||||
|
||||
expect(mocks.setBookContext).toHaveBeenCalledWith([10, 30], 2)
|
||||
expect(mocks.routerPush).toHaveBeenCalledWith({ name: 'book-detail', params: { bookId: 10 }, query: { tab: 'details' } })
|
||||
expect(mocks.routerPush).toHaveBeenCalledWith({ name: 'book-detail', params: { bookId: 10 }, query: { tab: 'edit' } })
|
||||
expect(mocks.exitSelectionMode).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineComponent, ref } from 'vue'
|
||||
import { defineComponent, nextTick, ref } from 'vue'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { BookCard } from '@bookorbit/types'
|
||||
@@ -112,6 +112,49 @@ describe('useBookViewContext', () => {
|
||||
expect(loadMore).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('does not let a deactivated kept-alive source passively clobber the active navigation context', async () => {
|
||||
const nav = useBookNavigation()
|
||||
const showSource = ref(true)
|
||||
const slots = ref<BookSlot[]>([makeBook(1), makeBook(2), makeBook(3)])
|
||||
const total = ref(3)
|
||||
const loadMore = vi.fn<() => Promise<void>>(async () => {})
|
||||
|
||||
const Source = defineComponent({
|
||||
setup() {
|
||||
useBookViewContext(slots, total, loadMore)
|
||||
return {}
|
||||
},
|
||||
template: '<div />',
|
||||
})
|
||||
|
||||
const Harness = defineComponent({
|
||||
components: { Source },
|
||||
setup() {
|
||||
return { showSource }
|
||||
},
|
||||
template: '<KeepAlive><Source v-if="showSource" /></KeepAlive>',
|
||||
})
|
||||
|
||||
const wrapper = mount(Harness)
|
||||
await nextTick()
|
||||
|
||||
nav.setBookContext([10, 20], 2)
|
||||
expect(nav.currentIndex(20)).toBe(1)
|
||||
|
||||
showSource.value = false
|
||||
await nextTick()
|
||||
|
||||
slots.value = []
|
||||
total.value = 0
|
||||
await nextTick()
|
||||
|
||||
expect(nav.bookIds.value).toEqual([10, 20])
|
||||
expect(nav.currentIndex(20)).toBe(1)
|
||||
expect(nav.total.value).toBe(2)
|
||||
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('reports the absolute slot index after a jump rail loads a far block', async () => {
|
||||
const nav = useBookNavigation()
|
||||
// First block (rows 0-1) plus a far block (rows 5000-5001) loaded; the
|
||||
|
||||
@@ -85,7 +85,7 @@ export function useBookTableShell({ books, querySelection }: BookTableShellOptio
|
||||
const orderedIds = books.value.filter((book) => selection.selectedIds.value.has(book.id)).map((book) => book.id)
|
||||
if (orderedIds.length === 0) return
|
||||
setBookContext(orderedIds, orderedIds.length)
|
||||
router.push({ name: 'book-detail', params: { bookId: orderedIds[0] }, query: { tab: 'details' } })
|
||||
router.push({ name: 'book-detail', params: { bookId: orderedIds[0] }, query: { tab: 'edit' } })
|
||||
selection.exitSelectionMode()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,36 @@
|
||||
import { onMounted, watch, type Ref } from 'vue'
|
||||
import { onActivated, onDeactivated, onMounted, watch, type Ref } from 'vue'
|
||||
import { useBookNavigation } from './useBookNavigation'
|
||||
import type { BookSlot } from './useBookWindow'
|
||||
|
||||
export function useBookViewContext(slots: Ref<BookSlot[]>, total: Ref<number>, loadMore: () => Promise<unknown> | unknown) {
|
||||
const { setBookSlotContext, registerLoadMore } = useBookNavigation()
|
||||
let active = true
|
||||
|
||||
const syncBookContext = () => {
|
||||
if (!active) return
|
||||
setBookSlotContext(slots.value, total.value)
|
||||
}
|
||||
|
||||
watch([slots, total], () => syncBookContext(), { immediate: true })
|
||||
|
||||
onMounted(() => {
|
||||
function registerContextLoader() {
|
||||
registerLoadMore(async () => {
|
||||
await loadMore()
|
||||
setBookSlotContext(slots.value, total.value)
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
registerContextLoader()
|
||||
})
|
||||
|
||||
onActivated(() => {
|
||||
active = true
|
||||
registerContextLoader()
|
||||
syncBookContext()
|
||||
})
|
||||
|
||||
onDeactivated(() => {
|
||||
active = false
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { computed, onMounted, ref, shallowRef, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { AlertTriangle, CheckSquare, FileSpreadsheet, FolderOpen, Layers, Pencil, Search, SlidersHorizontal, Square, X } from '@lucide/vue'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
@@ -46,7 +46,7 @@ const router = useRouter()
|
||||
const { viewMode, effectiveViewMode } = useEffectiveViewMode()
|
||||
const { hasPermission, isDemoRestrictedAccount } = usePermissions()
|
||||
|
||||
const collectionId = computed(() => Number(route.params.id))
|
||||
const collectionId = shallowRef(Number(route.params.id))
|
||||
const { tableDensity } = useDisplaySettings()
|
||||
const { allSavedViews, saveView, renameView, deleteView, duplicateView, toggleFavorite, importViews } = useSavedViews('collection', collectionId)
|
||||
const coverAspectRatio = computed(() => DEFAULT_COVER_ASPECT_RATIO)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { computed, onMounted, ref, shallowRef, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import {
|
||||
AlertTriangle,
|
||||
@@ -57,7 +57,7 @@ const { viewMode, effectiveViewMode } = useEffectiveViewMode()
|
||||
const { hasPermission, isDemoRestrictedAccount } = usePermissions()
|
||||
const { smartScopeFilterExpanded } = useDisplaySettings()
|
||||
|
||||
const smartScopeId = computed(() => Number(route.params.id))
|
||||
const smartScopeId = shallowRef(Number(route.params.id))
|
||||
const coverAspectRatio = computed(() => DEFAULT_COVER_ASPECT_RATIO)
|
||||
const { coverSize, gridGap } = useViewDisplaySettings('smartScope', smartScopeId, coverAspectRatio)
|
||||
const { tableDensity } = useDisplaySettings()
|
||||
|
||||
Reference in New Issue
Block a user