From b124bb4eda840f0b4d418916066fedb460878aa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= Date: Thu, 6 Feb 2025 11:21:51 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9F=BA=E6=9C=AC=E5=AE=8C=E6=88=90Q=20Scheme?= =?UTF-8?q?=E7=9A=84Builder=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/q-scheme/Builder.module.css | 37 ++ .../scheme/q-scheme/Builder.tsx | 378 ++++++++++++++++++ 2 files changed, 415 insertions(+) create mode 100644 src/page-components/scheme/q-scheme/Builder.module.css create mode 100644 src/page-components/scheme/q-scheme/Builder.tsx diff --git a/src/page-components/scheme/q-scheme/Builder.module.css b/src/page-components/scheme/q-scheme/Builder.module.css new file mode 100644 index 0000000..6a68ff8 --- /dev/null +++ b/src/page-components/scheme/q-scheme/Builder.module.css @@ -0,0 +1,37 @@ +@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: 200px 200px 200px; + gap: var(--spacing-xs); + .label { + max-width: 200px; + grid-column: 1; + padding-inline-end: var(--spacing-m); + text-align: right; + } + .color_picker_row { + grid-column: 2 / span 2; + display: flex; + align-items: center; + gap: var(--spacing-s); + .error_msg { + color: var(--color-danger); + font-size: var(--font-size-xs); + } + } + .segment_title { + grid-column: 1 / span 2; + text-align: center; + } + .parameter_input { + max-width: 8em; + } + h5 { + font-size: var(--font-size-m); + line-height: 1.7em; + } + } +} diff --git a/src/page-components/scheme/q-scheme/Builder.tsx b/src/page-components/scheme/q-scheme/Builder.tsx new file mode 100644 index 0000000..ef9edf2 --- /dev/null +++ b/src/page-components/scheme/q-scheme/Builder.tsx @@ -0,0 +1,378 @@ +import { every, isEmpty, isNil } from 'lodash-es'; +import { useActionState, useMemo } from 'react'; +import { + ColorExpand, + ColorShifting, + SchemeSetting, + WACGSetting, +} from '../../../color_functions/color_module'; +import { useColorFunction } from '../../../ColorFunctionContext'; +import { FloatColorPicker } from '../../../components/FloatcolorPicker'; +import { ScrollArea } from '../../../components/ScrollArea'; +import { VSegmentedControl } from '../../../components/VSegmentedControl'; +import { SchemeContent } from '../../../models'; +import { QSchemeSource, QSchemeStorage } from '../../../q-scheme'; +import { useUpdateScheme } from '../../../stores/schemes'; +import { defaultEmptyFormData } from '../../../utls'; +import styles from './Builder.module.css'; + +type QSchemeBuilderProps = { + scheme: SchemeContent; +}; + +export function QSchemeBuilder({ scheme }: QSchemeBuilderProps) { + const { colorFn } = useColorFunction(); + const updateScheme = useUpdateScheme(scheme.id); + const defaultSetting = useMemo(() => { + try { + if (!colorFn) throw 'Web Assembly functions is not available'; + const defaultValues = colorFn.q_scheme_default_settings(); + if (!scheme.schemeStorage.source?.setting) + return { + hover: { + chroma: + scheme.schemeStorage.source?.setting?.hover.chroma ?? defaultValues.hover.chroma, + lightness: + scheme.schemeStorage.source?.setting?.hover.lightness ?? + defaultValues.hover.lightness, + }, + active: { + chroma: + scheme.schemeStorage.source?.setting?.active.chroma ?? defaultValues.active.chroma, + lightness: + scheme.schemeStorage.source?.setting?.active.lightness ?? + defaultValues.active.lightness, + }, + focus: { + chroma: + scheme.schemeStorage.source?.setting?.focus.chroma ?? defaultValues.focus.chroma, + lightness: + scheme.schemeStorage.source?.setting?.focus.lightness ?? + defaultValues.focus.lightness, + }, + disabled: { + chroma: + scheme.schemeStorage.source?.setting?.disabled.chroma ?? + defaultValues.disabled.chroma, + lightness: + scheme.schemeStorage.source?.setting?.disabled.lightness ?? + defaultValues.disabled.lightness, + }, + dark_convert: { + chroma: + scheme.schemeStorage.source?.setting?.dark_convert.chroma ?? + defaultValues.dark_convert.chroma, + lightness: + scheme.schemeStorage.source?.setting?.dark_convert.lightness ?? + defaultValues.dark_convert.lightness, + }, + expand_method: + scheme.schemeStorage.source?.setting?.expand_method ?? defaultValues.expand_method, + wacg_follows: + scheme.schemeStorage.source?.setting?.wacg_follows ?? defaultValues.wacg_follows, + } as SchemeSetting; + return defaultValues; + } catch (e) { + console.error('[Q scheme builder]', e); + } + return null; + }, [scheme]); + const expandingMethods = useMemo(() => { + try { + if (!colorFn) throw 'Web Assembly functions is not available'; + return colorFn.q_scheme_color_expanding_methods(); + } catch (e) { + console.error('[Q scheme builder]', e); + } + return []; + }, []); + const wacgFollowStrategies = useMemo(() => { + try { + if (!colorFn) throw 'Web Assembly functions is not available'; + return colorFn.q_scheme_wacg_settings(); + } catch (e) { + console.error('[Q scheme builder]', e); + } + return []; + }, []); + + const [errMsg, handleSubmitAction] = useActionState((state, formData) => { + const errMsg = new Map(); + const requiredFields = [ + 'primary', + 'danger', + 'success', + 'warn', + 'info', + 'foreground', + 'background', + ]; + for (const field of requiredFields) { + if (!formData.get(field)) { + errMsg.set(field, 'This color is required for scheme generating.'); + } + } + if (!isEmpty(errMsg)) return errMsg; + try { + const schemeSetting = new SchemeSetting( + new ColorShifting( + Number(formData.get('hover_chroma')) / 100, + Number(formData.get('hover_lightness')) / 100, + ), + new ColorShifting( + Number(formData.get('active_chroma')) / 100, + Number(formData.get('active_lightness')) / 100, + ), + new ColorShifting( + Number(formData.get('focus_chroma')) / 100, + Number(formData.get('focus_lightness')) / 100, + ), + new ColorShifting( + Number(formData.get('disabled_chroma')) / 100, + Number(formData.get('disabled_lightness')) / 100, + ), + new ColorShifting( + Number(formData.get('dark_chroma')) / 100, + Number(formData.get('dark_lightness')) / 100, + ), + Number(formData.get('expanding')) as ColorExpand, + Number(formData.get('wacg')) as WACGSetting, + ); + + const source: QSchemeSource = { + primary: defaultEmptyFormData(formData, 'primary', null), + secondary: defaultEmptyFormData(formData, 'secondary', undefined), + tertiary: defaultEmptyFormData(formData, 'tertiary', undefined), + accent: defaultEmptyFormData(formData, 'accent', undefined), + danger: defaultEmptyFormData(formData, 'danger', null), + success: defaultEmptyFormData(formData, 'success', null), + warning: defaultEmptyFormData(formData, 'warn', null), + info: defaultEmptyFormData(formData, 'info', null), + foreground: defaultEmptyFormData(formData, 'foreground', null), + background: defaultEmptyFormData(formData, 'background', null), + setting: schemeSetting, + }; + console.debug('[collected]', source); + const generatedScheme = every([source.secondary, source.tertiary, source.accent], isNil) + ? colorFn?.generate_q_scheme_automatically( + source.primary, + source.danger, + source.success, + source.warning, + source.info, + source.foreground, + source.background, + source.setting, + ) + : colorFn?.generate_q_scheme_manually( + source.primary, + source.secondary ?? undefined, + source.tertiary ?? undefined, + source.accent ?? undefined, + source.danger, + source.success, + source.warning, + source.info, + source.foreground, + source.background, + source.setting, + ); + updateScheme((prev) => { + prev.schemeStorage.source = source; + prev.schemeStorage.scheme = generatedScheme[0]; + prev.schemeStorage.cssVariables = generatedScheme[1]; + prev.schemeStorage.scssVariables = generatedScheme[2]; + prev.schemeStorage.jsVariables = generatedScheme[3]; + return prev; + }); + console.debug('[generated]', generatedScheme); + } catch (e) { + console.error('[build q scheme]', e); + } + + return errMsg; + }, new Map()); + + return ( + +
+
Original Colors
+ +
+ + {errMsg.has('primary') && ( + {errMsg.get('primary')} + )} +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ + {errMsg.has('danger') && {errMsg.get('danger')}} +
+ +
+ + {errMsg.has('success') && ( + {errMsg.get('success')} + )} +
+ +
+ + {errMsg.has('warn') && {errMsg.get('warn')}} +
+ +
+ + {errMsg.has('info') && {errMsg.get('info')}} +
+ +
+ + {errMsg.has('foreground') && ( + {errMsg.get('foreground')} + )} +
+ +
+ + {errMsg.has('background') && ( + {errMsg.get('background')} + )} +
+
Automated parameters
+ + + +
+ + % +
+
+ + % +
+ +
+ + % +
+
+ + % +
+ +
+ + % +
+
+ + % +
+ +
+ + % +
+
+ + % +
+ +
+ + % +
+
+ + % +
+
Settings
+ +
+ +
+ +
+ +
+
+ +
+
+
+ ); +}