增加全功能editor页面
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
"heroTitle": "Create Stunning 3D Text Designs",
|
||||
"heroSubtitle": "Generate and customize beautiful 3D text effects for your projects",
|
||||
"toolTitle": "3D Text Generator Tool",
|
||||
"toolMore": "need more options",
|
||||
"getStarted": "Generate Now",
|
||||
"featuresTitle": "Powerful Features",
|
||||
"feature1Title": "3D Text Customization",
|
||||
@@ -45,6 +46,8 @@
|
||||
"downloadBackground": "Download"
|
||||
},
|
||||
"TextEditor": {
|
||||
"seoTitle": "3D Text Generator - More Options",
|
||||
"seoDescription": "Fast Create professional 3D text with more options. control the color, font, and effect",
|
||||
"title": "Text Editor",
|
||||
"defaultText": "please input your text",
|
||||
"textColor": "Text Color",
|
||||
@@ -59,10 +62,10 @@
|
||||
"copyright": "© {year} Screen Designer. All rights reserved."
|
||||
},
|
||||
"DoNotWriteOnThisPage": {
|
||||
"seoTitle": "Do Not Write On This Page Generator - Custom Background Creator",
|
||||
"seoDescription": "Free online tool to generate professional 'Do Not Write On This Page' backgrounds. Customize text, colors and download high-resolution images for notebooks, whiteboards and screens.",
|
||||
"toolTitle": "Do Not Write On This Page Generator",
|
||||
"heroTitle": "Professional 'Do Not Write On This Page' Background Creator",
|
||||
"seoTitle": "Do Not Write On This Page - 3D Text Generator",
|
||||
"seoDescription": "Free online tool to generate 'Do Not Write On This Page' 3D Text. Customize text, colors and download high-resolution images for notebooks, whiteboards and screens.",
|
||||
"toolTitle": "'Do Not Write On This Page' Generator",
|
||||
"heroTitle": "'Do Not Write On This Page' 3D Text Generator",
|
||||
"heroSubtitle": "Generate and customize perfect backgrounds for notebooks, whiteboards and digital screens",
|
||||
"feature1Title": "Text Customization",
|
||||
"feature1Desc": "Fully customize text content, fonts and colors to create your perfect design",
|
||||
@@ -72,6 +75,6 @@
|
||||
"feature3Desc": "Download print-ready high-quality images in multiple formats",
|
||||
"ctaTitle": "Create Your Custom Background Now",
|
||||
"ctaSubtitle": "Start generating professional 'Do Not Write On This Page' designs",
|
||||
"ctaButton": "Generate Background"
|
||||
"ctaButton": "Generate 3D Text"
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
"heroTitle": "创建惊艳的3D文字设计",
|
||||
"heroSubtitle": "为您的项目生成并定制精美的3D文字效果",
|
||||
"toolTitle": "3D文字生成工具",
|
||||
"toolMore": "需要更多选项",
|
||||
"getStarted": "立即生成",
|
||||
"featuresTitle": "强大功能",
|
||||
"feature1Title": "3D文字定制",
|
||||
@@ -45,6 +46,8 @@
|
||||
"downloadBackground": "下载图片"
|
||||
},
|
||||
"TextEditor": {
|
||||
"seoTitle": "3D 文字生成器 - 更多的选项",
|
||||
"seoDescription": "快速创建具有更多选项的专业 3D 文本。控制颜色、字体和效果",
|
||||
"title": "文字编辑",
|
||||
"defaultText": "输入您的文字",
|
||||
"textColor": "文字颜色",
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 73 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 320 KiB After Width: | Height: | Size: 146 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 76 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 460 KiB After Width: | Height: | Size: 163 KiB |
@@ -4,8 +4,8 @@ import Footer from "@/components/Footer";
|
||||
import Header from "@/components/Header";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Metadata } from "next";
|
||||
import Editor from "@/components/Editor";
|
||||
import { FontNames, FontWeights } from "@/components/common/TextSetting";
|
||||
import Editor from "@/components/SimpleEditor";
|
||||
import { FontNames, FontWeights, TextProp } from "@/components/common/TextSetting";
|
||||
|
||||
export default function Page({
|
||||
params,
|
||||
@@ -19,12 +19,7 @@ export default function Page({
|
||||
const t = useTranslations("DoNotWriteOnThisPage");
|
||||
const indexT = useTranslations("Index");
|
||||
|
||||
const text = {
|
||||
text: "Do Not Write On This Page",
|
||||
color: "#8e86fe",
|
||||
font: FontNames[0],
|
||||
weight: FontWeights[0],
|
||||
}
|
||||
const text = TextProp.default("Do Not Write On This Page");
|
||||
|
||||
return (
|
||||
<div className="flex flex-col min-h-screen overflow-hidden">
|
||||
|
||||
73
src/app/[locale]/editor/page.tsx
Normal file
73
src/app/[locale]/editor/page.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
|
||||
import Footer from "@/components/Footer";
|
||||
import FullEditor from "@/components/FullEditor";
|
||||
import Header from "@/components/Header";
|
||||
import { Locales } from "@/i18n/config";
|
||||
import { Box, Flex } from "@radix-ui/themes";
|
||||
import { Metadata } from "next";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
const host = process.env.NEXT_PUBLIC_HOST;
|
||||
export default function Page() {
|
||||
|
||||
return (
|
||||
<Flex direction={"column"} gap={"4"}>
|
||||
<Header />
|
||||
<FullEditor
|
||||
textProp={undefined}
|
||||
backgroundProp={undefined}
|
||||
/>
|
||||
<Footer />
|
||||
</Flex>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
const locales = Locales;
|
||||
|
||||
export function generateStaticParams() {
|
||||
return locales.map((locale) => ({ locale }));
|
||||
}
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ locale: string }>;
|
||||
}): Promise<Metadata> {
|
||||
const { locale } = await params;
|
||||
const t = await getTranslations({ locale, namespace: "TextEditor" });
|
||||
|
||||
const name = "editor";
|
||||
|
||||
return {
|
||||
title: t("seoTitle"),
|
||||
description: t("seoDescription"),
|
||||
openGraph: {
|
||||
title: t("seoTitle"),
|
||||
description: t("seoDescription"),
|
||||
url: `${host}/${locale}/${name}`,
|
||||
images: [
|
||||
{
|
||||
url: `${process.env.NEXT_PUBLIC_HOST}/og-image-${name}.png`,
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: t("seoTitle"),
|
||||
},
|
||||
],
|
||||
locale: locale,
|
||||
type: "website",
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: t("seoTitle"),
|
||||
description: t("seoDescription"),
|
||||
images: [`${process.env.NEXT_PUBLIC_HOST}/og-image-${name}.png`],
|
||||
},
|
||||
alternates: {
|
||||
canonical: `${host}/${name}`,
|
||||
languages: {
|
||||
en: `${host}/en/${name}`,
|
||||
zh: `${host}/zh/${name}`,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import { use } from "react";
|
||||
import { getTranslations, setRequestLocale } from "next-intl/server";
|
||||
import Footer from "@/components/Footer";
|
||||
import Header from "@/components/Header";
|
||||
import Editor from "@/components/Editor";
|
||||
import Editor from "@/components/SimpleEditor";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Locales } from "@/i18n/config";
|
||||
import { Metadata } from "next";
|
||||
@@ -182,7 +182,7 @@ export async function generateMetadata({
|
||||
title: t("title"),
|
||||
description: t("description"),
|
||||
images: [`${host}/og-image.png`],
|
||||
creator: "@s0ver5",
|
||||
creator: "Yaomker",
|
||||
},
|
||||
alternates: {
|
||||
canonical: `${host}`,
|
||||
|
||||
@@ -19,6 +19,18 @@ export default function sitemap(): MetadataRoute.Sitemap {
|
||||
changeFrequency: "monthly" as const,
|
||||
priority: 0.8,
|
||||
})),
|
||||
{
|
||||
url: baseUrl + "/editor",
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "yearly",
|
||||
priority: 1,
|
||||
},
|
||||
...locales.map((locale) => ({
|
||||
url: `${baseUrl}/${locale}/editor`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "monthly" as const,
|
||||
priority: 0.8,
|
||||
})),
|
||||
{
|
||||
url: baseUrl + "/do-not-write-on-this-page",
|
||||
lastModified: new Date(),
|
||||
|
||||
@@ -8,6 +8,14 @@ export default function Footer() {
|
||||
<footer className="w-full border-t backdrop-blur-sm bg-background/95 ">
|
||||
<Flex justify={"between"} align={"center"} direction={"column"} gap={"2"} p="2">
|
||||
<Flex justify={"center"} gap={"4"}>
|
||||
|
||||
<Link
|
||||
href="/editor"
|
||||
className="text-sm text-muted-foreground hover:text-primary"
|
||||
>
|
||||
Editor
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
href="/do-not-write-on-this-page"
|
||||
className="text-sm text-muted-foreground hover:text-primary"
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
"use client";
|
||||
import { Flex, Box } from "@radix-ui/themes";
|
||||
import { Flex, Box, Link } from "@radix-ui/themes";
|
||||
import BackgroundSelector, {
|
||||
BackgroundProp,
|
||||
} from "./common/BackgroundSelector";
|
||||
import PreviewToolbar from "./common/PreviewToolbar";
|
||||
import TextSetting, { FontNames, FontWeights, TextProp } from "./common/TextSetting";
|
||||
import { useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import TextSetting, { TextProp } from "./common/TextSetting";
|
||||
|
||||
/**
|
||||
* 全特性工具栏
|
||||
@@ -16,17 +16,12 @@ export default function Page({ textProp, backgroundProp }: { textProp: TextProp
|
||||
|
||||
const t = useTranslations("TextEditor");
|
||||
|
||||
const [background, setBackground] = useState<BackgroundProp>(backgroundProp || {
|
||||
const [background, setBackground] = useState<BackgroundProp>({
|
||||
type: "color",
|
||||
color: "#c4b1b1",
|
||||
image: null,
|
||||
});
|
||||
const [text, setText] = useState<TextProp>(textProp || {
|
||||
text: t("defaultText"),
|
||||
color: "#8e86fe",
|
||||
font: FontNames[0],
|
||||
weight: FontWeights[0],
|
||||
});
|
||||
const [text, setText] = useState<TextProp>(TextProp.default(t("defaultText")));
|
||||
|
||||
return (
|
||||
<Flex gap={"2"}>
|
||||
@@ -38,9 +33,9 @@ export default function Page({ textProp, backgroundProp }: { textProp: TextProp
|
||||
<TextSetting text={text} setText={setText} />
|
||||
</Flex>
|
||||
|
||||
<Box className="w-2/3" >
|
||||
<Flex className="w-2/3" direction={"column"} justify={"between"}>
|
||||
<PreviewToolbar background={background} text={text} />
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@@ -6,23 +6,23 @@ import { Box, Flex, Link, Strong, Text } from "@radix-ui/themes";
|
||||
export default function Header() {
|
||||
const t = useTranslations("Index");
|
||||
return (
|
||||
<header className="w-full py-2">
|
||||
<header className="w-full py-2 border-b-1 ">
|
||||
<Flex justify="center" gap="9" align="center">
|
||||
<Box >
|
||||
<Box className="w-1/4 text-center" >
|
||||
<a href="/" >
|
||||
<Text size="6" color="iris"><Strong>{t("appName")}</Strong></Text>
|
||||
</a>
|
||||
</Box >
|
||||
|
||||
<Flex gap={"4"} justify={"between"} align={"center"}>
|
||||
<Flex gap={"4"} justify={"center"} align={"center"} className="w-1/2">
|
||||
<Link
|
||||
href="/do-not-write-on-this-page"
|
||||
href="/editor"
|
||||
>
|
||||
Do Not Write On This Page
|
||||
Editor
|
||||
</Link>
|
||||
</Flex>
|
||||
|
||||
<Flex align="center" gap="4">
|
||||
<Flex align="center" gap="4" className="w-1/4">
|
||||
<LanguageSwitcher />
|
||||
<ModeToggle />
|
||||
</Flex>
|
||||
|
||||
46
src/components/SimpleEditor.tsx
Normal file
46
src/components/SimpleEditor.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
"use client";
|
||||
import { Flex, Box, Link } from "@radix-ui/themes";
|
||||
import BackgroundSelector, {
|
||||
BackgroundProp,
|
||||
} from "./common/BackgroundSelector";
|
||||
import PreviewToolbar from "./common/PreviewToolbar";
|
||||
import SimpleTextSetting from "./common/SimpleTextSetting";
|
||||
import { useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { TextProp } from "./common/TextSetting";
|
||||
|
||||
/**
|
||||
* 简易工具
|
||||
* @returns
|
||||
*/
|
||||
export default function Page({ textProp, backgroundProp }: { textProp: TextProp | undefined, backgroundProp: BackgroundProp | undefined }) {
|
||||
|
||||
const t = useTranslations("TextEditor");
|
||||
const tIndex = useTranslations("Index");
|
||||
|
||||
const [background, setBackground] = useState<BackgroundProp>(backgroundProp || {
|
||||
type: "color",
|
||||
color: "#c4b1b1",
|
||||
image: null,
|
||||
});
|
||||
|
||||
const [text, setText] = useState<TextProp>(textProp || TextProp.default(t("defaultText")));
|
||||
|
||||
return (
|
||||
<Flex gap={"2"}>
|
||||
<Flex gap={"2"} direction={"column"} className="w-1/3">
|
||||
<BackgroundSelector
|
||||
background={background}
|
||||
setBackground={setBackground}
|
||||
/>
|
||||
<SimpleTextSetting text={text} setText={setText} />
|
||||
</Flex>
|
||||
|
||||
<Flex className="w-2/3" direction={"column"} justify={"between"}>
|
||||
<PreviewToolbar background={background} text={text} />
|
||||
|
||||
<Box className="text-center"> <Link href="./editor">{tIndex("toolMore")}?</Link> </Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@@ -3,9 +3,9 @@ import { useState, useRef, useEffect, } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Eye, Download } from "lucide-react";
|
||||
import { BackgroundProp } from "./BackgroundSelector";
|
||||
import { TextProp } from "./TextSetting";
|
||||
import { Text, Flex } from "@radix-ui/themes";
|
||||
import { getPicture, resize, init as threeInit, updateBackground, updateTextProps } from "./ThreeTools";
|
||||
import { TextProp } from "./TextSetting";
|
||||
|
||||
const Sizes = [
|
||||
"1920x1080",
|
||||
|
||||
68
src/components/common/SimpleTextSetting.tsx
Normal file
68
src/components/common/SimpleTextSetting.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
import { Flex, Heading } from "@radix-ui/themes";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { FontNames, FontWeights, TextProp } from "./TextSetting";
|
||||
|
||||
export default function TextSetting({
|
||||
text,
|
||||
setText,
|
||||
}: {
|
||||
text: TextProp;
|
||||
setText: (text: TextProp) => void;
|
||||
}) {
|
||||
const t = useTranslations("TextEditor");
|
||||
|
||||
return (
|
||||
<Flex className="p-4 border rounded-lg " gap={"3"} direction={"column"}>
|
||||
<Heading size={"3"} 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}
|
||||
/>
|
||||
<div className="space-y-1">
|
||||
<label className="block text-sm text-muted-foreground">
|
||||
{t("textColor")}
|
||||
</label>
|
||||
<input
|
||||
type="color"
|
||||
value={text.color}
|
||||
onChange={e => setText({ ...text, color: e.target.value })}
|
||||
className="w-full h-10 rounded-md cursor-pointer"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<label className="block text-sm text-muted-foreground">
|
||||
{t("fontFamily")}
|
||||
</label>
|
||||
<select
|
||||
value={text.font}
|
||||
onChange={(e) => setText({ ...text, font: e.target.value })}
|
||||
className="w-full p-2 border rounded-md"
|
||||
>
|
||||
{FontNames.map((name) => (
|
||||
<option key={name} value={name}>
|
||||
{name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="block text-sm text-muted-foreground">
|
||||
{t("fontWeight")}
|
||||
</label>
|
||||
<select
|
||||
value={text.weight}
|
||||
onChange={(e) => setText({ ...text, weight: e.target.value })}
|
||||
className="w-full p-2 border rounded-md"
|
||||
>
|
||||
{FontWeights.map((weight) => (
|
||||
<option key={weight} value={weight}>
|
||||
{weight}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@@ -1,24 +1,63 @@
|
||||
import { Flex, Heading } from "@radix-ui/themes";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
export interface TextProp {
|
||||
text: string;
|
||||
color: string;
|
||||
font: string;
|
||||
weight: string;
|
||||
}
|
||||
export const FontWeights = ["regular", "bold"];
|
||||
export const FontNames = ["gentilis", "helvetiker", "optimer", "Noto_Sans_SC_zh", "Alibaba_PuHuiTi_3.0_zh"];
|
||||
|
||||
export function getFontPath(fontName: string, fontWeight: String) {
|
||||
if (fontName != "noto_sans_zh") {
|
||||
return `/fonts/${fontName}_${fontWeight}.typeface.json`;
|
||||
} else {
|
||||
fontWeight = fontWeight.charAt(0).toUpperCase() + fontWeight.slice(1);
|
||||
return `https://fast3dtest.mysoul.fun/Noto_Sans_SC_${fontWeight}.json`;
|
||||
type FontFrom = "local" | "upload";
|
||||
export class TextProp {
|
||||
text: string
|
||||
color: string
|
||||
fontFrom: FontFrom
|
||||
font: string
|
||||
weight: string
|
||||
|
||||
constructor(
|
||||
text: string,
|
||||
color: string,
|
||||
fontFrom: FontFrom,
|
||||
font: string,
|
||||
weight: string) {
|
||||
|
||||
this.text = text;
|
||||
this.color = color;
|
||||
this.fontFrom = fontFrom;
|
||||
this.font = font;
|
||||
this.weight = weight;
|
||||
}
|
||||
|
||||
static default(text: string): TextProp {
|
||||
|
||||
let font = FontNames[0];
|
||||
|
||||
if (containsChinese(text)) {
|
||||
font = "Alibaba_PuHuiTi_3.0_zh";
|
||||
}
|
||||
return {
|
||||
text,
|
||||
color: "#8e86fe",
|
||||
font,
|
||||
weight: FontWeights[0],
|
||||
fontFrom: "local",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const FontWeights = ["regular", "bold"];
|
||||
export const FontNames = ["gentilis", "helvetiker", "optimer", "noto_sans_zh"];
|
||||
function containsChinese(str: string) {
|
||||
return /[\u4e00-\u9fa5]/.test(str);
|
||||
}
|
||||
|
||||
export function getFontPath(fontName: string, fontWeight: String) {
|
||||
if (!fontName.endsWith("zh")) {
|
||||
return `/fonts/${fontName}_${fontWeight}.typeface.json`;
|
||||
} else {
|
||||
fontWeight = fontWeight.charAt(0).toUpperCase() + fontWeight.slice(1);
|
||||
let font = fontName.slice(0, -3);
|
||||
return `https://fast3dtest.mysoul.fun/${font}_${fontWeight}.json`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default function TextSetting({
|
||||
text,
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import * as THREE from "three";
|
||||
import { getFontPath, TextProp } from "./TextSetting";
|
||||
import { BackgroundProp } from "./BackgroundSelector";
|
||||
import { Font, FontLoader } from "three/addons/loaders/FontLoader.js";
|
||||
import { TextGeometry } from "three/addons/geometries/TextGeometry.js";
|
||||
|
||||
THREE.Cache.enabled = true;
|
||||
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
|
||||
import { TextProp, getFontPath } from "./TextSetting";
|
||||
|
||||
let camera: THREE.PerspectiveCamera,
|
||||
scene: THREE.Scene,
|
||||
|
||||
Reference in New Issue
Block a user