add pulse manipulate function in Pattern.

This commit is contained in:
Vixalie 2025-03-13 21:46:17 +08:00
parent b3ddf3710e
commit cbaf999692

View File

@ -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<string | null>(null);
export const CurrentPatternAtom = atom<Pattern | null>(async (get) => {
export const CurrentPatternAtom = atomWithRefresh<Pattern | null>(async (get) => {
try {
const patternId = get(SelectedPatternIdAtom);
if (patternId === null) {
@ -146,50 +201,49 @@ export const CurrentPatternAtom = atom<Pattern | null>(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<string | null>(null);
export const SelectedPulseAtom = atom<Pulse | null>((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;
}