diff options
author | Julius Härtl <jus@bitgrid.net> | 2019-05-07 15:39:13 +0300 |
---|---|---|
committer | Julius Härtl <jus@bitgrid.net> | 2019-05-07 15:39:13 +0300 |
commit | aed5581c6d40b2fd843cb1fd478c616e70a2f9f7 (patch) | |
tree | a5b08713d404b6ad1ceab5e3421fab9bb2b53e3c | |
parent | 66df4652bba0cbe403b6eb393d9b0e7f3ab8be36 (diff) |
Add conflict view and loading indicator
Signed-off-by: Julius Härtl <jus@bitgrid.net>
-rw-r--r-- | src/collab.js | 11 | ||||
-rw-r--r-- | src/components/Editor.vue | 78 |
2 files changed, 86 insertions, 3 deletions
diff --git a/src/collab.js b/src/collab.js index 17ff8f92c..4135516e2 100644 --- a/src/collab.js +++ b/src/collab.js @@ -115,6 +115,10 @@ class EditorSync { this._forcedSave = true } + manualSave() { + this._manualSave = true + } + fetchSteps() { if (this.lock) { return; @@ -136,13 +140,18 @@ class EditorSync { sessionId: this.session.id, token: this.session.token, version: authority.steps.length, - autosaveContent + autosaveContent, + force: !!this._forcedSave, + manualSave: !!this._manualSave } }).then((response) => { if (this.document.lastSavedVersion < response.data.document.lastSavedVersion) { console.debug('Saved document', response.data.document) this.document = response.data.document } + this.view.setProps({editable: () => true}) + + this.onSyncHandlers.forEach((handler) => handler(response.data)) diff --git a/src/components/Editor.vue b/src/components/Editor.vue index ff8f0db7b..380313cdd 100644 --- a/src/components/Editor.vue +++ b/src/components/Editor.vue @@ -26,7 +26,14 @@ <div class="save-status" :class="lastSavedStatusClass" v-tooltip="lastSavedStatusTooltip">{{ lastSavedStatus }}</div> <avatar v-for="session in activeSessions" :key="session.id" :user="session.userId" :displayName="session.displayName" :style="sessionStyle(session)"></avatar> </div> - <div id="editor"></div> + <div id="editor-wrapper" :class="{'has-conflicts': syncError && syncError.type === ERROR_TYPE.SAVE_COLLISSION, 'icon-loading': !initialLoading}"> + <div id="editor"></div> + <div id="remote" v-if="syncError && syncError.type === ERROR_TYPE.SAVE_COLLISSION"></div> + </div> + <div v-if="syncError && syncError.type === ERROR_TYPE.SAVE_COLLISSION" id="resolve-conflicts"> + <button @click="resolveUseThisVersion">Use your version</button> + <button @click="resolveUseServerVersion">Use the server version</button> + </div> </div> </template> @@ -44,7 +51,9 @@ import {EditorView} from 'prosemirror-view' import {exampleSetup} from 'prosemirror-example-setup' import {schema, defaultMarkdownParser, defaultMarkdownSerializer} from 'prosemirror-markdown' - import { debounce, bind } from 'lodash' + import debounce from 'lodash/debounce' + import bind from 'lodash/bind' + import {baseKeymap} from "prosemirror-commands" import {keymap} from "prosemirror-keymap" @@ -90,6 +99,7 @@ sessions: [], name: 'Guest', dirty: false, + initialLoading: false, lastSavedString: '', syncError: null, ERROR_TYPE: ERROR_TYPE @@ -171,6 +181,7 @@ const {editor, authority} = this.initEditor(response.data, fileContent.data); this.authority = authority this.authority.onSync((data) => { + this.syncError = null if (data.document) { this.document = data.document } @@ -182,10 +193,16 @@ type: ERROR_TYPE.SAVE_COLLISSION, data: data } + this.$nextTick(() => { + this.initRemoteView() + }) } }) this.authority.onStateChange(() => { this.dirty = this.authority.dirty + if (!this.initialLoading) { + this.initialLoading = !this.authority.dirty + } }) setInterval(() => { this.updateLastSavedStatus() }, 2000) @@ -198,6 +215,36 @@ }, + initRemoteView() { + if (this.remoteView) { + return; + } + this.remoteView = new EditorView(document.querySelector("#remote"), { + state: EditorState.create({ + doc: defaultMarkdownParser.parse(this.syncError.data.outsideChange), + plugins: [ + ...exampleSetup({schema}) + ] + }), + focus() { this.view.focus() }, + destroy() { this.view.destroy() } + }) + view.setProps({editable: () => false}) + }, + + resolveUseThisVersion() { + this.authority.forceSave() + this.removeRemoteView() + }, + + resolveUseServerVersion() { + this.removeRemoteView() + }, + + removeRemoteView() { + this.remoteView.destroy() + }, + initEditor: (data, fileContent) => { const authority = new EditorSync(defaultMarkdownParser.parse(fileContent), data) @@ -256,11 +303,38 @@ background-color: var(--color-main-background); } + #editor-wrapper { + display: flex; + height: 100%; + &.icon-loading { + #editor { + opacity: 0.3; + } + } + } + + #resolve-conflicts { + display: flex; + button { + margin: auto; + } + } + #editor { height: 100%; overflow-y: scroll; } + #editor-container.has-conflicts { + #remove, #editor { + width: 50%; + } + + #remote .ProseMirror-menubar { + visibility: hidden; + } + } + #editor-session-list { position: absolute; top: 0; |