修正大部分的编译错误。

This commit is contained in:
徐涛 2025-02-10 14:28:34 +08:00
parent 2144cd548a
commit 88e3d1f928
44 changed files with 429 additions and 381 deletions

View File

@ -1,6 +1,6 @@
import { Icon, IconProps } from '@iconify/react/dist/iconify.js';
import cx from 'clsx';
import { MouseEventHandler, useCallback } from 'react';
import { MouseEvent, MouseEventHandler, useCallback } from 'react';
import styles from './ActionIcon.module.css';
type ActionIconProps = {

View File

@ -87,7 +87,7 @@ export function ColorComponentInput({ color, onChange }: ColorComponentInputProp
}
};
const updateH = (evt: ChangeEvent<HTMLInputElement>) => {
const value = parseInt(evt.target.value, 10);
let value = parseInt(evt.target.value, 10);
if (value > 360) {
value -= 360;
}

View File

@ -28,7 +28,7 @@ export function ColorRangePicker({
}: ColorRangePickerProps) {
const [pickerValue, setPickerValue] = useState(value);
const handlePickerChange = (evt: ChangeEvent<HTMLInputElement>) => {
const value = evt.target.value as number;
const value = Number(evt.target.value);
setPickerValue(valueProcess(value));
onChange?.(valueProcess(value));
};

View File

@ -4,7 +4,7 @@ import { useRef, useState } from 'react';
import styles from './EditableTitle.module.css';
type EditableTitleProps = {
title: string;
title?: string;
onChange?: (newTitle: string) => void;
};

View File

@ -6,7 +6,7 @@ import styles from './FloatColorPicker.module.css';
type FloatColorPickerProps = {
name?: string;
color?: string;
color?: string | null;
onPick?: (color: string | null | undefined) => void;
};

View File

@ -52,6 +52,7 @@ export function HSegmentedControl({
<div
key={`${index}_${value}`}
className={cx(styles.option, isEqual(selected, value) && styles.selected)}
//@ts-expect-error TS2322
ref={(el) => (optionsRef.current[index] = el!)}
onClick={() => handleSelectAction(value, index)}>
{label}

View File

@ -26,7 +26,7 @@ export function LabeledPicker({
}: LabeledPickerProps) {
const [pickerValue, setPickerValue] = useState(value ?? min);
const handlePickerChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const value = event.target.value as number;
const value = Number(event.target.value);
setPickerValue(value);
onChange?.(value);
};

View File

@ -118,7 +118,7 @@ type ToastProps = {
icon?: string;
duration?: ToastDuration;
ref: RefObject<HTMLDivElement>;
closeAction: () => void;
closeAction: (tid?: string) => void;
};
const Toast = ({
kind,
@ -157,7 +157,7 @@ export function useNotification() {
type NotificationElement = {
id: string;
element: ReactNode;
ref: RefObject<ReactNode>;
ref: RefObject<ReactNode | HTMLDivElement>;
};
type NotificationsProps = {
defaultDuration?: number;
@ -184,7 +184,7 @@ export function Notifications({
duration?: number,
) => {
const id = v4();
const ref = createRef(null);
const ref = createRef<ReactNode | HTMLDivElement>();
const newNotify = (
<Notification
kind={kind}
@ -207,14 +207,9 @@ export function Notifications({
setToasts((prev) => filter(prev, (n) => !isEqual(n.id, id)));
}, []);
const showToast = useCallback(
(
kind: NotificationType,
message?: string,
icon?: IconifyIconProps['icon'],
duration?: ToastDuration,
) => {
(kind: NotificationType, message?: string, icon?: string, duration?: ToastDuration) => {
const id = v4();
const ref = createRef(null);
const ref = createRef<HTMLDivElement>();
const newToast = (
<Toast
kind={kind}

View File

@ -1,5 +1,5 @@
import { clamp } from 'lodash-es';
import { RefObject, useEffect, useRef, useState } from 'react';
import { RefObject, useEffect, useRef, useState, WheelEvent } from 'react';
import styles from './ScrollArea.module.css';
type ScrollBarProps = {
@ -148,10 +148,10 @@ export function ScrollArea({
enableY = false,
normalizedScroll = false,
}: ScrollAreaProps) {
const scrollContainerRef = useRef<HTMLDivElement>(null);
const scrollContainerRef = useRef<HTMLDivElement | null>(null);
const [xScrollNeeded, setXScrollNeeded] = useState(false);
const [yScrollNeeded, setYScrollNeeded] = useState(false);
const handleWheel = (evt: WheelEvent) => {
const handleWheel = (evt: WheelEvent<HTMLDivElement>) => {
const container = scrollContainerRef?.current;
if (enableY && container) {
const delta = evt.deltaY;
@ -177,7 +177,7 @@ export function ScrollArea({
return (
<div className={styles.scroll_area}>
<div className={styles.content} ref={scrollContainerRef} onWheel={handleWheel}>
<div className={styles.content} ref={scrollContainerRef} onWheel={(e) => handleWheel(e)}>
{children}
</div>
{enableY && yScrollNeeded && <VerticalScrollBar containerRef={scrollContainerRef} />}

View File

@ -18,7 +18,7 @@ const positionMap = {
export function Tooltip({ content, position = 'top', children }: TooltipProps) {
const [show, setShow] = useState(false);
const contentRef = useRef<HTMLDivElement>();
const contentRef = useRef<HTMLDivElement | null>(null);
return (
<div

View File

@ -52,6 +52,7 @@ export function VSegmentedControl({
<div
key={`${index}_${value}`}
className={cx(styles.option, isEqual(selected, value) && styles.selected)}
//@ts-expect-error TS2322
ref={(el) => (optionsRef.current[index] = el!)}
onClick={() => handleSelectAction(value, index)}>
{label}

View File

@ -6,7 +6,7 @@ import { NotificationType, useNotification } from '../components/Notifications';
export function useCopy() {
const { showToast } = useNotification();
const [cpState, copyToClipboard] = useCopyToClipboard();
const copyAction = useCallback((content: string) => {
const copyAction = useCallback((content?: string | null) => {
if (isNil(content) || isEmpty(content)) return;
copyToClipboard(content);
}, []);

View File

@ -4,12 +4,12 @@ import { MaterialDesign3SchemeStorage } from './material-3-scheme';
import { QSchemeStorage } from './q-scheme';
import { SwatchSchemeStorage } from './swatch_scheme';
export type Option =
export type Option<T = string | number | null> =
| {
label: string;
value: string | number | null;
value: T;
}
| Record<'label' | 'value', string | number | null>;
| Record<'label' | 'value', T>;
export type HarmonyColor = {
color: string;

View File

@ -23,18 +23,18 @@ export function Darkens({ color, darkens, mix, step, maximum, copyMode }: Darken
switch (mix) {
case 'progressive':
for (let i = 1; i <= darkens; i++) {
const darkenColor = colorFn.darken(last(darkenColors), step);
const darkenColor = colorFn.darken(last(darkenColors) ?? '', step ?? 0);
darkenColors.push(darkenColor);
}
break;
case 'linear':
for (let i = 1; i <= darkens; i++) {
const darkenColor = colorFn.darken(color, step * i);
const darkenColor = colorFn.darken(color, (step ?? 0) * i);
darkenColors.push(darkenColor);
}
break;
case 'average': {
const interval = maximum / darkens / 100;
const interval = (maximum ?? 0) / darkens / 100;
for (let i = 1; i <= darkens; i++) {
const darkenColor = colorFn.darken(color, interval * i);
darkenColors.push(darkenColor);

View File

@ -23,18 +23,18 @@ export function Lightens({ color, lightens, mix, step, maximum, copyMode }: Ligh
switch (mix) {
case 'progressive':
for (let i = 1; i <= lightens; i++) {
const lightenColor = colorFn.lighten(last(lightenColors), step);
const lightenColor = colorFn.lighten(last(lightenColors) ?? '', step ?? 0);
lightenColors.push(lightenColor);
}
break;
case 'linear':
for (let i = 1; i <= lightens; i++) {
const lightenColor = colorFn.lighten(color, step * i);
const lightenColor = colorFn.lighten(color, (step ?? 0) * i);
lightenColors.push(lightenColor);
}
break;
case 'average': {
const interval = maximum / lightens / 100;
const interval = (maximum ?? 0) / lightens / 100;
for (let i = 1; i <= lightens; i++) {
const lightenColor = colorFn.lighten(color, interval * i);
lightenColors.push(lightenColor);

View File

@ -1,6 +1,8 @@
import { isEqual, isNil } from 'lodash-es';
import { useState } from 'react';
import { Tab } from '../../components/Tab';
import { MaterialDesign2SchemeStorage } from '../../material-2-scheme';
import { SchemeContent } from '../../models';
import { SchemeExport } from './Export';
import { M2SchemeBuilder } from './m2-scheme/Builder';
import { M2SchemePreview } from './m2-scheme/Preview';
@ -11,18 +13,18 @@ const tabOptions = [
{ title: 'Exports', id: 'export' },
];
type M3SchemeProps = {
scheme: SchemeContent<MaterialDesign3SchemeStorage>;
type M2SchemeProps = {
scheme: SchemeContent<MaterialDesign2SchemeStorage>;
};
export function M2Scheme({ scheme }: M3SchemeProps) {
export function M2Scheme({ scheme }: M2SchemeProps) {
const [activeTab, setActiveTab] = useState<(typeof tabOptions)[number]['id']>(() =>
isNil(scheme.schemeStorage.scheme) ? 'builder' : 'overview',
);
return (
<>
<Tab tabs={tabOptions} activeTab={activeTab} onActive={setActiveTab} />
<Tab tabs={tabOptions} activeTab={activeTab} onActive={(v) => setActiveTab(v as string)} />
{isEqual(activeTab, 'overview') && <M2SchemePreview scheme={scheme} />}
{isEqual(activeTab, 'builder') && (
<M2SchemeBuilder scheme={scheme} onBuildComplete={() => setActiveTab('overview')} />

View File

@ -2,6 +2,7 @@ import { isEqual, isNil } from 'lodash-es';
import { useState } from 'react';
import { Tab } from '../../components/Tab';
import { MaterialDesign3SchemeStorage } from '../../material-3-scheme';
import { SchemeContent } from '../../models';
import { SchemeExport } from './Export';
import { M3SchemeBuilder } from './m3-scheme/Builder';
import { M3SchemePreview } from './m3-scheme/Preview';
@ -23,7 +24,7 @@ export function M3Scheme({ scheme }: M3SchemeProps) {
return (
<>
<Tab tabs={tabOptions} activeTab={activeTab} onActive={setActiveTab} />
<Tab tabs={tabOptions} activeTab={activeTab} onActive={(v) => setActiveTab(v as string)} />
{isEqual(activeTab, 'overview') && <M3SchemePreview scheme={scheme} />}
{isEqual(activeTab, 'builder') && (
<M3SchemeBuilder scheme={scheme} onBuildCompleted={() => setActiveTab('overview')} />

View File

@ -24,7 +24,7 @@ export function QScheme({ scheme }: QSchemeProps) {
return (
<>
<Tab tabs={tabOptions} activeTab={activeTab} onActive={setActiveTab} />
<Tab tabs={tabOptions} activeTab={activeTab} onActive={(v) => setActiveTab(v as string)} />
{isEqual(activeTab, 'overview') && <QSchemePreview scheme={scheme} />}
{isEqual(activeTab, 'builder') && (
<QSchemeBuilder scheme={scheme} onBuildCompleted={() => setActiveTab('overview')} />

View File

@ -24,7 +24,7 @@ export function SwatchScheme({ scheme }: SwatchSchemeProps) {
return (
<>
<Tab tabs={tabOptions} activeTab={activeTab} onActive={setActiveTab} />
<Tab tabs={tabOptions} activeTab={activeTab} onActive={(v) => setActiveTab(v as string)} />
{isEqual(activeTab, 'overview') && <SwatchSchemePreview scheme={scheme} />}
{isEqual(activeTab, 'builder') && (
<SwatchSchemeBuilder scheme={scheme} onBuildCompleted={() => setActiveTab('overview')} />

View File

@ -36,60 +36,63 @@ export function M2SchemeBuilder({ scheme, onBuildComplete }: M2SchemeBuilderProp
[originalColors, newColors, deleted],
);
const [errMsg, handleSubmitAction] = useActionState((state, formData) => {
const errMsg = new Map<string, string>();
try {
const primaryColor = formData.get('primary');
if (isNil(primaryColor) || isEmpty(primaryColor)) {
errMsg.set('primary', 'Primary color is required');
}
const secondaryColor = formData.get('secondary');
if (isNil(secondaryColor) || isEmpty(secondaryColor)) {
errMsg.set('secondary', 'Secondary color is required');
}
const errorColor = formData.get('error');
if (isNil(errorColor) || isEmpty(errorColor)) {
errMsg.set('error', 'Error color is required');
}
if (!isEmpty(errMsg)) return errMsg;
const [errMsg, handleSubmitAction] = useActionState<Map<string, string>, FormData>(
(_state, formData) => {
const errMsg = new Map<string, string>();
try {
const primaryColor = formData.get('primary') as string;
if (isNil(primaryColor) || isEmpty(primaryColor)) {
errMsg.set('primary', 'Primary color is required');
}
const secondaryColor = formData.get('secondary') as string;
if (isNil(secondaryColor) || isEmpty(secondaryColor)) {
errMsg.set('secondary', 'Secondary color is required');
}
const errorColor = formData.get('error') as string;
if (isNil(errorColor) || isEmpty(errorColor)) {
errMsg.set('error', 'Error color is required');
}
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(
primaryColor,
secondaryColor,
errorColor,
customColors,
);
updateScheme((prev) => {
prev.schemeStorage.source = {
primary: primaryColor,
secondary: secondaryColor,
error: errorColor,
custom_colors: customColors,
};
prev.schemeStorage.scheme = merge(generatedScheme[0], {
light: { custom_colors: mapToObject(generatedScheme[0].light.custom_colors) },
dark: { custom_colors: mapToObject(generatedScheme[0].dark.custom_colors) },
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(
primaryColor,
secondaryColor,
errorColor,
customColors,
);
updateScheme((prev) => {
prev.schemeStorage.source = {
primary: primaryColor,
secondary: secondaryColor,
error: errorColor,
custom_colors: customColors,
};
prev.schemeStorage.scheme = merge(generatedScheme[0], {
light: { custom_colors: mapToObject(generatedScheme[0].light.custom_colors) },
dark: { custom_colors: mapToObject(generatedScheme[0].dark.custom_colors) },
});
prev.schemeStorage.cssVariables = generatedScheme[1];
prev.schemeStorage.scssVariables = generatedScheme[2];
prev.schemeStorage.jsVariables = generatedScheme[3];
return prev;
});
prev.schemeStorage.cssVariables = generatedScheme[1];
prev.schemeStorage.scssVariables = generatedScheme[2];
prev.schemeStorage.jsVariables = generatedScheme[3];
return prev;
});
onBuildComplete?.();
} catch (e) {
console.error('[generate m2 scheme]', e);
}
onBuildComplete?.();
} catch (e) {
console.error('[generate m2 scheme]', e);
}
return errMsg;
}, new Map<string, string>());
return errMsg;
},
new Map<string, string>(),
);
return (
<ScrollArea enableY>

View File

@ -86,8 +86,8 @@ export function M2SchemePreview({ scheme }: M2SchemePreviewProps) {
return (
<ScrollArea enableY>
<div className={styles.preview_layout}>
<PreviewBlock title="Light" baseline={scheme.schemeStorage.scheme?.light} />
<PreviewBlock title="Dark" baseline={scheme.schemeStorage.scheme?.dark} />
<PreviewBlock title="Light" baseline={scheme.schemeStorage.scheme!.light} />
<PreviewBlock title="Dark" baseline={scheme.schemeStorage.scheme!.dark} />
</div>
</ScrollArea>
);

View File

@ -36,64 +36,67 @@ export function M3SchemeBuilder({ scheme, onBuildCompleted }: M3SchemeBuilderPro
[originalColors, newColors, deleted],
);
const [errMsg, handleSubmitAction] = useActionState((state, formData) => {
const errMsg = new Map<string, string>();
const [errMsg, handleSubmitAction] = useActionState<Map<string, string>, FormData>(
(_state, formData) => {
const errMsg = new Map<string, string>();
try {
const sourceColor = formData.get('source');
if (isNil(sourceColor) || isEmpty(sourceColor)) {
errMsg.set('source', 'Source color is required');
}
const errorColor = formData.get('error');
if (isNil(errorColor) || isEmpty(errorColor)) {
errMsg.set('error', 'Error color is required');
}
if (!isEmpty(errMsg)) return errMsg;
try {
const sourceColor = formData.get('source') as string;
if (isNil(sourceColor) || isEmpty(sourceColor)) {
errMsg.set('source', 'Source color is required');
}
const errorColor = formData.get('error') as string;
if (isNil(errorColor) || isEmpty(errorColor)) {
errMsg.set('error', 'Error color is required');
}
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 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(
sourceColor,
errorColor,
customColors,
);
updateScheme((prev) => {
prev.schemeStorage.source = {
source: sourceColor as string,
error: errorColor as string,
custom_colors: customColors,
};
prev.schemeStorage.scheme = {
white: generatedScheme[0].white,
black: generatedScheme[0].black,
light_baseline: {
...generatedScheme[0].light_baseline,
customs: mapToObject(generatedScheme[0].light_baseline.customs),
},
dark_baseline: {
...generatedScheme[0].dark_baseline,
customs: mapToObject(generatedScheme[0].dark_baseline.customs),
},
} as MaterialDesign3Scheme;
prev.schemeStorage.cssVariables = generatedScheme[1];
prev.schemeStorage.scssVariables = generatedScheme[2];
prev.schemeStorage.jsVariables = generatedScheme[3];
return prev;
});
onBuildCompleted?.();
} catch (e) {
console.error('[generate m3 scheme]', e);
}
const generatedScheme = colorFn?.generate_material_design_3_scheme(
sourceColor,
errorColor,
customColors,
);
updateScheme((prev) => {
prev.schemeStorage.source = {
source: sourceColor as string,
error: errorColor as string,
custom_colors: customColors,
};
prev.schemeStorage.scheme = {
white: generatedScheme[0].white,
black: generatedScheme[0].black,
light_baseline: {
...generatedScheme[0].light_baseline,
customs: mapToObject(generatedScheme[0].light_baseline.customs),
},
dark_baseline: {
...generatedScheme[0].dark_baseline,
customs: mapToObject(generatedScheme[0].dark_baseline.customs),
},
} as MaterialDesign3Scheme;
prev.schemeStorage.cssVariables = generatedScheme[1];
prev.schemeStorage.scssVariables = generatedScheme[2];
prev.schemeStorage.jsVariables = generatedScheme[3];
return prev;
});
onBuildCompleted?.();
} catch (e) {
console.error('[generate m3 scheme]', e);
}
return errMsg;
}, new Map<string, string>());
return errMsg;
},
new Map<string, string>(),
);
return (
<ScrollArea enableY>

View File

@ -264,8 +264,8 @@ export function M3SchemePreview({ scheme }: M3SchemePreviewProps) {
return (
<ScrollArea enableY>
<div className={styles.preview_layout}>
<PreviewBlock title="Light Scheme" baseline={scheme.schemeStorage.scheme?.light_baseline} />
<PreviewBlock title="Dark Scheme" baseline={scheme.schemeStorage.scheme?.dark_baseline} />
<PreviewBlock title="Light Scheme" baseline={scheme.schemeStorage.scheme!.light_baseline} />
<PreviewBlock title="Dark Scheme" baseline={scheme.schemeStorage.scheme!.dark_baseline} />
</div>
</ScrollArea>
);

View File

@ -82,102 +82,105 @@ export function QSchemeBuilder({ scheme, onBuildCompleted }: QSchemeBuilderProps
return [];
}, []);
const [errMsg, handleSubmitAction] = useActionState((state, formData) => {
const errMsg = new Map<string, string>();
const requiredFields = [
'primary',
'danger',
'success',
'warn',
'info',
'foreground',
'background',
];
for (const field of requiredFields) {
if (!formData.get(field)) {
errMsg.set(field, 'This color is required for scheme generating.');
const [errMsg, handleSubmitAction] = useActionState<Map<string, string>, FormData>(
(_state, formData) => {
const errMsg = new Map<string, string>();
const requiredFields = [
'primary',
'danger',
'success',
'warn',
'info',
'foreground',
'background',
];
for (const field of requiredFields) {
if (!formData.get(field)) {
errMsg.set(field, 'This color is required for scheme generating.');
}
}
}
if (!isEmpty(errMsg)) return errMsg;
try {
const schemeSetting = new SchemeSetting(
new ColorShifting(
Number(formData.get('hover_chroma')) / 100,
Number(formData.get('hover_lightness')) / 100,
),
new ColorShifting(
Number(formData.get('active_chroma')) / 100,
Number(formData.get('active_lightness')) / 100,
),
new ColorShifting(
Number(formData.get('focus_chroma')) / 100,
Number(formData.get('focus_lightness')) / 100,
),
new ColorShifting(
Number(formData.get('disabled_chroma')) / 100,
Number(formData.get('disabled_lightness')) / 100,
),
new ColorShifting(
Number(formData.get('dark_chroma')) / 100,
Number(formData.get('dark_lightness')) / 100,
),
Number(formData.get('expanding')) as ColorExpand,
Number(formData.get('wacg')) as WACGSetting,
);
const dumpedSetting = schemeSetting.toJsValue() as QSchemeSetting;
if (!isEmpty(errMsg)) return errMsg;
try {
const schemeSetting = new SchemeSetting(
new ColorShifting(
Number(formData.get('hover_chroma')) / 100,
Number(formData.get('hover_lightness')) / 100,
),
new ColorShifting(
Number(formData.get('active_chroma')) / 100,
Number(formData.get('active_lightness')) / 100,
),
new ColorShifting(
Number(formData.get('focus_chroma')) / 100,
Number(formData.get('focus_lightness')) / 100,
),
new ColorShifting(
Number(formData.get('disabled_chroma')) / 100,
Number(formData.get('disabled_lightness')) / 100,
),
new ColorShifting(
Number(formData.get('dark_chroma')) / 100,
Number(formData.get('dark_lightness')) / 100,
),
Number(formData.get('expanding')) as ColorExpand,
Number(formData.get('wacg')) as WACGSetting,
);
const dumpedSetting = schemeSetting.toJsValue() as QSchemeSetting;
const source: QSchemeSource = {
primary: defaultEmptyFormData(formData, 'primary', null),
secondary: defaultEmptyFormData(formData, 'secondary', undefined),
tertiary: defaultEmptyFormData(formData, 'tertiary', undefined),
accent: defaultEmptyFormData(formData, 'accent', undefined),
danger: defaultEmptyFormData(formData, 'danger', null),
success: defaultEmptyFormData(formData, 'success', null),
warning: defaultEmptyFormData(formData, 'warn', null),
info: defaultEmptyFormData(formData, 'info', null),
foreground: defaultEmptyFormData(formData, 'foreground', null),
background: defaultEmptyFormData(formData, 'background', null),
setting: dumpedSetting,
};
const generatedScheme = every([source.secondary, source.tertiary, source.accent], isNil)
? colorFn?.generate_q_scheme_automatically(
source.primary,
source.danger,
source.success,
source.warning,
source.info,
source.foreground,
source.background,
schemeSetting,
)
: colorFn?.generate_q_scheme_manually(
source.primary,
source.secondary ?? undefined,
source.tertiary ?? undefined,
source.accent ?? undefined,
source.danger,
source.success,
source.warning,
source.info,
source.foreground,
source.background,
schemeSetting,
);
updateScheme((prev) => {
prev.schemeStorage.source = source;
prev.schemeStorage.scheme = generatedScheme[0];
prev.schemeStorage.cssVariables = generatedScheme[1];
prev.schemeStorage.scssVariables = generatedScheme[2];
prev.schemeStorage.jsVariables = generatedScheme[3];
return prev;
});
onBuildCompleted?.();
} catch (e) {
console.error('[build q scheme]', e);
}
const source: QSchemeSource = {
primary: defaultEmptyFormData(formData, 'primary', null),
secondary: defaultEmptyFormData(formData, 'secondary', null),
tertiary: defaultEmptyFormData(formData, 'tertiary', null),
accent: defaultEmptyFormData(formData, 'accent', null),
danger: defaultEmptyFormData(formData, 'danger', null),
success: defaultEmptyFormData(formData, 'success', null),
warning: defaultEmptyFormData(formData, 'warn', null),
info: defaultEmptyFormData(formData, 'info', null),
foreground: defaultEmptyFormData(formData, 'foreground', null),
background: defaultEmptyFormData(formData, 'background', null),
setting: dumpedSetting,
};
const generatedScheme = every([source.secondary, source.tertiary, source.accent], isNil)
? colorFn?.generate_q_scheme_automatically(
source.primary ?? '',
source.danger ?? '',
source.success ?? '',
source.warning ?? '',
source.info ?? '',
source.foreground ?? '',
source.background ?? '',
schemeSetting,
)
: colorFn?.generate_q_scheme_manually(
source.primary ?? '',
source.secondary ?? undefined,
source.tertiary ?? undefined,
source.accent ?? undefined,
source.danger ?? '',
source.success ?? '',
source.warning ?? '',
source.info ?? '',
source.foreground ?? '',
source.background ?? '',
schemeSetting,
);
updateScheme((prev) => {
prev.schemeStorage.source = source;
prev.schemeStorage.scheme = generatedScheme[0];
prev.schemeStorage.cssVariables = generatedScheme[1];
prev.schemeStorage.scssVariables = generatedScheme[2];
prev.schemeStorage.jsVariables = generatedScheme[3];
return prev;
});
onBuildCompleted?.();
} catch (e) {
console.error('[build q scheme]', e);
}
return errMsg;
}, new Map<string, string>());
return errMsg;
},
new Map<string, string>(),
);
return (
<ScrollArea enableY>

View File

@ -10,7 +10,12 @@ import { ScrollArea } from '../../../components/ScrollArea';
import { Switch } from '../../../components/Switch';
import { SchemeContent } from '../../../models';
import { useUpdateScheme } from '../../../stores/schemes';
import { QSwatchEntry, QSwatchSchemeSetting, SwatchSchemeStorage } from '../../../swatch_scheme';
import {
QSwatchEntry,
QSwatchSchemeSetting,
SwatchScheme,
SwatchSchemeStorage,
} from '../../../swatch_scheme';
import { mapToObject } from '../../../utls';
import { ColorEntry, IdenticalColorEntry } from '../ColorEntry';
import styles from './Builder.module.css';
@ -64,75 +69,78 @@ export function SwatchSchemeBuilder({ scheme, onBuildCompleted }: SwatchSchemeBu
return null;
}, [scheme.schemeStorage.source]);
const [errMsg, handleSubmitAction] = useActionState((state, formData) => {
const 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) {
errMsg.set('amount', 'MUST be a positive number');
}
if (swatchAmount > 30) {
errMsg.set('amount', 'MUST be less than 30');
try {
const swatchAmount = Number(formData.get('amount'));
if (isNaN(swatchAmount) || swatchAmount <= 0) {
errMsg.set('amount', 'MUST be a positive number');
}
if (swatchAmount > 30) {
errMsg.set('amount', 'MUST be less than 30');
}
const minLightness = Number(formData.get('min_lightness'));
if (isNaN(minLightness) || minLightness < 0 || minLightness > 100) {
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) {
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)) {
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);
updateScheme((prev) => {
prev.schemeStorage.source = {
colors: dumpedEntries,
setting: dumpedSettings,
};
prev.schemeStorage.scheme = mapToObject(generatedScheme[0]) as SwatchScheme;
prev.schemeStorage.cssVariables = generatedScheme[1];
prev.schemeStorage.scssVariables = generatedScheme[2];
prev.schemeStorage.jsVariables = generatedScheme[3];
return prev;
});
onBuildCompleted?.();
} catch (e) {
console.error('[build swatch scheme]', e);
}
const minLightness = Number(formData.get('min_lightness'));
if (isNaN(minLightness) || minLightness < 0 || minLightness > 100) {
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) {
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)) {
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);
updateScheme((prev) => {
prev.schemeStorage.source = {
colors: dumpedEntries,
setting: dumpedSettings,
};
prev.schemeStorage.scheme = mapToObject(generatedScheme[0]);
prev.schemeStorage.cssVariables = generatedScheme[1];
prev.schemeStorage.scssVariables = generatedScheme[2];
prev.schemeStorage.jsVariables = generatedScheme[3];
return prev;
});
onBuildCompleted?.();
} catch (e) {
console.error('[build swatch scheme]', e);
}
return errMsg;
}, new Map<string, string>());
return errMsg;
},
new Map<string, string>(),
);
return (
<ScrollArea enableY>

View File

@ -54,12 +54,12 @@ export function SwatchSchemePreview({ scheme }: SwatchSchemePreviewProps) {
<h2>Light Scheme</h2>
<SchemeBlock
amount={scheme.schemeStorage.source?.setting?.amount ?? 0}
scheme={scheme.schemeStorage.scheme.light}
scheme={scheme.schemeStorage.scheme!.light}
/>
<h2>Dark Scheme</h2>
<SchemeBlock
amount={scheme.schemeStorage.source?.setting?.amount ?? 0}
scheme={scheme.schemeStorage.scheme.dark}
scheme={scheme.schemeStorage.scheme!.dark}
/>
</div>
</ScrollArea>

View File

@ -23,18 +23,18 @@ export function Shades({ color, shades, mix, step, maximum, copyMode }: ShadesLi
switch (mix) {
case 'progressive':
for (let i = 1; i <= shades; i++) {
const shade = colorFn!.shade(last(genColors), step);
const shade = colorFn!.shade(last(genColors) ?? '', step ?? 0);
genColors.push(shade);
}
break;
case 'linear':
for (let i = 1; i <= shades; i++) {
const shade = colorFn!.shade(color, step * i);
const shade = colorFn!.shade(color, (step ?? 0) * i);
genColors.push(shade);
}
break;
case 'average': {
const interval = maximum / shades / 100;
const interval = (maximum ?? 0) / shades / 100;
for (let i = 1; i <= shades; i++) {
const shade = colorFn!.shade(color, interval * i);
genColors.push(shade);

View File

@ -23,18 +23,18 @@ export function Tints({ color, tints, mix, step, maximum, copyMode }: TintsListP
switch (mix) {
case 'progressive':
for (let i = 1; i <= tints; i++) {
const tint = colorFn!.tint(last(genColors), step);
const tint = colorFn!.tint(last(genColors) ?? '', step ?? 0);
genColors.push(tint);
}
break;
case 'linear':
for (let i = 1; i <= tints; i++) {
const tint = colorFn!.tint(color, step * i);
const tint = colorFn!.tint(color, (step ?? 0) * i);
genColors.push(tint);
}
break;
case 'average': {
const interval = maximum / tints / 100;
const interval = (maximum ?? 0) / tints / 100;
for (let i = 1; i <= tints; i++) {
const tint = colorFn!.tint(color, interval * i);
genColors.push(tint);

View File

@ -8,6 +8,7 @@ import { ColorDescription } from '../models';
import { ColorCard } from '../page-components/cards-detail/ColorCard';
import styles from './CardsDetail.module.css';
type ColorModes = 'hex' | 'rgb' | 'hsl' | 'lab' | 'oklch';
type CardsDetailProps = {
mainTag: string;
};
@ -20,7 +21,7 @@ export function CardsDetail({ mainTag }: CardsDetailProps) {
}
try {
const embededCategories = colorFn.color_categories() as { label: string; value: string }[];
return embededCategories.filter((cate) => !isEqual(cate.get('value'), 'unknown'));
return embededCategories.filter((cate) => !isEqual(cate.value, 'unknown'));
} catch (e) {
console.error('[Fetch color categories]', e);
}
@ -31,7 +32,7 @@ export function CardsDetail({ mainTag }: CardsDetailProps) {
const selectedValue = e.target.value;
setCategory(selectedValue);
};
const [mode, setMode] = useState<'hex' | 'rgb' | 'hsl' | 'lab' | 'oklch'>('hex');
const [mode, setMode] = useState<ColorModes>('hex');
const colors = useMemo(() => {
if (!colorFn) {
return [];
@ -69,8 +70,8 @@ export function CardsDetail({ mainTag }: CardsDetailProps) {
onChange={handleSelectCategory}>
<option value="null">All</option>
{categories.map((cate, index) => (
<option key={`${cate.get('value')}-${index}`} value={cate.get('value')}>
{cate.get('label')}
<option key={`${cate.value}-${index}`} value={cate.value}>
{cate.label}
</option>
))}
</select>
@ -85,7 +86,7 @@ export function CardsDetail({ mainTag }: CardsDetailProps) {
{ label: 'OKLCH', value: 'oklch' },
]}
value={mode}
onChange={setMode}
onChange={(v) => setMode(v as ColorModes)}
/>
</div>
<ScrollArea enableY>

View File

@ -44,7 +44,7 @@ export function ColorCompare() {
{ label: 'Relative', value: 'relative' },
]}
value={analysisMode}
onChange={setMode}
onChange={(v) => setMode(v as Parameters<typeof setMode>[0])}
/>
</Labeled>
<RGBCompare basic={basicColor} compare={compareColor} mode={analysisMode} />

View File

@ -114,7 +114,7 @@ export function Harmonies() {
<div className={styles.mode_navigation}>
<h5>Color selection method</h5>
<VSegmentedControl
onChange={setSelectedMode}
onChange={(v) => setSelectedMode(v as string)}
options={[
{ label: 'Complementary', value: 'complementary' },
{ label: 'Analogous', value: 'analogous' },

View File

@ -13,6 +13,8 @@ import { Lightens } from '../page-components/lighten-darken/lightens';
import { currentPickedColor } from '../stores/colors';
import styles from './LightenDarken.module.css';
type ColorModes = 'hex' | 'rgb' | 'hsl' | 'lab' | 'oklch';
export function LightenDarken() {
const [selectedColor, setSelectedColor] = useAtom(currentPickedColor);
const [lighten, setLighten] = useState(3);
@ -20,7 +22,7 @@ export function LightenDarken() {
const [steps, setSteps] = useState(10);
const [maximum, setMaximum] = useState(90);
const [mixMode, setMixMode] = useState<'progressive' | 'average'>('progressive');
const [mode, setMode] = useState<'hex' | 'rgb' | 'hsl' | 'lab' | 'oklch'>('hex');
const [mode, setMode] = useState<ColorModes>('hex');
return (
<div className={cx('workspace', styles.lighten_workspace)}>
@ -123,8 +125,8 @@ export function LightenDarken() {
{ label: 'LAB', value: 'lab' },
{ label: 'OKLCH', value: 'oklch' },
]}
valu={mode}
onChange={setMode}
value={mode}
onChange={(v) => setMode(v as ColorModes)}
/>
</div>
</div>

View File

@ -8,12 +8,14 @@ import { LabeledPicker } from '../components/LabeledPicker';
import { ScrollArea } from '../components/ScrollArea';
import styles from './Mixer.module.css';
type ColorModes = 'hex' | 'rgb' | 'hsl' | 'lab' | 'oklch';
export function Mixer() {
const { colorFn } = useColorFunction();
const [basicColor, setBasicColor] = useState('000000');
const [mixColor, setMixColor] = useState('000000');
const [mixRatio, setMixRatio] = useState(0);
const [mode, setMode] = useState<'hex' | 'rgb' | 'hsl' | 'lab' | 'oklch'>('hex');
const [mode, setMode] = useState<ColorModes>('hex');
const mixedColor = useMemo(() => {
try {
if (!colorFn) {
@ -68,7 +70,7 @@ export function Mixer() {
{ label: 'OKLCH', value: 'oklch' },
]}
value={mode}
onChange={setMode}
onChange={(v) => setMode(v as ColorModes)}
/>
</div>
</div>

View File

@ -11,21 +11,24 @@ export function NewScheme() {
const createScheme = useCreateScheme();
const navigate = useNavigate();
const [schemeType, setSchemeType] = useState<SchemeTypeOption['value']>('q_scheme');
const [errors, formAction] = useActionState((prevState, formData) => {
try {
const name = formData.get('name') as string;
if (isNil(name) || isEmpty(name)) {
throw { name: 'Name is required' };
const [errors, formAction] = useActionState<{ [key: string]: string }, FormData>(
(_prevState, formData): { [key: string]: string } => {
try {
const name = formData.get('name') as string;
if (isNil(name) || isEmpty(name)) {
throw { name: 'Name is required' };
}
const description = (formData.get('description') ?? null) as string | null;
const schemeType = (formData.get('type') ?? 'q_scheme') as SchemeTypeOption['value'];
const newId = createScheme(name, schemeType, description);
navigate(`../${newId}`);
} catch (error) {
return error as { [key: string]: string };
}
const description = (formData.get('description') ?? null) as string | null;
const schemeType = (formData.get('type') ?? 'q_scheme') as SchemeTypeOption['value'];
const newId = createScheme(name, schemeType, description);
navigate(`../${newId}`);
} catch (error) {
return error;
}
return {};
}, {});
return {} as { [key: string]: string };
},
{},
);
return (
<form action={formAction} className={styles.create_scheme_form_layout}>
@ -37,7 +40,7 @@ export function NewScheme() {
options={SchemeTypeOptions}
extendClassName={styles.custom_segment}
value={schemeType}
onChange={setSchemeType}
onChange={(v) => setSchemeType(v as SchemeTypeOption['value'])}
/>
<input type="hidden" name="type" value={schemeType} />
</div>

View File

@ -11,6 +11,8 @@ import { PaletteColors } from '../page-components/auto-palette/PaletteColors';
import { currentPickedColor } from '../stores/colors';
import styles from './Palette.module.css';
type ColorModes = 'hex' | 'rgb' | 'hsl' | 'lab' | 'oklch';
export function AutomaticPalette() {
const [selectedColor, setSelectedColor] = useAtom(currentPickedColor);
const [useReferenceColor, setUseReferenceColor] = useState(false);
@ -22,7 +24,7 @@ export function AutomaticPalette() {
const [referenceBias, setReferenceBias] = useState(0);
const [minLightness, setMinLightness] = useState(10);
const [maxLightness, setMaxLightness] = useState(90);
const [mode, setMode] = useState<'hex' | 'rgb' | 'hsl' | 'lab' | 'oklch'>('hex');
const [mode, setMode] = useState<ColorModes>('hex');
useEffect(() => {
if (useReferenceColor) {
@ -111,8 +113,8 @@ export function AutomaticPalette() {
{ label: 'LAB', value: 'lab' },
{ label: 'OKLCH', value: 'oklch' },
]}
valu={mode}
onChange={setMode}
value={mode}
onChange={(v) => setMode(v as ColorModes)}
/>
</div>
</div>

View File

@ -5,12 +5,17 @@ import { useNavigate, useParams } from 'react-router-dom';
import { EditableDescription } from '../components/EditableDescription';
import { EditableTitle } from '../components/EditableTitle';
import { SchemeSign } from '../components/SchemeSign';
import { MaterialDesign2SchemeStorage } from '../material-2-scheme';
import { MaterialDesign3SchemeStorage } from '../material-3-scheme';
import { SchemeContent } from '../models';
import { CorruptedScheme } from '../page-components/scheme/CorruptedScheme';
import { M2Scheme } from '../page-components/scheme/M2Scheme';
import { M3Scheme } from '../page-components/scheme/M3Scheme';
import { QScheme } from '../page-components/scheme/QScheme';
import { SwatchScheme } from '../page-components/scheme/SwatchScheme';
import { QSchemeStorage } from '../q-scheme';
import { useScheme, useUpdateScheme } from '../stores/schemes';
import { SwatchSchemeStorage } from '../swatch_scheme';
import styles from './SchemeDetail.module.css';
export function SchemeDetail() {
@ -40,13 +45,13 @@ export function SchemeDetail() {
const schemeContent = useMemo(() => {
switch (scheme?.type) {
case 'q_scheme':
return <QScheme scheme={scheme} />;
return <QScheme scheme={scheme as SchemeContent<QSchemeStorage>} />;
case 'swatch_scheme':
return <SwatchScheme scheme={scheme} />;
return <SwatchScheme scheme={scheme as SchemeContent<SwatchSchemeStorage>} />;
case 'material_2':
return <M2Scheme scheme={scheme} />;
return <M2Scheme scheme={scheme as SchemeContent<MaterialDesign2SchemeStorage>} />;
case 'material_3':
return <M3Scheme scheme={scheme} />;
return <M3Scheme scheme={scheme as SchemeContent<MaterialDesign3SchemeStorage>} />;
default:
return <CorruptedScheme />;
}

View File

@ -13,6 +13,8 @@ import { Tints } from '../page-components/tints-shades/tints';
import { currentPickedColor } from '../stores/colors';
import styles from './TintsShades.module.css';
type ColorModes = 'hex' | 'rgb' | 'hsl' | 'lab' | 'oklch';
export function TintsShades() {
const [selectedColor, setSelectedColor] = useAtom(currentPickedColor);
const [steps, setSteps] = useState(10);
@ -20,7 +22,7 @@ export function TintsShades() {
const [shades, setShades] = useState(3);
const [maximum, setMaximum] = useState(90);
const [mixMode, setMixMode] = useState<'progressive' | 'average'>('progressive');
const [mode, setMode] = useState<'hex' | 'rgb' | 'hsl' | 'lab' | 'oklch'>('hex');
const [mode, setMode] = useState<ColorModes>('hex');
return (
<div className={cx('workspace', styles.tints_workspace)}>
@ -122,8 +124,8 @@ export function TintsShades() {
{ label: 'LAB', value: 'lab' },
{ label: 'OKLCH', value: 'oklch' },
]}
valu={mode}
onChange={setMode}
value={mode}
onChange={(v) => setMode(v as ColorModes)}
/>
</div>
</div>

View File

@ -11,13 +11,15 @@ import { ScrollArea } from '../components/ScrollArea';
import { currentPickedColor } from '../stores/colors';
import styles from './Tones.module.css';
type ColorModes = 'hex' | 'rgb' | 'hsl' | 'lab' | 'oklch';
export function Tones() {
const { colorFn } = useColorFunction();
const [selectedColor, setSelectedColor] = useAtom(currentPickedColor);
const [steps, setSteps] = useState(10);
const [tones, setTones] = useState(3);
const [seedBias, setSeedBias] = useState(0);
const [mode, setMode] = useState<'hex' | 'rgb' | 'hsl' | 'lab' | 'oklch'>('hex');
const [mode, setMode] = useState<ColorModes>('hex');
const colors = useMemo(() => {
try {
const lightenColors = colorFn!.tonal_lighten_series(
@ -98,8 +100,8 @@ export function Tones() {
{ label: 'LAB', value: 'lab' },
{ label: 'OKLCH', value: 'oklch' },
]}
valu={mode}
onChange={setMode}
value={mode}
onChange={(v) => setMode(v as ColorModes)}
/>
</div>
</div>

View File

@ -9,9 +9,11 @@ import { ColorWheel } from '../page-components/wheels/ColorWheel';
import { currentPickedColor } from '../stores/colors';
import styles from './Wheels.module.css';
type HighlightMode = Parameters<typeof ColorWheel>[0]['highlightMode'];
export function Wheels() {
const [selectedColor, setSelectedColor] = useAtom(currentPickedColor);
const [selectedMode, setSelectedMode] = useState('complementary');
const [selectedMode, setSelectedMode] = useState<HighlightMode>('complementary');
const [steps, setSteps] = useState(10);
const [tones, setTones] = useState(3);
@ -31,7 +33,7 @@ export function Wheels() {
<div className={styles.mode_navigation}>
<h5>Color selection method</h5>
<VSegmentedControl
onChange={setSelectedMode}
onChange={(v) => setSelectedMode(v as HighlightMode)}
options={[
{ label: 'Complementary', value: 'complementary' },
{ label: 'Analogous', value: 'analogous' },

View File

@ -47,7 +47,7 @@ export type QSchemeSource = {
warning: string | null;
info: string | null;
foreground: string | null;
background: strin | nullg;
background: string | null;
setting: QSchemeSetting | null;
};

View File

@ -42,7 +42,10 @@ export type SchemeSet = {
const schemesAtom = atomWithStorage<SchemeContent<SchemeStorage>[]>('schemes', []);
export const activeSchemeAtom = atomWithStorage<string | null>('activeScheme', null);
export function useSchemeList(): Pick<SchemeContent<SchemeStorage>, 'id' | 'name' | 'createdAt'>[] {
export function useSchemeList(): Pick<
SchemeContent<SchemeStorage>,
'id' | 'name' | 'createdAt' | 'type'
>[] {
const schemes = useAtomValue(schemesAtom);
const sortedSchemes = useMemo(
() =>
@ -56,9 +59,12 @@ export function useSchemeList(): Pick<SchemeContent<SchemeStorage>, 'id' | 'name
return sortedSchemes;
}
export function useScheme(id: string): SchemeContent<SchemeStorage> | null {
export function useScheme(id?: string | null): SchemeContent<SchemeStorage> | null {
const schemes = useAtomValue(schemesAtom);
const scheme = useMemo(() => schemes.find((s) => isEqual(id, s.id)) ?? null, [schemes, id]);
const scheme = useMemo(
() => schemes.find((s) => !isNil(id) && isEqual(id, s.id)) ?? null,
[schemes, id],
);
return scheme;
}
@ -71,11 +77,11 @@ export function useActiveScheme(): SchemeContent<SchemeStorage> | null {
export function useCreateScheme(): (
name: string,
type: SchemeType,
description?: string,
description?: string | null,
) => string {
const updateSchemes = useSetAtom(schemesAtom);
const createSchemeAction = useCallback(
(name: string, type: SchemeType, description?: string) => {
(name: string, type: SchemeType, description?: string | null) => {
const newId = v4();
updateSchemes((prev) => [
...prev.filter((s) => !isNil(s)),
@ -97,7 +103,7 @@ export function useCreateScheme(): (
}
export function useUpdateScheme(
id: string,
id?: string | null,
): (updater: (prev: SchemeContent<SchemeStorage>) => SchemeContent<SchemeStorage>) => void {
const updateSchemes = useSetAtom(schemesAtom);
const updateAction = useCallback(
@ -107,7 +113,7 @@ export function useUpdateScheme(
prev,
(acc, scheme) => {
if (!isNil(scheme)) {
if (isEqual(id, scheme.id)) {
if (!isNil(id) && isEqual(id, scheme.id)) {
acc.push(updater(scheme));
} else {
acc.push(scheme);

View File

@ -1,7 +1,7 @@
import { isEmpty, isNil } from 'lodash-es';
export function defaultEmptyFormData<D>(formData: FormData, param: string, defaultValue: D): D {
const value = formData.get(param);
const value = formData.get(param) as D;
if (isNil(value) || isEmpty(value)) {
return defaultValue;
}
@ -15,10 +15,8 @@ export function defaultEmptyValue<T, D>(value: T, defaultValue: D): T | D {
return value;
}
export function mapToObject<K extends string | number | symbol, V>(
map: Map<K, V>,
): Record<K, V extends Map<unknown, unknown> ? unknown : V> {
const obj: Record<K, V extends Map<unknown, unknown> ? unknown : V> = {};
export function mapToObject<K extends string | number | symbol, V>(map: Map<K, V>): Record<K, V> {
const obj = {} as Record<K, V>;
map.forEach((value, key) => {
if (value instanceof Map) {
obj[key] = mapToObject(value);

View File

@ -3,10 +3,13 @@
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"lib": [
"ES2020",
"DOM",
"DOM.Iterable"
],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
@ -14,13 +17,14 @@
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"strict": false,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src"]
}
"include": [
"src"
]
}

View File

@ -2,23 +2,25 @@
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2022",
"lib": ["ES2023"],
"lib": [
"ES2023"
],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"strict": false,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["vite.config.ts"]
}
"include": [
"vite.config.ts"
]
}