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