import './account-info.css';
import {
Menu,
MenuDivider,
MenuHeader,
MenuItem,
SubMenu,
} from '@szhsin/react-menu';
import { useEffect, useRef, useState } from 'preact/hooks';
import RelativeTime from '../components/relative-time';
import { api } from '../utils/api';
import emojifyText from '../utils/emojify-text';
import enhanceContent from '../utils/enhance-content';
import handleContentLinks from '../utils/handle-content-links';
import niceDateTime from '../utils/nice-date-time';
import shortenNumber from '../utils/shorten-number';
import showToast from '../utils/show-toast';
import states, { hideAllModals } from '../utils/states';
import store from '../utils/store';
import AccountBlock from './account-block';
import Avatar from './avatar';
import Icon from './icon';
import Link from './link';
const MUTE_DURATIONS = [
1000 * 60 * 5, // 5 minutes
1000 * 60 * 30, // 30 minutes
1000 * 60 * 60, // 1 hour
1000 * 60 * 60 * 6, // 6 hours
1000 * 60 * 60 * 24, // 1 day
1000 * 60 * 60 * 24 * 3, // 3 days
1000 * 60 * 60 * 24 * 7, // 1 week
0, // forever
];
const MUTE_DURATIONS_LABELS = {
0: 'Forever',
300_000: '5 minutes',
1_800_000: '30 minutes',
3_600_000: '1 hour',
21_600_000: '6 hours',
86_400_000: '1 day',
259_200_000: '3 days',
604_800_000: '1 week',
};
function AccountInfo({
account,
fetchAccount = () => {},
standalone,
instance,
authenticated,
}) {
const [uiState, setUIState] = useState('default');
const isString = typeof account === 'string';
const [info, setInfo] = useState(isString ? null : account);
useEffect(() => {
if (!isString) {
setInfo(account);
return;
}
setUIState('loading');
(async () => {
try {
const info = await fetchAccount();
states.accounts[`${info.id}@${instance}`] = info;
setInfo(info);
setUIState('default');
} catch (e) {
console.error(e);
setInfo(null);
setUIState('error');
}
})();
}, [isString, account, fetchAccount]);
const {
acct,
avatar,
avatarStatic,
bot,
createdAt,
displayName,
emojis,
fields,
followersCount,
followingCount,
group,
// header,
// headerStatic,
id,
lastStatusAt,
locked,
note,
statusesCount,
url,
username,
} = info || {};
let headerIsAvatar = false;
let { header, headerStatic } = info || {};
if (!header || /missing\.png$/.test(header)) {
if (avatar && !/missing\.png$/.test(avatar)) {
header = avatar;
headerIsAvatar = true;
if (avatarStatic && !/missing\.png$/.test(avatarStatic)) {
headerStatic = avatarStatic;
}
}
}
const [headerCornerColors, setHeaderCornerColors] = useState([]);
return (
{uiState === 'error' && (
)}
{uiState === 'loading' ? (
<>
████████ ███████
███████████████ ███████████████
Posts
██
Following
██
Followers
██
>
) : (
info && (
<>
{header && !/missing\.png$/.test(header) && (
{
if (e.target.crossOrigin) {
if (e.target.src !== headerStatic) {
e.target.src = headerStatic;
} else {
e.target.removeAttribute('crossorigin');
e.target.src = header;
}
} else if (e.target.src !== headerStatic) {
e.target.src = headerStatic;
} else {
e.target.remove();
}
}}
crossOrigin="anonymous"
onLoad={(e) => {
try {
// Get color from four corners of image
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = e.target.width;
canvas.height = e.target.height;
ctx.drawImage(e.target, 0, 0);
// const colors = [
// ctx.getImageData(0, 0, 1, 1).data,
// ctx.getImageData(e.target.width - 1, 0, 1, 1).data,
// ctx.getImageData(0, e.target.height - 1, 1, 1).data,
// ctx.getImageData(
// e.target.width - 1,
// e.target.height - 1,
// 1,
// 1,
// ).data,
// ];
// Get 10x10 pixels from corners, get average color from each
const pixelDimension = 10;
const colors = [
ctx.getImageData(0, 0, pixelDimension, pixelDimension)
.data,
ctx.getImageData(
e.target.width - pixelDimension,
0,
pixelDimension,
pixelDimension,
).data,
ctx.getImageData(
0,
e.target.height - pixelDimension,
pixelDimension,
pixelDimension,
).data,
ctx.getImageData(
e.target.width - pixelDimension,
e.target.height - pixelDimension,
pixelDimension,
pixelDimension,
).data,
].map((data) => {
let r = 0;
let g = 0;
let b = 0;
let a = 0;
for (let i = 0; i < data.length; i += 4) {
r += data[i];
g += data[i + 1];
b += data[i + 2];
a += data[i + 3];
}
const dataLength = data.length / 4;
return [
r / dataLength,
g / dataLength,
b / dataLength,
a / dataLength,
];
});
const rgbColors = colors.map((color) => {
const [r, g, b, a] = lightenRGB(color);
return `rgba(${r}, ${g}, ${b}, ${a})`;
});
setHeaderCornerColors(rgbColors);
console.log({ colors, rgbColors });
} catch (e) {
// Silently fail
}
}}
/>
)}
{bot && (
<>
Automated
>
)}
{fields?.length > 0 && (
)}
{standalone ? (
Posts
{shortenNumber(statusesCount)}
{' '}
) : (
{
hideAllModals();
}}
>
Posts
{shortenNumber(statusesCount)}
{' '}
)}
Following
{shortenNumber(followingCount)}
{' '}
Followers
{shortenNumber(followersCount)}
{' '}
{!!createdAt && (
Joined
)}
>
)
)}
);
}
function RelatedActions({ info, instance, authenticated }) {
if (!info) return null;
const {
masto: currentMasto,
instance: currentInstance,
authenticated: currentAuthenticated,
} = api();
const sameInstance = instance === currentInstance;
const [relationshipUIState, setRelationshipUIState] = useState('default');
const [relationship, setRelationship] = useState(null);
const [familiarFollowers, setFamiliarFollowers] = useState([]);
const { id, acct, url, username, locked, lastStatusAt } = info;
const accountID = useRef(id);
const {
following,
showingReblogs,
notifying,
followedBy,
blocking,
blockedBy,
muting,
mutingNotifications,
requested,
domainBlocking,
endorsed,
} = relationship || {};
const [currentInfo, setCurrentInfo] = useState(null);
const [isSelf, setIsSelf] = useState(false);
useEffect(() => {
if (info) {
const currentAccount = store.session.get('currentAccount');
let currentID;
(async () => {
if (sameInstance && authenticated) {
currentID = id;
} else if (!sameInstance && currentAuthenticated) {
// Grab this account from my logged-in instance
const acctHasInstance = info.acct.includes('@');
try {
const results = await currentMasto.v2.search({
q: acctHasInstance ? info.acct : `${info.username}@${instance}`,
type: 'accounts',
limit: 1,
resolve: true,
});
console.log('🥏 Fetched account from logged-in instance', results);
if (results.accounts.length) {
currentID = results.accounts[0].id;
setCurrentInfo(results.accounts[0]);
}
} catch (e) {
console.error(e);
}
}
if (!currentID) return;
if (currentAccount === currentID) {
// It's myself!
setIsSelf(true);
return;
}
accountID.current = currentID;
setRelationshipUIState('loading');
setFamiliarFollowers([]);
const fetchRelationships = currentMasto.v1.accounts.fetchRelationships([
currentID,
]);
const fetchFamiliarFollowers =
currentMasto.v1.accounts.fetchFamiliarFollowers(currentID);
try {
const relationships = await fetchRelationships;
console.log('fetched relationship', relationships);
if (relationships.length) {
const relationship = relationships[0];
setRelationship(relationship);
if (!relationship.following) {
try {
const followers = await fetchFamiliarFollowers;
console.log('fetched familiar followers', followers);
setFamiliarFollowers(followers[0].accounts.slice(0, 10));
} catch (e) {
console.error(e);
}
}
}
setRelationshipUIState('default');
} catch (e) {
console.error(e);
setRelationshipUIState('error');
}
})();
}
}, [info, authenticated]);
const loading = relationshipUIState === 'loading';
const menuInstanceRef = useRef(null);
return (
<>
{familiarFollowers?.length > 0 && (
Common followers{' '}
{familiarFollowers.map((follower) => (
{
e.preventDefault();
states.showAccount = {
account: follower,
instance,
};
}}
>
))}
)}
{followedBy ? (
Following you
) : (
Last status:
)}{' '}
{!!relationship && (
)}
>
);
}
// Apply more alpha if high luminence
function lightenRGB([r, g, b]) {
const luminence = 0.2126 * r + 0.7152 * g + 0.0722 * b;
console.log('luminence', luminence);
let alpha;
if (luminence >= 220) {
alpha = 1;
} else if (luminence <= 50) {
alpha = 0.1;
} else {
alpha = luminence / 255;
}
alpha = Math.min(1, alpha);
return [r, g, b, alpha];
}
function niceAccountURL(url) {
if (!url) return;
const urlObj = new URL(url);
const { host, pathname } = urlObj;
const path = pathname.replace(/\/$/, '').replace(/^\//, '');
return (
<>
{host}/
{path}
>
);
}
export default AccountInfo;