Skip to content

Commit 59d0e54

Browse files
Simplified share profile widget interface in ActivityPub (#24485)
ref PROD-2262 - Updated the copies - Removed download button and kept only copy-to-clipboard functionality for image generation - Simplified user experience by focusing on the most useful sharing method
1 parent e8ecdaf commit 59d0e54

File tree

2 files changed

+53
-65
lines changed

2 files changed

+53
-65
lines changed

apps/admin-x-activitypub/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@tryghost/admin-x-activitypub",
3-
"version": "0.9.8",
3+
"version": "0.9.9",
44
"license": "MIT",
55
"repository": {
66
"type": "git",

apps/admin-x-activitypub/src/views/Preferences/components/Profile.tsx

Lines changed: 52 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import {memo, useCallback, useEffect, useRef, useState} from 'react';
22

33
import APAvatar from '@src/components/global/APAvatar';
44
import DotsPattern from './DotsPattern';
5+
import html2canvas from 'html2canvas-objectfit-fix';
56
import {Account} from '@src/api/activitypub';
67
import {Button, H2, LoadingIndicator, LucideIcon, Skeleton, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger} from '@tryghost/shade';
78
import {imageUrlToDataUrl} from '@src/utils/image';
8-
import {takeScreenshot} from '@src/utils/screenshot';
99
import {toast} from 'sonner';
1010
import {useBrowseSite} from '@tryghost/admin-x-framework/api/site';
1111
import {useFeatureFlags} from '@src/lib/feature-flags';
@@ -80,8 +80,8 @@ const ProfileCard: React.FC<ProfileCardProps> = memo(({
8080
const margin = isScreenshot ? 'm-4' : 'm-16';
8181
const borderClass = isScreenshot ? backgroundColor === 'light' ? 'border border-gray-200' : '' : 'shadow-xl';
8282

83-
const cardWidth = format === 'square' ? 'w-[392px]' : 'w-[316px]';
84-
const cardHeight = 'h-[392px]';
83+
const cardWidth = format === 'square' ? 'w-[422px]' : 'w-[316px]';
84+
const cardHeight = 'h-[422px]';
8585

8686
const bannerImageSrc = isScreenshot && bannerDataUrl ? bannerDataUrl : (account?.bannerImageUrl || coverImage);
8787
const avatarImageSrc = isScreenshot && avatarDataUrl ? avatarDataUrl : (account?.avatarUrl || publicationIcon);
@@ -119,7 +119,7 @@ const ProfileCard: React.FC<ProfileCardProps> = memo(({
119119
</div>
120120
<div className={`flex grow flex-col items-center p-6 ${(account?.avatarUrl || publicationIcon) ? 'pt-9' : 'pt-3'} text-center ${format === 'square' ? 'flex-1 justify-center' : ''}`}>
121121
<H2 className={`${isScreenshot && 'tracking-normal'}`} style={{color: textColor}}>{!isLoading ? account?.name : <Skeleton className='w-32' />}</H2>
122-
<span className={`mt-0.5 text-lg ${isScreenshot && 'tracking-normal'}`} style={{color: textColor}}>{!isLoading ? 'Now on the Social Web!' : <Skeleton className='w-28' />}</span>
122+
<span className={`mt-1.5 leading-7 ${isScreenshot && 'tracking-normal'}`} style={{color: textColor}}>{!isLoading ? 'Available on Ghost, Flipboard, Threads, Bluesky, Mastodon, or wherever you get your social web feeds.' : <Skeleton className='w-28' />}</span>
123123
<div
124124
className={`mt-auto flex max-h-[60px] min-h-12 w-full items-center justify-center rounded-full border px-4 py-2 font-medium leading-7 ${isScreenshot && 'tracking-normal'}`}
125125
style={{
@@ -146,11 +146,10 @@ const Profile: React.FC<ProfileProps> = ({account, isLoading}) => {
146146
const profileCardRef = useRef<HTMLDivElement>(null);
147147
const [backgroundColor, setBackgroundColor] = useState<'light' | 'dark' | 'accent'>('light');
148148
const [cardFormat, setCardFormat] = useState<'vertical' | 'square'>('vertical');
149-
const [isAltKeyHeld, setIsAltKeyHeld] = useState(false);
150149
const [isProcessing, setIsProcessing] = useState(false);
151150
const [bannerDataUrl, setBannerDataUrl] = useState<string | null>(null);
152151
const [avatarDataUrl, setAvatarDataUrl] = useState<string | null>(null);
153-
const shareText = `You can now follow ${account?.name} on the social web, on ${account?.handle}`;
152+
const shareText = `${account?.name} is now available across the social web, on ${account?.handle}`;
154153

155154
const convertImagesToDataUrls = useCallback(async () => {
156155
if (account?.bannerImageUrl || coverImage) {
@@ -186,35 +185,6 @@ const Profile: React.FC<ProfileProps> = ({account, isLoading}) => {
186185
};
187186
}, [convertImagesToDataUrls]);
188187

189-
// Listen for Alt key press/release
190-
useEffect(() => {
191-
const handleKeyDown = (event: KeyboardEvent) => {
192-
if (event.altKey && !isAltKeyHeld) {
193-
setIsAltKeyHeld(true);
194-
}
195-
};
196-
197-
const handleKeyUp = (event: KeyboardEvent) => {
198-
if (!event.altKey && isAltKeyHeld) {
199-
setIsAltKeyHeld(false);
200-
}
201-
};
202-
203-
const handleWindowBlur = () => {
204-
setIsAltKeyHeld(false);
205-
};
206-
207-
window.addEventListener('keydown', handleKeyDown);
208-
window.addEventListener('keyup', handleKeyUp);
209-
window.addEventListener('blur', handleWindowBlur);
210-
211-
return () => {
212-
window.removeEventListener('keydown', handleKeyDown);
213-
window.removeEventListener('keyup', handleKeyUp);
214-
window.removeEventListener('blur', handleWindowBlur);
215-
};
216-
}, [isAltKeyHeld]);
217-
218188
const getGradient = () => {
219189
switch (backgroundColor) {
220190
case 'light':
@@ -241,38 +211,61 @@ const Profile: React.FC<ProfileProps> = ({account, isLoading}) => {
241211
}
242212
};
243213

244-
const handleDownload = async (event: React.MouseEvent) => {
214+
const handleCopy = async () => {
245215
if (!profileCardRef.current || isProcessing) {
246216
return;
247217
}
248218

249219
setIsProcessing(true);
250220

251-
await new Promise<void>((resolve) => {
252-
setTimeout(() => resolve(), 100);
221+
// Wait for the next frame to ensure the loading indicator is painted
222+
await new Promise((resolve) => {
223+
requestAnimationFrame(() => {
224+
requestAnimationFrame(resolve);
225+
});
253226
});
254227

255-
const shouldCopyToClipboard = event.altKey;
256-
257228
try {
258-
await takeScreenshot(profileCardRef.current, {
259-
filename: `${account?.handle}.png`,
260-
backgroundColor: 'transparent',
261-
copyToClipboard: shouldCopyToClipboard
262-
});
229+
if (!navigator.clipboard || !('write' in navigator.clipboard) || typeof ClipboardItem === 'undefined') {
230+
throw new Error('Clipboard API not supported in this browser');
231+
}
263232

264-
if (shouldCopyToClipboard) {
233+
try {
234+
const blobPromise = new Promise<Blob>(async (resolve, reject) => {
235+
try {
236+
const canvas = await html2canvas(profileCardRef.current!, {
237+
backgroundColor: 'transparent',
238+
scale: 2,
239+
logging: false,
240+
useCORS: true,
241+
allowTaint: true,
242+
imageTimeout: 0
243+
});
244+
245+
canvas.toBlob((blob) => {
246+
if (blob) {
247+
resolve(blob);
248+
} else {
249+
reject(new Error('Failed to create blob'));
250+
}
251+
}, 'image/png');
252+
} catch (error) {
253+
reject(error);
254+
}
255+
});
256+
257+
const clipboardItem = new ClipboardItem({
258+
'image/png': blobPromise
259+
});
260+
261+
await navigator.clipboard.write([clipboardItem]);
265262
toast.success('Image copied to clipboard');
266-
} else {
267-
toast.success('Image downloaded');
263+
} catch (error) {
264+
toast.error('Failed to copy image');
268265
}
266+
setIsProcessing(false);
269267
} catch (error) {
270-
if (shouldCopyToClipboard) {
271-
toast.error(`Failed to copy image`);
272-
} else {
273-
toast.error(`Failed to download image`);
274-
}
275-
} finally {
268+
toast.error('Failed to copy image');
276269
setIsProcessing(false);
277270
}
278271
};
@@ -386,15 +379,10 @@ const Profile: React.FC<ProfileProps> = ({account, isLoading}) => {
386379
<svg fill="none" viewBox="0 0 16 16"><g clipPath="url(#social-linkedin_svg__clip0_537_833)"><path className="social-linkedin_svg__linkedin" clipRule="evenodd" d="M1.778 16h12.444c.982 0 1.778-.796 1.778-1.778V1.778C16 .796 15.204 0 14.222 0H1.778C.796 0 0 .796 0 1.778v12.444C0 15.204.796 16 1.778 16z" fill="#007ebb" fillRule="evenodd"></path><path clipRule="evenodd" d="M13.778 13.778h-2.374V9.734c0-1.109-.421-1.729-1.299-1.729-.955 0-1.453.645-1.453 1.729v4.044H6.363V6.074h2.289v1.038s.688-1.273 2.322-1.273c1.634 0 2.804.997 2.804 3.061v4.878zM3.634 5.065c-.78 0-1.411-.636-1.411-1.421s.631-1.422 1.41-1.422c.78 0 1.411.637 1.411 1.422 0 .785-.631 1.421-1.41 1.421zm-1.182 8.713h2.386V6.074H2.452v7.704z" fill="#fff" fillRule="evenodd"></path></g><defs><clipPath id="social-linkedin_svg__clip0_537_833"><path d="M0 0h16v16H0z" fill="#fff"></path></clipPath></defs></svg>
387380
</a>
388381
</div>
389-
<Tooltip>
390-
<TooltipTrigger>
391-
<Button className={`min-w-[160px] dark:bg-black dark:text-white dark:hover:bg-black/90 ${backgroundColor === 'dark' && 'bg-white text-black hover:bg-gray-50 dark:bg-white dark:text-black dark:hover:bg-gray-50/90'}`} onClick={handleDownload}>
392-
{isProcessing ? <LoadingIndicator color={`${backgroundColor === 'dark' ? 'dark' : 'light'}`} size='sm' /> : isAltKeyHeld ? <LucideIcon.Copy /> : <LucideIcon.Download />}
393-
{!isProcessing && (isAltKeyHeld ? 'Copy image' : 'Download image')}
394-
</Button>
395-
</TooltipTrigger>
396-
<TooltipContent>Hold Alt/Option to copy to clipboard</TooltipContent>
397-
</Tooltip>
382+
<Button className={`min-w-[160px] dark:bg-black dark:text-white dark:hover:bg-black/90 ${backgroundColor === 'dark' && 'bg-white text-black hover:bg-gray-50 dark:bg-white dark:text-black dark:hover:bg-gray-50/90'}`} onClick={handleCopy}>
383+
{isProcessing ? <LoadingIndicator color={`${backgroundColor === 'dark' ? 'dark' : 'light'}`} size='sm' /> : <LucideIcon.Copy />}
384+
{!isProcessing && 'Copy image'}
385+
</Button>
398386
</div>
399387
{(account?.bannerImageUrl || coverImage) &&
400388
<DotsPattern className={`absolute left-1/2 top-1/2 h-[600px] w-[598px] -translate-x-1/2 -translate-y-1/2 ${backgroundColor === 'dark' && 'z-10'}`} style={{color: getDotsPatternColor()}} />
@@ -407,7 +395,7 @@ const Profile: React.FC<ProfileProps> = ({account, isLoading}) => {
407395
ref={profileCardRef}
408396
className='fixed left-[-9999px] top-0 z-[-1] flex w-fit justify-center overflow-hidden rounded-2xl bg-gray-50'
409397
style={{
410-
width: cardFormat === 'square' ? '424px' : '348px',
398+
width: cardFormat === 'square' ? '454px' : '348px',
411399
fontFamily: 'system-ui'
412400
}}
413401
>

0 commit comments

Comments
 (0)