Skip to content

Commit f1854ba

Browse files
committed
feat(object): add next/prev navigation and auto-page to current item
Fetch all collection members client-side (paginating in batches of 1000) so the "Other objects in collection" sidebar automatically opens to the page containing the current item. Add Next/Previous buttons that navigate directly to the adjacent object in the collection.
1 parent 09dba5e commit f1854ba

File tree

1 file changed

+63
-23
lines changed

1 file changed

+63
-23
lines changed

src/views/ObjectView.vue

Lines changed: 63 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<script setup lang="ts">
22
import { useGtm } from '@gtm-support/vue-gtm';
33
import { injectHead } from '@unhead/vue';
4-
import { inject, ref, watch } from 'vue';
4+
import { computed, inject, ref, watch } from 'vue';
55
import { useI18n } from 'vue-i18n';
66
import { useRoute } from 'vue-router';
77
import AccessHelper from '@/components/AccessHelper.vue';
@@ -40,13 +40,33 @@ const { name, meta, populateName, populateMeta, handleMissingEntity } = useEntit
4040
const parts = ref<({ '@id': string; name: string; encodingFormat: string[] } & Record<string, string>)[]>([]);
4141
const mediaTypes = ref<string[]>([]);
4242
const isLoading = ref(false);
43-
const isLoadingMembers = ref(false);
4443
const metadata = ref<RoCrate | undefined>();
4544
const entity = ref<EntityType | undefined>();
46-
const membersFiltered = ref<EntityType[]>([]);
45+
const allMembers = ref<EntityType[]>([]);
4746
const currentPage = ref(1);
4847
const pageSize = ref(10);
49-
const totalMembers = ref(0);
48+
49+
const totalMembers = computed(() => allMembers.value.length);
50+
51+
const currentMemberIndex = computed(() => allMembers.value.findIndex((e) => e.id === route.query.id));
52+
53+
const previousObject = computed(() => {
54+
const idx = currentMemberIndex.value;
55+
56+
return idx > 0 ? allMembers.value[idx - 1] : undefined;
57+
});
58+
59+
const nextObject = computed(() => {
60+
const idx = currentMemberIndex.value;
61+
62+
return idx >= 0 && idx < allMembers.value.length - 1 ? allMembers.value[idx + 1] : undefined;
63+
});
64+
65+
const membersFiltered = computed(() => {
66+
const start = (currentPage.value - 1) * pageSize.value;
67+
68+
return allMembers.value.slice(start, start + pageSize.value);
69+
});
5070
5171
const populateParts = (md: RoCrate) => {
5272
if (!md.hasPart) {
@@ -80,29 +100,44 @@ const populate = (md: RoCrate) => {
80100
useHead(head, md);
81101
};
82102
103+
const FETCH_LIMIT = 1000;
104+
83105
const fetchMembers = async () => {
84106
if (!entity.value?.memberOf) {
85107
return;
86108
}
87109
88-
const params: GetEntitiesParams = {
110+
const baseParams: GetEntitiesParams = {
89111
memberOf: entity.value.memberOf.id,
90112
entityType: 'http://pcdm.org/models#Object',
91-
limit: pageSize.value,
113+
limit: FETCH_LIMIT,
114+
sort: 'name',
115+
order: 'asc',
92116
};
93117
94-
if (currentPage.value !== 1) {
95-
params.offset = (currentPage.value - 1) * pageSize.value;
96-
}
118+
let collected: EntityType[] = [];
119+
let offset = 0;
120+
let total = 0;
121+
122+
do {
123+
const params: GetEntitiesParams = { ...baseParams, offset };
124+
const children = await api.getEntities(params);
97125
98-
isLoadingMembers.value = true;
99-
const children = await api.getEntities(params);
126+
if (!('entities' in children)) {
127+
break;
128+
}
100129
101-
if ('entities' in children) {
102-
membersFiltered.value = children.entities;
103-
totalMembers.value = children.total;
130+
collected = collected.concat(children.entities);
131+
total = children.total;
132+
offset += FETCH_LIMIT;
133+
} while (offset < total);
134+
135+
allMembers.value = collected;
136+
137+
const currentIndex = collected.findIndex((e) => e.id === route.query.id);
138+
if (currentIndex >= 0) {
139+
currentPage.value = Math.floor(currentIndex / pageSize.value) + 1;
104140
}
105-
isLoadingMembers.value = false;
106141
};
107142
108143
const fetchdata = async () => {
@@ -137,15 +172,13 @@ const fetchdata = async () => {
137172
await fetchMembers();
138173
};
139174
140-
const updatePage = async (page: number) => {
175+
const updatePage = (page: number) => {
141176
currentPage.value = page;
142-
await fetchMembers();
143177
};
144178
145179
watch(
146180
() => route.params,
147181
() => {
148-
currentPage.value = 1;
149182
fetchdata();
150183
},
151184
);
@@ -279,16 +312,23 @@ fetchdata();
279312
</el-col>
280313
</el-row>
281314

282-
<el-row v-if="membersFiltered?.length || totalMembers > 0">
315+
<el-row v-if="totalMembers > 0">
283316
<el-col>
284317
<el-card :body-style="{ padding: '0px' }" class="mx-10 p-5">
285318
<h5 class="text-2xl font-medium ">{{ t('object.otherObjectsInCollection') }} ({{ totalMembers }})</h5>
286319
<hr class="divider divider-gray pt-2" />
287-
<div v-if="isLoadingMembers" class="my-5">
288-
<el-skeleton :rows="4" animated />
320+
<div v-if="allMembers.length > 1" class="flex justify-between items-center mt-4">
321+
<router-link v-if="previousObject" :to="`/object?id=${encodeURIComponent(previousObject.id)}`">
322+
<el-button size="small">{{ t('common.previous') }}</el-button>
323+
</router-link>
324+
<span v-else />
325+
<router-link v-if="nextObject" :to="`/object?id=${encodeURIComponent(nextObject.id)}`">
326+
<el-button size="small">{{ t('common.next') }}</el-button>
327+
</router-link>
328+
<span v-else />
289329
</div>
290-
<ul v-else class="mt-4 space">
291-
<li v-for="d of membersFiltered">
330+
<ul class="mt-4 space">
331+
<li v-for="d of membersFiltered" :key="d.id">
292332
<p v-if="d.id === route.query.id" class="font-bold">
293333
{{ d.name || d.id }}
294334
</p>

0 commit comments

Comments
 (0)