Skip to content

Commit

Permalink
refactor: add Status and DocumentStatus components
Browse files Browse the repository at this point in the history
`Editor/Status` handles the status display in the menubar.
`Editor/DocumentStatus` handles the document messages on conflict etc.

Also move all files that are only used within it into the `Components/Editor` dir.

Signed-off-by: Max <max@nextcloud.com>
  • Loading branch information
max-nextcloud committed Jul 8, 2022
1 parent 5db2963 commit 41814bf
Show file tree
Hide file tree
Showing 6 changed files with 295 additions and 124 deletions.
File renamed without changes.
84 changes: 84 additions & 0 deletions src/components/Editor/DocumentStatus.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<!--
- @copyright Copyright (c) 2022 Max <max@nextcloud.com>
-
- @author Max <max@nextcloud.com>
-
- @license GNU AGPL version 3 or any later version
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-->

<template>
<div class="document-status">
<p v-if="idle" class="msg">
{{ t('text', 'Document idle for {timeout} minutes, click to continue editing', { timeout: IDLE_TIMEOUT }) }} <a class="button primary" @click="reconnect">{{ t('text', 'Reconnect') }}</a>
</p>
<p v-else-if="hasSyncCollission" class="msg icon-error">
{{ t('text', 'The document has been changed outside of the editor. The changes cannot be applied.') }}
</p>
<p v-else-if="hasConnectionIssue" class="msg">
{{ t('text', 'File could not be loaded. Please check your internet connection.') }} <a class="button primary" @click="reconnect">{{ t('text', 'Reconnect') }}</a>
</p>
<p v-if="lock" class="msg msg-locked">
<Lock /> {{ t('text', 'This file is opened read-only as it is currently locked by {user}.', { user: lock.displayName }) }}
</p>
</div>
</template>

<script>

import { ERROR_TYPE, IDLE_TIMEOUT } from './../../services/SyncService.js'
import Lock from 'vue-material-design-icons/Lock'

export default {
name: 'DocumentStatus',

components: {
Lock,
},

props: {
idle: {
type: Boolean,
require: true,
},
lock: {
type: Object,
default: null,
},
syncError: {
type: Object,
default: null,
},
hasConnectionIssue: {
type: Boolean,
require: true,
},
},

computed: {
hasSyncCollission() {
return this.syncError && this.syncError.type === ERROR_TYPE.SAVE_COLLISSION
},
},

methods: {
reconnect() {
this.$emit('reconnect')
},
},

}
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
import Tooltip from '@nextcloud/vue/dist/Directives/Tooltip'
import { generateUrl } from '@nextcloud/router'
import AvatarWrapper from './AvatarWrapper.vue'
import { useSyncServiceMixin } from './EditorWrapper.provider.js'
import { useSyncServiceMixin } from '../EditorWrapper.provider.js'

export default {
name: 'GuestNameDialog',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
import Popover from '@nextcloud/vue/dist/Components/Popover'
import Tooltip from '@nextcloud/vue/dist/Directives/Tooltip'
import AvatarWrapper from './AvatarWrapper.vue'
import store from '../mixins/store.js'
import store from '../../mixins/store.js'

const COLLABORATOR_IDLE_TIME = 60
const COLLABORATOR_DISCONNECT_TIME = 90
Expand Down
192 changes: 192 additions & 0 deletions src/components/Editor/Status.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
<!--
- @copyright Copyright (c) 2022 Max <max@nextcloud.com>
-
- @author Max <max@nextcloud.com>
-
- @license GNU AGPL version 3 or any later version
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-->

<template>
<div class="text-editor__session-list">
<div v-if="$isMobile" v-tooltip="lastSavedStatusTooltip" :class="saveStatusClass" />
<div v-else
v-tooltip="lastSavedStatusTooltip"
class="save-status"
:class="lastSavedStatusClass">
{{ lastSavedStatus }}
</div>
<SessionList :sessions="sessions">
<p slot="lastSaved" class="last-saved">
{{ t('text', 'Last saved') }}: {{ lastSavedString }}
</p>
<GuestNameDialog v-if="$isPublic && !currentSession.userId" :session="currentSession" />
</SessionList>
</div>
</template>

<script>

import { ERROR_TYPE } from './../../services/SyncService.js'
import {
useIsMobileMixin,
useIsPublicMixin,
} from '../EditorWrapper.provider.js'

export default {
name: 'Status',

components: {
SessionList: () => import(/* webpackChunkName: "editor-collab" */'./SessionList.vue'),
GuestNameDialog: () => import(/* webpackChunkName: "editor-guest" */'./GuestNameDialog.vue'),
},

mixins: [useIsMobileMixin, useIsPublicMixin],

props: {
hasConnectionIssue: {
type: Boolean,
require: true,
},
dirty: {
type: Boolean,
require: true,
},
lastSavedString: {
type: String,
default: '',
},
document: {
type: Object,
default: null,
},
syncError: {
type: Object,
default: null,
},
sessions: {
type: Object,
default: () => { return {} },
},
},

computed: {
lastSavedStatus() {
if (this.hasConnectionIssue) {
return t('text',
this.$isMobile
? 'Offline'
: 'Offline, changes will be saved when online'
)
}
return this.dirtyStateIndicator ? t('text', 'Saving …') : t('text', 'Saved')
},
lastSavedStatusClass() {
return this.syncError && this.lastSavedString !== '' ? 'error' : ''
},
dirtyStateIndicator() {
return this.dirty || this.hasUnsavedChanges
},
lastSavedStatusTooltip() {
let message = t('text', 'Last saved {lastSaved}', { lastSaved: this.lastSavedString })
if (this.hasSyncCollission) {
message = t('text', 'The document has been changed outside of the editor. The changes cannot be applied.')
}
if (this.dirty || this.hasUnsavedChanges) {
message += ' - ' + t('text', 'Unsaved changes')
}
return { content: message, placement: 'bottom' }
},

hasUnsavedChanges() {
return this.document && this.document.lastSavedVersion < this.document.currentVersion
},
hasSyncCollission() {
return this.syncError && this.syncError.type === ERROR_TYPE.SAVE_COLLISSION
},
saveStatusClass() {
if (this.syncError && this.lastSavedString !== '') {
return 'save-error'
}
return this.dirtyStateIndicator ? 'saving-status' : 'saved-status'
},
currentSession() {
return Object.values(this.sessions).find((session) => session.isCurrent)
},
},
}
</script>

<style scoped lang="scss">

.text-editor__session-list {
display: flex;

input, div {
vertical-align: middle;
margin-left: 3px;
}
}

.save-status {
display: inline-flex;
padding: 0;
text-overflow: ellipsis;
color: var(--color-text-lighter);
position: relative;
top: 9px;
min-width: 85px;
max-height: 36px;

&.error {
background-color: var(--color-error);
color: var(--color-main-background);
border-radius: 3px;
}
}
</style>

<style lang="scss">
.saved-status,.saving-status {
display: inline-flex;
padding: 0;
text-overflow: ellipsis;
color: var(--color-text-lighter);
position: relative;
background-color: white;
width: 38px !important;
height: 38px !important;
left: 25%;
z-index: 2;
top: 0px;
}

.saved-status {
border: 2px solid #04AA6D;
border-radius: 50%;
}

.saving-status {
border: 2px solid #f3f3f3;
border-top: 2px solid #3498db;
border-radius: 50%;
animation: spin 2s linear infinite;
}

.last-saved {
padding: 6px;
}
</style>
Loading

0 comments on commit 41814bf

Please sign in to comment.