mirror of
https://github.com/cheeaun/phanpy.git
synced 2025-01-22 16:46:28 +01:00
Refactor action buttons + optimistic UI
This commit is contained in:
parent
1c18184ef4
commit
5f0d1e8656
2 changed files with 111 additions and 99 deletions
|
@ -294,6 +294,7 @@
|
||||||
}
|
}
|
||||||
.status .media-audio {
|
.status .media-audio {
|
||||||
border: 0;
|
border: 0;
|
||||||
|
min-height: 0;
|
||||||
}
|
}
|
||||||
.status .media-audio audio {
|
.status .media-audio audio {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -471,7 +472,7 @@ a.card:hover {
|
||||||
.status .actions > button.plain.reblog-button:hover {
|
.status .actions > button.plain.reblog-button:hover {
|
||||||
color: var(--reblog-color);
|
color: var(--reblog-color);
|
||||||
}
|
}
|
||||||
.status .actions > button.plain.reblog-button.reblogged {
|
.status .actions > button.plain.reblog-button.checked {
|
||||||
color: var(--reblog-color);
|
color: var(--reblog-color);
|
||||||
border-color: var(--reblog-color);
|
border-color: var(--reblog-color);
|
||||||
}
|
}
|
||||||
|
@ -490,13 +491,13 @@ a.card:hover {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.status .actions > button.plain.reblog-button.reblogged .icon {
|
.status .actions > button.plain.reblog-button.checked .icon {
|
||||||
animation: reblogged 1s ease-in-out;
|
animation: reblogged 1s ease-in-out;
|
||||||
}
|
}
|
||||||
.status .actions > button.plain.favourite-button:hover {
|
.status .actions > button.plain.favourite-button:hover {
|
||||||
color: var(--favourite-color);
|
color: var(--favourite-color);
|
||||||
}
|
}
|
||||||
.status .actions > button.plain.favourite-button.favourited {
|
.status .actions > button.plain.favourite-button.checked {
|
||||||
color: var(--favourite-color);
|
color: var(--favourite-color);
|
||||||
border-color: var(--favourite-color);
|
border-color: var(--favourite-color);
|
||||||
}
|
}
|
||||||
|
@ -518,11 +519,11 @@ a.card:hover {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.status .actions > button.plain.favourite-button.favourited .icon {
|
.status .actions > button.plain.favourite-button.checked .icon {
|
||||||
transform-origin: bottom center;
|
transform-origin: bottom center;
|
||||||
animation: hearted 1s ease-in-out;
|
animation: hearted 1s ease-in-out;
|
||||||
}
|
}
|
||||||
.status .actions > button.plain.bookmark-button.bookmarked {
|
.status .actions > button.plain.bookmark-button.checked {
|
||||||
color: var(--link-color);
|
color: var(--link-color);
|
||||||
border-color: var(--link-color);
|
border-color: var(--link-color);
|
||||||
}
|
}
|
||||||
|
@ -544,7 +545,7 @@ a.card:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.status .actions > button.plain.bookmark-button.bookmarked .icon {
|
.status .actions > button.plain.bookmark-button.checked .icon {
|
||||||
animation: bookmarked 1s ease-in-out;
|
animation: bookmarked 1s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -857,42 +857,35 @@ function Status({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button
|
<StatusButton
|
||||||
type="button"
|
title="Reply"
|
||||||
title="Comment"
|
alt="Comments"
|
||||||
class="plain reply-button"
|
class="reply-button"
|
||||||
onClick={(e) => {
|
icon="comment"
|
||||||
e.preventDefault();
|
count={repliesCount}
|
||||||
e.stopPropagation();
|
onClick={() => {
|
||||||
states.showCompose = {
|
states.showCompose = {
|
||||||
replyToStatus: status,
|
replyToStatus: status,
|
||||||
};
|
};
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
<Icon icon="comment" size="l" alt="Reply" />
|
|
||||||
{!!repliesCount && (
|
|
||||||
<>
|
|
||||||
{' '}
|
|
||||||
<small>{shortenNumber(repliesCount)}</small>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
{/* TODO: if visibility = private, only can reblog own statuses */}
|
{/* TODO: if visibility = private, only can reblog own statuses */}
|
||||||
{visibility !== 'direct' && (
|
{visibility !== 'direct' && (
|
||||||
<button
|
<StatusButton
|
||||||
type="button"
|
checked={reblogged}
|
||||||
title={reblogged ? 'Unboost' : 'Boost'}
|
title={['Boost', 'Unboost']}
|
||||||
class={`plain reblog-button ${reblogged ? 'reblogged' : ''}`}
|
alt={['Boost', 'Boosted']}
|
||||||
disabled={actionsUIState === 'boost-loading'}
|
class="reblog-button"
|
||||||
onClick={async (e) => {
|
icon="rocket"
|
||||||
e.preventDefault();
|
count={reblogsCount}
|
||||||
e.stopPropagation();
|
onClick={async () => {
|
||||||
const yes = confirm(
|
|
||||||
reblogged ? 'Unboost this status?' : 'Boost this status?',
|
|
||||||
);
|
|
||||||
if (!yes) return;
|
|
||||||
setActionsUIState('boost-loading');
|
|
||||||
try {
|
try {
|
||||||
|
// Optimistic
|
||||||
|
states.statuses.set(id, {
|
||||||
|
...status,
|
||||||
|
reblogged: !reblogged,
|
||||||
|
reblogsCount: reblogsCount + (reblogged ? -1 : 1),
|
||||||
|
});
|
||||||
if (reblogged) {
|
if (reblogged) {
|
||||||
const newStatus = await masto.statuses.unreblog(id);
|
const newStatus = await masto.statuses.unreblog(id);
|
||||||
states.statuses.set(newStatus.id, newStatus);
|
states.statuses.set(newStatus.id, newStatus);
|
||||||
|
@ -905,38 +898,26 @@ function Status({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert(e);
|
|
||||||
console.error(e);
|
console.error(e);
|
||||||
} finally {
|
|
||||||
setActionsUIState(null);
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
<Icon
|
|
||||||
icon="rocket"
|
|
||||||
size="l"
|
|
||||||
alt={reblogged ? 'Boosted' : 'Boost'}
|
|
||||||
/>
|
|
||||||
{!!reblogsCount && (
|
|
||||||
<>
|
|
||||||
{' '}
|
|
||||||
<small>{shortenNumber(reblogsCount)}</small>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
)}
|
)}
|
||||||
<button
|
<StatusButton
|
||||||
type="button"
|
checked={favourited}
|
||||||
title={favourited ? 'Unfavourite' : 'Favourite'}
|
title={['Favourite', 'Unfavourite']}
|
||||||
class={`plain favourite-button ${
|
alt={['Favourite', 'Favourited']}
|
||||||
favourited ? 'favourited' : ''
|
class="favourite-button"
|
||||||
}`}
|
icon="heart"
|
||||||
disabled={actionsUIState === 'favourite-loading'}
|
count={favouritesCount}
|
||||||
onClick={async (e) => {
|
onClick={async () => {
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
setActionsUIState('favourite-loading');
|
|
||||||
try {
|
try {
|
||||||
|
// Optimistic
|
||||||
|
states.statuses.set(statusID, {
|
||||||
|
...status,
|
||||||
|
favourited: !favourited,
|
||||||
|
favouritesCount: favouritesCount + (favourited ? -1 : 1),
|
||||||
|
});
|
||||||
if (favourited) {
|
if (favourited) {
|
||||||
const newStatus = await masto.statuses.unfavourite(id);
|
const newStatus = await masto.statuses.unfavourite(id);
|
||||||
states.statuses.set(newStatus.id, newStatus);
|
states.statuses.set(newStatus.id, newStatus);
|
||||||
|
@ -945,37 +926,23 @@ function Status({
|
||||||
states.statuses.set(newStatus.id, newStatus);
|
states.statuses.set(newStatus.id, newStatus);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert(e);
|
|
||||||
console.error(e);
|
console.error(e);
|
||||||
} finally {
|
|
||||||
setActionsUIState(null);
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
<Icon
|
<StatusButton
|
||||||
icon="heart"
|
checked={bookmarked}
|
||||||
size="l"
|
title={['Bookmark', 'Unbookmark']}
|
||||||
alt={favourited ? 'Favourited' : 'Favourite'}
|
alt={['Bookmark', 'Bookmarked']}
|
||||||
/>
|
class="bookmark-button"
|
||||||
{!!favouritesCount && (
|
icon="bookmark"
|
||||||
<>
|
onClick={async () => {
|
||||||
{' '}
|
|
||||||
<small>{shortenNumber(favouritesCount)}</small>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
title={bookmarked ? 'Unbookmark' : 'Bookmark'}
|
|
||||||
class={`plain bookmark-button ${
|
|
||||||
bookmarked ? 'bookmarked' : ''
|
|
||||||
}`}
|
|
||||||
disabled={actionsUIState === 'bookmark-loading'}
|
|
||||||
onClick={async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
setActionsUIState('bookmark-loading');
|
|
||||||
try {
|
try {
|
||||||
|
// Optimistic
|
||||||
|
states.statuses.set(statusID, {
|
||||||
|
...status,
|
||||||
|
bookmarked: !bookmarked,
|
||||||
|
});
|
||||||
if (bookmarked) {
|
if (bookmarked) {
|
||||||
const newStatus = await masto.statuses.unbookmark(id);
|
const newStatus = await masto.statuses.unbookmark(id);
|
||||||
states.statuses.set(newStatus.id, newStatus);
|
states.statuses.set(newStatus.id, newStatus);
|
||||||
|
@ -984,19 +951,10 @@ function Status({
|
||||||
states.statuses.set(newStatus.id, newStatus);
|
states.statuses.set(newStatus.id, newStatus);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert(e);
|
|
||||||
console.error(e);
|
console.error(e);
|
||||||
} finally {
|
|
||||||
setActionsUIState(null);
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
<Icon
|
|
||||||
icon="bookmark"
|
|
||||||
size="l"
|
|
||||||
alt={bookmarked ? 'Bookmarked' : 'Bookmark'}
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
{isSelf && (
|
{isSelf && (
|
||||||
<span class="menu-container">
|
<span class="menu-container">
|
||||||
<button type="button" title="More" class="plain more-button">
|
<button type="button" title="More" class="plain more-button">
|
||||||
|
@ -1156,4 +1114,57 @@ function Status({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function StatusButton({
|
||||||
|
checked,
|
||||||
|
count,
|
||||||
|
class: className,
|
||||||
|
title,
|
||||||
|
alt,
|
||||||
|
icon,
|
||||||
|
onClick,
|
||||||
|
...props
|
||||||
|
}) {
|
||||||
|
if (typeof title === 'string') {
|
||||||
|
title = [title, title];
|
||||||
|
}
|
||||||
|
if (typeof alt === 'string') {
|
||||||
|
alt = [alt, alt];
|
||||||
|
}
|
||||||
|
|
||||||
|
const [buttonTitle, setButtonTitle] = useState(title[0] || '');
|
||||||
|
const [iconAlt, setIconAlt] = useState(alt[0] || '');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (checked) {
|
||||||
|
setButtonTitle(title[1] || '');
|
||||||
|
setIconAlt(alt[1] || '');
|
||||||
|
} else {
|
||||||
|
setButtonTitle(title[0] || '');
|
||||||
|
setIconAlt(alt[0] || '');
|
||||||
|
}
|
||||||
|
}, [checked, title, alt]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
title={buttonTitle}
|
||||||
|
class={`plain ${className} ${checked ? 'checked' : ''}`}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
onClick(e);
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<Icon icon={icon} size="l" alt={iconAlt} />
|
||||||
|
{!!count && (
|
||||||
|
<>
|
||||||
|
{' '}
|
||||||
|
<small>{shortenNumber(count)}</small>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default Status;
|
export default Status;
|
||||||
|
|
Loading…
Reference in a new issue