From cbaf999692e54a0eb711fb9d53b70c7a7577bb72 Mon Sep 17 00:00:00 2001 From: Vixalie Date: Thu, 13 Mar 2025 21:46:17 +0800 Subject: [PATCH] add pulse manipulate function in Pattern. --- src/context/Patterns.tsx | 148 ++++++++++++++++++++++++++------------- 1 file changed, 101 insertions(+), 47 deletions(-) diff --git a/src/context/Patterns.tsx b/src/context/Patterns.tsx index 56f47be..a32c07a 100644 --- a/src/context/Patterns.tsx +++ b/src/context/Patterns.tsx @@ -1,8 +1,9 @@ import { invoke } from '@tauri-apps/api/core'; import dayjs from 'dayjs'; -import { atom, useSetAtom } from 'jotai'; +import { atom, useAtomValue, useSetAtom } from 'jotai'; import { atomWithRefresh } from 'jotai/utils'; import { get, reduce } from 'lodash-es'; +import { useCallback } from 'react'; import { v4 } from 'uuid'; import { NotificationType, ToastDuration, useNotification } from '../components/Notifications'; @@ -94,22 +95,76 @@ export class Pattern { this.smoothRepeat = true; this.pulses = []; } -} -export function createNewPulse(pattern: Pattern): Pulse { - const maxOrder = reduce(pattern.pulses, (former, pulse) => Math.max(former, pulse.order), 0); - if (pattern.smoothRepeat) { - return new Pulse( + movePulseUp(pulseId: string, step: number) { + const index = this.pulses.findIndex((pulse) => pulse.id === pulseId); + if (index === -1 || index - step < 0) return; + + const targetIndex = index - step; + const targetPulse = this.pulses[targetIndex]; + const currentPulse = this.pulses[index]; + + // Swap the pulses + this.pulses[targetIndex] = currentPulse; + this.pulses[index] = targetPulse; + + // Swap their order + const tempOrder = currentPulse.order; + currentPulse.order = targetPulse.order; + targetPulse.order = tempOrder; + + // Sort pulses by order + this.pulses.sort((a, b) => a.order - b.order); + } + + movePulseDown(pulseId: string, step: number) { + const index = this.pulses.findIndex((pulse) => pulse.id === pulseId); + if (index === -1 || index + step >= this.pulses.length) return; + + const targetIndex = index + step; + const targetPulse = this.pulses[targetIndex]; + const currentPulse = this.pulses[index]; + + // Swap the pulses + this.pulses[targetIndex] = currentPulse; + this.pulses[index] = targetPulse; + + // Swap their order + const tempOrder = currentPulse.order; + currentPulse.order = targetPulse.order; + targetPulse.order = tempOrder; + + // Sort pulses by order + this.pulses.sort((a, b) => a.order - b.order); + } + + addPulse(): Pulse { + const maxOrder = reduce(this.pulses, (former, pulse) => Math.max(former, pulse.order), 0); + const newPulse = new Pulse( maxOrder + 1, - get(pattern.pulses, '[0].width', 0), - get(pattern.pulses, '[0].frequency', 1), - ); - } else { - return new Pulse( - maxOrder + 1, - get(pattern.pulses, '[-1].width', 0), - get(pattern.pulses, '[-1].frequency', 1), + this.smoothRepeat ? get(this.pulses, '[0].width', 0) : get(this.pulses, '[-1].width', 0), + this.smoothRepeat + ? get(this.pulses, '[0].frequency', 1) + : get(this.pulses, '[-1].frequency', 1), ); + this.pulses.push(newPulse); + return newPulse; + } + + updatePulse(pulseId: string, pulse: Pulse) { + const index = this.pulses.findIndex((p) => p.id === pulseId); + if (index !== -1) { + const { id, order, ...rest } = pulse; + this.pulses[index] = { ...this.pulses[index], ...rest }; + } + } + + deletePulse(pulseId: string) { + this.pulses = this.pulses.filter((pulse) => pulse.id !== pulseId); + this.pulses.sort((a, b) => a.order - b.order); + this.pulses.forEach((pulse, index) => { + pulse.order = index + 1; + }); } } @@ -133,7 +188,7 @@ export const PatternsAtom = atomWithRefresh(async (get) => { return []; }); export const SelectedPatternIdAtom = atom(null); -export const CurrentPatternAtom = atom(async (get) => { +export const CurrentPatternAtom = atomWithRefresh(async (get) => { try { const patternId = get(SelectedPatternIdAtom); if (patternId === null) { @@ -146,50 +201,49 @@ export const CurrentPatternAtom = atom(async (get) => { } return null; }); -export const PulsesInCurrentPatternAtom = atom( +export const PulsesInCurrentPatternAtom = atomWithRefresh( (get) => get(CurrentPatternAtom)?.pulses ?? [], - (get, set, pulse: Pulse) => { - const currentPulses = get(CurrentPatternAtom)?.pulses ?? []; - const newPulses = currentPulses.map((p) => (p.id === pulse.id ? pulse : p)); - if (!newPulses.some((p) => p.id === pulse.id)) { - newPulses.push(pulse); - } - newPulses.sort((a, b) => a.order - b.order); - const currentPattern = get(CurrentPatternAtom); - if (currentPattern) { - set(CurrentPatternAtom, { - ...currentPattern, - pulses: newPulses, - }); - } - }, ); export const CurrentPatternDuration = atom((get) => { const currentPattern = get(CurrentPatternAtom); if (!currentPattern) return 0; return totalDuration(currentPattern); }); +export const SelectedPulseIdAtom = atom(null); +export const SelectedPulseAtom = atom((get) => { + const pulses = get(PulsesInCurrentPatternAtom); + const selectedPulseId = get(SelectedPulseIdAtom); + return pulses.find((pulse) => pulse.id === selectedPulseId) ?? null; +}); export function useSavePattern() { const refreshPatterns = useSetAtom(PatternsAtom); + const selectedPatternId = useAtomValue(SelectedPatternIdAtom); + const refreshSelectedPattern = useSetAtom(CurrentPatternAtom); const { showToast } = useNotification(); - const savePattern = async (pattern: Pattern) => { - try { - await invoke('save_pattern', { pattern }); - refreshPatterns(); - return true; - } catch (error) { - console.error('[save pattern]', error); - showToast( - NotificationType.ERROR, - 'Failed to save pattern. Please try again.', - 'material-symbols-light:error-outline', - ToastDuration.MEDIUM, - ); - } - return false; - }; + const savePattern = useCallback( + async (pattern: Pattern) => { + try { + await invoke('save_pattern', { pattern }); + refreshPatterns(); + if (pattern.id === selectedPatternId) { + refreshSelectedPattern(); + } + return true; + } catch (error) { + console.error('[save pattern]', error); + showToast( + NotificationType.ERROR, + 'Failed to save pattern. Please try again.', + 'material-symbols-light:error-outline', + ToastDuration.MEDIUM, + ); + } + return false; + }, + [selectedPatternId], + ); return savePattern; }