Skip to content

Commit

Permalink
Merge pull request #5 from PetrIvan/feature/delete-all-chords
Browse files Browse the repository at this point in the history
Added an option to delete all chords
  • Loading branch information
PetrIvan authored Feb 18, 2024
2 parents f2e2123 + c21edc6 commit 80733f2
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 18 deletions.
31 changes: 31 additions & 0 deletions src/components/timeline/delete_all_dropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from "react";
import { useStore } from "@/state/use_store";

interface Props {
dropdownRef: React.RefObject<HTMLDivElement>;
setIsOpen: (isOpen: boolean) => void;
}

export default function DeleteAllDropdown({ dropdownRef, setIsOpen }: Props) {
const [clearChords] = useStore((state) => [state.clearChords]);

return (
<div
className="absolute z-[15] top-full mt-[0.5dvw] bg-zinc-950 rounded-[0.5dvw] flex flex-row items-center justify-between p-[1dvw] shadow-lg shadow-zinc-950"
ref={dropdownRef}
>
<p className="w-full select-none text-[2.5dvh] mr-[1dvw]">
Delete all chords?
</p>
<button
className="bg-zinc-800 rounded-[0.5dvw] p-[0.5dvw] hover:bg-zinc-900"
onClick={() => {
clearChords();
setIsOpen(false);
}}
>
Confirm
</button>
</div>
);
}
64 changes: 48 additions & 16 deletions src/components/timeline/timeline_controls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
} from "@/playback/player";

import SettingsDropdown from "./settings_dropdown";
import DeleteAllDropdown from "./delete_all_dropdown";
import { tokenToChord } from "@/data/token_to_chord";

interface Props {
Expand Down Expand Up @@ -233,26 +234,44 @@ export default function TimelineControls({
setBpm(bpm);
}, [bpm]);

/* Playback settings */
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const isDropdownOpenRef = useRef(isDropdownOpen);
/* Dropdowns */
const [isPlaybackSettingsOpen, setIsPlaybackSettingsOpen] = useState(false);
const isPlaybackSettingsOpenRef = useRef(isPlaybackSettingsOpen);

const dropdownRef = useRef<HTMLDivElement>(null);
const openDropdownButtonRef = useRef<HTMLButtonElement>(null);
const playbackSettingsRef = useRef<HTMLDivElement>(null);
const openPlaybackSettingsButtonRef = useRef<HTMLButtonElement>(null);

const [isDeleteAllOpen, setIsDeleteAllOpen] = useState(false);
const isDeleteAllOpenRef = useRef(isDeleteAllOpen);

const deleteAllRef = useRef<HTMLDivElement>(null);
const openDeleteAllButtonRef = useRef<HTMLButtonElement>(null);

useEffect(() => {
isDropdownOpenRef.current = isDropdownOpen;
}, [isDropdownOpen]);
isPlaybackSettingsOpenRef.current = isPlaybackSettingsOpen;
}, [isPlaybackSettingsOpen]);

useEffect(() => {
isDeleteAllOpenRef.current = isDeleteAllOpen;
}, [isDeleteAllOpen]);

