Skip to content

Commit

Permalink
chocolate-curry-19 Implement "Share"
Browse files Browse the repository at this point in the history
- Uses html2canvas with a custom patch to fix image size
  • Loading branch information
infinia-yzl committed Jul 8, 2024
1 parent 4e45950 commit 83d6cea
Show file tree
Hide file tree
Showing 7 changed files with 489 additions and 61 deletions.
4 changes: 2 additions & 2 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {ZenToggle} from "@/components/ZenToggle";
const Home = () => {
return (
<main className="flex flex-col items-center justify-between">
<div className="flex flex-row p-4 w-full justify-between items-center align-middle">
<div className="flex flex-row p-4 w-full justify-between items-center align-middle" data-html2canvas-ignore>
<h1 className="text-xl">Tier Scribble</h1>
<div className="space-x-1">
<ZenToggle/>
Expand All @@ -16,7 +16,7 @@ const Home = () => {
</span>
</div>
</div>
<Separator className="mb-8"/>
<Separator className="mb-8" data-html2canvas-ignore/>
<TierListManager initialTiers={DEFAULT_TIER_TEMPLATE}/>
</main>
);
Expand Down
51 changes: 25 additions & 26 deletions components/ItemTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,42 +19,43 @@ const ItemTile: React.FC<Item> = ({
setIsZenMode(document.documentElement.classList.contains('zen-mode'));
};

// Check initial state
checkZenMode();

// Set up a MutationObserver to watch for changes to the class on the html element
const observer = new MutationObserver(checkZenMode);
observer.observe(document.documentElement, {attributes: true, attributeFilter: ['class']});

// Cleanup
return () => observer.disconnect();
}, []);

const tileContent = (
<div
className="relative flex flex-col items-center justify-center text-center w-12 h-12 sm:w-16 sm:h-16 md:w-20 md:h-20 overflow-hidden"
>
<div className="relative w-16 h-16 sm:w-20 sm:h-20 md:w-24 md:h-24 overflow-hidden rounded-md">
{imageUrl ? (
<>
<Image
src={imageUrl}
alt={content}
width={128}
height={128}
style={{
objectFit: 'cover',
}}
/>
<div
className={`absolute bottom-0 left-0 right-0 p-0.5 bg-black bg-opacity-30 backdrop-blur-sm
transition-opacity duration-180 ease-in-out
${showLabel ? 'opacity-100' : 'opacity-0'}`}
>
<span className="text-[8px] leading-tight text-white block truncate">{content}</span>
<div className="absolute inset-0 overflow-hidden">
<Image
src={imageUrl}
alt={content}
fill
style={{
objectFit: "cover"
}}
/>
</div>
{showLabel && (
<div
className="absolute bottom-0 left-0 right-0 bg-black bg-opacity-30 p-1 backdrop-blur-sm transition-opacity duration-180 ease-in-out"
>
<span
className="text-[8px] leading-tight text-white block mb-0.5"
>
{content}
</span>
</div>
)}
</>
) : (
<div className="m-4 p-4">{content}</div>
<div className="flex items-center justify-center h-full text-sm">
{content}
</div>
)}
</div>
);
Expand All @@ -65,9 +66,7 @@ const ItemTile: React.FC<Item> = ({

return (
<ContextMenu>
<ContextMenuTrigger>
{tileContent}
</ContextMenuTrigger>
<ContextMenuTrigger>{tileContent}</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuItem
onClick={() => onDelete(id)}
Expand Down
145 changes: 145 additions & 0 deletions components/ShareButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import React, {useState} from 'react';
import html2canvas from 'html2canvas';
import Image from 'next/image';
import {Button} from "@/components/ui/button"
import {Popover, PopoverContent, PopoverTrigger} from "@/components/ui/popover"
import {Share1Icon, CopyIcon, TwitterLogoIcon} from '@radix-ui/react-icons';
import {toast} from 'sonner';

type SocialPlatform = 'facebook' | 'twitter';

const ShareButton: React.FC = () => {
const [imageBlob, setImageBlob] = useState<Blob | null>(null);

const captureImage = async () => {
// Uses a custom override https://github.com/niklasvh/html2canvas/issues/1064#issuecomment-1829552577
const root = document.body;

// Get the device pixel ratio
const scale = window.devicePixelRatio;

// Create a custom canvas
const canvas = document.createElement("canvas");
canvas.width = root.offsetWidth * scale;
canvas.height = root.offsetHeight * scale;

const ctx = canvas.getContext("2d");
if (!ctx) {
console.error("Unable to get 2D context");
return;
}

// Store the original drawImage function
const originalDrawImage = ctx.drawImage.bind(ctx);

// Override the drawImage function
// @ts-ignore
ctx.drawImage = function (image, sx, sy, sw, sh, dx, dy, dw, dh) {
if (image instanceof HTMLImageElement) {
if (sw && sh && dw && dh) {
if (sw / dw < sh / dh) {
const _dh = dh;
dh = sh * (dw / sw);
dy = dy + (_dh - dh) / 2;
} else {
const _dw = dw;
dw = sw * (dh / sh);
dx = dx + (_dw - dw) / 2;
}
}
}

return originalDrawImage(image, sx, sy, sw, sh, dx, dy, dw, dh);
};

try {
// Capture the image using html2canvas
await html2canvas(root, {
canvas,
scale,
});

// Convert canvas to blob
canvas.toBlob((blob) => {
if (blob) {
setImageBlob(blob);
}
}, 'image/png');
} catch (error) {
console.error("Error capturing image:", error);
toast.error("Failed to capture image");
}
};


const copyImage = () => {
if (imageBlob) {
navigator.clipboard.write([
new ClipboardItem({'image/png': imageBlob})
]).then(() => {
toast.success('Image copied to clipboard');
})
.catch(err => {
console.error('Error copying image: ', err);
toast.error('Failed to copy image');
});
}
};

const shareToSocial = (platform: SocialPlatform) => {
copyImage();
const shareText = "Check out my tier list! [Paste image here]";
let url: string;

if (platform === 'facebook') {
url = `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(window.location.href)}&quote=${encodeURIComponent(shareText)}`;
} else {
url = `https://twitter.com/intent/tweet?text=${encodeURIComponent(shareText)}&url=${encodeURIComponent(window.location.href)}`;
}

window.open(url, '_blank');
toast.success(`Sharing to ${platform}`, {
description: "Image copied. Please paste it into your post on the opened page."
});
};

return (
<Popover>
<PopoverTrigger asChild>
<Button variant="outline" size="icon" onClick={captureImage}>
<Share1Icon className="h-4 w-4"/>
</Button>
</PopoverTrigger>
<PopoverContent className="w-[400]">
<div className="grid gap-4">
<h4 className="font-medium leading-none">Share Tier List</h4>
{imageBlob && (
<div className="relative w-full h-64">
<Image
src={URL.createObjectURL(imageBlob)}
alt="Captured tier list"
fill
style={{
objectFit: "contain"
}}
/>
</div>
)}
<div className="flex justify-between">
<Button variant="outline" size="icon" onClick={copyImage}>
<CopyIcon className="h-4 w-4"/>
</Button>
<Button variant="outline" size="icon" onClick={() => shareToSocial('facebook')}>
F
</Button>
<Button variant="outline" size="icon" onClick={() => shareToSocial('twitter')}>
<TwitterLogoIcon className="h-4 w-4"/>
</Button>
</div>
</div>
</PopoverContent>
</Popover>
);
};

export default ShareButton;
6 changes: 4 additions & 2 deletions components/TierListManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import EditableLabel from "@/components/EditableLabel";
import ItemManager from "@/components/ItemManager";
import Tier, {LabelPosition} from "@/models/Tier";
import Item from "@/models/Item";
import ShareButton from "@/components/ShareButton";

interface TierListManagerProps {
initialTiers: Tier[];
Expand Down Expand Up @@ -147,7 +148,7 @@ const TierListManager: React.FC<TierListManagerProps> = ({initialTiers}) => {
contentClassName="scroll-m-20 border-b pb-2 text-3xl font-semibold tracking-tight first:mt-0 text-center"/>
</div>
<div className="flex flex-col items-center hide-in-zen">
<div className="flex space-x-2" id="settings">
<div className="flex space-x-2" id="settings" data-html2canvas-ignore>
<TierTemplateSelector/>
<ItemManager
onItemsCreate={handleItemsCreate}
Expand All @@ -157,8 +158,9 @@ const TierListManager: React.FC<TierListManagerProps> = ({initialTiers}) => {
undoReset={undoReset}
undoDelete={undoDelete}
/>
<ShareButton/>
</div>
<div className="p-2">
<div className="p-2" data-html2canvas-ignore>
<p className="text-sm text-muted-foreground text-center">
Drag to reorder.
</p>
Expand Down
Loading

0 comments on commit 83d6cea

Please sign in to comment.