mirror of https://github.com/nextcloud/photos
277 lines
5.9 KiB
Vue
277 lines
5.9 KiB
Vue
<!--
|
|
- @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/>.
|
|
-
|
|
-->
|
|
|
|
<template>
|
|
<router-link :class="{'folder--clear': isEmpty}"
|
|
class="folder"
|
|
:to="to"
|
|
:aria-label="ariaLabel">
|
|
<transition name="fade">
|
|
<div v-show="loaded"
|
|
:class="`folder-content--grid-${fileList.length}`"
|
|
class="folder-content"
|
|
role="none">
|
|
<img v-for="file in fileList"
|
|
:key="file.id"
|
|
:src="generateImgSrc(file)"
|
|
alt=""
|
|
@load="loaded = true">
|
|
</div>
|
|
</transition>
|
|
<div
|
|
class="folder-name">
|
|
<span :class="[!isEmpty ? 'icon-white' : 'icon-dark', icon]"
|
|
class="folder-name__icon"
|
|
role="img" />
|
|
<p :id="ariaUuid" class="folder-name__name">
|
|
{{ basename }}
|
|
</p>
|
|
</div>
|
|
<div class="cover" role="none" />
|
|
</router-link>
|
|
</template>
|
|
|
|
<script>
|
|
import { generateUrl } from '@nextcloud/router'
|
|
import { mapGetters } from 'vuex'
|
|
|
|
import getPictures from '../services/FileList'
|
|
import cancelableRequest from '../utils/CancelableRequest'
|
|
|
|
export default {
|
|
name: 'Folder',
|
|
inheritAttrs: false,
|
|
|
|
props: {
|
|
basename: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
filename: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
id: {
|
|
type: Number,
|
|
required: true,
|
|
},
|
|
icon: {
|
|
type: String,
|
|
default: 'icon-folder',
|
|
},
|
|
},
|
|
|
|
data() {
|
|
return {
|
|
loaded: false,
|
|
cancelRequest: () => {},
|
|
}
|
|
},
|
|
|
|
computed: {
|
|
// global lists
|
|
...mapGetters([
|
|
'files',
|
|
'folders',
|
|
]),
|
|
|
|
// files list of the current folder
|
|
folderContent() {
|
|
return this.folders[this.id]
|
|
},
|
|
fileList() {
|
|
return this.folderContent
|
|
? this.folderContent
|
|
.slice(0, 4) // only get the 4 first images
|
|
.map(id => this.files[id])
|
|
.filter(file => !!file)
|
|
: []
|
|
},
|
|
|
|
// folder is empty
|
|
isEmpty() {
|
|
return this.fileList.length === 0
|
|
},
|
|
|
|
ariaUuid() {
|
|
return `folder-${this.id}`
|
|
},
|
|
ariaLabel() {
|
|
return t('photos', 'Open the "{name}" sub-directory', { name: this.basename })
|
|
},
|
|
|
|
/**
|
|
* We do not want encoded slashes when browsing by folder
|
|
* so we generate a new valid route object, get the final url back
|
|
* decode it and use it as a direct string, which vue-router
|
|
* does not encode afterwards
|
|
* @returns {string}
|
|
*/
|
|
to() {
|
|
const route = Object.assign({}, this.$route, {
|
|
// always remove first slash
|
|
params: { path: this.filename.substr(1) },
|
|
})
|
|
return decodeURIComponent(this.$router.resolve(route).resolved.path)
|
|
},
|
|
},
|
|
|
|
async created() {
|
|
// init cancellable request
|
|
const { request, cancel } = cancelableRequest(getPictures)
|
|
this.cancelRequest = cancel
|
|
|
|
try {
|
|
// get data
|
|
const { files, folders } = await request(this.filename)
|
|
// this.cancelRequest('Stop!')
|
|
this.$store.dispatch('updateFolders', { id: this.id, files, folders })
|
|
this.$store.dispatch('updateFiles', { folder: this.folder, files, folders })
|
|
} catch (error) {
|
|
if (error.response && error.response.status) {
|
|
console.error('Failed to get folder content', this.folder, error.response)
|
|
}
|
|
// else we just cancelled the request
|
|
}
|
|
},
|
|
|
|
beforeDestroy() {
|
|
this.cancelRequest()
|
|
},
|
|
|
|
methods: {
|
|
generateImgSrc({ id, etag }) {
|
|
// use etag to force cache reload if file changed
|
|
return generateUrl(`/core/preview?fileId=${id}&x=${256}&y=${256}&a=true&v=${etag}`)
|
|
},
|
|
|
|
fetch() {
|
|
},
|
|
},
|
|
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
@import '../mixins/FileFolder.scss';
|
|
|
|
.folder-content {
|
|
position: absolute;
|
|
display: grid;
|
|
width: 100%;
|
|
height: 100%;
|
|
// folder layout if less than 4 pictures
|
|
&--grid-1 {
|
|
grid-template-columns: 1fr;
|
|
grid-template-rows: 1fr;
|
|
}
|
|
&--grid-2 {
|
|
grid-template-columns: 1fr;
|
|
grid-template-rows: 1fr 1fr;
|
|
}
|
|
&--grid-3 {
|
|
grid-template-columns: 1fr 1fr;
|
|
grid-template-rows: 1fr 1fr;
|
|
img:first-child {
|
|
grid-column: span 2;
|
|
}
|
|
}
|
|
&--grid-4 {
|
|
grid-template-columns: 1fr 1fr;
|
|
grid-template-rows: 1fr 1fr;
|
|
}
|
|
img {
|
|
width: 100%;
|
|
height: 100%;
|
|
|
|
object-fit: cover;
|
|
}
|
|
}
|
|
|
|
$name-height: 1.2rem;
|
|
|
|
.folder-name {
|
|
position: absolute;
|
|
z-index: 3;
|
|
display: flex;
|
|
overflow: hidden;
|
|
flex-direction: column;
|
|
width: 100%;
|
|
height: 100%;
|
|
transition: opacity var(--animation-quick) ease-in-out;
|
|
opacity: 1;
|
|
&__icon {
|
|
height: 40%;
|
|
margin-top: calc(30% - #{$name-height} / 2); // center name+icon
|
|
background-size: 40%;
|
|
}
|
|
&__name {
|
|
overflow: hidden;
|
|
height: $name-height;
|
|
padding: 0 10px;
|
|
text-align: center;
|
|
white-space: nowrap;
|
|
text-overflow: ellipsis;
|
|
color: var(--color-main-background);
|
|
text-shadow: 0 0 8px var(--color-main-text);
|
|
font-size: $name-height;
|
|
line-height: $name-height;
|
|
}
|
|
}
|
|
|
|
// Cover management empty/full
|
|
.folder {
|
|
// if no img, let's display the folder icon as default black
|
|
&--clear {
|
|
.folder-name__icon {
|
|
opacity: .3;
|
|
}
|
|
.folder-name__name {
|
|
color: var(--color-main-text);
|
|
text-shadow: 0 0 8px var(--color-main-background);
|
|
}
|
|
}
|
|
|
|
// show the cover as background
|
|
// if there are pictures in it
|
|
// so we can sho the folder+name above it
|
|
&:not(.folder--clear) {
|
|
.cover {
|
|
opacity: .3;
|
|
}
|
|
|
|
// hide everything but pictures
|
|
// on hover/active/focus
|
|
&.active,
|
|
&:active,
|
|
&:hover,
|
|
&:focus {
|
|
.folder-name,
|
|
.cover {
|
|
opacity: 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
</style>
|