From d0e8acc5c0868953bc77f5f55a9efb923bf8f164 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Fri, 14 Feb 2025 08:04:44 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9F=BA=E6=9C=AC=E5=AE=8C=E6=88=90M3=E5=8A=A8?= =?UTF-8?q?=E6=80=81Scheme=E7=9A=84=E6=9E=84=E5=BB=BA=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../m3-dynamic-scheme/Builder.module.css | 52 +++++ .../scheme/m3-dynamic-scheme/Builder.tsx | 185 +++++++++++++++++- 2 files changed, 235 insertions(+), 2 deletions(-) create mode 100644 src/page-components/scheme/m3-dynamic-scheme/Builder.module.css diff --git a/src/page-components/scheme/m3-dynamic-scheme/Builder.module.css b/src/page-components/scheme/m3-dynamic-scheme/Builder.module.css new file mode 100644 index 0000000..bd149d9 --- /dev/null +++ b/src/page-components/scheme/m3-dynamic-scheme/Builder.module.css @@ -0,0 +1,52 @@ +@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); + } + .parallel_row { + display: flex; + flex-direction: row; + 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-dynamic-scheme/Builder.tsx b/src/page-components/scheme/m3-dynamic-scheme/Builder.tsx index b1fc907..920e90f 100644 --- a/src/page-components/scheme/m3-dynamic-scheme/Builder.tsx +++ b/src/page-components/scheme/m3-dynamic-scheme/Builder.tsx @@ -1,5 +1,186 @@ +import { includes, isEmpty, isEqual, 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 { Switch } from '../../../components/Switch'; +import { VSegmentedControl } from '../../../components/VSegmentedControl'; +import { MaterialDesign3DynamicSchemeStorage } from '../../../material-3-scheme'; +import { Option, SchemeContent } from '../../../models'; +import { useUpdateScheme } from '../../../stores/schemes'; +import { ColorEntry, IdenticalColorEntry } from '../ColorEntry'; +import styles from './Builder.module.css'; -export function M3DynamicSchemeBuilder() { - return ; +type M3DynamicSchemeBuilderProps = { + scheme: SchemeContent; + onBuildCompleted?: () => void; +}; + +export function M3DynamicSchemeBuilder({ scheme, onBuildCompleted }: M3DynamicSchemeBuilderProps) { + 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 variantOptions = useMemo(() => { + if (!colorFn) return []; + try { + return colorFn.material_design_3_dynamic_variant() as Option[]; + } catch (e) { + console.error('[m3 dynamic builder]', e); + } + return []; + }, []); + const [contrastLevel, setContrastLevel] = useState( + scheme.schemeStorage.source?.constrastLevel ?? 0, + ); + + const [errMsg, handleSubmitAction] = useActionState, FormData>( + (_state, formData) => { + const errMsg = new Map(); + + const sourceColor = formData.get('source') as string; + if (isNil(sourceColor) || isEmpty(sourceColor)) { + errMsg.set('source', 'Source color is required'); + } + if (!isEmpty(errMsg)) return errMsg; + + try { + const dynamicVariant = Number(formData.get('variant')); + const contrastLevel = Number(formData.get('contrast_level')); + const harmonizeCustoms = isEqual(formData.get('harmonize_customs'), 'true'); + const errorColor = formData.get('error') as string; + 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 generate_scheme = colorFn.generate_material_design_3_dynamic_scheme( + sourceColor, + isNil(errorColor) || isEmpty(errorColor) ? null : errorColor, + dynamicVariant, + contrastLevel, + harmonizeCustoms, + customColors, + ); + console.debug('[generate m3d]', generate_scheme); + } catch (e) { + console.error('[generate m3d]', e); + } + + return errMsg; + }, + new Map(), + ); + + return ( + +
+
Required Colors
+ +
+ + {errMsg.has('source') && {errMsg.get('source')}} +
+ +
+ +
+
Dynamic Settings
+ +
+ +
+ +
+ setContrastLevel(parseFloat(e.target.value))} + /> + {contrastLevel} +
+ +
+ +
+
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])} + /> + ))} +
+ +
+ +
+ ); }