Commit 57d488f2 authored by fisherdaddy's avatar fisherdaddy

chore: update

parent c717bfff
......@@ -3,9 +3,11 @@ import '../styles/Timeline.css'; // 复用已有的Timeline样式
import events from '../data/anthropic-releases.json';
import SEO from '../components/SEO';
import { useTranslation } from '../js/i18n';
import { useScrollToTop } from '../hooks/useScrollToTop';
const AnthropicTimeline = () => {
const { t } = useTranslation();
useScrollToTop();
return (
<>
......
......@@ -3,6 +3,7 @@ import { removeBackground } from "@imgly/background-removal";
import styled from 'styled-components';
import { useTranslation } from '../js/i18n';
import SEO from './SEO';
import { useScrollToTop } from '../hooks/useScrollToTop';
// Reuse container style
const Container = styled.div`
......@@ -157,6 +158,7 @@ const PrivacyNote = styled.div`
`;
function BackgroundRemover() {
useScrollToTop();
const { t } = useTranslation();
const [selectedImage, setSelectedImage] = useState(null);
const [removedBgImage, setRemovedBgImage] = useState(null);
......
import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
import { usePageLoading } from '../hooks/usePageLoading';
import LoadingOverlay from './LoadingOverlay';
import '../styles/HandwriteGen.css';
import html2canvas from 'html2canvas';
import {
......@@ -16,6 +18,7 @@ const { Option } = Select;
const { Title } = Typography;
function HandwritingGenerator() {
const isLoading = usePageLoading();
const [text, setText] = useState('');
const [font, setFont] = useState("'XINYE'");
const [paperType, setPaperType] = useState('Lined Paper'); // 默认值为横线纸
......@@ -30,6 +33,15 @@ function HandwritingGenerator() {
const [lineSpacing, setLineSpacing] = useState(1.25);
const [charSpacing, setCharSpacing] = useState(0);
useEffect(() => {
// Clear any loading states from previous navigation
const clearLoadingState = () => {
const event = new CustomEvent('clearLoadingState');
window.dispatchEvent(event);
};
clearLoadingState();
}, []);
const handleGenerate = () => {
const previewElement = document.querySelector('.preview-area');
html2canvas(previewElement, {
......@@ -145,8 +157,9 @@ function HandwritingGenerator() {
const backgroundOffset = -(lineSpacing * fontSize - fontSize);
return (
<div className="handwrite-container" style={{ paddingTop: '4rem' }}>
<Layout>
<>
{isLoading && <LoadingOverlay />}
<Layout className="min-h-screen pt-20">
<Sider width={300} className="site-layout-background">
<div className="settings-section">
<h2 className="title-label">手写字体生成器</h2>
......@@ -314,7 +327,7 @@ function HandwritingGenerator() {
</Content>
</Layout>
</Layout>
</div>
</>
);
}
......
This diff is collapsed.
......@@ -3,6 +3,8 @@ import styled from 'styled-components';
import { useTranslation } from '../js/i18n';
import SEO from './SEO';
import imageCompression from 'browser-image-compression';
import { usePageLoading } from '../hooks/usePageLoading';
import LoadingOverlay from './LoadingOverlay';
// 复用 MarkdownToImage 的容器样式
const Container = styled.div`
......@@ -402,6 +404,7 @@ const truncateFilename = (filename, maxLength = 10) => {
function ImageCompressor() {
const { t } = useTranslation();
const isLoading = usePageLoading();
const [images, setImages] = useState([]); // 修改为数组存储多张图片
const [compressedImages, setCompressedImages] = useState([]);
const [settings, setSettings] = useState({
......@@ -523,6 +526,7 @@ function ImageCompressor() {
return (
<>
{isLoading && <LoadingOverlay />}
<SEO
title={t('tools.imageCompressor.title')}
description={t('tools.imageCompressor.description')}
......
......@@ -2,6 +2,8 @@ import React, { useState, useRef, useCallback, useEffect } from 'react';
import styled from 'styled-components';
import { useTranslation } from '../js/i18n';
import SEO from './SEO';
import { usePageLoading } from '../hooks/usePageLoading';
import LoadingOverlay from './LoadingOverlay';
// 复用 MarkdownToImage 的基础容器样式
const Container = styled.div`
......@@ -164,6 +166,7 @@ const PrivacyNote = styled.div`
function ImageWatermark() {
const { t } = useTranslation();
const isLoading = usePageLoading();
const [image, setImage] = useState(null);
const [watermarkText, setWatermarkText] = useState('');
const [watermarkImage, setWatermarkImage] = useState(null);
......@@ -339,6 +342,7 @@ function ImageWatermark() {
return (
<>
{isLoading && <LoadingOverlay />}
<SEO
title={t('tools.imageWatermark.title')}
description={t('tools.imageWatermark.description')}
......
......@@ -2,6 +2,8 @@ import React, { useState, useEffect } from 'react';
import { useTranslation } from '../js/i18n';
import SEO from '../components/SEO';
import styled from 'styled-components';
import { usePageLoading } from '../hooks/usePageLoading';
import LoadingOverlay from './LoadingOverlay';
const Container = styled.div`
min-height: 100vh;
......@@ -48,6 +50,7 @@ function JsonFormatter() {
const [parsedJson, setParsedJson] = useState(null);
const [isCopied, setIsCopied] = useState(false);
const [isCompressed, setIsCompressed] = useState(false);
const isLoading = usePageLoading();
useEffect(() => {
try {
......@@ -80,6 +83,7 @@ function JsonFormatter() {
return (
<>
{isLoading && <LoadingOverlay />}
<SEO
title={t('tools.jsonFormatter.title')}
description={t('tools.jsonFormatter.description')}
......
......@@ -6,6 +6,8 @@ import DOMPurify from 'dompurify';
import SEO from './SEO';
import { useTranslation } from '../js/i18n';
import html2canvas from 'html2canvas';
import { usePageLoading } from '../hooks/usePageLoading';
import LoadingOverlay from './LoadingOverlay';
// 容器样式
const Container = styled.div`
......@@ -144,6 +146,7 @@ const DownloadButton = styled.button`
function HtmlPreview() {
const [html, setHtml] = useState('');
const { t } = useTranslation();
const isLoading = usePageLoading();
// 处理 LaTeX 公式
const processLatex = (content) => {
......@@ -269,6 +272,7 @@ function HtmlPreview() {
return (
<>
{isLoading && <LoadingOverlay />}
<SEO
title={t('tools.latex2image.title')}
description={t('tools.latex2image.description')}
......
import React from 'react';
const LoadingOverlay = () => {
return (
<div className="fixed inset-0 bg-white/80 flex items-center justify-center z-50">
<div className="flex flex-col items-center gap-4">
<div className="w-12 h-12 border-4 border-indigo-500 border-t-transparent rounded-full animate-spin"></div>
<div className="text-indigo-600 font-medium">Loading...</div>
</div>
</div>
);
};
export default LoadingOverlay;
import React, { useState, useRef } from 'react';
import React, { useState, useRef, useEffect } from 'react';
import styled from 'styled-components';
import { marked } from 'marked';
import { useTranslation } from '../js/i18n';
import SEO from './SEO';
import DOMPurify from 'dompurify';
import { usePageLoading } from '../hooks/usePageLoading';
import LoadingOverlay from './LoadingOverlay';
// 更新预设模板
const templates = [
......@@ -303,11 +305,12 @@ const Preview = styled.div`
hyphens: auto;
`;
function TextToImage() {
function MarkdownToImage() {
const { t } = useTranslation();
const [text, setText] = useState('');
const [selectedTemplate, setSelectedTemplate] = useState(templates[0]);
const previewRef = useRef(null);
const isLoading = usePageLoading();
const formatText = (text) => {
return marked.parse(text, {
......@@ -419,6 +422,7 @@ function TextToImage() {
return (
<>
{isLoading && <LoadingOverlay />}
<SEO
title={t('tools.markdown2image.title')}
description={t('tools.markdown2image.description')}
......@@ -473,4 +477,4 @@ function TextToImage() {
);
}
export default TextToImage;
\ No newline at end of file
export default MarkdownToImage;
\ No newline at end of file
import React from 'react';
import { useScrollToTop } from '../hooks/useScrollToTop';
import '../styles/Timeline.css';
import events from '../data/openai-releases.json';
import SEO from '../components/SEO';
import { useTranslation } from '../js/i18n';
const Timeline = () => {
useScrollToTop();
const { t } = useTranslation();
return (
......
// PricingChart.jsx
import React, { useState } from 'react';
import { useScrollToTop } from '../hooks/useScrollToTop';
import '../styles/PricingChart.css';
const ChartLegend = ({ onLegendClick, highlightedBarTypes }) => {
......@@ -99,6 +100,7 @@ const GridLines = () => (
);
const PricingChart = ({ data }) => {
useScrollToTop();
const [highlightedBarTypes, setHighlightedBarTypes] = useState({
input: true,
output: true,
......
import React from 'react';
import { useScrollToTop } from '../hooks/useScrollToTop';
import PricingChart from '../components/PricingChart';
import OpenaiPricing from '../data/openai-pricing.json';
import LLMPricing from '../data/llm-pricing.json';
......@@ -6,6 +7,7 @@ import VisionPricing from '../data/vision-model-pricing.json';
import SEO from '../components/SEO';
const PricingCharts = () => {
useScrollToTop();
const lastUpdateTime = '2024-11-06 21:30';
return (
......
......@@ -2,6 +2,7 @@ import React, { useState, useRef } from 'react';
import styled from 'styled-components';
import html2canvas from 'html2canvas';
import { useTranslation } from '../js/i18n';
import { useScrollToTop } from '../hooks/useScrollToTop';
// 更新中文字体数组,包含显示名称和 CSS 字体族名称
const chineseFonts = [
......@@ -203,6 +204,7 @@ const DownloadButton = styled.button`
`;
function QuoteCard() {
useScrollToTop();
const { t } = useTranslation();
const [chineseText, setChineseText] = useState('');
......
This diff is collapsed.
......@@ -3,6 +3,8 @@ import { removeBackground } from "@imgly/background-removal";
import styled from 'styled-components';
import { useTranslation } from '../js/i18n';
import '../styles/fonts.css';
import { usePageLoading } from '../hooks/usePageLoading';
import LoadingOverlay from './LoadingOverlay';
// 复用现有的基础容器样式
const Container = styled.div`
......@@ -255,6 +257,7 @@ const PrivacyNote = styled.div`
function TextBehindImage() {
const { t } = useTranslation();
const isLoading = usePageLoading();
const [selectedImage, setSelectedImage] = useState(null);
const [isImageSetupDone, setIsImageSetupDone] = useState(false);
const [removedBgImageUrl, setRemovedBgImageUrl] = useState(null);
......@@ -460,6 +463,8 @@ function TextBehindImage() {
}, []);
return (
<>
{isLoading && <LoadingOverlay />}
<Container>
<ContentWrapper>
<ControlPanel>
......@@ -710,6 +715,7 @@ function TextBehindImage() {
</PreviewArea>
</ContentWrapper>
</Container>
</>
);
}
......
......@@ -2,6 +2,8 @@ import React, { useState } from 'react';
import styled from 'styled-components';
import { useTranslation } from '../js/i18n';
import SEO from './SEO';
import { usePageLoading } from '../hooks/usePageLoading';
import LoadingOverlay from './LoadingOverlay';
const Container = styled.div`
min-height: 100vh;
......@@ -167,6 +169,7 @@ const DiffHeader = styled(TitleLabel)`
function TextDiff() {
const { t } = useTranslation();
const isLoading = usePageLoading();
const [oldText, setOldText] = useState('');
const [newText, setNewText] = useState('');
......@@ -180,6 +183,7 @@ function TextDiff() {
return (
<>
{isLoading && <LoadingOverlay />}
<SEO
title={t('tools.textDiff.title')}
description={t('tools.textDiff.description')}
......
......@@ -2,6 +2,8 @@ import React, { useState, useCallback } from 'react';
import { useTranslation } from '../js/i18n';
import SEO from './SEO';
import styled from 'styled-components';
import { usePageLoading } from '../hooks/usePageLoading';
import LoadingOverlay from './LoadingOverlay';
// 复用相同的样式组件
const Container = styled.div`
......@@ -45,6 +47,7 @@ const Title = styled.h2`
function UrlEncoderDecoder() {
const { t } = useTranslation();
const isLoading = usePageLoading();
const [input, setInput] = useState('');
const [resultText, setResultText] = useState('');
const [isCopied, setIsCopied] = useState(false);
......@@ -81,6 +84,7 @@ function UrlEncoderDecoder() {
return (
<>
{isLoading && <LoadingOverlay />}
<SEO
title={t('tools.urlEncodeDecode.title')}
description={t('tools.urlEncodeDecode.description')}
......
import { useState, useEffect } from 'react';
export const usePageLoading = () => {
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
// Set loading to false after a short delay to ensure content is ready
const timer = setTimeout(() => {
setIsLoading(false);
}, 500);
return () => clearTimeout(timer);
}, []);
return isLoading;
};
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
export const useScrollToTop = () => {
const { pathname } = useLocation();
useEffect(() => {
window.scrollTo(0, 0);
}, [pathname]);
};
......@@ -7,6 +7,8 @@
"login": "Login",
"loginSubtitle": "Welcome to AI Toolbox, please log in for the full experience",
"logout": "Logout",
"copy": "Copy",
"copied": "Copied",
"dev-tools": {
"title": "Development Tools",
"description": "A collection of popular and efficient development tools, including JSON formatter, Base64 to image converter, online Diff tool, LaTeX renderer, and more. Designed to help developers handle everyday coding tasks quickly and boost development efficiency."
......
......@@ -99,7 +99,8 @@
"download": "Download Image",
"dragOrClick": "Drag and drop or click to upload",
"fileName": "File name",
"fileSize": "File size"
"fileSize": "File size",
"preview": "Preview"
},
"fisherai": {
"title": "FisherAI",
......@@ -117,7 +118,7 @@
"title": "Subtitle Generator",
"description": "Quickly generate multi-line subtitle images with customizable styles",
"uploadImage": "Upload Background Image",
"dragOrClick": "Drag and drop or click to upload",
"dropOrClick": "Drag and drop or click to upload",
"removeImage": "Remove Image",
"globalSettings": "Global Settings",
"fontColor": "Font Color",
......
......@@ -7,6 +7,8 @@
"login": "ログイン",
"loginSubtitle": "AIツールボックスへようこそ。フル体験のためにログインしてください",
"logout": "ログアウト",
"copy": "コピー",
"copied": "コピーされました",
"dev-tools": {
"title": "開発ツール",
"description": "JSONフォーマッター、Base64画像変換、オンラインDiffツール、LaTeXレンダリングなど、さまざまな人気で効率的な開発ツールを統合し、日常のコーディングタスクを迅速に処理し、開発効率を向上させます。"
......
......@@ -99,7 +99,8 @@
"download": "画像をダウンロード",
"dragOrClick": "画像をドラッグまたはクリックしてアップロード",
"fileName": "ファイル名",
"fileSize": "ファイルサイズ"
"fileSize": "ファイルサイズ",
"preview": "プレビュー"
},
"fisherai": {
"title": "FisherAI",
......
......@@ -7,6 +7,8 @@
"login": "로그인",
"loginSubtitle": "AI 툴��스에 오신 것을 환영합니다. 전체 경험을 위해 로그인해 주세요",
"logout": "로그아웃",
"copy": "복사",
"copied": "복사된 메시지",
"dev-tools": {
"title": "개발 도구",
"description": "JSON 포맷터, Base64 이미지 변환기, 온라인 Diff 도구, LaTeX 렌더러 등 인기 있고 효율적인 개발 도구를 통합하여, 일상적인 코딩 작업을 신속하게 처리하고 개발 효율성을 높입니다."
......
......@@ -100,7 +100,8 @@
"download": "이미지 다운로드",
"dragOrClick": "드래그 앤 드롭 또는 클릭하여 업로드",
"fileName": "파일 이름",
"fileSize": "파일 크기"
"fileSize": "파일 크기",
"preview": "미리보기"
},
"fisherai": {
"title": "FisherAI",
......
......@@ -7,6 +7,8 @@
"login": "登录",
"loginSubtitle": "欢迎使用 AI 工具箱,请登录以获得完整体验",
"logout": "退出登录",
"copy": "复制",
"copied": "已复制",
"dev-tools": {
"title": "开发工具",
"description": "集成多种热门高效的开发工具,包括JSON格式化、Base64转图片、在线Diff对比、LaTeX渲染等,帮助开发者快速处理日常编码任务,提升开发效率。"
......
......@@ -101,7 +101,8 @@
"download": "下载图片",
"dragOrClick": "拖拽或点击上传图片",
"fileName": "文件名",
"fileSize": "文件大小"
"fileSize": "文件大小",
"preview": "预览"
},
"fisherai": {
"title": "FisherAI",
......@@ -201,7 +202,7 @@
},
"textBehindImage": {
"title": "文字穿越图片",
"description": "在图片主体与背景之间添加文字,创造3D效果",
"description": "在图片主体与背景之间添加文字,3D效果",
"imageUpload": "图片上传",
"uploadPrompt": "点击或拖拽上传图片",
"textSettings": "文字设置",
......
import React from 'react';
import { Link } from 'react-router-dom';
import React, { useState, useEffect } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { useTranslation } from '../js/i18n';
import SEO from '../components/SEO';
......@@ -25,10 +25,37 @@ const tools = [
const Home = () => {
const { t } = useTranslation();
const [loading, setLoading] = useState('');
const navigate = useNavigate();
useEffect(() => {
const handleClearLoading = () => {
setLoading('');
};
window.addEventListener('clearLoadingState', handleClearLoading);
return () => {
window.removeEventListener('clearLoadingState', handleClearLoading);
};
}, []);
const handleNavigate = (tool) => {
if (tool.external) {
window.open(tool.path, '_blank', 'noopener,noreferrer');
return;
}
setLoading(tool.id);
window.scrollTo(0, 0);
navigate(tool.path);
};
const renderToolLink = (tool) => {
const content = (
<div className="group flex items-center gap-4 p-6 bg-white rounded-xl shadow-lg hover:shadow-xl transform hover:-translate-y-1 transition-all duration-300">
<div className={`group flex items-center gap-4 p-6 bg-white rounded-xl shadow-lg hover:shadow-xl transform hover:-translate-y-1 transition-all duration-300 relative ${loading === tool.id ? 'pointer-events-none' : ''}`}>
{loading === tool.id && (
<div className="absolute inset-0 bg-white/80 rounded-xl flex items-center justify-center z-10">
<div className="w-8 h-8 border-4 border-indigo-500 border-t-transparent rounded-full animate-spin"></div>
</div>
)}
<img
src={tool.icon}
alt={`${t(`tools.${tool.id}.title`)} icon`}
......@@ -48,17 +75,15 @@ const Home = () => {
return tool.external ? (
<a
href={tool.path}
className="block"
target="_blank"
rel="noopener noreferrer"
onClick={() => handleNavigate(tool)}
className="block cursor-pointer"
>
{content}
</a>
) : (
<Link to={tool.path} className="block">
<div onClick={() => handleNavigate(tool)} className="block cursor-pointer">
{content}
</Link>
</div>
);
};
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment