feat(client): ページの更新が必要なときに静かに通知する機能を追加

This commit is contained in:
NoriDev 2023-06-05 20:47:49 +09:00
parent 2cc10aa02d
commit d56b50a5f1
13 changed files with 97 additions and 34 deletions

View file

@ -65,6 +65,7 @@
- 노트 사이를 띄우는 옵션 활성화 시 알림 페이지의 노트도 띄우도록
- 안테나, 그룹, 리스트, 클립 페이지의 생성 버튼을 헤더로 이동
- 채팅 디자인 일부 개선
- 페이지 새로 고침 팝업을 조용히 알리는 기능 추가
- Fix: (Friendly) 위젯 영역에 safe-area-inset-bottom이 적용되지 않음
- Fix: (Friendly) 플로팅 메뉴를 길게 눌렀을 때 프로필 이미지를 드래그 할 수 있는 문제
- Fix: 위젯 편집 시 헤더 이외의 영역을 눌렀을 때 위젯 설정이 뜨는 문제

View file

@ -1,5 +1,6 @@
---
_lang_: "English"
requireRefresh: "When the page needs to refresh"
performanceWarning: "High resource usage can result in higher device temperatures and faster battery consumption"
photosensitiveSeizuresWarning: "Can cause photosensitive seizures"
friendlyEnableNotification: "Enable/Disable the notification area"
@ -1099,6 +1100,9 @@ later: "Later"
goToMisskey: "To CherryPick"
additionalEmojiDictionary: "Additional emoji dictionaries"
installed: "Installed"
_requireRefreshBehavior:
dialog: "Show warning dialog"
quiet: "Show unobtrusive alert"
_initialAccountSetting:
accountCreated: "Your account was successfully created!"
letsStartAccountSetup: "For starters, let's set up your profile."

11
locales/index.d.ts vendored
View file

