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