131 lines
3.3 KiB
Vue
131 lines
3.3 KiB
Vue
<script setup>
|
|
import { useInfiniteScroll } from '@vueuse/core'
|
|
import { Loader } from 'lucide-vue-next'
|
|
|
|
const links = ref([])
|
|
const limit = 24
|
|
let cursor = ''
|
|
let listComplete = false
|
|
let listError = false
|
|
const sortBy = ref('created_desc') // created_desc, created_asc, alpha_asc, alpha_desc
|
|
|
|
async function getLinks() {
|
|
try {
|
|
const data = await useAPI('/api/link/list', {
|
|
query: {
|
|
limit,
|
|
cursor,
|
|
sort: sortBy.value,
|
|
},
|
|
})
|
|
links.value = links.value.concat(data.links).filter(Boolean) // Sometimes cloudflare will return null, filter out
|
|
cursor = data.cursor
|
|
listComplete = data.list_complete
|
|
listError = false
|
|
}
|
|
catch (error) {
|
|
console.error(error)
|
|
listError = true
|
|
}
|
|
}
|
|
|
|
const { isLoading } = useInfiniteScroll(
|
|
document,
|
|
getLinks,
|
|
{
|
|
distance: 150,
|
|
interval: 1000,
|
|
canLoadMore: () => {
|
|
return !listError && !listComplete
|
|
},
|
|
},
|
|
)
|
|
|
|
function updateLinkList(link, type) {
|
|
if (type === 'edit') {
|
|
const index = links.value.findIndex(l => l.id === link.id)
|
|
links.value[index] = link
|
|
}
|
|
else if (type === 'delete') {
|
|
const index = links.value.findIndex(l => l.id === link.id)
|
|
links.value.splice(index, 1)
|
|
}
|
|
else {
|
|
links.value.unshift(link)
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<main class="space-y-6">
|
|
<div class="flex flex-col gap-6 sm:gap-2 sm:flex-row sm:justify-between">
|
|
<DashboardNav class="flex-1">
|
|
<DashboardLinksEditor @update:link="updateLinkList" />
|
|
</DashboardNav>
|
|
<LazyDashboardLinksSearch />
|
|
</div>
|
|
<div class="flex gap-2 items-center">
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
:class="{ 'bg-primary text-primary-foreground': sortBy === 'created_desc' }"
|
|
@click="sortBy = 'created_desc'; links = []; cursor = ''; getLinks()"
|
|
>
|
|
Newest First
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
:class="{ 'bg-primary text-primary-foreground': sortBy === 'created_asc' }"
|
|
@click="sortBy = 'created_asc'; links = []; cursor = ''; getLinks()"
|
|
>
|
|
Oldest First
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
:class="{ 'bg-primary text-primary-foreground': sortBy === 'alpha_asc' }"
|
|
@click="sortBy = 'alpha_asc'; links = []; cursor = ''; getLinks()"
|
|
>
|
|
A to Z
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
:class="{ 'bg-primary text-primary-foreground': sortBy === 'alpha_desc' }"
|
|
@click="sortBy = 'alpha_desc'; links = []; cursor = ''; getLinks()"
|
|
>
|
|
Z to A
|
|
</Button>
|
|
</div>
|
|
<section class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
|
|
<DashboardLinksLink
|
|
v-for="link in links"
|
|
:key="link.id"
|
|
:link="link"
|
|
@update:link="updateLinkList"
|
|
/>
|
|
</section>
|
|
<div
|
|
v-if="isLoading"
|
|
class="flex items-center justify-center"
|
|
>
|
|
<Loader class="animate-spin" />
|
|
</div>
|
|
<div
|
|
v-if="!isLoading && listComplete"
|
|
class="flex items-center justify-center text-sm"
|
|
>
|
|
No more links
|
|
</div>
|
|
<div
|
|
v-if="listError"
|
|
class="flex items-center justify-center text-sm"
|
|
>
|
|
Loading links failed,
|
|
<Button variant="link" @click="getLinks">
|
|
Try again
|
|
</Button>
|
|
</div>
|
|
</main>
|
|
</template>
|