@ -3,6 +3,10 @@
// Do not edit this file directly.
export interface Locale {
"_lang_": string;
"requireRefresh": string;
"performanceWarning": string;
"photosensitiveSeizuresWarning": string;
"friendlyEnableNotification": string;
"useBoldFont": string;
"newNoteReceivedNotification": string;
"disableRightClick": string;
@ -1099,9 +1103,10 @@ export interface Locale {
"goToMisskey": string;
"additionalEmojiDictionary": string;
"installed": string;
"performanceWarning": string;
"photosensitiveSeizuresWarning": string;
"friendlyEnableNotification": string;
"_requireRefreshBehavior": {
"dialog": string;
"quiet": string;
};
"_initialAccountSetting": {
"accountCreated": string;
"letsStartAccountSetup": string;

View file

@ -1,5 +1,6 @@
_lang_: "日本語"
requireRefresh: "페이지 새로 고침이 필요할 때"
performanceWarning: "リソースを多く使用するため、デバイスの温度が高くなり、バッテリーの消耗が速くなる可能性があります"
photosensitiveSeizuresWarning: "光敏感性発作を起こす可能性があります"
friendlyEnableNotification: "通知領域を有効化/無効化"
@ -1100,6 +1101,10 @@ goToMisskey: "CherryPickへ"
additionalEmojiDictionary: "絵文字の追加辞書"
installed: "インストール済み"
_requireRefreshBehavior:
dialog: "ダイアログで通知"
quiet: "控えめに通知"
_initialAccountSetting:
accountCreated: "アカウントの作成が完了しました!"
letsStartAccountSetup: "アカウントの初期設定を行いましょう。"

View file

@ -1,5 +1,6 @@
---
_lang_: "한국어"
requireRefresh: "페이지 새로 고침이 필요할 때"
performanceWarning: "리소스를 많이 사용하므로, 디바이스의 온도가 높아지고 배터리의 소모가 빨라질 수 있어요"
photosensitiveSeizuresWarning: "광과민성 발작을 일으킬 수 있어요"
friendlyEnableNotification: "알림 영역 활성화/비활성화"
@ -1100,6 +1101,9 @@ later: "나중에"
goToMisskey: "CherryPick으로"
additionalEmojiDictionary: "이모지 추가 사전"
installed: "설치됨"
_requireRefreshBehavior:
dialog: "알림창 표시"
quiet: "조용히 알림"
_initialAccountSetting:
accountCreated: "계정 생성이 완료되었어요!"
letsStartAccountSetup: "계정의 초기 설정을 진행해 볼까요?"

View file

@ -71,18 +71,21 @@ import { $i } from '@/account';
import { defaultStore } from '@/store';
import { miLocalStorage } from '@/local-storage';
import { unisonReload } from '@/scripts/unison-reload';
import { eventBus } from '@/scripts/cherrypick/eventBus';
const fontSizeBefore = ref(miLocalStorage.getItem('fontSize'));
const useBoldFont = ref(miLocalStorage.getItem('useBoldFont'));
async function reloadAsk() {
const { canceled } = await os.confirm({
type: 'info',
text: i18n.ts.reloadToApplySetting,
});
if (canceled) return;
if (defaultStore.state.requireRefreshBehavior === 'dialog') {
const { canceled } = await os.confirm({
type: 'info',
text: i18n.ts.reloadToApplySetting,
});
if (canceled) return;
unisonReload();
unisonReload();
} else eventBus.emit('hasRequireRefresh', true);
}
const fontSize = computed(defaultStore.makeGetterSetter('fontSize'));

View file

@ -215,6 +215,11 @@
<option value="quiet">{{ i18n.ts._serverDisconnectedBehavior.quiet }}</option>
<option value="none">{{ i18n.ts._serverDisconnectedBehavior.none }}</option>
</MkSelect>
<MkSelect v-model="requireRefreshBehavior">
<template #label>{{ i18n.ts.requireRefresh }}</template>
<option value="dialog">{{ i18n.ts._requireRefreshBehavior.dialog }}</option>
<option value="quiet">{{ i18n.ts._requireRefreshBehavior.quiet }}</option>
</MkSelect>
<MkSelect v-model="newNoteReceivedNotificationBehavior">
<template #label>{{ i18n.ts.newNoteReceivedNotification }}</template>
<option value="default">{{ i18n.ts._newNoteReceivedNotificationBehavior.default }}</option>
@ -264,6 +269,7 @@ import { unisonReload } from '@/scripts/unison-reload';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import { miLocalStorage } from '@/local-storage';
import { eventBus } from '@/scripts/cherrypick/eventBus';
import MkInfo from '@/components/MkInfo.vue';
const lang = ref(miLocalStorage.getItem('lang'));
@ -274,13 +280,15 @@ const fontSizeBefore = ref(miLocalStorage.getItem('fontSize'));
const useBoldFont = ref(miLocalStorage.getItem('useBoldFont'));
async function reloadAsk() {
const { canceled } = await os.confirm({
type: 'info',
text: i18n.ts.reloadToApplySetting,
});
if (canceled) return;
if (requireRefreshBehavior.value === 'dialog') {
const { canceled } = await os.confirm({
type: 'info',
text: i18n.ts.reloadToApplySetting,
});
if (canceled) return;
unisonReload();
unisonReload();
} else eventBus.emit('hasRequireRefresh', true);
}
const overridedDeviceKind = computed(defaultStore.makeGetterSetter('overridedDeviceKind'));
@ -319,6 +327,7 @@ const postFormVisibilityHotkey = computed(defaultStore.makeGetterSetter('postFor
const newNoteReceivedNotificationBehavior = computed(defaultStore.makeGetterSetter('newNoteReceivedNotificationBehavior'));
const fontSize = computed(defaultStore.makeGetterSetter('fontSize'));
const collapseDefault = computed(defaultStore.makeGetterSetter('collapseDefault'));
const requireRefreshBehavior = computed(defaultStore.makeGetterSetter('requireRefreshBehavior'));
watch(lang, () => {
miLocalStorage.setItem('lang', lang.value as string);

View file

@ -52,6 +52,7 @@ import { defaultStore } from '@/store';
import { unisonReload } from '@/scripts/unison-reload';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import { eventBus } from '@/scripts/cherrypick/eventBus';
import { deepClone } from '@/scripts/clone';
const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
@ -64,13 +65,15 @@ const items = ref(defaultStore.state.menu.map(x => ({
const menuDisplay = computed(defaultStore.makeGetterSetter('menuDisplay'));
async function reloadAsk() {
const { canceled } = await os.confirm({
type: 'info',
text: i18n.ts.reloadToApplySetting,
});
if (canceled) return;
if (defaultStore.state.requireRefreshBehavior === 'dialog') {
const { canceled } = await os.confirm({
type: 'info',
text: i18n.ts.reloadToApplySetting,
});
if (canceled) return;
unisonReload();
unisonReload();
} else eventBus.emit('hasRequireRefresh', true);
}
async function addItem() {

View file

@ -87,6 +87,7 @@ import { signout, $i } from '@/account';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import { unisonReload } from '@/scripts/unison-reload';
import { eventBus } from '@/scripts/cherrypick/eventBus';
import FormSection from '@/components/form/section.vue';
const reportError = computed(defaultStore.makeGetterSetter('reportError'));
@ -128,13 +129,15 @@ async function deleteAccount() {
}
async function reloadAsk() {
const { canceled } = await os.confirm({
type: 'info',
text: i18n.ts.reloadToApplySetting,
});
if (canceled) return;
if (defaultStore.state.requireRefreshBehavior === 'dialog') {
const { canceled } = await os.confirm({
type: 'info',
text: i18n.ts.reloadToApplySetting,
});
if (canceled) return;
unisonReload();
unisonReload();
} else eventBus.emit('hasRequireRefresh', true);
}
watch([

View file

@ -91,6 +91,7 @@ const defaultStoreSaveKeys: (keyof typeof defaultStore['state'])[] = [
'postFormVisibilityHotkey',
'newNoteReceivedNotificationBehavior',
'collapseDefault',
'requireRefreshBehavior',
];
const coldDeviceStorageSaveKeys: (keyof typeof ColdDeviceStorage.default)[] = [
'lightTheme',

View file

@ -156,13 +156,15 @@ function focus(): void {
}
async function reloadAsk() {
const { canceled } = await os.confirm({
type: 'info',
text: i18n.ts.reloadToApplySetting,
});
if (canceled) return;
if (defaultStore.state.requireRefreshBehavior === 'dialog') {
const { canceled } = await os.confirm({
type: 'info',
text: i18n.ts.reloadToApplySetting,
});
if (canceled) return;
unisonReload();
unisonReload();
} else eventBus.emit('hasRequireRefresh', true);
}
const headerActions = $computed(() => [{

View file

@ -367,6 +367,10 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'account',
default: true,
},
requireRefreshBehavior: {
where: 'device',
default: 'dialog' as 'quiet' | 'dialog',
},
}));
// TODO: 他のタブと永続化されたstateを同期

View file

@ -1,4 +1,11 @@
<template>
<div v-if="hasRequireRefresh && defaultStore.state.requireRefreshBehavior === 'quiet'" :class="$style.root" class="_panel _shadow" @click="resetRequireRefresh">
<div><i class="ti ti-alert-circle"></i> {{ i18n.ts.reloadToApplySetting2 }}</div>
<div :class="$style.command" class="_buttons">
<MkButton small primary @click="reload">{{ i18n.ts.reload }}</MkButton>
<MkButton small>{{ i18n.ts.doNothing }}</MkButton>
</div>
</div>
<div v-if="hasDisconnected && defaultStore.state.serverDisconnectedBehavior === 'quiet'" :class="$style.root" class="_panel _shadow" @click="resetDisconnected">
<div><i class="ti ti-alert-triangle"></i> {{ i18n.ts.disconnectedFromServer }}</div>
<div :class="$style.command" class="_buttons">
@ -9,15 +16,17 @@
</template>
<script lang="ts" setup>
import { onUnmounted } from 'vue';
import { onMounted, onUnmounted } from 'vue';
import { useStream } from '@/stream';
import { i18n } from '@/i18n';
import MkButton from '@/components/MkButton.vue';
import * as os from '@/os';
import { defaultStore } from '@/store';
import { eventBus } from '@/scripts/cherrypick/eventBus';
const zIndex = os.claimZIndex('high');
let hasRequireRefresh = $ref(false);
let hasDisconnected = $ref(false);
function onDisconnected() {
@ -28,12 +37,22 @@ function resetDisconnected() {
hasDisconnected = false;
}
function resetRequireRefresh() {
hasRequireRefresh = false;
}
function reload() {
location.reload();
}
useStream().on('_disconnected_', onDisconnected);
onMounted(() => {
eventBus.on('hasRequireRefresh', (hasRequireRefresh_receive) => {
hasRequireRefresh = hasRequireRefresh_receive;
});
});
onUnmounted(() => {
useStream().off('_disconnected_', onDisconnected);
});