refactor(client): extract interval logic to a composable function
あと`onUnmounted`を`onMounted`内で呼んでいたりしたのを修正したりとか
This commit is contained in:
		
							parent
							
								
									6a4574b612
								
							
						
					
					
						commit
						5e95a1f7af
					
				
					 18 changed files with 207 additions and 183 deletions
				
			
		|  | @ -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); | ||||
| 				}); | ||||
| 			}); | ||||
| 		}); | ||||
| 
 | ||||
|  |  | |||
|  | @ -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(() => { | ||||
|  |  | |||
|  | @ -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); | ||||
| 				}); | ||||
| 			}); | ||||
| 		}); | ||||
| 
 | ||||
|  |  | |||
|  | @ -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> | ||||
|  |  | |||
|  | @ -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> | ||||
|  |  | |||
|  | @ -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); | ||||
|  |  | |||
|  | @ -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> | ||||
| 
 | ||||
|  |  | |||
|  | @ -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, | ||||
| 			}); | ||||
| 		} | ||||
| 
 | ||||
|  |  | |||
|  | @ -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 { | ||||
|  |  | |||
|  | @ -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> | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										22
									
								
								packages/client/src/scripts/use-interval.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								packages/client/src/scripts/use-interval.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -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); | ||||
| 	}); | ||||
| } | ||||
|  | @ -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>({ | ||||
|  |  | |||
|  | @ -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>({ | ||||
|  |  | |||
|  | @ -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>({ | ||||
|  |  | |||
|  | @ -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>({ | ||||
|  |  | |||
|  | @ -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>({ | ||||
|  |  | |||
|  | @ -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>({ | ||||
|  |  | |||
|  | @ -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>({ | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue