Compare commits

..

No commits in common. "2638bbd99a9155d1465d039114651130549b8ecc" and "ef3ef2b34998c4c23dd1788559fa658b2194dffc" have entirely different histories.

8 changed files with 112 additions and 265 deletions

View File

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

View File

@ -2,12 +2,8 @@ import { includes, isEmpty, isNil, merge } from 'lodash-es';
import { useActionState, useCallback, useMemo, useState } from 'react'; import { useActionState, useCallback, useMemo, useState } from 'react';
import { useColorFunction } from '../../../ColorFunctionContext'; import { useColorFunction } from '../../../ColorFunctionContext';
import { FloatColorPicker } from '../../../components/FloatColorPicker'; import { FloatColorPicker } from '../../../components/FloatColorPicker';
import { NotificationType, useNotification } from '../../../components/Notifications';
import { ScrollArea } from '../../../components/ScrollArea'; import { ScrollArea } from '../../../components/ScrollArea';
import { import { MaterialDesign2SchemeStorage } from '../../../material-2-scheme';
MaterialDesign2SchemeSource,
MaterialDesign2SchemeStorage,
} from '../../../material-2-scheme';
import { SchemeContent } from '../../../models'; import { SchemeContent } from '../../../models';
import { useUpdateScheme } from '../../../stores/schemes'; import { useUpdateScheme } from '../../../stores/schemes';
import { mapToObject } from '../../../utls'; import { mapToObject } from '../../../utls';
@ -20,7 +16,6 @@ type M2SchemeBuilderProps = {
}; };
export function M2SchemeBuilder({ scheme, onBuildComplete }: M2SchemeBuilderProps) { export function M2SchemeBuilder({ scheme, onBuildComplete }: M2SchemeBuilderProps) {
const { showToast } = useNotification();
const { colorFn } = useColorFunction(); const { colorFn } = useColorFunction();
const updateScheme = useUpdateScheme(scheme.id); const updateScheme = useUpdateScheme(scheme.id);
const originalColors = useMemo(() => { const originalColors = useMemo(() => {
@ -40,67 +35,45 @@ export function M2SchemeBuilder({ scheme, onBuildComplete }: M2SchemeBuilderProp
.filter((c) => !includes(deleted, c)), .filter((c) => !includes(deleted, c)),
[originalColors, newColors, deleted], [originalColors, newColors, deleted],
); );
const colectSchemeSource = (formData: FormData): MaterialDesign2SchemeSource => {
const primaryColor = formData.get('primary') as string;
const secondaryColor = formData.get('secondary') as string;
const errorColor = formData.get('error') as string;
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;
}
return {
primary: isNil(primaryColor) || isEmpty(primaryColor) ? null : primaryColor,
secondary: isNil(secondaryColor) || isEmpty(secondaryColor) ? null : secondaryColor,
error: isNil(errorColor) || isEmpty(errorColor) ? null : errorColor,
custom_colors: customColors,
};
};
const [, handleDraftAction] = useActionState<Map<string, string>, FormData>(
(_state, formData) => {
const errMsg = new Map<string, string>();
const collectedSource = colectSchemeSource(formData);
updateScheme((prev) => {
prev.schemeStorage.source = collectedSource;
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>( const [errMsg, handleSubmitAction] = useActionState<Map<string, string>, FormData>(
(_state, formData) => { (_state, formData) => {
const errMsg = new Map<string, string>(); const errMsg = new Map<string, string>();
try { try {
const collected = colectSchemeSource(formData); const primaryColor = formData.get('primary') as string;
if (isNil(collected.primary) || isEmpty(collected.primary)) { if (isNil(primaryColor) || isEmpty(primaryColor)) {
errMsg.set('primary', 'Primary color is required'); errMsg.set('primary', 'Primary color is required');
} }
if (isNil(collected.secondary) || isEmpty(collected.secondary)) { const secondaryColor = formData.get('secondary') as string;
if (isNil(secondaryColor) || isEmpty(secondaryColor)) {
errMsg.set('secondary', 'Secondary color is required'); errMsg.set('secondary', 'Secondary color is required');
} }
if (isNil(collected.error) || isEmpty(collected.error)) { const errorColor = formData.get('error') as string;
if (isNil(errorColor) || isEmpty(errorColor)) {
errMsg.set('error', 'Error color is required'); errMsg.set('error', 'Error color is required');
} }
if (!isEmpty(errMsg)) return errMsg; 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_2_scheme( const generatedScheme = colorFn?.generate_material_design_2_scheme(
collected.primary, primaryColor,
collected.secondary, secondaryColor,
collected.error, errorColor,
collected.custom_colors, customColors,
); );
updateScheme((prev) => { updateScheme((prev) => {
prev.schemeStorage.source = collected; prev.schemeStorage.source = {
primary: primaryColor,
secondary: secondaryColor,
error: errorColor,
custom_colors: customColors,
};
prev.schemeStorage.scheme = merge(generatedScheme[0], { prev.schemeStorage.scheme = merge(generatedScheme[0], {
light: { custom_colors: mapToObject(generatedScheme[0].light.custom_colors) }, light: { custom_colors: mapToObject(generatedScheme[0].light.custom_colors) },
dark: { custom_colors: mapToObject(generatedScheme[0].dark.custom_colors) }, dark: { custom_colors: mapToObject(generatedScheme[0].dark.custom_colors) },
@ -195,13 +168,10 @@ export function M2SchemeBuilder({ scheme, onBuildComplete }: M2SchemeBuilderProp
onDelete={(index) => setDeleted((prev) => [...prev, index])} onDelete={(index) => setDeleted((prev) => [...prev, index])}
/> />
))} ))}
<div className={styles.button_row} style={{ gridColumn: '2 / span 2' }}> <div style={{ gridColumn: '2 / span 2' }}>
<button type="submit" className="primary"> <button type="submit" className="primary">
Build Scheme Build Scheme
</button> </button>
<button type="submit" className="secondary" formAction={handleDraftAction}>
Save Draft
</button>
</div> </div>
</form> </form>
</ScrollArea> </ScrollArea>

View File

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

View File

@ -2,14 +2,10 @@ import { includes, isEmpty, isEqual, isNil } from 'lodash-es';
import { useActionState, useCallback, useMemo, useState } from 'react'; import { useActionState, useCallback, useMemo, useState } from 'react';
import { useColorFunction } from '../../../ColorFunctionContext'; import { useColorFunction } from '../../../ColorFunctionContext';
import { FloatColorPicker } from '../../../components/FloatColorPicker'; import { FloatColorPicker } from '../../../components/FloatColorPicker';
import { NotificationType, useNotification } from '../../../components/Notifications';
import { ScrollArea } from '../../../components/ScrollArea'; import { ScrollArea } from '../../../components/ScrollArea';
import { Switch } from '../../../components/Switch'; import { Switch } from '../../../components/Switch';
import { VSegmentedControl } from '../../../components/VSegmentedControl'; import { VSegmentedControl } from '../../../components/VSegmentedControl';
import { import { MaterialDesign3DynamicSchemeStorage } from '../../../material-3-scheme';
MaterialDesign3DynamicSchemeSource,
MaterialDesign3DynamicSchemeStorage,
} from '../../../material-3-scheme';
import { Option, SchemeContent } from '../../../models'; import { Option, SchemeContent } from '../../../models';
import { useUpdateScheme } from '../../../stores/schemes'; import { useUpdateScheme } from '../../../stores/schemes';
import { mapToObject } from '../../../utls'; import { mapToObject } from '../../../utls';
@ -22,7 +18,6 @@ type M3DynamicSchemeBuilderProps = {
}; };
export function M3DynamicSchemeBuilder({ scheme, onBuildCompleted }: M3DynamicSchemeBuilderProps) { export function M3DynamicSchemeBuilder({ scheme, onBuildCompleted }: M3DynamicSchemeBuilderProps) {
const { showToast } = useNotification();
const { colorFn } = useColorFunction(); const { colorFn } = useColorFunction();
const updateScheme = useUpdateScheme(scheme.id); const updateScheme = useUpdateScheme(scheme.id);
const originalColors = useMemo(() => { const originalColors = useMemo(() => {
@ -54,69 +49,47 @@ export function M3DynamicSchemeBuilder({ scheme, onBuildCompleted }: M3DynamicSc
const [contrastLevel, setContrastLevel] = useState<number>( const [contrastLevel, setContrastLevel] = useState<number>(
() => scheme.schemeStorage.source?.contrastLevel ?? 1, () => scheme.schemeStorage.source?.contrastLevel ?? 1,
); );
const collectSchemeSource = (formData: FormData): MaterialDesign3DynamicSchemeSource => {
const sourceColor = formData.get('source') as string;
const dynamicVariant = Number(formData.get('variant'));
const contrast = Number(formData.get('contrast_level'));
const harmonizeCustoms = isEqual(formData.get('harmonize_customs'), 'true');
const errorColor = formData.get('error') as string;
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;
}
return {
source: isNil(sourceColor) || isEmpty(sourceColor) ? null : sourceColor,
error: isNil(errorColor) || isEmpty(errorColor) ? null : errorColor,
custom_colors: customColors,
variant: dynamicVariant,
contrastLevel: contrast,
harmonizeCustoms: harmonizeCustoms,
};
};
const [, handleDraftAction] = useActionState<Map<string, string>, FormData>(
(_state, formData) => {
const errMsg = new Map<string, string>();
const collectedSource = collectSchemeSource(formData);
updateScheme((prev) => {
prev.schemeStorage.source = collectedSource;
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>( const [errMsg, handleSubmitAction] = useActionState<Map<string, string>, FormData>(
(_state, formData) => { (_state, formData) => {
const errMsg = new Map<string, string>(); const errMsg = new Map<string, string>();
const collectedSource = collectSchemeSource(formData); const sourceColor = formData.get('source') as string;
if (isNil(collectedSource.source) || isEmpty(collectedSource.source)) { if (isNil(sourceColor) || isEmpty(sourceColor)) {
errMsg.set('source', 'Source color is required'); errMsg.set('source', 'Source color is required');
} }
if (!isEmpty(errMsg)) return errMsg; if (!isEmpty(errMsg)) return errMsg;
try { try {
const dynamicVariant = Number(formData.get('variant'));
const contrast = Number(formData.get('contrast_level'));
const harmonizeCustoms = isEqual(formData.get('harmonize_customs'), 'true');
const errorColor = formData.get('error') as string;
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 generate_scheme = colorFn.generate_material_design_3_dynamic_scheme( const generate_scheme = colorFn.generate_material_design_3_dynamic_scheme(
collectedSource.source, sourceColor,
collectedSource.error, isNil(errorColor) || isEmpty(errorColor) ? null : errorColor,
collectedSource.variant, dynamicVariant,
collectedSource.contrastLevel, contrastLevel,
collectedSource.harmonizeCustoms, harmonizeCustoms,
collectedSource.custom_colors, customColors,
); );
updateScheme((prev) => { updateScheme((prev) => {
prev.schemeStorage.source = collectedSource; prev.schemeStorage.source = {
source: sourceColor,
error: errorColor,
custom_colors: customColors,
variant: dynamicVariant,
contrastLevel: contrast,
harmonizeCustoms: harmonizeCustoms,
};
prev.schemeStorage.scheme = { prev.schemeStorage.scheme = {
white: generate_scheme[0].white, white: generate_scheme[0].white,
black: generate_scheme[0].black, black: generate_scheme[0].black,
@ -231,13 +204,10 @@ export function M3DynamicSchemeBuilder({ scheme, onBuildCompleted }: M3DynamicSc
onDelete={(index) => setDeleted((prev) => [...prev, index])} onDelete={(index) => setDeleted((prev) => [...prev, index])}
/> />
))} ))}
<div className={styles.button_row} style={{ gridColumn: '2 / span 2' }}> <div style={{ gridColumn: '2 / span 2' }}>
<button type="submit" className="primary"> <button type="submit" className="primary">
Build Scheme Build Scheme
</button> </button>
<button type="submit" className="secondary" formAction={handleDraftAction}>
Save Draft
</button>
</div> </div>
</form> </form>
</ScrollArea> </ScrollArea>

View File

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

View File

@ -2,13 +2,8 @@ import { includes, isEmpty, isNil } from 'lodash-es';
import { useActionState, useCallback, useMemo, useState } from 'react'; import { useActionState, useCallback, useMemo, useState } from 'react';
import { useColorFunction } from '../../../ColorFunctionContext'; import { useColorFunction } from '../../../ColorFunctionContext';
import { FloatColorPicker } from '../../../components/FloatColorPicker'; import { FloatColorPicker } from '../../../components/FloatColorPicker';
import { NotificationType, useNotification } from '../../../components/Notifications';
import { ScrollArea } from '../../../components/ScrollArea'; import { ScrollArea } from '../../../components/ScrollArea';
import { import { MaterialDesign3Scheme, MaterialDesign3SchemeStorage } from '../../../material-3-scheme';
MaterialDesign3Scheme,
MaterialDesign3SchemeSource,
MaterialDesign3SchemeStorage,
} from '../../../material-3-scheme';
import { SchemeContent } from '../../../models'; import { SchemeContent } from '../../../models';
import { useUpdateScheme } from '../../../stores/schemes'; import { useUpdateScheme } from '../../../stores/schemes';
import { mapToObject } from '../../../utls'; import { mapToObject } from '../../../utls';
@ -21,7 +16,6 @@ type M3SchemeBuilderProps = {
}; };
export function M3SchemeBuilder({ scheme, onBuildCompleted }: M3SchemeBuilderProps) { export function M3SchemeBuilder({ scheme, onBuildCompleted }: M3SchemeBuilderProps) {
const { showToast } = useNotification();
const { colorFn } = useColorFunction(); const { colorFn } = useColorFunction();
const updateScheme = useUpdateScheme(scheme.id); const updateScheme = useUpdateScheme(scheme.id);
const originalColors = useMemo(() => { const originalColors = useMemo(() => {
@ -42,62 +36,40 @@ export function M3SchemeBuilder({ scheme, onBuildCompleted }: M3SchemeBuilderPro
[originalColors, newColors, deleted], [originalColors, newColors, deleted],
); );
const collectSchemeSource = (formData: FormData): MaterialDesign3SchemeSource => {
const sourceColor = formData.get('source') as string;
const errorColor = formData.get('error') as string;
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;
}
return {
source: isNil(sourceColor) || isEmpty(sourceColor) ? null : sourceColor,
error: isNil(errorColor) || isEmpty(errorColor) ? null : errorColor,
custom_colors: customColors,
};
};
const [, handleDraftAction] = useActionState<Map<string, string>, FormData>(
(_state, formData) => {
const errMsg = new Map<string, string>();
const collectedSource = collectSchemeSource(formData);
updateScheme((prev) => {
prev.schemeStorage.source = collectedSource;
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>( const [errMsg, handleSubmitAction] = useActionState<Map<string, string>, FormData>(
(_state, formData) => { (_state, formData) => {
const errMsg = new Map<string, string>(); const errMsg = new Map<string, string>();
try { try {
const collectedSource = collectSchemeSource(formData); const sourceColor = formData.get('source') as string;
if (isNil(collectedSource.source) || isEmpty(collectedSource.source)) { if (isNil(sourceColor) || isEmpty(sourceColor)) {
errMsg.set('source', 'Source color is required'); errMsg.set('source', 'Source color is required');
} }
if (isNil(collectedSource.error) || isEmpty(collectedSource.error)) { const errorColor = formData.get('error') as string;
if (isNil(errorColor) || isEmpty(errorColor)) {
errMsg.set('error', 'Error color is required'); errMsg.set('error', 'Error color is required');
} }
if (!isEmpty(errMsg)) return errMsg; 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( const generatedScheme = colorFn?.generate_material_design_3_scheme(
collectedSource.source, sourceColor,
collectedSource.error, errorColor,
collectedSource.custom_colors, customColors,
); );
updateScheme((prev) => { updateScheme((prev) => {
prev.schemeStorage.source = collectedSource; prev.schemeStorage.source = {
source: sourceColor as string,
error: errorColor as string,
custom_colors: customColors,
};
prev.schemeStorage.scheme = { prev.schemeStorage.scheme = {
white: generatedScheme[0].white, white: generatedScheme[0].white,
black: generatedScheme[0].black, black: generatedScheme[0].black,
@ -183,13 +155,10 @@ export function M3SchemeBuilder({ scheme, onBuildCompleted }: M3SchemeBuilderPro
onDelete={(index) => setDeleted((prev) => [...prev, index])} onDelete={(index) => setDeleted((prev) => [...prev, index])}
/> />
))} ))}
<div className={styles.button_row} style={{ gridColumn: '2 / span 2' }}> <div style={{ gridColumn: '2 / span 2' }}>
<button type="submit" className="primary"> <button type="submit" className="primary">
Build Scheme Build Scheme
</button> </button>
<button type="submit" className="secondary" formAction={handleDraftAction}>
Save Draft
</button>
</div> </div>
</form> </form>
</ScrollArea> </ScrollArea>

View File

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

View File

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