Compare commits

..

15 commits

Author SHA1 Message Date
FloatingGhost
e7a558a533 Merge branch 'develop' into stable 2023-08-05 14:17:53 +01:00
FloatingGhost
5106fcedd6 Merge branch 'develop' into stable 2023-08-05 13:29:55 +01:00
FloatingGhost
1db322bae6 Merge branch 'develop' into stable 2023-08-05 13:29:26 +01:00
FloatingGhost
e530c2b462 Merge branch 'develop' into stable 2023-05-23 14:10:31 +01:00
FloatingGhost
9aa64d82c9 Merge branch 'develop' into stable 2023-04-14 18:10:41 +01:00
FloatingGhost
85abc62213 Merge branch 'develop' into stable 2023-03-11 17:27:22 +00:00
FloatingGhost
8569b5946e Merge branch 'develop' into stable 2023-02-11 10:50:04 +00:00
FloatingGhost
9c9b4cc07c Merge branch 'develop' into stable 2022-12-10 14:52:00 +00:00
floatingghost
2c9b73646c Merge pull request 'hotfix: mfm mysteries' (#215) from develop into stable
Reviewed-on: https://akkoma.dev/AkkomaGang/pleroma-fe/pulls/215
2022-11-15 16:01:07 +00:00
floatingghost
80a519d7e4 Merge pull request 'hotfix: translation' (#207) from develop into stable
Reviewed-on: https://akkoma.dev/AkkomaGang/pleroma-fe/pulls/207
2022-11-12 19:08:20 +00:00
floatingghost
975f04bf5a Merge pull request '2022.11 stable release' (#202) from develop into stable
Reviewed-on: https://akkoma.dev/AkkomaGang/pleroma-fe/pulls/202
2022-11-12 15:33:57 +00:00
floatingghost
c8c8d40827 Merge pull request '2022.10 stable' (#177) from develop into stable
Reviewed-on: https://akkoma.dev/AkkomaGang/pleroma-fe/pulls/177
2022-10-08 11:13:01 +00:00
floatingghost
d7499a1f91 Merge pull request '2022.09 stable' (#160) from develop into stable
Reviewed-on: https://akkoma.dev/AkkomaGang/pleroma-fe/pulls/160
2022-09-10 14:39:13 +00:00
floatingghost
5972d89117 Merge pull request 'stable release' (#130) from develop into stable
Reviewed-on: https://akkoma.dev/AkkomaGang/pleroma-fe/pulls/130
2022-08-12 15:26:51 +00:00
floatingghost
d03872d598 Merge pull request 'port MFM link into stable docs' (#38) from develop into stable
Reviewed-on: https://akkoma.dev/AkkomaGang/pleroma-fe/pulls/38
2022-07-15 13:02:06 +00:00
4 changed files with 48 additions and 117 deletions

View file

@ -13,7 +13,6 @@ const StillImage = {
return { return {
stopGifs: this.$store.getters.mergedConfig.stopGifs || window.matchMedia('(prefers-reduced-motion: reduce)').matches, stopGifs: this.$store.getters.mergedConfig.stopGifs || window.matchMedia('(prefers-reduced-motion: reduce)').matches,
isAnimated: false, isAnimated: false,
imageTypeLabel: ''
} }
}, },
computed: { computed: {
@ -40,22 +39,27 @@ const StillImage = {
this.imageLoadError && this.imageLoadError() this.imageLoadError && this.imageLoadError()
}, },
detectAnimation (image) { detectAnimation (image) {
// If there are no file extensions, the mimetype isn't set, and no mediaproxy is available, we can't figure out
// the mimetype of the image.
const hasFileExtension = this.src.split('/').pop().includes('.') // TODO: Better check?
const mediaProxyAvailable = this.$store.state.instance.mediaProxyAvailable const mediaProxyAvailable = this.$store.state.instance.mediaProxyAvailable
if (!hasFileExtension && this.mimetype === undefined && !mediaProxyAvailable) {
// harmless CORS errors without-- clean console with
if (!mediaProxyAvailable) {
// It's a bit aggressive to assume all images we can't find the mimetype of is animated, but necessary for // It's a bit aggressive to assume all images we can't find the mimetype of is animated, but necessary for
// people in need of reduced motion accessibility. As such, we'll consider those images animated if the user // people in need of reduced motion accessibility. As such, we'll consider those images animated if the user
// agent is set to prefer reduced motion. Otherwise, it'll just be used as an early exit. // agent is set to prefer reduced motion. Otherwise, it'll just be used as an early exit.
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) { if (window.matchMedia('(prefers-reduced-motion: reduce)').matches)
// Only for no media-proxy for now, since we're capturing gif, webp, and apng (are there others?)
// Since the canvas and images are not pixel-perfect matching (due to scaling),
// It makes the images jiggle on hover, which is not ideal for accessibility, methinks
this.isAnimated = true this.isAnimated = true
}
return return
} }
if (this.mimetype === 'image/gif' || this.src.endsWith('.gif')) {
this.isAnimated = true
return
}
// harmless CORS errors without-- clean console with
if (!mediaProxyAvailable) return
// Animated JPEGs?
if (!(this.src.endsWith('.webp') || this.src.endsWith('.png'))) return
// Browser Cache should ensure image doesn't get loaded twice if cache exists // Browser Cache should ensure image doesn't get loaded twice if cache exists
fetch(image.src, { fetch(image.src, {
referrerPolicy: 'same-origin' referrerPolicy: 'same-origin'
@ -64,20 +68,12 @@ const StillImage = {
// We don't need to read the whole file so only call it once // We don't need to read the whole file so only call it once
data.body.getReader().read() data.body.getReader().read()
.then(reader => { .then(reader => {
// Ordered from least to most intensive if (this.src.endsWith('.webp') && this.isAnimatedWEBP(reader.value)) {
if (this.isGIF(reader.value)) {
this.isAnimated = true this.isAnimated = true
this.setLabel('GIF')
return return
} }
if (this.isAnimatedWEBP(reader.value)) { if (this.src.endsWith('.png') && this.isAnimatedPNG(reader.value)) {
this.isAnimated = true this.isAnimated = true
this.setLabel('WEBP')
return
}
if (this.isAnimatedPNG(reader.value)) {
this.isAnimated = true
this.setLabel('APNG')
} }
}) })
}) })
@ -85,23 +81,6 @@ const StillImage = {
// this.imageLoadError && this.imageLoadError() // this.imageLoadError && this.imageLoadError()
}) })
}, },
setLabel (name) {
this.imageTypeLabel = name;
},
isGIF (data) {
// I am a perfectly sane individual
//
// GIF HEADER CHUNK
// === START HEADER ===
// 47 49 46 38 ("GIF8")
const gifHeader = [0x47, 0x49, 0x46];
for (let i = 0; i < 3; i++) {
if (data[i] !== gifHeader[i]) {
return false;
}
}
return true
},
isAnimatedWEBP (data) { isAnimatedWEBP (data) {
/** /**
* WEBP HEADER CHUNK * WEBP HEADER CHUNK
@ -135,55 +114,16 @@ const StillImage = {
const idatPos = str.indexOf('IDAT') const idatPos = str.indexOf('IDAT')
return (str.substring(0, idatPos > 0 ? idatPos : 0).indexOf('acTL') > 0) return (str.substring(0, idatPos > 0 ? idatPos : 0).indexOf('acTL') > 0)
}, },
drawThumbnail() { drawThumbnail () {
const canvas = this.$refs.canvas; const canvas = this.$refs.canvas
if (!canvas) return; if (!this.$refs.canvas) return
const image = this.$refs.src
const context = canvas.getContext('2d'); const width = image.naturalWidth
const image = this.$refs.src; const height = image.naturalHeight
const parentElement = canvas.parentElement; canvas.width = width
canvas.height = height
// Draw the quick, unscaled version first canvas.getContext('2d').drawImage(image, 0, 0, width, height)
context.drawImage(image, 0, 0, parentElement.clientWidth, parentElement.clientHeight); }
// Use requestAnimationFrame to schedule the scaling to the next frame
requestAnimationFrame(() => {
// Compute scaling ratio between the natural dimensions of the image and its display dimensions
const scalingRatioWidth = parentElement.clientWidth / image.naturalWidth;
const scalingRatioHeight = parentElement.clientHeight / image.naturalHeight;
// Adjust for high-DPI displays
const ratio = window.devicePixelRatio || 1;
canvas.width = image.naturalWidth * scalingRatioWidth * ratio;
canvas.height = image.naturalHeight * scalingRatioHeight * ratio;
canvas.style.width = `${parentElement.clientWidth}px`;
canvas.style.height = `${parentElement.clientHeight}px`;
context.scale(ratio, ratio);
// Maintain the aspect ratio of the image
const imgAspectRatio = image.naturalWidth / image.naturalHeight;
const canvasAspectRatio = parentElement.clientWidth / parentElement.clientHeight;
let drawWidth, drawHeight;
if (imgAspectRatio > canvasAspectRatio) {
drawWidth = parentElement.clientWidth;
drawHeight = parentElement.clientWidth / imgAspectRatio;
} else {
drawHeight = parentElement.clientHeight;
drawWidth = parentElement.clientHeight * imgAspectRatio;
}
context.clearRect(0, 0, canvas.width, canvas.height); // Clear the previous unscaled image
context.imageSmoothingEnabled = true;
context.imageSmoothingQuality = 'high';
// Draw the good one for realsies
const dx = (parentElement.clientWidth - drawWidth) / 2;
const dy = (parentElement.clientHeight - drawHeight) / 2;
context.drawImage(image, dx, dy, drawWidth, drawHeight);
});
}
}, },
updated () { updated () {
// On computed animated change // On computed animated change

View file

@ -1,15 +1,9 @@
<template> <template>
<div <div
ref="still-image"
class="still-image" class="still-image"
:class="{ animated: animated }" :class="{ animated: animated }"
:style="style" :style="style"
> >
<div
v-if="animated && imageTypeLabel"
class="image-type-label">
{{ imageTypeLabel }}
</div>
<canvas <canvas
v-if="animated" v-if="animated"
ref="canvas" ref="canvas"
@ -63,26 +57,30 @@
} }
} }
.image-type-label {
position: absolute;
top: 0.25em;
left: 0.25em;
line-height: 1;
font-size: 0.6em;
background: rgba(127, 127, 127, 0.5);
color: #fff;
padding: 2px 4px;
border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
z-index: 2;
visibility: var(--_still-image-label-visibility, visible);
}
&.animated { &.animated {
&::before {
zoom: var(--_still_image-label-scale, 1);
content: 'gif';
position: absolute;
line-height: 1;
font-size: 0.7em;
top: 0.5em;
left: 0.5em;
background: rgba(127, 127, 127, 0.5);
color: #fff;
display: block;
padding: 2px 4px;
border-radius: $fallback--tooltipRadius;
border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
z-index: 2;
visibility: var(--_still-image-label-visibility, visible);
}
&:hover canvas { &:hover canvas {
display: none; display: none;
} }
&:hover .image-type-label { &:hover::before {
visibility: var(--_still-image-label-visibility, hidden); visibility: var(--_still-image-label-visibility, hidden);
} }

View file

@ -33,7 +33,7 @@
--_avatarShadowBox: var(--avatarStatusShadow); --_avatarShadowBox: var(--avatarStatusShadow);
--_avatarShadowFilter: var(--avatarStatusShadowFilter); --_avatarShadowFilter: var(--avatarStatusShadowFilter);
--_avatarShadowInset: var(--avatarStatusShadowInset); --_avatarShadowInset: var(--avatarStatusShadowInset);
// --_still-image-label-visibility: hidden; --_still-image-label-visibility: hidden;
display: inline-block; display: inline-block;
position: relative; position: relative;

View file

@ -37,18 +37,11 @@ const recentEmojis = {
getters: { getters: {
recentEmojis: (state, getters, rootState) => state.emojis.reduce((objects, displayText) => { recentEmojis: (state, getters, rootState) => state.emojis.reduce((objects, displayText) => {
let comparator = emoji => emoji.displayText === displayText const allEmojis = rootState.instance.emoji.concat(rootState.instance.customEmoji)
let emojiObject = allEmojis.find(emoji => emoji.displayText === displayText)
let emojiObject = rootState.instance.emoji.find(comparator)
if (emojiObject !== undefined) { if (emojiObject !== undefined) {
objects.push(emojiObject) objects.push(emojiObject)
} else {
emojiObject = rootState.instance.customEmoji.find(comparator)
if (emojiObject !== undefined) {
objects.push(emojiObject)
}
} }
return objects return objects
}, []), }, []),
}, },