Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 74 additions & 5 deletions src/views/ActivityAppFeed.vue
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ import { loadState } from '@nextcloud/initial-state'
import { translate as t } from '@nextcloud/l10n'
import moment from '@nextcloud/moment'
import { generateOcsUrl } from '@nextcloud/router'
import { useInfiniteScroll } from '@vueuse/core'
import { useDocumentVisibility, useInfiniteScroll } from '@vueuse/core'
import axios from 'axios'
import { computed, nextTick, onMounted, ref, watch } from 'vue'
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
import { useRoute } from 'vue-router'
import NcAppContent from '@nextcloud/vue/components/NcAppContent'
import NcEmptyContent from '@nextcloud/vue/components/NcEmptyContent'
Expand Down Expand Up @@ -99,11 +99,31 @@ const hasMoreActivites = ref(true)
const allActivities = ref<ActivityModel[]>([])

/**
* Last loaded activity
* Last loaded activity (oldest) for backward pagination
* This is set by the backend in the API result as a header value for pagination
*/
const lastActivityLoaded = ref<string>()

/**
* First loaded activity ID (newest) for polling new activities
*/
const newestActivityId = ref<number>()

/**
* Polling interval in milliseconds
*/
const POLL_INTERVAL = 30000

/**
* Polling timer reference
*/
let pollTimer: ReturnType<typeof setInterval> | undefined

/**
* Document visibility for pausing polling when tab is hidden
*/
const visibility = useDocumentVisibility()

/**
* Container element for the activites
*/
Expand Down Expand Up @@ -153,10 +173,16 @@ async function loadActivities() {
const since = lastActivityLoaded.value ?? '0'
loading.value = true
const response = await ncAxios.get(generateOcsUrl('apps/activity/api/v2/activity/{filter}?format=json&previews=true&since={since}', { filter: props.filter, since }))
allActivities.value.push(...response.data.ocs.data.map((raw) => new ActivityModel(raw)))
const newActivities = response.data.ocs.data.map((raw) => new ActivityModel(raw))
allActivities.value.push(...newActivities)
lastActivityLoaded.value = response.headers['x-activity-last-given']
hasMoreActivites.value = true

// Track the newest activity ID for polling
if (newestActivityId.value === undefined && newActivities.length > 0) {
newestActivityId.value = newActivities[0].id
}

nextTick(async () => {
if (container.value && container.value.clientHeight === container.value.scrollHeight) {
// Container is non-scrollable, thus useInfiniteScroll isn't triggered
Expand All @@ -179,10 +205,52 @@ async function loadActivities() {
}

/**
* Load activites when mounted
* Poll for new activities and prepend them to the list
*/
async function pollNewActivities() {
if (loading.value || newestActivityId.value === undefined || visibility.value === 'hidden') {
return
}

try {
const response = await ncAxios.get(generateOcsUrl('apps/activity/api/v2/activity/{filter}?format=json&previews=true&since={since}&sort=asc', { filter: props.filter, since: String(newestActivityId.value) }))
const newActivities: ActivityModel[] = response.data.ocs.data.map((raw) => new ActivityModel(raw))
if (newActivities.length > 0) {
// Sort newest first for prepending
newActivities.sort((a: ActivityModel, b: ActivityModel) => b.id - a.id)
newestActivityId.value = newActivities[0]!.id
allActivities.value.unshift(...newActivities)
}
} catch (error) {
// Silently ignore polling errors (304 = no new activities)
if (!axios.isAxiosError(error) || error.response?.status !== 304) {
logger.error(error as Error)
}
}
}

function startPolling() {
stopPolling()
pollTimer = setInterval(pollNewActivities, POLL_INTERVAL)
}

function stopPolling() {
if (pollTimer !== undefined) {
clearInterval(pollTimer)
pollTimer = undefined
}
}

/**
* Load activites when mounted and start polling
*/
onMounted(() => {
loadActivities()
startPolling()
})

onUnmounted(() => {
stopPolling()
})

/**
Expand All @@ -191,6 +259,7 @@ onMounted(() => {
watch(props, () => {
allActivities.value = []
lastActivityLoaded.value = undefined
newestActivityId.value = undefined
loadActivities()
})
</script>
Expand Down
Loading