Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reimplement context menu #84

Merged
merged 20 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 30 additions & 41 deletions src/renderer/src/components/song/context-menu/SongContextMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
import "../../../assets/css/song/song-context-menu.css";
import { Accessor, Component, createEffect, For, onCleanup, onMount, Show, Signal } from "solid-js";
import {
Component,
createEffect,
createSignal,
For,
onCleanup,
onMount,
Show,
Signal,
} from "solid-js";

type SongContextMenuProps = {
show: Signal<boolean>;
coords: Accessor<[number, number]>;
container: any;
children: any;
};

const SongContextMenu: Component<SongContextMenuProps> = (props) => {
const [show, setShow] = props.show;
const [pos, setPos] = createSignal<[number, number]>([0, 0]);
let menu: HTMLDivElement | undefined;

const windowContextMenu = (evt: MouseEvent) => {
Expand All @@ -19,21 +27,11 @@ const SongContextMenu: Component<SongContextMenuProps> = (props) => {
return;
}

const targetItem = t.closest(".song-item");
const menuParent = menu?.closest(".song-item");
const targetItem = t.closest(".group");

if (targetItem === menuParent) {
evt.stopPropagation();
return;
if (targetItem !== null) {
setPos([evt.offsetX + 30, evt.clientY - 52]);
}

setShow(false);
};

const calculatePosition = () => {
const c = props.coords();
menu?.style.setProperty("--x", `${Math.round(c[0])}px`);
menu?.style.setProperty("--y", `${Math.round(c[1])}px`);
};

onMount(() => {
Expand All @@ -45,22 +43,32 @@ const SongContextMenu: Component<SongContextMenuProps> = (props) => {
return;
}

calculatePosition();
window.addEventListener("click", () => setShow(false), { once: true });
window.addEventListener(
"click",
() => {
setShow(false);
},
{ once: true },
);
window.addEventListener("contextmenu", windowContextMenu);
});
});

onCleanup(() => {
window.removeEventListener("click", () => setShow(false));
window.removeEventListener("click", () => {
setShow(false);
});
window.removeEventListener("contextmenu", windowContextMenu);
menu?.removeEventListener("click", (evt) => evt.stopPropagation());
});

