<!-- SPDX-FileCopyrightText: syuilo and other misskey contributors SPDX-License-Identifier: AGPL-3.0-only --> <template> <button class="_button" :class="[$style.root, ,{ [$style.wait]: wait, [$style.active]: isFollowing || hasPendingFollowRequestFromYou,[$style.gamingDark]: gaming === 'dark' || isFollowing || hasPendingFollowRequestFromYou,[$style.gamingLight]: gaming === 'light' || isFollowing || hasPendingFollowRequestFromYou , [$style.full]: full, [$style.large]: large }]" :disabled="wait" @click="onClick" > <template v-if="!wait"> <template v-if="hasPendingFollowRequestFromYou && user.isLocked"> <span v-if="full" :class="$style.text,{[$style.gamingDark]: gaming === 'dark',[$style.gamingLight]: gaming === 'light'} ">{{ i18n.ts.followRequestPending }}</span><i class="ti ti-hourglass-empty"></i> </template> <template v-else-if="hasPendingFollowRequestFromYou && !user.isLocked"> <!-- つまりリモートフォローの場合。 --> <span v-if="full" :class="$style.text,{[$style.gamingDark]: gaming === 'dark' ,[$style.gamingLight]: gaming === 'light'} ">{{ i18n.ts.processing }}</span> <MkLoading :em="true" :colored="false"/> </template> <template v-else-if="isFollowing"> <span v-if="full" :class="$style.text,{[$style.gamingDark]: gaming === 'dark',[$style.gamingLight]: gaming === 'light' } ">{{ i18n.ts.unfollow }}</span><i class="ti ti-minus"></i> </template> <template v-else-if="!isFollowing && user.isLocked"> <span v-if="full" :class="$style.text,{[$style.gamingDark]: gaming === 'dark' ,[$style.gamingLight]: gaming === 'light'} ">{{ i18n.ts.followRequest }}</span><i class="ti ti-plus"></i> </template> <template v-else-if="!isFollowing && !user.isLocked"> <span v-if="full" :class="$style.text,{[$style.gamingDark]: gaming === 'dark' ,[$style.gamingLight]: gaming === 'light'} ">{{ i18n.ts.follow }}</span><i class="ti ti-plus"></i> </template> </template> <template v-else> <span v-if="full" :class="$style.text,{[$style.gamingDark]: gaming === 'dark' ,[$style.gamingLight]: gaming === 'light'} ">{{ i18n.ts.processing }}</span> <MkLoading :em="true" :colored="false"/> </template> </button> </template> <script lang="ts" setup> import {computed, onBeforeUnmount, onMounted, ref, watch} from 'vue'; import * as Misskey from 'misskey-js'; import * as os from '@/os.js'; import {useStream} from '@/stream.js'; import {i18n} from '@/i18n.js'; import {claimAchievement} from '@/scripts/achievements.js'; import {$i} from '@/account.js'; import {defaultStore} from "@/store.js"; let gaming = ref(''); const gamingMode = computed(defaultStore.makeGetterSetter('gamingMode')); const darkMode = computed(defaultStore.makeGetterSetter('darkMode')); if (darkMode.value && gamingMode.value == true) { gaming.value = 'dark'; } else if (!darkMode.value && gamingMode.value == true) { gaming.value = 'light'; } else { gaming.value = ''; } watch(darkMode, () => { if (darkMode.value && gamingMode.value == true) { gaming.value = 'dark'; } else if (!darkMode.value && gamingMode.value == true) { gaming.value = 'light'; } else { gaming.value = ''; } }) watch(gamingMode, () => { if (darkMode.value && gamingMode.value == true) { gaming.value = 'dark'; } else if (!darkMode.value && gamingMode.value == true) { gaming.value = 'light'; } else { gaming.value = ''; } }) const props = withDefaults(defineProps<{ user: Misskey.entities.UserDetailed, full?: boolean, large?: boolean, }>(), { full: false, large: false, }); let isFollowing = $ref(props.user.isFollowing); let hasPendingFollowRequestFromYou = $ref(props.user.hasPendingFollowRequestFromYou); let wait = $ref(false); const connection = useStream().useChannel('main'); if (props.user.isFollowing == null) { os.api('users/show', { userId: props.user.id, }) .then(onFollowChange); } function onFollowChange(user: Misskey.entities.UserDetailed) { if (user.id === props.user.id) { isFollowing = user.isFollowing; hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou; } } async function onClick() { wait = true; try { if (isFollowing) { const {canceled} = await os.confirm({ type: 'warning', text: i18n.t('unfollowConfirm', {name: props.user.name || props.user.username}), }); if (canceled) return; await os.api('following/delete', { userId: props.user.id, }); } else { if (hasPendingFollowRequestFromYou) { await os.api('following/requests/cancel', { userId: props.user.id, }); hasPendingFollowRequestFromYou = false; } else { await os.api('following/create', { userId: props.user.id, }); hasPendingFollowRequestFromYou = true; claimAchievement('following1'); if ($i.followingCount >= 10) { claimAchievement('following10'); } if ($i.followingCount >= 50) { claimAchievement('following50'); } if ($i.followingCount >= 100) { claimAchievement('following100'); } if ($i.followingCount >= 300) { claimAchievement('following300'); } } } } catch (err) { console.error(err); } finally { wait = false; } } onMounted(() => { connection.on('follow', onFollowChange); connection.on('unfollow', onFollowChange); }); onBeforeUnmount(() => { connection.dispose(); }); </script> <style lang="scss" module> .root { position: relative; display: inline-block; font-weight: bold; color: var(--fgOnWhite); border: solid 1px; padding: 0; height: 31px; font-size: 16px; border-radius: 32px; background: #fff; &.gamingDark { color: black; background: linear-gradient(270deg, #e7a2a2, #e3cfa2, #ebefa1, #b3e7a6, #a6ebe7, #aec5e3, #cabded, #e0b9e3, #f4bddd); background-size: 1800% 1800%; -webkit-animation: AnimationDark 44s cubic-bezier(0, 0.2, 0.90, 1) infinite; -moz-animation: AnimationDark 44s cubic-bezier(0, 0.2, 0.90, 1) infinite; animation: AnimationDark 44s cubic-bezier(0, 0.2, 0.90, 1) infinite; -webkit-background-clip: text; -webkit-text-fill-color: transparent; } &.gamingLight { color: #dee7e4; background: linear-gradient(270deg, #c06161, #c0a567, #b6ba69, #81bc72, #63c3be, #8bacd6, #9f8bd6, #d18bd6, #d883b4); background-size: 1800% 1800% !important; -webkit-animation: AnimationLight 45s cubic-bezier(0, 0.2, 0.90, 1) infinite !important; -moz-animation: AnimationLight 45s cubic-bezier(0, 0.2, 0.90, 1) infinite !important; animation: AnimationLight 45s cubic-bezier(0, 0.2, 0.90, 1) infinite !important; -webkit-background-clip: text; -webkit-text-fill-color: transparent; } &.full { padding: 0 8px 0 12px; font-size: 14px; } &.large { font-size: 16px; height: 38px; padding: 0 12px 0 16px; } &:not(.full) { width: 31px; } &:focus-visible { &:after { content: ""; pointer-events: none; position: absolute; top: -5px; right: -5px; bottom: -5px; left: -5px; border: 2px solid var(--focus); border-radius: 32px; } } &:hover { //background: mix($primary, #fff, 20); } &:active { //background: mix($primary, #fff, 40); } &.active { color: var(--fgOnAccent); background: var(--accent); &:hover { background: var(--accentLighten); border-color: var(--accentLighten); } &:active { background: var(--accentDarken); border-color: var(--accentDarken); } &.gamingDark:hover { -webkit-text-fill-color: unset; color: white; background: linear-gradient(270deg, #e7a2a2, #e3cfa2, #ebefa1, #b3e7a6, #a6ebe7, #aec5e3, #cabded, #e0b9e3, #f4bddd); background-size: 1800% 1800%; -webkit-animation: AnimationDark 44s cubic-bezier(0, 0.2, 0.90, 1) infinite; -moz-animation: AnimationDark 44s cubic-bezier(0, 0.2, 0.90, 1) infinite; animation: AnimationDark 44s cubic-bezier(0, 0.2, 0.90, 1) infinite; } &.gamingDark:active { -webkit-text-fill-color: unset !important; color: white; background: linear-gradient(270deg, #e7a2a2, #e3cfa2, #ebefa1, #b3e7a6, #a6ebe7, #aec5e3, #cabded, #e0b9e3, #f4bddd); background-size: 1800% 1800%; -webkit-animation: AnimationDark 44s cubic-bezier(0, 0.2, 0.90, 1) infinite; -moz-animation: AnimationDark 44s cubic-bezier(0, 0.2, 0.90, 1) infinite; animation: AnimationDark 44s cubic-bezier(0, 0.2, 0.90, 1) infinite; border-color: white; } &.gamingLight:hover { -webkit-text-fill-color: unset !important; background: linear-gradient(270deg, #c06161, #c0a567, #b6ba69, #81bc72, #63c3be, #8bacd6, #9f8bd6, #d18bd6, #d883b4); background-size: 1800% 1800% !important; -webkit-animation: AnimationLight 45s cubic-bezier(0, 0.2, 0.90, 1) infinite !important; -moz-animation: AnimationLight 45s cubic-bezier(0, 0.2, 0.90, 1) infinite !important; animation: AnimationLight 45s cubic-bezier(0, 0.2, 0.90, 1) infinite !important; border-color: white; } &.gamingLight:active { -webkit-text-fill-color: unset !important; color: white; background: linear-gradient(270deg, #c06161, #c0a567, #b6ba69, #81bc72, #63c3be, #8bacd6, #9f8bd6, #d18bd6, #d883b4); background-size: 1800% 1800% !important; -webkit-animation: AnimationLight 45s cubic-bezier(0, 0.2, 0.90, 1) infinite !important; -moz-animation: AnimationLight 45s cubic-bezier(0, 0.2, 0.90, 1) infinite !important; animation: AnimationLight 45s cubic-bezier(0, 0.2, 0.90, 1) infinite !important; border-color: white; } &.gamingDark { -webkit-text-fill-color: unset !important; color: white; background: linear-gradient(270deg, #e7a2a2, #e3cfa2, #ebefa1, #b3e7a6, #a6ebe7, #aec5e3, #cabded, #e0b9e3, #f4bddd); background-size: 1800% 1800%; -webkit-animation: AnimationDark 44s cubic-bezier(0, 0.2, 0.90, 1) infinite; -moz-animation: AnimationDark 44s cubic-bezier(0, 0.2, 0.90, 1) infinite; animation: AnimationDark 44s cubic-bezier(0, 0.2, 0.90, 1) infinite; } &.gamingLight { -webkit-text-fill-color: unset !important; color: white; background: linear-gradient(270deg, #c06161, #c0a567, #b6ba69, #81bc72, #63c3be, #8bacd6, #9f8bd6, #d18bd6, #d883b4); background-size: 1800% 1800% !important; -webkit-animation: AnimationLight 45s cubic-bezier(0, 0.2, 0.90, 1) infinite !important; -moz-animation: AnimationLight 45s cubic-bezier(0, 0.2, 0.90, 1) infinite !important; animation: AnimationLight 45s cubic-bezier(0, 0.2, 0.90, 1) infinite !important; } } &.wait { cursor: wait !important; opacity: 0.7; } } .text { margin-right: 6px; &.gamingDark { color: black; -webkit-text-fill-color: unset !important; } &.gamingLight { color: white; -webkit-text-fill-color: unset !important; } } @-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>