Compare commits
6 Commits
ef3ef2b349
...
2638bbd99a
Author | SHA1 | Date | |
---|---|---|---|
|
2638bbd99a | ||
|
f284a7ef62 | ||
|
00d1e425c0 | ||
|
0a5d475655 | ||
|
a1f63cd724 | ||
|
5f3d58f0f5 |
|
@ -23,6 +23,12 @@
|
||||||
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;
|
||||||
|
|
|
@ -2,8 +2,12 @@ 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 { MaterialDesign2SchemeStorage } from '../../../material-2-scheme';
|
import {
|
||||||
|
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';
|
||||||
|
@ -16,6 +20,7 @@ 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(() => {
|
||||||
|
@ -35,45 +40,67 @@ 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 primaryColor = formData.get('primary') as string;
|
const collected = colectSchemeSource(formData);
|
||||||
if (isNil(primaryColor) || isEmpty(primaryColor)) {
|
if (isNil(collected.primary) || isEmpty(collected.primary)) {
|
||||||
errMsg.set('primary', 'Primary color is required');
|
errMsg.set('primary', 'Primary color is required');
|
||||||
}
|
}
|
||||||
const secondaryColor = formData.get('secondary') as string;
|
if (isNil(collected.secondary) || isEmpty(collected.secondary)) {
|
||||||
if (isNil(secondaryColor) || isEmpty(secondaryColor)) {
|
|
||||||
errMsg.set('secondary', 'Secondary color is required');
|
errMsg.set('secondary', 'Secondary color is required');
|
||||||
}
|
}
|
||||||
const errorColor = formData.get('error') as string;
|
if (isNil(collected.error) || isEmpty(collected.error)) {
|
||||||
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(
|
||||||
primaryColor,
|
collected.primary,
|
||||||
secondaryColor,
|
collected.secondary,
|
||||||
errorColor,
|
collected.error,
|
||||||
customColors,
|
collected.custom_colors,
|
||||||
);
|
);
|
||||||
updateScheme((prev) => {
|
updateScheme((prev) => {
|
||||||
prev.schemeStorage.source = {
|
prev.schemeStorage.source = collected;
|
||||||
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) },
|
||||||
|
@ -168,10 +195,13 @@ export function M2SchemeBuilder({ scheme, onBuildComplete }: M2SchemeBuilderProp
|
||||||
onDelete={(index) => setDeleted((prev) => [...prev, index])}
|
onDelete={(index) => setDeleted((prev) => [...prev, index])}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<div style={{ gridColumn: '2 / span 2' }}>
|
<div className={styles.button_row} 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>
|
||||||
|
|
|
@ -29,6 +29,12 @@
|
||||||
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;
|
||||||
|
|
|
@ -2,10 +2,14 @@ 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 { MaterialDesign3DynamicSchemeStorage } from '../../../material-3-scheme';
|
import {
|
||||||
|
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';
|
||||||
|
@ -18,6 +22,7 @@ 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(() => {
|
||||||
|
@ -49,47 +54,69 @@ 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 sourceColor = formData.get('source') as string;
|
const collectedSource = collectSchemeSource(formData);
|
||||||
if (isNil(sourceColor) || isEmpty(sourceColor)) {
|
if (isNil(collectedSource.source) || isEmpty(collectedSource.source)) {
|
||||||
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(
|
||||||
sourceColor,
|
collectedSource.source,
|
||||||
isNil(errorColor) || isEmpty(errorColor) ? null : errorColor,
|
collectedSource.error,
|
||||||
dynamicVariant,
|
collectedSource.variant,
|
||||||
contrastLevel,
|
collectedSource.contrastLevel,
|
||||||
harmonizeCustoms,
|
collectedSource.harmonizeCustoms,
|
||||||
customColors,
|
collectedSource.custom_colors,
|
||||||
);
|
);
|
||||||
updateScheme((prev) => {
|
updateScheme((prev) => {
|
||||||
prev.schemeStorage.source = {
|
prev.schemeStorage.source = collectedSource;
|
||||||
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,
|
||||||
|
@ -204,10 +231,13 @@ export function M3DynamicSchemeBuilder({ scheme, onBuildCompleted }: M3DynamicSc
|
||||||
onDelete={(index) => setDeleted((prev) => [...prev, index])}
|
onDelete={(index) => setDeleted((prev) => [...prev, index])}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<div style={{ gridColumn: '2 / span 2' }}>
|
<div className={styles.button_row} 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>
|
||||||
|
|
|
@ -23,6 +23,12 @@
|
||||||
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;
|
||||||
|
|
|
@ -2,8 +2,13 @@ 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 { MaterialDesign3Scheme, MaterialDesign3SchemeStorage } from '../../../material-3-scheme';
|
import {
|
||||||
|
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';
|
||||||
|
@ -16,6 +21,7 @@ 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(() => {
|
||||||
|
@ -36,40 +42,62 @@ 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 sourceColor = formData.get('source') as string;
|
const collectedSource = collectSchemeSource(formData);
|
||||||
if (isNil(sourceColor) || isEmpty(sourceColor)) {
|
if (isNil(collectedSource.source) || isEmpty(collectedSource.source)) {
|
||||||
errMsg.set('source', 'Source color is required');
|
errMsg.set('source', 'Source color is required');
|
||||||
}
|
}
|
||||||
const errorColor = formData.get('error') as string;
|
if (isNil(collectedSource.error) || isEmpty(collectedSource.error)) {
|
||||||
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(
|
||||||
sourceColor,
|
collectedSource.source,
|
||||||
errorColor,
|
collectedSource.error,
|
||||||
customColors,
|
collectedSource.custom_colors,
|
||||||
);
|
);
|
||||||
updateScheme((prev) => {
|
updateScheme((prev) => {
|
||||||
prev.schemeStorage.source = {
|
prev.schemeStorage.source = collectedSource;
|
||||||
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,
|
||||||
|
@ -155,10 +183,13 @@ export function M3SchemeBuilder({ scheme, onBuildCompleted }: M3SchemeBuilderPro
|
||||||
onDelete={(index) => setDeleted((prev) => [...prev, index])}
|
onDelete={(index) => setDeleted((prev) => [...prev, index])}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<div style={{ gridColumn: '2 / span 2' }}>
|
<div className={styles.button_row} 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>
|
||||||
|
|
|
@ -30,6 +30,12 @@
|
||||||
.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;
|
||||||
|
|
|
@ -2,6 +2,7 @@ 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';
|
||||||
|
@ -10,6 +11,7 @@ import {
|
||||||
QSwatchEntry,
|
QSwatchEntry,
|
||||||
QSwatchSchemeSetting,
|
QSwatchSchemeSetting,
|
||||||
SwatchScheme,
|
SwatchScheme,
|
||||||
|
SwatchSchemeSource,
|
||||||
SwatchSchemeStorage,
|
SwatchSchemeStorage,
|
||||||
} from '../../../swatch_scheme';
|
} from '../../../swatch_scheme';
|
||||||
import { mapToObject } from '../../../utls';
|
import { mapToObject } from '../../../utls';
|
||||||
|
@ -22,6 +24,7 @@ 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(() => {
|
||||||
|
@ -64,63 +67,95 @@ 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 swatchAmount = Number(formData.get('amount'));
|
const collected = collectSchemeSource(formData);
|
||||||
if (isNaN(swatchAmount) || swatchAmount <= 0) {
|
if (isNaN(collected.setting.amount) || collected.setting.amount <= 0) {
|
||||||
errMsg.set('amount', 'MUST be a positive number');
|
errMsg.set('amount', 'MUST be a positive number');
|
||||||
}
|
}
|
||||||
if (swatchAmount > 30) {
|
if (collected.setting.amount > 30) {
|
||||||
errMsg.set('amount', 'MUST be less than 30');
|
errMsg.set('amount', 'MUST be less than 30');
|
||||||
}
|
}
|
||||||
|
|
||||||
const minLightness = Number(formData.get('min_lightness'));
|
if (
|
||||||
if (isNaN(minLightness) || minLightness < 0 || minLightness > 100) {
|
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');
|
errMsg.set('min', 'MUST be a number between 0 and 100');
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxLightness = Number(formData.get('max_lightness'));
|
if (
|
||||||
if (isNaN(maxLightness) || maxLightness < 0 || maxLightness > 100) {
|
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');
|
errMsg.set('max', 'MUST be a number between 0 and 100');
|
||||||
}
|
}
|
||||||
|
|
||||||
const includePrimary = isEqual(formData.get('include_primary'), 'true');
|
if (isEmpty(collected.colors)) {
|
||||||
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(entries, swatchSetting);
|
const generatedScheme = colorFn?.generate_swatch_scheme(
|
||||||
console.debug('[generated scheme]', generatedScheme);
|
collected.colors,
|
||||||
|
collected.setting,
|
||||||
|
);
|
||||||
updateScheme((prev) => {
|
updateScheme((prev) => {
|
||||||
prev.schemeStorage.source = {
|
prev.schemeStorage.source = collected;
|
||||||
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];
|
||||||
|
@ -237,10 +272,13 @@ 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 style={{ gridColumn: '2 / span 2' }}>
|
<div className={styles.button_row} 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>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user