1
1
mirror of https://github.com/kokonect-link/cherrypick synced 2024-11-23 22:56:53 +09:00

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) 위젯 영역에 safe-area-inset-bottom이 적용되지 않음
- Fix: (Friendly) 플로팅 메뉴를 길게 눌렀을 때 프로필 이미지를 드래그 할 수 있는 문제 - Fix: (Friendly) 플로팅 메뉴를 길게 눌렀을 때 프로필 이미지를 드래그 할 수 있는 문제
- Fix: 위젯 편집 시 헤더 이외의 영역을 눌렀을 때 위젯 설정이 뜨는 문제 - Fix: 위젯 편집 시 헤더 이외의 영역을 눌렀을 때 위젯 설정이 뜨는 문제

View File

@ -1,5 +1,6 @@
--- ---
_lang_: "English" _lang_: "English"
requireRefresh: "When the page needs to refresh"
performanceWarning: "High resource usage can result in higher device temperatures and faster battery consumption" performanceWarning: "High resource usage can result in higher device temperatures and faster battery consumption"
photosensitiveSeizuresWarning: "Can cause photosensitive seizures" photosensitiveSeizuresWarning: "Can cause photosensitive seizures"
friendlyEnableNotification: "Enable/Disable the notification area" friendlyEnableNotification: "Enable/Disable the notification area"
@ -1099,6 +1100,9 @@ later: "Later"
goToMisskey: "To CherryPick" goToMisskey: "To CherryPick"
additionalEmojiDictionary: "Additional emoji dictionaries" additionalEmojiDictionary: "Additional emoji dictionaries"
installed: "Installed" installed: "Installed"
_requireRefreshBehavior:
dialog: "Show warning dialog"
quiet: "Show unobtrusive alert"
_initialAccountSetting: _initialAccountSetting:
accountCreated: "Your account was successfully created!" accountCreated: "Your account was successfully created!"
letsStartAccountSetup: "For starters, let's set up your profile." 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. // Do not edit this file directly.
export interface Locale { export interface Locale {
"_lang_": string; "_lang_": string;
"requireRefresh": string;
"performanceWarning": string;
"photosensitiveSeizuresWarning": string;
"friendlyEnableNotification": string;
"useBoldFont": string; "useBoldFont": string;
"newNoteReceivedNotification": string; "newNoteReceivedNotification": string;
"disableRightClick": string; "disableRightClick": string;
@ -1099,9 +1103,10 @@ export interface Locale {
"goToMisskey": string; "goToMisskey": string;
"additionalEmojiDictionary": string; "additionalEmojiDictionary": string;
"installed": string; "installed": string;
"performanceWarning": string; "_requireRefreshBehavior": {
"photosensitiveSeizuresWarning": string; "dialog": string;
"friendlyEnableNotification": string; "quiet": string;
};
"_initialAccountSetting": { "_initialAccountSetting": {
"accountCreated": string; "accountCreated": string;
"letsStartAccountSetup": string; "letsStartAccountSetup": string;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,11 @@
<template> <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 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><i class="ti ti-alert-triangle"></i> {{ i18n.ts.disconnectedFromServer }}</div>
<div :class="$style.command" class="_buttons"> <div :class="$style.command" class="_buttons">
@ -9,15 +16,17 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onUnmounted } from 'vue'; import { onMounted, onUnmounted } from 'vue';
import { useStream } from '@/stream'; import { useStream } from '@/stream';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import * as os from '@/os'; import * as os from '@/os';
import { defaultStore } from '@/store'; import { defaultStore } from '@/store';
import { eventBus } from '@/scripts/cherrypick/eventBus';
const zIndex = os.claimZIndex('high'); const zIndex = os.claimZIndex('high');
let hasRequireRefresh = $ref(false);
let hasDisconnected = $ref(false); let hasDisconnected = $ref(false);
function onDisconnected() { function onDisconnected() {
@ -28,12 +37,22 @@ function resetDisconnected() {
hasDisconnected = false; hasDisconnected = false;
} }
function resetRequireRefresh() {
hasRequireRefresh = false;
}
function reload() { function reload() {
location.reload(); location.reload();
} }
useStream().on('_disconnected_', onDisconnected); useStream().on('_disconnected_', onDisconnected);
onMounted(() => {
eventBus.on('hasRequireRefresh', (hasRequireRefresh_receive) => {
hasRequireRefresh = hasRequireRefresh_receive;
});
});
onUnmounted(() => { onUnmounted(() => {
useStream().off('_disconnected_', onDisconnected); useStream().off('_disconnected_', onDisconnected);
}); });