增加bartex的style
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import Footer from "@/components/Footer";
|
||||
import Header from "@/components/Header";
|
||||
import Editor from "@/components/SimpleEditor";
|
||||
import Editor from "@/components/FullEditor";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Locales } from "@/i18n/config";
|
||||
import { Metadata } from "next";
|
||||
|
||||
BIN
src/app/[locale]/styles/barbie-pink/1024_576.png
Normal file
BIN
src/app/[locale]/styles/barbie-pink/1024_576.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 138 KiB |
BIN
src/app/[locale]/styles/barbie-pink/512_288.png
Normal file
BIN
src/app/[locale]/styles/barbie-pink/512_288.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
148
src/app/[locale]/styles/barbie-pink/page.tsx
Normal file
148
src/app/[locale]/styles/barbie-pink/page.tsx
Normal file
@@ -0,0 +1,148 @@
|
||||
import Image from 'next/image';
|
||||
import { useLocale, useTranslations } from 'next-intl';
|
||||
import { StyleItem } from '../list';
|
||||
import { Locales } from '@/i18n/config';
|
||||
import { Metadata } from 'next';
|
||||
import { Box, Flex, Container, Heading, Text, Button, Strong } from '@radix-ui/themes';
|
||||
import Footer from '@/components/Footer';
|
||||
import Header from '@/components/Header';
|
||||
import highCover from "./1024_576.png";
|
||||
import cover from "./512_288.png";
|
||||
const host = process.env.NEXT_PUBLIC_HOST;
|
||||
|
||||
export const styleContent = {
|
||||
id: "barbie-pink",
|
||||
cover: cover,
|
||||
date: "2025-08-07",
|
||||
en: {
|
||||
title: "Barbie-Pink 3D Text",
|
||||
summary: "Use our online 3D text editor to craft eye-catching Barbie-inspired designs.",
|
||||
},
|
||||
zh: {
|
||||
title: "芭比粉3D文本",
|
||||
summary: "使用我们的在线3D文本编辑器,为项目创建引人注目的芭比粉3D文本。",
|
||||
},
|
||||
} satisfies StyleItem;
|
||||
|
||||
export default function BarbiePinkLandingPage() {
|
||||
const t = useTranslations('StyleBarbie');
|
||||
const common = useTranslations('StylePage');
|
||||
const locale = useLocale();
|
||||
|
||||
return (
|
||||
<Flex direction={"column"} >
|
||||
<Header />
|
||||
<Flex justify={"center"} width={"full"} p={"4"} style={{ backgroundColor: 'var(--pink-3)' }}>
|
||||
<Flex className="min-h-screen" direction={"column"} gap={"3"}>
|
||||
<Flex justify="center" p="4">
|
||||
<Image
|
||||
src={highCover}
|
||||
alt="3D Barbie Text Example"
|
||||
width={300}
|
||||
height={169}
|
||||
style={{ borderRadius: 'var(--radius-3)', boxShadow: 'var(--shadow-4)' }}
|
||||
/>
|
||||
</Flex>
|
||||
<Container>
|
||||
<Flex direction="column" align="center" gap="4">
|
||||
<Heading as='h1' size="8" align="center">{t('title')}</Heading>
|
||||
<Text size="5" align="center">{t('description')}</Text>
|
||||
</Flex>
|
||||
</Container>
|
||||
|
||||
<Container className='text-center'>
|
||||
<Heading as='h2' size="5" mb="4" >{t('templateTitle')}</Heading>
|
||||
<Flex gap={"2"} direction={"column"} >
|
||||
<Flex gap={"2"} justify={"center"}>
|
||||
<Text className='font-bold'>{common("templateFontLabel")}:</Text>
|
||||
<Text size="3">{t('templateFont')}</Text>
|
||||
</Flex>
|
||||
<Flex gap={"2"} justify={"center"}>
|
||||
<Text className='font-bold'>{common("templateBgColorLabel")}:</Text>
|
||||
<Text size="3">{t('templateBgColor')}</Text>
|
||||
</Flex>
|
||||
<Flex gap={"2"} justify={"center"}>
|
||||
<Text className='font-bold'>{common("templateTextGradientColorLabel")}:</Text>
|
||||
<Text size="3">{t('templateTextGradient')}</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Container>
|
||||
|
||||
<Container className='text-center'>
|
||||
<Heading as='h2' size="6" mb="4">{t('tipsTitle')}</Heading>
|
||||
<Text as="p" mb="4">{t('content1')}</Text>
|
||||
<Text as="p" mb="4">{t('content2')}</Text>
|
||||
</Container>
|
||||
|
||||
<Flex direction={"column"} gap={"2"} justify={"center"} align={"center"}>
|
||||
|
||||
<Button size="3" asChild >
|
||||
<a href={`/${locale}/editor/U2FsdGVkX19rP8xCyBPFUL%2FO0fDre3wZBjUP%2FxsyN60rkJvZFHgAhV1OIq3LX7XhLRacG0NzByrl7Xt1t9tAt2PO8UkFCRk7fABsu7HlfxSyIYeE%2Bp6ikdiqfIO%2BNEzNxx3GzasHxdw1FxEaOtZcspT8hIWpxb59WXsJ%2BvheZpiZV8N%2FqYZTUMSWD0GYX1AOi6bWV%2BmTHp8hRJzko1SfrWWX5%2F9NxCrxYeAhFpXxH%2FFKtt3EAlg4XDrvjsqIvX%2FDbESOv7reY3HYydZnBFKbGdALPqNiIuHxcMpChrIqxSebNhKbBVDOy24yAR7aYBNuZYN1BHZCV9tpa3tfzy2B6dVaW80zNBSBpypi7foPYVQDJ8K9QggnWHXqED4V65LSApKoCcm56W1A%2BP%2BMWKmMJw%3D%3D`}>{t('cta')}</a>
|
||||
</Button>
|
||||
|
||||
<Button size="3" variant="soft" asChild>
|
||||
<a href={`/${locale}/editor/U2FsdGVkX1%2BitOSouauvY7pzI7LQf%2BxXMLoslpg1yqvA1SCE53KtvcsLe5lHk9HUQ7TDbae9MMFc%2F%2FiNYT7sUyftCN2UgjelVSqfUYI9gk%2FlFCnTkuzd4iPPWqaOR5fwoxHkebMnqBTOfi%2F6vJBc2i0ujKQDgGB%2Byny1ygk60%2F0P65eR6wTMPWCU5mTVa7ZDGYKl7uFUMipmu3c2nirDn%2BWzHQBQoGH9xvxWNhqlbM0oZLY8fMSJT%2FpZ1RjVZ785pAUP5wFMUc5yJhvBLs6C8uLYaGjilqTfJn5NflecZZ3vgG%2FZv0TWgrRoScM1OmvQhIDiAnXCGjFQ3Ek2s6otpXf%2FG0OPTPMqx964F4iqgvfrOuZGY5q4DwWr48tVKjrORmgOPqC846MvaQxWCvpnVQ%3D%3D`}>you can also try <Strong>Barbie_Doll</Strong></a>
|
||||
</Button>
|
||||
|
||||
<Button size="3" variant="soft" asChild>
|
||||
<a href={`/${locale}/editor/U2FsdGVkX1%2FIkxznkprehHz%2FtkJbwnqS3IzlVZhN9HOq%2Fv2IB9gBj2ZPWnR1879R1bYzi5iy77X%2B9tGWnZO0p%2BxtjCwQcYLij%2BFc8VcJafJLvErwByaUlZCKqEjnZ1rKqxO1EsiJdBKJFjRE0PsgUqk5E2kM6LaO03jpXk7D1zey3pYfWsj5%2FjZxLdtD5w146bmMwI7ygRik9wFxlizZV%2BrYlzCoB7lqSDb9%2FkvH2ZqAV72TFCwSLqLGm%2B%2FnGs7VlOro%2FPU99jgxVKnNQpZz7lIaCt30qgZC3i6UCftQt00TrxzlGwSIA0iG8w2GnV2%2BAzrjje1JOgp%2Bg8onEuuMzMIxpxjzjrdhDSHzxyErt3Ag2Sk2jKvkFy0R%2FDT2H5GHitpPSTBWJVFi0lc5zAdoaDfeIKX%2BULZUXYzErDuOB3U%3D`}>you can also try <Strong>Barbie_Princess</Strong></a>
|
||||
</Button>
|
||||
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
</Flex>
|
||||
<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 name = styleContent.id;
|
||||
const title = styleContent[locale as "en" | "zh"].title;
|
||||
const description = styleContent[locale as "en" | "zh"].summary;
|
||||
return {
|
||||
title,
|
||||
description,
|
||||
keywords: [],
|
||||
openGraph: {
|
||||
title,
|
||||
description,
|
||||
url: `${host}/${locale}/styles/${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}/styles/${name}`,
|
||||
languages: {
|
||||
en: `${host}/en/styles/${name}`,
|
||||
zh: `${host}/zh/styles/${name}`,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
17
src/app/[locale]/styles/list.ts
Normal file
17
src/app/[locale]/styles/list.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { StaticImageData } from "next/image";
|
||||
import { styleContent as barbieStyle } from "./barbie-pink/page";
|
||||
export interface StyleItem {
|
||||
id: string;
|
||||
date: string;
|
||||
cover: StaticImageData;
|
||||
en: {
|
||||
title: string;
|
||||
summary: string;
|
||||
};
|
||||
zh: {
|
||||
title: string;
|
||||
summary: string;
|
||||
};
|
||||
}
|
||||
|
||||
export const styles: StyleItem[] = [barbieStyle];
|
||||
94
src/app/[locale]/styles/page.tsx
Normal file
94
src/app/[locale]/styles/page.tsx
Normal file
@@ -0,0 +1,94 @@
|
||||
import Footer from '@/components/Footer'
|
||||
import Header from '@/components/Header'
|
||||
import { Card, Flex, Text, Heading, Box, Link } from '@radix-ui/themes'
|
||||
import { styles } from './list'
|
||||
import { useLocale, useTranslations } from 'next-intl';
|
||||
import { Locales } from '@/i18n/config';
|
||||
import { Metadata } from 'next';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
import Image from 'next/image';
|
||||
const host = process.env.NEXT_PUBLIC_HOST;
|
||||
|
||||
export default function StyleListPage() {
|
||||
const locale = useLocale() as "zh" | "en";
|
||||
const t = useTranslations("StylePage");
|
||||
|
||||
return (
|
||||
<Flex direction={"column"} gap={"4"}>
|
||||
<Header />
|
||||
<Box p="4" className="text-center">
|
||||
<Heading as='h1' size="7" mb="4">{t("title")}</Heading>
|
||||
<Flex justify={"center"} gap={"4"} wrap={"wrap"}>
|
||||
{styles.map(styleItem => (
|
||||
<Card key={styleItem.id} size="2" style={{ maxWidth: 300, boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)' }} mx="4" my="4">
|
||||
<Link href={`/${locale}/styles/${styleItem.id}`} color='iris'>
|
||||
<Flex direction="column" gap="4">
|
||||
<Box style={{ overflow: 'hidden' }}>
|
||||
<Image src={styleItem.cover} alt={styleItem[locale].title} width={512} height={288} />
|
||||
</Box>
|
||||
<Flex direction={"column"} gap={"1"}>
|
||||
<Heading as='h2' size="5" weight="bold" className='text-black dark:text-white'>{styleItem[locale].title}</Heading>
|
||||
<Text color="gray" >{styleItem.date}</Text>
|
||||
<Text truncate={true} >{styleItem[locale].summary}</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Link>
|
||||
</Card>
|
||||
))}
|
||||
</Flex>
|
||||
</Box>
|
||||
<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: "StylePage" });
|
||||
|
||||
const name = "styles";
|
||||
|
||||
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.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.png`],
|
||||
},
|
||||
alternates: {
|
||||
canonical: `${host}/${name}`,
|
||||
languages: {
|
||||
en: `${host}/en/${name}`,
|
||||
zh: `${host}/zh/${name}`,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,73 +1,39 @@
|
||||
import { Locales } from "@/i18n/config";
|
||||
import { MetadataRoute } from "next";
|
||||
import { blogs } from "./[locale]/blogs/list";
|
||||
import { styles } from "./[locale]/styles/list";
|
||||
|
||||
const host = process.env.NEXT_PUBLIC_HOST;
|
||||
export default function sitemap(): MetadataRoute.Sitemap {
|
||||
const baseUrl = host!;
|
||||
const locales = Locales;
|
||||
|
||||
const urls = ["/", "/blogs", "/editor", "/do-not-write-on-this-page"];
|
||||
blogs.forEach((blog) => {
|
||||
urls.push(`/blogs/${blog.id}`);
|
||||
});
|
||||
styles.forEach((style) => {
|
||||
urls.push(`/styles/${style.id}`);
|
||||
});
|
||||
|
||||
const multiLocaleUrls = locales
|
||||
.map((locale) =>
|
||||
urls.map((url) => ({
|
||||
url: `${baseUrl}/${locale}${url}`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "monthly" as const,
|
||||
priority: 0.8,
|
||||
}))
|
||||
)
|
||||
.flat();
|
||||
|
||||
return [
|
||||
{
|
||||
url: baseUrl,
|
||||
...urls.map((url) => ({
|
||||
url: `${baseUrl}${url}`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "yearly",
|
||||
changeFrequency: "daily" as const,
|
||||
priority: 1,
|
||||
},
|
||||
...locales.map((locale) => ({
|
||||
url: `${baseUrl}/${locale}`,
|
||||
lastModified: new Date(),
|
||||
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(),
|
||||
changeFrequency: "yearly",
|
||||
priority: 1,
|
||||
},
|
||||
...locales.map((locale) => ({
|
||||
url: `${baseUrl}/${locale}/do-not-write-on-this-page`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "monthly" as const,
|
||||
priority: 0.8,
|
||||
})),
|
||||
|
||||
{
|
||||
url: baseUrl + "/blogs",
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "yearly",
|
||||
priority: 1,
|
||||
},
|
||||
...locales.map((locale) => ({
|
||||
url: `${baseUrl}/${locale}/blogs`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "monthly" as const,
|
||||
priority: 0.8,
|
||||
})),
|
||||
|
||||
{
|
||||
url: baseUrl + "/blogs/Create-3D-Text-with-the-Barbie-Font",
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "yearly",
|
||||
priority: 1,
|
||||
},
|
||||
...locales.map((locale) => ({
|
||||
url: `${baseUrl}/${locale}/blogs/Create-3D-Text-with-the-Barbie-Font`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "monthly" as const,
|
||||
priority: 0.8,
|
||||
})),
|
||||
...multiLocaleUrls,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -20,12 +20,19 @@ export default function Footer() {
|
||||
>
|
||||
{t("editorName")}
|
||||
</Link>
|
||||
<Link
|
||||
href={`/${locale}/styles`}
|
||||
className="text-sm text-muted-foreground hover:text-primary"
|
||||
>
|
||||
{t("styleName")}
|
||||
</Link>
|
||||
<Link
|
||||
href={`/${locale}/blogs`}
|
||||
className="text-sm text-muted-foreground hover:text-primary"
|
||||
>
|
||||
{t("blogName")}
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
href="/features-form"
|
||||
className="text-sm text-muted-foreground hover:text-primary"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"use client";
|
||||
import { Flex, Box, Link } from "@radix-ui/themes";
|
||||
import { Flex } from "@radix-ui/themes";
|
||||
import BackgroundSelector, {
|
||||
BackgroundProp,
|
||||
} from "./common/BackgroundSelector";
|
||||
@@ -7,8 +7,6 @@ import PreviewToolbar from "./common/PreviewToolbar";
|
||||
import { useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import TextSetting, { TextProp } from "./common/TextSetting";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { decodeText } from "@/lib/utils";
|
||||
|
||||
/**
|
||||
* 全特性工具栏
|
||||
|
||||
@@ -24,18 +24,20 @@ export default function Header() {
|
||||
{t("editorName")}
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
href={`/${locale}/styles`}
|
||||
className="text-sm text-muted-foreground hover:text-primary"
|
||||
>
|
||||
{t("styleName")}
|
||||
</Link>
|
||||
<Link
|
||||
href={`/${locale}/blogs`}
|
||||
className="text-sm text-muted-foreground hover:text-primary"
|
||||
>
|
||||
{t("blogName")}
|
||||
</Link>
|
||||
<Link
|
||||
href="/features-form"
|
||||
className="text-sm text-muted-foreground hover:text-primary"
|
||||
>
|
||||
Features Wanted
|
||||
</Link>
|
||||
|
||||
|
||||
</Flex>
|
||||
|
||||
<Flex align="center" gap="4" className="w-1/4">
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
"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 { useEffect, useState } from "react";
|
||||
import { useLocale, useTranslations } from "next-intl";
|
||||
import { TextProp } from "./common/TextSetting";
|
||||
import { useRouter } from "@/i18n/navigation";
|
||||
|
||||
/**
|
||||
* 简易工具
|
||||
* @returns
|
||||
*/
|
||||
export default function SimpleEditor({ textProp, backgroundProp }: { textProp: TextProp | undefined, backgroundProp: BackgroundProp | undefined }) {
|
||||
|
||||
const t = useTranslations("TextEditor");
|
||||
const tIndex = useTranslations("HomePage");
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
backgroundProp = {
|
||||
type: "color",
|
||||
color: "#c4b1b1",
|
||||
image: null,
|
||||
} satisfies BackgroundProp;
|
||||
|
||||
textProp = textProp || TextProp.default(t("defaultText"));
|
||||
|
||||
const [background, setBackground] = useState<BackgroundProp>(backgroundProp);
|
||||
|
||||
const [text, setText] = useState<TextProp>(textProp);
|
||||
|
||||
// useEffect(() => {
|
||||
// let bg = sessionStorage.getItem("background");
|
||||
|
||||
// if (bg) {
|
||||
// console.log("初始化设置 bg", bg);
|
||||
|
||||
// setBackground(JSON.parse(bg));
|
||||
// }
|
||||
|
||||
// let txt = sessionStorage.getItem("text");
|
||||
|
||||
// if (txt) {
|
||||
// console.log("初始化设置 txt", txt);
|
||||
|
||||
// setText(JSON.parse(txt));
|
||||
// }
|
||||
|
||||
// }, []);
|
||||
|
||||
useEffect(() => {
|
||||
sessionStorage.setItem("background", JSON.stringify(background));
|
||||
}, [background]);
|
||||
|
||||
useEffect(() => {
|
||||
sessionStorage.setItem("text", JSON.stringify(text));
|
||||
|
||||
}, [text]);
|
||||
const locale = useLocale();
|
||||
|
||||
return (
|
||||
<Flex gap={"2"}>
|
||||
<Flex gap={"2"} direction={"column"} className="w-1/3">
|
||||
<BackgroundSelector
|
||||
background={background}
|
||||
setBackground={setBackground}
|
||||
/>
|
||||
<SimpleTextSetting text={text} setText={setText} />
|
||||
<Box className="text-center"> <Link href={`/${locale}/editor`} >{tIndex("toolMore")} ?</Link> </Box>
|
||||
</Flex>
|
||||
|
||||
<Flex className="w-2/3" direction={"column"} justify={"between"}>
|
||||
<PreviewToolbar background={background} text={text} />
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@@ -40,7 +40,7 @@ export default function BackgroundSelector({
|
||||
};
|
||||
|
||||
return (
|
||||
<Box className="space-y-4 p-4 border rounded-lg min-w-64">
|
||||
<Box className="p-4 border rounded-lg min-w-64">
|
||||
<Heading as="h2" size="4" className="font-medium text-lg">{t("title")}</Heading>
|
||||
<Flex gap={"2"} p="2">
|
||||
|
||||
@@ -53,13 +53,24 @@ export default function BackgroundSelector({
|
||||
|
||||
<Box className="w-full">
|
||||
{background.type === "color" && (
|
||||
<input
|
||||
type="color"
|
||||
id="color-picker"
|
||||
value={background.color}
|
||||
onChange={handleColorChange}
|
||||
className="w-full h-10 rounded-md cursor-pointer"
|
||||
/>
|
||||
<Flex gap={"6"} p="2">
|
||||
<input
|
||||
type="color"
|
||||
id="color-picker"
|
||||
value={background.color}
|
||||
onChange={handleColorChange}
|
||||
className="w-1/3 h-10 rounded-md cursor-pointer"
|
||||
/>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
value={background.color}
|
||||
onChange={handleColorChange}
|
||||
className="w-1/3 h-10 rounded-md cursor-pointer pl-4"
|
||||
/>
|
||||
|
||||
</Flex>
|
||||
|
||||
)}
|
||||
|
||||
{background.type === "image" && (
|
||||
|
||||
@@ -32,8 +32,9 @@ export default function PreviewToolbar({
|
||||
}) {
|
||||
let host = process.env.NEXT_PUBLIC_HOST?.substring("https://".length);
|
||||
const t = useTranslations("PreviewBar");
|
||||
const [aspectRadio, setAspectRadio] = useState<number>(0);
|
||||
const split = Sizes[0].split("x").map(Number);
|
||||
const initAspectRadio = 0;
|
||||
const [aspectRadio, setAspectRadio] = useState<number>(initAspectRadio);
|
||||
const split = Sizes[initAspectRadio].split("x").map(Number);
|
||||
const [size, setSize] = useState<Size>({ width: split[0], height: split[1] });
|
||||
const container = useRef<HTMLCanvasElement>(null);
|
||||
const fullscreenElement = useRef<HTMLImageElement>(null);
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
import { Flex, Heading, Select } 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"} wrap={"wrap"}>
|
||||
<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 w-full">
|
||||
<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 w-1/3">
|
||||
<label className="block text-sm text-muted-foreground">
|
||||
{t("fontFamily")}
|
||||
</label>
|
||||
<Select.Root defaultValue={`${text.font}`} onValueChange={(e) => setText({ ...text, font: e })}>
|
||||
<Select.Trigger />
|
||||
<Select.Content>
|
||||
{FontNames.map((name) => <Select.Item key={name} value={name}>{name}</Select.Item>)}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
<div className="space-y-2 w-1/3">
|
||||
<label className="block text-sm text-muted-foreground">
|
||||
{t("fontWeight")}
|
||||
</label>
|
||||
|
||||
<Select.Root defaultValue={`${text.weight}`} onValueChange={(e) => setText({ ...text, weight: e })}>
|
||||
<Select.Trigger />
|
||||
<Select.Content>
|
||||
{FontWeights.map((name) => <Select.Item key={name} value={name}>{name}</Select.Item>)}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
|
||||
</div>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@@ -1,14 +1,15 @@
|
||||
'use client'
|
||||
import { containsChinese, DefaultFontChinese, FontLang, Fonts, FontWeight, getFontWeight, getOnlineFontPath } from "@/lib/fonts";
|
||||
import { Flex, Heading, Select, Tooltip, IconButton, Link, Box, Tabs, RadioGroup } from "@radix-ui/themes";
|
||||
import { PlusIcon, CircleQuestionMarkIcon } from "lucide-react";
|
||||
import { useLocale, useTranslations } from "next-intl";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
export const FontWeights = ["Regular", "Bold"];
|
||||
export const FontNames = ["Gentilis", "Helvetiker", "Optimer", "Noto_Sans_SC_zh", "Alibaba_PuHuiTi_3.0_zh"];
|
||||
|
||||
export type ColorGradientDir = "l2r" | "t2b";
|
||||
export type FontFrom = "online" | "upload";
|
||||
export enum FontFrom {
|
||||
online,
|
||||
upload,
|
||||
}
|
||||
export class TextProp {
|
||||
text: string
|
||||
color: string | string[]
|
||||
@@ -16,13 +17,13 @@ export class TextProp {
|
||||
fontFrom: FontFrom
|
||||
font: string
|
||||
fontUrl: string
|
||||
weight: string
|
||||
weight: FontWeight
|
||||
constructor(
|
||||
text: string,
|
||||
color: string,
|
||||
fontFrom: FontFrom,
|
||||
font: string,
|
||||
weight: string) {
|
||||
weight: FontWeight) {
|
||||
|
||||
this.text = text;
|
||||
this.color = color;
|
||||
@@ -35,35 +36,22 @@ export class TextProp {
|
||||
|
||||
static default(text: string): TextProp {
|
||||
|
||||
let font = FontNames[0];
|
||||
let font = Fonts[0].name;
|
||||
|
||||
if (containsChinese(text)) {
|
||||
font = "Alibaba_PuHuiTi_3.0_zh";
|
||||
font = DefaultFontChinese;
|
||||
}
|
||||
return {
|
||||
text,
|
||||
color: "#8e86fe",
|
||||
colorGradientDir: "l2r",
|
||||
font,
|
||||
fontUrl: getOnlineFontPath(font, FontWeights[0]),
|
||||
weight: FontWeights[0],
|
||||
fontFrom: "online",
|
||||
fontUrl: getOnlineFontPath(font, FontWeight.Regular),
|
||||
weight: FontWeight.Regular,
|
||||
fontFrom: FontFrom.online,
|
||||
}
|
||||
}
|
||||
}
|
||||
function getOnlineFontPath(fontName: string, fontWeight: String) {
|
||||
|
||||
let font = fontName;
|
||||
if (fontName.endsWith("zh")) {
|
||||
font = fontName.slice(0, -3);
|
||||
}
|
||||
return `https://fast3dtest.mysoul.fun/${font}_${fontWeight}.json`;
|
||||
|
||||
}
|
||||
|
||||
function containsChinese(str: string) {
|
||||
return /[\u4e00-\u9fa5]/.test(str);
|
||||
}
|
||||
|
||||
export interface UploadFont {
|
||||
name: string;
|
||||
@@ -72,6 +60,17 @@ export interface UploadFont {
|
||||
|
||||
type TextMode = "color" | "gradient";
|
||||
|
||||
const getFontWeightEnabled = (font: string) => {
|
||||
|
||||
let f = Fonts.find(item => item.name == font);
|
||||
|
||||
const map = new Map<string, boolean>()
|
||||
|
||||
if (f) {
|
||||
f.weight.forEach(w => map.set(w, true))
|
||||
}
|
||||
return map;
|
||||
};
|
||||
export default function TextSetting({
|
||||
text,
|
||||
setText,
|
||||
@@ -81,15 +80,16 @@ export default function TextSetting({
|
||||
}) {
|
||||
const locale = useLocale();
|
||||
|
||||
const t = useTranslations("TextEditor");
|
||||
|
||||
|
||||
const t = useTranslations("TextEditor");
|
||||
const [uploadFonts, setUploadFonts] = useState<UploadFont[]>([]);
|
||||
const isPureColor = !Array.isArray(text.color);
|
||||
const [textColorMode, setTextColorMode] = useState<TextMode>(isPureColor ? "color" : "gradient");
|
||||
const [textColor, setTextColor] = useState<string>(isPureColor ? text.color as string : "#000000");
|
||||
const [textGradientColor, setTextGradientColor] = useState<string[]>(!isPureColor ? text.color as string[] : ["#ce6464", "#63635a"]);
|
||||
const [colorGradientDir, setColorGradientDir] = useState<ColorGradientDir>(text.colorGradientDir as ColorGradientDir);
|
||||
|
||||
const [fontWeightEnbled, setFontWeightEnabled] = useState<Map<string, boolean>>(getFontWeightEnabled(text.font));
|
||||
|
||||
let inited = useRef(false);
|
||||
useEffect(() => {
|
||||
@@ -102,7 +102,7 @@ export default function TextSetting({
|
||||
if (uploadFonts.length > 0) {
|
||||
handleSelectFont(uploadFonts[uploadFonts.length - 1].name)
|
||||
} else {
|
||||
handleSelectFont(FontNames[0])
|
||||
handleSelectFont(Fonts[0].name);
|
||||
}
|
||||
}, [uploadFonts]);
|
||||
|
||||
@@ -150,14 +150,25 @@ export default function TextSetting({
|
||||
}, [colorGradientDir]);
|
||||
|
||||
const handleSelectFont = (font: string) => {
|
||||
if (FontNames.indexOf(font) !== -1) {
|
||||
setText({ ...text, font: font, fontFrom: "online", fontUrl: getOnlineFontPath(font, text.weight) });
|
||||
const f = Fonts.find(item => item.name == font);
|
||||
if (f) {
|
||||
if (f.weight.includes(text.weight)) {
|
||||
setText({ ...text, font: font, fontFrom: FontFrom.online, fontUrl: getOnlineFontPath(font, text.weight) });
|
||||
} else {
|
||||
const w = f.weight[0];
|
||||
setText({ ...text, font: font, fontFrom: FontFrom.online, fontUrl: getOnlineFontPath(font, w), weight: w });
|
||||
}
|
||||
} else {
|
||||
let f = uploadFonts.find((item) => item.name === font)!;
|
||||
setText({ ...text, font: font, fontFrom: "upload", fontUrl: f.url });
|
||||
setText({ ...text, font: font, fontFrom: FontFrom.upload, fontUrl: f.url });
|
||||
}
|
||||
|
||||
const map = getFontWeightEnabled(font);
|
||||
setFontWeightEnabled(map);
|
||||
};
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<Flex className="p-4 border rounded-lg " gap={"3"} direction={"column"}>
|
||||
<Heading as="h2" size="4" className="font-medium text-lg" >{t("title")}</Heading>
|
||||
@@ -271,7 +282,7 @@ export default function TextSetting({
|
||||
|
||||
<Select.Group>
|
||||
<Select.Label>Online</Select.Label>
|
||||
{FontNames.map((name) => <Select.Item key={name} value={name}>{name}</Select.Item>)}
|
||||
{Fonts.map(({ name }) => <Select.Item key={name} value={name}>{name}</Select.Item>)}
|
||||
</Select.Group>
|
||||
|
||||
</Select.Content>
|
||||
@@ -311,13 +322,13 @@ export default function TextSetting({
|
||||
{t("fontWeight")}
|
||||
</Heading>
|
||||
|
||||
<Select.Root defaultValue={`${text.weight}`} onValueChange={(e) => setText({ ...text, weight: e })}>
|
||||
<Select.Root value={text.weight} onValueChange={(e) => setText({ ...text, weight: getFontWeight(e) })}>
|
||||
<Select.Trigger />
|
||||
<Select.Content>
|
||||
{FontWeights.map((name) => <Select.Item disabled={text.fontFrom == "upload"} key={name} value={name}>{name}</Select.Item>)}
|
||||
{Object.entries(FontWeight).map(([name, value]) =>
|
||||
<Select.Item disabled={!fontWeightEnbled.get(name)} key={name} value={name}>{value}</Select.Item>)}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
|
||||
</div>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
@@ -97,6 +97,8 @@ export function resize(
|
||||
console.log("resize to width = " + width + " height = " + height);
|
||||
|
||||
camera.aspect = width / height;
|
||||
camera.updateProjectionMatrix();
|
||||
|
||||
// camera = new THREE.OrthographicCamera(
|
||||
// clientWidth / -2,
|
||||
// clientWidth / 2,
|
||||
@@ -105,6 +107,7 @@ export function resize(
|
||||
// 0.1,
|
||||
// 1000
|
||||
// );
|
||||
|
||||
renderer.setSize(width, height, false);
|
||||
}
|
||||
|
||||
|
||||
85
src/lib/fonts.ts
Normal file
85
src/lib/fonts.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
export enum FontWeight {
|
||||
Regular = "Regular",
|
||||
Bold = "Bold",
|
||||
}
|
||||
|
||||
export enum FontLang {
|
||||
EN = "en",
|
||||
ZH = "zh",
|
||||
}
|
||||
|
||||
export function getFontWeight(s: string) {
|
||||
switch (s) {
|
||||
case "Regular":
|
||||
return FontWeight.Regular;
|
||||
case "Bold":
|
||||
return FontWeight.Bold;
|
||||
default:
|
||||
return FontWeight.Regular;
|
||||
}
|
||||
}
|
||||
|
||||
export class FontDefine {
|
||||
name: string;
|
||||
weight: FontWeight[];
|
||||
lang: FontLang[];
|
||||
|
||||
constructor(name: string, weight: FontWeight[], lang: FontLang[]) {
|
||||
this.name = name;
|
||||
this.weight = weight;
|
||||
this.lang = lang;
|
||||
}
|
||||
}
|
||||
|
||||
export function getOnlineFontPath(fontName: string, w: FontWeight) {
|
||||
return `https://fast3dtest.mysoul.fun/${fontName}_${w}.json`;
|
||||
}
|
||||
|
||||
export function containsChinese(str: string) {
|
||||
return /[\u4e00-\u9fa5]/.test(str);
|
||||
}
|
||||
|
||||
export const DefaultFontChinese: string = "Alibaba_PuHuiTi_3.0";
|
||||
|
||||
export const Fonts: FontDefine[] = [
|
||||
{
|
||||
name: "Gentilis",
|
||||
weight: [FontWeight.Regular, FontWeight.Bold],
|
||||
lang: [FontLang.EN],
|
||||
},
|
||||
{
|
||||
name: "Helvetiker",
|
||||
weight: [FontWeight.Regular, FontWeight.Bold],
|
||||
lang: [FontLang.EN],
|
||||
},
|
||||
{
|
||||
name: "Optimer",
|
||||
weight: [FontWeight.Regular, FontWeight.Bold],
|
||||
lang: [FontLang.EN],
|
||||
},
|
||||
{
|
||||
name: "Alibaba_PuHuiTi_3.0",
|
||||
weight: [FontWeight.Regular, FontWeight.Bold],
|
||||
lang: [FontLang.EN, FontLang.ZH],
|
||||
},
|
||||
{
|
||||
name: "Noto_Sans_SC",
|
||||
weight: [FontWeight.Regular, FontWeight.Bold],
|
||||
lang: [FontLang.EN, FontLang.ZH],
|
||||
},
|
||||
{
|
||||
name: "Barbie_Doll",
|
||||
weight: [FontWeight.Regular],
|
||||
lang: [FontLang.EN],
|
||||
},
|
||||
{
|
||||
name: "Barbie_Princess",
|
||||
weight: [FontWeight.Regular],
|
||||
lang: [FontLang.EN],
|
||||
},
|
||||
{
|
||||
name: "Bartex",
|
||||
weight: [FontWeight.Regular],
|
||||
lang: [FontLang.EN],
|
||||
},
|
||||
];
|
||||
Reference in New Issue
Block a user