From 9384f5399da39e53855beb8e7f8ded1aa56bf72e Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 27 Dec 2022 14:36:33 +0900 Subject: rename: client -> frontend --- packages/frontend/src/directives/user-preview.ts | 118 +++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 packages/frontend/src/directives/user-preview.ts (limited to 'packages/frontend/src/directives/user-preview.ts') diff --git a/packages/frontend/src/directives/user-preview.ts b/packages/frontend/src/directives/user-preview.ts new file mode 100644 index 0000000000..ed5f00ca65 --- /dev/null +++ b/packages/frontend/src/directives/user-preview.ts @@ -0,0 +1,118 @@ +import { defineAsyncComponent, Directive, ref } from 'vue'; +import autobind from 'autobind-decorator'; +import { popup } from '@/os'; + +export class UserPreview { + private el; + private user; + private showTimer; + private hideTimer; + private checkTimer; + private promise; + + constructor(el, user) { + this.el = el; + this.user = user; + + this.attach(); + } + + @autobind + private show() { + if (!document.body.contains(this.el)) return; + if (this.promise) return; + + const showing = ref(true); + + popup(defineAsyncComponent(() => import('@/components/MkUserPreview.vue')), { + showing, + q: this.user, + source: this.el, + }, { + mouseover: () => { + window.clearTimeout(this.hideTimer); + }, + mouseleave: () => { + window.clearTimeout(this.showTimer); + this.hideTimer = window.setTimeout(this.close, 500); + }, + }, 'closed'); + + this.promise = { + cancel: () => { + showing.value = false; + }, + }; + + this.checkTimer = window.setInterval(() => { + if (!document.body.contains(this.el)) { + window.clearTimeout(this.showTimer); + window.clearTimeout(this.hideTimer); + this.close(); + } + }, 1000); + } + + @autobind + private close() { + if (this.promise) { + window.clearInterval(this.checkTimer); + this.promise.cancel(); + this.promise = null; + } + } + + @autobind + private onMouseover() { + window.clearTimeout(this.showTimer); + window.clearTimeout(this.hideTimer); + this.showTimer = window.setTimeout(this.show, 500); + } + + @autobind + private onMouseleave() { + window.clearTimeout(this.showTimer); + window.clearTimeout(this.hideTimer); + this.hideTimer = window.setTimeout(this.close, 500); + } + + @autobind + private onClick() { + window.clearTimeout(this.showTimer); + this.close(); + } + + @autobind + public attach() { + this.el.addEventListener('mouseover', this.onMouseover); + this.el.addEventListener('mouseleave', this.onMouseleave); + this.el.addEventListener('click', this.onClick); + } + + @autobind + public detach() { + this.el.removeEventListener('mouseover', this.onMouseover); + this.el.removeEventListener('mouseleave', this.onMouseleave); + this.el.removeEventListener('click', this.onClick); + window.clearInterval(this.checkTimer); + } +} + +export default { + mounted(el: HTMLElement, binding, vn) { + if (binding.value == null) return; + + // TODO: 新たにプロパティを作るのをやめMapを使う + // ただメモリ的には↓の方が省メモリかもしれないので検討中 + const self = (el as any)._userPreviewDirective_ = {} as any; + + self.preview = new UserPreview(el, binding.value); + }, + + unmounted(el, binding, vn) { + if (binding.value == null) return; + + const self = el._userPreviewDirective_; + self.preview.detach(); + }, +} as Directive; -- cgit v1.2.3-freya