import { defineStore } from 'pinia' import { toast } from '../utils/toast.js' import { ref } from 'vue' import { useApi } from '../composables/useApi.js' // Pure, unit-tested: move arr[from] to index `to`, returning a new array. export function moveItem(arr, from, to) { const next = arr.slice() const [x] = next.splice(from, 1) next.splice(to, 0, x) return next } // FC-6.x: a series is ONE flat, series-global ordered run of pages, with // optional cosmetic chapter DIVIDERS anchored to the page that begins them. export const useSeriesManageStore = defineStore('seriesManage', () => { const api = useApi() const tagId = ref(null) const series = ref(null) const pages = ref([]) // flat, ordered: [{image_id, page_number, stated_page, thumbnail_url, image_url, source_post}] const dividers = ref([]) // [{id, anchor_image_id, page_number, title, stated_part}] const gaps = ref([]) // missing-number gaps: [{after_image_id, before_image_id, from, to}] const pending = ref([]) // staged-from-post: [{post:{id,title}|null, start_page, pages:[{image_id, stated_page, thumbnail_url, image_url}]}] const partGaps = ref([]) // [{after_divider_id, start, end}] const pageCount = ref(0) const picker = ref([]) // gallery scroll results const pickerCursor = ref(null) const pickerSelection = ref([]) // image ids const loading = ref(false) async function load(id) { tagId.value = id loading.value = true try { const body = await api.get(`/api/series/${id}/pages`) series.value = body.series pages.value = body.pages || [] dividers.value = body.dividers || [] gaps.value = body.gaps || [] pending.value = body.pending || [] partGaps.value = body.part_gaps || [] pageCount.value = pages.value.length } finally { loading.value = false } } async function refresh() { if (tagId.value != null) await load(tagId.value) } // The divider anchored at a given page (shown as a chapter bar before it). function dividerAt(imageId) { return dividers.value.find(d => d.anchor_image_id === imageId) || null } function partGapAfter(dividerId) { return partGaps.value.find(g => g.after_divider_id === dividerId) || null } // ---- pages (number-driven) ---- // Set one page's number (sparse, gaps allowed; pass null to unnumber). async function setPageNumber(imageId, pageNumber) { await api.post(`/api/series/${tagId.value}/pages/number`, { body: { image_id: imageId, page_number: pageNumber } }) await refresh() } function gapAfter(imageId) { return gaps.value.find(g => g.after_image_id === imageId) || null } async function remove(imageId) { await api.post(`/api/series/${tagId.value}/pages/remove`, { body: { image_ids: [imageId] } }) await refresh() } async function setCover(imageId) { await api.post(`/api/series/${tagId.value}/cover`, { body: { image_id: imageId } }) await refresh() } // ---- chapter dividers ---- async function createDivider(anchorImageId, { title = null, statedPart = null } = {}) { await api.post(`/api/series/${tagId.value}/chapters`, { body: { anchor_image_id: anchorImageId, title, stated_part: statedPart } }) await refresh() } async function renameDivider(dividerId, title) { await api.patch(`/api/series/${tagId.value}/chapters/${dividerId}`, { body: { title } }) await refresh() } async function setDividerPart(dividerId, part) { await api.patch(`/api/series/${tagId.value}/chapters/${dividerId}`, { body: { stated_part: part } }) await refresh() } async function deleteDivider(dividerId) { await api.delete(`/api/series/${tagId.value}/chapters/${dividerId}`) await refresh() toast({ text: 'Chapter divider removed', type: 'success' }) } // ---- pending staging (add-from-post) ---- // Place pending pages, numbered sequentially from startPage in the given order. async function placePending(imageIds, startPage = null) { await api.post(`/api/series/${tagId.value}/pending/place`, { body: { image_ids: imageIds, start_page: startPage } }) await refresh() toast({ text: 'Pages placed into the series', type: 'success' }) } // ---- picker / add pages ---- async function loadPicker(reset = false) { if (reset) { picker.value = []; pickerCursor.value = null } const params = { limit: 50 } if (pickerCursor.value) params.cursor = pickerCursor.value const body = await api.get('/api/gallery/scroll', { params }) picker.value.push(...body.images) pickerCursor.value = body.next_cursor } function togglePick(imageId) { const i = pickerSelection.value.indexOf(imageId) if (i === -1) pickerSelection.value.push(imageId) else pickerSelection.value.splice(i, 1) } async function addSelected() { if (pickerSelection.value.length === 0) return await api.post(`/api/series/${tagId.value}/pages`, { body: { image_ids: pickerSelection.value } }) pickerSelection.value = [] await refresh() toast({ text: 'Added to series', type: 'success' }) } return { tagId, series, pages, dividers, gaps, pending, partGaps, pageCount, picker, pickerCursor, pickerSelection, loading, load, refresh, dividerAt, partGapAfter, gapAfter, setPageNumber, remove, setCover, placePending, createDivider, renameDivider, setDividerPart, deleteDivider, loadPicker, togglePick, addSelected } })