completely refactor patterns atom workflow.
This commit is contained in:
parent
1c48bb36d3
commit
8b0ddcecec
|
@ -1,7 +1,7 @@
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { atom, useSetAtom } from 'jotai';
|
import { atom, useSetAtom } from 'jotai';
|
||||||
import { atomWithDefault, atomWithRefresh } from 'jotai/utils';
|
import { atomWithRefresh } from 'jotai/utils';
|
||||||
import { get, reduce } from 'lodash-es';
|
import { get, reduce } from 'lodash-es';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
import { NotificationType, ToastDuration, useNotification } from '../components/Notifications';
|
import { NotificationType, ToastDuration, useNotification } from '../components/Notifications';
|
||||||
|
@ -125,9 +125,6 @@ export const SearchKeywordAtom = atom<string | null>(null);
|
||||||
export const PatternsAtom = atomWithRefresh(async (get) => {
|
export const PatternsAtom = atomWithRefresh(async (get) => {
|
||||||
try {
|
try {
|
||||||
const keyword = get(SearchKeywordAtom);
|
const keyword = get(SearchKeywordAtom);
|
||||||
if (keyword === null) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const patterns = await invoke<Pattern[]>('list_patterns', { keyword });
|
const patterns = await invoke<Pattern[]>('list_patterns', { keyword });
|
||||||
return patterns;
|
return patterns;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -135,7 +132,7 @@ export const PatternsAtom = atomWithRefresh(async (get) => {
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
export const SelectedPatternIdAtom = atomWithDefault<string | null>(null);
|
export const SelectedPatternIdAtom = atom<string | null>(null);
|
||||||
export const CurrentPatternAtom = atom<Pattern | null>(async (get) => {
|
export const CurrentPatternAtom = atom<Pattern | null>(async (get) => {
|
||||||
try {
|
try {
|
||||||
const patternId = get(SelectedPatternIdAtom);
|
const patternId = get(SelectedPatternIdAtom);
|
||||||
|
@ -181,6 +178,7 @@ export function useSavePattern() {
|
||||||
try {
|
try {
|
||||||
await invoke('save_pattern', { pattern });
|
await invoke('save_pattern', { pattern });
|
||||||
refreshPatterns();
|
refreshPatterns();
|
||||||
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[save pattern]', error);
|
console.error('[save pattern]', error);
|
||||||
showToast(
|
showToast(
|
||||||
|
@ -190,6 +188,7 @@ export function useSavePattern() {
|
||||||
ToastDuration.MEDIUM,
|
ToastDuration.MEDIUM,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
return savePattern;
|
return savePattern;
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
import { Icon } from '@iconify/react/dist/iconify.js';
|
import { Icon } from '@iconify/react/dist/iconify.js';
|
||||||
import { useAtom } from 'jotai';
|
import { useAtomValue, useSetAtom } from 'jotai';
|
||||||
import { FC, useCallback } from 'react';
|
import { FC, useCallback } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import EditableContent from '../../components/EditableContent';
|
import EditableContent from '../../components/EditableContent';
|
||||||
import { CurrentPatternAtom } from '../../context/Patterns';
|
import { CurrentPatternAtom, SelectedPatternIdAtom } from '../../context/Patterns';
|
||||||
import styles from './PatternHeader.module.css';
|
import styles from './PatternHeader.module.css';
|
||||||
|
|
||||||
const PatternHeader: FC = () => {
|
const PatternHeader: FC = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [currentPattern, setPattern] = useAtom(CurrentPatternAtom);
|
const currentPattern = useAtomValue(CurrentPatternAtom);
|
||||||
|
const setActivePattern = useSetAtom(SelectedPatternIdAtom);
|
||||||
const handleClosePattern = useCallback(() => {
|
const handleClosePattern = useCallback(() => {
|
||||||
setPattern(null);
|
setActivePattern(null);
|
||||||
navigate('/library');
|
navigate('/library');
|
||||||
}, [currentPattern]);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.pattern_header}>
|
<div className={styles.pattern_header}>
|
||||||
|
|
|
@ -9,7 +9,9 @@
|
||||||
gap: calc(var(--spacing) * 2);
|
gap: calc(var(--spacing) * 2);
|
||||||
}
|
}
|
||||||
.empty_promption {
|
.empty_promption {
|
||||||
flex: 1;
|
flex: 2;
|
||||||
|
border-radius: calc(var(--border-radius) * 2);
|
||||||
|
background-color: var(--color-surface-container);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { useAtomValue } from 'jotai';
|
import { useAtomValue } from 'jotai';
|
||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
import { CurrentPatternAtom } from '../../context/Patterns';
|
import { CurrentPatternAtom, Pattern } from '../../context/Patterns';
|
||||||
import styles from './PatternDetail.module.css';
|
import styles from './PatternDetail.module.css';
|
||||||
|
|
||||||
const EmptyPromption: FC = () => {
|
const EmptyPromption: FC = () => {
|
||||||
|
@ -14,10 +14,14 @@ const EmptyPromption: FC = () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const Detail: FC<{ pattern: Pattern }> = ({ pattern }) => {
|
||||||
|
return <div className={styles.pattern_detail}></div>;
|
||||||
|
};
|
||||||
|
|
||||||
const PatternDetail: FC = () => {
|
const PatternDetail: FC = () => {
|
||||||
const currentPattern = useAtomValue(CurrentPatternAtom);
|
const currentPattern = useAtomValue(CurrentPatternAtom);
|
||||||
|
|
||||||
return <div className={styles.pattern_detail}>{!currentPattern && <EmptyPromption />}</div>;
|
return !currentPattern ? <EmptyPromption /> : <Detail pattern={currentPattern} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PatternDetail;
|
export default PatternDetail;
|
||||||
|
|
|
@ -1,29 +1,35 @@
|
||||||
import { Icon } from '@iconify/react/dist/iconify.js';
|
import { Icon } from '@iconify/react/dist/iconify.js';
|
||||||
import cx from 'clsx';
|
import cx from 'clsx';
|
||||||
import { useAtom, useAtomValue } from 'jotai';
|
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
|
||||||
import { FC, useCallback, useMemo, useState } from 'react';
|
import { FC, useCallback, useMemo, useState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useDebounce } from 'react-use';
|
import { useDebounce } from 'react-use';
|
||||||
import { ScrollArea } from '../../components/ScrollArea';
|
import { ScrollArea } from '../../components/ScrollArea';
|
||||||
import { CurrentPatternAtom, Pattern, PatternsAtom, totalDuration } from '../../context/Patterns';
|
import {
|
||||||
|
Pattern,
|
||||||
|
PatternsAtom,
|
||||||
|
SearchKeywordAtom,
|
||||||
|
SelectedPatternIdAtom,
|
||||||
|
totalDuration,
|
||||||
|
} from '../../context/Patterns';
|
||||||
import styles from './Patterns.module.css';
|
import styles from './Patterns.module.css';
|
||||||
|
|
||||||
const PatternCard: FC<{ pattern: Pattern }> = ({ pattern }) => {
|
const PatternCard: FC<{ pattern: Pattern }> = ({ pattern }) => {
|
||||||
const [currentPattern, setCurrentPattern] = useAtom(CurrentPatternAtom);
|
const [selectedId, setSelectedId] = useAtom(SelectedPatternIdAtom);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const duration = useMemo(() => {
|
const duration = useMemo(() => {
|
||||||
return totalDuration(pattern) / 1000;
|
return totalDuration(pattern) / 1000;
|
||||||
}, [pattern]);
|
}, [pattern]);
|
||||||
const selected = useMemo(() => currentPattern?.id === pattern.id, [currentPattern, pattern]);
|
const selected = useMemo(() => selectedId === pattern.id, [selectedId, pattern]);
|
||||||
const handleSingleClick = useCallback(() => {
|
const handleSingleClick = useCallback(() => {
|
||||||
if (currentPattern?.id === pattern.id) {
|
if (selectedId === pattern.id) {
|
||||||
setCurrentPattern(null);
|
setSelectedId(null);
|
||||||
} else {
|
} else {
|
||||||
setCurrentPattern(pattern);
|
setSelectedId(pattern.id);
|
||||||
}
|
}
|
||||||
}, [pattern, currentPattern]);
|
}, [pattern, selectedId]);
|
||||||
const handleDblClick = useCallback(() => {
|
const handleDblClick = useCallback(() => {
|
||||||
setCurrentPattern(pattern);
|
setSelectedId(pattern.id);
|
||||||
navigate('/pattern-editor/edit');
|
navigate('/pattern-editor/edit');
|
||||||
}, [pattern]);
|
}, [pattern]);
|
||||||
|
|
||||||
|
@ -40,8 +46,14 @@ const PatternCard: FC<{ pattern: Pattern }> = ({ pattern }) => {
|
||||||
|
|
||||||
const Patterns: FC = () => {
|
const Patterns: FC = () => {
|
||||||
const [rawKeyword, setRawKeyword] = useState<string>('');
|
const [rawKeyword, setRawKeyword] = useState<string>('');
|
||||||
const [keyword, setKeyword] = useState<string | null>(null);
|
const [keyword, setKeyword] = useAtom(SearchKeywordAtom);
|
||||||
const patterns = useAtomValue(PatternsAtom(keyword));
|
const patterns = useAtomValue(PatternsAtom);
|
||||||
|
const setPatternSelection = useSetAtom(SelectedPatternIdAtom);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const createNewAction = useCallback(() => {
|
||||||
|
setPatternSelection(null);
|
||||||
|
navigate('/pattern-editor/new');
|
||||||
|
}, []);
|
||||||
|
|
||||||
useDebounce(
|
useDebounce(
|
||||||
() => {
|
() => {
|
||||||
|
@ -67,7 +79,7 @@ const Patterns: FC = () => {
|
||||||
onChange={(evt) => setRawKeyword(evt.currentTarget.value)}
|
onChange={(evt) => setRawKeyword(evt.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button className="tonal secondary">
|
<button className="tonal secondary" onClick={createNewAction}>
|
||||||
<Icon icon="material-symbols-light:add" />
|
<Icon icon="material-symbols-light:add" />
|
||||||
New Pattern
|
New Pattern
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
|
||||||
import cx from 'clsx';
|
import cx from 'clsx';
|
||||||
import { useSetAtom } from 'jotai';
|
import { useSetAtom } from 'jotai';
|
||||||
import { FC, useActionState } from 'react';
|
import { FC, useActionState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { NotificationType, ToastDuration, useNotification } from '../components/Notifications';
|
import { NotificationType, ToastDuration, useNotification } from '../components/Notifications';
|
||||||
import { CurrentPatternAtom, Pattern } from '../context/Patterns';
|
import { Pattern, SelectedPatternIdAtom, useSavePattern } from '../context/Patterns';
|
||||||
import styles from './CreatePattern.module.css';
|
import styles from './CreatePattern.module.css';
|
||||||
|
|
||||||
const CreatePattern: FC = () => {
|
const CreatePattern: FC = () => {
|
||||||
const { showToast } = useNotification();
|
const { showToast } = useNotification();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const loadPattern = useSetAtom(CurrentPatternAtom);
|
const loadPattern = useSetAtom(SelectedPatternIdAtom);
|
||||||
|
const savePattern = useSavePattern();
|
||||||
|
|
||||||
const [errState, handleFormSubmit] = useActionState(async (state, formData) => {
|
const [errState, handleFormSubmit] = useActionState(async (state, formData) => {
|
||||||
const patternName = formData.get('pattern_name') as string | null;
|
const patternName = formData.get('pattern_name') as string | null;
|
||||||
|
@ -29,9 +29,8 @@ const CreatePattern: FC = () => {
|
||||||
newPattern.name = patternName;
|
newPattern.name = patternName;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await invoke('save_pattern', { pattern: newPattern });
|
const updated = await savePattern(newPattern);
|
||||||
const reloadedPattern = await invoke('get_pattern', { patternId: newPattern.id });
|
if (!updated) {
|
||||||
if (!reloadedPattern) {
|
|
||||||
showToast(
|
showToast(
|
||||||
NotificationType.ERROR,
|
NotificationType.ERROR,
|
||||||
'Failed to reload the created pattern. Please try again.',
|
'Failed to reload the created pattern. Please try again.',
|
||||||
|
@ -42,8 +41,8 @@ const CreatePattern: FC = () => {
|
||||||
navigate('/library');
|
navigate('/library');
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
loadPattern(reloadedPattern);
|
loadPattern(newPattern.id);
|
||||||
navigate('./edit');
|
navigate('/pattern-editor/edit');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('[save pattern]', e);
|
console.error('[save pattern]', e);
|
||||||
loadPattern(null);
|
loadPattern(null);
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { useAtomValue } from 'jotai';
|
import { useAtomValue } from 'jotai';
|
||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
import { Navigate } from 'react-router-dom';
|
import { Navigate } from 'react-router-dom';
|
||||||
import { CurrentPatternAtom } from '../context/Patterns';
|
import { SelectedPatternIdAtom } from '../context/Patterns';
|
||||||
|
|
||||||
const PatternNavigator: FC = () => {
|
const PatternNavigator: FC = () => {
|
||||||
const currentPattern = useAtomValue(CurrentPatternAtom);
|
const selected = useAtomValue(SelectedPatternIdAtom);
|
||||||
|
|
||||||
return currentPattern === null ? <Navigate to="new" /> : <Navigate to="edit" />;
|
return selected === null ? <Navigate to="new" /> : <Navigate to="edit" />;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PatternNavigator;
|
export default PatternNavigator;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user