113 lines
3.9 KiB
TypeScript
113 lines
3.9 KiB
TypeScript
import { invoke } from '@tauri-apps/api/core';
|
|
import { ask } from '@tauri-apps/plugin-dialog';
|
|
import dayjs from 'dayjs';
|
|
import { useAtomValue, useSetAtom } from 'jotai';
|
|
import { FC, useCallback, useMemo } from 'react';
|
|
import { useNavigate } from 'react-router-dom';
|
|
import { NotificationType, useNotification } from '../../components/Notifications';
|
|
import PatternPreview from '../../components/PatternPreview';
|
|
import { CurrentPatternAtom, PatternsAtom, SelectedPatternIdAtom } from '../../context/Patterns';
|
|
import { Pattern, totalDuration } from '../../context/pattern-model';
|
|
import styles from './PatternDetail.module.css';
|
|
|
|
const EmptyPromption: FC = () => {
|
|
return (
|
|
<div className={styles.empty_promption}>
|
|
<div className={styles.promption}>
|
|
<span className="empty_prompt">Select a pattern from left first.</span>
|
|
</div>
|
|
<div className={styles.spacer} />
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const Detail: FC<{ pattern: Pattern }> = ({ pattern }) => {
|
|
const { showToast } = useNotification();
|
|
const navigate = useNavigate();
|
|
const refreshPatterns = useSetAtom(PatternsAtom);
|
|
const resetSelected = useSetAtom(SelectedPatternIdAtom);
|
|
const patternDuration = useMemo(() => totalDuration(pattern), [pattern]);
|
|
const createTime = useMemo(
|
|
() => dayjs(pattern.createdAt).format('YYYY-MM-DD HH:mm:ss'),
|
|
[pattern],
|
|
);
|
|
|
|
const handleDeleteAction = useCallback(async () => {
|
|
try {
|
|
const answer = await ask(
|
|
`The pattern ${pattern.name} will be deleted, and cannot be revoked. Are you sure?`,
|
|
{
|
|
title: 'Confirm action',
|
|
kind: 'warning',
|
|
},
|
|
);
|
|
if (answer) {
|
|
await invoke('remove_pattern', { patternId: pattern.id });
|
|
showToast(NotificationType.SUCCESS, 'Pattern deleted.');
|
|
refreshPatterns();
|
|
resetSelected(null);
|
|
}
|
|
} catch (e) {
|
|
console.error('[delete pattern]', e);
|
|
showToast(NotificationType.ERROR, 'Failed to delete pattern. Please try again.');
|
|
}
|
|
}, [pattern]);
|
|
|
|
return (
|
|
<div className={styles.pattern_detail}>
|
|
<div className={styles.control_panel}>
|
|
<div className={styles.button_row}>
|
|
<button className="tonal" onClick={() => navigate('/pattern-editor/edit')}>
|
|
Edit Pattern
|
|
</button>
|
|
<button className="tonal danger" onClick={handleDeleteAction}>
|
|
Delete Pattern
|
|
</button>
|
|
</div>
|
|
<div className={styles.button_row}>
|
|
<button className="tonal warn">Test Run</button>
|
|
</div>
|
|
<div className={styles.button_row}>
|
|
<button className="tonal secondary">Add to Channel A Playlist</button>
|
|
<button className="tonal secondary">Add to Channel B Playlist</button>
|
|
</div>
|
|
</div>
|
|
<hr className="dotted" />
|
|
<div className={styles.detail_panel}>
|
|
<div className={styles.detail_row}>
|
|
<div className={styles.detail_unit}>
|
|
<label>Created At</label>
|
|
<div className={styles.content}>{createTime}</div>
|
|
</div>
|
|
</div>
|
|
<div className={styles.detail_row}>
|
|
<div className={styles.detail_unit}>
|
|
<label>Duration</label>
|
|
<div className={styles.content}>{(patternDuration / 1000).toFixed(2)} s</div>
|
|
</div>
|
|
</div>
|
|
<div className={styles.detail_row}>
|
|
<div className={styles.detail_unit}>
|
|
<label> </label>
|
|
<div className={styles.content}>
|
|
{pattern.pulses.length} key frame{pattern.pulses.length > 1 && 's'}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<hr className="dotted" />
|
|
<div className={styles.preview_panel}>
|
|
<PatternPreview />
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const PatternDetail: FC = () => {
|
|
const currentPattern = useAtomValue(CurrentPatternAtom);
|
|
|
|
return !currentPattern ? <EmptyPromption /> : <Detail pattern={currentPattern} />;
|
|
};
|
|
|
|
export default PatternDetail;
|