forked from cybrespace/pinafore
feat: Allow Media to be shown in a grid (as an option) (#747)
* Allow Media to be shown in a grid * Bring back the sidebar for ungrouped images
This commit is contained in:
parent
ab548a0a5d
commit
530ad6b35c
|
@ -1,7 +1,10 @@
|
|||
<video
|
||||
class="autoplay-video {className || ''}"
|
||||
<div class="autoplay-wrapper {$groupedImages ? 'fixed-size': ''}"
|
||||
style="width: {width}px; height: {height}px;"
|
||||
>
|
||||
<video
|
||||
class="autoplay-video {$groupedImages ? 'fixed-size': ''}"
|
||||
aria-label={ariaLabel || ''}
|
||||
style="background-image: url({poster});"
|
||||
style="{focusStyle} background-image: url({poster}); "
|
||||
{poster}
|
||||
{width}
|
||||
{height}
|
||||
|
@ -11,11 +14,54 @@
|
|||
loop
|
||||
webkit-playsinline
|
||||
playsinline
|
||||
/>
|
||||
/>
|
||||
</div>
|
||||
<style>
|
||||
.autoplay-wrapper {
|
||||
position: relative;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.autoplay-video {
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: contain;
|
||||
background-size: cover;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.fixed-size {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
.fixed-size {
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
import { store } from '../_store/store'
|
||||
const coordsToPercent = coord => (1 + coord) / 2 * 100
|
||||
|
||||
export default {
|
||||
|
||||
data: () => ({
|
||||
focus: void 0
|
||||
}),
|
||||
store: () => store,
|
||||
computed: {
|
||||
focusStyle: ({ focus }) => {
|
||||
// Here we do a pure css version instead of using
|
||||
// https://github.com/jonom/jquery-focuspoint#1-calculate-your-images-focus-point
|
||||
|
||||
if (!focus) return 'background-position: center;'
|
||||
return `object-position: ${coordsToPercent(focus.x)}% ${100 - coordsToPercent(focus.y)}%;`
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<LazyImage
|
||||
className={computedClass}
|
||||
ariaHidden="true"
|
||||
forceSize=true
|
||||
alt=""
|
||||
src={account.avatar}
|
||||
{width}
|
||||
|
|
|
@ -1,24 +1,40 @@
|
|||
<div class="lazy-image" style={computedStyle} >
|
||||
<div class="lazy-image {fillFixSize ? 'fixed-size': ''}" style={computedStyle} >
|
||||
<img
|
||||
class={className}
|
||||
aria-hidden={ariaHidden}
|
||||
{alt}
|
||||
{title}
|
||||
{width}
|
||||
{height}
|
||||
src={displaySrc}
|
||||
style={focusStyle}
|
||||
ref:node
|
||||
/>
|
||||
</div>
|
||||
<style>
|
||||
.lazy-image {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.fixed-size img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.fixed-size {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
<script>
|
||||
import { decodeImage } from '../_utils/decodeImage'
|
||||
const coordsToPercent = coord => (1 + coord) / 2 * 100
|
||||
|
||||
export default {
|
||||
export default {
|
||||
async oncreate () {
|
||||
try {
|
||||
await decodeImage(this.refs.node)
|
||||
|
@ -28,23 +44,30 @@
|
|||
},
|
||||
data: () => ({
|
||||
error: false,
|
||||
forceSize: false,
|
||||
fallback: void 0,
|
||||
focus: void 0,
|
||||
background: '',
|
||||
width: void 0,
|
||||
height: void 0,
|
||||
className: '',
|
||||
ariaHidden: false,
|
||||
alt: '',
|
||||
title: ''
|
||||
}),
|
||||
computed: {
|
||||
computedStyle: ({ width, height, background }) => {
|
||||
computedStyle: ({ background }) => {
|
||||
return [
|
||||
width && `width: ${width}px;`,
|
||||
height && `height: ${height}px;`,
|
||||
background && `background: ${background};`
|
||||
].filter(Boolean).join('')
|
||||
},
|
||||
focusStyle: ({ focus }) => {
|
||||
// Here we do a pure css version instead of using
|
||||
// https://github.com/jonom/jquery-focuspoint#1-calculate-your-images-focus-point
|
||||
|
||||
if (!focus) return 'background-position: center;'
|
||||
return `object-position: ${coordsToPercent(focus.x)}% ${100 - coordsToPercent(focus.y)}%;`
|
||||
},
|
||||
fillFixSize: ({ forceSize, $groupedImages }) => $groupedImages && !forceSize,
|
||||
displaySrc: ({ error, src, fallback }) => ((error && fallback) || src)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
<div class="non-autoplay-gifv" style="width: {width}px; height: {height}px;"
|
||||
<div class="non-autoplay-gifv {$groupedImages ? 'fixed-size': ''}"
|
||||
style="width: {width}px; height: {height}px;"
|
||||
on:mouseover="onMouseOver(event)"
|
||||
ref:node
|
||||
>
|
||||
{#if playing}
|
||||
<AutoplayVideo
|
||||
className={class}
|
||||
ariaLabel={label}
|
||||
{poster}
|
||||
{src}
|
||||
{width}
|
||||
{height}
|
||||
{focus}
|
||||
/>
|
||||
{:else}
|
||||
<LazyImage
|
||||
|
@ -20,7 +21,7 @@
|
|||
{width}
|
||||
{height}
|
||||
background="var(--loading-bg)"
|
||||
className={class}
|
||||
{focus}
|
||||
/>
|
||||
{/if}
|
||||
<PlayVideoIcon className={playing ? 'hidden' : ''}/>
|
||||
|
@ -28,8 +29,20 @@
|
|||
<style>
|
||||
.non-autoplay-gifv {
|
||||
cursor: zoom-in;
|
||||
display: flex;
|
||||
position: relative;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.fixed-size {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
:global(.non-autoplay-gifv .play-video-icon) {
|
||||
transition: opacity 0.2s linear;
|
||||
}
|
||||
|
@ -51,7 +64,8 @@
|
|||
mouseover
|
||||
},
|
||||
data: () => ({
|
||||
oneTransparentPixel: ONE_TRANSPARENT_PIXEL
|
||||
oneTransparentPixel: ONE_TRANSPARENT_PIXEL,
|
||||
focus: void 0
|
||||
}),
|
||||
components: {
|
||||
PlayVideoIcon,
|
||||
|
|
|
@ -6,12 +6,18 @@
|
|||
className="image-modal-dialog"
|
||||
>
|
||||
{#if type === 'gifv'}
|
||||
<AutoplayVideo
|
||||
ariaLabel="Animated GIF: {description || ''}"
|
||||
{poster}
|
||||
{src}
|
||||
<video
|
||||
class="autoplay-video"
|
||||
aria-label="Animated GIF: {description || ''}"
|
||||
style="background-image: url({poster}); "
|
||||
{width}
|
||||
{height}
|
||||
{src}
|
||||
autoplay
|
||||
muted
|
||||
loop
|
||||
webkit-playsinline
|
||||
playsinline
|
||||
/>
|
||||
{:else}
|
||||
<img
|
||||
|
@ -30,18 +36,23 @@
|
|||
max-height: calc(100% - 20px);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.autoplay-video {
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
</style>
|
||||
<script>
|
||||
import ModalDialog from './ModalDialog.html'
|
||||
import AutoplayVideo from '../../AutoplayVideo.html'
|
||||
import { show } from '../helpers/showDialog'
|
||||
import { oncreate } from '../helpers/onCreateDialog'
|
||||
|
||||
export default {
|
||||
oncreate,
|
||||
components: {
|
||||
ModalDialog,
|
||||
AutoplayVideo
|
||||
ModalDialog
|
||||
},
|
||||
methods: {
|
||||
show
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{#if type === 'video'}
|
||||
<button type="button"
|
||||
class="play-video-button"
|
||||
class="play-video-button {$groupedImages ? 'fixed-size': ''}"
|
||||
aria-label="Play video: {description}"
|
||||
delegate-key={delegateKey}
|
||||
style="width: {inlineWidth}px; height: {inlineHeight}px;">
|
||||
|
@ -13,26 +13,25 @@
|
|||
width={inlineWidth}
|
||||
height={inlineHeight}
|
||||
background="var(--loading-bg)"
|
||||
className={noNativeWidthHeight ? 'no-native-width-height' : ''}
|
||||
{focus}
|
||||
/>
|
||||
</button>
|
||||
{:else}
|
||||
<button type="button"
|
||||
class="show-image-button"
|
||||
class="show-image-button {$groupedImages ? 'fixed-size': ''}"
|
||||
aria-label="Show image: {description}"
|
||||
title={description}
|
||||
delegate-key={delegateKey}
|
||||
on:mouseover="set({mouseover: event})"
|
||||
style="width: {inlineWidth}px; height: {inlineHeight}px;"
|
||||
>
|
||||
style="width: {inlineWidth}px; height: {inlineHeight}px;">
|
||||
{#if type === 'gifv' && $autoplayGifs}
|
||||
<AutoplayVideo
|
||||
className={noNativeWidthHeight ? 'no-native-width-height' : ''}
|
||||
ariaLabel="Animated GIF: {description}"
|
||||
poster={previewUrl}
|
||||
src={url}
|
||||
width={inlineWidth}
|
||||
height={inlineHeight}
|
||||
{focus}
|
||||
/>
|
||||
{:elseif type === 'gifv' && !$autoplayGifs}
|
||||
<NonAutoplayGifv
|
||||
|
@ -44,6 +43,7 @@
|
|||
width={inlineWidth}
|
||||
height={inlineHeight}
|
||||
playing={mouseover}
|
||||
{focus}
|
||||
/>
|
||||
{:else}
|
||||
<LazyImage
|
||||
|
@ -54,46 +54,27 @@
|
|||
width={inlineWidth}
|
||||
height={inlineHeight}
|
||||
background="var(--loading-bg)"
|
||||
className={noNativeWidthHeight ? 'no-native-width-height' : ''}
|
||||
{focus}
|
||||
/>
|
||||
{/if}
|
||||
</button>
|
||||
{/if}
|
||||
<style>
|
||||
|
||||
:global(.status-media video, .status-media img) {
|
||||
object-fit: cover;
|
||||
}
|
||||
:global(.no-native-width-height) {
|
||||
background-color: var(--mask-bg);
|
||||
}
|
||||
.play-video-button {
|
||||
margin: 0;
|
||||
.play-video-button, .show-image-button {
|
||||
margin: auto;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
background: none;
|
||||
position: relative;
|
||||
}
|
||||
.show-image-button {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: zoom-in;
|
||||
}
|
||||
|
||||
:global(.status-media video, .status-media img, .status-media .lazy-image,
|
||||
.status-media .show-image-button, .status-media .non-autoplay-gifv,
|
||||
.status-media .play-video-button) {
|
||||
max-width: calc(100vw - 40px);
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
:global(.status-media video, .status-media img, .status-media .lazy-image,
|
||||
.status-media .show-image-button, .status-media .non-autoplay-gifv,
|
||||
.status-media .play-video-button) {
|
||||
max-width: calc(100vw - 20px);
|
||||
}
|
||||
.show-image-button {
|
||||
cursor: zoom-in;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
|
@ -120,9 +101,16 @@
|
|||
})
|
||||
},
|
||||
computed: {
|
||||
focus: ({ meta }) => meta && meta.focus,
|
||||
// width/height to show inline
|
||||
inlineWidth: ({ smallWidth }) => smallWidth || DEFAULT_MEDIA_WIDTH,
|
||||
inlineHeight: ({ smallHeight }) => smallHeight || DEFAULT_MEDIA_HEIGHT,
|
||||
inlineWidth: ({ smallWidth, $groupedImages }) => {
|
||||
if ($groupedImages) return '100%'
|
||||
return smallWidth || DEFAULT_MEDIA_WIDTH
|
||||
},
|
||||
inlineHeight: ({ smallHeight, $groupedImages }) => {
|
||||
if ($groupedImages) return 'auto'
|
||||
return smallHeight || DEFAULT_MEDIA_HEIGHT
|
||||
},
|
||||
// width/height to show in a modal
|
||||
modalWidth: ({ originalWidth, inlineWidth }) => originalWidth || inlineWidth,
|
||||
modalHeight: ({ originalHeight, inlineHeight }) => originalHeight || inlineHeight,
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
<div class="status-media {sensitive ? 'status-media-is-sensitive' : ''}"
|
||||
style="grid-template-columns: repeat(auto-fit, minmax({maxMediaWidth}px, 1fr));" >
|
||||
<div class="status-media
|
||||
{sensitive ? 'status-media-is-sensitive' : ''}
|
||||
{oddCols ? 'odd-cols' : ''}
|
||||
{twoCols ? 'two-cols' : ''}
|
||||
{$groupedImages ? 'grouped-images' : ''}
|
||||
"
|
||||
style="grid-template-columns: repeat({nCols}, 1fr); " >
|
||||
{#each mediaAttachments as media}
|
||||
<Media {media} {uuid} />
|
||||
{/each}
|
||||
|
@ -11,35 +16,55 @@
|
|||
align-items: center;
|
||||
justify-content: center;
|
||||
justify-items: center;
|
||||
grid-column-gap: 10px;
|
||||
grid-row-gap: 10px;
|
||||
margin: 10px 0;
|
||||
grid-column-gap: 5px;
|
||||
grid-row-gap: 5px;
|
||||
|
||||
overflow: hidden;
|
||||
max-width: calc(100vw - 40px);
|
||||
margin: 10px 10px 10px 5px;
|
||||
}
|
||||
|
||||
.status-media.grouped-images {
|
||||
grid-area: media-grp;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.status-media.grouped-images > :global(*) {
|
||||
padding-bottom: 56.25%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.status-media.grouped-images.two-cols > :global(*) {
|
||||
padding-bottom: calc(112.5% + 5px);
|
||||
}
|
||||
|
||||
.status-media.grouped-images.odd-cols > :global(:first-child) {
|
||||
grid-row-end: span 2;
|
||||
padding-bottom: calc(112.5% + 5px);
|
||||
background-color: blue;
|
||||
}
|
||||
|
||||
.status-media.status-media-is-sensitive {
|
||||
margin: 0;
|
||||
}
|
||||
.status-media {
|
||||
overflow: hidden;
|
||||
}
|
||||
.status-media {
|
||||
max-width: calc(100vw - 40px);
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
.status-media {
|
||||
max-width: calc(100vw - 20px);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
import Media from './Media.html'
|
||||
import { DEFAULT_MEDIA_WIDTH } from '../../_static/media'
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
maxMediaWidth: ({ mediaAttachments }) => {
|
||||
return Math.max.apply(Math, mediaAttachments.map(media => {
|
||||
return media.meta && media.meta.small && typeof media.meta.small.width === 'number' ? media.meta.small.width : DEFAULT_MEDIA_WIDTH
|
||||
}))
|
||||
nCols:
|
||||
({ mediaAttachments, $groupedImages }) => {
|
||||
return ($groupedImages && mediaAttachments.length > 1) ? 2 : 1
|
||||
},
|
||||
oddCols:
|
||||
({ mediaAttachments }) => {
|
||||
return (mediaAttachments.length > 1 && (mediaAttachments.length % 2))
|
||||
},
|
||||
|
||||
twoCols:
|
||||
({ mediaAttachments }) => {
|
||||
return (mediaAttachments.length === 2)
|
||||
}
|
||||
},
|
||||
components: {
|
||||
|
|
|
@ -49,6 +49,7 @@
|
|||
"sidebar spoiler-btn spoiler-btn spoiler-btn"
|
||||
"sidebar mentions mentions mentions"
|
||||
"sidebar content content content"
|
||||
"sidebar media-grp media-grp media-grp"
|
||||
"media media media media"
|
||||
"....... toolbar toolbar toolbar"
|
||||
"compose compose compose compose";
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
<style>
|
||||
.status-sensitive-media-container {
|
||||
grid-area: media;
|
||||
width: 100%;
|
||||
margin: 10px 0;
|
||||
position: relative;
|
||||
border-radius: 0;
|
||||
|
|
|
@ -27,6 +27,11 @@
|
|||
bind:checked="$autoplayGifs" on:change="onChange(event)">
|
||||
<label for="choice-autoplay-gif">Autoplay GIFs</label>
|
||||
</div>
|
||||
<div class="setting-group">
|
||||
<input type="checkbox" id="choice-grouped-images"
|
||||
bind:checked="$groupedImages" on:change="onChange(event)">
|
||||
<label for="choice-groupes-images">Group images</label>
|
||||
</div>
|
||||
<div class="setting-group">
|
||||
<input type="checkbox" id="choice-reduce-motion"
|
||||
bind:checked="$reduceMotion" on:change="onChange(event)">
|
||||
|
|
|
@ -13,6 +13,7 @@ const persistedState = {
|
|||
disableCustomScrollbars: false,
|
||||
disableLongAriaLabels: false,
|
||||
disableTapOnStatus: false,
|
||||
groupedImages: false,
|
||||
instanceNameInSearch: '',
|
||||
instanceThemes: {},
|
||||
loggedInInstances: {},
|
||||
|
@ -22,7 +23,9 @@ const persistedState = {
|
|||
omitEmojiInDisplayNames: undefined,
|
||||
pinnedPages: {},
|
||||
pushSubscription: null,
|
||||
reduceMotion: !process.browser || window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
||||
reduceMotion:
|
||||
!process.browser ||
|
||||
window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
||||
}
|
||||
|
||||
const nonPersistedState = {
|
||||
|
@ -31,7 +34,11 @@ const nonPersistedState = {
|
|||
instanceLists: {},
|
||||
online: !process.browser || navigator.onLine,
|
||||
pinnedStatuses: {},
|
||||
pushNotificationsSupport: process.browser && ('serviceWorker' in navigator && 'PushManager' in window && 'getKey' in window.PushSubscription.prototype),
|
||||
pushNotificationsSupport:
|
||||
process.browser &&
|
||||
('serviceWorker' in navigator &&
|
||||
'PushManager' in window &&
|
||||
'getKey' in window.PushSubscription.prototype),
|
||||
queryInSearch: '',
|
||||
repliesShown: {},
|
||||
sensitivesShown: {},
|
||||
|
|
Loading…
Reference in New Issue