SharingTab.vue 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. <!--
  2. - @copyright Copyright (c) 2019 John Molakvoæ <skjnldsv@protonmail.com>
  3. -
  4. - @author John Molakvoæ <skjnldsv@protonmail.com>
  5. -
  6. - @license GNU AGPL version 3 or any later version
  7. -
  8. - This program is free software: you can redistribute it and/or modify
  9. - it under the terms of the GNU Affero General Public License as
  10. - published by the Free Software Foundation, either version 3 of the
  11. - License, or (at your option) any later version.
  12. -
  13. - This program is distributed in the hope that it will be useful,
  14. - but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. - GNU Affero General Public License for more details.
  17. -
  18. - You should have received a copy of the GNU Affero General Public License
  19. - along with this program. If not, see <http://www.gnu.org/licenses/>.
  20. -
  21. -->
  22. <template>
  23. <Tab :icon="icon" :name="name" :class="{ 'icon-loading': loading }">
  24. <!-- error message -->
  25. <div v-if="error" class="emptycontent">
  26. <div class="icon icon-error" />
  27. <h2>{{ error }}</h2>
  28. </div>
  29. <!-- shares content -->
  30. <template v-else>
  31. <!-- shared with me information -->
  32. <SharingEntrySimple v-if="isSharedWithMe" v-bind="sharedWithMe" class="sharing-entry__reshare">
  33. <template #avatar>
  34. <Avatar #avatar
  35. :user="sharedWithMe.user"
  36. :display-name="sharedWithMe.displayName"
  37. class="sharing-entry__avatar"
  38. tooltip-message="" />
  39. </template>
  40. </SharingEntrySimple>
  41. <!-- add new share input -->
  42. <SharingInput v-if="!loading"
  43. :can-reshare="canReshare"
  44. :file-info="fileInfo"
  45. :link-shares="linkShares"
  46. :reshare="reshare"
  47. :shares="shares"
  48. @add:share="addShare" />
  49. <!-- link shares list -->
  50. <SharingLinkList v-if="!loading"
  51. :can-reshare="canReshare"
  52. :file-info="fileInfo"
  53. :shares="linkShares" />
  54. <!-- other shares list -->
  55. <SharingList v-if="!loading"
  56. :shares="shares"
  57. :file-info="fileInfo" />
  58. <!-- internal link copy -->
  59. <SharingEntryInternal :file-info="fileInfo" />
  60. </template>
  61. </Tab>
  62. </template>
  63. <script>
  64. import { generateOcsUrl } from '@nextcloud/router'
  65. import Tab from 'nextcloud-vue/dist/Components/AppSidebarTab'
  66. import Avatar from 'nextcloud-vue/dist/Components/Avatar'
  67. import axios from '@nextcloud/axios'
  68. import { shareWithTitle } from '../utils/SharedWithMe'
  69. import Share from '../models/Share'
  70. import ShareTypes from '../mixins/ShareTypes'
  71. import SharingEntryInternal from '../components/SharingEntryInternal'
  72. import SharingEntrySimple from '../components/SharingEntrySimple'
  73. import SharingInput from '../components/SharingInput'
  74. import SharingLinkList from './SharingLinkList'
  75. import SharingList from './SharingList'
  76. export default {
  77. name: 'SharingTab',
  78. components: {
  79. Avatar,
  80. SharingEntryInternal,
  81. SharingEntrySimple,
  82. SharingInput,
  83. SharingLinkList,
  84. SharingList,
  85. Tab
  86. },
  87. mixins: [ShareTypes],
  88. props: {
  89. fileInfo: {
  90. type: Object,
  91. default: () => {},
  92. required: true
  93. }
  94. },
  95. data() {
  96. return {
  97. error: '',
  98. expirationInterval: null,
  99. icon: 'icon-share',
  100. loading: true,
  101. name: t('files_sharing', 'Sharing'),
  102. // reshare Share object
  103. reshare: null,
  104. sharedWithMe: {},
  105. shares: [],
  106. linkShares: [],
  107. sections: OCA.Sharing.ShareTabSections.getSections()
  108. }
  109. },
  110. computed: {
  111. /**
  112. * Needed to differenciate the tabs
  113. * pulled from the AppSidebarTab component
  114. *
  115. * @returns {string}
  116. */
  117. id() {
  118. return this.name.toLowerCase().replace(/ /g, '-')
  119. },
  120. /**
  121. * Returns the current active tab
  122. * needed because AppSidebarTab also uses $parent.activeTab
  123. *
  124. * @returns {string}
  125. */
  126. activeTab() {
  127. return this.$parent.activeTab
  128. },
  129. /**
  130. * Is this share shared with me?
  131. *
  132. * @returns {boolean}
  133. */
  134. isSharedWithMe() {
  135. return Object.keys(this.sharedWithMe).length > 0
  136. },
  137. canReshare() {
  138. return !!(this.fileInfo.permissions & OC.PERMISSION_SHARE)
  139. || !!(this.reshare && this.reshare.hasSharePermission)
  140. }
  141. },
  142. watch: {
  143. fileInfo() {
  144. this.resetState()
  145. this.getShares()
  146. }
  147. },
  148. beforeMount() {
  149. this.getShares()
  150. },
  151. methods: {
  152. /**
  153. * Get the existing shares infos
  154. */
  155. async getShares() {
  156. try {
  157. this.loading = true
  158. // init params
  159. const shareUrl = generateOcsUrl('apps/files_sharing/api/v1', 2) + 'shares'
  160. const format = 'json'
  161. // TODO: replace with proper getFUllpath implementation of our own FileInfo model
  162. const path = (this.fileInfo.path + '/' + this.fileInfo.name).replace('//', '/')
  163. // fetch shares
  164. const fetchShares = axios.get(shareUrl, {
  165. params: {
  166. format,
  167. path,
  168. reshares: true
  169. }
  170. })
  171. const fetchSharedWithMe = axios.get(shareUrl, {
  172. params: {
  173. format,
  174. path,
  175. shared_with_me: true
  176. }
  177. })
  178. // wait for data
  179. const [shares, sharedWithMe] = await Promise.all([fetchShares, fetchSharedWithMe])
  180. this.loading = false
  181. // process results
  182. this.processSharedWithMe(sharedWithMe)
  183. this.processShares(shares)
  184. } catch (error) {
  185. this.error = t('files_sharing', 'Unable to load the shares list')
  186. this.loading = false
  187. console.error('Error loading the shares list', error)
  188. }
  189. },
  190. /**
  191. * Reset the current view to its default state
  192. */
  193. resetState() {
  194. clearInterval(this.expirationInterval)
  195. this.loading = true
  196. this.error = ''
  197. this.sharedWithMe = {}
  198. this.shares = []
  199. },
  200. /**
  201. * Update sharedWithMe.subtitle with the appropriate
  202. * expiration time left
  203. *
  204. * @param {Share} share the sharedWith Share object
  205. */
  206. updateExpirationSubtitle(share) {
  207. const expiration = moment(share.expireDate).unix()
  208. this.$set(this.sharedWithMe, 'subtitle', t('files_sharing', 'Expires {relativetime}', {
  209. relativetime: OC.Util.relativeModifiedDate(expiration * 1000)
  210. }))
  211. // share have expired
  212. if (moment().unix() > expiration) {
  213. clearInterval(this.expirationInterval)
  214. // TODO: clear ui if share is expired
  215. this.$set(this.sharedWithMe, 'subtitle', t('files_sharing', 'this share just expired.'))
  216. }
  217. },
  218. /**
  219. * Process the current shares data
  220. * and init shares[]
  221. *
  222. * @param {Object} share the share ocs api request data
  223. * @param {Object} share.data the request data
  224. */
  225. processShares({ data }) {
  226. if (data.ocs && data.ocs.data && data.ocs.data.length > 0) {
  227. // create Share objects and sort by newest
  228. const shares = data.ocs.data
  229. .map(share => new Share(share))
  230. .sort((a, b) => b.createdTime - a.createdTime)
  231. this.linkShares = shares.filter(share => share.type === this.SHARE_TYPES.SHARE_TYPE_LINK || share.type === this.SHARE_TYPES.SHARE_TYPE_EMAIL)
  232. this.shares = shares.filter(share => share.type !== this.SHARE_TYPES.SHARE_TYPE_LINK && share.type !== this.SHARE_TYPES.SHARE_TYPE_EMAIL)
  233. }
  234. },
  235. /**
  236. * Process the sharedWithMe share data
  237. * and init sharedWithMe
  238. *
  239. * @param {Object} share the share ocs api request data
  240. * @param {Object} share.data the request data
  241. */
  242. processSharedWithMe({ data }) {
  243. if (data.ocs && data.ocs.data && data.ocs.data[0]) {
  244. const share = new Share(data)
  245. const title = shareWithTitle(share)
  246. const displayName = share.ownerDisplayName
  247. const user = share.owner
  248. this.sharedWithMe = {
  249. displayName,
  250. title,
  251. user
  252. }
  253. this.reshare = share
  254. // If we have an expiration date, use it as subtitle
  255. // Refresh the status every 10s and clear if expired
  256. if (share.expireDate && moment(share.expireDate).unix() > moment().unix()) {
  257. // first update
  258. this.updateExpirationSubtitle(share)
  259. // interval update
  260. this.expirationInterval = setInterval(this.updateExpirationSubtitle, 10000, share)
  261. }
  262. }
  263. },
  264. /**
  265. * Insert share at top of arrays
  266. *
  267. * @param {Share} share the share to insert
  268. */
  269. addShare(share) {
  270. // only catching share type MAIL as link shares are added differently
  271. // meaning: not from the ShareInput
  272. if (share.type === this.SHARE_TYPES.SHARE_TYPE_EMAIL) {
  273. this.linkShares.unshift(share)
  274. } else {
  275. this.shares.unshift(share)
  276. }
  277. }
  278. }
  279. }
  280. </script>
  281. <style lang="scss" scoped>
  282. </style>