feat: enhance screenshot with auth check, nickname watermark and label capture
parent
7037628831
commit
bdedd30f2f
|
|
@ -15,6 +15,7 @@
|
|||
"@react-three/fiber": "^9.4.0",
|
||||
"antd": "^6.0.0",
|
||||
"axios": "^1.13.2",
|
||||
"html2canvas": "^1.4.1",
|
||||
"lucide-react": "^0.555.0",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
*/
|
||||
import { useState, useCallback, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { message } from 'antd';
|
||||
import { useSpaceData } from './hooks/useSpaceData';
|
||||
import { useHistoricalData } from './hooks/useHistoricalData';
|
||||
import { useTrajectory } from './hooks/useTrajectory';
|
||||
|
|
@ -103,6 +104,18 @@ function App() {
|
|||
setSelectedBody(body);
|
||||
};
|
||||
|
||||
// Screenshot handler with auth check
|
||||
const handleScreenshot = useCallback(() => {
|
||||
if (!user) {
|
||||
message.warning('请先登录以拍摄宇宙快照');
|
||||
setShowAuthModal(true);
|
||||
return;
|
||||
}
|
||||
// Use username or full_name or fallback
|
||||
const nickname = user.full_name || user.username || 'Explorer';
|
||||
takeScreenshot(nickname);
|
||||
}, [user, takeScreenshot]);
|
||||
|
||||
// Auth handlers
|
||||
const handleLoginSuccess = (userData: any) => {
|
||||
setUser(userData);
|
||||
|
|
@ -156,7 +169,7 @@ function App() {
|
|||
onToggleSound={() => setIsSoundOn(!isSoundOn)}
|
||||
showDanmaku={showDanmaku}
|
||||
onToggleDanmaku={() => setShowDanmaku(!showDanmaku)}
|
||||
onScreenshot={takeScreenshot}
|
||||
onScreenshot={handleScreenshot}
|
||||
/>
|
||||
|
||||
{/* Auth Modal */}
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ export function Scene({ bodies, selectedBody, trajectoryPositions = [], showOrbi
|
|||
}, [selectedBody, bodies]);
|
||||
|
||||
return (
|
||||
<div className="w-full h-full bg-black">
|
||||
<div id="cosmo-scene-container" className="w-full h-full bg-black">
|
||||
<Canvas
|
||||
camera={{
|
||||
position: [25, 20, 25], // Closer view to make solar system appear larger
|
||||
|
|
|
|||
|
|
@ -1,57 +1,65 @@
|
|||
import { useCallback } from 'react';
|
||||
import * as THREE from 'three';
|
||||
import html2canvas from 'html2canvas';
|
||||
|
||||
export function useScreenshot() {
|
||||
const takeScreenshot = useCallback(() => {
|
||||
// 1. Find the Three.js Canvas
|
||||
const canvas = document.querySelector('canvas');
|
||||
if (!canvas) {
|
||||
console.error('Canvas not found');
|
||||
const takeScreenshot = useCallback(async (username: string = 'Explorer') => {
|
||||
// 1. Find the container that includes both the Canvas and the HTML overlays (labels)
|
||||
const element = document.getElementById('cosmo-scene-container');
|
||||
if (!element) {
|
||||
console.error('Scene container not found');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 2. Create a temporary 2D canvas for compositing
|
||||
const tempCanvas = document.createElement('canvas');
|
||||
const width = canvas.width;
|
||||
const height = canvas.height;
|
||||
tempCanvas.width = width;
|
||||
tempCanvas.height = height;
|
||||
const ctx = tempCanvas.getContext('2d');
|
||||
// 2. Use html2canvas to capture the visual composite
|
||||
const canvas = await html2canvas(element, {
|
||||
backgroundColor: '#000000',
|
||||
useCORS: true, // Allow loading cross-origin images (textures)
|
||||
logging: false,
|
||||
scale: window.devicePixelRatio, // Capture at high resolution
|
||||
allowTaint: true, // Allow tainted canvas (might limit editing but okay for saving)
|
||||
ignoreElements: (_el) => {
|
||||
// Ignore elements that we don't want in the screenshot (if any end up inside)
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) return;
|
||||
|
||||
// 3. Draw the 3D scene onto the temp canvas
|
||||
ctx.drawImage(canvas, 0, 0, width, height);
|
||||
const width = canvas.width;
|
||||
const height = canvas.height;
|
||||
|
||||
// 4. Get current camera info (approximate from scene if possible, or just current date)
|
||||
// 3. Add Overlay / Watermark
|
||||
const now = new Date();
|
||||
const dateStr = now.toLocaleDateString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit' });
|
||||
const timeStr = now.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
||||
|
||||
// We can try to get camera position if we had access to the store,
|
||||
// but for now let's stick to generic data or we can grab it from global window if exposed
|
||||
// Better: just show the Date and App Name.
|
||||
|
||||
// 5. Add Overlay / Watermark
|
||||
// Background gradient for text
|
||||
const gradient = ctx.createLinearGradient(0, height - 100, 0, height);
|
||||
// Background gradient for text legibility
|
||||
const gradient = ctx.createLinearGradient(0, height - 150, 0, height);
|
||||
gradient.addColorStop(0, 'transparent');
|
||||
gradient.addColorStop(1, 'rgba(0,0,0,0.8)');
|
||||
gradient.addColorStop(1, 'rgba(0,0,0,0.9)');
|
||||
ctx.fillStyle = gradient;
|
||||
ctx.fillRect(0, height - 150, width, 150);
|
||||
ctx.fillRect(0, height - 200, width, 200);
|
||||
|
||||
// App Name
|
||||
// Text Settings
|
||||
ctx.shadowColor = 'rgba(0,0,0,0.5)';
|
||||
ctx.shadowBlur = 4;
|
||||
|
||||
// User Name & App Name (Bottom Right)
|
||||
ctx.textAlign = 'right';
|
||||
|
||||
// Nickname@Cosmo
|
||||
ctx.font = 'bold 32px sans-serif';
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.textAlign = 'right';
|
||||
ctx.fillText('Cosmo', width - 40, height - 60);
|
||||
ctx.fillText(`${username}@Cosmo`, width - 40, height - 60);
|
||||
|
||||
// Subtitle
|
||||
ctx.font = '16px sans-serif';
|
||||
ctx.fillStyle = '#aaaaaa';
|
||||
ctx.fillText('DEEP SPACE EXPLORER', width - 40, height - 35);
|
||||
|
||||
// Date/Time
|
||||
// Date/Time (Bottom Left)
|
||||
ctx.textAlign = 'left';
|
||||
ctx.font = 'bold 24px monospace';
|
||||
ctx.fillStyle = '#44aaff';
|
||||
|
|
@ -61,8 +69,8 @@ export function useScreenshot() {
|
|||
ctx.fillStyle = '#cccccc';
|
||||
ctx.fillText(timeStr, 40, height - 35);
|
||||
|
||||
// 6. Trigger Download
|
||||
const dataUrl = tempCanvas.toDataURL('image/png');
|
||||
// 4. Trigger Download
|
||||
const dataUrl = canvas.toDataURL('image/png');
|
||||
const link = document.createElement('a');
|
||||
link.download = `Cosmo_Snapshot_${now.toISOString().slice(0,19).replace(/[:T]/g, '-')}.png`;
|
||||
link.href = dataUrl;
|
||||
|
|
@ -74,4 +82,4 @@ export function useScreenshot() {
|
|||
}, []);
|
||||
|
||||
return { takeScreenshot };
|
||||
}
|
||||
}
|
||||
|
|
@ -1434,6 +1434,11 @@ balanced-match@^1.0.0:
|
|||
resolved "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
|
||||
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
|
||||
|
||||
base64-arraybuffer@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npmmirror.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz#1c37589a7c4b0746e34bd1feb951da2df01c1bdc"
|
||||
integrity sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==
|
||||
|
||||
base64-js@^1.3.1, base64-js@^1.5.1:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
|
||||
|
|
@ -1618,6 +1623,13 @@ cross-spawn@^7.0.1, cross-spawn@^7.0.6:
|
|||
shebang-command "^2.0.0"
|
||||
which "^2.0.1"
|
||||
|
||||
css-line-break@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npmmirror.com/css-line-break/-/css-line-break-2.1.0.tgz#bfef660dfa6f5397ea54116bb3cb4873edbc4fa0"
|
||||
integrity sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==
|
||||
dependencies:
|
||||
utrie "^1.0.2"
|
||||
|
||||
cssesc@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmmirror.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
|
||||
|
|
@ -2085,6 +2097,14 @@ hls.js@^1.5.17:
|
|||
resolved "https://registry.npmmirror.com/hls.js/-/hls.js-1.6.15.tgz#9ce13080d143a9bc9b903fb43f081e335b8321e5"
|
||||
integrity sha512-E3a5VwgXimGHwpRGV+WxRTKeSp2DW5DI5MWv34ulL3t5UNmyJWCQ1KmLEHbYzcfThfXG8amBL+fCYPneGHC4VA==
|
||||
|
||||
html2canvas@^1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.npmmirror.com/html2canvas/-/html2canvas-1.4.1.tgz#7cef1888311b5011d507794a066041b14669a543"
|
||||
integrity sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==
|
||||
dependencies:
|
||||
css-line-break "^2.1.0"
|
||||
text-segmentation "^1.0.3"
|
||||
|
||||
ieee754@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
|
||||
|
|
@ -2845,6 +2865,13 @@ tailwindcss@^3.4.0:
|
|||
resolve "^1.22.8"
|
||||
sucrase "^3.35.0"
|
||||
|
||||
text-segmentation@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.npmmirror.com/text-segmentation/-/text-segmentation-1.0.3.tgz#52a388159efffe746b24a63ba311b6ac9f2d7943"
|
||||
integrity sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==
|
||||
dependencies:
|
||||
utrie "^1.0.2"
|
||||
|
||||
thenify-all@^1.0.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.npmmirror.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726"
|
||||
|
|
@ -3000,6 +3027,13 @@ utility-types@^3.11.0:
|
|||
resolved "https://registry.npmmirror.com/utility-types/-/utility-types-3.11.0.tgz#607c40edb4f258915e901ea7995607fdf319424c"
|
||||
integrity sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==
|
||||
|
||||
utrie@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npmmirror.com/utrie/-/utrie-1.0.2.tgz#d42fe44de9bc0119c25de7f564a6ed1b2c87a645"
|
||||
integrity sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==
|
||||
dependencies:
|
||||
base64-arraybuffer "^1.0.2"
|
||||
|
||||
vite@^7.2.4:
|
||||
version "7.2.4"
|
||||
resolved "https://registry.npmmirror.com/vite/-/vite-7.2.4.tgz#a3a09c7e25487612ecc1119c7d412c73da35bd4e"
|
||||
|
|
|
|||
Loading…
Reference in New Issue