From 5e95a1f7af841f10646133ad0cc155a2c5cea9fd Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Sun, 26 Jun 2022 03:12:58 +0900 Subject: [PATCH] refactor(client): extract interval logic to a composable function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit あと`onUnmounted`を`onMounted`内で呼んでいたりしたのを修正したりとか --- packages/client/src/components/form/input.vue | 73 ++++++++++--------- packages/client/src/components/form/range.vue | 24 +++--- .../client/src/components/form/select.vue | 57 ++++++++------- .../client/src/components/global/time.vue | 18 ++--- packages/client/src/components/mini-chart.vue | 8 +- .../client/src/components/notification.vue | 17 +++-- .../client/src/components/notifications.vue | 10 ++- packages/client/src/components/poll.vue | 14 ++-- packages/client/src/components/sparkle.vue | 17 +++-- .../src/pages/admin/overview.federation.vue | 13 +--- packages/client/src/scripts/use-interval.ts | 22 ++++++ packages/client/src/widgets/aichan.vue | 31 ++++---- packages/client/src/widgets/calendar.vue | 25 +++---- packages/client/src/widgets/federation.vue | 10 +-- packages/client/src/widgets/online-users.vue | 12 ++- packages/client/src/widgets/rss.vue | 12 ++- packages/client/src/widgets/slideshow.vue | 15 ++-- packages/client/src/widgets/trends.vue | 12 ++- 18 files changed, 207 insertions(+), 183 deletions(-) create mode 100644 packages/client/src/scripts/use-interval.ts diff --git a/packages/client/src/components/form/input.vue b/packages/client/src/components/form/input.vue index 7165671af3..5065e28892 100644 --- a/packages/client/src/components/form/input.vue +++ b/packages/client/src/components/form/input.vue @@ -3,7 +3,8 @@ <div class="label" @click="focus"><slot name="label"></slot></div> <div class="input" :class="{ inline, disabled, focused }"> <div ref="prefixEl" class="prefix"><slot name="prefix"></slot></div> - <input ref="inputEl" + <input + ref="inputEl" v-model="v" v-adaptive-border :type="type" @@ -34,8 +35,9 @@ <script lang="ts"> import { defineComponent, onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs } from 'vue'; -import MkButton from '@/components/ui/button.vue'; import { debounce } from 'throttle-debounce'; +import MkButton from '@/components/ui/button.vue'; +import { useInterval } from '@/scripts/use-interval'; export default defineComponent({ components: { @@ -44,45 +46,45 @@ export default defineComponent({ props: { modelValue: { - required: true + required: true, }, type: { type: String, - required: false + required: false, }, required: { type: Boolean, - required: false + required: false, }, readonly: { type: Boolean, - required: false + required: false, }, disabled: { type: Boolean, - required: false + required: false, }, pattern: { type: String, - required: false + required: false, }, placeholder: { type: String, - required: false + required: false, }, autofocus: { type: Boolean, required: false, - default: false + default: false, }, autocomplete: { - required: false + required: false, }, spellcheck: { - required: false + required: false, }, step: { - required: false + required: false, }, datalist: { type: Array, @@ -91,17 +93,17 @@ export default defineComponent({ inline: { type: Boolean, required: false, - default: false + default: false, }, debounce: { type: Boolean, required: false, - default: false + default: false, }, manualSave: { type: Boolean, required: false, - default: false + default: false, }, }, @@ -134,7 +136,7 @@ export default defineComponent({ const updated = () => { changed.value = false; - if (type?.value === 'number') { + if (type.value === 'number') { context.emit('update:modelValue', parseFloat(v.value)); } else { context.emit('update:modelValue', v.value); @@ -159,30 +161,29 @@ export default defineComponent({ invalid.value = inputEl.value.validity.badInput; }); + // このコンポーネントが作成された時、非表示状態である場合がある + // 非表示状態だと要素の幅などは0になってしまうので、定期的に計算する + useInterval(() => { + if (prefixEl.value) { + if (prefixEl.value.offsetWidth) { + inputEl.value.style.paddingLeft = prefixEl.value.offsetWidth + 'px'; + } + } + if (suffixEl.value) { + if (suffixEl.value.offsetWidth) { + inputEl.value.style.paddingRight = suffixEl.value.offsetWidth + 'px'; + } + } + }, 100, { + immediate: true, + afterMounted: true, + }); + onMounted(() => { nextTick(() => { if (autofocus.value) { focus(); } - - // このコンポーネントが作成された時、非表示状態である場合がある - // 非表示状態だと要素の幅などは0になってしまうので、定期的に計算する - const clock = window.setInterval(() => { - if (prefixEl.value) { - if (prefixEl.value.offsetWidth) { - inputEl.value.style.paddingLeft = prefixEl.value.offsetWidth + 'px'; - } - } - if (suffixEl.value) { - if (suffixEl.value.offsetWidth) { - inputEl.value.style.paddingRight = suffixEl.value.offsetWidth + 'px'; - } - } - }, 100); - - onUnmounted(() => { - window.clearInterval(clock); - }); }); }); diff --git a/packages/client/src/components/form/range.vue b/packages/client/src/components/form/range.vue index 9bf7651119..221ad029a7 100644 --- a/packages/client/src/components/form/range.vue +++ b/packages/client/src/components/form/range.vue @@ -24,31 +24,31 @@ export default defineComponent({ modelValue: { type: Number, required: false, - default: 0 + default: 0, }, disabled: { type: Boolean, required: false, - default: false + default: false, }, min: { type: Number, required: false, - default: 0 + default: 0, }, max: { type: Number, required: false, - default: 100 + default: 100, }, step: { type: Number, required: false, - default: 1 + default: 1, }, autofocus: { type: Boolean, - required: false + required: false, }, textConverter: { type: Function, @@ -90,14 +90,18 @@ export default defineComponent({ } }; watch([steppedValue, containerEl], calcThumbPosition); + + let ro: ResizeObserver | undefined; + onMounted(() => { - const ro = new ResizeObserver((entries, observer) => { + ro = new ResizeObserver((entries, observer) => { calcThumbPosition(); }); ro.observe(containerEl.value); - onUnmounted(() => { - ro.disconnect(); - }); + }); + + onUnmounted(() => { + if (ro) ro.disconnect(); }); const steps = computed(() => { diff --git a/packages/client/src/components/form/select.vue b/packages/client/src/components/form/select.vue index 87196027a8..7f5f8784b6 100644 --- a/packages/client/src/components/form/select.vue +++ b/packages/client/src/components/form/select.vue @@ -3,7 +3,8 @@ <div class="label" @click="focus"><slot name="label"></slot></div> <div ref="container" class="input" :class="{ inline, disabled, focused }" @click.prevent="onClick"> <div ref="prefixEl" class="prefix"><slot name="prefix"></slot></div> - <select ref="inputEl" + <select + ref="inputEl" v-model="v" v-adaptive-border class="select" @@ -29,6 +30,7 @@ import { defineComponent, onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs, VNode } from 'vue'; import MkButton from '@/components/ui/button.vue'; import * as os from '@/os'; +import { useInterval } from '@/scripts/use-interval'; export default defineComponent({ components: { @@ -37,38 +39,38 @@ export default defineComponent({ props: { modelValue: { - required: true + required: true, }, required: { type: Boolean, - required: false + required: false, }, readonly: { type: Boolean, - required: false + required: false, }, disabled: { type: Boolean, - required: false + required: false, }, placeholder: { type: String, - required: false + required: false, }, autofocus: { type: Boolean, required: false, - default: false + default: false, }, inline: { type: Boolean, required: false, - default: false + default: false, }, manualSave: { type: Boolean, required: false, - default: false + default: false, }, }, @@ -109,30 +111,29 @@ export default defineComponent({ invalid.value = inputEl.value.validity.badInput; }); + // このコンポーネントが作成された時、非表示状態である場合がある + // 非表示状態だと要素の幅などは0になってしまうので、定期的に計算する + useInterval(() => { + if (prefixEl.value) { + if (prefixEl.value.offsetWidth) { + inputEl.value.style.paddingLeft = prefixEl.value.offsetWidth + 'px'; + } + } + if (suffixEl.value) { + if (suffixEl.value.offsetWidth) { + inputEl.value.style.paddingRight = suffixEl.value.offsetWidth + 'px'; + } + } + }, 100, { + immediate: true, + afterMounted: true, + }); + onMounted(() => { nextTick(() => { if (autofocus.value) { focus(); } - - // このコンポーネントが作成された時、非表示状態である場合がある - // 非表示状態だと要素の幅などは0になってしまうので、定期的に計算する - const clock = window.setInterval(() => { - if (prefixEl.value) { - if (prefixEl.value.offsetWidth) { - inputEl.value.style.paddingLeft = prefixEl.value.offsetWidth + 'px'; - } - } - if (suffixEl.value) { - if (suffixEl.value.offsetWidth) { - inputEl.value.style.paddingRight = suffixEl.value.offsetWidth + 'px'; - } - } - }, 100); - - onUnmounted(() => { - window.clearInterval(clock); - }); }); }); diff --git a/packages/client/src/components/global/time.vue b/packages/client/src/components/global/time.vue index a7f142f961..801490225b 100644 --- a/packages/client/src/components/global/time.vue +++ b/packages/client/src/components/global/time.vue @@ -24,14 +24,14 @@ let now = $ref(new Date()); const relative = $computed(() => { const ago = (now.getTime() - _time.getTime()) / 1000/*ms*/; return ( - ago >= 31536000 ? i18n.t('_ago.yearsAgo', { n: Math.round(ago / 31536000).toString() }) : - ago >= 2592000 ? i18n.t('_ago.monthsAgo', { n: Math.round(ago / 2592000).toString() }) : - ago >= 604800 ? i18n.t('_ago.weeksAgo', { n: Math.round(ago / 604800).toString() }) : - ago >= 86400 ? i18n.t('_ago.daysAgo', { n: Math.round(ago / 86400).toString() }) : - ago >= 3600 ? i18n.t('_ago.hoursAgo', { n: Math.round(ago / 3600).toString() }) : - ago >= 60 ? i18n.t('_ago.minutesAgo', { n: (~~(ago / 60)).toString() }) : - ago >= 10 ? i18n.t('_ago.secondsAgo', { n: (~~(ago % 60)).toString() }) : - ago >= -1 ? i18n.ts._ago.justNow : + ago >= 31536000 ? i18n.t('_ago.yearsAgo', { n: Math.round(ago / 31536000).toString() }) : + ago >= 2592000 ? i18n.t('_ago.monthsAgo', { n: Math.round(ago / 2592000).toString() }) : + ago >= 604800 ? i18n.t('_ago.weeksAgo', { n: Math.round(ago / 604800).toString() }) : + ago >= 86400 ? i18n.t('_ago.daysAgo', { n: Math.round(ago / 86400).toString() }) : + ago >= 3600 ? i18n.t('_ago.hoursAgo', { n: Math.round(ago / 3600).toString() }) : + ago >= 60 ? i18n.t('_ago.minutesAgo', { n: (~~(ago / 60)).toString() }) : + ago >= 10 ? i18n.t('_ago.secondsAgo', { n: (~~(ago % 60)).toString() }) : + ago >= -1 ? i18n.ts._ago.justNow : i18n.ts._ago.future); }); @@ -50,7 +50,7 @@ if (props.mode === 'relative' || props.mode === 'detail') { tickId = window.requestAnimationFrame(tick); onUnmounted(() => { - window.clearTimeout(tickId); + window.cancelAnimationFrame(tickId); }); } </script> diff --git a/packages/client/src/components/mini-chart.vue b/packages/client/src/components/mini-chart.vue index 5e842b1975..c64ce163f9 100644 --- a/packages/client/src/components/mini-chart.vue +++ b/packages/client/src/components/mini-chart.vue @@ -29,6 +29,7 @@ import { onUnmounted, watch } from 'vue'; import { v4 as uuid } from 'uuid'; import tinycolor from 'tinycolor2'; +import { useInterval } from '@/scripts/use-interval'; const props = defineProps<{ src: number[]; @@ -65,9 +66,8 @@ function draw(): void { watch(() => props.src, draw, { immediate: true }); // Vueが何故かWatchを発動させない場合があるので -clock = window.setInterval(draw, 1000); - -onUnmounted(() => { - window.clearInterval(clock); +useInterval(draw, 1000, { + immediate: false, + afterMounted: true, }); </script> diff --git a/packages/client/src/components/notification.vue b/packages/client/src/components/notification.vue index cbfd809f37..26fbeecb68 100644 --- a/packages/client/src/components/notification.vue +++ b/packages/client/src/components/notification.vue @@ -112,9 +112,12 @@ export default defineComponent({ const elRef = ref<HTMLElement>(null); const reactionRef = ref(null); + let readObserver: IntersectionObserver | undefined; + let connection; + onMounted(() => { if (!props.notification.isRead) { - const readObserver = new IntersectionObserver((entries, observer) => { + readObserver = new IntersectionObserver((entries, observer) => { if (!entries.some(entry => entry.isIntersecting)) return; stream.send('readNotification', { id: props.notification.id, @@ -124,19 +127,19 @@ export default defineComponent({ readObserver.observe(elRef.value); - const connection = stream.useChannel('main'); + connection = stream.useChannel('main'); connection.on('readAllNotifications', () => readObserver.disconnect()); watch(props.notification.isRead, () => { readObserver.disconnect(); }); - - onUnmounted(() => { - readObserver.disconnect(); - connection.dispose(); - }); } }); + + onUnmounted(() => { + if (readObserver) readObserver.disconnect(); + if (connection) connection.dispose(); + }); const followRequestDone = ref(false); const groupInviteDone = ref(false); diff --git a/packages/client/src/components/notifications.vue b/packages/client/src/components/notifications.vue index 8eb569c369..eb19ad488c 100644 --- a/packages/client/src/components/notifications.vue +++ b/packages/client/src/components/notifications.vue @@ -60,8 +60,10 @@ const onNotification = (notification) => { } }; +let connection; + onMounted(() => { - const connection = stream.useChannel('main'); + connection = stream.useChannel('main'); connection.on('notification', onNotification); connection.on('readAllNotifications', () => { if (pagingComponent.value) { @@ -87,10 +89,10 @@ onMounted(() => { } } }); +}); - onUnmounted(() => { - connection.dispose(); - }); +onUnmounted(() => { + if (connection) connection.dispose(); }); </script> diff --git a/packages/client/src/components/poll.vue b/packages/client/src/components/poll.vue index d9ef5970cb..35f87325d8 100644 --- a/packages/client/src/components/poll.vue +++ b/packages/client/src/components/poll.vue @@ -27,18 +27,19 @@ import { sum } from '@/scripts/array'; import { pleaseLogin } from '@/scripts/please-login'; import * as os from '@/os'; import { i18n } from '@/i18n'; +import { useInterval } from '@/scripts/use-interval'; export default defineComponent({ props: { note: { type: Object, - required: true + required: true, }, readOnly: { type: Boolean, required: false, default: false, - } + }, }, setup(props) { @@ -54,7 +55,7 @@ export default defineComponent({ s: Math.floor(remaining.value % 60), m: Math.floor(remaining.value / 60) % 60, h: Math.floor(remaining.value / 3600) % 24, - d: Math.floor(remaining.value / 86400) + d: Math.floor(remaining.value / 86400), })); const showResult = ref(props.readOnly || isVoted.value); @@ -68,10 +69,9 @@ export default defineComponent({ } }; - tick(); - const intevalId = window.setInterval(tick, 3000); - onUnmounted(() => { - window.clearInterval(intevalId); + useInterval(tick, 3000, { + immediate: true, + afterMounted: false, }); } diff --git a/packages/client/src/components/sparkle.vue b/packages/client/src/components/sparkle.vue index f52e5a3f9b..b52dbe31c4 100644 --- a/packages/client/src/components/sparkle.vue +++ b/packages/client/src/components/sparkle.vue @@ -33,7 +33,8 @@ </svg> --> <svg v-for="particle in particles" :key="particle.id" :width="width" :height="height" :viewBox="`0 0 ${width} ${height}`" xmlns="http://www.w3.org/2000/svg"> - <path style="transform-origin: center; transform-box: fill-box;" + <path + style="transform-origin: center; transform-box: fill-box;" :transform="`translate(${particle.x} ${particle.y})`" :fill="particle.color" d="M29.427,2.011C29.721,0.83 30.782,0 32,0C33.218,0 34.279,0.83 34.573,2.011L39.455,21.646C39.629,22.347 39.991,22.987 40.502,23.498C41.013,24.009 41.653,24.371 42.354,24.545L61.989,29.427C63.17,29.721 64,30.782 64,32C64,33.218 63.17,34.279 61.989,34.573L42.354,39.455C41.653,39.629 41.013,39.991 40.502,40.502C39.991,41.013 39.629,41.653 39.455,42.354L34.573,61.989C34.279,63.17 33.218,64 32,64C30.782,64 29.721,63.17 29.427,61.989L24.545,42.354C24.371,41.653 24.009,41.013 23.498,40.502C22.987,39.991 22.347,39.629 21.646,39.455L2.011,34.573C0.83,34.279 0,33.218 0,32C0,30.782 0.83,29.721 2.011,29.427L21.646,24.545C22.347,24.371 22.987,24.009 23.498,23.498C24.009,22.987 24.371,22.347 24.545,21.646L29.427,2.011Z" @@ -73,14 +74,15 @@ export default defineComponent({ const width = ref(0); const height = ref(0); const colors = ['#FF1493', '#00FFFF', '#FFE202', '#FFE202', '#FFE202']; + let stop = false; + let ro: ResizeObserver | undefined; onMounted(() => { - const ro = new ResizeObserver((entries, observer) => { + ro = new ResizeObserver((entries, observer) => { width.value = el.value?.offsetWidth + 64; height.value = el.value?.offsetHeight + 64; }); ro.observe(el.value); - let stop = false; const add = () => { if (stop) return; const x = (Math.random() * (width.value - 64)); @@ -104,10 +106,11 @@ export default defineComponent({ }, 500 + (Math.random() * 500)); }; add(); - onUnmounted(() => { - ro.disconnect(); - stop = true; - }); + }); + + onUnmounted(() => { + if (ro) ro.disconnect(); + stop = true; }); return { diff --git a/packages/client/src/pages/admin/overview.federation.vue b/packages/client/src/pages/admin/overview.federation.vue index 6da1fa4e98..6c99cad33c 100644 --- a/packages/client/src/pages/admin/overview.federation.vue +++ b/packages/client/src/pages/admin/overview.federation.vue @@ -18,6 +18,7 @@ import { onMounted, onUnmounted, ref } from 'vue'; import MkMiniChart from '@/components/mini-chart.vue'; import * as os from '@/os'; +import { useInterval } from '@/scripts/use-interval'; const instances = ref([]); const charts = ref([]); @@ -34,15 +35,9 @@ const fetch = async () => { fetching.value = false; }; -let intervalId; - -onMounted(() => { - fetch(); - intervalId = window.setInterval(fetch, 1000 * 60); -}); - -onUnmounted(() => { - window.clearInterval(intervalId); +useInterval(fetch, 1000 * 60, { + immediate: true, + afterMounted: true, }); </script> diff --git a/packages/client/src/scripts/use-interval.ts b/packages/client/src/scripts/use-interval.ts new file mode 100644 index 0000000000..eb6e44338d --- /dev/null +++ b/packages/client/src/scripts/use-interval.ts @@ -0,0 +1,22 @@ +import { onMounted, onUnmounted } from 'vue'; + +export function useInterval(fn: () => void, interval: number, options: { + immediate: boolean; + afterMounted: boolean; +}): void { + let intervalId: number | null = null; + + if (options.afterMounted) { + onMounted(() => { + if (options.immediate) fn(); + intervalId = window.setInterval(fn, interval); + }); + } else { + if (options.immediate) fn(); + intervalId = window.setInterval(fn, interval); + } + + onUnmounted(() => { + if (intervalId) window.clearInterval(intervalId); + }); +} diff --git a/packages/client/src/widgets/aichan.vue b/packages/client/src/widgets/aichan.vue index cdd367cc84..828490fd9c 100644 --- a/packages/client/src/widgets/aichan.vue +++ b/packages/client/src/widgets/aichan.vue @@ -6,8 +6,8 @@ <script lang="ts" setup> import { onMounted, onUnmounted, reactive, ref } from 'vue'; -import { GetFormResultType } from '@/scripts/form'; import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import { GetFormResultType } from '@/scripts/form'; const name = 'ai'; @@ -38,22 +38,23 @@ const touched = () => { //if (this.live2d) this.live2d.changeExpression('gurugurume'); }; -onMounted(() => { - const onMousemove = (ev: MouseEvent) => { - const iframeRect = live2d.value.getBoundingClientRect(); - live2d.value.contentWindow.postMessage({ - type: 'moveCursor', - body: { - x: ev.clientX - iframeRect.left, - y: ev.clientY - iframeRect.top, - } - }, '*'); - }; +const onMousemove = (ev: MouseEvent) => { + const iframeRect = live2d.value.getBoundingClientRect(); + live2d.value.contentWindow.postMessage({ + type: 'moveCursor', + body: { + x: ev.clientX - iframeRect.left, + y: ev.clientY - iframeRect.top, + }, + }, '*'); +}; +onMounted(() => { window.addEventListener('mousemove', onMousemove, { passive: true }); - onUnmounted(() => { - window.removeEventListener('mousemove', onMousemove); - }); +}); + +onUnmounted(() => { + window.removeEventListener('mousemove', onMousemove); }); defineExpose<WidgetComponentExpose>({ diff --git a/packages/client/src/widgets/calendar.vue b/packages/client/src/widgets/calendar.vue index 2a2b035541..3a0dc8970c 100644 --- a/packages/client/src/widgets/calendar.vue +++ b/packages/client/src/widgets/calendar.vue @@ -34,9 +34,10 @@ <script lang="ts" setup> import { onUnmounted, ref } from 'vue'; -import { GetFormResultType } from '@/scripts/form'; import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import { GetFormResultType } from '@/scripts/form'; import { i18n } from '@/i18n'; +import { useInterval } from '@/scripts/use-interval'; const name = 'calendar'; @@ -85,28 +86,26 @@ const tick = () => { i18n.ts._weekday.wednesday, i18n.ts._weekday.thursday, i18n.ts._weekday.friday, - i18n.ts._weekday.saturday + i18n.ts._weekday.saturday, ][now.getDay()]; - const dayNumer = now.getTime() - new Date(ny, nm, nd).getTime(); - const dayDenom = 1000/*ms*/ * 60/*s*/ * 60/*m*/ * 24/*h*/; + const dayNumer = now.getTime() - new Date(ny, nm, nd).getTime(); + const dayDenom = 1000/*ms*/ * 60/*s*/ * 60/*m*/ * 24/*h*/; const monthNumer = now.getTime() - new Date(ny, nm, 1).getTime(); const monthDenom = new Date(ny, nm + 1, 1).getTime() - new Date(ny, nm, 1).getTime(); - const yearNumer = now.getTime() - new Date(ny, 0, 1).getTime(); - const yearDenom = new Date(ny + 1, 0, 1).getTime() - new Date(ny, 0, 1).getTime(); + const yearNumer = now.getTime() - new Date(ny, 0, 1).getTime(); + const yearDenom = new Date(ny + 1, 0, 1).getTime() - new Date(ny, 0, 1).getTime(); - dayP.value = dayNumer / dayDenom * 100; + dayP.value = dayNumer / dayDenom * 100; monthP.value = monthNumer / monthDenom * 100; - yearP.value = yearNumer / yearDenom * 100; + yearP.value = yearNumer / yearDenom * 100; isHoliday.value = now.getDay() === 0 || now.getDay() === 6; }; -tick(); - -const intervalId = window.setInterval(tick, 1000); -onUnmounted(() => { - window.clearInterval(intervalId); +useInterval(tick, 1000, { + immediate: true, + afterMounted: false, }); defineExpose<WidgetComponentExpose>({ diff --git a/packages/client/src/widgets/federation.vue b/packages/client/src/widgets/federation.vue index afe7af0e96..ac87cdac2e 100644 --- a/packages/client/src/widgets/federation.vue +++ b/packages/client/src/widgets/federation.vue @@ -25,6 +25,7 @@ import { GetFormResultType } from '@/scripts/form'; import MkContainer from '@/components/ui/container.vue'; import MkMiniChart from '@/components/mini-chart.vue'; import * as os from '@/os'; +import { useInterval } from '@/scripts/use-interval'; const name = 'federation'; @@ -64,12 +65,9 @@ const fetch = async () => { fetching.value = false; }; -onMounted(() => { - fetch(); - const intervalId = window.setInterval(fetch, 1000 * 60); - onUnmounted(() => { - window.clearInterval(intervalId); - }); +useInterval(fetch, 1000 * 60, { + immediate: true, + afterMounted: true, }); defineExpose<WidgetComponentExpose>({ diff --git a/packages/client/src/widgets/online-users.vue b/packages/client/src/widgets/online-users.vue index eb3184fe9d..4122a82657 100644 --- a/packages/client/src/widgets/online-users.vue +++ b/packages/client/src/widgets/online-users.vue @@ -8,9 +8,10 @@ <script lang="ts" setup> import { onMounted, onUnmounted, ref } from 'vue'; -import { GetFormResultType } from '@/scripts/form'; import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import { GetFormResultType } from '@/scripts/form'; import * as os from '@/os'; +import { useInterval } from '@/scripts/use-interval'; const name = 'onlineUsers'; @@ -43,12 +44,9 @@ const tick = () => { }); }; -onMounted(() => { - tick(); - const intervalId = window.setInterval(tick, 1000 * 15); - onUnmounted(() => { - window.clearInterval(intervalId); - }); +useInterval(tick, 1000 * 15, { + immediate: true, + afterMounted: true, }); defineExpose<WidgetComponentExpose>({ diff --git a/packages/client/src/widgets/rss.vue b/packages/client/src/widgets/rss.vue index fc65f11813..e5da291a8d 100644 --- a/packages/client/src/widgets/rss.vue +++ b/packages/client/src/widgets/rss.vue @@ -14,10 +14,11 @@ <script lang="ts" setup> import { onMounted, onUnmounted, ref, watch } from 'vue'; -import { GetFormResultType } from '@/scripts/form'; import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import { GetFormResultType } from '@/scripts/form'; import * as os from '@/os'; import MkContainer from '@/components/ui/container.vue'; +import { useInterval } from '@/scripts/use-interval'; const name = 'rss'; @@ -60,12 +61,9 @@ const tick = () => { watch(() => widgetProps.url, tick); -onMounted(() => { - tick(); - const intervalId = window.setInterval(tick, 60000); - onUnmounted(() => { - window.clearInterval(intervalId); - }); +useInterval(tick, 60000, { + immediate: true, + afterMounted: true, }); defineExpose<WidgetComponentExpose>({ diff --git a/packages/client/src/widgets/slideshow.vue b/packages/client/src/widgets/slideshow.vue index fd78edbe40..c286312161 100644 --- a/packages/client/src/widgets/slideshow.vue +++ b/packages/client/src/widgets/slideshow.vue @@ -13,9 +13,10 @@ <script lang="ts" setup> import { nextTick, onMounted, onUnmounted, reactive, ref } from 'vue'; -import { GetFormResultType } from '@/scripts/form'; import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import { GetFormResultType } from '@/scripts/form'; import * as os from '@/os'; +import { useInterval } from '@/scripts/use-interval'; const name = 'slideshow'; @@ -75,7 +76,7 @@ const fetch = () => { os.api('drive/files', { folderId: widgetProps.folderId, type: 'image/*', - limit: 100 + limit: 100, }).then(res => { images.value = res; fetching.value = false; @@ -96,15 +97,15 @@ const choose = () => { }); }; +useInterval(change, 10000, { + immediate: false, + afterMounted: true, +}); + onMounted(() => { if (widgetProps.folderId != null) { fetch(); } - - const intervalId = window.setInterval(change, 10000); - onUnmounted(() => { - window.clearInterval(intervalId); - }); }); defineExpose<WidgetComponentExpose>({ diff --git a/packages/client/src/widgets/trends.vue b/packages/client/src/widgets/trends.vue index 9680f1c892..0f34ea6341 100644 --- a/packages/client/src/widgets/trends.vue +++ b/packages/client/src/widgets/trends.vue @@ -19,11 +19,12 @@ <script lang="ts" setup> import { onMounted, onUnmounted, ref } from 'vue'; -import { GetFormResultType } from '@/scripts/form'; import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import { GetFormResultType } from '@/scripts/form'; import MkContainer from '@/components/ui/container.vue'; import MkMiniChart from '@/components/mini-chart.vue'; import * as os from '@/os'; +import { useInterval } from '@/scripts/use-interval'; const name = 'hashtags'; @@ -58,12 +59,9 @@ const fetch = () => { }); }; -onMounted(() => { - fetch(); - const intervalId = window.setInterval(fetch, 1000 * 60); - onUnmounted(() => { - window.clearInterval(intervalId); - }); +useInterval(fetch, 1000 * 60, { + immediate: true, + afterMounted: true, }); defineExpose<WidgetComponentExpose>({