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 10 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
6 changes: 5 additions & 1 deletion src/renderer/src/components/popover/Popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import "./styles.css";
import {
computePosition,
ComputePositionReturn,
Middleware,
offset,
OffsetOptions,
Placement,
Expand All @@ -20,6 +21,7 @@ export type Props = {
defaultProp?: boolean;
isOpen?: Accessor<boolean>;
onValueChange?: (newOpen: boolean) => void;
middlewares?: Middleware[];
};

export type Context = ReturnType<typeof useProviderValue>;
Expand Down Expand Up @@ -53,10 +55,12 @@ function useProviderValue(props: Props) {
return;
}

const localMiddlewares: Middleware[] = props.middlewares === undefined ? [] : props.middlewares;

computePosition(trigger, content, {
placement: props.placement,
strategy: "fixed",
middleware: [offset(props.offset)],
middleware: [offset(props.offset), ...localMiddlewares],
}).then(setPosition);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,70 +1,19 @@
import "../../../assets/css/song/song-context-menu.css";
import { Accessor, Component, createEffect, For, onCleanup, onMount, Show, Signal } from "solid-js";
import { Component, For } 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;
let menu: HTMLDivElement | undefined;

const windowContextMenu = (evt: MouseEvent) => {
const t = evt.target;

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

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

if (targetItem === menuParent) {
evt.stopPropagation();
return;
}

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(() => {
createEffect(() => {
const s = show();

if (s === false) {
window.removeEventListener("contextmenu", windowContextMenu);
return;
}

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

onCleanup(() => {
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">
<For each={props.children}>{(child) => child}</For>
</div>
<div class="z-30 rounded-lg bg-thick-material" ref={menu}>
<div class="flex flex-col gap-1 rounded-lg bg-thick-material p-2">
<For each={props.children}>{(child) => child}</For>
</div>
</Show>
</div>
);
};

Expand All @@ -75,13 +24,6 @@ export function ignoreClickInContextMenu(fn: (evt: MouseEvent) => any): (evt: Mo
const t = evt.target;

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

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

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

Expand Down
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 items-center justify-between rounded-md bg-thick-material text-left transition-colors duration-200 hover:bg-accent/20"
>
{props.children}
</button>
</Button>
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Song } from "../../../../../../@types";
import SongContextMenuItem from "../SongContextMenuItem";
import { PlusIcon } from "lucide-solid";
import { Component } from "solid-js";

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

const AddToPlaylist: Component<AddToPlaylistProps> = (props) => {
return (
<SongContextMenuItem
onClick={() => {
if (props.path !== undefined && props.path !== "") {
console.log("TODO: add " + props.path + " to playlist");
}
}}
>
<p>Add to Playlist</p>
<PlusIcon />
</SongContextMenuItem>
);
};

export default AddToPlaylist;
30 changes: 13 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,24 @@
import { Song } from "../../../../../../@types";
import SongContextMenuItem from "../SongContextMenuItem";
import { Component, createSignal, Show } from "solid-js";
import { ListStartIcon } from "lucide-solid";
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>
<ListStartIcon />
</SongContextMenuItem>
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import SongContextMenuItem from "../SongContextMenuItem";
import { DeleteIcon } from "lucide-solid";
import { Component } from "solid-js";
import { Song } from "src/@types";

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

const RemoveFromQueue: Component<RemoveFromQueueProps> = (props) => {
return (
<SongContextMenuItem
onClick={() => {
if (props.path !== undefined && props.path !== "") {
window.api.request("queue::removeSong", props.path);
}
}}
>
<p>Remove from queue</p>
<DeleteIcon />
</SongContextMenuItem>
);
};

export default RemoveFromQueue;
89 changes: 55 additions & 34 deletions src/renderer/src/components/song/song-item/SongItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ import SongHint from "../SongHint";
import SongImage from "../SongImage";
import { ignoreClickInContextMenu } from "../context-menu/SongContextMenu";
import { song as selectedSong } from "../song.utils";
import { flip, offset } from "@floating-ui/dom";
import Button from "@renderer/components/button/Button";
import Popover from "@renderer/components/popover/Popover";
import { EllipsisVerticalIcon } from "lucide-solid";
import { Component, createSignal, onMount } from "solid-js";
import { Portal } from "solid-js/web";

type SongItemProps = {
song: Song;
Expand All @@ -20,24 +25,13 @@ const SongItem: Component<SongItemProps> = ({
group,
onSelect,
song,
children,
draggable: isDraggable,
onDrop,
selectable,
children,
}) => {
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]);
showSignal[1](true);
};
const [localShow, setLocalShow] = createSignal(false);

onMount(() => {
if (!item) {
Expand All @@ -57,32 +51,59 @@ const SongItem: Component<SongItemProps> = ({
});

return (
<div
class="group relative isolate select-none rounded-md"
classList={{
"outline outline-2 outline-accent": selectedSong().path === song.path,
}}
data-active={selectedSong().path === song.path}
ref={item}
data-url={song.bg}
onContextMenu={showMenu}
<Popover
isOpen={localShow}
onValueChange={setLocalShow}
middlewares={[flip(), offset({ crossAxis: 30 })]}
placement="right"
offset={15}
>
<SongImage
class="absolute inset-0 z-[-1] h-full w-full rounded-md bg-cover bg-center bg-no-repeat opacity-30 group-hover:opacity-90"
<Portal>
<Popover.Overlay />
<Popover.Content
onClick={(e) => {
e.stopImmediatePropagation();
setLocalShow(false);
}}
>
{...children}
D0m1nos marked this conversation as resolved.
Show resolved Hide resolved
</Popover.Content>
</Portal>
<div
class="group relative isolate z-20 select-none rounded-md"
classList={{
"opacity-90": selectedSong().path === song.path,
"outline outline-2 outline-accent": selectedSong().path === song.path,
}}
src={song.bg}
group={group}
/>
data-active={selectedSong().path === song.path}
ref={item}
data-url={song.bg}
onContextMenu={() => setLocalShow(true)}
>
<SongImage
class="absolute inset-0 z-[-1] h-full w-full rounded-md bg-cover bg-center bg-no-repeat opacity-30 group-hover:opacity-90"
classList={{
"opacity-90": selectedSong().path === song.path,
}}
src={song.bg}
group={group}
/>

<div class="flex flex-row justify-between rounded-md bg-black/50">
<div class="z-20 flex min-h-[72px] flex-col justify-center overflow-hidden rounded-md p-3">
<h3 class="text-shadow text-[22px] font-extrabold leading-7 shadow-black/60">
{song.title}
</h3>
<p class="text-base text-subtext">{song.artist}</p>
</div>

<div class="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>
<p class="text-base text-subtext">{song.artist}</p>
<Popover.Trigger class="opacity-0 transition-opacity group-hover:opacity-100">
<Button variant={"ghost"} size={"icon"} class="z-50 mr-1 rounded-lg">
D0m1nos marked this conversation as resolved.
Show resolved Hide resolved
<EllipsisVerticalIcon />
</Button>
</Popover.Trigger>
</div>
</div>
</div>
</Popover>
);
};

Expand Down
Loading