feat: Show a warning for 10 seconds when disconnected from the server and then automatically hide, Show disconnected in header when disconnected from server

This commit is contained in:
NoriDev 2022-09-08 23:54:11 +09:00
parent c59640202c
commit b8b9d36d63
4 changed files with 126 additions and 15 deletions

View file

@ -13,6 +13,8 @@
### Improvements
- 클라이언트: (friendly) 모바일 환경에서 서버와 연결이 끊어졌을 때 표시되는 경고창의 UI 개선
- 클라이언트: (friendly) 모바일 환경에서 스크롤을 내리면 플로팅 버튼이 감춰지도록 변경
- 클라이언트: (friendly) 서버와 연결이 끊어졌을 때 10초간 경고를 표시한 후 자동으로 숨기기
- 클라이언트: (friendly) 서버와 연결이 끊어졌을 때 헤더에 연결 끊김 표시
- 클라이언트: Google Translate 서비스 추가 (thanks to @ltlapy)
- 클라이언트: DeepL과 Google Translate를 선택할 수 있는 옵션 추가
- 클라이언트: Enter 키를 눌러 보내기 옵션 추가

View file

@ -1,5 +1,8 @@
<template>
<div v-if="show" ref="el" class="fdidabkb" :class="{ slim: narrow, thin: thin_ }" :style="{ background: bg }" @click="onClick">
<div v-if="hasDisconnected && isMobile && narrow && hasTabs" class="buttons left">
<button v-tooltip.noDelay="i18n.ts.reload" class="_button button disconnected" @click.stop="reload" @touchstart="preventDrag"><i class="fa-solid fa-bolt"></i></button>
</div>
<template v-if="metadata">
<div v-if="!hideTitle" class="titleContainer" @click="showTabsPopup">
<MkAvatar v-if="metadata.avatar" class="avatar" :user="metadata.avatar" :disable-preview="true" :show-indicator="true"/>
@ -29,6 +32,7 @@
<template v-for="action in actions">
<button v-tooltip.noDelay="action.text" class="_button button" :class="{ highlighted: action.highlighted }" @click.stop="action.handler" @touchstart="preventDrag"><i :class="action.icon"></i></button>
</template>
<button v-if="hasDisconnected && !isMobile && (!narrow || hideTitle)" v-tooltip.noDelay="i18n.ts.reload" class="_button button disconnected disconnected-right" @click="reload" @touchstart="preventDrag"><i class="fa-solid fa-bolt"></i></button>
</div>
</div>
</template>
@ -41,6 +45,15 @@ import { scrollToTop } from '@/scripts/scroll';
import { i18n } from '@/i18n';
import { globalEvents } from '@/events';
import { injectPageMetadata } from '@/scripts/page-metadata';
import { deviceKind } from '@/scripts/device-kind';
import { stream } from '@/stream';
const MOBILE_THRESHOLD = 500;
const isMobile = ref(deviceKind === 'smartphone' || window.innerWidth <= MOBILE_THRESHOLD);
window.addEventListener('resize', () => {
isMobile.value = deviceKind === 'smartphone' || window.innerWidth <= MOBILE_THRESHOLD;
});
type Tab = {
key?: string | null;
@ -124,6 +137,10 @@ function onTabClick(tab: Tab, ev: MouseEvent): void {
}
}
function reload() {
location.reload();
}
const calcBg = () => {
const rawBg = metadata?.bg || 'var(--bg)';
const tinyBg = tinycolor(rawBg.startsWith('var(') ? getComputedStyle(document.documentElement).getPropertyValue(rawBg.slice(4, -1)) : rawBg);
@ -131,6 +148,13 @@ const calcBg = () => {
bg.value = tinyBg.toRgbString();
};
let hasDisconnected = $ref(false);
function onDisconnected() {
hasDisconnected = true;
}
stream.on('_disconnected_', onDisconnected);
let ro: ResizeObserver | null;
onMounted(() => {
@ -167,6 +191,8 @@ onMounted(() => {
onUnmounted(() => {
globalEvents.off('themeChanged', calcBg);
if (ro) ro.disconnect();
stream.off('_disconnected_', onDisconnected);
});
</script>
@ -216,12 +242,28 @@ onUnmounted(() => {
height: var(--height);
margin: 0 var(--margin);
&.right {
margin-left: auto;
&.left {
margin-right: auto;
> .disconnected {
transition: opacity 1s, transform 1s;
}
}
&:empty {
width: var(--height);
&.right {
margin-left: auto;
&:empty {
width: var(--height);
}
> .disconnected {
transition: opacity 1s, transform 1s;
}
> .disconnected-right {
margin-left: 15px;
}
}
> .button {

View file

@ -1,5 +1,8 @@
<template>
<div v-if="show" ref="el" class="fdidabkb" :class="{ slim: narrow, thin: thin_ }" :style="{ background: bg }" @click="onClick">
<div v-if="hasDisconnected && isMobile && narrow && hasTabs" class="buttons left">
<button v-tooltip.noDelay="i18n.ts.reload" class="_button button disconnected" @click.stop="reload" @touchstart="preventDrag"><i class="fa-solid fa-bolt"></i></button>
</div>
<template v-if="metadata">
<div v-if="!hideTitle" class="titleContainer" @click="showTabsPopup">
<MkAvatar v-if="metadata.avatar" class="avatar" :user="metadata.avatar" :disable-preview="true" :show-indicator="true"/>
@ -29,6 +32,7 @@
<template v-for="action in actions">
<button v-tooltip.noDelay="action.text" class="_button button" :class="{ highlighted: action.highlighted }" @click.stop="action.handler" @touchstart="preventDrag"><i :class="action.icon"></i></button>
</template>
<button v-if="hasDisconnected && !isMobile && (!narrow || hideTitle)" v-tooltip.noDelay="i18n.ts.reload" class="_button button disconnected disconnected-right" @click="reload" @touchstart="preventDrag"><i class="fa-solid fa-bolt"></i></button>
</div>
</div>
</template>
@ -41,6 +45,15 @@ import { scrollToTop } from '@/scripts/scroll';
import { i18n } from '@/i18n';
import { globalEvents } from '@/events';
import { injectPageMetadata } from '@/scripts/page-metadata';
import { stream } from '@/stream';
import { deviceKind } from '@/scripts/device-kind';
const MOBILE_THRESHOLD = 500;
const isMobile = ref(deviceKind === 'smartphone' || window.innerWidth <= MOBILE_THRESHOLD);
window.addEventListener('resize', () => {
isMobile.value = deviceKind === 'smartphone' || window.innerWidth <= MOBILE_THRESHOLD;
});
type Tab = {
key?: string | null;
@ -124,6 +137,10 @@ function onTabClick(tab: Tab, ev: MouseEvent): void {
}
}
function reload() {
location.reload();
}
const calcBg = () => {
const rawBg = metadata?.bg || 'var(--bg)';
const tinyBg = tinycolor(rawBg.startsWith('var(') ? getComputedStyle(document.documentElement).getPropertyValue(rawBg.slice(4, -1)) : rawBg);
@ -131,6 +148,13 @@ const calcBg = () => {
bg.value = tinyBg.toRgbString();
};
let hasDisconnected = $ref(false);
function onDisconnected() {
hasDisconnected = true;
}
stream.on('_disconnected_', onDisconnected);
let ro: ResizeObserver | null;
onMounted(() => {
@ -167,6 +191,8 @@ onMounted(() => {
onUnmounted(() => {
globalEvents.off('themeChanged', calcBg);
if (ro) ro.disconnect();
stream.off('_disconnected_', onDisconnected);
});
</script>
@ -216,12 +242,28 @@ onUnmounted(() => {
height: var(--height);
margin: 0 var(--margin);
&.right {
margin-left: auto;
&.left {
margin-right: auto;
> .disconnected {
transition: opacity 1s, transform 1s;
}
}
&:empty {
width: var(--height);
&.right {
margin-left: auto;
&:empty {
width: var(--height);
}
> .disconnected {
transition: opacity 1s, transform 1s;
}
> .disconnected-right {
margin-left: 15px;
}
}
> .button {

View file

@ -1,28 +1,44 @@
<template>
<div v-if="hasDisconnected && $store.state.serverDisconnectedBehavior === 'quiet'" class="nsbbhtug" :class="{ friendly: isFriendly }" @click="resetDisconnected">
<div class="text">{{ i18n.ts.disconnectedFromServer }}</div>
<div class="command">
<button class="_textButton" @click="reload">{{ i18n.ts.reload }}</button>
<button class="_textButton">{{ i18n.ts.doNothing }}</button>
<transition v-if="showing && hasDisconnected && $store.state.serverDisconnectedBehavior === 'quiet'" :name="$store.state.animation && isFriendly ? 'friendly' : ''" appear>
<div class="nsbbhtug" :class="{ friendly: isFriendly }" @click="resetDisconnected">
<div class="text">{{ i18n.ts.disconnectedFromServer }}</div>
<div class="command">
<button class="_textButton" @click="reload">{{ i18n.ts.reload }}</button>
<button class="_textButton">{{ i18n.ts.doNothing }}</button>
</div>
</div>
</div>
</transition>
</template>
<script lang="ts" setup>
import { onUnmounted, ref } from 'vue';
import { onMounted, onUnmounted, ref } from 'vue';
import { stream } from '@/stream';
import { i18n } from '@/i18n';
const isFriendly = $ref(localStorage.getItem('ui') === 'friendly');
let showing = $ref(true);
let hasDisconnected = $ref(false);
let currentTimeout = $ref(0);
function timeout() {
currentTimeout = window.setTimeout(() => {
showing = !isFriendly;
}, 10000);
}
function clearTimeout() {
window.clearTimeout(currentTimeout);
}
function onDisconnected() {
hasDisconnected = true;
timeout();
}
function resetDisconnected() {
hasDisconnected = false;
clearTimeout();
}
function reload() {
@ -37,6 +53,14 @@ onUnmounted(() => {
</script>
<style lang="scss" scoped>
.friendly-enter-active, .friendly-leave-active {
// transition: opacity 0.3s, transform 0.3s !important;
}
.friendly-enter-from, .friendly-leave-to {
opacity: 0;
transform: translateY(-250px);
}
.nsbbhtug {
position: fixed;
z-index: 16385;
@ -60,6 +84,7 @@ onUnmounted(() => {
bottom: initial;
right: initial;
border-radius: initial;
transition: opacity 0.5s, transform 0.5s;
> .text {
padding: 0.7em;