nextcloud / tasks

:white_check_mark: Tasks app for Nextcloud
https://apps.nextcloud.com/apps/tasks
GNU Affero General Public License v3.0
572 stars 99 forks source link

Progressive Web App (PWA) support #2516

Open RayBB opened 6 months ago

RayBB commented 6 months ago

Is your feature request related to a problem? Please describe.

It would be great if we could get some basic PWA setup going to start caching. Right now when I'm on a slow connection it can be quite slow to load the page.

First step is to just cache the response to show right away and then show updates once we get them from the server. Later we could think about things like adding tasks when offline and sending later.

Describe the solution you'd like

The memories app has a pretty good PWA that we could look at for inspiration

https://github.com/pulsejet/memories/blob/e765a89989c42cd51e39ecfe053cf8625837d3ab/src/service-worker.ts

Describe alternatives you've considered

No response

Additional context

No response

RayBB commented 1 month ago

@raimund-schluessler I've been thinking about how to speed of the first page load. It seems that a significant reworking would have to be done to support something like stale-while-revalidate caching (tanstack query is the popular choice for this now), which is common in PWAs.

However, perhaps a simpler approach is to just cache the data in local storage after it loads and then show it before we get updated data from the server on page load.

Example:

async beforeMount() {
  const cachedData = localStorage.getItem('tasksAppCache')

  if (cachedData) {
    const { collections, settings, calendars, principals, tasks } = JSON.parse(cachedData)
    this.$store.commit('setCollections', collections)
    this.$store.commit('setSettings', settings)
    this.$store.commit('setCalendars', calendars)
    this.$store.commit('setPrincipals', principals)
    this.$store.commit('setTasks', tasks)
  }

    await this.$store.dispatch('loadCollections')
    await this.$store.dispatch('loadSettings')
    await client.connect({ enableCalDAV: true })
    await this.$store.dispatch('fetchCurrentUserPrincipal')
    let { calendars } = await this.$store.dispatch('getCalendarsAndTrashBin')
    calendars = calendars.filter(calendar => calendar.supportsTasks)
    const owners = [...new Set(calendars.map(calendar => calendar.owner))]
    await Promise.all(owners.map(owner => this.$store.dispatch('fetchPrincipalByUrl', { url: owner })))

    if (calendars.length === 0) {
      const color = this.$OCA.Theming?.color || '#0082C9'
      await this.$store.dispatch('appendCalendar', { displayName: t('tasks', 'Tasks'), color })
    }

    await this.fetchTasks()

    const cacheData = {
      collections: this.$store.state.collections,
      settings: this.$store.state.settings,
      calendars: this.$store.state.calendars,
      principals: this.$store.state.principals,
      tasks: this.$store.state.tasks,
    }
    localStorage.setItem('tasksAppCache', JSON.stringify(cacheData))
},

This would make it so we don't have to wait for responses from the server before rendering tasks and new tasks would just pop into view a moment later. We could also add a loading indicator. In later PRs the cache could be updated anytime a change is set to the server.

What are your thoughts on an approach like this?