Compare commits
8 Commits
develop
...
vercel/rea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9155419710 | ||
|
|
6ff08a0340 | ||
|
|
505167ea92 | ||
|
|
1c7e2fbb10 | ||
|
|
956f1f6c56 | ||
|
|
3d72b3b253 | ||
|
|
551c33f220 | ||
|
|
a0c9ae3bfc |
@@ -89,6 +89,11 @@
|
||||
"faqQuestion1": "Why do text characters sometimes appear as question marks in the preview?",
|
||||
"faqAnswer1": "Because the selected font doesn't support the language of the text. Try changing the font or uploading a custom font that supports the language."
|
||||
},
|
||||
"Effects": {
|
||||
"title": "Effects",
|
||||
"shadowOption": "Shadow",
|
||||
"shadowOptionHelp": "Shadow works only with Pure Color background"
|
||||
},
|
||||
"Footer": {
|
||||
"copyright": "© {year} Screen Designer. All rights reserved."
|
||||
},
|
||||
|
||||
@@ -89,6 +89,11 @@
|
||||
"faqQuestion1": "为什么预览框内的文字内容会显示问号?",
|
||||
"faqAnswer1": "因为该内容的语言没有被选中字体支持,建议更换字体或者上传自定义字体"
|
||||
},
|
||||
"Effects": {
|
||||
"title": "特效",
|
||||
"shadowOption": "阴影",
|
||||
"shadowOptionHelp": "阴影只在纯色背景下有效"
|
||||
},
|
||||
"Footer": {
|
||||
"copyright": "© {year} 3D文字设计工具 版权所有"
|
||||
},
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"lucide-react": "^0.536.0",
|
||||
"lz-string": "^1.5.0",
|
||||
"motion": "^12.23.12",
|
||||
"next": "15.2.4",
|
||||
"next": "15.2.8",
|
||||
"next-intl": "^4.3.4",
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "19.0.0",
|
||||
@@ -46,4 +46,4 @@
|
||||
"@types/react": "19.0.12",
|
||||
"@types/react-dom": "19.0.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
100
pnpm-lock.yaml
generated
100
pnpm-lock.yaml
generated
@@ -13,10 +13,10 @@ importers:
|
||||
version: 3.2.1(@types/react-dom@19.0.4(@types/react@19.0.12))(@types/react@19.0.12)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
'@vercel/analytics':
|
||||
specifier: ^1.5.0
|
||||
version: 1.5.0(next@15.2.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)
|
||||
version: 1.5.0(next@15.2.8(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)
|
||||
'@vercel/speed-insights':
|
||||
specifier: ^1.2.0
|
||||
version: 1.2.0(next@15.2.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)
|
||||
version: 1.2.0(next@15.2.8(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)
|
||||
class-variance-authority:
|
||||
specifier: ^0.7.1
|
||||
version: 0.7.1
|
||||
@@ -36,11 +36,11 @@ importers:
|
||||
specifier: ^12.23.12
|
||||
version: 12.23.12(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
next:
|
||||
specifier: 15.2.4
|
||||
version: 15.2.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
specifier: 15.2.8
|
||||
version: 15.2.8(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
next-intl:
|
||||
specifier: ^4.3.4
|
||||
version: 4.3.4(next@15.2.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)(typescript@5.9.2)
|
||||
version: 4.3.4(next@15.2.8(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)(typescript@5.9.2)
|
||||
next-themes:
|
||||
specifier: ^0.4.6
|
||||
version: 0.4.6(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
@@ -338,56 +338,56 @@ packages:
|
||||
'@napi-rs/wasm-runtime@0.2.12':
|
||||
resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==}
|
||||
|
||||
'@next/env@15.2.4':
|
||||
resolution: {integrity: sha512-+SFtMgoiYP3WoSswuNmxJOCwi06TdWE733D+WPjpXIe4LXGULwEaofiiAy6kbS0+XjM5xF5n3lKuBwN2SnqD9g==}
|
||||
'@next/env@15.2.8':
|
||||
resolution: {integrity: sha512-TaEsAki14R7BlgywA05t2PFYfwZiNlGUHyIQHVyloXX3y+Dm0HUITe5YwTkjtuOQuDhuuLotNEad4VtnmE11Uw==}
|
||||
|
||||
'@next/eslint-plugin-next@15.2.3':
|
||||
resolution: {integrity: sha512-eNSOIMJtjs+dp4Ms1tB1PPPJUQHP3uZK+OQ7iFY9qXpGO6ojT6imCL+KcUOqE/GXGidWbBZJzYdgAdPHqeCEPA==}
|
||||
|
||||
'@next/swc-darwin-arm64@15.2.4':
|
||||
resolution: {integrity: sha512-1AnMfs655ipJEDC/FHkSr0r3lXBgpqKo4K1kiwfUf3iE68rDFXZ1TtHdMvf7D0hMItgDZ7Vuq3JgNMbt/+3bYw==}
|
||||
'@next/swc-darwin-arm64@15.2.5':
|
||||
resolution: {integrity: sha512-4OimvVlFTbgzPdA0kh8A1ih6FN9pQkL4nPXGqemEYgk+e7eQhsst/p35siNNqA49eQA6bvKZ1ASsDtu9gtXuog==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@next/swc-darwin-x64@15.2.4':
|
||||
resolution: {integrity: sha512-3qK2zb5EwCwxnO2HeO+TRqCubeI/NgCe+kL5dTJlPldV/uwCnUgC7VbEzgmxbfrkbjehL4H9BPztWOEtsoMwew==}
|
||||
'@next/swc-darwin-x64@15.2.5':
|
||||
resolution: {integrity: sha512-ohzRaE9YbGt1ctE0um+UGYIDkkOxHV44kEcHzLqQigoRLaiMtZzGrA11AJh2Lu0lv51XeiY1ZkUvkThjkVNBMA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@next/swc-linux-arm64-gnu@15.2.4':
|
||||
resolution: {integrity: sha512-HFN6GKUcrTWvem8AZN7tT95zPb0GUGv9v0d0iyuTb303vbXkkbHDp/DxufB04jNVD+IN9yHy7y/6Mqq0h0YVaQ==}
|
||||
'@next/swc-linux-arm64-gnu@15.2.5':
|
||||
resolution: {integrity: sha512-FMSdxSUt5bVXqqOoZCc/Seg4LQep9w/fXTazr/EkpXW2Eu4IFI9FD7zBDlID8TJIybmvKk7mhd9s+2XWxz4flA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@next/swc-linux-arm64-musl@15.2.4':
|
||||
resolution: {integrity: sha512-Oioa0SORWLwi35/kVB8aCk5Uq+5/ZIumMK1kJV+jSdazFm2NzPDztsefzdmzzpx5oGCJ6FkUC7vkaUseNTStNA==}
|
||||
'@next/swc-linux-arm64-musl@15.2.5':
|
||||
resolution: {integrity: sha512-4ZNKmuEiW5hRKkGp2HWwZ+JrvK4DQLgf8YDaqtZyn7NYdl0cHfatvlnLFSWUayx9yFAUagIgRGRk8pFxS8Qniw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@next/swc-linux-x64-gnu@15.2.4':
|
||||
resolution: {integrity: sha512-yb5WTRaHdkgOqFOZiu6rHV1fAEK0flVpaIN2HB6kxHVSy/dIajWbThS7qON3W9/SNOH2JWkVCyulgGYekMePuw==}
|
||||
'@next/swc-linux-x64-gnu@15.2.5':
|
||||
resolution: {integrity: sha512-bE6lHQ9GXIf3gCDE53u2pTl99RPZW5V1GLHSRMJ5l/oB/MT+cohu9uwnCK7QUph2xIOu2a6+27kL0REa/kqwZw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@next/swc-linux-x64-musl@15.2.4':
|
||||
resolution: {integrity: sha512-Dcdv/ix6srhkM25fgXiyOieFUkz+fOYkHlydWCtB0xMST6X9XYI3yPDKBZt1xuhOytONsIFJFB08xXYsxUwJLw==}
|
||||
'@next/swc-linux-x64-musl@15.2.5':
|
||||
resolution: {integrity: sha512-y7EeQuSkQbTAkCEQnJXm1asRUuGSWAchGJ3c+Qtxh8LVjXleZast8Mn/rL7tZOm7o35QeIpIcid6ufG7EVTTcA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@next/swc-win32-arm64-msvc@15.2.4':
|
||||
resolution: {integrity: sha512-dW0i7eukvDxtIhCYkMrZNQfNicPDExt2jPb9AZPpL7cfyUo7QSNl1DjsHjmmKp6qNAqUESyT8YFl/Aw91cNJJg==}
|
||||
'@next/swc-win32-arm64-msvc@15.2.5':
|
||||
resolution: {integrity: sha512-gQMz0yA8/dskZM2Xyiq2FRShxSrsJNha40Ob/M2n2+JGRrZ0JwTVjLdvtN6vCxuq4ByhOd4a9qEf60hApNR2gQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@next/swc-win32-x64-msvc@15.2.4':
|
||||
resolution: {integrity: sha512-SbnWkJmkS7Xl3kre8SdMF6F/XDh1DTFEhp0jRTj/uB8iPKoU2bb2NDfcu+iifv1+mxQEd1g2vvSxcZbXSKyWiQ==}
|
||||
'@next/swc-win32-x64-msvc@15.2.5':
|
||||
resolution: {integrity: sha512-tBDNVUcI7U03+3oMvJ11zrtVin5p0NctiuKmTGyaTIEAVj9Q77xukLXGXRnWxKRIIdFG4OTA2rUVGZDYOwgmAA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
@@ -2356,8 +2356,8 @@ packages:
|
||||
react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
|
||||
|
||||
next@15.2.4:
|
||||
resolution: {integrity: sha512-VwL+LAaPSxEkd3lU2xWbgEOtrM8oedmyhBqaVNmgKB+GvZlCy9rgaEc+y2on0wv+l0oSFqLtYD6dcC1eAedUaQ==}
|
||||
next@15.2.8:
|
||||
resolution: {integrity: sha512-pe2trLKZTdaCuvNER0S9Wp+SP2APf7SfFmyUP9/w1SFA2UqmW0u+IsxCKkiky3n6um7mryaQIlgiDnKrf1ZwIw==}
|
||||
engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
@@ -3082,34 +3082,34 @@ snapshots:
|
||||
'@tybys/wasm-util': 0.10.0
|
||||
optional: true
|
||||
|
||||
'@next/env@15.2.4': {}
|
||||
'@next/env@15.2.8': {}
|
||||
|
||||
'@next/eslint-plugin-next@15.2.3':
|
||||
dependencies:
|
||||
fast-glob: 3.3.1
|
||||
|
||||
'@next/swc-darwin-arm64@15.2.4':
|
||||
'@next/swc-darwin-arm64@15.2.5':
|
||||
optional: true
|
||||
|
||||
'@next/swc-darwin-x64@15.2.4':
|
||||
'@next/swc-darwin-x64@15.2.5':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-arm64-gnu@15.2.4':
|
||||
'@next/swc-linux-arm64-gnu@15.2.5':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-arm64-musl@15.2.4':
|
||||
'@next/swc-linux-arm64-musl@15.2.5':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-x64-gnu@15.2.4':
|
||||
'@next/swc-linux-x64-gnu@15.2.5':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-x64-musl@15.2.4':
|
||||
'@next/swc-linux-x64-musl@15.2.5':
|
||||
optional: true
|
||||
|
||||
'@next/swc-win32-arm64-msvc@15.2.4':
|
||||
'@next/swc-win32-arm64-msvc@15.2.5':
|
||||
optional: true
|
||||
|
||||
'@next/swc-win32-x64-msvc@15.2.4':
|
||||
'@next/swc-win32-x64-msvc@15.2.5':
|
||||
optional: true
|
||||
|
||||
'@nodelib/fs.scandir@2.1.5':
|
||||
@@ -4164,14 +4164,14 @@ snapshots:
|
||||
'@unrs/resolver-binding-win32-x64-msvc@1.11.1':
|
||||
optional: true
|
||||
|
||||
'@vercel/analytics@1.5.0(next@15.2.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)':
|
||||
'@vercel/analytics@1.5.0(next@15.2.8(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)':
|
||||
optionalDependencies:
|
||||
next: 15.2.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
next: 15.2.8(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
react: 19.0.0
|
||||
|
||||
'@vercel/speed-insights@1.2.0(next@15.2.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)':
|
||||
'@vercel/speed-insights@1.2.0(next@15.2.8(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)':
|
||||
optionalDependencies:
|
||||
next: 15.2.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
next: 15.2.8(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
react: 19.0.0
|
||||
|
||||
'@webgpu/types@0.1.64': {}
|
||||
@@ -5192,11 +5192,11 @@ snapshots:
|
||||
|
||||
negotiator@1.0.0: {}
|
||||
|
||||
next-intl@4.3.4(next@15.2.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)(typescript@5.9.2):
|
||||
next-intl@4.3.4(next@15.2.8(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)(typescript@5.9.2):
|
||||
dependencies:
|
||||
'@formatjs/intl-localematcher': 0.5.10
|
||||
negotiator: 1.0.0
|
||||
next: 15.2.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
next: 15.2.8(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
react: 19.0.0
|
||||
use-intl: 4.3.4(react@19.0.0)
|
||||
optionalDependencies:
|
||||
@@ -5207,9 +5207,9 @@ snapshots:
|
||||
react: 19.0.0
|
||||
react-dom: 19.0.0(react@19.0.0)
|
||||
|
||||
next@15.2.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
|
||||
next@15.2.8(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
|
||||
dependencies:
|
||||
'@next/env': 15.2.4
|
||||
'@next/env': 15.2.8
|
||||
'@swc/counter': 0.1.3
|
||||
'@swc/helpers': 0.5.15
|
||||
busboy: 1.6.0
|
||||
@@ -5219,14 +5219,14 @@ snapshots:
|
||||
react-dom: 19.0.0(react@19.0.0)
|
||||
styled-jsx: 5.1.6(react@19.0.0)
|
||||
optionalDependencies:
|
||||
'@next/swc-darwin-arm64': 15.2.4
|
||||
'@next/swc-darwin-x64': 15.2.4
|
||||
'@next/swc-linux-arm64-gnu': 15.2.4
|
||||
'@next/swc-linux-arm64-musl': 15.2.4
|
||||
'@next/swc-linux-x64-gnu': 15.2.4
|
||||
'@next/swc-linux-x64-musl': 15.2.4
|
||||
'@next/swc-win32-arm64-msvc': 15.2.4
|
||||
'@next/swc-win32-x64-msvc': 15.2.4
|
||||
'@next/swc-darwin-arm64': 15.2.5
|
||||
'@next/swc-darwin-x64': 15.2.5
|
||||
'@next/swc-linux-arm64-gnu': 15.2.5
|
||||
'@next/swc-linux-arm64-musl': 15.2.5
|
||||
'@next/swc-linux-x64-gnu': 15.2.5
|
||||
'@next/swc-linux-x64-musl': 15.2.5
|
||||
'@next/swc-win32-arm64-msvc': 15.2.5
|
||||
'@next/swc-win32-x64-msvc': 15.2.5
|
||||
sharp: 0.33.5
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
|
||||
@@ -36,7 +36,7 @@ export default function Page() {
|
||||
<h2 className="text-3xl font-bold text-center mb-12">
|
||||
{t("toolTitle")}
|
||||
</h2>
|
||||
<Editor textProp={text} backgroundProp={undefined}></Editor>
|
||||
<Editor textProp={text} backgroundProp={undefined} effectProp={undefined}></Editor>
|
||||
</section>
|
||||
|
||||
{/* Features Section */}
|
||||
|
||||
@@ -1,16 +1,46 @@
|
||||
import { OnlyPage } from "@/components/editor/OnlyPage";
|
||||
import { decodeText } from "@/lib/utils";
|
||||
import { decode } from "@/lib/utils";
|
||||
import { Metadata } from "next";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
|
||||
export default async function Page({ params }: { params: Promise<{ data: string }> }) {
|
||||
|
||||
const { data } = await params
|
||||
|
||||
let backgroundProp, textProp, effectProp
|
||||
|
||||
if (data) {
|
||||
try {
|
||||
const { bg, text, effect } = decode(data);
|
||||
|
||||
backgroundProp = bg;
|
||||
textProp = text;
|
||||
effectProp = effect;
|
||||
} catch (error) {
|
||||
console.error("parse data from url error", error)
|
||||
}
|
||||
}
|
||||
|
||||
return (<OnlyPage textProp={textProp} backgroundProp={backgroundProp} effectProp={effectProp}></OnlyPage>)
|
||||
|
||||
}
|
||||
|
||||
const host = process.env.NEXT_PUBLIC_HOST;
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ locale: string, data: string }>;
|
||||
}): Promise<Metadata> {
|
||||
const { locale, data } = await params;
|
||||
const t = await getTranslations({ locale, namespace: "TextEditor" });
|
||||
|
||||
const name = `editor/${data}`;
|
||||
let backgroundProp, textProp
|
||||
|
||||
if (data) {
|
||||
try {
|
||||
const { bg, text } = JSON.parse(decodeText(data));
|
||||
|
||||
const { bg, text } = decode(data);
|
||||
backgroundProp = bg;
|
||||
textProp = text;
|
||||
} catch (error) {
|
||||
@@ -18,6 +48,38 @@ export default async function Page({ params }: { params: Promise<{ data: string
|
||||
}
|
||||
}
|
||||
|
||||
return (<OnlyPage textProp={textProp} backgroundProp={backgroundProp}></OnlyPage>)
|
||||
const description = t("seoDescription") + `bacground: ${JSON.stringify(backgroundProp)}; text: ${JSON.stringify(textProp)})}`;
|
||||
|
||||
}
|
||||
return {
|
||||
title: t("seoTitle"),
|
||||
description,
|
||||
openGraph: {
|
||||
title: t("seoTitle"),
|
||||
description,
|
||||
url: `${host}/${locale}/${name}`,
|
||||
images: [
|
||||
{
|
||||
url: `${process.env.NEXT_PUBLIC_HOST}/og-image.png`,
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: t("seoTitle"),
|
||||
},
|
||||
],
|
||||
locale: locale,
|
||||
type: "website",
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: t("seoTitle"),
|
||||
description,
|
||||
images: [`${process.env.NEXT_PUBLIC_HOST}/og-image.png`],
|
||||
},
|
||||
alternates: {
|
||||
canonical: `${host}/${name}`,
|
||||
languages: {
|
||||
en: `${host}/en/${name}`,
|
||||
zh: `${host}/zh/${name}`,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Metadata } from "next";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
const host = process.env.NEXT_PUBLIC_HOST;
|
||||
export default function Page() {
|
||||
return (<OnlyPage textProp={undefined} backgroundProp={undefined}></OnlyPage>)
|
||||
return (<OnlyPage textProp={undefined} backgroundProp={undefined} effectProp={undefined}></OnlyPage>)
|
||||
}
|
||||
|
||||
const locales = Locales;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import Footer from "@/components/Footer";
|
||||
import Header from "@/components/Header";
|
||||
import { Box, Flex } from "@radix-ui/themes";
|
||||
import { Metadata } from "next";
|
||||
|
||||
export default function Page() {
|
||||
|
||||
@@ -15,4 +16,51 @@ export default function Page() {
|
||||
</Flex>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
const host = process.env.NEXT_PUBLIC_HOST;
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ locale: string }>;
|
||||
}): Promise<Metadata> {
|
||||
const { locale } = await params;
|
||||
|
||||
const name = "features-form";
|
||||
|
||||
const title = "new features wanted";
|
||||
const description = title;
|
||||
return {
|
||||
title,
|
||||
description,
|
||||
openGraph: {
|
||||
title,
|
||||
description,
|
||||
url: `${host}/${locale}/${name}`,
|
||||
images: [
|
||||
{
|
||||
url: `${process.env.NEXT_PUBLIC_HOST}/og-image.png`,
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: title,
|
||||
},
|
||||
],
|
||||
locale: locale,
|
||||
type: "website",
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title,
|
||||
description,
|
||||
images: [`${process.env.NEXT_PUBLIC_HOST}/og-image.png`],
|
||||
},
|
||||
alternates: {
|
||||
canonical: `${host}/${name}`,
|
||||
languages: {
|
||||
en: `${host}/en/${name}`,
|
||||
zh: `${host}/zh/${name}`,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { routing } from "@/i18n/routing";
|
||||
import { getTranslations, setRequestLocale } from "next-intl/server";
|
||||
import { jsonLdScriptProps } from "react-schemaorg";
|
||||
import { WebSite } from "schema-dts";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
// import { Geist, Geist_Mono } from "next/font/google";
|
||||
import { Analytics } from "@vercel/analytics/react";
|
||||
import { SpeedInsights } from "@vercel/speed-insights/next";
|
||||
import "../globals.css";
|
||||
@@ -12,15 +12,15 @@ import { ThemeProvider } from "next-themes";
|
||||
import { Theme } from "@radix-ui/themes";
|
||||
const host = process.env.NEXT_PUBLIC_HOST;
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
// const geistSans = Geist({
|
||||
// variable: "--font-geist-sans",
|
||||
// subsets: ["latin"],
|
||||
// });
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
variable: "--font-geist-mono",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
// const geistMono = Geist_Mono({
|
||||
// variable: "--font-geist-mono",
|
||||
// subsets: ["latin"],
|
||||
// });
|
||||
|
||||
export default async function RootLayout({
|
||||
children,
|
||||
@@ -66,7 +66,7 @@ export default async function RootLayout({
|
||||
/>
|
||||
</head>
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
// className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
suppressHydrationWarning
|
||||
>
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ export default function HomePage() {
|
||||
<Heading as="h2" size={"8"}>
|
||||
{t("toolTitle")}
|
||||
</Heading>
|
||||
<Editor textProp={undefined} backgroundProp={undefined}></Editor>
|
||||
<Editor textProp={undefined} backgroundProp={undefined} effectProp={undefined}></Editor>
|
||||
</Flex>
|
||||
|
||||
</Section>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Box, Flex, Heading, Link } from "@radix-ui/themes";
|
||||
import { useLocale, useTranslations } from "next-intl";
|
||||
|
||||
import Image from "next/image";
|
||||
export default function Footer() {
|
||||
const f = useTranslations("Footer");
|
||||
const t = useTranslations("Header");
|
||||
@@ -68,6 +68,14 @@ export default function Footer() {
|
||||
>
|
||||
UIUXDECK
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
href="https://twelve.tools"
|
||||
className="text-sm text-muted-foreground hover:text-primary"
|
||||
target="_blank"
|
||||
>
|
||||
<Image src="https://twelve.tools/badge0-white.svg" alt="Featured on Twelve Tools" width={100} height={28} />
|
||||
</Link>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
|
||||
@@ -7,12 +7,13 @@ import PreviewToolbar from "./common/PreviewToolbar";
|
||||
import { useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import TextSetting, { TextProp } from "./common/TextSetting";
|
||||
import Effects, { EffectProp } from "./common/Effects";
|
||||
|
||||
/**
|
||||
* 全特性工具栏
|
||||
* @returns
|
||||
*/
|
||||
export default function Page({ textProp, backgroundProp }: { textProp: TextProp | undefined, backgroundProp: BackgroundProp | undefined }) {
|
||||
export default function Page({ textProp, backgroundProp, effectProp }: { textProp: TextProp | undefined, backgroundProp: BackgroundProp | undefined, effectProp: EffectProp | undefined }) {
|
||||
|
||||
const t = useTranslations("TextEditor");
|
||||
|
||||
@@ -22,9 +23,11 @@ export default function Page({ textProp, backgroundProp }: { textProp: TextProp
|
||||
} satisfies BackgroundProp;
|
||||
|
||||
textProp = textProp || TextProp.default(t("defaultText"));
|
||||
effectProp = effectProp || { enableShadow: true, shadowColor: "#000000" } satisfies EffectProp;
|
||||
|
||||
const [background, setBackground] = useState<BackgroundProp>(backgroundProp!);
|
||||
const [text, setText] = useState<TextProp>(textProp!);
|
||||
const [effect, setEffect] = useState<EffectProp>(effectProp);
|
||||
|
||||
return (
|
||||
<Flex gap={"2"}>
|
||||
@@ -34,10 +37,12 @@ export default function Page({ textProp, backgroundProp }: { textProp: TextProp
|
||||
setBackground={setBackground}
|
||||
/>
|
||||
<TextSetting text={text} setText={setText} />
|
||||
|
||||
<Effects effect={effect} setEffect={setEffect} background={background} />
|
||||
</Flex>
|
||||
|
||||
<Flex className="w-2/3" direction={"column"} justify={"between"}>
|
||||
<PreviewToolbar background={background} text={text} />
|
||||
<PreviewToolbar background={background} text={text} effect={effect} />
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
@@ -37,10 +37,12 @@ export default function Header() {
|
||||
{t("blogName")}
|
||||
</Link>
|
||||
|
||||
|
||||
</Flex>
|
||||
|
||||
<Flex align="center" gap="4" className="w-1/4">
|
||||
<Link href="https://github.com/wms-why/fast3dtextonline" target="_blank">
|
||||
<svg width="24" height="24" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M7.49933 0.25C3.49635 0.25 0.25 3.49593 0.25 7.50024C0.25 10.703 2.32715 13.4206 5.2081 14.3797C5.57084 14.446 5.70302 14.2222 5.70302 14.0299C5.70302 13.8576 5.69679 13.4019 5.69323 12.797C3.67661 13.235 3.25112 11.825 3.25112 11.825C2.92132 10.9874 2.44599 10.7644 2.44599 10.7644C1.78773 10.3149 2.49584 10.3238 2.49584 10.3238C3.22353 10.375 3.60629 11.0711 3.60629 11.0711C4.25298 12.1788 5.30335 11.8588 5.71638 11.6732C5.78225 11.205 5.96962 10.8854 6.17658 10.7043C4.56675 10.5209 2.87415 9.89918 2.87415 7.12104C2.87415 6.32925 3.15677 5.68257 3.62053 5.17563C3.54576 4.99226 3.29697 4.25521 3.69174 3.25691C3.69174 3.25691 4.30015 3.06196 5.68522 3.99973C6.26337 3.83906 6.8838 3.75895 7.50022 3.75583C8.1162 3.75895 8.73619 3.83906 9.31523 3.99973C10.6994 3.06196 11.3069 3.25691 11.3069 3.25691C11.7026 4.25521 11.4538 4.99226 11.3795 5.17563C11.8441 5.68257 12.1245 6.32925 12.1245 7.12104C12.1245 9.9063 10.4292 10.5192 8.81452 10.6985C9.07444 10.9224 9.30633 11.3648 9.30633 12.0413C9.30633 13.0102 9.29742 13.7922 9.29742 14.0299C9.29742 14.2239 9.42828 14.4496 9.79591 14.3788C12.6746 13.4179 14.75 10.7025 14.75 7.50024C14.75 3.49593 11.5036 0.25 7.49933 0.25Z" fill="currentColor" fillRule="evenodd" clipRule="evenodd"></path></svg>
|
||||
</Link>
|
||||
<LanguageSwitcher />
|
||||
<ModeToggle />
|
||||
</Flex>
|
||||
|
||||
@@ -83,33 +83,33 @@ export default function BackgroundSelector({
|
||||
};
|
||||
|
||||
return (
|
||||
<Box className="p-4 border rounded-lg min-w-64">
|
||||
<Box className="p-4 border rounded-lg min-w-64 border-t-2 border-t-purple-500 shadow">
|
||||
<Heading as="h2" size="4" className="font-medium text-lg">{t("title")}</Heading>
|
||||
<Flex gap={"2"} p="2" direction={"column"}>
|
||||
<Flex gap="2" align={"center"}>
|
||||
<Checkbox checked={backgroundType.includes("color")} onClick={(e) => handleBackgroundTypeChange("color")} className="cursor-pointer" />
|
||||
<Heading as="h3" size={"3"}>{t("colorOption")}</Heading>
|
||||
<Heading as="h3" size={"3"} className="w-24">{t("colorOption")}</Heading>
|
||||
<Flex gap={"4"} >
|
||||
<input
|
||||
type="color"
|
||||
id="color-picker"
|
||||
value={color || "black"}
|
||||
onChange={handleColorChange}
|
||||
className="w-1/3 h-10 rounded-md cursor-pointer"
|
||||
className="w-1/3 h-8 rounded-md cursor-pointer"
|
||||
/>
|
||||
|
||||
{color && (<input
|
||||
type="text"
|
||||
value={color}
|
||||
onChange={handleColorChange}
|
||||
className="w-1/2 h-10 rounded-md cursor-pointer pl-4"
|
||||
className="w-1/2 h-8 rounded-md cursor-pointer pl-4"
|
||||
/>)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Flex gap="2" align={"center"}>
|
||||
<Checkbox checked={backgroundType.includes("image")} onClick={(e) => handleBackgroundTypeChange("image")} className="cursor-pointer" />
|
||||
<Heading as="h3" size={"3"}>{t("imageOption")}</Heading>
|
||||
<Heading as="h3" size={"3"} className="w-24">{t("imageOption")}</Heading>
|
||||
<Flex gap={"4"} >
|
||||
<input
|
||||
type="file"
|
||||
@@ -117,7 +117,7 @@ export default function BackgroundSelector({
|
||||
accept="image/*"
|
||||
onChange={handleImageUpload}
|
||||
className="block w-full text-sm text-muted-foreground
|
||||
file:mr-4 file:py-2 file:px-4
|
||||
file:mr-4 file:py-1 file:px-2
|
||||
file:rounded-md file:border-0
|
||||
file:text-sm file:font-semibold
|
||||
file:bg-primary file:text-primary-foreground
|
||||
|
||||
93
src/components/common/Effects.tsx
Normal file
93
src/components/common/Effects.tsx
Normal file
@@ -0,0 +1,93 @@
|
||||
'use client'
|
||||
import { Box, Checkbox, Flex, Heading, IconButton, Tooltip } from "@radix-ui/themes";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useEffect, useState } from "react";
|
||||
import { BackgroundProp } from "./BackgroundSelector";
|
||||
import { CircleQuestionMarkIcon } from "lucide-react";
|
||||
export interface EffectProp {
|
||||
enableShadow: boolean;
|
||||
shadowColor: string;
|
||||
}
|
||||
export default function EffectsPage({
|
||||
effect,
|
||||
setEffect,
|
||||
background
|
||||
}: {
|
||||
effect: EffectProp;
|
||||
setEffect: (e: EffectProp) => void;
|
||||
background: BackgroundProp
|
||||
}) {
|
||||
const t = useTranslations("Effects");
|
||||
|
||||
// const [shadowValid, setShadowValid] = useState(true);
|
||||
// useEffect(() => {
|
||||
// setShadowValid(!background.image);
|
||||
// }, [background])
|
||||
|
||||
return (
|
||||
<Box className="p-4 border rounded-lg min-w-64 border-t-2 border-t-purple-500 shadow">
|
||||
<Heading as="h2" size="4" className="font-medium text-lg">{t("title")}</Heading>
|
||||
<Flex gap={"2"} p="2" direction={"column"}>
|
||||
|
||||
<Flex gap="2" align={"center"}>
|
||||
<Checkbox checked={effect.enableShadow} onClick={(e) => setEffect({ ...effect, enableShadow: !effect.enableShadow })} />
|
||||
{t("shadowOption")}
|
||||
<input
|
||||
type="color"
|
||||
id="color-picker"
|
||||
value={effect.shadowColor}
|
||||
onChange={e => setEffect({ ...effect, shadowColor: e.target.value })}
|
||||
className="w-1/3 h-8 rounded-md cursor-pointer"
|
||||
/>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
value={effect.shadowColor}
|
||||
onChange={e => setEffect({ ...effect, shadowColor: e.target.value })}
|
||||
className="w-1/3 h-8 rounded-md cursor-pointer pl-4"
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
{/* <Flex gap="2" align={"center"}>
|
||||
<Checkbox checked={backgroundType.includes("color")} onClick={(e) => handleBackgroundTypeChange("color")} className="cursor-pointer" />
|
||||
<Heading as="h3" size={"3"}>{t("colorOption")}</Heading>
|
||||
<Flex gap={"4"} >
|
||||
<input
|
||||
type="color"
|
||||
id="color-picker"
|
||||
value={color || "black"}
|
||||
onChange={handleColorChange}
|
||||
className="w-1/3 h-10 rounded-md cursor-pointer"
|
||||
/>
|
||||
|
||||
{color && (<input
|
||||
type="text"
|
||||
value={color}
|
||||
onChange={handleColorChange}
|
||||
className="w-1/2 h-10 rounded-md cursor-pointer pl-4"
|
||||
/>)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Flex gap="2" align={"center"}>
|
||||
<Checkbox checked={backgroundType.includes("image")} onClick={(e) => handleBackgroundTypeChange("image")} className="cursor-pointer" />
|
||||
<Heading as="h3" size={"3"}>{t("imageOption")}</Heading>
|
||||
<Flex gap={"4"} >
|
||||
<input
|
||||
type="file"
|
||||
id="file-upload"
|
||||
accept="image/*"
|
||||
onChange={handleImageUpload}
|
||||
className="block w-full text-sm text-muted-foreground
|
||||
file:mr-4 file:py-2 file:px-4
|
||||
file:rounded-md file:border-0
|
||||
file:text-sm file:font-semibold
|
||||
file:bg-primary file:text-primary-foreground
|
||||
hover:file:bg-primary/90"
|
||||
/>
|
||||
</Flex>
|
||||
</Flex> */}
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -4,9 +4,10 @@ import { useLocale, useTranslations } from "next-intl";
|
||||
import { Eye, Download, Share } from "lucide-react";
|
||||
import { BackgroundProp } from "./BackgroundSelector";
|
||||
import { Text, Flex, Button, Select, AlertDialog, Code, AspectRatio } from "@radix-ui/themes";
|
||||
import { getPicture, resize, init as threeInit, updateBackground, updateTextProps } from "./ThreeTools";
|
||||
import { getPicture, resize, init as threeInit, updateBackground, updateEffectProp, updateTextProp } from "./ThreeTools";
|
||||
import { TextProp } from "./TextSetting";
|
||||
import { encodeText, getShareLink } from "@/lib/utils";
|
||||
import { getShareLink } from "@/lib/utils";
|
||||
import { EffectProp } from "./Effects";
|
||||
|
||||
const Sizes = [
|
||||
"1920x1080",
|
||||
@@ -27,9 +28,11 @@ const AspectRatios = Sizes.map(o => {
|
||||
export default function PreviewToolbar({
|
||||
background,
|
||||
text,
|
||||
effect
|
||||
}: {
|
||||
background: BackgroundProp;
|
||||
text: TextProp;
|
||||
effect: EffectProp
|
||||
}) {
|
||||
let host = process.env.NEXT_PUBLIC_HOST?.substring("https://".length);
|
||||
const t = useTranslations("PreviewBar");
|
||||
@@ -70,12 +73,17 @@ export default function PreviewToolbar({
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
updateTextProps(text);
|
||||
updateTextProp(text);
|
||||
|
||||
console.log("text change", text);
|
||||
|
||||
}, [text]);
|
||||
|
||||
useEffect(() => {
|
||||
updateEffectProp(effect);
|
||||
console.log("effect change", effect);
|
||||
}, [effect]);
|
||||
|
||||
const generateImage = async (w: number, h: number): Promise<string> => {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -126,9 +134,6 @@ export default function PreviewToolbar({
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
const handleDownload = async () => {
|
||||
@@ -193,11 +198,8 @@ export default function PreviewToolbar({
|
||||
}
|
||||
|
||||
const bg = { ...background, image: null };
|
||||
let txt = JSON.stringify({ bg, text });
|
||||
txt = encodeText(txt);
|
||||
|
||||
|
||||
const link = getShareLink(txt, locale);
|
||||
const link = getShareLink({ bg, text }, locale);
|
||||
|
||||
setShareLink(link);
|
||||
|
||||
@@ -228,7 +230,7 @@ export default function PreviewToolbar({
|
||||
}, [handleFullScreen]);
|
||||
|
||||
return (
|
||||
<Flex direction={"column"} justify={"center"} align={"center"} p="2" className="rounded-lg border w-full" gap={"2"}>
|
||||
<Flex direction={"column"} justify={"center"} align={"center"} p="2" className="shadow rounded-lg border w-full border-t-2 border-t-purple-500" gap={"2"}>
|
||||
<Flex gap={"4"} >
|
||||
{t("tipsTitle")}:
|
||||
<Text>{t("mouseLeft")}</Text>
|
||||
|
||||
@@ -167,16 +167,14 @@ export default function TextSetting({
|
||||
setFontWeightEnabled(map);
|
||||
};
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<Flex className="p-4 border rounded-lg " gap={"3"} direction={"column"}>
|
||||
<Flex className="p-4 border rounded-lg border-t-2 border-t-purple-500 shadow" gap={"3"} direction={"column"}>
|
||||
<Heading as="h2" size="4" className="font-medium text-lg" >{t("title")}</Heading>
|
||||
<textarea
|
||||
value={text.text}
|
||||
onChange={e => setText({ ...text, text: e.target.value })}
|
||||
className="w-full p-3 border rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||
rows={4}
|
||||
rows={2}
|
||||
/>
|
||||
<div className="space-y-1">
|
||||
<Heading as="h3" size={"3"} >{t("textColor")}</Heading>
|
||||
@@ -220,13 +218,13 @@ export default function TextSetting({
|
||||
type="color"
|
||||
value={textGradientColor[0]}
|
||||
onChange={e => setTextGradientColor([e.target.value, text.color[1]])}
|
||||
className="w-1/2 h-10 rounded-md cursor-pointer"
|
||||
className="w-1/2 h-8 rounded-md cursor-pointer"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
value={textGradientColor[0]}
|
||||
onChange={e => setTextGradientColor([e.target.value, text.color[1]])}
|
||||
className="w-1/2 h-10 rounded-md cursor-pointer pl-4"
|
||||
className="w-1/2 h-8 rounded-md cursor-pointer pl-4"
|
||||
/>
|
||||
</Flex>
|
||||
<Flex gap={"4"}>
|
||||
@@ -235,13 +233,13 @@ export default function TextSetting({
|
||||
value={textGradientColor[1]}
|
||||
onChange={e => setTextGradientColor([text.color[0], e.target.value])}
|
||||
|
||||
className="w-1/2 h-10 rounded-md cursor-pointer"
|
||||
className="w-1/2 h-8 rounded-md cursor-pointer"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
value={textGradientColor[1]}
|
||||
onChange={e => setTextGradientColor([text.color[0], e.target.value])}
|
||||
className="w-1/2 h-10 rounded-md cursor-pointer pl-4"
|
||||
className="w-1/2 h-8 rounded-md cursor-pointer pl-4"
|
||||
/>
|
||||
</Flex>
|
||||
</Box>
|
||||
|
||||
@@ -2,10 +2,12 @@ import * as THREE from "three";
|
||||
import { BackgroundProp } from "./BackgroundSelector";
|
||||
import { FontLoader } from "three/addons/loaders/FontLoader.js";
|
||||
import { TextGeometry } from "three/addons/geometries/TextGeometry.js";
|
||||
import { ShadowMapViewer } from "three/addons/utils/ShadowMapViewer.js";
|
||||
|
||||
THREE.Cache.enabled = true;
|
||||
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
|
||||
import { ColorGradientDir, TextProp } from "./TextSetting";
|
||||
import { EffectProp } from "./Effects";
|
||||
|
||||
let camera: THREE.PerspectiveCamera,
|
||||
scene: THREE.Scene,
|
||||
@@ -28,9 +30,10 @@ export function init(
|
||||
renderer.setPixelRatio(1);
|
||||
renderer.setSize(width, height, false);
|
||||
renderer.setAnimationLoop(animate);
|
||||
renderer.shadowMap.enabled = true;
|
||||
|
||||
camera = new THREE.PerspectiveCamera(60, width / height, 0.1, 1000);
|
||||
camera.position.set(0, 80, 0);
|
||||
camera.position.set(0, 0, 50);
|
||||
|
||||
// const domWidth = container.clientWidth;
|
||||
// const domHeight = container.clientHeight;
|
||||
@@ -49,9 +52,10 @@ export function init(
|
||||
// controls
|
||||
|
||||
controls = new OrbitControls(camera, renderer.domElement);
|
||||
controls.screenSpacePanning = false;
|
||||
controls.screenSpacePanning = true;
|
||||
|
||||
controls.enabled = true;
|
||||
// controls.enablePan = false;
|
||||
|
||||
//controls.addEventListener( 'change', render ); // call this only in static scenes (i.e., if there is no animation loop)
|
||||
|
||||
@@ -63,8 +67,6 @@ export function init(
|
||||
controls.minDistance = 0.1;
|
||||
controls.maxDistance = 50000;
|
||||
|
||||
controls.maxPolarAngle = Math.PI / 2;
|
||||
|
||||
// lights
|
||||
|
||||
const dirLight1 = new THREE.DirectionalLight(0xffffff, 3);
|
||||
@@ -72,9 +74,25 @@ export function init(
|
||||
scene.add(dirLight1);
|
||||
|
||||
const dirLight2 = new THREE.DirectionalLight(0x002288, 3);
|
||||
dirLight2.position.set(-1, -1, -1);
|
||||
dirLight2.position.set(-10, 10, 50);
|
||||
dirLight2.castShadow = true;
|
||||
dirLight2.shadow.camera.left = -100;
|
||||
dirLight2.shadow.camera.top = 100;
|
||||
dirLight2.shadow.camera.bottom = -100;
|
||||
dirLight2.shadow.camera.right = 100;
|
||||
dirLight2.shadow.camera.near = 0;
|
||||
dirLight2.shadow.camera.far = 200;
|
||||
dirLight2.shadow.bias = -0.000222;
|
||||
dirLight2.shadow.mapSize.width = 2048;
|
||||
dirLight2.shadow.mapSize.height = 2048;
|
||||
scene.add(dirLight2);
|
||||
|
||||
// const helper = new THREE.DirectionalLightHelper(dirLight2, 50);
|
||||
// scene.add(helper);
|
||||
|
||||
// const helper2 = new THREE.CameraHelper(dirLight2.shadow.camera);
|
||||
// scene.add(helper2);
|
||||
|
||||
const ambientLight = new THREE.AmbientLight(0x555555);
|
||||
scene.add(ambientLight);
|
||||
}
|
||||
@@ -113,7 +131,7 @@ export function resize(
|
||||
|
||||
let textMesh: THREE.Mesh;
|
||||
let lastTextProps: TextProp | null = null;
|
||||
export async function updateTextProps(textProps: TextProp) {
|
||||
export async function updateTextProp(textProps: TextProp) {
|
||||
// const mirror = true;
|
||||
// const plane = new THREE.Mesh(
|
||||
// new THREE.PlaneGeometry(10000, 10000),
|
||||
@@ -143,8 +161,10 @@ export async function updateTextProps(textProps: TextProp) {
|
||||
let size = new THREE.Vector3();
|
||||
geo.boundingBox?.getSize(size);
|
||||
let textMesh1 = new THREE.Mesh(geo, mat);
|
||||
textMesh1.rotateX(-Math.PI / 2);
|
||||
textMesh1.scale.multiplyScalar(100 / size.x);
|
||||
// textMesh1.rotateX(-Math.PI / 2);
|
||||
textMesh1.scale.multiplyScalar(50).divideScalar(size.x);
|
||||
textMesh1.castShadow = true;
|
||||
textMesh1.receiveShadow = true;
|
||||
|
||||
scene.add(textMesh1);
|
||||
|
||||
@@ -159,6 +179,9 @@ export async function updateTextProps(textProps: TextProp) {
|
||||
let geo = await getTextGeometry(textProps);
|
||||
textMesh.geometry.dispose();
|
||||
textMesh.geometry = geo;
|
||||
let size = new THREE.Vector3();
|
||||
geo.boundingBox?.getSize(size);
|
||||
textMesh.scale.set(1, 1, 1).multiplyScalar(50).divideScalar(size.x);
|
||||
|
||||
if (Array.isArray(textProps.color)) {
|
||||
setGradient(
|
||||
@@ -271,6 +294,7 @@ async function getTextGeometry(textProps: TextProp) {
|
||||
|
||||
textGeo.computeBoundingBox();
|
||||
textGeo.center();
|
||||
textGeo.translate(0, size / 2, depth / 2);
|
||||
textGeo.computeVertexNormals();
|
||||
return textGeo;
|
||||
|
||||
@@ -334,3 +358,28 @@ export function getPicture(width: number, height: number) {
|
||||
renderer.setSize(lastWidth, lastHeight, false);
|
||||
return img;
|
||||
}
|
||||
|
||||
let shadowPlane: THREE.Mesh | null = null;
|
||||
export function updateEffectProp(effect: EffectProp) {
|
||||
// && background.color && !background.image
|
||||
if (effect.enableShadow) {
|
||||
if (!shadowPlane) {
|
||||
shadowPlane = new THREE.Mesh(
|
||||
new THREE.PlaneGeometry(100, 100, 10, 10),
|
||||
new THREE.ShadowMaterial({
|
||||
color: new THREE.Color(effect.shadowColor),
|
||||
opacity: 0.3,
|
||||
})
|
||||
);
|
||||
shadowPlane.receiveShadow = true;
|
||||
}
|
||||
scene.add(shadowPlane);
|
||||
|
||||
(shadowPlane.material as THREE.ShadowMaterial).color.set(
|
||||
effect.shadowColor
|
||||
);
|
||||
shadowPlane.visible = true;
|
||||
} else {
|
||||
shadowPlane && (shadowPlane.visible = false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,12 @@ import Header from "@/components/Header";
|
||||
import { Box, Container, Flex, Heading, Text, Card } from "@radix-ui/themes";
|
||||
import { HelpCircle } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { EffectProp } from "../common/Effects";
|
||||
|
||||
export function OnlyPage({ textProp, backgroundProp }: {
|
||||
export function OnlyPage({ textProp, backgroundProp, effectProp }: {
|
||||
textProp: TextProp | undefined;
|
||||
backgroundProp: BackgroundProp | undefined
|
||||
backgroundProp: BackgroundProp | undefined;
|
||||
effectProp: EffectProp | undefined;
|
||||
}) {
|
||||
const t = useTranslations('TextEditor');
|
||||
|
||||
@@ -20,6 +22,7 @@ export function OnlyPage({ textProp, backgroundProp }: {
|
||||
<FullEditor
|
||||
textProp={textProp}
|
||||
backgroundProp={backgroundProp}
|
||||
effectProp={effectProp}
|
||||
/>
|
||||
</Container>
|
||||
<Container p="4">
|
||||
|
||||
@@ -2,18 +2,21 @@ import { clsx, type ClassValue } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import CryptoJS from "crypto-js";
|
||||
import LZString from "lz-string";
|
||||
import { BackgroundProp } from "@/components/common/BackgroundSelector";
|
||||
import { EffectProp } from "@/components/common/Effects";
|
||||
import { TextProp } from "@/components/common/TextSetting";
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
const SECRET_KEY = "fast3dtext-ymk";
|
||||
export function encodeText(text: string) {
|
||||
function encodeText(text: string) {
|
||||
const compressed = LZString.compressToEncodedURIComponent(text);
|
||||
const encrypted = CryptoJS.AES.encrypt(compressed, SECRET_KEY).toString();
|
||||
return encodeURIComponent(encrypted);
|
||||
}
|
||||
|
||||
export function decodeText(encodedText: string) {
|
||||
function decodeText(encodedText: string) {
|
||||
const decoded = decodeURIComponent(encodedText);
|
||||
const decrypted = CryptoJS.AES.decrypt(decoded, SECRET_KEY).toString(
|
||||
CryptoJS.enc.Utf8
|
||||
@@ -22,6 +25,16 @@ export function decodeText(encodedText: string) {
|
||||
return decompressed;
|
||||
}
|
||||
|
||||
export function getShareLink(data: string, locale: string) {
|
||||
return `${window.location.origin}/${locale}/editor/${data}`;
|
||||
export interface ShareObj {
|
||||
bg: BackgroundProp;
|
||||
text: TextProp;
|
||||
effect?: EffectProp;
|
||||
}
|
||||
export function getShareLink(data: ShareObj, locale: string) {
|
||||
const dataStr = JSON.stringify(data);
|
||||
return `${window.location.origin}/${locale}/editor/${encodeText(dataStr)}`;
|
||||
}
|
||||
export function decode(data: string) {
|
||||
const decoded = decodeText(data);
|
||||
return JSON.parse(decoded) as ShareObj;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user