diff --git a/src/Forms.vue b/src/Forms.vue index 457d44b1a..61be16a46 100644 --- a/src/Forms.vue +++ b/src/Forms.vue @@ -351,6 +351,53 @@ export default { loading.value = false } + /** + * Clean up stale localStorage entries for forms that are no longer available. + * Removes localStorage keys matching the pattern `nextcloud_forms_*_activeResponseView` + * where the form hash no longer exists in the current forms list. + */ + const cleanupStaleLocalStorageEntries = () => { + try { + // Get all current form hashes + const currentFormHashes = new Set( + [...forms.value, ...allSharedForms.value].map( + (form) => form.hash, + ), + ) + + // Iterate through all localStorage keys + const keysToRemove = [] + for (let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i) + if ( + key + && key.startsWith('nextcloud_forms_') + && key.endsWith('_activeResponseView') + ) { + // Extract hash from key: nextcloud_forms__activeResponseView + const hash = key.substring( + 'nextcloud_forms_'.length, + key.length - '_activeResponseView'.length, + ) + // If form hash is not in current forms, mark for removal + if (!currentFormHashes.has(hash)) { + keysToRemove.push(key) + } + } + } + + // Remove stale entries + keysToRemove.forEach((key) => { + localStorage.removeItem(key) + logger.debug(`Removed stale localStorage entry: ${key}`) + }) + } catch (err) { + logger.debug('Error cleaning up stale localStorage entries', { + error: err, + }) + } + } + /** * Fetch a partial form by its hash after initial load completes. * @@ -447,6 +494,17 @@ export default { forms.value.splice(formIndex, 1) deletedFormHash.value = deletedHash + // Remove localStorage entry for this form's active response view + try { + localStorage.removeItem( + `nextcloud_forms_${deletedHash}_activeResponseView`, + ) + } catch (err) { + logger.debug('Error removing localStorage entry for deleted form', { + error: err, + }) + } + if (deletedHash === routeHash.value && route.name !== 'root') { // Navigate to root without triggering route guards router.replace({ name: 'root' }) @@ -477,8 +535,9 @@ export default { } } - onMounted(() => { - loadForms() + onMounted(async () => { + await loadForms() + cleanupStaleLocalStorageEntries() subscribe('forms:last-updated:set', onLastUpdatedByEventBus) subscribe('forms:ownership-transfered', onDeleteForm) }) diff --git a/src/views/Results.vue b/src/views/Results.vue index cfc61775a..f27f16210 100644 --- a/src/views/Results.vue +++ b/src/views/Results.vue @@ -498,6 +498,7 @@ export default { // Reload results when form changes async hash() { await this.fetchFullForm(this.form.id) + this.loadActiveResponseViewFromLocalStorage() this.loadFormResults() SetWindowTitle(this.formTitle) }, @@ -521,15 +522,63 @@ export default { }) this.loadFormResults() }, INPUT_DEBOUNCE_MS), + + // Persist active response view to localStorage when it changes + activeResponseView(newView) { + this.saveActiveResponseViewToLocalStorage(newView.id) + }, }, async beforeMount() { await this.fetchFullForm(this.form.id) + this.loadActiveResponseViewFromLocalStorage() this.loadFormResults() SetWindowTitle(this.formTitle) }, methods: { + /** + * Load the active response view preference from localStorage for the current form. + * Applies stored value if available and otherwise resets to default (summary) + */ + loadActiveResponseViewFromLocalStorage() { + try { + const storedViewId = localStorage.getItem( + `nextcloud_forms_${this.form.hash}_activeResponseView`, + ) + if (storedViewId) { + const view = responseViews.find((v) => v.id === storedViewId) + if (view) { + this.activeResponseView = view + } + } else { + this.activeResponseView = responseViews[0] + } + } catch (err) { + logger.debug('Error loading activeResponseView from localStorage', { + error: err, + }) + } + }, + + /** + * Save the active response view preference to localStorage for the current form. + * + * @param {string} viewId - The ID of the view ('summary' or 'responses') + */ + saveActiveResponseViewToLocalStorage(viewId) { + try { + localStorage.setItem( + `nextcloud_forms_${this.form.hash}_activeResponseView`, + viewId, + ) + } catch (err) { + logger.debug('Error saving activeResponseView to localStorage', { + error: err, + }) + } + }, + async onUnlinkFile() { await axios.patch( generateOcsUrl('apps/forms/api/v3/forms/{formId}', {