summaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
authorかっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>2025-09-12 17:12:50 +0900
committerGitHub <noreply@github.com>2025-09-12 17:12:50 +0900
commitaebc3f781e32c40faf2ee7fa5a23042e6382ce2e (patch)
tree6b92541ce8bedc69f824c8d119600b4c3ceba6b7 /packages
parentchore(gh): add frontend-builder to renovate (diff)
downloadmisskey-aebc3f781e32c40faf2ee7fa5a23042e6382ce2e.tar.gz
misskey-aebc3f781e32c40faf2ee7fa5a23042e6382ce2e.tar.bz2
misskey-aebc3f781e32c40faf2ee7fa5a23042e6382ce2e.zip
perf(frontend): 低精度な現在時刻を一か所で管理するように (#16479)
* perf(frontend): 低精度な現在時刻を一か所で管理するように * lint * fix * remove unused imports * fix * Update Changelog * [ci skip] typo * enhance: カレンダーウィジェットの日付変更は時間通りに行うように * [ci skip] fix
Diffstat (limited to 'packages')
-rw-r--r--packages/frontend/src/components/MkPoll.vue37
-rw-r--r--packages/frontend/src/components/global/MkTime.vue30
-rw-r--r--packages/frontend/src/composables/use-lowres-time.ts34
-rw-r--r--packages/frontend/src/widgets/WidgetCalendar.vue40
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>({