修正大部分的编译错误。

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 { Icon, IconProps } from '@iconify/react/dist/iconify.js';
import cx from 'clsx'; import cx from 'clsx';
import { MouseEventHandler, useCallback } from 'react'; import { MouseEvent, MouseEventHandler, useCallback } from 'react';
import styles from './ActionIcon.module.css'; import styles from './ActionIcon.module.css';
type ActionIconProps = { type ActionIconProps = {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -24,7 +24,7 @@ export function QScheme({ scheme }: QSchemeProps) {
return ( 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, 'overview') && <QSchemePreview scheme={scheme} />}
{isEqual(activeTab, 'builder') && ( {isEqual(activeTab, 'builder') && (
<QSchemeBuilder scheme={scheme} onBuildCompleted={() => setActiveTab('overview')} /> <QSchemeBuilder scheme={scheme} onBuildCompleted={() => setActiveTab('overview')} />

View File

@ -24,7 +24,7 @@ export function SwatchScheme({ scheme }: SwatchSchemeProps) {
return ( 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, 'overview') && <SwatchSchemePreview scheme={scheme} />}
{isEqual(activeTab, 'builder') && ( {isEqual(activeTab, 'builder') && (
<SwatchSchemeBuilder scheme={scheme} onBuildCompleted={() => setActiveTab('overview')} /> <SwatchSchemeBuilder scheme={scheme} onBuildCompleted={() => setActiveTab('overview')} />

View File

@ -36,18 +36,19 @@ export function M2SchemeBuilder({ scheme, onBuildComplete }: M2SchemeBuilderProp
[originalColors, newColors, deleted], [originalColors, newColors, deleted],
); );
const [errMsg, handleSubmitAction] = useActionState((state, formData) => { const [errMsg, handleSubmitAction] = useActionState<Map<string, string>, FormData>(
(_state, formData) => {
const errMsg = new Map<string, string>(); const errMsg = new Map<string, string>();
try { try {
const primaryColor = formData.get('primary'); const primaryColor = formData.get('primary') as string;
if (isNil(primaryColor) || isEmpty(primaryColor)) { if (isNil(primaryColor) || isEmpty(primaryColor)) {
errMsg.set('primary', 'Primary color is required'); errMsg.set('primary', 'Primary color is required');
} }
const secondaryColor = formData.get('secondary'); const secondaryColor = formData.get('secondary') as string;
if (isNil(secondaryColor) || isEmpty(secondaryColor)) { if (isNil(secondaryColor) || isEmpty(secondaryColor)) {
errMsg.set('secondary', 'Secondary color is required'); errMsg.set('secondary', 'Secondary color is required');
} }
const errorColor = formData.get('error'); const errorColor = formData.get('error') as string;
if (isNil(errorColor) || isEmpty(errorColor)) { if (isNil(errorColor) || isEmpty(errorColor)) {
errMsg.set('error', 'Error color is required'); errMsg.set('error', 'Error color is required');
} }
@ -89,7 +90,9 @@ export function M2SchemeBuilder({ scheme, onBuildComplete }: M2SchemeBuilderProp
} }
return errMsg; return errMsg;
}, new Map<string, string>()); },
new Map<string, string>(),
);
return ( return (
<ScrollArea enableY> <ScrollArea enableY>

View File

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

View File

@ -36,15 +36,16 @@ export function M3SchemeBuilder({ scheme, onBuildCompleted }: M3SchemeBuilderPro
[originalColors, newColors, deleted], [originalColors, newColors, deleted],
); );
const [errMsg, handleSubmitAction] = useActionState((state, formData) => { const [errMsg, handleSubmitAction] = useActionState<Map<string, string>, FormData>(
(_state, formData) => {
const errMsg = new Map<string, string>(); const errMsg = new Map<string, string>();
try { try {
const sourceColor = formData.get('source'); const sourceColor = formData.get('source') as string;
if (isNil(sourceColor) || isEmpty(sourceColor)) { if (isNil(sourceColor) || isEmpty(sourceColor)) {
errMsg.set('source', 'Source color is required'); errMsg.set('source', 'Source color is required');
} }
const errorColor = formData.get('error'); const errorColor = formData.get('error') as string;
if (isNil(errorColor) || isEmpty(errorColor)) { if (isNil(errorColor) || isEmpty(errorColor)) {
errMsg.set('error', 'Error color is required'); errMsg.set('error', 'Error color is required');
} }
@ -93,7 +94,9 @@ export function M3SchemeBuilder({ scheme, onBuildCompleted }: M3SchemeBuilderPro
} }
return errMsg; return errMsg;
}, new Map<string, string>()); },
new Map<string, string>(),
);
return ( return (
<ScrollArea enableY> <ScrollArea enableY>

View File

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

View File

@ -82,7 +82,8 @@ export function QSchemeBuilder({ scheme, onBuildCompleted }: QSchemeBuilderProps
return []; return [];
}, []); }, []);
const [errMsg, handleSubmitAction] = useActionState((state, formData) => { const [errMsg, handleSubmitAction] = useActionState<Map<string, string>, FormData>(
(_state, formData) => {
const errMsg = new Map<string, string>(); const errMsg = new Map<string, string>();
const requiredFields = [ const requiredFields = [
'primary', 'primary',
@ -128,9 +129,9 @@ export function QSchemeBuilder({ scheme, onBuildCompleted }: QSchemeBuilderProps
const source: QSchemeSource = { const source: QSchemeSource = {
primary: defaultEmptyFormData(formData, 'primary', null), primary: defaultEmptyFormData(formData, 'primary', null),
secondary: defaultEmptyFormData(formData, 'secondary', undefined), secondary: defaultEmptyFormData(formData, 'secondary', null),
tertiary: defaultEmptyFormData(formData, 'tertiary', undefined), tertiary: defaultEmptyFormData(formData, 'tertiary', null),
accent: defaultEmptyFormData(formData, 'accent', undefined), accent: defaultEmptyFormData(formData, 'accent', null),
danger: defaultEmptyFormData(formData, 'danger', null), danger: defaultEmptyFormData(formData, 'danger', null),
success: defaultEmptyFormData(formData, 'success', null), success: defaultEmptyFormData(formData, 'success', null),
warning: defaultEmptyFormData(formData, 'warn', null), warning: defaultEmptyFormData(formData, 'warn', null),
@ -141,26 +142,26 @@ export function QSchemeBuilder({ scheme, onBuildCompleted }: QSchemeBuilderProps
}; };
const generatedScheme = every([source.secondary, source.tertiary, source.accent], isNil) const generatedScheme = every([source.secondary, source.tertiary, source.accent], isNil)
? colorFn?.generate_q_scheme_automatically( ? colorFn?.generate_q_scheme_automatically(
source.primary, source.primary ?? '',
source.danger, source.danger ?? '',
source.success, source.success ?? '',
source.warning, source.warning ?? '',
source.info, source.info ?? '',
source.foreground, source.foreground ?? '',
source.background, source.background ?? '',
schemeSetting, schemeSetting,
) )
: colorFn?.generate_q_scheme_manually( : colorFn?.generate_q_scheme_manually(
source.primary, source.primary ?? '',
source.secondary ?? undefined, source.secondary ?? undefined,
source.tertiary ?? undefined, source.tertiary ?? undefined,
source.accent ?? undefined, source.accent ?? undefined,
source.danger, source.danger ?? '',
source.success, source.success ?? '',
source.warning, source.warning ?? '',
source.info, source.info ?? '',
source.foreground, source.foreground ?? '',
source.background, source.background ?? '',
schemeSetting, schemeSetting,
); );
updateScheme((prev) => { updateScheme((prev) => {
@ -177,7 +178,9 @@ export function QSchemeBuilder({ scheme, onBuildCompleted }: QSchemeBuilderProps
} }
return errMsg; return errMsg;
}, new Map<string, string>()); },
new Map<string, string>(),
);
return ( return (
<ScrollArea enableY> <ScrollArea enableY>

View File

@ -10,7 +10,12 @@ import { ScrollArea } from '../../../components/ScrollArea';
import { Switch } from '../../../components/Switch'; import { Switch } from '../../../components/Switch';
import { SchemeContent } from '../../../models'; import { SchemeContent } from '../../../models';
import { useUpdateScheme } from '../../../stores/schemes'; 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 { mapToObject } from '../../../utls';
import { ColorEntry, IdenticalColorEntry } from '../ColorEntry'; import { ColorEntry, IdenticalColorEntry } from '../ColorEntry';
import styles from './Builder.module.css'; import styles from './Builder.module.css';
@ -64,7 +69,8 @@ export function SwatchSchemeBuilder({ scheme, onBuildCompleted }: SwatchSchemeBu
return null; return null;
}, [scheme.schemeStorage.source]); }, [scheme.schemeStorage.source]);
const [errMsg, handleSubmitAction] = useActionState((state, formData) => { const [errMsg, handleSubmitAction] = useActionState<Map<string, string>, FormData>(
(_state, formData) => {
const errMsg = new Map<string, string>(); const errMsg = new Map<string, string>();
try { try {
@ -119,7 +125,7 @@ export function SwatchSchemeBuilder({ scheme, onBuildCompleted }: SwatchSchemeBu
colors: dumpedEntries, colors: dumpedEntries,
setting: dumpedSettings, setting: dumpedSettings,
}; };
prev.schemeStorage.scheme = mapToObject(generatedScheme[0]); prev.schemeStorage.scheme = mapToObject(generatedScheme[0]) as SwatchScheme;
prev.schemeStorage.cssVariables = generatedScheme[1]; prev.schemeStorage.cssVariables = generatedScheme[1];
prev.schemeStorage.scssVariables = generatedScheme[2]; prev.schemeStorage.scssVariables = generatedScheme[2];
prev.schemeStorage.jsVariables = generatedScheme[3]; prev.schemeStorage.jsVariables = generatedScheme[3];
@ -132,7 +138,9 @@ export function SwatchSchemeBuilder({ scheme, onBuildCompleted }: SwatchSchemeBu
} }
return errMsg; return errMsg;
}, new Map<string, string>()); },
new Map<string, string>(),
);
return ( return (
<ScrollArea enableY> <ScrollArea enableY>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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