From 1553c5162133074d6ee7f4c9d0370f80d8a5f3dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Sat, 8 Feb 2025 13:54:12 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90M3=20Scheme=E6=9E=84=E5=BB=BA?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E7=9A=84=E5=8A=9F=E8=83=BD=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../scheme/m3-scheme/Builder.module.css | 46 ++++++ .../scheme/m3-scheme/Builder.tsx | 150 ++++++++++++++++++ 2 files changed, 196 insertions(+) create mode 100644 src/page-components/scheme/m3-scheme/Builder.module.css create mode 100644 src/page-components/scheme/m3-scheme/Builder.tsx diff --git a/src/page-components/scheme/m3-scheme/Builder.module.css b/src/page-components/scheme/m3-scheme/Builder.module.css new file mode 100644 index 0000000..9de5b8d --- /dev/null +++ b/src/page-components/scheme/m3-scheme/Builder.module.css @@ -0,0 +1,46 @@ +@layer pages { + .builder_layout { + padding: var(--spacing-s) var(--spacing-m); + font-size: var(--font-size-s); + line-height: 1.3em; + display: grid; + grid-template-columns: repeat(3, 200px); + align-items: center; + gap: var(--spacing-xs); + .label { + max-width: 200px; + grid-column: 1; + padding-inline-end: var(--spacing-m); + text-align: right; + } + .segment_title { + grid-column: 1 / span 2; + text-align: center; + } + .color_picker_row { + grid-column: 2 / span 2; + display: flex; + align-items: center; + gap: var(--spacing-s); + } + h5 { + font-size: var(--font-size-m); + line-height: 1.7em; + } + .error_msg { + color: var(--color-danger); + font-size: var(--font-size-xs); + } + .delete_btn { + font-size: var(--font-size-xs); + color: var(--color-yuebai); + background-color: oklch(from var(--color-danger) l c h / 0.25); + &:hover { + background-color: oklch(from var(--color-danger-hover) l c h / 0.65); + } + &:active { + background-color: oklch(from var(--color-danger-active) l c h / 0.65); + } + } + } +} diff --git a/src/page-components/scheme/m3-scheme/Builder.tsx b/src/page-components/scheme/m3-scheme/Builder.tsx new file mode 100644 index 0000000..fbbd462 --- /dev/null +++ b/src/page-components/scheme/m3-scheme/Builder.tsx @@ -0,0 +1,150 @@ +import { includes, isEmpty, isNil } from 'lodash-es'; +import { useActionState, useCallback, useMemo, useState } from 'react'; +import { useColorFunction } from '../../../ColorFunctionContext'; +import { FloatColorPicker } from '../../../components/FloatcolorPicker'; +import { ScrollArea } from '../../../components/ScrollArea'; +import { MaterialDesign3SchemeStorage } from '../../../material-3-scheme'; +import { SchemeContent } from '../../../models'; +import { useUpdateScheme } from '../../../stores/schemes'; +import { ColorEntry, IdenticalColorEntry } from '../ColorEntry'; +import styles from './Builder.module.css'; + +type M3SchemeBuilderProps = { + scheme: SchemeContent; + onBuildCompleted?: () => void; +}; + +export function M3SchemeBuilder({ scheme, onBuildCompleted }: M3SchemeBuilderProps) { + const { colorFn } = useColorFunction(); + const updateScheme = useUpdateScheme(scheme.id); + const originalColors = useMemo(() => { + return Object.entries(scheme.schemeStorage.source?.custom_colors ?? {}).map( + ([name, color], index) => ({ id: `oc_${index}`, name, color } as IdenticalColorEntry), + ); + }, [scheme.schemeStorage.source]); + const [newColors, setNewColors] = useState([]); + const [deleted, setDeleted] = useState([]); + const addEntryAction = useCallback(() => { + setNewColors((prev) => [...prev, { id: `nc_${prev.length}`, name: '', color: '' }]); + }, []); + const colorKeys = useMemo( + () => + [...originalColors, ...newColors] + .map((color) => color.id) + .filter((c) => !includes(deleted, c)), + [originalColors, newColors, deleted], + ); + + const [errMsg, handleSubmitAction] = useActionState((state, formData) => { + const errMsg = new Map(); + + try { + const sourceColor = formData.get('source'); + if (isNil(sourceColor) || isEmpty(sourceColor)) { + errMsg.set('source', 'Source color is required'); + } + const errorColor = formData.get('error'); + if (isNil(errorColor) || isEmpty(errorColor)) { + errMsg.set('error', 'Error color is required'); + } + if (!isEmpty(errMsg)) return errMsg; + + const customColors: Record = {}; + for (const key of colorKeys) { + const name = formData.get(`name_${key}`) as string; + const color = formData.get(`color_${key}`) as string; + if (isNil(name) || isEmpty(name) || isNil(color) || isEmpty(color)) continue; + customColors[name] = color; + } + + const generatedScheme = colorFn?.generate_material_design_3_scheme( + sourceColor, + errorColor, + customColors, + ); + updateScheme((prev) => { + prev.schemeStorage.source = { + source: sourceColor as string, + error: errorColor as string, + custom_colors: customColors, + }; + prev.schemeStorage.scheme = generatedScheme[0]; + prev.schemeStorage.cssVariables = generatedScheme[1]; + prev.schemeStorage.scssVariables = generatedScheme[2]; + prev.schemeStorage.jsVariables = generatedScheme[3]; + return prev; + }); + + onBuildCompleted?.(); + } catch (e) { + console.error('[generate m3 scheme]', e); + } + + return errMsg; + }, new Map()); + + return ( + +
+
Required Colors
+ +
+ + {errMsg.has('source') && {errMsg.get('source')}} +
+ +
+ + {errMsg.has('error') && {errMsg.get('error')}} +
+
Custom Colors
+ + +
+ +
+ {originalColors + .filter((color) => !includes(deleted, color.id)) + .map((color) => ( + setDeleted((prev) => [...prev, index])} + /> + ))} + {newColors + .filter((color) => !includes(deleted, color.id)) + .map((color) => ( + setDeleted((prev) => [...prev, index])} + /> + ))} +
+ +
+ +
+ ); +}