completely refactor patterns atom workflow.
This commit is contained in:
		@@ -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;
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user