123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341 |
- <!--
- - @copyright 2022 Carl Schwan <carl@carlschwan.eu>
- - @license AGPL-3.0-or-later
- -
- - 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>
- <NcListItem class="version"
- :name="versionLabel"
- :href="downloadURL"
- :force-display-actions="true"
- data-files-versions-version>
- <template #icon>
- <div v-if="!(loadPreview || previewLoaded)" class="version__image" />
- <img v-else-if="isCurrent || version.hasPreview"
- :src="previewURL"
- alt=""
- decoding="async"
- fetchpriority="low"
- loading="lazy"
- class="version__image"
- @load="previewLoaded = true">
- <div v-else
- class="version__image">
- <ImageOffOutline :size="20" />
- </div>
- </template>
- <template #subname>
- <div class="version__info">
- <span :title="formattedDate">{{ version.mtime | humanDateFromNow }}</span>
- <!-- Separate dot to improve alignement -->
- <span class="version__info__size">•</span>
- <span class="version__info__size">{{ version.size | humanReadableSize }}</span>
- </div>
- </template>
- <template #actions>
- <NcActionButton v-if="enableLabeling"
- :close-after-click="true"
- @click="openVersionLabelModal">
- <template #icon>
- <Pencil :size="22" />
- </template>
- {{ version.label === '' ? t('files_versions', 'Name this version') : t('files_versions', 'Edit version name') }}
- </NcActionButton>
- <NcActionButton v-if="!isCurrent"
- :close-after-click="true"
- @click="restoreVersion">
- <template #icon>
- <BackupRestore :size="22" />
- </template>
- {{ t('files_versions', 'Restore version') }}
- </NcActionButton>
- <NcActionLink :href="downloadURL"
- :close-after-click="true"
- :download="downloadURL">
- <template #icon>
- <Download :size="22" />
- </template>
- {{ t('files_versions', 'Download version') }}
- </NcActionLink>
- <NcActionButton v-if="!isCurrent && enableDeletion"
- :close-after-click="true"
- @click="deleteVersion">
- <template #icon>
- <Delete :size="22" />
- </template>
- {{ t('files_versions', 'Delete version') }}
- </NcActionButton>
- </template>
- </NcListItem>
- <NcModal v-if="showVersionLabelForm"
- :title="t('files_versions', 'Name this version')"
- @close="showVersionLabelForm = false">
- <form class="version-label-modal"
- @submit.prevent="setVersionLabel(formVersionLabelValue)">
- <label>
- <div class="version-label-modal__title">{{ t('photos', 'Version name') }}</div>
- <NcTextField ref="labelInput"
- :value.sync="formVersionLabelValue"
- :placeholder="t('photos', 'Version name')"
- :label-outside="true" />
- </label>
- <div class="version-label-modal__info">
- {{ t('photos', 'Named versions are persisted, and excluded from automatic cleanups when your storage quota is full.') }}
- </div>
- <div class="version-label-modal__actions">
- <NcButton :disabled="formVersionLabelValue.trim().length === 0" @click="setVersionLabel('')">
- {{ t('files_versions', 'Remove version name') }}
- </NcButton>
- <NcButton type="primary" native-type="submit">
- <template #icon>
- <Check />
- </template>
- {{ t('files_versions', 'Save version name') }}
- </NcButton>
- </div>
- </form>
- </NcModal>
- </div>
- </template>
- <script>
- import BackupRestore from 'vue-material-design-icons/BackupRestore.vue'
- import Download from 'vue-material-design-icons/Download.vue'
- import Pencil from 'vue-material-design-icons/Pencil.vue'
- import Check from 'vue-material-design-icons/Check.vue'
- import Delete from 'vue-material-design-icons/Delete.vue'
- import ImageOffOutline from 'vue-material-design-icons/ImageOffOutline.vue'
- import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
- import NcActionLink from '@nextcloud/vue/dist/Components/NcActionLink.js'
- import NcListItem from '@nextcloud/vue/dist/Components/NcListItem.js'
- import NcModal from '@nextcloud/vue/dist/Components/NcModal.js'
- import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
- import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
- import Tooltip from '@nextcloud/vue/dist/Directives/Tooltip.js'
- import moment from '@nextcloud/moment'
- import { translate } from '@nextcloud/l10n'
- import { joinPaths } from '@nextcloud/paths'
- import { generateUrl, getRootUrl } from '@nextcloud/router'
- import { loadState } from '@nextcloud/initial-state'
- export default {
- name: 'Version',
- components: {
- NcActionLink,
- NcActionButton,
- NcListItem,
- NcModal,
- NcButton,
- NcTextField,
- BackupRestore,
- Download,
- Pencil,
- Check,
- Delete,
- ImageOffOutline,
- },
- directives: {
- tooltip: Tooltip,
- },
- filters: {
- /**
- * @param {number} bytes
- * @return {string}
- */
- humanReadableSize(bytes) {
- return OC.Util.humanFileSize(bytes)
- },
- /**
- * @param {number} timestamp
- * @return {string}
- */
- humanDateFromNow(timestamp) {
- return moment(timestamp).fromNow()
- },
- },
- props: {
- /** @type {Vue.PropOptions<import('../utils/versions.js').Version>} */
- version: {
- type: Object,
- required: true,
- },
- fileInfo: {
- type: Object,
- required: true,
- },
- isCurrent: {
- type: Boolean,
- default: false,
- },
- isFirstVersion: {
- type: Boolean,
- default: false,
- },
- loadPreview: {
- type: Boolean,
- default: false,
- },
- },
- data() {
- return {
- previewLoaded: false,
- showVersionLabelForm: false,
- formVersionLabelValue: this.version.label,
- capabilities: loadState('core', 'capabilities', { files: { version_labeling: false, version_deletion: false } }),
- }
- },
- computed: {
- /**
- * @return {string}
- */
- versionLabel() {
- const label = this.version.label ?? ''
- if (this.isCurrent) {
- if (label === '') {
- return translate('files_versions', 'Current version')
- } else {
- return `${label} (${translate('files_versions', 'Current version')})`
- }
- }
- if (this.isFirstVersion && label === '') {
- return translate('files_versions', 'Initial version')
- }
- return label
- },
- /**
- * @return {string}
- */
- downloadURL() {
- if (this.isCurrent) {
- return getRootUrl() + joinPaths('/remote.php/webdav', this.fileInfo.path, this.fileInfo.name)
- } else {
- return getRootUrl() + this.version.url
- }
- },
- /**
- * @return {string}
- */
- previewURL() {
- if (this.isCurrent) {
- return generateUrl('/core/preview?fileId={fileId}&c={fileEtag}&x=250&y=250&forceIcon=0&a=0', {
- fileId: this.fileInfo.id,
- fileEtag: this.fileInfo.etag,
- })
- } else {
- return this.version.preview
- }
- },
- /** @return {string} */
- formattedDate() {
- return moment(this.version.mtime).format('LLL')
- },
- /** @return {boolean} */
- enableLabeling() {
- return this.capabilities.files.version_labeling === true && this.fileInfo.mountType !== 'group'
- },
- /** @return {boolean} */
- enableDeletion() {
- return this.capabilities.files.version_deletion === true && this.fileInfo.mountType !== 'group'
- }
- },
- methods: {
- openVersionLabelModal() {
- this.showVersionLabelForm = true
- this.$nextTick(() => {
- this.$refs.labelInput.$el.getElementsByTagName('input')[0].focus()
- })
- },
- restoreVersion() {
- this.$emit('restore', this.version)
- },
- setVersionLabel(label) {
- this.formVersionLabelValue = label
- this.showVersionLabelForm = false
- this.$emit('label-update', this.version, label)
- },
- deleteVersion() {
- this.$emit('delete', this.version)
- },
- },
- }
- </script>
- <style scoped lang="scss">
- .version {
- display: flex;
- flex-direction: row;
- &__info {
- display: flex;
- flex-direction: row;
- align-items: center;
- gap: 0.5rem;
- &__size {
- color: var(--color-text-lighter);
- }
- }
- &__image {
- width: 3rem;
- height: 3rem;
- border: 1px solid var(--color-border);
- border-radius: var(--border-radius-large);
- // Useful to display no preview icon.
- display: flex;
- justify-content: center;
- color: var(--color-text-light);
- }
- }
- .version-label-modal {
- display: flex;
- justify-content: space-between;
- flex-direction: column;
- height: 250px;
- padding: 16px;
- &__title {
- margin-bottom: 12px;
- font-weight: 600;
- }
- &__info {
- margin-top: 12px;
- color: var(--color-text-maxcontrast);
- }
- &__actions {
- display: flex;
- justify-content: space-between;
- margin-top: 64px;
- }
- }
- </style>
|