完成M3 Scheme构建页面的功能。
This commit is contained in:
parent
14d775e956
commit
1553c51621
46
src/page-components/scheme/m3-scheme/Builder.module.css
Normal file
46
src/page-components/scheme/m3-scheme/Builder.module.css
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
150
src/page-components/scheme/m3-scheme/Builder.tsx
Normal file
150
src/page-components/scheme/m3-scheme/Builder.tsx
Normal file
|
@ -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<MaterialDesign3SchemeStorage>;
|
||||||
|
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<IdenticalColorEntry[]>([]);
|
||||||
|
const [deleted, setDeleted] = useState<string[]>([]);
|
||||||
|
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<string, string>();
|
||||||
|
|
||||||
|
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<string, string> = {};
|
||||||
|
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<string, string>());
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ScrollArea enableY>
|
||||||
|
<form action={handleSubmitAction} className={styles.builder_layout}>
|
||||||
|
<h5 className={styles.segment_title}>Required Colors</h5>
|
||||||
|
<label className={styles.label}>Source Color</label>
|
||||||
|
<div className={styles.color_picker_row}>
|
||||||
|
<FloatColorPicker
|
||||||
|
name="source"
|
||||||
|
color={
|
||||||
|
isNil(scheme.schemeStorage.source?.source) ||
|
||||||
|
isEmpty(scheme.schemeStorage.source?.source)
|
||||||
|
? undefined
|
||||||
|
: scheme.schemeStorage.source?.source
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{errMsg.has('source') && <span className={styles.error_msg}>{errMsg.get('source')}</span>}
|
||||||
|
</div>
|
||||||
|
<label className={styles.label}>Error Color</label>
|
||||||
|
<div className={styles.color_picker_row}>
|
||||||
|
<FloatColorPicker
|
||||||
|
name="error"
|
||||||
|
color={
|
||||||
|
isNil(scheme.schemeStorage.source?.error) ||
|
||||||
|
isEmpty(scheme.schemeStorage.source.error)
|
||||||
|
? undefined
|
||||||
|
: scheme.schemeStorage.source.error
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{errMsg.has('error') && <span className={styles.error_msg}>{errMsg.get('error')}</span>}
|
||||||
|
</div>
|
||||||
|
<h5 className={styles.segment_title}>Custom Colors</h5>
|
||||||
|
<label style={{ gridColumn: 1 }}>Name</label>
|
||||||
|
<label>Color</label>
|
||||||
|
<div>
|
||||||
|
<button type="button" className="small" onClick={addEntryAction}>
|
||||||
|
Add Color
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{originalColors
|
||||||
|
.filter((color) => !includes(deleted, color.id))
|
||||||
|
.map((color) => (
|
||||||
|
<ColorEntry
|
||||||
|
key={color.id}
|
||||||
|
entry={color}
|
||||||
|
onDelete={(index) => setDeleted((prev) => [...prev, index])}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{newColors
|
||||||
|
.filter((color) => !includes(deleted, color.id))
|
||||||
|
.map((color) => (
|
||||||
|
<ColorEntry
|
||||||
|
key={color.id}
|
||||||
|
entry={color}
|
||||||
|
onDelete={(index) => setDeleted((prev) => [...prev, index])}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<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