eebc8e2413
- utils/date.js: add formatRelative(iso, {future,nullText}); migrate 6 sites
(SourceRow, SubscriptionsTab, SourceHealthDot, SchedulerStatusBar +
thin adapters in BackupRunsTable/SystemActivityTab for their '—' null text).
PostCard (30d->absolute) and CredentialCard (mo/y buckets) intentionally
keep bespoke formatters.
- utils/toast.js: toast(opts) wraps the globalThis.window?.__fcToast?.(...)
incantation; migrate 63 call sites across 24 files.
- utils/downloadStatus.js: single source for the download-event status enum
-> label/color/icon; collapse the 3 duplicate maps (DownloadStatChips,
DownloadsFilterPopover, DownloadEventRow).
Net -33 lines. Platform metadata was already centralized in platformColor.js.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
93 lines
2.6 KiB
JavaScript
93 lines
2.6 KiB
JavaScript
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
|
|
}
|
|
|
|
export const useSeriesManageStore = defineStore('seriesManage', () => {
|
|
const api = useApi()
|
|
|
|
const tagId = ref(null)
|
|
const series = ref(null)
|
|
const pages = ref([]) // [{image_id, page_number, thumbnail_url}]
|
|
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
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
async function refresh() {
|
|
if (tagId.value != null) await load(tagId.value)
|
|
}
|
|
|
|
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' })
|
|
}
|
|
|
|
async function remove(imageId) {
|
|
await api.post(`/api/series/${tagId.value}/pages/remove`, {
|
|
body: { image_ids: [imageId] }
|
|
})
|
|
await refresh()
|
|
}
|
|
|
|
async function reorder(orderedImageIds) {
|
|
await api.post(`/api/series/${tagId.value}/reorder`, {
|
|
body: { image_ids: orderedImageIds }
|
|
})
|
|
await refresh()
|
|
}
|
|
|
|
async function setCover(imageId) {
|
|
await api.post(`/api/series/${tagId.value}/cover`, {
|
|
body: { image_id: imageId }
|
|
})
|
|
await refresh()
|
|
}
|
|
|
|
return {
|
|
tagId, series, pages, picker, pickerCursor, pickerSelection, loading,
|
|
load, refresh, loadPicker, togglePick, addSelected, remove,
|
|
reorder, setCover
|
|
}
|
|
})
|