Compare commits
No commits in common. "cbaf999692e54a0eb711fb9d53b70c7a7577bb72" and "cc91868c63be8f530c8f5bb172a96692970c50a8" have entirely different histories.
cbaf999692
...
cc91868c63
@ -1,9 +1,8 @@
|
|||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { atom, useAtomValue, useSetAtom } from 'jotai';
|
import { atom, useSetAtom } from 'jotai';
|
||||||
import { atomWithRefresh } from 'jotai/utils';
|
import { atomWithRefresh } from 'jotai/utils';
|
||||||
import { get, reduce } from 'lodash-es';
|
import { get, reduce } from 'lodash-es';
|
||||||
import { useCallback } from 'react';
|
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
import { NotificationType, ToastDuration, useNotification } from '../components/Notifications';
|
import { NotificationType, ToastDuration, useNotification } from '../components/Notifications';
|
||||||
|
|
||||||
@ -95,76 +94,22 @@ export class Pattern {
|
|||||||
this.smoothRepeat = true;
|
this.smoothRepeat = true;
|
||||||
this.pulses = [];
|
this.pulses = [];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
movePulseUp(pulseId: string, step: number) {
|
export function createNewPulse(pattern: Pattern): Pulse {
|
||||||
const index = this.pulses.findIndex((pulse) => pulse.id === pulseId);
|
const maxOrder = reduce(pattern.pulses, (former, pulse) => Math.max(former, pulse.order), 0);
|
||||||
if (index === -1 || index - step < 0) return;
|
if (pattern.smoothRepeat) {
|
||||||
|
return new Pulse(
|
||||||
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,
|
maxOrder + 1,
|
||||||
this.smoothRepeat ? get(this.pulses, '[0].width', 0) : get(this.pulses, '[-1].width', 0),
|
get(pattern.pulses, '[0].width', 0),
|
||||||
this.smoothRepeat
|
get(pattern.pulses, '[0].frequency', 1),
|
||||||
? get(this.pulses, '[0].frequency', 1)
|
);
|
||||||
: get(this.pulses, '[-1].frequency', 1),
|
} else {
|
||||||
|
return new Pulse(
|
||||||
|
maxOrder + 1,
|
||||||
|
get(pattern.pulses, '[-1].width', 0),
|
||||||
|
get(pattern.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;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -188,7 +133,7 @@ export const PatternsAtom = atomWithRefresh(async (get) => {
|
|||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
export const SelectedPatternIdAtom = atom<string | null>(null);
|
export const SelectedPatternIdAtom = atom<string | null>(null);
|
||||||
export const CurrentPatternAtom = atomWithRefresh<Pattern | null>(async (get) => {
|
export const CurrentPatternAtom = atom<Pattern | null>(async (get) => {
|
||||||
try {
|
try {
|
||||||
const patternId = get(SelectedPatternIdAtom);
|
const patternId = get(SelectedPatternIdAtom);
|
||||||
if (patternId === null) {
|
if (patternId === null) {
|
||||||
@ -201,49 +146,50 @@ export const CurrentPatternAtom = atomWithRefresh<Pattern | null>(async (get) =>
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
export const PulsesInCurrentPatternAtom = atomWithRefresh(
|
export const PulsesInCurrentPatternAtom = atom(
|
||||||
(get) => get(CurrentPatternAtom)?.pulses ?? [],
|
(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) => {
|
export const CurrentPatternDuration = atom((get) => {
|
||||||
const currentPattern = get(CurrentPatternAtom);
|
const currentPattern = get(CurrentPatternAtom);
|
||||||
if (!currentPattern) return 0;
|
if (!currentPattern) return 0;
|
||||||
return totalDuration(currentPattern);
|
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() {
|
export function useSavePattern() {
|
||||||
const refreshPatterns = useSetAtom(PatternsAtom);
|
const refreshPatterns = useSetAtom(PatternsAtom);
|
||||||
const selectedPatternId = useAtomValue(SelectedPatternIdAtom);
|
|
||||||
const refreshSelectedPattern = useSetAtom(CurrentPatternAtom);
|
|
||||||
const { showToast } = useNotification();
|
const { showToast } = useNotification();
|
||||||
|
|
||||||
const savePattern = useCallback(
|
const savePattern = async (pattern: Pattern) => {
|
||||||
async (pattern: Pattern) => {
|
try {
|
||||||
try {
|
await invoke('save_pattern', { pattern });
|
||||||
await invoke('save_pattern', { pattern });
|
refreshPatterns();
|
||||||
refreshPatterns();
|
return true;
|
||||||
if (pattern.id === selectedPatternId) {
|
} catch (error) {
|
||||||
refreshSelectedPattern();
|
console.error('[save pattern]', error);
|
||||||
}
|
showToast(
|
||||||
return true;
|
NotificationType.ERROR,
|
||||||
} catch (error) {
|
'Failed to save pattern. Please try again.',
|
||||||
console.error('[save pattern]', error);
|
'material-symbols-light:error-outline',
|
||||||
showToast(
|
ToastDuration.MEDIUM,
|
||||||
NotificationType.ERROR,
|
);
|
||||||
'Failed to save pattern. Please try again.',
|
}
|
||||||
'material-symbols-light:error-outline',
|
return false;
|
||||||
ToastDuration.MEDIUM,
|
};
|
||||||
);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
[selectedPatternId],
|
|
||||||
);
|
|
||||||
|
|
||||||
return savePattern;
|
return savePattern;
|
||||||
}
|
}
|
||||||
|
@ -6,15 +6,6 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: calc(var(--spacing) * 2);
|
gap: calc(var(--spacing) * 2);
|
||||||
}
|
}
|
||||||
.attribute_unit {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
gap: calc(var(--spacing));
|
|
||||||
label {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.pulses {
|
.pulses {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
@ -26,10 +17,4 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
}
|
}
|
||||||
.pulse_cards {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: stretch;
|
|
||||||
gap: calc(var(--spacing) * 2);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,51 +1,19 @@
|
|||||||
import { Icon } from '@iconify/react/dist/iconify.js';
|
import { Icon } from '@iconify/react/dist/iconify.js';
|
||||||
import { useAtomValue } from 'jotai';
|
|
||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
import { ScrollArea } from '../../components/ScrollArea';
|
import { ScrollArea } from '../../components/ScrollArea';
|
||||||
import {
|
|
||||||
CurrentPatternAtom,
|
|
||||||
CurrentPatternDuration,
|
|
||||||
PulsesInCurrentPatternAtom,
|
|
||||||
} from '../../context/Patterns';
|
|
||||||
import styles from './PulseList.module.css';
|
import styles from './PulseList.module.css';
|
||||||
|
|
||||||
const PulseList: FC = () => {
|
const PulseList: FC = () => {
|
||||||
const pattern = useAtomValue(CurrentPatternAtom);
|
|
||||||
const pulses = useAtomValue(PulsesInCurrentPatternAtom);
|
|
||||||
const duration = useAtomValue(CurrentPatternDuration);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={styles.pulse_tools}>
|
<div className={styles.pulse_tools}>
|
||||||
<button className="text">
|
|
||||||
<Icon icon="material-symbols-light:play-arrow-outline" />
|
|
||||||
<span>Test Run</span>
|
|
||||||
</button>
|
|
||||||
<button className="text">
|
<button className="text">
|
||||||
<Icon icon="material-symbols-light:add" />
|
<Icon icon="material-symbols-light:add" />
|
||||||
<span>Add Pulse</span>
|
<span>Add Pulse</span>
|
||||||
</button>
|
</button>
|
||||||
<button className="text">
|
|
||||||
<Icon icon="material-symbols-light:delete-forever-outline" />
|
|
||||||
<span>Delete Selected</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className={styles.pulse_tools}>
|
|
||||||
<div className={styles.attribute_unit}>
|
|
||||||
<span>{pulses.length ?? 0}</span>
|
|
||||||
<label>Key Pulses</label>
|
|
||||||
</div>
|
|
||||||
<div className={styles.attribute_unit}>
|
|
||||||
<label>Total Duration</label>
|
|
||||||
<span>{duration.toFixed(2)} s</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.pulses}>
|
<div className={styles.pulses}>
|
||||||
<ScrollArea enableY>
|
<ScrollArea enableY></ScrollArea>
|
||||||
<div className={styles.pulse_cards}>
|
|
||||||
{pulses.length === 0 && <div className="empty_prompt">No key pulses.</div>}
|
|
||||||
</div>
|
|
||||||
</ScrollArea>
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user