为Swatch Builder增加保存草稿的功能。
This commit is contained in:
		@@ -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>
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user