fix share log
Browse files
src/lib/components/Piclets/PicletDetail.svelte
CHANGED
@@ -3,7 +3,6 @@
|
|
3 |
import type { PicletInstance } from '$lib/db/schema';
|
4 |
import { deletePicletInstance } from '$lib/db/piclets';
|
5 |
import { uiStore } from '$lib/stores/ui';
|
6 |
-
import { downloadPicletCard } from '$lib/services/picletExport';
|
7 |
import { TYPE_DATA } from '$lib/types/picletTypes';
|
8 |
import AbilityDisplay from './AbilityDisplay.svelte';
|
9 |
import MoveDisplay from './MoveDisplay.svelte';
|
@@ -19,7 +18,6 @@
|
|
19 |
|
20 |
let { instance, onClose, onDeleted }: Props = $props();
|
21 |
let selectedTab = $state<'about' | 'abilities'>('about');
|
22 |
-
let isSharing = $state(false);
|
23 |
|
24 |
// Ensure stats are up-to-date with current level and nature
|
25 |
const updatedInstance = $derived(recalculatePicletStats(instance));
|
@@ -70,16 +68,8 @@
|
|
70 |
}
|
71 |
|
72 |
|
73 |
-
|
74 |
-
|
75 |
-
try {
|
76 |
-
await downloadPicletCard(instance);
|
77 |
-
} catch (err) {
|
78 |
-
console.error('Failed to share piclet:', err);
|
79 |
-
alert('Failed to create shareable image');
|
80 |
-
} finally {
|
81 |
-
isSharing = false;
|
82 |
-
}
|
83 |
}
|
84 |
</script>
|
85 |
|
@@ -106,7 +96,6 @@
|
|
106 |
<button
|
107 |
class="share-button"
|
108 |
onclick={handleShare}
|
109 |
-
disabled={isSharing}
|
110 |
aria-label="Share Piclet"
|
111 |
>
|
112 |
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
3 |
import type { PicletInstance } from '$lib/db/schema';
|
4 |
import { deletePicletInstance } from '$lib/db/piclets';
|
5 |
import { uiStore } from '$lib/stores/ui';
|
|
|
6 |
import { TYPE_DATA } from '$lib/types/picletTypes';
|
7 |
import AbilityDisplay from './AbilityDisplay.svelte';
|
8 |
import MoveDisplay from './MoveDisplay.svelte';
|
|
|
18 |
|
19 |
let { instance, onClose, onDeleted }: Props = $props();
|
20 |
let selectedTab = $state<'about' | 'abilities'>('about');
|
|
|
21 |
|
22 |
// Ensure stats are up-to-date with current level and nature
|
23 |
const updatedInstance = $derived(recalculatePicletStats(instance));
|
|
|
68 |
}
|
69 |
|
70 |
|
71 |
+
function handleShare() {
|
72 |
+
console.log("placeholder");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
73 |
}
|
74 |
</script>
|
75 |
|
|
|
96 |
<button
|
97 |
class="share-button"
|
98 |
onclick={handleShare}
|
|
|
99 |
aria-label="Share Piclet"
|
100 |
>
|
101 |
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
src/lib/services/picletExport.ts
DELETED
@@ -1,233 +0,0 @@
|
|
1 |
-
import type { PicletInstance } from '$lib/db/schema';
|
2 |
-
import { embedPicletMetadata } from './picletMetadata';
|
3 |
-
|
4 |
-
/**
|
5 |
-
* Generates a shareable image of a piclet with embedded metadata
|
6 |
-
*/
|
7 |
-
export async function generateShareableImage(piclet: PicletInstance): Promise<Blob> {
|
8 |
-
// Create canvas
|
9 |
-
const canvas = document.createElement('canvas');
|
10 |
-
const ctx = canvas.getContext('2d');
|
11 |
-
if (!ctx) throw new Error('Could not create canvas context');
|
12 |
-
|
13 |
-
// Polyfill for roundRect if not available
|
14 |
-
if (!ctx.roundRect) {
|
15 |
-
ctx.roundRect = function(x: number, y: number, width: number, height: number, radius: number) {
|
16 |
-
ctx.beginPath();
|
17 |
-
ctx.moveTo(x + radius, y);
|
18 |
-
ctx.lineTo(x + width - radius, y);
|
19 |
-
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
|
20 |
-
ctx.lineTo(x + width, y + height - radius);
|
21 |
-
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
|
22 |
-
ctx.lineTo(x + radius, y + height);
|
23 |
-
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
|
24 |
-
ctx.lineTo(x, y + radius);
|
25 |
-
ctx.quadraticCurveTo(x, y, x + radius, y);
|
26 |
-
ctx.closePath();
|
27 |
-
};
|
28 |
-
}
|
29 |
-
|
30 |
-
// Set canvas size - full piclet size plus padding for name and stats
|
31 |
-
const canvasWidth = 1024;
|
32 |
-
const canvasHeight = 1400; // Extra height for name and stats
|
33 |
-
canvas.width = canvasWidth;
|
34 |
-
canvas.height = canvasHeight;
|
35 |
-
|
36 |
-
// Fill with striped background like battle view
|
37 |
-
ctx.fillStyle = '#f8f9fa';
|
38 |
-
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
|
39 |
-
|
40 |
-
// Create striped pattern
|
41 |
-
const stripeHeight = 10;
|
42 |
-
ctx.fillStyle = 'rgba(76, 175, 80, 0.2)'; // Light green
|
43 |
-
for (let y = 0; y < canvasHeight; y += stripeHeight * 2) {
|
44 |
-
ctx.fillRect(0, y, canvasWidth, stripeHeight);
|
45 |
-
}
|
46 |
-
|
47 |
-
// Add alternating darker stripes
|
48 |
-
ctx.fillStyle = 'rgba(76, 175, 80, 0.1)'; // Lighter green
|
49 |
-
for (let y = stripeHeight; y < canvasHeight; y += stripeHeight * 2) {
|
50 |
-
ctx.fillRect(0, y, canvasWidth, stripeHeight);
|
51 |
-
}
|
52 |
-
|
53 |
-
// Load piclet image first
|
54 |
-
const picletImg = await loadImage(piclet.imageData || piclet.imageUrl);
|
55 |
-
const picletSize = 1024; // Full size!
|
56 |
-
const picletX = 0;
|
57 |
-
const picletY = 150; // Leave room for name at top
|
58 |
-
|
59 |
-
// Load and draw grass platform positioned under the piclet
|
60 |
-
const grassImg = await loadImage('/assets/grass.PNG');
|
61 |
-
const platformSize = 1200; // Larger than piclet for proper positioning
|
62 |
-
const platformX = (canvasWidth - platformSize) / 2;
|
63 |
-
const platformY = picletY + picletSize - 300 - 384; // Platform moved up by 384px
|
64 |
-
ctx.drawImage(grassImg, platformX, platformY, platformSize, platformSize);
|
65 |
-
|
66 |
-
// Draw piclet on top of platform
|
67 |
-
ctx.drawImage(picletImg, picletX, picletY, picletSize, picletSize);
|
68 |
-
|
69 |
-
// Add sleek modern text styling
|
70 |
-
const nameText = piclet.nickname || piclet.typeId;
|
71 |
-
|
72 |
-
// Create gradient for text
|
73 |
-
const gradient = ctx.createLinearGradient(0, 50, 0, 120);
|
74 |
-
gradient.addColorStop(0, '#ffffff');
|
75 |
-
gradient.addColorStop(1, '#e0e0e0');
|
76 |
-
|
77 |
-
// Modern sleek font with gradient and subtle shadow
|
78 |
-
ctx.font = 'bold 72px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif';
|
79 |
-
ctx.textAlign = 'center';
|
80 |
-
ctx.textBaseline = 'middle';
|
81 |
-
|
82 |
-
// Subtle shadow for depth
|
83 |
-
ctx.shadowColor = 'rgba(0, 0, 0, 0.3)';
|
84 |
-
ctx.shadowBlur = 15;
|
85 |
-
ctx.shadowOffsetX = 0;
|
86 |
-
ctx.shadowOffsetY = 3;
|
87 |
-
|
88 |
-
// Dark outline for contrast
|
89 |
-
ctx.strokeStyle = '#2c3e50';
|
90 |
-
ctx.lineWidth = 4;
|
91 |
-
ctx.strokeText(nameText.toUpperCase(), canvasWidth / 2, 80);
|
92 |
-
|
93 |
-
// Fill with gradient
|
94 |
-
ctx.fillStyle = gradient;
|
95 |
-
ctx.fillText(nameText.toUpperCase(), canvasWidth / 2, 80);
|
96 |
-
|
97 |
-
// Reset shadow
|
98 |
-
ctx.shadowColor = 'transparent';
|
99 |
-
ctx.shadowBlur = 0;
|
100 |
-
ctx.shadowOffsetX = 0;
|
101 |
-
ctx.shadowOffsetY = 0;
|
102 |
-
|
103 |
-
// Draw base stats in a sleek format
|
104 |
-
const statsY = picletY + picletSize + 50; // Position below piclet
|
105 |
-
const stats = [
|
106 |
-
{ label: 'HP', value: piclet.baseHp, color: '#4caf50' },
|
107 |
-
{ label: 'ATK', value: piclet.baseAttack, color: '#f44336' },
|
108 |
-
{ label: 'DEF', value: piclet.baseDefense, color: '#2196f3' },
|
109 |
-
{ label: 'S.ATK', value: piclet.baseFieldAttack, color: '#ff9800' },
|
110 |
-
{ label: 'S.DEF', value: piclet.baseFieldDefense, color: '#9c27b0' },
|
111 |
-
{ label: 'SPD', value: piclet.baseSpeed, color: '#00bcd4' }
|
112 |
-
];
|
113 |
-
|
114 |
-
// Stats container background
|
115 |
-
const containerX = 100;
|
116 |
-
const containerY = statsY - 20;
|
117 |
-
const containerWidth = canvasWidth - 200;
|
118 |
-
const containerHeight = 150;
|
119 |
-
|
120 |
-
// Draw semi-transparent background using the polyfilled roundRect
|
121 |
-
ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
|
122 |
-
ctx.beginPath();
|
123 |
-
(ctx as any).roundRect(containerX, containerY, containerWidth, containerHeight, 20);
|
124 |
-
ctx.fill();
|
125 |
-
|
126 |
-
// Draw stats bars
|
127 |
-
const barHeight = 16;
|
128 |
-
const barSpacing = 20;
|
129 |
-
const maxStatValue = 255;
|
130 |
-
const barMaxWidth = containerWidth - 240; // Leave room for labels and values
|
131 |
-
|
132 |
-
stats.forEach((stat, index) => {
|
133 |
-
const y = statsY + (index * barSpacing);
|
134 |
-
|
135 |
-
// Draw stat label
|
136 |
-
ctx.font = 'bold 14px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif';
|
137 |
-
ctx.fillStyle = '#ffffff';
|
138 |
-
ctx.textAlign = 'left';
|
139 |
-
ctx.fillText(stat.label, containerX + 20, y + 3);
|
140 |
-
|
141 |
-
// Draw stat value
|
142 |
-
ctx.textAlign = 'right';
|
143 |
-
ctx.fillText(stat.value.toString(), containerX + 100, y + 3);
|
144 |
-
|
145 |
-
// Draw background bar
|
146 |
-
const barX = containerX + 120;
|
147 |
-
ctx.fillStyle = 'rgba(255, 255, 255, 0.2)';
|
148 |
-
ctx.beginPath();
|
149 |
-
(ctx as any).roundRect(barX, y - 8, barMaxWidth, barHeight, 8);
|
150 |
-
ctx.fill();
|
151 |
-
|
152 |
-
// Draw filled bar
|
153 |
-
const fillWidth = (stat.value / maxStatValue) * barMaxWidth;
|
154 |
-
ctx.fillStyle = stat.color;
|
155 |
-
ctx.beginPath();
|
156 |
-
(ctx as any).roundRect(barX, y - 8, fillWidth, barHeight, 8);
|
157 |
-
ctx.fill();
|
158 |
-
|
159 |
-
// Add shine effect
|
160 |
-
const shineGradient = ctx.createLinearGradient(barX, y - 8, barX, y - 8 + barHeight);
|
161 |
-
shineGradient.addColorStop(0, 'rgba(255, 255, 255, 0.3)');
|
162 |
-
shineGradient.addColorStop(0.5, 'rgba(255, 255, 255, 0.1)');
|
163 |
-
shineGradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
|
164 |
-
ctx.fillStyle = shineGradient;
|
165 |
-
ctx.beginPath();
|
166 |
-
(ctx as any).roundRect(barX, y - 8, fillWidth, barHeight / 2, 8);
|
167 |
-
ctx.fill();
|
168 |
-
});
|
169 |
-
|
170 |
-
// Draw BST (Base Stat Total)
|
171 |
-
const bst = piclet.bst || (piclet.baseHp + piclet.baseAttack + piclet.baseDefense +
|
172 |
-
piclet.baseFieldAttack + piclet.baseFieldDefense + piclet.baseSpeed);
|
173 |
-
ctx.font = 'bold 18px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif';
|
174 |
-
ctx.fillStyle = '#ffd700'; // Gold color
|
175 |
-
ctx.textAlign = 'center';
|
176 |
-
ctx.fillText(`BST: ${bst}`, canvasWidth / 2, containerY + containerHeight - 10);
|
177 |
-
|
178 |
-
// Load and draw translucent watermark in top-right
|
179 |
-
const logoImg = await loadImage('/assets/snap_logo.png');
|
180 |
-
const logoSize = 120; // Slightly smaller for top placement
|
181 |
-
ctx.globalAlpha = 0.3; // More translucent
|
182 |
-
ctx.drawImage(logoImg, canvasWidth - logoSize - 30, 30, logoSize, logoSize);
|
183 |
-
ctx.globalAlpha = 1.0;
|
184 |
-
|
185 |
-
// Get the image as blob
|
186 |
-
const blob = await canvasToBlob(canvas);
|
187 |
-
|
188 |
-
// Embed metadata in the blob
|
189 |
-
return embedPicletMetadata(blob, piclet);
|
190 |
-
}
|
191 |
-
|
192 |
-
/**
|
193 |
-
* Downloads a piclet card image
|
194 |
-
*/
|
195 |
-
export async function downloadPicletCard(piclet: PicletInstance, filename?: string): Promise<void> {
|
196 |
-
const blob = await generateShareableImage(piclet);
|
197 |
-
const url = URL.createObjectURL(blob);
|
198 |
-
|
199 |
-
const a = document.createElement('a');
|
200 |
-
a.href = url;
|
201 |
-
a.download = filename || `Piclet_${piclet.nickname || piclet.typeId}_Lv${piclet.level}.png`;
|
202 |
-
document.body.appendChild(a);
|
203 |
-
a.click();
|
204 |
-
document.body.removeChild(a);
|
205 |
-
|
206 |
-
URL.revokeObjectURL(url);
|
207 |
-
}
|
208 |
-
|
209 |
-
/**
|
210 |
-
* Helper to load an image
|
211 |
-
*/
|
212 |
-
function loadImage(src: string): Promise<HTMLImageElement> {
|
213 |
-
return new Promise((resolve, reject) => {
|
214 |
-
const img = new Image();
|
215 |
-
img.crossOrigin = 'anonymous';
|
216 |
-
img.onload = () => resolve(img);
|
217 |
-
img.onerror = reject;
|
218 |
-
img.src = src;
|
219 |
-
});
|
220 |
-
}
|
221 |
-
|
222 |
-
/**
|
223 |
-
* Convert canvas to blob
|
224 |
-
*/
|
225 |
-
function canvasToBlob(canvas: HTMLCanvasElement): Promise<Blob> {
|
226 |
-
return new Promise((resolve, reject) => {
|
227 |
-
canvas.toBlob((blob) => {
|
228 |
-
if (blob) resolve(blob);
|
229 |
-
else reject(new Error('Failed to create blob'));
|
230 |
-
}, 'image/png');
|
231 |
-
});
|
232 |
-
}
|
233 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|