parent
8761e039d0
commit
3978aaef84
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 34 KiB |
@ -0,0 +1,55 @@
|
||||
import Attachment from '../attachment/attachment.vue'
|
||||
import { chunk, last, dropRight } from 'lodash'
|
||||
|
||||
const Gallery = {
|
||||
data: () => ({
|
||||
width: 500
|
||||
}),
|
||||
props: [
|
||||
'attachments',
|
||||
'nsfw',
|
||||
'setMedia'
|
||||
],
|
||||
components: { Attachment },
|
||||
mounted () {
|
||||
this.resize()
|
||||
window.addEventListener('resize', this.resize)
|
||||
},
|
||||
destroyed () {
|
||||
window.removeEventListener('resize', this.resize)
|
||||
},
|
||||
computed: {
|
||||
rows () {
|
||||
if (!this.attachments) {
|
||||
return []
|
||||
}
|
||||
const rows = chunk(this.attachments, 3)
|
||||
if (last(rows).length === 1 && rows.length > 1) {
|
||||
// if 1 attachment on last row -> add it to the previous row instead
|
||||
const lastAttachment = last(rows)[0]
|
||||
const allButLastRow = dropRight(rows)
|
||||
last(allButLastRow).push(lastAttachment)
|
||||
return allButLastRow
|
||||
}
|
||||
return rows
|
||||
},
|
||||
rowHeight () {
|
||||
return itemsPerRow => ({ 'height': `${(this.width / (itemsPerRow + 0.6))}px` })
|
||||
},
|
||||
useContainFit () {
|
||||
return this.$store.state.config.useContainFit
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
resize () {
|
||||
// Quick optimization to make resizing not always trigger state change,
|
||||
// only update attachment size in 10px steps
|
||||
const width = Math.floor(this.$el.getBoundingClientRect().width / 10) * 10
|
||||
if (this.width !== width) {
|
||||
this.width = width
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Gallery
|
@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<div ref="galleryContainer" style="width: 100%;">
|
||||
<div class="gallery-row" v-for="row in rows" :style="rowHeight(row.length)" :class="{ 'contain-fit': useContainFit, 'cover-fit': !useContainFit }">
|
||||
<attachment
|
||||
v-for="attachment in row"
|
||||
:setMedia="setMedia"
|
||||
:nsfw="nsfw"
|
||||
:attachment="attachment"
|
||||
:allowPlay="false"
|
||||
:key="attachment.id"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src='./gallery.js'></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
|
||||
.gallery-row {
|
||||
height: 200px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
align-content: stretch;
|
||||
flex-grow: 1;
|
||||
margin-top: 0.5em;
|
||||
|
||||
.attachments, .attachment {
|
||||
margin: 0 0.5em 0 0;
|
||||
flex-grow: 1;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.image-attachment {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.video-container {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&.contain-fit {
|
||||
img, video {
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
&.cover-fit {
|
||||
img, video {
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
@ -0,0 +1,31 @@
|
||||
|
||||
const VideoAttachment = {
|
||||
props: ['attachment', 'controls'],
|
||||
data () {
|
||||
return {
|
||||
loopVideo: this.$store.state.config.loopVideo
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onVideoDataLoad (e) {
|
||||
const target = e.srcElement || e.target
|
||||
if (typeof target.webkitAudioDecodedByteCount !== 'undefined') {
|
||||
// non-zero if video has audio track
|
||||
if (target.webkitAudioDecodedByteCount > 0) {
|
||||
this.loopVideo = this.loopVideo && !this.$store.state.config.loopVideoSilentOnly
|
||||
}
|
||||
} else if (typeof target.mozHasAudio !== 'undefined') {
|
||||
// true if video has audio track
|
||||
if (target.mozHasAudio) {
|
||||
this.loopVideo = this.loopVideo && !this.$store.state.config.loopVideoSilentOnly
|
||||
}
|
||||
} else if (typeof target.audioTracks !== 'undefined') {
|
||||
if (target.audioTracks.length > 0) {
|
||||
this.loopVideo = this.loopVideo && !this.$store.state.config.loopVideoSilentOnly
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default VideoAttachment
|
@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<video class="video"
|
||||
@loadeddata="onVideoDataLoad"
|
||||
:src="attachment.url"
|
||||
:loop="loopVideo"
|
||||
:controls="controls"
|
||||
playsinline
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script src="./video_attachment.js"></script>
|
@ -1,27 +1,32 @@
|
||||
const fileType = (typeString) => {
|
||||
let type = 'unknown'
|
||||
|
||||
if (typeString.match(/text\/html/)) {
|
||||
type = 'html'
|
||||
// TODO this func might as well take the entire file and use its mimetype
|
||||
// or the entire service could be just mimetype service that only operates
|
||||
// on mimetypes and not files. Currently the naming is confusing.
|
||||
const fileType = mimetype => {
|
||||
if (mimetype.match(/text\/html/)) {
|
||||
return 'html'
|
||||
}
|
||||
|
||||
if (typeString.match(/image/)) {
|
||||
type = 'image'
|
||||
if (mimetype.match(/image/)) {
|
||||
return 'image'
|
||||
}
|
||||
|
||||
if (typeString.match(/video/)) {
|
||||
type = 'video'
|
||||
if (mimetype.match(/video/)) {
|
||||
return 'video'
|
||||
}
|
||||
|
||||
if (typeString.match(/audio/)) {
|
||||
type = 'audio'
|
||||
if (mimetype.match(/audio/)) {
|
||||
return 'audio'
|
||||
}
|
||||
|
||||
return type
|
||||
return 'unknown'
|
||||
}
|
||||
|
||||
const fileMatchesSomeType = (types, file) =>
|
||||
types.some(type => fileType(file.mimetype) === type)
|
||||
|
||||
const fileTypeService = {
|
||||
fileType
|
||||
fileType,
|
||||
fileMatchesSomeType
|
||||
}
|
||||
|
||||
export default fileTypeService
|
||||
|
@ -0,0 +1,19 @@
|
||||
import fileType from 'src/services/file_type/file_type.service.js'
|
||||
|
||||
describe('fileType service', () => {
|
||||
describe('fileMatchesSomeType', () => {
|
||||
it('should be true when file type is one of the listed', () => {
|
||||
const file = { mimetype: 'audio/mpeg' }
|
||||
const types = ['video', 'audio']
|
||||
|
||||
expect(fileType.fileMatchesSomeType(types, file)).to.eql(true)
|
||||
})
|
||||
|
||||
it('should be false when files type is not included in type list', () => {
|
||||
const file = { mimetype: 'audio/mpeg' }
|
||||
const types = ['image', 'video']
|
||||
|
||||
expect(fileType.fileMatchesSomeType(types, file)).to.eql(false)
|
||||
})
|
||||
})
|
||||
})
|
Loading…
Reference in new issue