mirror of
https://github.com/bookorbit/bookorbit.git
synced 2026-11-03 08:21:39 -06:00
feat(books): add jump rail display preference
Let readers disable jump rails from grid display controls and skip bucket queries while the preference is off.
This commit is contained in:
@@ -21,6 +21,8 @@ withDefaults(
|
||||
total: number
|
||||
coverSize: number
|
||||
gridGap: number
|
||||
showJumpRailToggle?: boolean
|
||||
showJumpRails?: boolean
|
||||
viewMode: BookViewMode
|
||||
selectionMode?: boolean
|
||||
showSelection?: boolean
|
||||
@@ -56,6 +58,7 @@ withDefaults(
|
||||
const emit = defineEmits<{
|
||||
'update:coverSize': [value: number]
|
||||
'update:gridGap': [value: number]
|
||||
'update:showJumpRails': [value: boolean]
|
||||
'update:viewMode': [value: BookViewMode]
|
||||
'toggle-selection': []
|
||||
'update:coverShape': [value: 'square' | 'circle']
|
||||
@@ -66,6 +69,10 @@ const { t } = useI18n()
|
||||
|
||||
const mobileDisplayOpen = ref(false)
|
||||
const mobileSearchOpen = ref(false)
|
||||
|
||||
function handleShowJumpRailsUpdate(value: boolean) {
|
||||
emit('update:showJumpRails', value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -149,6 +156,8 @@ const mobileSearchOpen = ref(false)
|
||||
:view-mode="viewMode"
|
||||
:cover-size="coverSize"
|
||||
:grid-gap="gridGap"
|
||||
:show-jump-rail-toggle="showJumpRailToggle"
|
||||
:show-jump-rails="showJumpRails"
|
||||
:cover-shape="coverShape"
|
||||
:cover-size-min="coverSizeMin"
|
||||
:cover-size-max="coverSizeMax"
|
||||
@@ -158,6 +167,7 @@ const mobileSearchOpen = ref(false)
|
||||
:grid-gap-step="gridGapStep"
|
||||
@update:cover-size="emit('update:coverSize', $event)"
|
||||
@update:grid-gap="emit('update:gridGap', $event)"
|
||||
@update:show-jump-rails="handleShowJumpRailsUpdate"
|
||||
@update:cover-shape="emit('update:coverShape', $event)"
|
||||
>
|
||||
<template #columns>
|
||||
@@ -201,6 +211,8 @@ const mobileSearchOpen = ref(false)
|
||||
:view-mode="viewMode"
|
||||
:cover-size="coverSize"
|
||||
:grid-gap="gridGap"
|
||||
:show-jump-rail-toggle="showJumpRailToggle"
|
||||
:show-jump-rails="showJumpRails"
|
||||
:cover-shape="coverShape"
|
||||
:cover-size-min="coverSizeMin"
|
||||
:cover-size-max="coverSizeMax"
|
||||
@@ -210,6 +222,7 @@ const mobileSearchOpen = ref(false)
|
||||
:grid-gap-step="gridGapStep"
|
||||
@update:cover-size="emit('update:coverSize', $event)"
|
||||
@update:grid-gap="emit('update:gridGap', $event)"
|
||||
@update:show-jump-rails="handleShowJumpRailsUpdate"
|
||||
@update:cover-shape="emit('update:coverShape', $event)"
|
||||
>
|
||||
<template #columns>
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import ViewHeaderDisplayControls from './ViewHeaderDisplayControls.vue'
|
||||
|
||||
function mountControls(overrides: Partial<InstanceType<typeof ViewHeaderDisplayControls>['$props']> = {}) {
|
||||
return mount(ViewHeaderDisplayControls, {
|
||||
props: {
|
||||
viewMode: 'grid',
|
||||
coverSize: 120,
|
||||
gridGap: 24,
|
||||
...overrides,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
describe('ViewHeaderDisplayControls', () => {
|
||||
it('renders the jump rail checkbox in grid mode and emits changes', async () => {
|
||||
const wrapper = mountControls({ showJumpRailToggle: true, showJumpRails: true })
|
||||
const checkbox = wrapper.get('input[type="checkbox"]')
|
||||
|
||||
expect(wrapper.text()).toContain('Show jump rails')
|
||||
expect((checkbox.element as HTMLInputElement).checked).toBe(true)
|
||||
|
||||
await checkbox.setValue(false)
|
||||
|
||||
expect(wrapper.emitted('update:showJumpRails')).toEqual([[false]])
|
||||
})
|
||||
|
||||
it('omits the jump rail checkbox when unsupported or outside grid mode', () => {
|
||||
expect(mountControls().find('input[type="checkbox"]').exists()).toBe(false)
|
||||
expect(mountControls({ viewMode: 'list', showJumpRailToggle: true, showJumpRails: true }).find('input[type="checkbox"]').exists()).toBe(false)
|
||||
expect(mountControls({ viewMode: 'table', showJumpRailToggle: true, showJumpRails: true }).find('input[type="checkbox"]').exists()).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -10,6 +10,8 @@ withDefaults(
|
||||
viewMode: BookViewMode
|
||||
coverSize: number
|
||||
gridGap: number
|
||||
showJumpRailToggle?: boolean
|
||||
showJumpRails?: boolean
|
||||
coverShape?: 'square' | 'circle'
|
||||
coverSizeMin?: number
|
||||
coverSizeMax?: number
|
||||
@@ -31,8 +33,13 @@ withDefaults(
|
||||
const emit = defineEmits<{
|
||||
'update:coverSize': [value: number]
|
||||
'update:gridGap': [value: number]
|
||||
'update:showJumpRails': [value: boolean]
|
||||
'update:coverShape': [value: 'square' | 'circle']
|
||||
}>()
|
||||
|
||||
function handleShowJumpRailsChange(event: Event) {
|
||||
emit('update:showJumpRails', (event.target as HTMLInputElement).checked)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -71,6 +78,16 @@ const emit = defineEmits<{
|
||||
class="w-full accent-primary cursor-pointer"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<label v-if="showJumpRailToggle && viewMode === 'grid'" class="flex cursor-pointer items-center justify-between gap-3 pt-1">
|
||||
<span class="text-xs text-muted-foreground">{{ t('components.viewHeader.displayControls.showJumpRails') }}</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="showJumpRails"
|
||||
class="size-4 shrink-0 cursor-pointer rounded border-input accent-primary"
|
||||
@change="handleShowJumpRailsChange"
|
||||
/>
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
|
||||
@@ -12,6 +12,7 @@ function resetDisplaySettings() {
|
||||
settings.squareGridGap.value = 28
|
||||
settings.viewMode.value = 'grid'
|
||||
settings.cardOverlays.value = ['progress-bar', 'format', 'rating', 'read-status', 'series-position']
|
||||
settings.showJumpRails.value = true
|
||||
settings.smartScopeFilterExpanded.value = true
|
||||
settings.authorCoverSize.value = 120
|
||||
settings.authorCoverShape.value = 'circle'
|
||||
@@ -44,6 +45,7 @@ describe('useDisplaySettings preferences helpers', () => {
|
||||
coverSizeScope: 'per-view',
|
||||
viewMode: 'grid',
|
||||
cardOverlays: ['format', 'rating'],
|
||||
showJumpRails: true,
|
||||
bookSpineOverlay: 'strong',
|
||||
bookCoverDisplayMode: 'natural-bottom',
|
||||
thumbnailClickAction: 'reader',
|
||||
@@ -56,6 +58,7 @@ describe('useDisplaySettings preferences helpers', () => {
|
||||
squareCoverSize: 'large',
|
||||
coverSizeScope: 'synced',
|
||||
cardOverlays: ['format', 'unknown', 'format', 'lock-status'],
|
||||
showJumpRails: false,
|
||||
bookCoverDisplayMode: 'fill-crop',
|
||||
thumbnailClickAction: 'details',
|
||||
tableDensity: 'huge',
|
||||
@@ -66,6 +69,7 @@ describe('useDisplaySettings preferences helpers', () => {
|
||||
portraitCoverSize: 400,
|
||||
coverSizeScope: 'synced',
|
||||
cardOverlays: ['format', 'lock-status'],
|
||||
showJumpRails: false,
|
||||
bookCoverDisplayMode: 'fill-crop',
|
||||
thumbnailClickAction: 'details',
|
||||
})
|
||||
@@ -77,6 +81,7 @@ describe('useDisplaySettings preferences helpers', () => {
|
||||
gridGap: 120,
|
||||
viewMode: 'table',
|
||||
authorCoverShape: 'square',
|
||||
showJumpRails: false,
|
||||
tableZebraStriping: true,
|
||||
bookShadowStrength: 'strong',
|
||||
bookCoverDisplayMode: 'natural-bottom',
|
||||
@@ -88,6 +93,7 @@ describe('useDisplaySettings preferences helpers', () => {
|
||||
expect(settings.gridGap.value).toBe(80)
|
||||
expect(settings.viewMode.value).toBe('table')
|
||||
expect(settings.authorCoverShape.value).toBe('square')
|
||||
expect(settings.showJumpRails.value).toBe(false)
|
||||
expect(settings.tableZebraStriping.value).toBe(true)
|
||||
expect(settings.tableDensity.value).toBe('comfortable')
|
||||
expect(settings.bookShadowStrength.value).toBe('strong')
|
||||
@@ -100,6 +106,12 @@ describe('useDisplaySettings preferences helpers', () => {
|
||||
expect(sanitizeDisplayPreferences('bad')).toEqual({})
|
||||
})
|
||||
|
||||
it('sanitizes only boolean showJumpRails values', () => {
|
||||
expect(sanitizeDisplayPreferences({ showJumpRails: true })).toEqual({ showJumpRails: true })
|
||||
expect(sanitizeDisplayPreferences({ showJumpRails: false })).toEqual({ showJumpRails: false })
|
||||
expect(sanitizeDisplayPreferences({ showJumpRails: 'false' })).toEqual({})
|
||||
})
|
||||
|
||||
it('includes seriesCardCoverMode in snapshot', () => {
|
||||
settings.seriesCardCoverMode.value = 'first-volume'
|
||||
const snap = getDisplayPreferencesSnapshot()
|
||||
|
||||
@@ -12,6 +12,7 @@ function validDisplayPreferences(overrides: Partial<DisplayPreferences> = {}): D
|
||||
squareGridGap: 20,
|
||||
viewMode: 'grid',
|
||||
cardOverlays: ['progress-bar', 'format', 'rating'],
|
||||
showJumpRails: true,
|
||||
smartScopeFilterExpanded: true,
|
||||
authorCoverSize: 140,
|
||||
authorCoverShape: 'circle',
|
||||
@@ -193,6 +194,24 @@ describe('useDisplaySettingsSync', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('syncs showJumpRails changes to the server', async () => {
|
||||
vi.useFakeTimers()
|
||||
const { apiMock, displaySettings, sync } = await loadModules()
|
||||
apiMock.mockResolvedValue({ ok: true })
|
||||
|
||||
sync.initDisplaySettingsSync()
|
||||
displaySettings.useDisplaySettings().showJumpRails.value = false
|
||||
await vi.advanceTimersByTimeAsync(1500)
|
||||
|
||||
expect(apiMock).toHaveBeenCalledWith(
|
||||
'/api/v1/user-preferences/display',
|
||||
expect.objectContaining({
|
||||
method: 'PUT',
|
||||
body: expect.stringContaining('"showJumpRails":false'),
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('flushes a pending save with keepalive on pagehide', async () => {
|
||||
vi.useFakeTimers()
|
||||
const fetchMock = vi.fn<() => Promise<Response>>().mockResolvedValue({ ok: true } as Response)
|
||||
|
||||
@@ -140,6 +140,7 @@ function normalizeViewMode(v: unknown): BookViewMode {
|
||||
|
||||
const viewMode = ref<BookViewMode>(normalizeViewMode(storage.get('viewMode', 'grid')))
|
||||
const cardOverlays = ref<CardOverlayKey[]>(normalizeCardOverlays(storage.get('cardOverlays', DEFAULT_CARD_OVERLAYS)))
|
||||
const showJumpRails = ref(storage.get<boolean>('showJumpRails', true) !== false)
|
||||
const smartScopeFilterExpanded = ref(storage.get<boolean>('smartScopeFilterExpanded', true) === true)
|
||||
const authorCoverSize = ref(normalizePositiveInteger(storage.get('authorCoverSize', 120), 120, 100, 400))
|
||||
const authorCoverShape = ref<AuthorCoverShape>(normalizeAuthorCoverShape(storage.get('authorCoverShape', 'circle')))
|
||||
@@ -167,6 +168,7 @@ watch(portraitGridGap, (v) => storage.set('portraitGridGap', v))
|
||||
watch(squareGridGap, (v) => storage.set('squareGridGap', v))
|
||||
watch(viewMode, (v) => storage.set('viewMode', v))
|
||||
watch(cardOverlays, (v) => storage.set('cardOverlays', normalizeCardOverlays(v)), { deep: true })
|
||||
watch(showJumpRails, (v) => storage.set('showJumpRails', v))
|
||||
watch(smartScopeFilterExpanded, (v) => storage.set('smartScopeFilterExpanded', v))
|
||||
watch(authorCoverSize, (v) => storage.set('authorCoverSize', v))
|
||||
watch(authorCoverShape, (v) => storage.set('authorCoverShape', normalizeAuthorCoverShape(v)))
|
||||
@@ -192,6 +194,7 @@ export function getDisplayPreferencesSnapshot(): DisplayPreferences {
|
||||
squareGridGap: normalizePositiveInteger(squareGridGap.value, DEFAULT_GRID_GAP, 1, 80),
|
||||
viewMode: normalizeViewMode(viewMode.value),
|
||||
cardOverlays: normalizeCardOverlays(cardOverlays.value),
|
||||
showJumpRails: showJumpRails.value !== false,
|
||||
smartScopeFilterExpanded: smartScopeFilterExpanded.value === true,
|
||||
authorCoverSize: normalizePositiveInteger(authorCoverSize.value, 120, 100, 400),
|
||||
authorCoverShape: normalizeAuthorCoverShape(authorCoverShape.value),
|
||||
@@ -224,6 +227,7 @@ export function sanitizeDisplayPreferences(raw: unknown): Partial<DisplayPrefere
|
||||
if (typeof obj.squareGridGap === 'number') out.squareGridGap = normalizePositiveInteger(obj.squareGridGap, DEFAULT_GRID_GAP, 1, 80)
|
||||
if (BOOK_VIEW_MODES.includes(obj.viewMode as BookViewMode)) out.viewMode = obj.viewMode as BookViewMode
|
||||
if (Array.isArray(obj.cardOverlays)) out.cardOverlays = normalizeCardOverlays(obj.cardOverlays)
|
||||
if (typeof obj.showJumpRails === 'boolean') out.showJumpRails = obj.showJumpRails
|
||||
if (typeof obj.smartScopeFilterExpanded === 'boolean') out.smartScopeFilterExpanded = obj.smartScopeFilterExpanded
|
||||
if (typeof obj.authorCoverSize === 'number') out.authorCoverSize = normalizePositiveInteger(obj.authorCoverSize, 120, 100, 400)
|
||||
if (AUTHOR_COVER_SHAPES.includes(obj.authorCoverShape as AuthorCoverShape)) out.authorCoverShape = obj.authorCoverShape as AuthorCoverShape
|
||||
@@ -265,6 +269,7 @@ export function applyDisplayPreferences(raw: unknown): void {
|
||||
if (prefs.squareGridGap !== undefined) squareGridGap.value = prefs.squareGridGap
|
||||
if (prefs.viewMode !== undefined) viewMode.value = prefs.viewMode
|
||||
if (prefs.cardOverlays !== undefined) cardOverlays.value = prefs.cardOverlays
|
||||
if (prefs.showJumpRails !== undefined) showJumpRails.value = prefs.showJumpRails
|
||||
if (prefs.smartScopeFilterExpanded !== undefined) smartScopeFilterExpanded.value = prefs.smartScopeFilterExpanded
|
||||
if (prefs.authorCoverSize !== undefined) authorCoverSize.value = prefs.authorCoverSize
|
||||
if (prefs.authorCoverShape !== undefined) authorCoverShape.value = prefs.authorCoverShape
|
||||
@@ -291,6 +296,7 @@ export function useDisplaySettings() {
|
||||
squareGridGap,
|
||||
viewMode,
|
||||
cardOverlays,
|
||||
showJumpRails,
|
||||
smartScopeFilterExpanded,
|
||||
authorCoverSize,
|
||||
authorCoverShape,
|
||||
|
||||
@@ -109,6 +109,7 @@ export function initDisplaySettingsSync(): void {
|
||||
settings.squareGridGap.value,
|
||||
settings.viewMode.value,
|
||||
settings.cardOverlays.value.join('\u0000'),
|
||||
settings.showJumpRails.value,
|
||||
settings.smartScopeFilterExpanded.value,
|
||||
settings.authorCoverSize.value,
|
||||
settings.authorCoverShape.value,
|
||||
|
||||
@@ -86,9 +86,10 @@ async function flush() {
|
||||
await nextTick()
|
||||
}
|
||||
|
||||
function setup(options: { scope?: number | null; viewMode?: string; collapse?: boolean; q?: string } = {}) {
|
||||
function setup(options: { scope?: number | null; viewMode?: string; collapse?: boolean; q?: string; railEnabled?: boolean } = {}) {
|
||||
const scopeId = ref<number | null>(options.scope === undefined ? 1 : options.scope)
|
||||
const viewMode = ref(options.viewMode ?? 'table')
|
||||
const railEnabled = ref(options.railEnabled ?? true)
|
||||
const collapseEnabled = ref(options.collapse ?? false)
|
||||
const q = ref(options.q ?? '')
|
||||
const win = useBookViewWindow({
|
||||
@@ -96,10 +97,11 @@ function setup(options: { scope?: number | null; viewMode?: string; collapse?: b
|
||||
listEndpoint: (id) => `/api/v1/libraries/${id}/books`,
|
||||
bucketsEndpoint: (id) => `/api/v1/libraries/${id}/books/jump-buckets`,
|
||||
viewMode,
|
||||
railEnabled,
|
||||
collapseEnabled,
|
||||
q,
|
||||
})
|
||||
return { win, scopeId, viewMode, collapseEnabled, q }
|
||||
return { win, scopeId, viewMode, railEnabled, collapseEnabled, q }
|
||||
}
|
||||
|
||||
function listRequests(): number[] {
|
||||
@@ -108,6 +110,10 @@ function listRequests(): number[] {
|
||||
.map(([, init]) => (JSON.parse(String(init?.body)) as { pagination: { page: number } }).pagination.page)
|
||||
}
|
||||
|
||||
function jumpRequestCount(): number {
|
||||
return fetchMock.mock.calls.filter(([url]) => url.includes('jump-buckets')).length
|
||||
}
|
||||
|
||||
describe('useBookViewWindow', () => {
|
||||
beforeEach(() => {
|
||||
fetchMock.mockReset()
|
||||
@@ -248,6 +254,22 @@ describe('useBookViewWindow', () => {
|
||||
expect(win.railGutterReserved.value).toBe(false)
|
||||
})
|
||||
|
||||
it('does not request buckets while jump rails are disabled and loads them when enabled', async () => {
|
||||
mockApi(250, [bucket('A', 0), bucket('B', 120)])
|
||||
const { win, railEnabled } = setup({ viewMode: 'grid', railEnabled: false })
|
||||
await flush()
|
||||
|
||||
expect(jumpRequestCount()).toBe(0)
|
||||
expect(win.railVisible.value).toBe(false)
|
||||
|
||||
railEnabled.value = true
|
||||
await flush()
|
||||
await flush()
|
||||
|
||||
expect(jumpRequestCount()).toBe(1)
|
||||
expect(win.railVisible.value).toBe(true)
|
||||
})
|
||||
|
||||
it('shows the jump rail in grid mode once buckets load and jumps scroll to the bucket index', async () => {
|
||||
mockApi(250, [bucket('A', 0), bucket('B', 120)])
|
||||
const { win } = setup({ viewMode: 'grid' })
|
||||
|
||||
@@ -28,6 +28,7 @@ export function useBookViewWindow(options: {
|
||||
listEndpoint: (id: number) => string
|
||||
bucketsEndpoint: (id: number) => string
|
||||
viewMode: Ref<string>
|
||||
railEnabled?: Ref<boolean>
|
||||
railViewport?: Ref<HTMLElement | null>
|
||||
collapseEnabled?: Ref<boolean>
|
||||
q?: Ref<string>
|
||||
@@ -68,7 +69,9 @@ export function useBookViewWindow(options: {
|
||||
const bucketKind = computed(() => jumpBucketKindForSort(sort.value))
|
||||
const primarySortField = computed(() => sort.value[0]?.field ?? 'title')
|
||||
const railModeActive = computed(() => options.viewMode.value === 'grid')
|
||||
const railEligible = computed(() => bucketKind.value !== null && railModeActive.value && window.total.value >= MIN_TOTAL_FOR_RAIL)
|
||||
const railEligible = computed(
|
||||
() => (options.railEnabled?.value ?? true) && bucketKind.value !== null && railModeActive.value && window.total.value >= MIN_TOTAL_FOR_RAIL,
|
||||
)
|
||||
|
||||
const bucketsApi = useJumpBuckets({
|
||||
endpoint: bucketsEndpoint,
|
||||
|
||||
@@ -4129,6 +4129,7 @@
|
||||
"display": "Anzeige",
|
||||
"coverSize": "Covergröße",
|
||||
"gridGap": "Rasterabstand",
|
||||
"showJumpRails": "Show jump rails",
|
||||
"columnsHint": "Verwende das Spaltensichtbarkeits-Panel in der Tabellenkopfzeile, um Spalten ein- oder auszublenden.",
|
||||
"coverShape": "Coverform",
|
||||
"circle": "Kreis",
|
||||
|
||||
@@ -4087,6 +4087,7 @@
|
||||
"display": "Display",
|
||||
"coverSize": "Cover size",
|
||||
"gridGap": "Grid gap",
|
||||
"showJumpRails": "Show jump rails",
|
||||
"columnsHint": "Use the column visibility panel in the table header to show or hide columns.",
|
||||
"coverShape": "Cover shape",
|
||||
"circle": "Circle",
|
||||
|
||||
@@ -4129,6 +4129,7 @@
|
||||
"display": "Weergave",
|
||||
"coverSize": "Omslaggrootte",
|
||||
"gridGap": "Rasterafstand",
|
||||
"showJumpRails": "Show jump rails",
|
||||
"columnsHint": "Gebruik het paneel voor kolomzichtbaarheid in de tabelkop om kolommen te tonen of te verbergen.",
|
||||
"coverShape": "Omslagvorm",
|
||||
"circle": "Cirkel",
|
||||
|
||||
@@ -4129,6 +4129,7 @@
|
||||
"display": "Exibição",
|
||||
"coverSize": "Tamanho da capa",
|
||||
"gridGap": "Espaçamento da grade",
|
||||
"showJumpRails": "Show jump rails",
|
||||
"columnsHint": "Use o painel de visibilidade de colunas no cabeçalho da tabela para mostrar ou ocultar colunas.",
|
||||
"coverShape": "Formato da capa",
|
||||
"circle": "Círculo",
|
||||
|
||||
@@ -4129,6 +4129,7 @@
|
||||
"display": "Prikaz",
|
||||
"coverSize": "Velikost naslovnice",
|
||||
"gridGap": "Razmik mreže",
|
||||
"showJumpRails": "Show jump rails",
|
||||
"columnsHint": "Uporabi ploščo za vidnost stolpcev v glavi tabele, da prikažeš ali skriješ stolpce.",
|
||||
"coverShape": "Oblika naslovnice",
|
||||
"circle": "Krog",
|
||||
|
||||
@@ -50,7 +50,7 @@ const { viewMode, effectiveViewMode } = useEffectiveViewMode()
|
||||
const { hasPermission, isDemoRestrictedAccount } = usePermissions()
|
||||
|
||||
const collectionId = shallowRef(Number(route.params.id))
|
||||
const { tableDensity } = useDisplaySettings()
|
||||
const { tableDensity, showJumpRails } = useDisplaySettings()
|
||||
const { allSavedViews, saveView, renameView, deleteView, duplicateView, toggleFavorite, importViews } = useSavedViews('collection', collectionId)
|
||||
const coverAspectRatio = computed(() => DEFAULT_COVER_ASPECT_RATIO)
|
||||
const { coverSize, gridGap } = useViewDisplaySettings('collection', collectionId, coverAspectRatio)
|
||||
@@ -109,6 +109,7 @@ const {
|
||||
listEndpoint: (id) => `/api/v1/collections/${id}/books/query`,
|
||||
bucketsEndpoint: (id) => `/api/v1/collections/${id}/books/jump-buckets`,
|
||||
viewMode: effectiveViewMode,
|
||||
railEnabled: showJumpRails,
|
||||
railViewport: mainRef,
|
||||
collapseEnabled: collapseEnabledRef,
|
||||
q: debouncedQuery,
|
||||
@@ -438,6 +439,8 @@ defineOptions({ name: 'CollectionView' })
|
||||
:total="total"
|
||||
v-model:coverSize="coverSize"
|
||||
v-model:gridGap="gridGap"
|
||||
:show-jump-rail-toggle="true"
|
||||
v-model:showJumpRails="showJumpRails"
|
||||
v-model:viewMode="viewMode"
|
||||
:selection-mode="selectionMode"
|
||||
:searchable="true"
|
||||
|
||||
@@ -78,6 +78,7 @@ const libraryId = shallowRef<number | null>(route.params.id ? Number(route.param
|
||||
const currentLibrary = computed(() => libraries.value.find((l) => l.id === libraryId.value))
|
||||
const currentCoverAspectRatio = computed(() => currentLibrary.value?.coverAspectRatio ?? DEFAULT_COVER_ASPECT_RATIO)
|
||||
const { coverSize, gridGap } = useViewDisplaySettings('library', libraryId, currentCoverAspectRatio)
|
||||
const { tableDensity, showJumpRails } = useDisplaySettings()
|
||||
|
||||
const libraryNotFound = computed(() => librariesLoaded.value && libraryId.value !== null && !currentLibrary.value)
|
||||
const title = computed(() => currentLibrary.value?.name ?? t('views.library.title'))
|
||||
@@ -138,6 +139,7 @@ const {
|
||||
listEndpoint: (id) => `/api/v1/libraries/${id}/books`,
|
||||
bucketsEndpoint: (id) => `/api/v1/libraries/${id}/books/jump-buckets`,
|
||||
viewMode: effectiveViewMode,
|
||||
railEnabled: showJumpRails,
|
||||
railViewport: mainRef,
|
||||
collapseEnabled: collapseEnabledRef,
|
||||
q: debouncedQuery,
|
||||
@@ -157,7 +159,6 @@ const hasSavedFilter = computed(() => savedFilter.value !== undefined)
|
||||
const isFilterSaved = computed(() => JSON.stringify(filter.value) === JSON.stringify(savedFilter.value))
|
||||
|
||||
const { sortModel, isDefaultSort, sortSummary, resetSort } = useViewSort(sort, 'library', libraryId)
|
||||
const { tableDensity } = useDisplaySettings()
|
||||
const { allSavedViews, saveView, renameView, deleteView, duplicateView, toggleFavorite, importViews } = useSavedViews('library', libraryId)
|
||||
|
||||
function handleSaveCurrentView(name: string) {
|
||||
@@ -567,6 +568,8 @@ defineOptions({ name: 'HomeView' })
|
||||
:total="total"
|
||||
v-model:coverSize="coverSize"
|
||||
v-model:gridGap="gridGap"
|
||||
:show-jump-rail-toggle="true"
|
||||
v-model:showJumpRails="showJumpRails"
|
||||
v-model:viewMode="viewMode"
|
||||
:selection-mode="selectionMode"
|
||||
:searchable="true"
|
||||
|
||||
@@ -65,12 +65,11 @@ const route = useRoute()
|
||||
const router = useRouter()
|
||||
const { viewMode, effectiveViewMode } = useEffectiveViewMode()
|
||||
const { hasPermission, isDemoRestrictedAccount } = usePermissions()
|
||||
const { smartScopeFilterExpanded } = useDisplaySettings()
|
||||
const { smartScopeFilterExpanded, tableDensity, showJumpRails } = useDisplaySettings()
|
||||
|
||||
const smartScopeId = shallowRef(Number(route.params.id))
|
||||
const coverAspectRatio = computed(() => DEFAULT_COVER_ASPECT_RATIO)
|
||||
const { coverSize, gridGap } = useViewDisplaySettings('smartScope', smartScopeId, coverAspectRatio)
|
||||
const { tableDensity } = useDisplaySettings()
|
||||
const { allSavedViews, saveView, renameView, deleteView, duplicateView, toggleFavorite, importViews } = useSavedViews('smartScope', smartScopeId)
|
||||
|
||||
const { getEffectivePreference, setPreference, prefs } = useSeriesCollapsePreference()
|
||||
@@ -118,6 +117,7 @@ const {
|
||||
listEndpoint: (id) => `/api/v1/smart-scopes/${id}/books/query`,
|
||||
bucketsEndpoint: (id) => `/api/v1/smart-scopes/${id}/books/jump-buckets`,
|
||||
viewMode: effectiveViewMode,
|
||||
railEnabled: showJumpRails,
|
||||
railViewport: mainRef,
|
||||
collapseEnabled: collapseEnabledRef,
|
||||
q: debouncedQuery,
|
||||
@@ -475,6 +475,8 @@ defineOptions({ name: 'SmartScopeView' })
|
||||
:total="total"
|
||||
v-model:coverSize="coverSize"
|
||||
v-model:gridGap="gridGap"
|
||||
:show-jump-rail-toggle="true"
|
||||
v-model:showJumpRails="showJumpRails"
|
||||
v-model:viewMode="viewMode"
|
||||
:selection-mode="selectionMode"
|
||||
:searchable="true"
|
||||
|
||||
Reference in New Issue
Block a user