feat(ssr): load initial data for remaining public routes
Use useAsyncData to fetch initial list pages and options server-side Apply SSR loading to Habitats, Items, Artifacts, Recipes, Dishes, and Home
This commit is contained in:
@@ -11,11 +11,11 @@ import Skeleton from '../components/Skeleton.vue';
|
||||
import Tabs, { type TabOption } from '../components/Tabs.vue';
|
||||
import TagsSelect from '../components/TagsSelect.vue';
|
||||
import { iconAdd, iconArtifact } from '../icons';
|
||||
import { api, getAuthToken, type AncientArtifact, type AuthUser, type Options } from '../services/api';
|
||||
import { api, getAuthToken, type AncientArtifact, type AuthUser, type ListPage, type Options } from '../services/api';
|
||||
import ItemEdit from './ItemEdit.vue';
|
||||
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
const { t, locale } = useI18n();
|
||||
const options = ref<Options | null>(null);
|
||||
const artifacts = ref<AncientArtifact[]>([]);
|
||||
const currentUser = ref<AuthUser | null>(null);
|
||||
@@ -42,6 +42,40 @@ const artifactQuery = computed(() => ({
|
||||
categoryId: categoryId.value,
|
||||
tagIds: tagIds.value.join(',')
|
||||
}));
|
||||
|
||||
type AncientArtifactListInitialData = {
|
||||
options: Options | null;
|
||||
page: ListPage<AncientArtifact> | null;
|
||||
};
|
||||
|
||||
const { data: initialData } = await useAsyncData<AncientArtifactListInitialData>(
|
||||
`ancient-artifact-list-initial:${locale.value}`,
|
||||
async () => {
|
||||
const [optionsResult, artifactsResult] = await Promise.allSettled([
|
||||
api.options(),
|
||||
api.ancientArtifactsPage({
|
||||
...artifactQuery.value,
|
||||
cursor: null,
|
||||
limit: listPageSize
|
||||
})
|
||||
]);
|
||||
|
||||
return {
|
||||
options: optionsResult.status === 'fulfilled' ? optionsResult.value : null,
|
||||
page: artifactsResult.status === 'fulfilled' ? artifactsResult.value : null
|
||||
};
|
||||
},
|
||||
{ default: () => ({ options: null, page: null }) }
|
||||
);
|
||||
|
||||
const initialPage = initialData.value?.page ?? null;
|
||||
options.value = initialData.value?.options ?? null;
|
||||
artifacts.value = initialPage?.items ?? [];
|
||||
const initialPageLoaded = ref(initialPage !== null);
|
||||
loading.value = !initialPageLoaded.value;
|
||||
nextCursor.value = initialPage?.nextCursor ?? null;
|
||||
hasMoreArtifacts.value = initialPage?.hasMore ?? false;
|
||||
|
||||
const showEditor = computed(() => route.name === 'ancient-artifact-new');
|
||||
const canCreateArtifact = computed(() => currentUser.value?.permissions.includes('items.create') === true);
|
||||
|
||||
@@ -83,6 +117,14 @@ async function loadArtifacts(reset = true) {
|
||||
}
|
||||
nextCursor.value = page.nextCursor;
|
||||
hasMoreArtifacts.value = page.hasMore;
|
||||
initialPageLoaded.value = true;
|
||||
} catch {
|
||||
if (requestId === loadRequestId && reset) {
|
||||
artifacts.value = [];
|
||||
nextCursor.value = null;
|
||||
hasMoreArtifacts.value = false;
|
||||
initialPageLoaded.value = true;
|
||||
}
|
||||
} finally {
|
||||
if (requestId === loadRequestId) {
|
||||
loading.value = false;
|
||||
@@ -103,8 +145,16 @@ onMounted(async () => {
|
||||
currentUser.value = null;
|
||||
}
|
||||
}
|
||||
options.value = await api.options();
|
||||
await loadArtifacts();
|
||||
if (!options.value) {
|
||||
try {
|
||||
options.value = await api.options();
|
||||
} catch {
|
||||
options.value = null;
|
||||
}
|
||||
}
|
||||
if (!initialPageLoaded.value) {
|
||||
await loadArtifacts();
|
||||
}
|
||||
});
|
||||
|
||||
watch(artifactQuery, () => {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useI18n } from 'vue-i18n';
|
||||
import LoadMoreSentinel from '../components/LoadMoreSentinel.vue';
|
||||
import PageHeader from '../components/PageHeader.vue';
|
||||
import Skeleton from '../components/Skeleton.vue';
|
||||
import { api, type DailyChecklistItem } from '../services/api';
|
||||
import { api, type DailyChecklistItem, type ListPage } from '../services/api';
|
||||
|
||||
type ChecklistState = {
|
||||
date: string;
|
||||
@@ -12,7 +12,7 @@ type ChecklistState = {
|
||||
};
|
||||
|
||||
const checklistStateKey = 'pokopia_daily_checklist_state';
|
||||
const { t } = useI18n();
|
||||
const { t, locale } = useI18n();
|
||||
const stateRefreshIntervalMs = 60_000;
|
||||
const checklistItems = ref<DailyChecklistItem[]>([]);
|
||||
const checkedTaskIds = ref<Set<number>>(new Set());
|
||||
@@ -25,6 +25,28 @@ const listPageSize = 20;
|
||||
let stateRefreshTimer: number | null = null;
|
||||
let loadRequestId = 0;
|
||||
|
||||
const { data: initialData } = await useAsyncData<ListPage<DailyChecklistItem> | null>(
|
||||
`daily-checklist-initial:${locale.value}`,
|
||||
async () => {
|
||||
try {
|
||||
return await api.dailyChecklistPage({
|
||||
cursor: null,
|
||||
limit: listPageSize
|
||||
});
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
{ default: () => null }
|
||||
);
|
||||
|
||||
const initialPage = initialData.value;
|
||||
checklistItems.value = initialPage?.items ?? [];
|
||||
const initialPageLoaded = ref(initialPage !== null);
|
||||
loading.value = !initialPageLoaded.value;
|
||||
nextCursor.value = initialPage?.nextCursor ?? null;
|
||||
hasMoreItems.value = initialPage?.hasMore ?? false;
|
||||
|
||||
function todayKey() {
|
||||
const today = new Date();
|
||||
const year = today.getFullYear();
|
||||
@@ -124,9 +146,17 @@ async function loadDailyChecklist(reset = true) {
|
||||
}
|
||||
nextCursor.value = page.nextCursor;
|
||||
hasMoreItems.value = page.hasMore;
|
||||
initialPageLoaded.value = true;
|
||||
if (!page.hasMore) {
|
||||
syncChecklistState();
|
||||
}
|
||||
} catch {
|
||||
if (requestId === loadRequestId && reset) {
|
||||
checklistItems.value = [];
|
||||
nextCursor.value = null;
|
||||
hasMoreItems.value = false;
|
||||
initialPageLoaded.value = true;
|
||||
}
|
||||
} finally {
|
||||
if (requestId === loadRequestId) {
|
||||
loading.value = false;
|
||||
@@ -141,8 +171,13 @@ function loadMoreDailyChecklist() {
|
||||
|
||||
onMounted(() => {
|
||||
loadChecklistState();
|
||||
if (initialPageLoaded.value && !hasMoreItems.value) {
|
||||
syncChecklistState();
|
||||
}
|
||||
stateRefreshTimer = window.setInterval(loadChecklistState, stateRefreshIntervalMs);
|
||||
void loadDailyChecklist();
|
||||
if (!initialPageLoaded.value) {
|
||||
void loadDailyChecklist();
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
type TranslationMap
|
||||
} from '../services/api';
|
||||
|
||||
const { t } = useI18n();
|
||||
const { t, locale } = useI18n();
|
||||
const categories = ref<DishCategory[]>([]);
|
||||
const activeCategoryId = ref('');
|
||||
const loading = ref(true);
|
||||
@@ -96,6 +96,24 @@ const dishFormValid = computed(
|
||||
dishForm.value.mosslaxEffect.trim() !== ''
|
||||
);
|
||||
|
||||
const { data: initialData } = await useAsyncData<DishCategory[] | null>(
|
||||
`dish-initial:${locale.value}`,
|
||||
async () => {
|
||||
try {
|
||||
return await api.dish();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
{ default: () => null }
|
||||
);
|
||||
|
||||
const initialCategories = initialData.value;
|
||||
categories.value = initialCategories ?? [];
|
||||
activeCategoryId.value = categories.value[0] ? String(categories.value[0].id) : '';
|
||||
const initialCategoriesLoaded = ref(initialCategories !== null);
|
||||
loading.value = !initialCategoriesLoaded.value;
|
||||
|
||||
function itemImage(item: ItemLink) {
|
||||
return item.image ? { src: item.image.url, alt: t('media.imageAlt', { name: item.name }) } : null;
|
||||
}
|
||||
@@ -221,6 +239,7 @@ async function loadDish(showSkeleton = false) {
|
||||
}
|
||||
categories.value = await api.dish();
|
||||
activeCategoryId.value = categories.value[0] ? String(categories.value[0].id) : '';
|
||||
initialCategoriesLoaded.value = true;
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
@@ -289,7 +308,7 @@ async function loadPage() {
|
||||
currentUser.value = null;
|
||||
}
|
||||
}
|
||||
await Promise.all([loadDish(), loadEditorOptions()]);
|
||||
await Promise.all([initialCategoriesLoaded.value ? Promise.resolve() : loadDish(), loadEditorOptions()]);
|
||||
}
|
||||
|
||||
watch(categories, (nextCategories) => {
|
||||
|
||||
@@ -8,7 +8,7 @@ import LoadMoreSentinel from '../components/LoadMoreSentinel.vue';
|
||||
import PageHeader from '../components/PageHeader.vue';
|
||||
import Skeleton from '../components/Skeleton.vue';
|
||||
import { iconAdd, iconHabitat } from '../icons';
|
||||
import { api, getAuthToken, type AuthUser, type Habitat } from '../services/api';
|
||||
import { api, getAuthToken, type AuthUser, type Habitat, type ListPage } from '../services/api';
|
||||
import HabitatEdit from './HabitatEdit.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -18,8 +18,7 @@ const props = defineProps<{
|
||||
const habitats = ref<Habitat[]>([]);
|
||||
const currentUser = ref<AuthUser | null>(null);
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
const loading = ref(true);
|
||||
const { t, locale } = useI18n();
|
||||
const loadingMore = ref(false);
|
||||
const nextCursor = ref<string | null>(null);
|
||||
const hasMoreHabitats = ref(false);
|
||||
@@ -29,6 +28,30 @@ let loadRequestId = 0;
|
||||
const query = computed(() => ({
|
||||
isEventItem: props.eventOnly ? 'true' : 'false'
|
||||
}));
|
||||
|
||||
const { data: initialData } = await useAsyncData<ListPage<Habitat> | null>(
|
||||
`${props.eventOnly ? 'event-habitat-list-initial' : 'habitat-list-initial'}:${locale.value}`,
|
||||
async () => {
|
||||
try {
|
||||
return await api.habitatsPage({
|
||||
...query.value,
|
||||
cursor: null,
|
||||
limit: listPageSize
|
||||
});
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
{ default: () => null }
|
||||
);
|
||||
|
||||
const initialPage = initialData.value;
|
||||
habitats.value = initialPage?.items ?? [];
|
||||
const initialPageLoaded = ref(initialPage !== null);
|
||||
const loading = ref(!initialPageLoaded.value);
|
||||
nextCursor.value = initialPage?.nextCursor ?? null;
|
||||
hasMoreHabitats.value = initialPage?.hasMore ?? false;
|
||||
|
||||
const showEditor = computed(() => route.name === 'habitat-new' || route.name === 'event-habitat-new');
|
||||
const canCreateHabitat = computed(() => currentUser.value?.permissions.includes('habitats.create') === true);
|
||||
const pageTitle = computed(() => t(props.eventOnly ? 'pages.eventHabitats.title' : 'pages.habitats.title'));
|
||||
@@ -75,6 +98,14 @@ async function loadHabitats(reset = true) {
|
||||
}
|
||||
nextCursor.value = page.nextCursor;
|
||||
hasMoreHabitats.value = page.hasMore;
|
||||
initialPageLoaded.value = true;
|
||||
} catch {
|
||||
if (requestId === loadRequestId && reset) {
|
||||
habitats.value = [];
|
||||
nextCursor.value = null;
|
||||
hasMoreHabitats.value = false;
|
||||
initialPageLoaded.value = true;
|
||||
}
|
||||
} finally {
|
||||
if (requestId === loadRequestId) {
|
||||
loading.value = false;
|
||||
@@ -95,7 +126,9 @@ onMounted(async () => {
|
||||
currentUser.value = null;
|
||||
}
|
||||
}
|
||||
await loadHabitats();
|
||||
if (!initialPageLoaded.value) {
|
||||
await loadHabitats();
|
||||
}
|
||||
});
|
||||
|
||||
watch(query, () => {
|
||||
|
||||
@@ -63,8 +63,27 @@ const showProjectUpdates = computed(
|
||||
const showProjectUpdatesViewAll = computed(() => projectCommits.value.length > 0 || latestReleases.value.length > 0);
|
||||
const repositoryUpdatedAt = computed(() => formatDateTime(projectUpdates.value?.repository.updatedAt ?? null));
|
||||
|
||||
const { data: initialProjectUpdates } = await useAsyncData<ProjectUpdates | null>(
|
||||
`home-project-updates:${locale.value}`,
|
||||
async () => {
|
||||
try {
|
||||
return await api.projectUpdates({ limit: projectCommitPageSize });
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
{ default: () => null }
|
||||
);
|
||||
|
||||
projectUpdates.value = initialProjectUpdates.value;
|
||||
projectCommits.value = initialProjectUpdates.value?.commits.items ?? [];
|
||||
const initialProjectUpdatesLoaded = ref(initialProjectUpdates.value !== null);
|
||||
projectUpdatesLoading.value = !initialProjectUpdatesLoaded.value;
|
||||
|
||||
onMounted(() => {
|
||||
void loadProjectUpdates();
|
||||
if (!initialProjectUpdatesLoaded.value) {
|
||||
void loadProjectUpdates();
|
||||
}
|
||||
});
|
||||
|
||||
function sectionTitleKey(key: string) {
|
||||
@@ -81,9 +100,11 @@ async function loadProjectUpdates(): Promise<void> {
|
||||
const updates = await api.projectUpdates({ limit: projectCommitPageSize });
|
||||
projectUpdates.value = updates;
|
||||
projectCommits.value = updates.commits.items;
|
||||
initialProjectUpdatesLoaded.value = true;
|
||||
} catch {
|
||||
projectUpdates.value = null;
|
||||
projectCommits.value = [];
|
||||
initialProjectUpdatesLoaded.value = true;
|
||||
} finally {
|
||||
projectUpdatesLoading.value = false;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import Skeleton from '../components/Skeleton.vue';
|
||||
import Tabs, { type TabOption } from '../components/Tabs.vue';
|
||||
import TagsSelect from '../components/TagsSelect.vue';
|
||||
import { iconAdd, iconChevronDown, iconChevronUp, iconItem } from '../icons';
|
||||
import { api, getAuthToken, type AuthUser, type Item, type Options } from '../services/api';
|
||||
import { api, getAuthToken, type AuthUser, type Item, type ListPage, type Options } from '../services/api';
|
||||
import ItemEdit from './ItemEdit.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -21,7 +21,7 @@ const props = defineProps<{
|
||||
const options = ref<Options | null>(null);
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const { t } = useI18n();
|
||||
const { t, locale } = useI18n();
|
||||
const items = ref<Item[]>([]);
|
||||
const currentUser = ref<AuthUser | null>(null);
|
||||
const loading = ref(true);
|
||||
@@ -104,6 +104,40 @@ const itemQuery = computed(() => ({
|
||||
tagIds: tagIds.value.join(','),
|
||||
isEventItem: props.eventOnly
|
||||
}));
|
||||
|
||||
type ItemListInitialData = {
|
||||
options: Options | null;
|
||||
page: ListPage<Item> | null;
|
||||
};
|
||||
|
||||
const { data: initialData } = await useAsyncData<ItemListInitialData>(
|
||||
`${props.eventOnly ? 'event-item-list-initial' : 'item-list-initial'}:${locale.value}`,
|
||||
async () => {
|
||||
const [optionsResult, itemsResult] = await Promise.allSettled([
|
||||
api.options(),
|
||||
api.itemsPage({
|
||||
...itemQuery.value,
|
||||
cursor: null,
|
||||
limit: listPageSize
|
||||
})
|
||||
]);
|
||||
|
||||
return {
|
||||
options: optionsResult.status === 'fulfilled' ? optionsResult.value : null,
|
||||
page: itemsResult.status === 'fulfilled' ? itemsResult.value : null
|
||||
};
|
||||
},
|
||||
{ default: () => ({ options: null, page: null }) }
|
||||
);
|
||||
|
||||
const initialPage = initialData.value?.page ?? null;
|
||||
options.value = initialData.value?.options ?? null;
|
||||
items.value = initialPage?.items ?? [];
|
||||
const initialPageLoaded = ref(initialPage !== null);
|
||||
loading.value = !initialPageLoaded.value;
|
||||
nextCursor.value = initialPage?.nextCursor ?? null;
|
||||
hasMoreItems.value = initialPage?.hasMore ?? false;
|
||||
|
||||
const showEditor = computed(() => route.name === 'item-new' || route.name === 'event-item-new');
|
||||
const canCreateItem = computed(() => currentUser.value?.permissions.includes('items.create') === true);
|
||||
const hasItemCreateDefaults = computed(
|
||||
@@ -458,6 +492,14 @@ async function loadItems(reset = true) {
|
||||
}
|
||||
nextCursor.value = page.nextCursor;
|
||||
hasMoreItems.value = page.hasMore;
|
||||
initialPageLoaded.value = true;
|
||||
} catch {
|
||||
if (requestId === loadRequestId && reset) {
|
||||
items.value = [];
|
||||
nextCursor.value = null;
|
||||
hasMoreItems.value = false;
|
||||
initialPageLoaded.value = true;
|
||||
}
|
||||
} finally {
|
||||
if (requestId === loadRequestId) {
|
||||
loading.value = false;
|
||||
@@ -480,9 +522,17 @@ onMounted(async () => {
|
||||
currentUser.value = null;
|
||||
}
|
||||
}
|
||||
options.value = await api.options();
|
||||
if (!options.value) {
|
||||
try {
|
||||
options.value = await api.options();
|
||||
} catch {
|
||||
options.value = null;
|
||||
}
|
||||
}
|
||||
sanitizeItemCreateDefaults();
|
||||
await loadItems();
|
||||
if (!initialPageLoaded.value) {
|
||||
await loadItems();
|
||||
}
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
|
||||
@@ -27,11 +27,33 @@ const loadMoreSentinel = ref<HTMLElement | null>(null);
|
||||
const expandedCommitShas = ref<Set<string>>(new Set());
|
||||
let projectUpdatesObserver: IntersectionObserver | null = null;
|
||||
|
||||
const { data: initialData } = await useAsyncData<ProjectUpdates | null>(
|
||||
`project-updates-initial:${locale.value}`,
|
||||
async () => {
|
||||
try {
|
||||
return await api.projectUpdates({ limit: projectCommitPageSize });
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
{ default: () => null }
|
||||
);
|
||||
|
||||
const initialUpdates = initialData.value;
|
||||
projectUpdates.value = initialUpdates;
|
||||
projectCommits.value = initialUpdates?.commits.items ?? [];
|
||||
projectCommitCursor.value = initialUpdates?.commits.nextCursor ?? null;
|
||||
projectHasMoreCommits.value = initialUpdates?.commits.hasMore ?? false;
|
||||
const initialUpdatesLoaded = ref(initialUpdates !== null);
|
||||
loading.value = !initialUpdatesLoaded.value;
|
||||
|
||||
const releases = computed(() => projectUpdates.value?.releases ?? []);
|
||||
const repositoryUpdatedAt = computed(() => formatDateTime(projectUpdates.value?.repository.updatedAt ?? null));
|
||||
|
||||
onMounted(() => {
|
||||
void loadProjectUpdates();
|
||||
if (!initialUpdatesLoaded.value) {
|
||||
void loadProjectUpdates();
|
||||
}
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
@@ -53,9 +75,11 @@ async function loadProjectUpdates(): Promise<void> {
|
||||
projectCommits.value = updates.commits.items;
|
||||
projectCommitCursor.value = updates.commits.nextCursor;
|
||||
projectHasMoreCommits.value = updates.commits.hasMore;
|
||||
initialUpdatesLoaded.value = true;
|
||||
} catch {
|
||||
projectUpdates.value = null;
|
||||
projectCommits.value = [];
|
||||
initialUpdatesLoaded.value = true;
|
||||
loadError.value = true;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
|
||||
@@ -11,12 +11,12 @@ import Skeleton from '../components/Skeleton.vue';
|
||||
import Tabs, { type TabOption } from '../components/Tabs.vue';
|
||||
import TagsSelect from '../components/TagsSelect.vue';
|
||||
import { iconAdd, iconNoRecipe, iconRecipe } from '../icons';
|
||||
import { api, getAuthToken, type AuthUser, type Item, type Options } from '../services/api';
|
||||
import { api, getAuthToken, type AuthUser, type Item, type ListPage, type Options } from '../services/api';
|
||||
import RecipeEdit from './RecipeEdit.vue';
|
||||
|
||||
const options = ref<Options | null>(null);
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
const { t, locale } = useI18n();
|
||||
const items = ref<Item[]>([]);
|
||||
const currentUser = ref<AuthUser | null>(null);
|
||||
const loading = ref(true);
|
||||
@@ -46,6 +46,40 @@ const itemQuery = computed(() => ({
|
||||
tagIds: tagIds.value.join(','),
|
||||
recipeOrder: 1
|
||||
}));
|
||||
|
||||
type RecipeListInitialData = {
|
||||
options: Options | null;
|
||||
page: ListPage<Item> | null;
|
||||
};
|
||||
|
||||
const { data: initialData } = await useAsyncData<RecipeListInitialData>(
|
||||
`recipe-list-initial:${locale.value}`,
|
||||
async () => {
|
||||
const [optionsResult, itemsResult] = await Promise.allSettled([
|
||||
api.options(),
|
||||
api.itemsPage({
|
||||
...itemQuery.value,
|
||||
cursor: null,
|
||||
limit: listPageSize
|
||||
})
|
||||
]);
|
||||
|
||||
return {
|
||||
options: optionsResult.status === 'fulfilled' ? optionsResult.value : null,
|
||||
page: itemsResult.status === 'fulfilled' ? itemsResult.value : null
|
||||
};
|
||||
},
|
||||
{ default: () => ({ options: null, page: null }) }
|
||||
);
|
||||
|
||||
const initialPage = initialData.value?.page ?? null;
|
||||
options.value = initialData.value?.options ?? null;
|
||||
items.value = initialPage?.items ?? [];
|
||||
const initialPageLoaded = ref(initialPage !== null);
|
||||
loading.value = !initialPageLoaded.value;
|
||||
nextCursor.value = initialPage?.nextCursor ?? null;
|
||||
hasMoreItems.value = initialPage?.hasMore ?? false;
|
||||
|
||||
const showEditor = computed(() => route.name === 'recipe-new');
|
||||
const canCreateRecipe = computed(() => currentUser.value?.permissions.includes('recipes.create') === true);
|
||||
|
||||
@@ -103,6 +137,14 @@ async function loadItems(reset = true) {
|
||||
}
|
||||
nextCursor.value = page.nextCursor;
|
||||
hasMoreItems.value = page.hasMore;
|
||||
initialPageLoaded.value = true;
|
||||
} catch {
|
||||
if (requestId === loadRequestId && reset) {
|
||||
items.value = [];
|
||||
nextCursor.value = null;
|
||||
hasMoreItems.value = false;
|
||||
initialPageLoaded.value = true;
|
||||
}
|
||||
} finally {
|
||||
if (requestId === loadRequestId) {
|
||||
loading.value = false;
|
||||
@@ -123,8 +165,16 @@ onMounted(async () => {
|
||||
currentUser.value = null;
|
||||
}
|
||||
}
|
||||
options.value = await api.options();
|
||||
await loadItems();
|
||||
if (!options.value) {
|
||||
try {
|
||||
options.value = await api.options();
|
||||
} catch {
|
||||
options.value = null;
|
||||
}
|
||||
}
|
||||
if (!initialPageLoaded.value) {
|
||||
await loadItems();
|
||||
}
|
||||
});
|
||||
|
||||
watch(itemQuery, () => {
|
||||
|
||||
Reference in New Issue
Block a user