logo

pleroma-fe

My custom branche(s) on git.pleroma.social/pleroma/pleroma-fe git clone https://hacktivis.me/git/pleroma-fe.git
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:

Msrc/components/attachment/attachment.js41+++++++++++++++++++++++++++++++----------
Msrc/components/attachment/attachment.vue307+++++++++++++++++++------------------------------------------------------------
Msrc/components/chat_message/chat_message.scss4----
Msrc/components/flash/flash.js5+++--
Msrc/components/flash/flash.vue7-------
Msrc/components/gallery/gallery.js65+++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Msrc/components/gallery/gallery.vue175++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
Msrc/components/status_content/status_content.js20+-------------------
Msrc/components/status_content/status_content.vue31+++++++++----------------------
Msrc/i18n/en.json6+++++-
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",