123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303 |
- /**
- * @copyright Copyright (c) 2019 John Molakvoæ <skjnldsv@protonmail.com>
- *
- * @author John Molakvoæ <skjnldsv@protonmail.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/>.
- *
- */
- import PQueue from 'p-queue'
- import debounce from 'debounce'
- import Share from '../models/Share'
- import SharesRequests from './ShareRequests'
- import ShareTypes from './ShareTypes'
- import Config from '../services/ConfigService'
- import { getCurrentUser } from '@nextcloud/auth'
- export default {
- mixins: [SharesRequests, ShareTypes],
- props: {
- fileInfo: {
- type: Object,
- default: () => {},
- required: true
- },
- share: {
- type: Share,
- default: null
- }
- },
- data() {
- return {
- config: new Config(),
- // errors helpers
- errors: {},
- // component status toggles
- loading: false,
- saving: false,
- open: false,
- // concurrency management queue
- // we want one queue per share
- updateQueue: new PQueue({ concurrency: 1 }),
- /**
- * ! This allow vue to make the Share class state reactive
- * ! do not remove it ot you'll lose all reactivity here
- */
- reactiveState: this.share && this.share.state,
- SHARE_TYPES: {
- SHARE_TYPE_USER: OC.Share.SHARE_TYPE_USER,
- SHARE_TYPE_GROUP: OC.Share.SHARE_TYPE_GROUP,
- SHARE_TYPE_LINK: OC.Share.SHARE_TYPE_LINK,
- SHARE_TYPE_EMAIL: OC.Share.SHARE_TYPE_EMAIL,
- SHARE_TYPE_REMOTE: OC.Share.SHARE_TYPE_REMOTE,
- SHARE_TYPE_CIRCLE: OC.Share.SHARE_TYPE_CIRCLE,
- SHARE_TYPE_GUEST: OC.Share.SHARE_TYPE_GUEST,
- SHARE_TYPE_REMOTE_GROUP: OC.Share.SHARE_TYPE_REMOTE_GROUP,
- SHARE_TYPE_ROOM: OC.Share.SHARE_TYPE_ROOM
- }
- }
- },
- computed: {
- /**
- * Does the current share have an expiration date
- * @returns {boolean}
- */
- hasExpirationDate: {
- get: function() {
- return this.config.isDefaultExpireDateEnforced || !!this.share.expireDate
- },
- set: function(enabled) {
- this.share.expireDate = enabled
- ? this.config.defaultExpirationDateString !== ''
- ? this.config.defaultExpirationDateString
- : moment().format('YYYY-MM-DD')
- : ''
- }
- },
- /**
- * Does the current share have a note
- * @returns {boolean}
- */
- hasNote: {
- get: function() {
- return !!this.share.note
- },
- set: function(enabled) {
- this.share.note = enabled
- ? t('files_sharing', 'Enter a note for the share recipient')
- : ''
- }
- },
- dateTomorrow() {
- return moment().add(1, 'days')
- },
- dateMaxEnforced() {
- return this.config.isDefaultExpireDateEnforced
- && moment().add(1 + this.config.defaultExpireDate, 'days')
- },
- /**
- * Datepicker lang values
- * https://github.com/nextcloud/nextcloud-vue/pull/146
- * TODO: have this in vue-components
- *
- * @returns {int}
- */
- firstDay() {
- return window.firstDay
- ? window.firstDay
- : 0 // sunday as default
- },
- lang() {
- // fallback to default in case of unavailable data
- return {
- days: window.dayNamesShort
- ? window.dayNamesShort // provided by nextcloud
- : ['Sun.', 'Mon.', 'Tue.', 'Wed.', 'Thu.', 'Fri.', 'Sat.'],
- months: window.monthNamesShort
- ? window.monthNamesShort // provided by nextcloud
- : ['Jan.', 'Feb.', 'Mar.', 'Apr.', 'May.', 'Jun.', 'Jul.', 'Aug.', 'Sep.', 'Oct.', 'Nov.', 'Dec.'],
- placeholder: {
- date: 'Select Date' // TODO: Translate
- }
- }
- },
- isShareOwner() {
- return this.share && this.share.owner === getCurrentUser().uid
- }
- },
- methods: {
- /**
- * Check if a share is valid before
- * firing the request
- *
- * @param {Share} share the share to check
- * @returns {Boolean}
- */
- checkShare(share) {
- if (share.password) {
- if (typeof share.password !== 'string' || share.password.trim() === '') {
- return false
- }
- }
- if (share.expirationDate) {
- const date = moment(share.expirationDate)
- if (!date.isValid()) {
- return false
- }
- }
- return true
- },
- /**
- * ActionInput can be a little tricky to work with.
- * Since we expect a string and not a Date,
- * we need to process the value here
- *
- * @param {Date} date js date to be parsed by moment.js
- */
- onExpirationChange(date) {
- // format to YYYY-MM-DD
- const value = moment(date).format('YYYY-MM-DD')
- this.share.expireDate = value
- this.queueUpdate('expireDate')
- },
- /**
- * Uncheck expire date
- * We need this method because @update:checked
- * is ran simultaneously as @uncheck, so
- * so we cannot ensure data is up-to-date
- */
- onExpirationDisable() {
- this.share.expireDate = ''
- this.queueUpdate('expireDate')
- },
- /**
- * Delete share button handler
- */
- async onDelete() {
- try {
- this.loading = true
- this.open = false
- await this.deleteShare(this.share.id)
- console.debug('Share deleted', this.share.id)
- this.$emit('remove:share', this.share)
- } catch (error) {
- // re-open menu if error
- this.open = true
- } finally {
- this.loading = false
- }
- },
- /**
- * Send an update of the share to the queue
- *
- * @param {string} property the property to sync
- */
- queueUpdate(property) {
- if (this.share.id) {
- // force value to string because that is what our
- // share api controller accepts
- const value = this.share[property].toString()
- this.updateQueue.add(async() => {
- this.saving = true
- this.errors = {}
- try {
- await this.updateShare(this.share.id, {
- property,
- value
- })
- // clear any previous errors
- this.$delete(this.errors, property)
- // reset password state after sync
- this.$delete(this.share, 'newPassword')
- } catch ({ property, message }) {
- this.onSyncError(property, message)
- } finally {
- this.saving = false
- }
- })
- } else {
- console.error('Cannot update share.', this.share, 'No valid id')
- }
- },
- /**
- * Manage sync errors
- * @param {string} property the errored property, e.g. 'password'
- * @param {string} message the error message
- */
- onSyncError(property, message) {
- // re-open menu if closed
- this.open = true
- switch (property) {
- case 'password':
- case 'pending':
- case 'expireDate':
- case 'note': {
- // show error
- this.$set(this.errors, property, message)
- let propertyEl = this.$refs[property]
- if (propertyEl) {
- if (propertyEl.$el) {
- propertyEl = propertyEl.$el
- }
- // focus if there is a focusable action element
- const focusable = propertyEl.querySelector('.focusable')
- if (focusable) {
- focusable.focus()
- }
- }
- break
- }
- }
- },
- /**
- * Debounce queueUpdate to avoid requests spamming
- * more importantly for text data
- *
- * @param {string} property the property to sync
- */
- debounceQueueUpdate: debounce(function(property) {
- this.queueUpdate(property)
- }, 500)
- }
- }
|