Experimental posting stats for non-following accounts

Also recode+redesign the multiple metadata boxes in account info
This commit is contained in:
Lim Chee Aun 2023-09-15 22:15:41 +08:00
parent b116cbfe8c
commit 9571271d83
2 changed files with 335 additions and 124 deletions

View file

@ -139,13 +139,13 @@
/* flex-wrap: wrap; */
column-gap: 24px;
row-gap: 8px;
opacity: 0.75;
/* opacity: 0.75; */
font-size: 90%;
background-color: var(--bg-faded-color);
padding: 12px;
border-radius: 16px;
/* border-radius: 16px; */
line-height: 1.25;
overflow-x: auto;
overflow-x: auto !important;
justify-content: flex-start;
position: relative;
@ -185,11 +185,33 @@
display: flex;
}
.account-container .account-metadata-box {
overflow: hidden;
border-radius: 16px;
& > * {
margin-bottom: 2px;
border-radius: 4px;
overflow: hidden;
}
&:has(+ .account-metadata-box) {
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
}
+ .account-metadata-box {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
border-bottom-left-radius: 16px;
border-bottom-right-radius: 16px;
}
}
.account-container .profile-metadata {
display: flex;
/* flex-wrap: wrap; */
gap: 2px;
border-radius: 16px;
overflow: hidden;
overflow-x: auto;
}
@ -235,12 +257,11 @@
margin: 0;
}
.account-container .common-followers p {
.account-container .common-followers {
font-size: 90%;
color: var(--text-insignificant-color);
border-top: 1px solid var(--outline-color);
border-bottom: 1px solid var(--outline-color);
padding: 8px 0;
background-color: var(--bg-faded-color);
padding: 8px 12px;
margin: 0;
}
@ -261,6 +282,74 @@
opacity: 0.5;
}
@keyframes swoosh-bg-image {
0% {
background-position: -320px 0;
opacity: 0.25;
}
100% {
background-position: 0 0;
opacity: 1;
}
}
.account-container .posting-stats {
font-size: 90%;
color: var(--text-insignificant-color);
background-color: var(--bg-faded-color);
padding: 8px 12px;
--size: 8px;
--original-color: var(--link-color);
.posting-stats-bar {
height: var(--size);
border-radius: var(--size);
overflow: hidden;
margin: 8px 0;
box-shadow: inset 0 0 0 1px var(--outline-color),
inset 0 0 0 1.5px var(--bg-blur-color);
background-color: var(--bg-color);
background-repeat: no-repeat;
animation: swoosh-bg-image 0.3s ease-in-out 0.3s both;
background-image: linear-gradient(
to right,
var(--original-color) 0%,
var(--original-color) var(--originals-percentage),
var(--reply-to-color) var(--originals-percentage),
var(--reply-to-color) var(--replies-percentage),
var(--reblog-color) var(--replies-percentage),
var(--reblog-color) 100%
);
}
.posting-stats-legends {
font-size: 12px;
text-transform: uppercase;
}
.posting-stats-legend-item {
display: inline-block;
width: var(--size);
height: var(--size);
border-radius: var(--size);
background-color: var(--text-insignificant-color);
vertical-align: middle;
margin: 0 4px 2px;
/* border: 1px solid var(--outline-color); */
box-shadow: inset 0 0 0 1px var(--outline-color),
inset 0 0 0 1.5px var(--bg-blur-color);
&.posting-stats-legend-item-originals {
background-color: var(--original-color);
}
&.posting-stats-legend-item-replies {
background-color: var(--reply-to-color);
}
&.posting-stats-legend-item-boosts {
background-color: var(--reblog-color);
}
}
}
@keyframes shine {
0% {
left: -100%;

View file

@ -357,94 +357,99 @@ function AccountInfo({
__html: enhanceContent(note, { emojis }),
}}
/>
{fields?.length > 0 && (
<div class="profile-metadata">
{fields.map(({ name, value, verifiedAt }, i) => (
<div
class={`profile-field ${
verifiedAt ? 'profile-verified' : ''
}`}
key={name + i}
>
<b>
<EmojiText text={name} emojis={emojis} />{' '}
{!!verifiedAt && <Icon icon="check-circle" size="s" />}
</b>
<p
dangerouslySetInnerHTML={{
__html: enhanceContent(value, { emojis }),
}}
/>
</div>
))}
</div>
)}
<p class="stats">
<LinkOrDiv
tabIndex={0}
to={accountLink}
onClick={() => {
states.showAccount = false;
states.showGenericAccounts = {
heading: 'Followers',
fetchAccounts: fetchFollowers,
};
}}
>
<span title={followersCount}>
{shortenNumber(followersCount)}
</span>{' '}
Followers
</LinkOrDiv>
<LinkOrDiv
class="insignificant"
tabIndex={0}
to={accountLink}
onClick={() => {
states.showAccount = false;
states.showGenericAccounts = {
heading: 'Following',
fetchAccounts: fetchFollowing,
};
}}
>
<span title={followingCount}>
{shortenNumber(followingCount)}
</span>{' '}
Following
<br />
</LinkOrDiv>
<LinkOrDiv
class="insignificant"
to={accountLink}
onClick={
standalone
? undefined
: () => {
hideAllModals();
}
}
>
<span title={statusesCount}>
{shortenNumber(statusesCount)}
</span>{' '}
Posts
</LinkOrDiv>
{!!createdAt && (
<div class="insignificant">
Joined{' '}
<time datetime={createdAt}>
{niceDateTime(createdAt, {
hideTime: true,
})}
</time>
<div class="account-metadata-box">
{fields?.length > 0 && (
<div class="profile-metadata">
{fields.map(({ name, value, verifiedAt }, i) => (
<div
class={`profile-field ${
verifiedAt ? 'profile-verified' : ''
}`}
key={name + i}
>
<b>
<EmojiText text={name} emojis={emojis} />{' '}
{!!verifiedAt && (
<Icon icon="check-circle" size="s" />
)}
</b>
<p
dangerouslySetInnerHTML={{
__html: enhanceContent(value, { emojis }),
}}
/>
</div>
))}
</div>
)}
</p>
<div class="stats">
<LinkOrDiv
tabIndex={0}
to={accountLink}
onClick={() => {
states.showAccount = false;
states.showGenericAccounts = {
heading: 'Followers',
fetchAccounts: fetchFollowers,
};
}}
>
<span title={followersCount}>
{shortenNumber(followersCount)}
</span>{' '}
Followers
</LinkOrDiv>
<LinkOrDiv
class="insignificant"
tabIndex={0}
to={accountLink}
onClick={() => {
states.showAccount = false;
states.showGenericAccounts = {
heading: 'Following',
fetchAccounts: fetchFollowing,
};
}}
>
<span title={followingCount}>
{shortenNumber(followingCount)}
</span>{' '}
Following
<br />
</LinkOrDiv>
<LinkOrDiv
class="insignificant"
to={accountLink}
onClick={
standalone
? undefined
: () => {
hideAllModals();
}
}
>
<span title={statusesCount}>
{shortenNumber(statusesCount)}
</span>{' '}
Posts
</LinkOrDiv>
{!!createdAt && (
<div class="insignificant">
Joined{' '}
<time datetime={createdAt}>
{niceDateTime(createdAt, {
hideTime: true,
})}
</time>
</div>
)}
</div>
</div>
<RelatedActions
info={info}
instance={instance}
authenticated={authenticated}
standalone={standalone}
/>
</main>
</>
@ -454,7 +459,9 @@ function AccountInfo({
);
}
function RelatedActions({ info, instance, authenticated }) {
const FAMILIAR_FOLLOWERS_LIMIT = 10;
function RelatedActions({ info, instance, authenticated, standalone }) {
if (!info) return null;
const {
masto: currentMasto,
@ -466,6 +473,7 @@ function RelatedActions({ info, instance, authenticated }) {
const [relationshipUIState, setRelationshipUIState] = useState('default');
const [relationship, setRelationship] = useState(null);
const [familiarFollowers, setFamiliarFollowers] = useState([]);
const [postingStats, setPostingStats] = useState();
const { id, acct, url, username, locked, lastStatusAt, note, fields } = info;
const accountID = useRef(id);
@ -526,12 +534,11 @@ function RelatedActions({ info, instance, authenticated }) {
setRelationshipUIState('loading');
setFamiliarFollowers([]);
setPostingStats(null);
const fetchRelationships = currentMasto.v1.accounts.fetchRelationships([
currentID,
]);
const fetchFamiliarFollowers =
currentMasto.v1.accounts.fetchFamiliarFollowers(currentID);
try {
const relationships = await fetchRelationships;
@ -542,9 +549,55 @@ function RelatedActions({ info, instance, authenticated }) {
if (!relationship.following) {
try {
const fetchFamiliarFollowers =
currentMasto.v1.accounts.fetchFamiliarFollowers(currentID);
const fetchStatuses = currentMasto.v1.accounts
.listStatuses(currentID, {
limit: 20,
})
.next();
const followers = await fetchFamiliarFollowers;
console.log('fetched familiar followers', followers);
setFamiliarFollowers(followers[0].accounts.slice(0, 10));
setFamiliarFollowers(followers[0].accounts);
if (standalone) return;
const { value: statuses } = await fetchStatuses;
console.log('fetched statuses', statuses);
const stats = {
total: statuses.length,
originals: 0,
replies: 0,
boosts: 0,
};
// Categories statuses by type
// - Original posts (not replies to others)
// - Threads (self-replies + 1st original post)
// - Boosts (reblogs)
// - Replies (not-self replies)
statuses.forEach((status) => {
if (status.reblog) {
stats.boosts++;
} else if (
status.inReplyToAccountId !== currentID &&
!!status.inReplyToId
) {
stats.replies++;
} else {
stats.originals++;
}
});
// Count days since last post
stats.daysSinceLastPost = Math.ceil(
(Date.now() -
new Date(statuses[statuses.length - 1].createdAt)) /
86400000,
);
console.log('posting stats', stats);
setPostingStats(stats);
} catch (e) {
console.error(e);
}
@ -571,40 +624,109 @@ function RelatedActions({ info, instance, authenticated }) {
const [showTranslatedBio, setShowTranslatedBio] = useState(false);
const [showAddRemoveLists, setShowAddRemoveLists] = useState(false);
const hasFamiliarFollowers = familiarFollowers?.length > 0;
const hasPostingStats = postingStats?.total >= 3;
return (
<>
<div
class="common-followers shazam-container no-animation"
hidden={!familiarFollowers?.length}
>
<div class="shazam-container-inner">
<p>
Followed by{' '}
<span class="ib">
{familiarFollowers.map((follower) => (
<a
href={follower.url}
rel="noopener noreferrer"
onClick={(e) => {
e.preventDefault();
states.showAccount = {
account: follower,
instance,
};
}}
>
<Avatar
url={follower.avatarStatic}
size="l"
alt={`${follower.displayName} @${follower.acct}`}
squircle={follower?.bot}
{(hasFamiliarFollowers || hasPostingStats) && (
<div class="account-metadata-box">
{hasFamiliarFollowers && (
<div class="shazam-container">
<div class="shazam-container-inner">
<p class="common-followers">
Followed by{' '}
<span class="ib">
{familiarFollowers
.slice(0, FAMILIAR_FOLLOWERS_LIMIT)
.map((follower) => (
<a
href={follower.url}
rel="noopener noreferrer"
onClick={(e) => {
e.preventDefault();
states.showAccount = {
account: follower,
instance,
};
}}
>
<Avatar
url={follower.avatarStatic}
size="l"
alt={`${follower.displayName} @${follower.acct}`}
squircle={follower?.bot}
/>
</a>
))}
{familiarFollowers.length > FAMILIAR_FOLLOWERS_LIMIT && (
<button
type="button"
class="small plain4"
onClick={() => {
states.showGenericAccounts = {
heading: 'Followed by',
accounts: familiarFollowers,
};
}}
>
+{familiarFollowers.length - FAMILIAR_FOLLOWERS_LIMIT}
<Icon icon="chevron-down" size="s" />
</button>
)}
</span>
</p>
</div>
</div>
)}
{hasPostingStats && (
<div class="shazam-container">
<div class="shazam-container-inner">
<div class="posting-stats">
<div>
{postingStats.daysSinceLastPost < 365
? `Last ${postingStats.total} posts in the past
${postingStats.daysSinceLastPost} day${
postingStats.daysSinceLastPost > 1 ? 's' : ''
}`
: `
Last ${postingStats.total} posts in the past year(s)
`}
</div>
<div
class="posting-stats-bar"
style={{
// [originals | replies | boosts]
'--originals-percentage': `${
(postingStats.originals / postingStats.total) * 100
}%`,
'--replies-percentage': `${
((postingStats.originals + postingStats.replies) /
postingStats.total) *
100
}%`,
}}
/>
</a>
))}
</span>
</p>
<div class="posting-stats-legends">
<span class="ib">
<span class="posting-stats-legend-item posting-stats-legend-item-originals" />{' '}
Original
</span>{' '}
<span class="ib">
<span class="posting-stats-legend-item posting-stats-legend-item-replies" />{' '}
Replies
</span>{' '}
<span class="ib">
<span class="posting-stats-legend-item posting-stats-legend-item-boosts" />{' '}
Boosts
</span>
</div>
</div>
</div>
</div>
)}
</div>
</div>
)}
<p class="actions">
<span>
{followedBy ? (