basic selection

This commit is contained in:
Gauthier Roebroeck 2026-01-21 11:57:00 +08:00
parent 4df5b825e5
commit 2f99782a8e
8 changed files with 179 additions and 1 deletions

View file

@ -50,6 +50,7 @@ declare module 'vue' {
ImportReadlistTable: typeof import('./components/import/readlist/Table.vue')['default']
ItemCard: typeof import('./components/item/card/ItemCard.vue')['default']
LayoutAppBar: typeof import('./components/layout/app/Bar.vue')['default']
LayoutAppBarHolder: typeof import('./components/layout/app/BarHolder.vue')['default']
LayoutAppDrawer: typeof import('./components/layout/app/drawer/Drawer.vue')['default']
LayoutAppDrawerFooter: typeof import('./components/layout/app/drawer/Footer.vue')['default']
LayoutAppDrawerMenu: typeof import('./components/layout/app/drawer/menu/Menu.vue')['default']
@ -82,6 +83,7 @@ declare module 'vue' {
RemoteFileList: typeof import('./components/RemoteFileList.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
SelectionBar: typeof import('./components/selection/Bar.vue')['default']
SeriesCard: typeof import('./components/series/card/SeriesCard.vue')['default']
SeriesDeletionWarning: typeof import('./components/series/DeletionWarning.vue')['default']
SeriesFormEditMetadata: typeof import('./components/series/form/EditMetadata.vue')['default']

View file

@ -0,0 +1,30 @@
import type { Meta, StoryObj } from '@storybook/vue3-vite'
import Bar from './Bar.vue'
const meta = {
component: Bar,
render: (args: object) => ({
components: { Bar },
setup() {
return { args }
},
template: '<Bar v-bind="args"/>',
}),
parameters: {
// More on how to position stories at: https://storybook.js.org/docs/configure/story-layout
docs: {
description: {
component: '',
},
},
},
args: {},
} satisfies Meta<typeof Bar>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: {},
}

View file

@ -0,0 +1,38 @@
import type { Meta, StoryObj } from '@storybook/vue3-vite'
import BarHolder from './BarHolder.vue'
import { useSelectionStore } from '@/stores/selection'
const meta = {
component: BarHolder,
render: (args: object) => ({
components: { BarHolder },
setup() {
const selectionStore = useSelectionStore()
return { args, selectionStore }
},
template:
'<BarHolder v-bind="args"/><v-btn @click="selectionStore.selection.push(`a`)">Add item</v-btn>',
}),
parameters: {
// More on how to position stories at: https://storybook.js.org/docs/configure/story-layout
docs: {
description: {
component:
'Holder to display the app bar, or the selection bar on top when items are selected.',
},
},
},
args: {},
} satisfies Meta<typeof BarHolder>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: {},
play: () => {
const selectionStore = useSelectionStore()
selectionStore.selection = ['a', 'b']
},
}

View file

@ -0,0 +1,12 @@
<template>
<LayoutAppBar v-if="selectionStore.isEmpty" />
<v-fade-transition>
<SelectionBar v-if="!selectionStore.isEmpty" />
</v-fade-transition>
</template>
<script setup lang="ts">
import { useSelectionStore } from '@/stores/selection'
const selectionStore = useSelectionStore()
</script>

View file

@ -0,0 +1,35 @@
import type { Meta, StoryObj } from '@storybook/vue3-vite'
import Bar from './Bar.vue'
import { useSelectionStore } from '@/stores/selection'
const meta = {
component: Bar,
render: (args: object) => ({
components: { Bar },
setup() {
return { args }
},
template: '<Bar v-bind="args"/>',
}),
parameters: {
// More on how to position stories at: https://storybook.js.org/docs/configure/story-layout
docs: {
description: {
component: '',
},
},
},
args: {},
} satisfies Meta<typeof Bar>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: {},
play: () => {
const selectionStore = useSelectionStore()
selectionStore.selection = ['a', 'b']
},
}

View file

@ -0,0 +1,38 @@
<template>
<v-app-bar
elevation="2"
color="surface-light"
>
<template #prepend>
<v-app-bar-nav-icon
icon="i-mdi:close"
@click="selectionStore.clear()"
/>
</template>
<v-app-bar-title>
{{
$formatMessage(
{
description: 'Selection bar: count of items',
defaultMessage: '{count} selected',
id: 'lqyIjk',
},
{
count: selectionStore.count,
},
)
}}
</v-app-bar-title>
</v-app-bar>
</template>
<script setup lang="ts">
import { useSelectionStore } from '@/stores/selection'
const selectionStore = useSelectionStore()
</script>
<script lang="ts"></script>
<style scoped></style>

View file

@ -74,6 +74,7 @@ import { useAppStore } from '@/stores/app'
import { useItemsPerPage, usePagination } from '@/composables/pagination'
import { useSearchConditionLibraries } from '@/composables/search'
import { storeToRefs } from 'pinia'
import { useSelectionStore } from '@/stores/selection'
const route = useRoute('/libraries/[id]/series')
const libraryId = route.params.id
@ -87,7 +88,8 @@ const presentationMode = appStore.getPresentationMode(`${libraryId}_series`, 'gr
const { itemsPerPage } = useItemsPerPage(browsingPageSize)
const { page0, page1, pageCount } = usePagination()
const selectedItems = ref<components['schemas']['SeriesDto'][]>([])
const selectionStore = useSelectionStore()
const { selection: selectedItems } = storeToRefs(selectionStore)
const preSelect = computed(() => selectedItems.value.length > 0)
const { data: series } = useQuery(seriesListQuery, () => {

View file

@ -0,0 +1,21 @@
import { defineStore } from 'pinia'
export const useSelectionStore = defineStore('selection', () => {
const route = useRoute()
const selection = ref<unknown[]>([])
watch(
() => route?.path,
() => {
selection.value = []
},
)
const isEmpty = computed(() => selection.value.length === 0)
const count = computed(() => selection.value.length)
const clear = () => (selection.value = [])
return { selection, count, isEmpty, clear }
})