From a2a445e6a2ddfe3b787a556c52943ad18f365c53 Mon Sep 17 00:00:00 2001 From: Richard Tibbles Date: Fri, 9 Jun 2023 15:19:45 -0700 Subject: [PATCH 01/63] Switch unsynced change monitoring to a liveQuery to further remove Dexie Observable. --- .../contentcuration/frontend/shared/app.js | 3 +++ .../shared/vuex/syncProgressPlugin/index.js | 27 +++++++++++-------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/contentcuration/contentcuration/frontend/shared/app.js b/contentcuration/contentcuration/frontend/shared/app.js index 071e0c0813..f93353ba74 100644 --- a/contentcuration/contentcuration/frontend/shared/app.js +++ b/contentcuration/contentcuration/frontend/shared/app.js @@ -350,5 +350,8 @@ export default async function startApp({ store, router, index }) { // to the session state. injectVuexStore(store); + // Start listening for unsynced change events in IndexedDB + store.listenForIndexedDBChanges(); + rootVue = new Vue(config); } diff --git a/contentcuration/contentcuration/frontend/shared/vuex/syncProgressPlugin/index.js b/contentcuration/contentcuration/frontend/shared/vuex/syncProgressPlugin/index.js index 60a2324d2e..07811b8666 100644 --- a/contentcuration/contentcuration/frontend/shared/vuex/syncProgressPlugin/index.js +++ b/contentcuration/contentcuration/frontend/shared/vuex/syncProgressPlugin/index.js @@ -1,3 +1,4 @@ +import { liveQuery } from 'dexie'; import syncProgressModule from './syncProgressModule'; import db from 'shared/data/db'; import { CHANGES_TABLE } from 'shared/data/constants'; @@ -5,18 +6,22 @@ import { CHANGES_TABLE } from 'shared/data/constants'; const SyncProgressPlugin = store => { store.registerModule('syncProgress', syncProgressModule); - db.on('changes', function(changes) { - const changesTableUpdated = changes.some(change => change.table === CHANGES_TABLE); - if (!changesTableUpdated) { - return; - } + store.listenForIndexedDBChanges = () => { + const observable = liveQuery(() => { + return db[CHANGES_TABLE].toCollection() + .filter(c => !c.synced) + .first(Boolean); + }); - db[CHANGES_TABLE].toCollection() - .filter(c => !c.synced) - .limit(1) - .count() - .then(count => store.commit('SET_UNSAVED_CHANGES', count > 0)); - }); + const subscription = observable.subscribe({ + next(result) { + store.commit('SET_UNSAVED_CHANGES', result); + }, + error() { + subscription.unsubscribe(); + }, + }); + }; }; export default SyncProgressPlugin; From 6960803b93953ca8d1b3319f13a38387261cd367 Mon Sep 17 00:00:00 2001 From: Richard Tibbles Date: Fri, 9 Jun 2023 15:29:41 -0700 Subject: [PATCH 02/63] Remove resource based listeners in favour of liveQuery. --- .../contentcuration/frontend/shared/app.js | 18 ++++++++++++++++++ .../frontend/shared/data/index.js | 8 -------- .../frontend/shared/data/resources.js | 11 ----------- 3 files changed, 18 insertions(+), 19 deletions(-) diff --git a/contentcuration/contentcuration/frontend/shared/app.js b/contentcuration/contentcuration/frontend/shared/app.js index f93353ba74..582044ff67 100644 --- a/contentcuration/contentcuration/frontend/shared/app.js +++ b/contentcuration/contentcuration/frontend/shared/app.js @@ -1,4 +1,5 @@ import 'regenerator-runtime/runtime'; +import { liveQuery } from 'dexie'; import * as Sentry from '@sentry/vue'; import Vue from 'vue'; import VueRouter from 'vue-router'; @@ -115,6 +116,7 @@ import { i18nSetup } from 'shared/i18n'; import './styles/vuetify.css'; import 'shared/styles/main.less'; import Base from 'shared/Base.vue'; +import urls from 'shared/urls'; import ActionLink from 'shared/views/ActionLink'; import Menu from 'shared/views/Menu'; import { initializeDB, resetDB } from 'shared/data'; @@ -316,7 +318,23 @@ export default async function startApp({ store, router, index }) { await resetDB(); } if (currentUser.id !== undefined && currentUser.id !== null) { + // The user is logged on, so persist that to the session table in indexeddb await store.dispatch('saveSession', currentUser, { root: true }); + // Also watch in case the user logs out, then we should redirect to the login page + const observable = liveQuery(() => { + return Session.table.toCollection().first(Boolean); + }); + + const subscription = observable.subscribe({ + next(result) { + if (!result && !window.location.pathname.endsWith(urls.accounts())) { + window.location = urls.accounts(); + } + }, + error() { + subscription.unsubscribe(); + }, + }); } await Session.setChannelScope(); diff --git a/contentcuration/contentcuration/frontend/shared/data/index.js b/contentcuration/contentcuration/frontend/shared/data/index.js index 380e09f9c1..1baed3d8d5 100644 --- a/contentcuration/contentcuration/frontend/shared/data/index.js +++ b/contentcuration/contentcuration/frontend/shared/data/index.js @@ -37,18 +37,10 @@ if (process.env.NODE_ENV !== 'production' && typeof window !== 'undefined') { window.resetDB = resetDB; } -function applyResourceListener(change) { - const resource = INDEXEDDB_RESOURCES[change.table]; - if (resource && resource.listeners && resource.listeners[change.type]) { - resource.listeners[change.type](change); - } -} - export async function initializeDB() { try { setupSchema(); await db.open(); - db.on('changes', changes => changes.map(applyResourceListener)); document.addEventListener('visibilitychange', () => { if (document.hidden) { stopSyncing(); diff --git a/contentcuration/contentcuration/frontend/shared/data/resources.js b/contentcuration/contentcuration/frontend/shared/data/resources.js index 3843753b6b..008883fa18 100644 --- a/contentcuration/contentcuration/frontend/shared/data/resources.js +++ b/contentcuration/contentcuration/frontend/shared/data/resources.js @@ -214,7 +214,6 @@ class IndexedDBResource { uuid = true, indexFields = [], syncable = false, - listeners = {}, ...options } = {}) { this.tableName = tableName; @@ -228,9 +227,6 @@ class IndexedDBResource { copyProperties(this, options); // By default these resources do not sync changes to the backend. this.syncable = syncable; - // An object for listening to specific change events on this resource in order to - // allow for side effects from changes - should be a map of change type to handler function. - this.listeners = listeners; } get table() { @@ -977,13 +973,6 @@ export const Session = new IndexedDBResource({ tableName: TABLE_NAMES.SESSION, idField: CURRENT_USER, uuid: false, - listeners: { - [CHANGE_TYPES.DELETED]: function() { - if (!window.location.pathname.endsWith(urls.accounts())) { - window.location = urls.accounts(); - } - }, - }, get currentChannel() { return window.CHANNEL_EDIT_GLOBAL || {}; }, From d9388937937e39e6b06ee12d7b73ae1ab3e657fd Mon Sep 17 00:00:00 2001 From: Richard Tibbles Date: Fri, 9 Jun 2023 15:47:52 -0700 Subject: [PATCH 03/63] Adds a basic global saving indicator to the channel edit page. --- .../components/edit/SavingIndicator.vue | 43 ++++++++++++------- .../views/TreeView/TreeViewBase.vue | 3 ++ 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/edit/SavingIndicator.vue b/contentcuration/contentcuration/frontend/channelEdit/components/edit/SavingIndicator.vue index 682a58d6c7..09a43210b0 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/edit/SavingIndicator.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/components/edit/SavingIndicator.vue @@ -27,12 +27,12 @@ props: { nodeIds: { type: Array, - default: () => [], + default: null, }, }, data() { return { - hasChanges: false, + hasNodeChanges: false, isSaving: false, lastSaved: null, lastSavedText: '', @@ -43,6 +43,13 @@ computed: { ...mapGetters('file', ['getContentNodeFiles']), ...mapGetters('assessmentItem', ['getAssessmentItems']), + ...mapGetters(['areAllChangesSaved']), + hasChanges() { + if (this.nodeIds) { + return this.hasNodeChanges; + } + return !this.areAllChangesSaved; + }, }, watch: { hasChanges(hasChanges) { @@ -62,21 +69,27 @@ }, }, mounted() { - this.interval = setInterval(() => { - const files = flatten(this.nodeIds.map(this.getContentNodeFiles)); - const assessmentItems = flatten(this.nodeIds.map(this.getAssessmentItems)); - this.checkSavingProgress({ - contentNodeIds: this.nodeIds, - fileIds: files.map(f => f.id).filter(Boolean), - assessmentIds: assessmentItems - .map(ai => [ai.contentnode, ai.assessment_id]) - .filter(Boolean), - }).then(hasChanges => (this.hasChanges = hasChanges)); - }, CHECK_SAVE_INTERVAL); + if (this.nodeIds) { + this.interval = setInterval(() => { + const files = flatten(this.nodeIds.map(this.getContentNodeFiles)); + const assessmentItems = flatten(this.nodeIds.map(this.getAssessmentItems)); + this.checkSavingProgress({ + contentNodeIds: this.nodeIds, + fileIds: files.map(f => f.id).filter(Boolean), + assessmentIds: assessmentItems + .map(ai => [ai.contentnode, ai.assessment_id]) + .filter(Boolean), + }).then(hasChanges => (this.hasNodeChanges = hasChanges)); + }, CHECK_SAVE_INTERVAL); + } }, beforeDestroy() { - clearInterval(this.interval); - clearInterval(this.updateLastSavedInterval); + if (this.interval) { + clearInterval(this.interval); + } + if (this.updateLastSavedInterval) { + clearInterval(this.updateLastSavedInterval); + } }, methods: { ...mapActions('contentNode', ['checkSavingProgress']), diff --git a/contentcuration/contentcuration/frontend/channelEdit/views/TreeView/TreeViewBase.vue b/contentcuration/contentcuration/frontend/channelEdit/views/TreeView/TreeViewBase.vue index 222cb73ebc..d9e49ca59b 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/views/TreeView/TreeViewBase.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/views/TreeView/TreeViewBase.vue @@ -55,6 +55,7 @@ +
Date: Tue, 13 Jun 2023 11:27:39 -0700 Subject: [PATCH 04/63] Handle side-loading node data separate APIs --- .../views/ImportFromChannels/BrowsingCard.vue | 2 +- .../ImportFromChannelsModal.vue | 6 +- .../channelEdit/vuex/contentNode/actions.js | 21 +- .../channelEdit/vuex/contentNode/utils.js | 5 +- .../vuex/importFromChannels/actions.js | 47 ++++- .../__tests__/ContentNodeResource.spec.js | 40 +++- .../shared/data/applyRemoteChanges.js | 4 +- .../frontend/shared/data/public.js | 190 ++++++++++++++++++ .../frontend/shared/data/resources.js | 34 +++- .../shared/views/form/DropdownWrapper.vue | 6 +- .../search/viewsets/contentnode.py | 32 +-- 11 files changed, 341 insertions(+), 46 deletions(-) create mode 100644 contentcuration/contentcuration/frontend/shared/data/public.js diff --git a/contentcuration/contentcuration/frontend/channelEdit/views/ImportFromChannels/BrowsingCard.vue b/contentcuration/contentcuration/frontend/channelEdit/views/ImportFromChannels/BrowsingCard.vue index a8a92087cd..9a9621f8f5 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/views/ImportFromChannels/BrowsingCard.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/views/ImportFromChannels/BrowsingCard.vue @@ -149,7 +149,7 @@ if (this.isTopic) { return `${baseUrl}#/${this.node.id}`; } - return `${baseUrl}#/${this.node.parent_id}/${this.node.id}`; + return `${baseUrl}#/${this.node.parent}/${this.node.id}`; }, resourcesMsg() { let count; diff --git a/contentcuration/contentcuration/frontend/channelEdit/views/ImportFromChannels/ImportFromChannelsModal.vue b/contentcuration/contentcuration/frontend/channelEdit/views/ImportFromChannels/ImportFromChannelsModal.vue index e3e394602d..60423ee33b 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/views/ImportFromChannels/ImportFromChannelsModal.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/views/ImportFromChannels/ImportFromChannelsModal.vue @@ -74,7 +74,7 @@ From 0ac48fd9864376fb22be86534baa1ce75d201d38 Mon Sep 17 00:00:00 2001 From: Samson Akol Date: Fri, 16 Jun 2023 23:11:40 +0300 Subject: [PATCH 16/63] Fixes error that appears in the console on drag nested resource to to its root --- .../channelEdit/views/CurrentTopicView.vue | 91 +++++-------------- 1 file changed, 23 insertions(+), 68 deletions(-) diff --git a/contentcuration/contentcuration/frontend/channelEdit/views/CurrentTopicView.vue b/contentcuration/contentcuration/frontend/channelEdit/views/CurrentTopicView.vue index d953290801..8417a88140 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/views/CurrentTopicView.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/views/CurrentTopicView.vue @@ -1,33 +1,14 @@ @@ -56,11 +30,7 @@ - +
-
+
{{ selectionText }}
@@ -154,12 +120,7 @@