commit: e654fead23ebb457f81e8642c65e1f3e98ee0027
parent 9c4957268de656acdfceae526f18ab5250808bec
Author: Henry Jameson <me@hjkos.com>
Date: Thu, 17 Jun 2021 16:29:46 +0300
refactored attachments and gallery. All attachments now are in gallery.
Diffstat:
10 files changed, 296 insertions(+), 365 deletions(-)
diff --git a/src/components/attachment/attachment.js b/src/components/attachment/attachment.js
@@ -11,7 +11,9 @@ import {
faImage,
faVideo,
faPlayCircle,
- faTimes
+ faTimes,
+ faStop,
+ faSearchPlus
} from '@fortawesome/free-solid-svg-icons'
library.add(
@@ -20,7 +22,9 @@ library.add(
faImage,
faVideo,
faPlayCircle,
- faTimes
+ faTimes,
+ faStop,
+ faSearchPlus
)
const Attachment = {
@@ -28,7 +32,6 @@ const Attachment = {
'attachment',
'nsfw',
'size',
- 'allowPlay',
'setMedia',
'naturalSizeLoad'
],
@@ -40,7 +43,8 @@ const Attachment = {
loading: false,
img: fileTypeService.fileType(this.attachment.mimetype) === 'image' && document.createElement('img'),
modalOpen: false,
- showHidden: false
+ showHidden: false,
+ flashLoaded: false
}
},
components: {
@@ -49,9 +53,22 @@ const Attachment = {
VideoAttachment
},
computed: {
+ classNames () {
+ return [
+ {
+ '-loading': this.loading,
+ '-nsfw-placeholder': this.hidden
+ },
+ '-' + this.type,
+ `-${this.useContainFit ? 'contain' : 'cover'}-fit`
+ ]
+ },
usePlaceholder () {
return this.size === 'hide' || this.type === 'unknown'
},
+ useContainFit () {
+ return this.$store.getters.mergedConfig.useContainFit
+ },
placeholderName () {
if (this.attachment.description === '' || !this.attachment.description) {
return this.type.toUpperCase()
@@ -79,10 +96,6 @@ const Attachment = {
isSmall () {
return this.size === 'small'
},
- fullwidth () {
- if (this.size === 'hide') return false
- return this.type === 'html' || this.type === 'audio' || this.type === 'unknown'
- },
useModal () {
const modalTypes = this.size === 'hide' ? ['image', 'video', 'audio']
: this.mergedConfig.playVideosInModal
@@ -100,12 +113,20 @@ const Attachment = {
},
openModal (event) {
if (this.useModal) {
- event.stopPropagation()
- event.preventDefault()
this.setMedia()
this.$store.dispatch('setCurrent', this.attachment)
}
},
+ openModalForce (event) {
+ this.setMedia()
+ this.$store.dispatch('setCurrent', this.attachment)
+ },
+ stopFlash () {
+ this.$refs.flash.closePlayer()
+ },
+ setFlashLoaded (event) {
+ this.flashLoaded = event
+ },
toggleHidden (event) {
if (
(this.mergedConfig.useOneClickNsfw && !this.showHidden) &&
diff --git a/src/components/attachment/attachment.vue b/src/components/attachment/attachment.vue
@@ -1,7 +1,8 @@
<template>
- <div
+ <button
v-if="usePlaceholder"
- :class="{ 'fullwidth': fullwidth }"
+ class="Attachment -placeholder button-unstyled"
+ :class="classNames"
@click="openModal"
>
<a
@@ -15,16 +16,16 @@
<FAIcon :icon="placeholderIconClass" />
<b>{{ nsfw ? "NSFW / " : "" }}</b>{{ placeholderName }}
</a>
- </div>
+ </button>
<div
v-else
v-show="!isEmpty"
- class="attachment"
- :class="{[type]: true, loading, 'fullwidth': fullwidth, 'nsfw-placeholder': hidden}"
+ class="Attachment"
+ :class="classNames"
>
<a
v-if="hidden"
- class="image-attachment"
+ class="image-container"
:href="attachment.url"
:alt="attachment.description"
:title="attachment.description"
@@ -34,7 +35,6 @@
:key="nsfwImage"
class="nsfw"
:src="nsfwImage"
- :class="{'small': isSmall}"
>
<FAIcon
v-if="type === 'video'"
@@ -42,21 +42,40 @@
icon="play-circle"
/>
</a>
- <button
- v-if="nsfw && hideNsfwLocal && !hidden"
- class="button-unstyled hider"
- @click.prevent="toggleHidden"
- >
- <FAIcon icon="times" />
- </button>
+ <div
+ class="attachment-buttons"
+ v-if="!hidden"
+ >
+ <button
+ v-if="type === 'flash' && flashLoaded"
+ class="button-unstyled attachment-button"
+ @click.prevent="stopFlash"
+ >
+ <FAIcon icon="stop" />
+ </button>
+ <button
+ v-if="!useModal"
+ class="button-unstyled attachment-button"
+ @click.prevent="openModalForce"
+ >
+ <FAIcon icon="search-plus" />
+ </button>
+ <button
+ v-if="nsfw && hideNsfwLocal"
+ class="button-unstyled attachment-button"
+ @click.prevent="toggleHidden"
+ >
+ <FAIcon icon="times" />
+ </button>
+ </div>
<a
v-if="type === 'image' && (!hidden || preloadImage)"
- class="image-attachment"
- :class="{'hidden': hidden && preloadImage }"
+ class="image-container"
+ :class="{'-hidden': hidden && preloadImage }"
:href="attachment.url"
target="_blank"
- @click="openModal"
+ @click.stop.prevent="openModal"
>
<StillImage
class="image"
@@ -71,37 +90,43 @@
<a
v-if="type === 'video' && !hidden"
class="video-container"
- :class="{'small': isSmall}"
- :href="allowPlay ? undefined : attachment.url"
- @click="openModal"
+ :href="attachment.url"
+ @click.stop.prevent="openModal"
>
<VideoAttachment
class="video"
:attachment="attachment"
- :controls="allowPlay"
+ :controls="!useModal"
@play="$emit('play')"
@pause="$emit('pause')"
/>
<FAIcon
- v-if="!allowPlay"
+ v-if="useModal"
class="play-icon"
icon="play-circle"
/>
</a>
- <audio
- v-if="type === 'audio'"
- :src="attachment.url"
- :alt="attachment.description"
- :title="attachment.description"
- controls
- @play="$emit('play')"
- @pause="$emit('pause')"
- />
+ <a
+ v-if="type === 'audio' && !hidden"
+ class="audio-container"
+ :href="attachment.url"
+ @click.stop.prevent="openModal"
+ >
+ <audio
+ v-if="type === 'audio'"
+ :src="attachment.url"
+ :alt="attachment.description"
+ :title="attachment.description"
+ controls
+ @play="$emit('play')"
+ @pause="$emit('pause')"
+ />
+ </a>
<div
v-if="type === 'html' && attachment.oembed"
- class="oembed"
+ class="oembed-container"
@click.prevent="linkClicked"
>
<div
@@ -118,211 +143,23 @@
</div>
</div>
- <Flash
- v-if="type === 'flash'"
- :src="attachment.large_thumb_url || attachment.url"
- />
+ <a
+ v-if="type === 'flash' && !hidden"
+ class="flash-container"
+ :href="attachment.url"
+ @click.stop.prevent="openModal"
+ >
+ <Flash
+ class="flash"
+ ref="flash"
+ :src="attachment.large_thumb_url || attachment.url"
+ @playerOpened="setFlashLoaded(true)"
+ @playerClosed="setFlashLoaded(false)"
+ />
+ </a>
</div>
</template>
<script src="./attachment.js"></script>
-<style lang="scss">
-@import '../../_variables.scss';
-
-.attachments {
- display: flex;
- flex-wrap: wrap;
-
- .non-gallery {
- max-width: 100%;
- }
-
- .placeholder {
- display: inline-block;
- padding: 0.3em 1em 0.3em 0;
- color: $fallback--link;
- color: var(--postLink, $fallback--link);
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
- max-width: 100%;
-
- svg {
- color: inherit;
- }
- }
-
- .nsfw-placeholder {
- cursor: pointer;
-
- &.loading {
- cursor: progress;
- }
- }
-
- .attachment {
- position: relative;
- margin-top: 0.5em;
- align-self: flex-start;
- line-height: 0;
-
- border-style: solid;
- border-width: 1px;
- border-radius: $fallback--attachmentRadius;
- border-radius: var(--attachmentRadius, $fallback--attachmentRadius);
- border-color: $fallback--border;
- border-color: var(--border, $fallback--border);
- overflow: hidden;
- }
-
- .non-gallery.attachment {
- &.flash,
- &.video {
- flex: 1 0 40%;
- }
- .nsfw {
- height: 260px;
- }
- .small {
- height: 120px;
- flex-grow: 0;
- }
- .video {
- height: 260px;
- display: flex;
- }
- video {
- max-height: 100%;
- object-fit: contain;
- }
- }
-
- .fullwidth {
- flex-basis: 100%;
- }
- // fixes small gap below video
- &.video {
- line-height: 0;
- }
-
- .video-container {
- display: flex;
- max-height: 100%;
- }
-
- .video {
- width: 100%;
- height: 100%;
- }
-
- .play-icon {
- position: absolute;
- font-size: 64px;
- top: calc(50% - 32px);
- left: calc(50% - 32px);
- color: rgba(255, 255, 255, 0.75);
- text-shadow: 0 0 2px rgba(0, 0, 0, 0.4);
- }
-
- .play-icon::before {
- margin: 0;
- }
-
- &.html {
- flex-basis: 90%;
- width: 100%;
- display: flex;
- }
-
- .hider {
- position: absolute;
- right: 0;
- margin: 10px;
- padding: 0;
- z-index: 4;
- border-radius: $fallback--tooltipRadius;
- border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
- text-align: center;
- width: 2em;
- height: 2em;
- font-size: 1.25em;
- // TODO: theming? hard to theme with unknown background image color
- background: rgba(230, 230, 230, 0.7);
- .svg-inline--fa {
- color: rgba(0, 0, 0, 0.6);
- }
- &:hover .svg-inline--fa {
- color: rgba(0, 0, 0, 0.9);
- }
- }
-
- video {
- z-index: 0;
- }
-
- audio {
- width: 100%;
- }
-
- img.media-upload {
- line-height: 0;
- max-height: 200px;
- max-width: 100%;
- }
-
- .oembed {
- line-height: 1.2em;
- flex: 1 0 100%;
- width: 100%;
- margin-right: 15px;
- display: flex;
-
- img {
- width: 100%;
- }
-
- .image {
- flex: 1;
- img {
- border: 0px;
- border-radius: 5px;
- height: 100%;
- object-fit: cover;
- }
- }
-
- .text {
- flex: 2;
- margin: 8px;
- word-break: break-all;
- h1 {
- font-size: 14px;
- margin: 0px;
- }
- }
- }
-
- .image-attachment {
- &,
- & .image {
- width: 100%;
- height: 100%;
- }
-
- &.hidden {
- display: none;
- }
-
- .nsfw {
- object-fit: cover;
- width: 100%;
- height: 100%;
- }
-
- img {
- image-orientation: from-image; // NOTE: only FF supports this
- }
- }
-}
-</style>
+<style src="./attachment.scss" lang="scss"></style>
diff --git a/src/components/chat_message/chat_message.scss b/src/components/chat_message/chat_message.scss
@@ -62,10 +62,6 @@
&.with-media {
width: 100%;
- .gallery-row {
- overflow: hidden;
- }
-
.status {
width: 100%;
}
diff --git a/src/components/flash/flash.js b/src/components/flash/flash.js
@@ -39,12 +39,13 @@ const Flash = {
this.player = 'error'
})
this.ruffleInstance = player
+ this.$emit('playerOpened')
})
},
closePlayer () {
- console.log(this.ruffleInstance)
- this.ruffleInstance.remove()
+ this.ruffleInstance && this.ruffleInstance.remove()
this.player = false
+ this.$emit('playerClosed')
}
}
}
diff --git a/src/components/flash/flash.vue b/src/components/flash/flash.vue
@@ -36,13 +36,6 @@
</p>
</span>
</button>
- <button
- v-if="player"
- class="button-unstyled hider"
- @click="closePlayer"
- >
- <FAIcon icon="stop" />
- </button>
</div>
</template>
diff --git a/src/components/gallery/gallery.js b/src/components/gallery/gallery.js
@@ -1,15 +1,17 @@
import Attachment from '../attachment/attachment.vue'
-import { chunk, last, dropRight, sumBy } from 'lodash'
+import { sumBy } from 'lodash'
const Gallery = {
props: [
'attachments',
'nsfw',
- 'setMedia'
+ 'setMedia',
+ 'size'
],
data () {
return {
- sizes: {}
+ sizes: {},
+ hidingLong: true
}
},
components: { Attachment },
@@ -18,26 +20,54 @@ const Gallery = {
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
+ console.log(this.size)
+ if (this.size === 'hide') {
+ return this.attachments.map(item => ({ minimal: true, items: [item] }))
}
+ const rows = this.attachments.reduce((acc, attachment, i) => {
+ if (attachment.mimetype.includes('audio')) {
+ return [...acc, { audio: true, items: [attachment] }, { items: [] }]
+ }
+ const maxPerRow = 3
+ const attachmentsRemaining = this.attachments.length - i - 1
+ const currentRow = acc[acc.length - 1].items
+ if (
+ currentRow.length <= maxPerRow ||
+ attachmentsRemaining === 1
+ ) {
+ currentRow.push(attachment)
+ }
+ if (currentRow.length === maxPerRow && attachmentsRemaining > 1) {
+ return [...acc, { items: [] }]
+ } else {
+ return acc
+ }
+ }, [{ items: [] }]).filter(_ => _.items.length > 0)
return rows
},
- useContainFit () {
- return this.$store.getters.mergedConfig.useContainFit
+ attachmentsDimensionalScore () {
+ return this.rows.reduce((acc, row) => {
+ return acc + (row.audio ? 0.25 : (1 / (row.items.length + 0.6)))
+ }, 0)
+ },
+ tooManyAttachments () {
+ if (this.size === 'hide') {
+ return this.attachments.length > 8
+ } else {
+ return this.attachmentsDimensionalScore > 1
+ }
}
},
methods: {
onNaturalSizeLoad (id, size) {
this.$set(this.sizes, id, size)
},
- rowStyle (itemsPerRow) {
- return { 'padding-bottom': `${(100 / (itemsPerRow + 0.6))}%` }
+ rowStyle (row) {
+ if (row.audio) {
+ return { 'padding-bottom': '25%' } // fixed reduced height for audio
+ } else if (!row.minimal) {
+ return { 'padding-bottom': `${(100 / (row.items.length + 0.6))}%` }
+ }
},
itemStyle (id, row) {
const total = sumBy(row, item => this.getAspectRatio(item.id))
@@ -46,6 +76,13 @@ const Gallery = {
getAspectRatio (id) {
const size = this.sizes[id]
return size ? size.width / size.height : 1
+ },
+ toggleHidingLong (event) {
+ this.hidingLong = event
+ },
+ openGallery () {
+ this.setMedia()
+ this.$store.dispatch('setCurrent', this.attachments[0])
}
}
}
diff --git a/src/components/gallery/gallery.vue b/src/components/gallery/gallery.vue
@@ -1,26 +1,74 @@
<template>
<div
+ class="Gallery"
ref="galleryContainer"
- style="width: 100%;"
+ :class="{ '-long': tooManyAttachments && hidingLong }"
>
+ <div class="gallery-rows">
+ <div
+ v-for="(row, index) in rows"
+ :key="index"
+ class="gallery-row"
+ :style="rowStyle(row)"
+ :class="{ '-audio': row.audio, '-minimal': row.minimal }"
+ >
+ <div class="gallery-row-inner">
+ <attachment
+ v-for="attachment in row.items"
+ class="gallery-item"
+ :key="attachment.id"
+ :set-media="setMedia"
+ :nsfw="nsfw"
+ :attachment="attachment"
+ :allow-play="false"
+ :size="size"
+ :natural-size-load="onNaturalSizeLoad.bind(null, attachment.id)"
+ :style="itemStyle(attachment.id, row.items)"
+ />
+ </div>
+ </div>
+ </div>
<div
- v-for="(row, index) in rows"
- :key="index"
- class="gallery-row"
- :style="rowStyle(row.length)"
- :class="{ 'contain-fit': useContainFit, 'cover-fit': !useContainFit }"
+ v-if="tooManyAttachments"
+ class="many-attachments"
>
- <div class="gallery-row-inner">
- <attachment
- v-for="attachment in row"
- :key="attachment.id"
- :set-media="setMedia"
- :nsfw="nsfw"
- :attachment="attachment"
- :allow-play="false"
- :natural-size-load="onNaturalSizeLoad.bind(null, attachment.id)"
- :style="itemStyle(attachment.id, row)"
- />
+ <div class="many-attachments-text">
+ {{ $t("status.many_attachments", { number: attachments.length })}}
+ </div>
+ <div class="many-attachments-buttons">
+ <span
+ v-if="!hidingLong"
+ class="many-attachments-button"
+ >
+ <button
+ class="button-unstyled -link"
+ @click="toggleHidingLong(true)"
+ >
+ {{ $t("status.collapse_attachments") }}
+ </button>
+ </span>
+ <span
+ v-if="hidingLong"
+ class="many-attachments-button"
+ >
+ <button
+ class="button-unstyled -link"
+ @click="toggleHidingLong(false)"
+ >
+ {{ $t("status.show_all_attachments") }}
+ </button>
+ </span>
+ <span
+ class="many-attachments-button"
+ v-if="hidingLong"
+ >
+ <button
+ class="button-unstyled -link"
+ @click="openGallery"
+ >
+ {{ $t("status.open_gallery") }}
+ </button>
+ </span>
</div>
</div>
</div>
@@ -31,12 +79,64 @@
<style lang="scss">
@import '../../_variables.scss';
-.gallery-row {
- position: relative;
- height: 0;
- width: 100%;
- flex-grow: 1;
- margin-top: 0.5em;
+.Gallery {
+ .gallery-rows {
+ display: flex;
+ flex-direction: column;
+ }
+
+ .gallery-row {
+ position: relative;
+ height: 0;
+ width: 100%;
+ flex-grow: 1;
+ margin-top: 0.5em;
+ }
+
+ &.-long {
+ .gallery-rows {
+ max-height: 25em;
+ overflow: hidden;
+ mask:
+ linear-gradient(to top, white, transparent) bottom/100% 70px no-repeat,
+ linear-gradient(to top, white, white);
+
+ /* Autoprefixed seem to ignore this one, and also syntax is different */
+ -webkit-mask-composite: xor;
+ mask-composite: exclude;
+ }
+ }
+
+ .many-attachments-text {
+ text-align: center;
+ line-height: 2;
+ }
+
+ .many-attachments-buttons {
+ display: flex;
+ }
+
+ .many-attachments-button {
+ display: flex;
+ flex: 1;
+ justify-content: center;
+ line-height: 2;
+
+ button {
+ padding: 0 2em;
+ }
+ }
+
+ .gallery-row {
+
+ &.-minimal {
+ height: auto;
+
+ .gallery-row-inner {
+ position: relative;
+ }
+ }
+ }
.gallery-row-inner {
position: absolute;
@@ -50,7 +150,7 @@
align-content: stretch;
}
- .gallery-row-inner .attachment {
+ .gallery-item {
margin: 0 0.5em 0 0;
flex-grow: 1;
height: 100%;
@@ -61,32 +161,5 @@
margin: 0;
}
}
-
- .image-attachment {
- width: 100%;
- height: 100%;
- }
-
- .video-container {
- height: 100%;
- }
-
- &.contain-fit {
- img,
- video,
- canvas {
- object-fit: contain;
- height: 100%;
- }
- }
-
- &.cover-fit {
- img,
- video,
- canvas {
- object-fit: cover;
- }
- }
}
-
</style>
diff --git a/src/components/status_content/status_content.js b/src/components/status_content/status_content.js
@@ -58,24 +58,6 @@ const StatusContent = {
}
return 'normal'
},
- galleryTypes () {
- if (this.attachmentSize === 'hide') {
- return []
- }
- return this.mergedConfig.playVideosInModal
- ? ['image', 'video']
- : ['image']
- },
- galleryAttachments () {
- return this.status.attachments.filter(
- file => fileType.fileMatchesSomeType(this.galleryTypes, file)
- )
- },
- nonGalleryAttachments () {
- return this.status.attachments.filter(
- file => !fileType.fileMatchesSomeType(this.galleryTypes, file)
- )
- },
maxThumbnails () {
return this.mergedConfig.maxThumbnails
},
@@ -93,7 +75,7 @@ const StatusContent = {
},
methods: {
setMedia () {
- const attachments = this.attachmentSize === 'hide' ? this.status.attachments : this.galleryAttachments
+ const attachments = this.status.attachments
return () => this.$store.dispatch('setMedia', attachments)
}
}
diff --git a/src/components/status_content/status_content.vue b/src/components/status_content/status_content.vue
@@ -11,29 +11,16 @@
<poll :base-poll="status.poll" />
</div>
- <div
- v-if="status.attachments.length !== 0"
+ <gallery
class="attachments media-body"
- >
- <attachment
- v-for="attachment in nonGalleryAttachments"
- :key="attachment.id"
- class="non-gallery"
- :size="attachmentSize"
- :nsfw="nsfwClickthrough"
- :attachment="attachment"
- :allow-play="true"
- :set-media="setMedia()"
- @play="$emit('mediaplay', attachment.id)"
- @pause="$emit('mediapause', attachment.id)"
- />
- <gallery
- v-if="galleryAttachments.length > 0"
- :nsfw="nsfwClickthrough"
- :attachments="galleryAttachments"
- :set-media="setMedia()"
- />
- </div>
+ v-if="status.attachments.length !== 0"
+ :nsfw="nsfwClickthrough"
+ :attachments="status.attachments"
+ :set-media="setMedia()"
+ :size="attachmentSize"
+ @play="$emit('mediaplay', attachment.id)"
+ @pause="$emit('mediapause', attachment.id)"
+ />
<div
v-if="status.card && !noHeading"
diff --git a/src/i18n/en.json b/src/i18n/en.json
@@ -717,7 +717,11 @@
"nsfw": "NSFW",
"expand": "Expand",
"you": "(You)",
- "plus_more": "+{number} more"
+ "plus_more": "+{number} more",
+ "many_attachments": "Post has {number} attachment(s)",
+ "collapse_attachments": "Collapse attachments",
+ "show_all_attachments": "Show all attachments",
+ "open_gallery": "Open gallery"
},
"user_card": {
"approve": "Approve",