Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 50 additions & 22 deletions packages/mask/src/plugins/Avatar/SNSAdaptor/NFTAvatarRing.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { makeStyles } from '@masknet/theme'
import { RainbowBox } from './RainbowBox'
import { rainbowBorderKeyFrames, RainbowBox } from './RainbowBox'
import { useMount } from 'react-use'
import { searchTwitterAvatarLinkSelector } from '../../../social-network-adaptor/twitter.com/utils/selector'

const useStyles = makeStyles()((theme) => ({
root: {
Expand All @@ -20,24 +22,57 @@ export function NFTAvatarRing(props: NFTAvatarRingProps) {
const { stroke, strokeWidth, fontSize, text, width, id } = props

const avatarSize = width + 3
const R = width + 2 * strokeWidth - 4
const r = R / 2 - strokeWidth
const path_r = R / 2 - strokeWidth + fontSize / 2
const x1 = R / 2 - path_r / 2
const y1 = R / 2 + Math.sqrt(Math.pow(path_r, 2) - Math.pow(path_r / 2, 2))
const x2 = R / 2 + path_r / 2
const y2 = y1
const R = avatarSize / 2
const path_r = R - strokeWidth + fontSize / 2
const x1 = R - path_r / 2
const y1 = R + Math.sqrt(Math.pow(path_r, 2) - Math.pow(path_r / 2, 2))
const x2 = R + path_r / 2

useMount(() => {
const linkDom = searchTwitterAvatarLinkSelector().evaluate()

if (linkDom?.firstElementChild && linkDom.childNodes.length === 4) {
const linkParentDom = linkDom.closest('div')

if (linkParentDom) linkParentDom.style.overflow = 'visible'

// remove useless border
linkDom.removeChild(linkDom.firstElementChild)

// create rainbow shadow border
if (linkDom.firstElementChild.tagName !== 'style') {
const style = document.createElement('style')
style.innerText = `
${rainbowBorderKeyFrames.styles}

.rainbowBorder {
animation: ${rainbowBorderKeyFrames.name} 6s linear infinite;
box-shadow: 0 5px 15px rgba(0, 248, 255, 0.4), 0 10px 30px rgba(37, 41, 46, 0.2);
transition: .125s ease;
border: 2px solid #00f8ff;
}
`
linkDom.firstElementChild.classList.add('rainbowBorder')
linkDom.insertBefore(style, linkDom.firstChild)
}
}
})

return (
<RainbowBox width={avatarSize} height={avatarSize}>
<svg className={classes.root} width={avatarSize} height={avatarSize} viewBox={`0 0 ${R} ${R}`} id={id}>
<RainbowBox>
<svg
className={classes.root}
width="100%"
height="100%"
viewBox={`0 0 ${avatarSize} ${avatarSize}`}
id={id}>
<defs>
<path
id={`${id}-path`}
fill="none"
stroke="none"
strokeWidth="0"
d={`M${x1} ${y1} A${path_r} ${path_r} 0 1 1 ${x2} ${y2}`}
d={`M${x1} ${y1} A${path_r} ${path_r} 0 1 1 ${x2} ${y1}`}
/>
<linearGradient id={`${id}-gradient`} x1="0%" y1="0%" x2="100%" y2="0">
<stop offset="0%" stopColor="#00f8ff" />
Expand All @@ -49,23 +84,16 @@ export function NFTAvatarRing(props: NFTAvatarRingProps) {
</linearGradient>
</defs>

<circle
cx={R / 2}
cy={R / 2}
r={r + strokeWidth / 2}
fill="none"
stroke={stroke}
strokeWidth={strokeWidth}
/>
<circle cx={R} cy={R} r={R - strokeWidth / 2} fill="none" stroke={stroke} strokeWidth={strokeWidth} />
<pattern id={`${id}-pattern`} x="0" y="0" width="300%" height="100%" patternUnits="userSpaceOnUse">
<circle cx={R / 2} cy={R / 2} r={R / 2} fill={`url(#${id}-gradient)`}>
<circle cx={R} cy={R} r={R} fill={`url(#${id}-gradient)`}>
<animateTransform
attributeName="transform"
type="rotate"
dur="10s"
repeatCount="indefinite"
from={`0 ${R / 2} ${R / 2}`}
to={`360 ${R / 2} ${R / 2}`}
from={`0 ${R} ${R}`}
to={`360 ${R} ${R}`}
/>
</circle>
</pattern>
Expand Down
58 changes: 28 additions & 30 deletions packages/mask/src/plugins/Avatar/SNSAdaptor/RainbowBox.tsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,41 @@
import { keyframes, makeStyles, useStylesExtends } from '@masknet/theme'
import type { Keyframes } from '@emotion/serialize'

const rainbowBorderKeyFrames = keyframes`
0%,to {
border-color: #00f8ff;
box-shadow: 0 5px 15px rgba(0,248,255,.4),0 10px 30px rgba(37,41,46,.2);
}

20% {
border-color: #a4ff00;
box-shadow: 0 5px 15px rgba(164,255,0,.4),0 10px 30px rgba(37,41,46,.2);
}

40% {
border-color: #f7275e;
box-shadow: 0 5px 15px rgba(247,39,94,.4),0 10px 30px rgba(37,41,46,.2)
}

60% {
border-color: #ffd300;
box-shadow: 0 5px 15px rgba(255,211,0,.4),0 10px 30px rgba(37,41,46,.2)
}

80% {
border-color: #ff8a00;
box-shadow: 0 5px 15px rgba(255,138,0,.4),0 10px 30px rgba(37,41,46,.2)
}
export const rainbowBorderKeyFrames: Keyframes = keyframes`
0%,
to {
border-color: #00f8ff;
box-shadow: 0 5px 15px rgba(0, 248, 255, 0.4), 0 10px 30px rgba(37, 41, 46, 0.2);
}
20% {
border-color: #a4ff00;
box-shadow: 0 5px 15px rgba(164, 255, 0, 0.4), 0 10px 30px rgba(37, 41, 46, 0.2);
}
40% {
border-color: #f7275e;
box-shadow: 0 5px 15px rgba(247, 39, 94, 0.4), 0 10px 30px rgba(37, 41, 46, 0.2);
}
60% {
border-color: #ffd300;
box-shadow: 0 5px 15px rgba(255, 211, 0, 0.4), 0 10px 30px rgba(37, 41, 46, 0.2);
}
80% {
border-color: #ff8a00;
box-shadow: 0 5px 15px rgba(255, 138, 0, 0.4), 0 10px 30px rgba(37, 41, 46, 0.2);
}
`

interface StyleProps {
width: number
height: number
width?: number
height?: number
radius?: string
}
const useStyles = makeStyles<StyleProps>()((theme, { width, height, radius = '100%' }) => ({
root: {
animation: `${rainbowBorderKeyFrames} 6s linear infinite`,
width,
height,
boxShadow: 'box-shadow: 0 5px 15px rgb(0 248 255 / 40%), 0 10px 30px rgb(37 41 46 / 20%);',
boxShadow: '0 5px 15px rgba(0, 248, 255, 0.4), 0 10px 30px rgba(37, 41, 46, 0.2)',
transition: '.125s ease',
borderRadius: radius,
display: 'flex',
Expand All @@ -49,8 +47,8 @@ const useStyles = makeStyles<StyleProps>()((theme, { width, height, radius = '10
}))

interface RainbowBoxProps extends withClasses<'root'> {
width: number
height: number
width?: number
height?: number
radius?: string
children?: React.ReactNode
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { createReactRootShadowed, MaskMessages, NFTAvatarEvent, startWatch } from '../../../../utils'
import { searchTwitterAvatarLinkSelector, searchTwitterAvatarSelector } from '../../utils/selector'
import { searchTwitterAvatarSelector } from '../../utils/selector'
import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit'
import { makeStyles } from '@masknet/theme'
import { useState, useEffect } from 'react'
import { useState, useEffect, useMemo } from 'react'

import { useCurrentVisitingIdentity } from '../../../../components/DataSource/useActivatedUI'
import { useWallet } from '@masknet/web3-shared-evm'
Expand All @@ -12,45 +12,22 @@ import { getAvatarId } from '../../utils/user'
import { PluginNFTAvatarRPC } from '../../../../plugins/Avatar/messages'
import { NFTBadge } from '../../../../plugins/Avatar/SNSAdaptor/NFTBadge'
import { NFTAvatar } from '../../../../plugins/Avatar/SNSAdaptor/NFTAvatar'
import { useWindowSize } from 'react-use'

const offset = 10
export function injectNFTAvatarInTwitter(signal: AbortSignal) {
const watcher = new MutationObserverWatcher(searchTwitterAvatarSelector())
startWatch(watcher, signal)
createReactRootShadowed(watcher.firstDOMProxy.afterShadow, { signal }).render(<NFTAvatarInTwitter />)
}

interface StyleProps {
width: number
size: number
}

const useStyles = makeStyles<StyleProps>()((theme, props) => ({
const useStyles = makeStyles()(() => ({
root: {
position: 'absolute',
bottom: '-10px !important',
textAlign: 'center',
color: 'white',
minWidth: 134,
zIndex: 2,

left: -1 * props.width + offset / 2,
top: -1 * props.width + offset / 2,
width: props.size,
height: props.size,

[`@media (max-width: ${theme.breakpoints.values.sm}px)`]: {
left: -27,
top: -1 * props.width,
},
},
update: {
position: 'absolute',
bottom: '-10px !important',
left: 55,
textAlign: 'center',
color: 'white',
minWidth: 134,
width: '100%',
height: '100%',
},
text: {
fontSize: '20px !important',
Expand All @@ -67,14 +44,19 @@ function NFTAvatarInTwitter() {
const wallet = useWallet()
const { value: _avatar } = useNFTAvatar(identity.identifier.userId)
const [avatar, setAvatar] = useState<AvatarMetaDB | undefined>()
const ele = searchTwitterAvatarLinkSelector().evaluate()
let size = 170
const width = 15
if (ele) {
const style = window.getComputedStyle(ele)
size = Number(style.width.replace('px', '') ?? 0) - Number(style.borderWidth.replace('px', '') ?? 0) - offset
}
const { classes } = useStyles({ size: size + width * 2, width })

const windowSize = useWindowSize()

const size = useMemo(() => {
const ele = searchTwitterAvatarSelector().evaluate()
if (ele) {
const style = window.getComputedStyle(ele)
return Number.parseInt(style.width.replace('px', '') ?? 0, 10)
}
return 0
}, [windowSize])

const { classes } = useStyles()

const [NFTEvent, setNFTEvent] = useState<NFTAvatarEvent>()
const onUpdate = (data: NFTAvatarEvent) => {
Expand Down Expand Up @@ -120,26 +102,7 @@ function NFTAvatarInTwitter() {
return MaskMessages.events.NFTAvatarUpdated.on((data) => onUpdate(data))
}, [onUpdate])

useEffect(() => {
if (!avatar || !avatar.avatarId) return
if (getAvatarId(identity.avatar ?? '') !== avatar.avatarId) return
const avatarDom = searchTwitterAvatarSelector().evaluate()?.parentElement
if (avatarDom) {
avatarDom.style.marginBottom = '10px'
avatarDom.style.overflow = 'unset'
}

const backgroundImgDom = searchTwitterAvatarSelector().evaluate()?.firstChild?.nextSibling?.firstChild
?.firstChild as HTMLElement
if (backgroundImgDom) {
backgroundImgDom.style.borderRadius = '100%'
}
}, [identity, avatar, searchTwitterAvatarSelector, searchTwitterAvatarSelector])

if (!avatar) return null

const avatarParent = searchTwitterAvatarSelector().closest(2).evaluate() as HTMLElement
if (avatarParent) avatarParent.style.clipPath = 'unset'
if (!avatar || !size) return null

return (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export const searchProfileTabListSelector = () =>
export const searchForegroundColorSelector: () => LiveSelector<E, true> = () =>
querySelector<E>('[data-testid="primaryColumn"] > div > div > div > div > div > div > div > div > div > div')
export const searchNewTweetButtonSelector: () => LiveSelector<E, true> = () => {
const q = querySelector<E>('[data-testid="FloatingActionButtons_Tweet_Button"')
const q = querySelector<E>('[data-testid="FloatingActionButtons_Tweet_Button"]')
if (q.evaluate()) return q
return querySelector<E>('[data-testid="SideNav_NewTweet_Button"]')
}
Expand Down Expand Up @@ -225,17 +225,14 @@ export const searchProfessionalButtonSelector = () =>
//#endregion

//#region avatar selector

export const searchTwitterAvatarLinkSelector: () => LiveSelector<E, true> = () =>
querySelector<E, true>('[data-testid="UserProfileHeader_Items"]').closest<E>(2).querySelector('div a')

export const searchTwitterAvatarSelector = () => searchTwitterAvatarLinkSelector().querySelector<E>('div')
export const searchTwitterAvatarSelector = () =>
querySelector<E, true>('[data-testid="UserProfileHeader_Items"]').closest<E>(2).querySelector('img').closest<E>(1)
//#endregion

//#region twitter avatar
export const searchAccountSwitherButtonSelector = () =>
querySelector<E>('[data-testid="SideNav_AccountSwitcher_Button"]')

export const searchUseCellSelector = () => querySelector<E>('[data-testid="UserCell"]')
//#endregion

Expand Down