diff options
Diffstat (limited to 'packages/frontend')
| -rw-r--r-- | packages/frontend/src/components/MkPoll.vue | 37 | ||||
| -rw-r--r-- | packages/frontend/src/components/global/MkTime.vue | 30 | ||||
| -rw-r--r-- | packages/frontend/src/composables/use-lowres-time.ts | 34 | ||||
| -rw-r--r-- | packages/frontend/src/widgets/WidgetCalendar.vue | 40 |
4 files changed, 89 insertions, 52 deletions
diff --git a/packages/frontend/src/components/MkPoll.vue b/packages/frontend/src/components/MkPoll.vue index 359ee08812..76c65397ae 100644 --- a/packages/frontend/src/components/MkPoll.vue +++ b/packages/frontend/src/components/MkPoll.vue @@ -27,16 +27,16 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script lang="ts" setup> -import { computed, ref } from 'vue'; +import { computed, ref, watch } from 'vue'; import * as Misskey from 'misskey-js'; import { host } from '@@/js/config.js'; -import { useInterval } from '@@/js/use-interval.js'; import type { OpenOnRemoteOptions } from '@/utility/please-login.js'; import { sum } from '@/utility/array.js'; import { pleaseLogin } from '@/utility/please-login.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/utility/misskey-api.js'; import { i18n } from '@/i18n.js'; +import { useLowresTime } from '@/composables/use-lowres-time.js'; const props = defineProps<{ noteId: string; @@ -48,7 +48,21 @@ const props = defineProps<{ author?: Misskey.entities.UserLite; }>(); -const remaining = ref(-1); +const now = useLowresTime(); + +const expiresAtTime = computed(() => props.expiresAt ? new Date(props.expiresAt).getTime() : null); + +const remaining = computed(() => { + if (expiresAtTime.value == null) return -1; + return Math.floor(Math.max(expiresAtTime.value - now.value, 0) / 1000); +}); + +const remainingWatchStop = watch(remaining, (to) => { + if (to <= 0) { + showResult.value = true; + remainingWatchStop(); + } +}, { immediate: true }); const total = computed(() => sum(props.choices.map(x => x.votes))); const closed = computed(() => remaining.value === 0); @@ -71,22 +85,7 @@ const pleaseLoginContext = computed<OpenOnRemoteOptions>(() => ({ url: `https://${host}/notes/${props.noteId}`, })); -// 期限付きアンケート -if (props.expiresAt) { - const tick = () => { - remaining.value = Math.floor(Math.max(new Date(props.expiresAt!).getTime() - Date.now(), 0) / 1000); - if (remaining.value === 0) { - showResult.value = true; - } - }; - - useInterval(tick, 3000, { - immediate: true, - afterMounted: false, - }); -} - -const vote = async (id) => { +const vote = async (id: number) => { if (props.readOnly || closed.value || isVoted.value) return; pleaseLogin({ openOnRemote: pleaseLoginContext.value }); diff --git a/packages/frontend/src/components/global/MkTime.vue b/packages/frontend/src/components/global/MkTime.vue index f600f7eed2..88cccb99a2 100644 --- a/packages/frontend/src/components/global/MkTime.vue +++ b/packages/frontend/src/components/global/MkTime.vue @@ -14,9 +14,10 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import isChromatic from 'chromatic/isChromatic'; -import { onMounted, onUnmounted, ref, computed } from 'vue'; +import { computed } from 'vue'; import { i18n } from '@/i18n.js'; import { dateTimeFormat } from '@@/js/intl-const.js'; +import { useLowresTime } from '@/composables/use-lowres-time.js'; const props = withDefaults(defineProps<{ time: Date | string | number | null; @@ -46,8 +47,10 @@ const _time = props.time == null ? NaN : getDateSafe(props.time).getTime(); const invalid = Number.isNaN(_time); const absolute = !invalid ? dateTimeFormat.format(_time) : i18n.ts._ago.invalid; +const actualNow = useLowresTime(); +const now = computed(() => (props.origin ? props.origin.getTime() : actualNow.value)); + // eslint-disable-next-line vue/no-setup-props-reactivity-loss -const now = ref(props.origin?.getTime() ?? Date.now()); const ago = computed(() => (now.value - _time) / 1000/*ms*/); const relative = computed<string>(() => { @@ -72,29 +75,6 @@ const relative = computed<string>(() => { i18n.tsx._timeIn.seconds({ n: (~~(-ago.value % 60)).toString() }) ); }); - -let tickId: number; -let currentInterval: number; - -function tick() { - now.value = Date.now(); - const nextInterval = ago.value < 60 ? 10000 : ago.value < 3600 ? 60000 : 180000; - - if (currentInterval !== nextInterval) { - if (tickId) window.clearInterval(tickId); - currentInterval = nextInterval; - tickId = window.setInterval(tick, nextInterval); - } -} - -if (!invalid && props.origin === null && (props.mode === 'relative' || props.mode === 'detail')) { - onMounted(() => { - tick(); - }); - onUnmounted(() => { - if (tickId) window.clearInterval(tickId); - }); -} </script> <style lang="scss" module> diff --git a/packages/frontend/src/composables/use-lowres-time.ts b/packages/frontend/src/composables/use-lowres-time.ts new file mode 100644 index 0000000000..3c5b561f51 --- /dev/null +++ b/packages/frontend/src/composables/use-lowres-time.ts @@ -0,0 +1,34 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { ref, readonly, computed } from 'vue'; + +const time = ref(Date.now()); + +export const TIME_UPDATE_INTERVAL = 10000; // 10秒 + +/** + * 精度が求められないが定期的に更新しないといけない時計で使用(10秒に一度更新)。 + * tickを各コンポーネントで行うのではなく、ここで一括して行うことでパフォーマンスを改善する。 + * + * ※ マウント前の時刻を返す可能性があるため、通常は`useLowresTime`を使用する +*/ +export const lowresTime = readonly(time); + +/** + * 精度が求められないが定期的に更新しないといけない時計で使用(10秒に一度更新)。 + * tickを各コンポーネントで行うのではなく、ここで一括して行うことでパフォーマンスを改善する。 + * + * 必ず現在時刻以降を返すことを保証するコンポーサブル + */ +export function useLowresTime() { + // lowresTime自体はマウント前の時刻を返す可能性があるため、必ず現在時刻以降を返すことを保証する + const now = Date.now(); + return computed(() => Math.max(time.value, now)); +} + +window.setInterval(() => { + time.value = Date.now(); +}, TIME_UPDATE_INTERVAL); diff --git a/packages/frontend/src/widgets/WidgetCalendar.vue b/packages/frontend/src/widgets/WidgetCalendar.vue index 12c0a66c5c..f2321ca9fa 100644 --- a/packages/frontend/src/widgets/WidgetCalendar.vue +++ b/packages/frontend/src/widgets/WidgetCalendar.vue @@ -38,12 +38,12 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script lang="ts" setup> -import { ref } from 'vue'; +import { ref, watch } from 'vue'; import { useWidgetPropsManager } from './widget.js'; import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js'; import type { FormWithDefault, GetFormResultType } from '@/utility/form.js'; import { i18n } from '@/i18n.js'; -import { useInterval } from '@@/js/use-interval.js'; +import { useLowresTime, TIME_UPDATE_INTERVAL } from '@/composables/use-lowres-time.js'; const name = 'calendar'; @@ -65,6 +65,7 @@ const { widgetProps, configure } = useWidgetPropsManager(name, emit, ); +const fNow = useLowresTime(); const year = ref(0); const month = ref(0); const day = ref(0); @@ -73,8 +74,14 @@ const yearP = ref(0); const monthP = ref(0); const dayP = ref(0); const isHoliday = ref(false); -const tick = () => { - const now = new Date(); + +const nextDay = new Date(); +nextDay.setHours(24, 0, 0, 0); +let nextDayMidnightTime = nextDay.getTime(); +let nextDayTimer: number | null = null; + +function update(time: number) { + const now = new Date(time); const nd = now.getDate(); const nm = now.getMonth(); const ny = now.getFullYear(); @@ -104,11 +111,28 @@ const tick = () => { yearP.value = yearNumer / yearDenom * 100; isHoliday.value = now.getDay() === 0 || now.getDay() === 6; -}; +} + +watch(fNow, (to) => { + update(to); + + // 次回更新までに日付が変わる場合、日付が変わった直後に強制的に更新するタイマーをセットする + if (nextDayMidnightTime - to <= TIME_UPDATE_INTERVAL) { + if (nextDayTimer != null) { + window.clearTimeout(nextDayTimer); + nextDayTimer = null; + } + + nextDayTimer = window.setTimeout(() => { + update(nextDayMidnightTime); + nextDayTimer = null; + }, nextDayMidnightTime - to); + } +}, { immediate: true }); -useInterval(tick, 1000, { - immediate: true, - afterMounted: false, +watch(day, () => { + nextDay.setHours(24, 0, 0, 0); + nextDayMidnightTime = nextDay.getTime(); }); defineExpose<WidgetComponentExpose>({ |