feat: client side media proxy

This commit is contained in:
2022-11-20 13:37:22 +09:00
parent 7a13813904
commit 0c92a9a22d
6 changed files with 43 additions and 14 deletions

View File

@ -1,13 +1,14 @@
<template>
<div class="xubzgfgb" :class="{ cover }" :title="title">
<canvas v-if="!loaded" ref="canvas" :width="size" :height="size" :title="title"/>
<img v-if="src" :src="src" :title="title" :alt="alt" @load="onLoad"/>
<img v-if="src" :src="imgSrc" :title="title" :alt="alt" @load="onLoad"/>
</div>
</template>
<script lang="ts" setup>
import { onMounted } from 'vue';
import { decode } from 'blurhash';
import { defaultStore } from '@/store';
const props = withDefaults(defineProps<{
src?: string | null;
@ -24,6 +25,11 @@ const props = withDefaults(defineProps<{
cover: true,
});
const imgSrc = defaultStore.state.mediaProxy ?
defaultStore.state.mediaProxy + '?url=' + props.src
:
props.src;
const canvas = $ref<HTMLCanvasElement>();
let loaded = $ref(false);

View File

@ -1,6 +1,6 @@
<template>
<MkA v-if="url.startsWith('/')" v-user-preview="canonical" class="akbvjaqn" :class="{ isMe }" :to="url" :style="{ background: bgCss }">
<img class="icon" :src="`/avatar/@${username}@${host}`" alt="">
<img class="icon" :src="imageUrl" alt="">
<span class="main">
<span class="username">@{{ username }}</span>
<span v-if="(host != localHost) || $store.state.showFullAcct" class="host">@{{ toUnicode(host) }}</span>
@ -18,14 +18,20 @@
import { toUnicode } from 'punycode';
import { } from 'vue';
import tinycolor from 'tinycolor2';
import { host as localHost } from '@/config';
import { host as localHost, url as localUrl } from '@/config';
import { $i } from '@/account';
import { defaultStore } from '@/store';
const props = defineProps<{
username: string;
host: string;
}>();
const imageUrl = defaultStore.state.mediaProxy ?
defaultStore.state.mediaProxy + '?url=' + `${localUrl}/avatar/@${props.username}@${props.host}`
:
`${localUrl}/avatar/@${props.username}@${props.host}`;
const canonical = props.host === localHost ? `@${props.username}` : `@${props.username}@${toUnicode(props.host)}`;
const url = `/${canonical}`;

View File

@ -35,9 +35,14 @@ const emit = defineEmits<{
(ev: 'click', v: MouseEvent): void;
}>();
const url = $computed(() => defaultStore.state.disableShowingAnimatedImages
const imageSrc = defaultStore.state.disableShowingAnimatedImages
? getStaticImageUrl(props.user.avatarUrl)
: props.user.avatarUrl);
: props.user.avatarUrl;
const url = defaultStore.state.mediaProxy ?
defaultStore.state.mediaProxy + '?url=' + imageSrc
:
imageSrc;
function onClick(ev: MouseEvent) {
emit('click', ev);

View File

@ -26,15 +26,17 @@ const char = computed(() => isCustom.value ? null : props.emoji);
const useOsNativeEmojis = computed(() => defaultStore.state.useOsNativeEmojis && !props.isReaction);
const ce = computed(() => props.customEmojis ?? instance.emojis ?? []);
const customEmoji = computed(() => isCustom.value ? ce.value.find(x => x.name === props.emoji.substr(1, props.emoji.length - 2)) : null);
const url = computed(() => {
if (char.value) {
return char2filePath(char.value);
} else {
return defaultStore.state.disableShowingAnimatedImages
? getStaticImageUrl(customEmoji.value.url)
: customEmoji.value.url;
}
});
const imageSrc = defaultStore.state.disableShowingAnimatedImages
? getStaticImageUrl(customEmoji.value?.url)
: customEmoji.value?.url;
const url = char.value ?
char2filePath(char.value)
:
defaultStore.state.mediaProxy ?
defaultStore.state.mediaProxy + '?url=' + imageSrc
:
imageSrc;
const alt = computed(() => customEmoji.value ? `:${customEmoji.value.name}:` : char.value);
</script>

View File

@ -35,6 +35,10 @@
<option value="dialog">{{ i18n.ts._serverDisconnectedBehavior.dialog }}</option>
<option value="quiet">{{ i18n.ts._serverDisconnectedBehavior.quiet }}</option>
</FormSelect>
<FormInput v-model="mediaProxy">
<template #label>{{ i18n.ts.mediaProxy }}</template>
<template #caption>{{ i18n.ts.mediaProxyDesc }}</template>
</FormInput>
</FormSection>
<FormSection>
@ -100,6 +104,7 @@ import FormRadios from '@/components/form/radios.vue';
import FormRange from '@/components/form/range.vue';
import FormSection from '@/components/form/section.vue';
import FormLink from '@/components/form/link.vue';
import FormInput from '@/components/form/input.vue';
import MkLink from '@/components/MkLink.vue';
import { langs } from '@/config';
import { defaultStore } from '@/store';
@ -143,6 +148,7 @@ const enableInfiniteScroll = computed(defaultStore.makeGetterSetter('enableInfin
const useReactionPickerForContextMenu = computed(defaultStore.makeGetterSetter('useReactionPickerForContextMenu'));
const squareAvatars = computed(defaultStore.makeGetterSetter('squareAvatars'));
const aiChanMode = computed(defaultStore.makeGetterSetter('aiChanMode'));
const mediaProxy = computed(defaultStore.makeGetterSetter('mediaProxy'));
watch(lang, () => {
localStorage.setItem('lang', lang.value as string);

View File

@ -251,6 +251,10 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'device',
default: false,
},
mediaProxy: {
where: 'device',
default: '',
},
}));
// TODO: 他のタブと永続化されたstateを同期