基本完成Q Scheme的Builder功能。
This commit is contained in:
parent
92229b0de4
commit
b124bb4eda
37
src/page-components/scheme/q-scheme/Builder.module.css
Normal file
37
src/page-components/scheme/q-scheme/Builder.module.css
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
378
src/page-components/scheme/q-scheme/Builder.tsx
Normal file
378
src/page-components/scheme/q-scheme/Builder.tsx
Normal file
|
@ -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<QSchemeStorage>;
|
||||||
|
};
|
||||||
|
|
||||||
|
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<string, string>();
|
||||||
|
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<string, string>());
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ScrollArea enableY>
|
||||||
|
<form action={handleSubmitAction} className={styles.builder_layout}>
|
||||||
|
<h5 className={styles.segment_title}>Original Colors</h5>
|
||||||
|
<label className={styles.label}>Primary Color*</label>
|
||||||
|
<div className={styles.color_picker_row}>
|
||||||
|
<FloatColorPicker name="primary" color={scheme.schemeStorage.source?.primary} />
|
||||||
|
{errMsg.has('primary') && (
|
||||||
|
<span className={styles.error_msg}>{errMsg.get('primary')}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<label className={styles.label}>Secondary Color</label>
|
||||||
|
<div className={styles.color_picker_row}>
|
||||||
|
<FloatColorPicker name="secondary" color={scheme.schemeStorage.source?.secondary} />
|
||||||
|
</div>
|
||||||
|
<label className={styles.label}>Tertiary Color</label>
|
||||||
|
<div className={styles.color_picker_row}>
|
||||||
|
<FloatColorPicker name="tertiary" color={scheme.schemeStorage.source?.tertiary} />
|
||||||
|
</div>
|
||||||
|
<label className={styles.label}>Accent Color</label>
|
||||||
|
<div className={styles.color_picker_row}>
|
||||||
|
<FloatColorPicker name="accent" color={scheme.schemeStorage.source?.accent} />
|
||||||
|
</div>
|
||||||
|
<label className={styles.label}>Danger Color*</label>
|
||||||
|
<div className={styles.color_picker_row}>
|
||||||
|
<FloatColorPicker name="danger" color={scheme.schemeStorage.source?.danger} />
|
||||||
|
{errMsg.has('danger') && <span className={styles.error_msg}>{errMsg.get('danger')}</span>}
|
||||||
|
</div>
|
||||||
|
<label className={styles.label}>Success Color*</label>
|
||||||
|
<div className={styles.color_picker_row}>
|
||||||
|
<FloatColorPicker name="success" color={scheme.schemeStorage.source?.success} />
|
||||||
|
{errMsg.has('success') && (
|
||||||
|
<span className={styles.error_msg}>{errMsg.get('success')}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<label className={styles.label}>Warning Color*</label>
|
||||||
|
<div className={styles.color_picker_row}>
|
||||||
|
<FloatColorPicker name="warn" color={scheme.schemeStorage.source?.warning} />
|
||||||
|
{errMsg.has('warn') && <span className={styles.error_msg}>{errMsg.get('warn')}</span>}
|
||||||
|
</div>
|
||||||
|
<label className={styles.label}>Info Color*</label>
|
||||||
|
<div className={styles.color_picker_row}>
|
||||||
|
<FloatColorPicker name="info" color={scheme.schemeStorage.source?.info} />
|
||||||
|
{errMsg.has('info') && <span className={styles.error_msg}>{errMsg.get('info')}</span>}
|
||||||
|
</div>
|
||||||
|
<label className={styles.label}>Foreground Color*</label>
|
||||||
|
<div className={styles.color_picker_row}>
|
||||||
|
<FloatColorPicker name="foreground" color={scheme.schemeStorage.source?.foreground} />
|
||||||
|
{errMsg.has('foreground') && (
|
||||||
|
<span className={styles.error_msg}>{errMsg.get('foreground')}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<label className={styles.label}>Background Color*</label>
|
||||||
|
<div className={styles.color_picker_row}>
|
||||||
|
<FloatColorPicker name="background" color={scheme.schemeStorage.source?.background} />
|
||||||
|
{errMsg.has('background') && (
|
||||||
|
<span className={styles.error_msg}>{errMsg.get('background')}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<h5 className={styles.segment_title}>Automated parameters</h5>
|
||||||
|
<label style={{ gridColumn: 2 }}>Chroma shifting</label>
|
||||||
|
<label style={{ gridColumn: 3 }}>Lightness shifting</label>
|
||||||
|
<label className={styles.label}>Hover</label>
|
||||||
|
<div className="input_wrapper">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
name="hover_chroma"
|
||||||
|
defaultValue={((defaultSetting?.hover.chroma ?? 0) * 100).toFixed(2)}
|
||||||
|
className={styles.parameter_input}
|
||||||
|
/>
|
||||||
|
<span>%</span>
|
||||||
|
</div>
|
||||||
|
<div className="input_wrapper">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
name="hover_lightness"
|
||||||
|
defaultValue={((defaultSetting?.hover.lightness ?? 0) * 100).toFixed(2)}
|
||||||
|
className={styles.parameter_input}
|
||||||
|
/>
|
||||||
|
<span>%</span>
|
||||||
|
</div>
|
||||||
|
<label className={styles.label}>Active</label>
|
||||||
|
<div className="input_wrapper">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
name="active_chroma"
|
||||||
|
defaultValue={((defaultSetting?.active.chroma ?? 0) * 100).toFixed(2)}
|
||||||
|
className={styles.parameter_input}
|
||||||
|
/>
|
||||||
|
<span>%</span>
|
||||||
|
</div>
|
||||||
|
<div className="input_wrapper">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
name="active_lightness"
|
||||||
|
defaultValue={((defaultSetting?.active.lightness ?? 0) * 100).toFixed(2)}
|
||||||
|
className={styles.parameter_input}
|
||||||
|
/>
|
||||||
|
<span>%</span>
|
||||||
|
</div>
|
||||||
|
<label className={styles.label}>Focus</label>
|
||||||
|
<div className="input_wrapper">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
name="focus_chroma"
|
||||||
|
defaultValue={((defaultSetting?.focus.chroma ?? 0) * 100).toFixed(2)}
|
||||||
|
className={styles.parameter_input}
|
||||||
|
/>
|
||||||
|
<span>%</span>
|
||||||
|
</div>
|
||||||
|
<div className="input_wrapper">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
name="focus_lightness"
|
||||||
|
defaultValue={((defaultSetting?.focus.lightness ?? 0) * 100).toFixed(2)}
|
||||||
|
className={styles.parameter_input}
|
||||||
|
/>
|
||||||
|
<span>%</span>
|
||||||
|
</div>
|
||||||
|
<label className={styles.label}>Disabled</label>
|
||||||
|
<div className="input_wrapper">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
name="disabled_chroma"
|
||||||
|
defaultValue={((defaultSetting?.disabled.chroma ?? 0) * 100).toFixed(2)}
|
||||||
|
className={styles.parameter_input}
|
||||||
|
/>
|
||||||
|
<span>%</span>
|
||||||
|
</div>
|
||||||
|
<div className="input_wrapper">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
name="disabled_lightness"
|
||||||
|
defaultValue={((defaultSetting?.disabled.lightness ?? 0) * 100).toFixed(2)}
|
||||||
|
className={styles.parameter_input}
|
||||||
|
/>
|
||||||
|
<span>%</span>
|
||||||
|
</div>
|
||||||
|
<label className={styles.label}>Convert to Dark scheme</label>
|
||||||
|
<div className="input_wrapper">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
name="dark_chroma"
|
||||||
|
defaultValue={((defaultSetting?.dark_convert.chroma ?? 0) * 100).toFixed(2)}
|
||||||
|
className={styles.parameter_input}
|
||||||
|
/>
|
||||||
|
<span>%</span>
|
||||||
|
</div>
|
||||||
|
<div className="input_wrapper">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
name="dark_lightness"
|
||||||
|
defaultValue={((defaultSetting?.dark_convert.lightness ?? 0) * 100).toFixed(2)}
|
||||||
|
className={styles.parameter_input}
|
||||||
|
/>
|
||||||
|
<span>%</span>
|
||||||
|
</div>
|
||||||
|
<h5 className={styles.segment_title}>Settings</h5>
|
||||||
|
<label className={styles.label}>Color Expanding Method</label>
|
||||||
|
<div style={{ gridColumn: '2 / span 2' }}>
|
||||||
|
<VSegmentedControl
|
||||||
|
options={expandingMethods}
|
||||||
|
name="expanding"
|
||||||
|
defaultValue={defaultSetting?.expand_method}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<label className={styles.label}>Follow WACG Standard</label>
|
||||||
|
<div style={{ gridColumn: '2 / span 2' }}>
|
||||||
|
<VSegmentedControl
|
||||||
|
options={wacgFollowStrategies}
|
||||||
|
name="wacg"
|
||||||
|
defaultValue={defaultSetting?.wacg_follows}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ gridColumn: '2 / span 2' }}>
|
||||||
|
<button type="submit" className="primary">
|
||||||
|
Build Scheme
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</ScrollArea>
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user