Show users who sent reaction on hover (#5362)
* Show users who sent reaction on hover * Support i18n * detail -> details * Extract methods * Update on change
This commit is contained in:
		
							parent
							
								
									a47baad943
								
							
						
					
					
						commit
						749200d22b
					
				
					 3 changed files with 147 additions and 0 deletions
				
			
		|  | @ -413,6 +413,10 @@ common/views/pages/explore.vue: | |||
|   explore: "{host}を探索" | ||||
|   users-info: "現在{users}ユーザーが登録されています" | ||||
| 
 | ||||
| common/views/components/reactions-viewer.details.vue: | ||||
|   few-users: "{users}が{reaction}をリアクション" | ||||
|   many-users: "{users}と他{omitted}人が{reaction}をリアクション" | ||||
| 
 | ||||
| common/views/components/url-preview.vue: | ||||
|   enable-player: "プレイヤーを開く" | ||||
|   disable-player: "プレイヤーを閉じる" | ||||
|  |  | |||
|  | @ -0,0 +1,103 @@ | |||
| <template> | ||||
| 	<transition name="zoom-in-top"> | ||||
| 		<div class="buebdbiu" ref="popover" v-if="show"> | ||||
| 			<i18n path="few-users" v-if="users.length <= 10"> | ||||
| 				<span slot="users">{{ users.join(', ') }}</span> | ||||
| 				<mk-reaction-icon slot="reaction" :reaction="reaction" ref="icon" /> | ||||
| 			</i18n> | ||||
| 			<i18n path="many-users" v-if="10 < users.length"> | ||||
| 				<span slot="users">{{ users.slice(0, 10).join(', ') }}</span> | ||||
| 				<span slot="ommited">{{ users.length - 10 }}</span> | ||||
| 				<mk-reaction-icon slot="reaction" :reaction="reaction" ref="icon" /> | ||||
| 			</i18n> | ||||
| 		</div> | ||||
| 	</transition> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import i18n from '../../../i18n'; | ||||
| 
 | ||||
| export default Vue.extend({ | ||||
| 	i18n: i18n('common/views/components/reactions-viewer.details.vue'), | ||||
| 	props: { | ||||
| 		reaction: { | ||||
| 			type: String, | ||||
| 			required: true, | ||||
| 		}, | ||||
| 		users: { | ||||
| 			type: Array, | ||||
| 			required: true, | ||||
| 		}, | ||||
| 		source: { | ||||
| 			required: true, | ||||
| 		} | ||||
| 	}, | ||||
| 	data() { | ||||
| 		return { | ||||
| 			show: false | ||||
| 		}; | ||||
| 	}, | ||||
| 	mounted() { | ||||
| 		this.show = true; | ||||
| 
 | ||||
| 		this.$nextTick(() => { | ||||
| 			const popover = this.$refs.popover as any; | ||||
| 
 | ||||
| 			const rect = this.source.getBoundingClientRect(); | ||||
| 
 | ||||
| 			const x = rect.left + window.pageXOffset + (this.source.offsetWidth / 2); | ||||
| 			const y = rect.top + window.pageYOffset + this.source.offsetHeight; | ||||
| 			popover.style.left = (x - 28) + 'px'; | ||||
| 			popover.style.top = (y + 16) + 'px'; | ||||
| 		}); | ||||
| 	} | ||||
| 	methods: { | ||||
| 		close() { | ||||
| 			this.show = false; | ||||
| 			setTimeout(this.destroyDom, 300); | ||||
| 		} | ||||
| 	} | ||||
| }) | ||||
| </script> | ||||
| 
 | ||||
| <style lang="stylus" scoped> | ||||
| .buebdbiu | ||||
| 	$bgcolor = var(--popupBg) | ||||
| 	z-index 10000 | ||||
| 	display block | ||||
| 	position absolute | ||||
| 	min-width max-content | ||||
| 	max-width 240px | ||||
| 	font-size 0.8em | ||||
| 	padding 5px 8px | ||||
| 	background $bgcolor | ||||
| 	text-align center | ||||
| 	color var(--text) | ||||
| 	border-radius 4px | ||||
| 	box-shadow 0 var(--lineWidth) 4px rgba(#000, 0.25) | ||||
| 
 | ||||
| 	&:before | ||||
| 		content "" | ||||
| 		pointer-events none | ||||
| 		display block | ||||
| 		position absolute | ||||
| 		top -28px | ||||
| 		left 12px | ||||
| 		border-top solid 14px transparent | ||||
| 		border-right solid 14px transparent | ||||
| 		border-bottom solid 14px rgba(#000, 0.1) | ||||
| 		border-left solid 14px transparent | ||||
| 
 | ||||
| 	&:after | ||||
| 		content "" | ||||
| 		pointer-events none | ||||
| 		display block | ||||
| 		position absolute | ||||
| 		top -27px | ||||
| 		left 12px | ||||
| 		border-top solid 14px transparent | ||||
| 		border-right solid 14px transparent | ||||
| 		border-bottom solid 14px $bgcolor | ||||
| 		border-left solid 14px transparent | ||||
| </style> | ||||
|  | @ -5,6 +5,9 @@ | |||
| 	@click="toggleReaction(reaction)" | ||||
| 	v-if="count > 0" | ||||
| 	v-particle="!isMe" | ||||
| 	@mouseover="onMouseover" | ||||
| 	@mouseleave="onMouseleave" | ||||
| 	ref="reaction" | ||||
| > | ||||
| 	<mk-reaction-icon :reaction="reaction" ref="icon"/> | ||||
| 	<span>{{ count }}</span> | ||||
|  | @ -15,6 +18,7 @@ | |||
| import Vue from 'vue'; | ||||
| import Icon from './reaction-icon.vue'; | ||||
| import anime from 'animejs'; | ||||
| import XDetails from './reactions-viewer.details.vue'; | ||||
| 
 | ||||
| export default Vue.extend({ | ||||
| 	props: { | ||||
|  | @ -40,6 +44,12 @@ export default Vue.extend({ | |||
| 			default: true, | ||||
| 		}, | ||||
| 	}, | ||||
| 	data() { | ||||
| 		return { | ||||
| 			details: null, | ||||
| 			detailsTimeoutId: null | ||||
| 		}; | ||||
| 	}, | ||||
| 	computed: { | ||||
| 		isMe(): boolean { | ||||
| 			return this.$store.getters.isSignedIn && this.$store.state.i.id === this.note.userId; | ||||
|  | @ -51,6 +61,7 @@ export default Vue.extend({ | |||
| 	watch: { | ||||
| 		count(newCount, oldCount) { | ||||
| 			if (oldCount < newCount) this.anime(); | ||||
| 			if (this.details != null) this.openDetails(); | ||||
| 		}, | ||||
| 	}, | ||||
| 	methods: { | ||||
|  | @ -77,6 +88,35 @@ export default Vue.extend({ | |||
| 				}); | ||||
| 			} | ||||
| 		}, | ||||
| 		onMouseover() { | ||||
| 			this.detailsTimeoutId = setTimeout(this.openDetails, 300); | ||||
| 		}, | ||||
| 		onMouseleave() { | ||||
| 			clearTimeout(this.detailsTimeoutId); | ||||
| 			this.closeDetails(); | ||||
| 		}, | ||||
| 		openDetails() { | ||||
| 			this.$root.api('notes/reactions', { | ||||
| 				noteId: this.note.id | ||||
| 			}).then((reactions: any[]) => { | ||||
| 				const users = reactions.filter(x => x.type === this.reaction) | ||||
| 					.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()) | ||||
| 					.map(x => x.user.username); | ||||
| 
 | ||||
| 				this.closeDetails(); | ||||
| 				this.details = this.$root.new(XDetails, { | ||||
| 					reaction: this.reaction, | ||||
| 					users, | ||||
| 					source: this.$refs.reaction | ||||
| 				}); | ||||
| 			}); | ||||
| 		}, | ||||
| 		closeDetails() { | ||||
| 			if (this.details != null) { | ||||
| 				this.details.close(); | ||||
| 				this.details = null; | ||||
| 			} | ||||
| 		}, | ||||
| 		anime() { | ||||
| 			if (this.$store.state.device.reduceMotion) return; | ||||
| 			if (document.hidden) return; | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue