增加bartex的style

This commit is contained in:
ymk
2025-08-11 10:38:30 +08:00
parent a293f985f5
commit 4a8e375ad0
21 changed files with 634 additions and 384 deletions

View File

@@ -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";

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

View 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}`,
},
},
};
}

View 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];

View 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}`,
},
},
};
}

View File

@@ -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,
];
}

View File

@@ -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"

View File

@@ -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";
/**
* 全特性工具栏

View File

@@ -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">

View File

@@ -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>
);
}

View File

@@ -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" && (

View File

@@ -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);

View File

@@ -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>
);
}

View File

@@ -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>
);

View File

@@ -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
View 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],
},
];