mizzkey/packages/frontend/src/components/MkReactionsViewer.reaction.vue
mattyatea cb1005811d Merge remote-tracking branch 'misskey-original/develop' into develop
# Conflicts:
#	locales/en-US.yml
#	locales/index.d.ts
#	locales/ja-JP.yml
#	package.json
#	packages/backend/src/server/api/endpoints/notes/create.ts
#	packages/frontend/src/components/MkDrive.file.vue
#	packages/frontend/src/components/MkNote.vue
#	packages/frontend/src/components/MkNoteHeader.vue
#	packages/frontend/src/components/MkPostForm.vue
#	packages/frontend/src/components/MkReactionsViewer.reaction.vue
#	packages/frontend/src/components/MkUserSetupDialog.vue
#	packages/frontend/src/pages/timeline.tutorial.vue
#	packages/frontend/src/pages/timeline.vue
#	packages/misskey-js/etc/misskey-js.api.md
2023-11-04 01:24:44 +09:00

292 lines
6.9 KiB
Vue

<!--
SPDX-FileCopyrightText: syuilo and other misskey contributors
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<button
ref="buttonEl"
v-ripple="canToggle"
class="_button"
:class="[$style.root, { [$style.gamingDark]: gamingType === 'dark',[$style.gamingLight]: gamingType === 'light' ,[$style.reacted]: note.myReaction == reaction, [$style.canToggle]: canToggle, [$style.small]: defaultStore.state.reactionsDisplaySize === 'small', [$style.large]: defaultStore.state.reactionsDisplaySize === 'large' }]"
@click="toggleReaction()"
>
<MkReactionIcon :class="$style.icon" :reaction="reaction" :emojiUrl="note.reactionEmojis[reaction.substring(1, reaction.length - 1)]"/>
<span :class="[$style.count,{ [$style.gamingDark]: gamingType === 'dark',[$style.gamingLight]: gamingType === 'light'}]">{{ count }}</span>
</button>
</template>
<script lang="ts" setup>
import {computed, inject, onMounted, ref, shallowRef, watch} from 'vue';
import * as Misskey from 'misskey-js';
import XDetails from '@/components/MkReactionsViewer.details.vue';
import MkReactionIcon from '@/components/MkReactionIcon.vue';
import * as os from '@/os.js';
import { useTooltip } from '@/scripts/use-tooltip.js';
import { $i } from '@/account.js';
import MkReactionEffect from '@/components/MkReactionEffect.vue';
import { claimAchievement } from '@/scripts/achievements.js';
import { defaultStore } from '@/store.js';
import { i18n } from '@/i18n.js';
let gamingType = computed(defaultStore.makeGetterSetter('gamingType'));
const props = defineProps<{
reaction: string;
count: number;
isInitial: boolean;
note: Misskey.entities.Note;
}>();
const mock = inject<boolean>('mock', false);
const emit = defineEmits<{
(ev: 'reactionToggled', emoji: string, newCount: number): void;
}>();
const buttonEl = shallowRef<HTMLElement>();
const canToggle = computed(() => !props.reaction.match(/@\w/) && $i);
async function toggleReaction() {
if (!canToggle.value) return;
// TODO: その絵文字を使う権限があるかどうか確認
const oldReaction = props.note.myReaction;
if (oldReaction) {
const confirm = await os.confirm({
type: 'warning',
text: oldReaction !== props.reaction ? i18n.ts.changeReactionConfirm : i18n.ts.cancelReactionConfirm,
});
if (confirm.canceled) return;
if (mock) {
emit('reactionToggled', props.reaction, (props.count - 1));
return;
}
os.api('notes/reactions/delete', {
noteId: props.note.id,
}).then(() => {
if (oldReaction !== props.reaction) {
os.api('notes/reactions/create', {
noteId: props.note.id,
reaction: props.reaction,
});
}
});
} else {
if (mock) {
emit('reactionToggled', props.reaction, (props.count + 1));
return;
}
os.api('notes/reactions/create', {
noteId: props.note.id,
reaction: props.reaction,
});
if (props.note.text && props.note.text.length > 100 && (Date.now() - new Date(props.note.createdAt).getTime() < 1000 * 3)) {
claimAchievement('reactWithoutRead');
}
}
}
function anime() {
if (document.hidden) return;
if (!defaultStore.state.animation) return;
const rect = buttonEl.value.getBoundingClientRect();
const x = rect.left + 16;
const y = rect.top + (buttonEl.value.offsetHeight / 2);
os.popup(MkReactionEffect, { reaction: props.reaction, x, y }, {}, 'end');
}
watch(() => props.count, (newCount, oldCount) => {
if (oldCount < newCount) anime();
});
onMounted(() => {
if (!props.isInitial) anime();
});
if (!mock) {
useTooltip(buttonEl, async (showing) => {
const reactions = await os.apiGet('notes/reactions', {
noteId: props.note.id,
type: props.reaction,
limit: 10,
_cacheKey_: props.count,
});
const users = reactions.map(x => x.user);
os.popup(XDetails, {
showing,
reaction: props.reaction,
users,
count: props.count,
targetElement: buttonEl.value,
}, {}, 'closed');
}, 100);
}
</script>
<style lang="scss" module>
.root {
display: inline-block;
height: 42px;
margin: 2px;
padding: 0 6px;
font-size: 1.5em;
border-radius: 6px;
&.canToggle {
background: var(--buttonBg);
&:hover {
background: rgba(0, 0, 0, 0.1);
}
}
&:not(.canToggle) {
cursor: default;
}
&.small {
height: 32px;
font-size: 1em;
border-radius: 4px;
> .count {
font-size: 0.9em;
line-height: 32px;
}
}
&.large {
height: 52px;
font-size: 2em;
border-radius: 8px;
> .count {
font-size: 0.6em;
line-height: 52px;
}
}
&.reacted, &.reacted:hover {
background: var(--accentedBg);
color: var(--accent);
box-shadow: 0 0 0px 1px var(--accent) inset;
&.gamingDark{
color: black;
background: linear-gradient(270deg, #e7a2a2, #e3cfa2, #ebefa1, #b3e7a6, #a6ebe7, #aec5e3, #cabded, #e0b9e3, #f4bddd); background-size: 1800% 1800%;
-webkit-animation: AnimationDark var(--gamingspeed) cubic-bezier(0, 0.2, 0.90, 1) infinite;
-moz-animation: AnimationDark var(--gamingspeed) cubic-bezier(0, 0.2, 0.90, 1) infinite;
animation: AnimationDark var(--gamingspeed) cubic-bezier(0, 0.2, 0.90, 1) infinite;
box-shadow: 0 0 0px 1px white inset;
}
&.gamingLight{
background: linear-gradient(270deg, #c06161, #c0a567, #b6ba69, #81bc72, #63c3be, #8bacd6, #9f8bd6, #d18bd6, #d883b4); background-size: 1800% 1800% !important;
-webkit-animation: AnimationLight var(--gamingspeed) cubic-bezier(0, 0.2, 0.90, 1) infinite !important;
-moz-animation: AnimationLight var(--gamingspeed) cubic-bezier(0, 0.2, 0.90, 1) infinite !important;
animation: AnimationLight var(--gamingspeed) cubic-bezier(0, 0.2, 0.90, 1) infinite !important;
box-shadow: 0 0 0px 1px white inset;
color: white !important;
}
> .count {
color: var(--accent);
&.gamingLight{
color: white;
}
&.gamingDark{
color: black;
}
}
> .icon {
filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.5));
}
}
}
.icon {
max-width: 150px;
}
.count {
font-size: 0.7em;
line-height: 42px;
margin: 0 0 0 4px;
}
@-webkit-keyframes AnimationLight {
0% {
background-position: 0% 50%
}
50% {
background-position: 100% 50%
}
100% {
background-position: 0% 50%
}
}
@-moz-keyframes AnimationLight {
0% {
background-position: 0% 50%
}
50% {
background-position: 100% 50%
}
100% {
background-position: 0% 50%
}
} @keyframes AnimationLight {
0% {
background-position: 0% 50%
}
50% {
background-position: 100% 50%
}
100% {
background-position: 0% 50%
}
}
@-webkit-keyframes AnimationDark {
0% {
background-position: 0% 50%
}
50% {
background-position: 100% 50%
}
100% {
background-position: 0% 50%
}
}
@-moz-keyframes AnimationDark {
0% {
background-position: 0% 50%
}
50% {
background-position: 100% 50%
}
100% {
background-position: 0% 50%
}
}
@keyframes AnimationDark {
0% {
background-position: 0% 50%
}
50% {
background-position: 100% 50%
}
100% {
background-position: 0% 50%
}
}
</style>