Version.vue 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. <!--
  2. - @copyright 2022 Carl Schwan <carl@carlschwan.eu>
  3. - @license AGPL-3.0-or-later
  4. -
  5. - This program is free software: you can redistribute it and/or modify
  6. - it under the terms of the GNU Affero General Public License as
  7. - published by the Free Software Foundation, either version 3 of the
  8. - License, or (at your option) any later version.
  9. -
  10. - This program is distributed in the hope that it will be useful,
  11. - but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. - GNU Affero General Public License for more details.
  14. -
  15. - You should have received a copy of the GNU Affero General Public License
  16. - along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. -->
  18. <template>
  19. <div>
  20. <NcListItem class="version"
  21. :name="versionLabel"
  22. :href="downloadURL"
  23. :force-display-actions="true"
  24. data-files-versions-version>
  25. <template #icon>
  26. <div v-if="!(loadPreview || previewLoaded)" class="version__image" />
  27. <img v-else-if="isCurrent || version.hasPreview"
  28. :src="previewURL"
  29. alt=""
  30. decoding="async"
  31. fetchpriority="low"
  32. loading="lazy"
  33. class="version__image"
  34. @load="previewLoaded = true">
  35. <div v-else
  36. class="version__image">
  37. <ImageOffOutline :size="20" />
  38. </div>
  39. </template>
  40. <template #subname>
  41. <div class="version__info">
  42. <span :title="formattedDate">{{ version.mtime | humanDateFromNow }}</span>
  43. <!-- Separate dot to improve alignement -->
  44. <span class="version__info__size">•</span>
  45. <span class="version__info__size">{{ version.size | humanReadableSize }}</span>
  46. </div>
  47. </template>
  48. <template #actions>
  49. <NcActionButton v-if="enableLabeling"
  50. :close-after-click="true"
  51. @click="openVersionLabelModal">
  52. <template #icon>
  53. <Pencil :size="22" />
  54. </template>
  55. {{ version.label === '' ? t('files_versions', 'Name this version') : t('files_versions', 'Edit version name') }}
  56. </NcActionButton>
  57. <NcActionButton v-if="!isCurrent"
  58. :close-after-click="true"
  59. @click="restoreVersion">
  60. <template #icon>
  61. <BackupRestore :size="22" />
  62. </template>
  63. {{ t('files_versions', 'Restore version') }}
  64. </NcActionButton>
  65. <NcActionLink :href="downloadURL"
  66. :close-after-click="true"
  67. :download="downloadURL">
  68. <template #icon>
  69. <Download :size="22" />
  70. </template>
  71. {{ t('files_versions', 'Download version') }}
  72. </NcActionLink>
  73. <NcActionButton v-if="!isCurrent && enableDeletion"
  74. :close-after-click="true"
  75. @click="deleteVersion">
  76. <template #icon>
  77. <Delete :size="22" />
  78. </template>
  79. {{ t('files_versions', 'Delete version') }}
  80. </NcActionButton>
  81. </template>
  82. </NcListItem>
  83. <NcModal v-if="showVersionLabelForm"
  84. :title="t('files_versions', 'Name this version')"
  85. @close="showVersionLabelForm = false">
  86. <form class="version-label-modal"
  87. @submit.prevent="setVersionLabel(formVersionLabelValue)">
  88. <label>
  89. <div class="version-label-modal__title">{{ t('photos', 'Version name') }}</div>
  90. <NcTextField ref="labelInput"
  91. :value.sync="formVersionLabelValue"
  92. :placeholder="t('photos', 'Version name')"
  93. :label-outside="true" />
  94. </label>
  95. <div class="version-label-modal__info">
  96. {{ t('photos', 'Named versions are persisted, and excluded from automatic cleanups when your storage quota is full.') }}
  97. </div>
  98. <div class="version-label-modal__actions">
  99. <NcButton :disabled="formVersionLabelValue.trim().length === 0" @click="setVersionLabel('')">
  100. {{ t('files_versions', 'Remove version name') }}
  101. </NcButton>
  102. <NcButton type="primary" native-type="submit">
  103. <template #icon>
  104. <Check />
  105. </template>
  106. {{ t('files_versions', 'Save version name') }}
  107. </NcButton>
  108. </div>
  109. </form>
  110. </NcModal>
  111. </div>
  112. </template>
  113. <script>
  114. import BackupRestore from 'vue-material-design-icons/BackupRestore.vue'
  115. import Download from 'vue-material-design-icons/Download.vue'
  116. import Pencil from 'vue-material-design-icons/Pencil.vue'
  117. import Check from 'vue-material-design-icons/Check.vue'
  118. import Delete from 'vue-material-design-icons/Delete.vue'
  119. import ImageOffOutline from 'vue-material-design-icons/ImageOffOutline.vue'
  120. import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
  121. import NcActionLink from '@nextcloud/vue/dist/Components/NcActionLink.js'
  122. import NcListItem from '@nextcloud/vue/dist/Components/NcListItem.js'
  123. import NcModal from '@nextcloud/vue/dist/Components/NcModal.js'
  124. import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
  125. import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
  126. import Tooltip from '@nextcloud/vue/dist/Directives/Tooltip.js'
  127. import moment from '@nextcloud/moment'
  128. import { translate } from '@nextcloud/l10n'
  129. import { joinPaths } from '@nextcloud/paths'
  130. import { generateUrl, getRootUrl } from '@nextcloud/router'
  131. import { loadState } from '@nextcloud/initial-state'
  132. export default {
  133. name: 'Version',
  134. components: {
  135. NcActionLink,
  136. NcActionButton,
  137. NcListItem,
  138. NcModal,
  139. NcButton,
  140. NcTextField,
  141. BackupRestore,
  142. Download,
  143. Pencil,
  144. Check,
  145. Delete,
  146. ImageOffOutline,
  147. },
  148. directives: {
  149. tooltip: Tooltip,
  150. },
  151. filters: {
  152. /**
  153. * @param {number} bytes
  154. * @return {string}
  155. */
  156. humanReadableSize(bytes) {
  157. return OC.Util.humanFileSize(bytes)
  158. },
  159. /**
  160. * @param {number} timestamp
  161. * @return {string}
  162. */
  163. humanDateFromNow(timestamp) {
  164. return moment(timestamp).fromNow()
  165. },
  166. },
  167. props: {
  168. /** @type {Vue.PropOptions<import('../utils/versions.js').Version>} */
  169. version: {
  170. type: Object,
  171. required: true,
  172. },
  173. fileInfo: {
  174. type: Object,
  175. required: true,
  176. },
  177. isCurrent: {
  178. type: Boolean,
  179. default: false,
  180. },
  181. isFirstVersion: {
  182. type: Boolean,
  183. default: false,
  184. },
  185. loadPreview: {
  186. type: Boolean,
  187. default: false,
  188. },
  189. },
  190. data() {
  191. return {
  192. previewLoaded: false,
  193. showVersionLabelForm: false,
  194. formVersionLabelValue: this.version.label,
  195. capabilities: loadState('core', 'capabilities', { files: { version_labeling: false, version_deletion: false } }),
  196. }
  197. },
  198. computed: {
  199. /**
  200. * @return {string}
  201. */
  202. versionLabel() {
  203. const label = this.version.label ?? ''
  204. if (this.isCurrent) {
  205. if (label === '') {
  206. return translate('files_versions', 'Current version')
  207. } else {
  208. return `${label} (${translate('files_versions', 'Current version')})`
  209. }
  210. }
  211. if (this.isFirstVersion && label === '') {
  212. return translate('files_versions', 'Initial version')
  213. }
  214. return label
  215. },
  216. /**
  217. * @return {string}
  218. */
  219. downloadURL() {
  220. if (this.isCurrent) {
  221. return getRootUrl() + joinPaths('/remote.php/webdav', this.fileInfo.path, this.fileInfo.name)
  222. } else {
  223. return getRootUrl() + this.version.url
  224. }
  225. },
  226. /**
  227. * @return {string}
  228. */
  229. previewURL() {
  230. if (this.isCurrent) {
  231. return generateUrl('/core/preview?fileId={fileId}&c={fileEtag}&x=250&y=250&forceIcon=0&a=0', {
  232. fileId: this.fileInfo.id,
  233. fileEtag: this.fileInfo.etag,
  234. })
  235. } else {
  236. return this.version.preview
  237. }
  238. },
  239. /** @return {string} */
  240. formattedDate() {
  241. return moment(this.version.mtime).format('LLL')
  242. },
  243. /** @return {boolean} */
  244. enableLabeling() {
  245. return this.capabilities.files.version_labeling === true && this.fileInfo.mountType !== 'group'
  246. },
  247. /** @return {boolean} */
  248. enableDeletion() {
  249. return this.capabilities.files.version_deletion === true && this.fileInfo.mountType !== 'group'
  250. }
  251. },
  252. methods: {
  253. openVersionLabelModal() {
  254. this.showVersionLabelForm = true
  255. this.$nextTick(() => {
  256. this.$refs.labelInput.$el.getElementsByTagName('input')[0].focus()
  257. })
  258. },
  259. restoreVersion() {
  260. this.$emit('restore', this.version)
  261. },
  262. setVersionLabel(label) {
  263. this.formVersionLabelValue = label
  264. this.showVersionLabelForm = false
  265. this.$emit('label-update', this.version, label)
  266. },
  267. deleteVersion() {
  268. this.$emit('delete', this.version)
  269. },
  270. },
  271. }
  272. </script>
  273. <style scoped lang="scss">
  274. .version {
  275. display: flex;
  276. flex-direction: row;
  277. &__info {
  278. display: flex;
  279. flex-direction: row;
  280. align-items: center;
  281. gap: 0.5rem;
  282. &__size {
  283. color: var(--color-text-lighter);
  284. }
  285. }
  286. &__image {
  287. width: 3rem;
  288. height: 3rem;
  289. border: 1px solid var(--color-border);
  290. border-radius: var(--border-radius-large);
  291. // Useful to display no preview icon.
  292. display: flex;
  293. justify-content: center;
  294. color: var(--color-text-light);
  295. }
  296. }
  297. .version-label-modal {
  298. display: flex;
  299. justify-content: space-between;
  300. flex-direction: column;
  301. height: 250px;
  302. padding: 16px;
  303. &__title {
  304. margin-bottom: 12px;
  305. font-weight: 600;
  306. }
  307. &__info {
  308. margin-top: 12px;
  309. color: var(--color-text-maxcontrast);
  310. }
  311. &__actions {
  312. display: flex;
  313. justify-content: space-between;
  314. margin-top: 64px;
  315. }
  316. }
  317. </style>