为Swatch Builder增加保存草稿的功能。

This commit is contained in:
徐涛 2025-03-30 22:48:49 +08:00
parent f284a7ef62
commit 2638bbd99a
2 changed files with 79 additions and 35 deletions

View File

@ -30,6 +30,12 @@
.parameter_input {
max-width: 8em;
}
.button_row {
display: flex;
flex-direction: row;
align-items: center;
gap: var(--spacing-s);
}
h5 {
font-size: var(--font-size-m);
line-height: 1.7em;

View File

@ -2,6 +2,7 @@ import { ColorShifting, SwatchEntry, SwatchSchemeSetting } from 'color-module';
import { includes, isEmpty, isEqual, isNaN } from 'lodash-es';
import { useActionState, useCallback, useMemo, useState } from 'react';
import { useColorFunction } from '../../../ColorFunctionContext';
import { NotificationType, useNotification } from '../../../components/Notifications';
import { ScrollArea } from '../../../components/ScrollArea';
import { Switch } from '../../../components/Switch';
import { SchemeContent } from '../../../models';
@ -10,6 +11,7 @@ import {
QSwatchEntry,
QSwatchSchemeSetting,
SwatchScheme,
SwatchSchemeSource,
SwatchSchemeStorage,
} from '../../../swatch_scheme';
import { mapToObject } from '../../../utls';
@ -22,6 +24,7 @@ type SwatchSchemeBuilderProps = {
};
export function SwatchSchemeBuilder({ scheme, onBuildCompleted }: SwatchSchemeBuilderProps) {
const { showToast } = useNotification();
const { colorFn } = useColorFunction();
const updateScheme = useUpdateScheme(scheme.id);
const originalColors = useMemo(() => {
@ -64,63 +67,95 @@ export function SwatchSchemeBuilder({ scheme, onBuildCompleted }: SwatchSchemeBu
}
return null;
}, [scheme.schemeStorage.source]);
const collectSchemeSource = (formData: FormData): SwatchSchemeSource => {
const swatchAmount = Number(formData.get('amount'));
const minLightness = Number(formData.get('min_lightness'));
const maxLightness = Number(formData.get('max_lightness'));
const includePrimary = isEqual(formData.get('include_primary'), 'true');
const darkConvertChroma = Number(formData.get('dark_chroma')) / 100.0;
const darkConvertLightness = Number(formData.get('dark_lightness')) / 100.0;
const swatchSetting = new SwatchSchemeSetting(
swatchAmount,
minLightness / 100.0,
maxLightness / 100.0,
includePrimary,
new ColorShifting(darkConvertChroma, darkConvertLightness),
);
const dumpedSettings = swatchSetting.toJsValue() as QSwatchSchemeSetting;
const entries: SwatchEntry[] = [];
for (const key of colorKeys) {
const name = String(formData.get(`name_${key}`));
const color = String(formData.get(`color_${key}`));
if (isEmpty(name) || isEmpty(color)) continue;
entries.push(new SwatchEntry(name, color));
}
const dumpedEntries = entries.map((entry) => entry.toJsValue() as QSwatchEntry);
return {
colors: dumpedEntries,
setting: dumpedSettings,
};
};
const [, handleDraftAction] = useActionState<Map<string, string>, FormData>(
(_state, formData) => {
const errMsg = new Map<string, string>();
const collected = collectSchemeSource(formData);
updateScheme((prev) => {
prev.schemeStorage.source = collected;
return prev;
});
setNewColors([]);
showToast(NotificationType.SUCCESS, 'Scheme draft saved!', 'tabler:device-floppy', 3000);
return errMsg;
},
new Map<string, string>(),
);
const [errMsg, handleSubmitAction] = useActionState<Map<string, string>, FormData>(
(_state, formData) => {
const errMsg = new Map<string, string>();
try {
const swatchAmount = Number(formData.get('amount'));
if (isNaN(swatchAmount) || swatchAmount <= 0) {
const collected = collectSchemeSource(formData);
if (isNaN(collected.setting.amount) || collected.setting.amount <= 0) {
errMsg.set('amount', 'MUST be a positive number');
}
if (swatchAmount > 30) {
if (collected.setting.amount > 30) {
errMsg.set('amount', 'MUST be less than 30');
}
const minLightness = Number(formData.get('min_lightness'));
if (isNaN(minLightness) || minLightness < 0 || minLightness > 100) {
if (
isNaN(collected.setting.min_lightness) ||
collected.setting.min_lightness < 0 ||
collected.setting.min_lightness > 1.0
) {
errMsg.set('min', 'MUST be a number between 0 and 100');
}
const maxLightness = Number(formData.get('max_lightness'));
if (isNaN(maxLightness) || maxLightness < 0 || maxLightness > 100) {
if (
isNaN(collected.setting.max_lightness) ||
collected.setting.max_lightness < 0 ||
collected.setting.max_lightness > 1.0
) {
errMsg.set('max', 'MUST be a number between 0 and 100');
}
const includePrimary = isEqual(formData.get('include_primary'), 'true');
const darkConvertChroma = Number(formData.get('dark_chroma')) / 100.0;
const darkConvertLightness = Number(formData.get('dark_lightness')) / 100.0;
const swatchSetting = new SwatchSchemeSetting(
swatchAmount,
minLightness / 100.0,
maxLightness / 100.0,
includePrimary,
new ColorShifting(darkConvertChroma, darkConvertLightness),
);
const dumpedSettings = swatchSetting.toJsValue() as QSwatchSchemeSetting;
const entries: SwatchEntry[] = [];
for (const key of colorKeys) {
const name = String(formData.get(`name_${key}`));
const color = String(formData.get(`color_${key}`));
if (isEmpty(name) || isEmpty(color)) continue;
entries.push(new SwatchEntry(name, color));
}
const dumpedEntries = entries.map((entry) => entry.toJsValue() as QSwatchEntry);
if (isEmpty(entries)) {
if (isEmpty(collected.colors)) {
errMsg.set('color', 'At least one color is required');
}
if (!isEmpty(errMsg)) return errMsg;
const generatedScheme = colorFn?.generate_swatch_scheme(entries, swatchSetting);
console.debug('[generated scheme]', generatedScheme);
const generatedScheme = colorFn?.generate_swatch_scheme(
collected.colors,
collected.setting,
);
updateScheme((prev) => {
prev.schemeStorage.source = {
colors: dumpedEntries,
setting: dumpedSettings,
};
prev.schemeStorage.source = collected;
prev.schemeStorage.scheme = mapToObject(generatedScheme[0]) as SwatchScheme;
prev.schemeStorage.cssVariables = generatedScheme[1];
prev.schemeStorage.cssAutoSchemeVariables = generatedScheme[2];
@ -237,10 +272,13 @@ export function SwatchSchemeBuilder({ scheme, onBuildCompleted }: SwatchSchemeBu
<span className={styles.error_msg}>{errMsg.get('color')}</span>
</div>
)}
<div style={{ gridColumn: '2 / span 2' }}>
<div className={styles.button_row} style={{ gridColumn: '2 / span 2' }}>
<button type="submit" className="primary">
Build Scheme
</button>
<button type="submit" className="secondary" formAction={handleDraftAction}>
Save Draft
</button>
</div>
</form>
</ScrollArea>