return (
<Show when={show()}>
<div class="absolute z-50 overflow-hidden rounded-md bg-surface shadow-lg" ref={menu}>
<div class="bg-gradient-to-b from-black/30 to-transparent">
<div
class={"absolute z-30 rounded-lg bg-thick-material"}
style={{ top: pos()[1] + "px", left: pos()[0] + "px" }}
ref={menu}
>
<div class="flex flex-col gap-1 rounded-lg bg-thick-material p-2">
<For each={props.children}>{(child) => child}</For>
</div>
</div>
Expand All @@ -69,22 +77,3 @@ const SongContextMenu: Component<SongContextMenuProps> = (props) => {
};

export default SongContextMenu;

export function ignoreClickInContextMenu(fn: (evt: MouseEvent) => any): (evt: MouseEvent) => void {
return (evt: MouseEvent) => {
const t = evt.target;

if (!(t instanceof HTMLElement)) {
fn(evt);
return;
}

const menu = t.closest(".song-menu");

if (menu !== null) {
return;
}

fn(evt);
};
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import Button from "@renderer/components/button/Button";
import { Component, onCleanup } from "solid-js";

type SongContextMenuItemProps = {
Expand All @@ -18,12 +19,13 @@ const SongContextMenuItem: Component<SongContextMenuItemProps> = (props) => {
});

return (
<button
<Button
D0m1nos marked this conversation as resolved.
Show resolved Hide resolved
ref={divAccessor}
class="w-full px-4 py-2 text-left transition-colors duration-200 hover:bg-accent/20"
variant={"ghost"}
class="flex min-w-56 flex-row justify-between rounded-md bg-thick-material text-left transition-colors duration-200 hover:bg-accent/20"
>
{props.children}
</button>
</Button>
);
};

Expand Down
29 changes: 12 additions & 17 deletions src/renderer/src/components/song/context-menu/items/PlayNext.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,23 @@
import { Song } from "../../../../../../@types";
import SongContextMenuItem from "../SongContextMenuItem";
import { Component, createSignal, Show } from "solid-js";
import { Component } from "solid-js";

type SongPlayNextProps = {
path: Song["path"];
path: Song["path"] | undefined;
};

const PlayNext: Component<SongPlayNextProps> = (props) => {
const [show, setShow] = createSignal(false);

window.api.listen("queue::created", () => {
setShow(true);
});

window.api.listen("queue::destroyed", () => {
setShow(false);
});

return (
<Show when={show()}>
<SongContextMenuItem onClick={() => window.api.request("queue::playNext", props.path)}>
Play Next
</SongContextMenuItem>
</Show>
<SongContextMenuItem
onClick={() => {
if (props.path !== undefined && props.path !== "") {
window.api.request("queue::playNext", props.path);
}
}}
>
<p>Play next</p>
<i class="ri-menu-add-line"></i>
D0m1nos marked this conversation as resolved.
Show resolved Hide resolved
</SongContextMenuItem>
);
};

Expand Down
25 changes: 10 additions & 15 deletions src/renderer/src/components/song/song-item/SongItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ import { ResourceID, Song } from "../../../../../@types";
import draggable from "../../../lib/draggable/draggable";
import SongHint from "../SongHint";
import SongImage from "../SongImage";
import { ignoreClickInContextMenu } from "../context-menu/SongContextMenu";
import { song as selectedSong } from "../song.utils";
import { Component, createSignal, onMount } from "solid-js";
import { Component, onMount, Setter, Signal } from "solid-js";

type SongItemProps = {
song: Song;
Expand All @@ -14,28 +13,24 @@ type SongItemProps = {
draggable?: true;
onDrop?: (before: Element | null) => any;
children?: any;
showSignal: Signal<boolean>;
setSong: Setter<Song | undefined>;
};

const SongItem: Component<SongItemProps> = ({
group,
onSelect,
song,
children,
draggable: isDraggable,
onDrop,
selectable,
showSignal,
setSong,
}) => {
const showSignal = createSignal(false);
const [, setCoords] = createSignal<[number, number]>([0, 0], { equals: false });
let item: HTMLDivElement | undefined;

const showMenu = (evt: MouseEvent) => {
if (children === undefined) {
showSignal[1](false);
return;
}

setCoords([evt.clientX, evt.clientY]);
const showMenu = () => {
setSong(song);
showSignal[1](true);
};

Expand All @@ -45,7 +40,7 @@ const SongItem: Component<SongItemProps> = ({
}

draggable(item, {
onClick: ignoreClickInContextMenu(() => onSelect(song.path)),
onClick: () => onSelect(song.path),
onDrop: onDrop ?? (() => {}),
createHint: SongHint,
useOnlyAsOnClickBinder: !isDraggable || selectedSong().path === song.path,
Expand All @@ -58,7 +53,7 @@ const SongItem: Component<SongItemProps> = ({

return (
<div
class="group relative isolate select-none rounded-md"
class="group relative isolate z-20 select-none rounded-md"
classList={{
"outline outline-2 outline-accent": selectedSong().path === song.path,
}}
Expand All @@ -76,7 +71,7 @@ const SongItem: Component<SongItemProps> = ({
group={group}
/>

<div class="flex min-h-[72px] flex-col justify-center overflow-hidden rounded-md bg-black/50 p-3">
<div class="z-20 flex min-h-[72px] flex-col justify-center overflow-hidden rounded-md bg-black/50 p-3">
<h3 class="text-shadow text-[22px] font-extrabold leading-7 shadow-black/60">
{song.title}
</h3>
Expand Down
40 changes: 32 additions & 8 deletions src/renderer/src/components/song/song-list/SongList.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { Optional, Order, ResourceID, SongsQueryPayload, Tag } from "../../../../../@types";
import { Optional, Order, ResourceID, Song, SongsQueryPayload, Tag } from "../../../../../@types";
import { SearchQueryError } from "../../../../../main/lib/search-parser/@search-types";
import { namespace } from "../../../App";
import Impulse from "../../../lib/Impulse";
import { none, some } from "../../../lib/rust-like-utils-client/Optional";
import InfiniteScroller from "../../InfiniteScroller";
import SongContextMenu from "../context-menu/SongContextMenu";
import SongContextMenuItem from "../context-menu/SongContextMenuItem";
import PlayNext from "../context-menu/items/PlayNext";
import SongItem from "../song-item/SongItem";
import SongListSearch from "../song-list-search/SongListSearch";
import { songsSearch } from "./song-list.utils";
import { Component, createEffect, createSignal, onCleanup, onMount } from "solid-js";
import { Component, createEffect, createSignal, onCleanup, onMount, Show } from "solid-js";

export type SongViewProps = {
isAllSongs?: boolean;
Expand All @@ -23,6 +25,10 @@ const SongList: Component<SongViewProps> = (props) => {
const [order, setOrder] = createSignal<Order>({ option: "title", direction: "asc" });
const [count, setCount] = createSignal(0);

const showSignal = createSignal(false);
const [song, setSong] = createSignal<Song>();
const [queryCreated, setQueryCreated] = createSignal(false);

const [payload, setPayload] = createSignal<SongsQueryPayload>({
view: props,
order: order(),
Expand Down Expand Up @@ -68,6 +74,7 @@ const SongList: Component<SongViewProps> = (props) => {
startSong: songResource,
...payload(),
});
setQueryCreated(true);
};

const group = namespace.create(true);
Expand All @@ -88,15 +95,32 @@ const SongList: Component<SongViewProps> = (props) => {
reset={resetListing}
fallback={<div class="py-8 text-center text-text">No songs...</div>}
builder={(s) => (
<SongItem song={s} group={group} onSelect={createQueue}>
<PlayNext path={s.path} />
<button class="w-full px-4 py-2 text-left transition-colors duration-200 hover:bg-accent/20">
Add to playlist
</button>
</SongItem>
<div>
<SongItem
song={s}
group={group}
onSelect={createQueue}
showSignal={showSignal}
setSong={setSong}
></SongItem>
</div>
)}
/>
</div>
<SongContextMenu show={showSignal}>
<Show when={queryCreated() === true}>
<PlayNext path={song()?.path} />
</Show>

<SongContextMenuItem
onClick={() => {
console.log("todo");
}}
>
<p>Add to playlist</p>
<i class="ri-add-line"></i>
D0m1nos marked this conversation as resolved.
Show resolved Hide resolved
</SongContextMenuItem>
</SongContextMenu>
</div>
);
};
Expand Down
30 changes: 25 additions & 5 deletions src/renderer/src/components/song/song-queue/SongQueue.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { namespace } from "../../../App";
import Impulse from "../../../lib/Impulse";
import scrollIfNeeded from "../../../lib/tungsten/scroll-if-needed";
import InfiniteScroller from "../../InfiniteScroller";
import SongContextMenu from "../context-menu/SongContextMenu";
import SongContextMenuItem from "../context-menu/SongContextMenuItem";
import SongItem from "../song-item/SongItem";
import { setSongQueueModalOpen } from "./song-queue.utils";
Expand All @@ -11,6 +12,8 @@ import { Component, createSignal, onCleanup, onMount } from "solid-js";

const SongQueue: Component = () => {
const [count, setCount] = createSignal(0);
const showSignal = createSignal(false);
const [song, setSong] = createSignal<Song>();
const resetListing = new Impulse();
const group = namespace.create(true);
let view: HTMLDivElement | undefined;
Expand Down Expand Up @@ -105,14 +108,31 @@ const SongQueue: Component = () => {
draggable={true}
onSelect={() => window.api.request("queue::play", s.path)}
onDrop={onDrop(s)}
>
<SongContextMenuItem onClick={() => window.api.request("queue::removeSong", s.path)}>
Remove from queue
</SongContextMenuItem>
</SongItem>
showSignal={showSignal}
setSong={setSong}
></SongItem>
)}
/>
</div>
<SongContextMenu show={showSignal}>
<SongContextMenuItem
onClick={() => {
console.log("todo");
}}
>
<p>Add to playlist</p>
<i class="ri-add-line"></i>
</SongContextMenuItem>

<SongContextMenuItem
onClick={() => {
window.api.request("queue::removeSong", song()?.path);
}}
>
<p>Remove from queue</p>
<i class="ri-delete-back-2-line"></i>
D0m1nos marked this conversation as resolved.
Show resolved Hide resolved
</SongContextMenuItem>
</SongContextMenu>
</div>
);
};
Expand Down