useEffect(() => {
// Hide the dropdown on click outside
const handleClickOutside = (e: MouseEvent) => {
if (
isDropdownOpenRef.current &&
!openDropdownButtonRef.current?.contains(e.target as Node) &&
!dropdownRef.current?.contains(e.target as Node)
isPlaybackSettingsOpenRef.current &&
!openPlaybackSettingsButtonRef.current?.contains(e.target as Node) &&
!playbackSettingsRef.current?.contains(e.target as Node)
) {
setIsDropdownOpen(false);
setIsPlaybackSettingsOpen(false);
}

if (
isDeleteAllOpenRef.current &&
!openDeleteAllButtonRef.current?.contains(e.target as Node) &&
!deleteAllRef.current?.contains(e.target as Node)
) {
setIsDeleteAllOpen(false);
}
};

Expand Down Expand Up @@ -289,14 +308,16 @@ export default function TimelineControls({
<button
className="grow select-none filter active:brightness-90 flex flex-col justify-center items-center"
title="Playback settings"
onClick={() => setIsDropdownOpen(!isDropdownOpen)}
ref={openDropdownButtonRef}
onClick={() => setIsPlaybackSettingsOpen(!isPlaybackSettingsOpen)}
ref={openPlaybackSettingsButtonRef}
>
<img src="/settings.svg" alt="Settings" className="h-full w-full" />
</button>
{isDropdownOpen && <SettingsDropdown dropdownRef={dropdownRef} />}
{isPlaybackSettingsOpen && (
<SettingsDropdown dropdownRef={playbackSettingsRef} />
)}
</div>
<div className="bg-zinc-950 rounded-t-[0.5dvw] grow-[9] flex flex-row justify-evenly p-[2dvh]">
<div className="relative bg-zinc-950 rounded-t-[0.5dvw] grow-[9] flex flex-row justify-evenly p-[2dvh]">
<button
className="grow select-none filter active:brightness-90 disabled:brightness-75 flex flex-col justify-center items-center"
disabled={stateWindowIndex <= 0}
Expand All @@ -316,8 +337,13 @@ export default function TimelineControls({
<button
className="grow select-none filter active:brightness-90 disabled:brightness-75 flex flex-col justify-center items-center"
disabled={selectedChord === -1}
title="Delete chord (Del)"
title="Delete chord (Del)/right click to delete all"
onClick={() => deleteChord()}
onContextMenu={(e) => {
e.preventDefault();
setIsDeleteAllOpen(!isDeleteAllOpen);
}}
ref={openDeleteAllButtonRef}
>
<img src="/trash.svg" alt="Delete" className="h-full w-full" />
</button>
Expand All @@ -328,6 +354,12 @@ export default function TimelineControls({
>
<img src="/plus.svg" alt="Add" className="h-full w-full" />
</button>
{isDeleteAllOpen && (
<DeleteAllDropdown
dropdownRef={deleteAllRef}
setIsOpen={setIsDeleteAllOpen}
/>
)}
</div>
</div>
);
Expand Down
8 changes: 8 additions & 0 deletions src/components/timeline/timeline_editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export default function TimelineEditor() {
setZoom,
setPlayheadPosition,
stateWindow,
initializeStateWindow,
] = useStore(
(state) => [
state.signature,
Expand All @@ -29,10 +30,17 @@ export default function TimelineEditor() {
state.setZoom,
state.setPlayheadPosition,
state.stateWindow,
state.initializeStateWindow,
],
shallow
);

/* State window logic */
useEffect(() => {
// Once the app starts, initialize the state window
initializeStateWindow();
}, []);

/* Timeline position logic */
const timelinePositionRef = useRef(timelinePosition);
const timelineRef = useRef<HTMLDivElement>(null);
Expand Down
15 changes: 13 additions & 2 deletions src/state/use_store.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ interface StoreState {
}[];
deleteChord: () => void;
replaceChord: (token: number, variant: number) => void;
clearChords: () => void;

// Timeline
resizingChord: boolean; // Whether the user is resizing any chord
Expand All @@ -65,6 +66,7 @@ interface StoreState {
setStateWindowIndex: (stateWindowIndex: number) => void;
saveStateWindow: () => void;
loadStateWindow: (index: number) => void;
initializeStateWindow: () => void;
undo: () => void;
redo: () => void;

Expand Down Expand Up @@ -203,6 +205,11 @@ export const useStore = createWithEqualityFn<StoreState>()(
chords[get().selectedChord].variant = variant;
get().setChords(chords);
},
clearChords: () => {
if (get().chords.length === 0) return;
get().setSelectedChord(-1, true);
get().setChords([]);
},

// Timeline
resizingChord: false,
Expand Down Expand Up @@ -234,8 +241,8 @@ export const useStore = createWithEqualityFn<StoreState>()(
saveStateWindow: () => {
const currStateWindow = [...get().stateWindow];

// Prevent saving if there are no chords or if the user is resizing a chord
if (get().chords.length === 0 || get().resizingChord) return;
// Prevent saving if the user is resizing a chord
if (get().resizingChord) return;

// Whether to overwrite the future states (when we change something after undoing)
if (get().stateWindowIndex < get().stateWindow.length - 1) {
Expand Down Expand Up @@ -269,6 +276,10 @@ export const useStore = createWithEqualityFn<StoreState>()(
get().setSignature(data.signature, true);
get().setSelectedChord(data.selected, true);
},
initializeStateWindow: () => {
// Save the initial state so that we can undo to it
if (get().stateWindow.length === 0) get().saveStateWindow();
},
undo: () => {
if (get().stateWindowIndex > 0) {
get().loadStateWindow(get().stateWindowIndex - 1);
Expand Down

0 comments on commit 80733f2

Please sign in to comment.