Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enhance/feat(frontend): データセーバーの改良・強化 #12526

Merged
merged 13 commits into from
Dec 3, 2023
Merged
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -23,6 +23,7 @@

### Client
- Feat: 今日誕生日のフォロー中のユーザーを一覧表示できるウィジェットを追加
- Feat: データセーバーでコードハイライトの読み込みを削減できるように
- Enhance: 絵文字のオートコンプリート機能強化 #12364
- Enhance: ユーザーのRawデータを表示するページが復活
- Enhance: リアクション選択時に音を鳴らせるように
@@ -31,6 +32,8 @@
- Enhance: Shareページで投稿を完了すると、親ウィンドウ(親フレーム)にpostMessageするように
- Enhance: チャンネル、クリップ、ページ、Play、ギャラリーにURLのコピーボタンを設置 #11305
- Enhance: ノートプレビューに「内容を隠す」が反映されるように
- Enhance: データセーバーの適用範囲を個別で設定できるように
- 従来のデータセーバーの設定はリセットされます
- fix: 「設定のバックアップ」で一部の項目がバックアップに含まれていなかった問題を修正
- Fix: ウィジェットのジョブキューにて音声の発音方法変更に追従できていなかったのを修正 #12367
- Enhance: 絵文字の詳細ページに記載される情報を追加
20 changes: 20 additions & 0 deletions locales/index.d.ts
Original file line number Diff line number Diff line change
@@ -1171,6 +1171,8 @@ export interface Locale {
"signupPendingError": string;
"cwNotationRequired": string;
"doReaction": string;
"code": string;
"reloadRequiredToApplySettings": string;
"_announcement": {
"forExistingUsers": string;
"forExistingUsersDescription": string;
@@ -2502,6 +2504,24 @@ export interface Locale {
};
};
};
"_dataSaver": {
"_media": {
"title": string;
"description": string;
};
"_avatar": {
"title": string;
"description": string;
};
"_urlPreview": {
"title": string;
"description": string;
};
"_code": {
"title": string;
"description": string;
};
};
}
declare const locales: {
[lang: string]: Locale;
16 changes: 16 additions & 0 deletions locales/ja-JP.yml
Original file line number Diff line number Diff line change
@@ -1168,6 +1168,8 @@ useGroupedNotifications: "通知をグルーピングして表示する"
signupPendingError: "メールアドレスの確認中に問題が発生しました。リンクの有効期限が切れている可能性があります。"
cwNotationRequired: "「内容を隠す」がオンの場合は注釈の記述が必要です。"
doReaction: "リアクションする"
code: "コード"
reloadRequiredToApplySettings: "設定の反映にはリロードが必要です。"

_announcement:
forExistingUsers: "既存ユーザーのみ"
@@ -2389,3 +2391,17 @@ _externalResourceInstaller:
_themeInstallFailed:
title: "テーマのインストールに失敗しました"
description: "テーマのインストール中に問題が発生しました。もう一度お試しください。エラーの詳細はJavascriptコンソールをご覧ください。"

_dataSaver:
_media:
title: "メディアの読み込み"
description: "画像・動画が自動で読み込まれるのを防止します。隠れている画像・動画はタップすると読み込まれます。"
_avatar:
title: "アイコン画像"
description: "アイコン画像のアニメーションが停止します。アニメーション画像は通常の画像よりファイルサイズが大きいことがあるので、データ通信量をさらに削減できます。"
_urlPreview:
title: "URLプレビューのサムネイル"
description: "URLプレビューのサムネイル画像が読み込まれなくなります。"
_code:
title: "コードハイライト"
description: "MFMなどでコードハイライト記法が使われている場合、タップするまで読み込まれなくなります。コードハイライトではハイライトする言語ごとにその定義ファイルを読み込む必要がありますが、それらが自動で読み込まれなくなるため、通信量の削減が見込めます。"
2 changes: 1 addition & 1 deletion packages/frontend/src/components/MkCode.core.vue
Original file line number Diff line number Diff line change
@@ -62,7 +62,7 @@ watch(() => props.lang, (to) => {
padding: 1em;
margin: .5em 0;
overflow: auto;
border-radius: .3em;
border-radius: 8px;

& pre,
& code {
49 changes: 41 additions & 8 deletions packages/frontend/src/components/MkCode.vue
Original file line number Diff line number Diff line change
@@ -4,25 +4,35 @@ SPDX-License-Identifier: AGPL-3.0-only
-->

<template>
<Suspense>
<template #fallback>
<MkLoading v-if="!inline ?? true" />
</template>
<code v-if="inline" :class="$style.codeInlineRoot">{{ code }}</code>
<XCode v-else :code="code" :lang="lang"/>
</Suspense>
<Suspense>
<template #fallback>
<MkLoading v-if="!inline ?? true"/>
</template>
<code v-if="inline" :class="$style.codeInlineRoot">{{ code }}</code>
<XCode v-else-if="show" :code="code" :lang="lang"/>
<button v-else :class="$style.codePlaceholderRoot" @click="show = true">
<div :class="$style.codePlaceholderContainer">
<div><i class="ti ti-code"></i> {{ i18n.ts.code }}</div>
<div>{{ i18n.ts.clickToShow }}</div>
</div>
</button>
</Suspense>
</template>

<script lang="ts" setup>
import { defineAsyncComponent } from 'vue';
import { defineAsyncComponent, ref } from 'vue';
import MkLoading from '@/components/global/MkLoading.vue';
import { defaultStore } from '@/store.js';
import { i18n } from '@/i18n.js';

defineProps<{
code: string;
lang?: string;
inline?: boolean;
}>();

const show = ref(!defaultStore.state.dataSaver.code);

const XCode = defineAsyncComponent(() => import('@/components/MkCode.core.vue'));
</script>

@@ -36,4 +46,27 @@ const XCode = defineAsyncComponent(() => import('@/components/MkCode.core.vue'))
padding: .1em;
border-radius: .3em;
}

.codePlaceholderRoot {
display: block;
width: 100%;
background: none;
border: none;
outline: none;
font: inherit;
color: inherit;
cursor: pointer;

box-sizing: border-box;
border-radius: 8px;
padding: 24px;
margin-top: 4px;
color: #D4D4D4;
background: #1E1E1E;
}

.codePlaceholderContainer {
text-align: center;
font-size: 0.8em;
}
</style>
8 changes: 4 additions & 4 deletions packages/frontend/src/components/MkMediaImage.vue
Original file line number Diff line number Diff line change
@@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
>
<ImgWithBlurhash
:hash="image.blurhash"
:src="(defaultStore.state.enableDataSaverMode && hide) ? null : url"
:src="(defaultStore.state.dataSaver.media && hide) ? null : url"
:forceBlurhash="hide"
:cover="hide || cover"
:alt="image.comment || image.name"
@@ -32,8 +32,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<template v-if="hide">
<div :class="$style.hiddenText">
<div :class="$style.hiddenTextWrapper">
<b v-if="image.isSensitive" style="display: block;"><i class="ti ti-eye-exclamation"></i> {{ i18n.ts.sensitive }}{{ defaultStore.state.enableDataSaverMode ? ` (${i18n.ts.image}${image.size ? ' ' + bytes(image.size) : ''})` : '' }}</b>
<b v-else style="display: block;"><i class="ti ti-photo"></i> {{ defaultStore.state.enableDataSaverMode && image.size ? bytes(image.size) : i18n.ts.image }}</b>
<b v-if="image.isSensitive" style="display: block;"><i class="ti ti-eye-exclamation"></i> {{ i18n.ts.sensitive }}{{ defaultStore.state.dataSaver.media ? ` (${i18n.ts.image}${image.size ? ' ' + bytes(image.size) : ''})` : '' }}</b>
<b v-else style="display: block;"><i class="ti ti-photo"></i> {{ defaultStore.state.dataSaver.media && image.size ? bytes(image.size) : i18n.ts.image }}</b>
<span v-if="controls" style="display: block;">{{ i18n.ts.clickToShow }}</span>
</div>
</div>
@@ -94,7 +94,7 @@ function onclick() {

// Plugin:register_note_view_interruptor を使って書き換えられる可能性があるためwatchする
watch(() => props.image, () => {
hide = (defaultStore.state.nsfw === 'force' || defaultStore.state.enableDataSaverMode) ? true : (props.image.isSensitive && defaultStore.state.nsfw !== 'ignore');
hide = (defaultStore.state.nsfw === 'force' || defaultStore.state.dataSaver.media) ? true : (props.image.isSensitive && defaultStore.state.nsfw !== 'ignore');
}, {
deep: true,
immediate: true,
6 changes: 3 additions & 3 deletions packages/frontend/src/components/MkMediaVideo.vue
Original file line number Diff line number Diff line change
@@ -7,8 +7,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<div v-if="hide" :class="[$style.hidden, (video.isSensitive && defaultStore.state.highlightSensitiveMedia) && $style.sensitiveContainer]" @click="hide = false">
<!-- 【注意】dataSaverMode が有効になっている際には、hide が false になるまでサムネイルや動画を読み込まないようにすること -->
<div :class="$style.sensitive">
<b v-if="video.isSensitive" style="display: block;"><i class="ti ti-alert-triangle"></i> {{ i18n.ts.sensitive }}{{ defaultStore.state.enableDataSaverMode ? ` (${i18n.ts.video}${video.size ? ' ' + bytes(video.size) : ''})` : '' }}</b>
<b v-else style="display: block;"><i class="ti ti-movie"></i> {{ defaultStore.state.enableDataSaverMode && video.size ? bytes(video.size) : i18n.ts.video }}</b>
<b v-if="video.isSensitive" style="display: block;"><i class="ti ti-alert-triangle"></i> {{ i18n.ts.sensitive }}{{ defaultStore.state.dataSaver.media ? ` (${i18n.ts.video}${video.size ? ' ' + bytes(video.size) : ''})` : '' }}</b>
<b v-else style="display: block;"><i class="ti ti-movie"></i> {{ defaultStore.state.dataSaver.media && video.size ? bytes(video.size) : i18n.ts.video }}</b>
<span>{{ i18n.ts.clickToShow }}</span>
</div>
</div>
@@ -43,7 +43,7 @@ const props = defineProps<{
video: Misskey.entities.DriveFile;
}>();

const hide = ref((defaultStore.state.nsfw === 'force' || defaultStore.state.enableDataSaverMode) ? true : (props.video.isSensitive && defaultStore.state.nsfw !== 'ignore'));
const hide = ref((defaultStore.state.nsfw === 'force' || defaultStore.state.dataSaver.media) ? true : (props.video.isSensitive && defaultStore.state.nsfw !== 'ignore'));

const videoEl = shallowRef<HTMLVideoElement>();

2 changes: 1 addition & 1 deletion packages/frontend/src/components/MkUrlPreview.vue
Original file line number Diff line number Diff line change
@@ -45,7 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<div v-else>
<component :is="self ? 'MkA' : 'a'" :class="[$style.link, { [$style.compact]: compact }]" :[attr]="self ? url.substring(local.length) : url" rel="nofollow noopener" :target="target" :title="url">
<div v-if="thumbnail" :class="$style.thumbnail" :style="defaultStore.state.enableDataSaverMode ? '' : `background-image: url('${thumbnail}')`">
<div v-if="thumbnail" :class="$style.thumbnail" :style="defaultStore.state.dataSaver.urlPreview ? '' : `background-image: url('${thumbnail}')`">
</div>
<article :class="$style.body">
<header :class="$style.header">
2 changes: 1 addition & 1 deletion packages/frontend/src/components/global/MkAvatar.vue
Original file line number Diff line number Diff line change
@@ -83,7 +83,7 @@ const bound = $computed(() => props.link
? { to: userPage(props.user), target: props.target }
: {});

const url = $computed(() => (defaultStore.state.disableShowingAnimatedImages || defaultStore.state.enableDataSaverMode)
const url = $computed(() => (defaultStore.state.disableShowingAnimatedImages || defaultStore.state.dataSaver.avatar)
? getStaticImageUrl(props.user.avatarUrl)
: props.user.avatarUrl);

57 changes: 55 additions & 2 deletions packages/frontend/src/pages/settings/general.vue
Original file line number Diff line number Diff line change
@@ -122,7 +122,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSwitch v-model="useSystemFont">{{ i18n.ts.useSystemFont }}</MkSwitch>
<MkSwitch v-model="disableDrawer">{{ i18n.ts.disableDrawer }}</MkSwitch>
<MkSwitch v-model="forceShowAds">{{ i18n.ts.forceShowAds }}</MkSwitch>
<MkSwitch v-model="enableDataSaverMode">{{ i18n.ts.dataSaver }}</MkSwitch>
</div>
<div>
<MkRadios v-model="emojiStyle">
@@ -165,6 +164,37 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #label>{{ i18n.ts.numberOfPageCache }}</template>
<template #caption>{{ i18n.ts.numberOfPageCacheDescription }}</template>
</MkRange>

<MkFolder>
<template #label>{{ i18n.ts.dataSaver }}</template>

<div class="_gaps_m">
<MkInfo>{{ i18n.ts.reloadRequiredToApplySettings }}</MkInfo>

<div class="_buttons">
<MkButton inline @click="enableAllDataSaver">{{ i18n.ts.enableAll }}</MkButton>
<MkButton inline @click="disableAllDataSaver">{{ i18n.ts.disableAll }}</MkButton>
</div>
<div class="_gaps_m">
<MkSwitch v-model="dataSaver.media">
{{ i18n.ts._dataSaver._media.title }}
<template #caption>{{ i18n.ts._dataSaver._media.description }}</template>
</MkSwitch>
<MkSwitch v-model="dataSaver.avatar">
{{ i18n.ts._dataSaver._avatar.title }}
<template #caption>{{ i18n.ts._dataSaver._avatar.description }}</template>
</MkSwitch>
<MkSwitch v-model="dataSaver.urlPreview">
{{ i18n.ts._dataSaver._urlPreview.title }}
<template #caption>{{ i18n.ts._dataSaver._urlPreview.description }}</template>
</MkSwitch>
<MkSwitch v-model="dataSaver.code">
{{ i18n.ts._dataSaver._code.title }}
<template #caption>{{ i18n.ts._dataSaver._code.description }}</template>
</MkSwitch>
</div>
</div>
</MkFolder>
</div>
</FormSection>

@@ -198,6 +228,7 @@ import MkButton from '@/components/MkButton.vue';
import FormSection from '@/components/form/section.vue';
import FormLink from '@/components/form/link.vue';
import MkLink from '@/components/MkLink.vue';
import MkInfo from '@/components/MkInfo.vue';
import { langs } from '@/config.js';
import { defaultStore } from '@/store.js';
import * as os from '@/os.js';
@@ -211,6 +242,7 @@ import { claimAchievement } from '@/scripts/achievements.js';
const lang = ref(miLocalStorage.getItem('lang'));
const fontSize = ref(miLocalStorage.getItem('fontSize'));
const useSystemFont = ref(miLocalStorage.getItem('useSystemFont') != null);
const dataSaver = ref(defaultStore.state.dataSaver);

async function reloadAsk() {
const { canceled } = await os.confirm({
@@ -241,7 +273,6 @@ const disableShowingAnimatedImages = computed(defaultStore.makeGetterSetter('dis
const forceShowAds = computed(defaultStore.makeGetterSetter('forceShowAds'));
const loadRawImages = computed(defaultStore.makeGetterSetter('loadRawImages'));
const highlightSensitiveMedia = computed(defaultStore.makeGetterSetter('highlightSensitiveMedia'));
const enableDataSaverMode = computed(defaultStore.makeGetterSetter('enableDataSaverMode'));
const imageNewTab = computed(defaultStore.makeGetterSetter('imageNewTab'));
const nsfw = computed(defaultStore.makeGetterSetter('nsfw'));
const showFixedPostForm = computed(defaultStore.makeGetterSetter('showFixedPostForm'));
@@ -374,6 +405,28 @@ function testNotification(): void {
}, 300);
}

function enableAllDataSaver() {
const g = { ...defaultStore.state.dataSaver };

Object.keys(g).forEach((key) => { g[key] = true; });

dataSaver.value = g;
}

function disableAllDataSaver() {
const g = { ...defaultStore.state.dataSaver };

Object.keys(g).forEach((key) => { g[key] = false; });

dataSaver.value = g;
}

watch(dataSaver, (to) => {
defaultStore.set('dataSaver', to);
}, {
deep: true,
});

const headerActions = $computed(() => []);

const headerTabs = $computed(() => []);
Original file line number Diff line number Diff line change
@@ -71,7 +71,7 @@ const defaultStoreSaveKeys: (keyof typeof defaultStore['state'])[] = [
'advancedMfm',
'loadRawImages',
'imageNewTab',
'enableDataSaverMode',
'dataSaver',
'disableShowingAnimatedImages',
'emojiStyle',
'disableDrawer',
13 changes: 9 additions & 4 deletions packages/frontend/src/store.ts
Original file line number Diff line number Diff line change
@@ -223,10 +223,6 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'device',
default: false,
},
enableDataSaverMode: {
where: 'device',
default: false,
},
disableShowingAnimatedImages: {
where: 'device',
default: window.matchMedia('(prefers-reduced-motion)').matches,
@@ -403,6 +399,15 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'device',
default: true,
},
dataSaver: {
where: 'device',
default: {
media: false,
avatar: false,
urlPreview: false,
code: false,
} as Record<string, boolean>,
},

sound_masterVolume: {
where: 'device',
12 changes: 11 additions & 1 deletion packages/frontend/test/init.ts
Original file line number Diff line number Diff line change
@@ -21,7 +21,17 @@ vi.stubGlobal('WebSocket', class WebSocket extends EventTarget { static CLOSING
vi.mock('@/store.js', () => {
return {
defaultStore: {
state: {},
state: {

// なんかtestがうまいこと動かないのでここに書く
dataSaver: {
media: false,
avatar: false,
urlPreview: false,
code: false,
},
Comment on lines +26 to +32
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

vitestが落ちるのでむりやり初期値を書いて回避しました


},
},
};
});

Unchanged files with check annotations Beta

};
export function parse(acct: string): Acct {
if (acct.startsWith('@')) acct = acct.substring(1);

Check warning on line 7 in packages/misskey-js/src/acct.ts

GitHub Actions / lint (misskey-js)

Assignment to function parameter 'acct'
const split = acct.split('@', 2);
return { username: split[0], host: split[1] || null };
}
code: string;
message: string;
kind: 'client' | 'server';
info: Record<string, any>;

Check warning on line 15 in packages/misskey-js/src/api.ts

GitHub Actions / lint (misskey-js)

Unexpected any. Specify a different type
};
export function isAPIError(reason: any): reason is APIError {

Check warning on line 18 in packages/misskey-js/src/api.ts

GitHub Actions / lint (misskey-js)

Unexpected any. Specify a different type
return reason[MK_API_ERROR] === true;
}
headers: { [key in string]: string }
}) => Promise<{
status: number;
json(): Promise<any>;

Check warning on line 30 in packages/misskey-js/src/api.ts

GitHub Actions / lint (misskey-js)

Unexpected any. Specify a different type
}>;
export class APIClient {
type SwitchCase = {
$switch: {
$cases: [any, any][],

Check warning on line 12 in packages/misskey-js/src/api.types.ts

GitHub Actions / lint (misskey-js)

Unexpected any. Specify a different type

Check warning on line 12 in packages/misskey-js/src/api.types.ts

GitHub Actions / lint (misskey-js)

Unexpected any. Specify a different type
$default: any;

Check warning on line 13 in packages/misskey-js/src/api.types.ts

GitHub Actions / lint (misskey-js)

Unexpected any. Specify a different type
};
};
type IsCaseMatched<E extends keyof Endpoints, P extends Endpoints[E]['req'], C extends number> =
Endpoints[E]['res'] extends SwitchCase
? IsNeverType<StrictExtract<Endpoints[E]['res']['$switch']['$cases'][C], [P, any]>> extends false ? true : false

Check warning on line 22 in packages/misskey-js/src/api.types.ts

GitHub Actions / lint (misskey-js)

Unexpected any. Specify a different type
: false
type GetCaseResult<E extends keyof Endpoints, P extends Endpoints[E]['req'], C extends number> =
Endpoints[E]['res'] extends SwitchCase
? StrictExtract<Endpoints[E]['res']['$switch']['$cases'][C], [P, any]>[1]

Check warning on line 27 in packages/misskey-js/src/api.types.ts

GitHub Actions / lint (misskey-js)

Unexpected any. Specify a different type
: never
export type SwitchCaseResponseType<E extends keyof Endpoints, P extends Endpoints[E]['req']> = Endpoints[E]['res'] extends SwitchCase
export type ModerationLogPayloads = {
updateServerSettings: {
before: any | null;

Check warning on line 90 in packages/misskey-js/src/consts.ts

GitHub Actions / lint (misskey-js)

Unexpected any. Specify a different type
after: any | null;
};
suspend: {