feat: Add context button with "open in" links in title pages & modals

This commit is contained in:
Aleksi Lassila
2023-08-15 02:15:51 +03:00
parent 383d43d52c
commit 95dd5e070c
17 changed files with 220 additions and 82 deletions

View File

@@ -4,22 +4,26 @@
export let heading = '';
export let disabled = false;
export let position: 'absolute' | 'fixed' = 'fixed';
let anchored = position === 'absolute';
const id = Symbol();
export let id = Symbol();
let menu: HTMLDivElement;
let position = { x: 0, y: 0 };
let fixedPosition = { x: 0, y: 0 };
function close() {
contextMenu.hide();
}
function handleOpen(event: MouseEvent) {
if (disabled) return;
export function handleOpen(event: MouseEvent) {
if (disabled || (anchored && $contextMenu === id)) return; // Clicking button will close menu
position = { x: event.clientX, y: event.clientY };
fixedPosition = { x: event.clientX, y: event.clientY };
contextMenu.show(id);
event.preventDefault();
event.stopPropagation();
}
function handleClickOutside(event: MouseEvent) {
@@ -50,23 +54,25 @@
<!-- <svelte:body bind:this={body} /> -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div on:contextmenu|preventDefault={handleOpen}>
<div on:contextmenu|preventDefault={handleOpen} on:click={(e) => anchored && e.stopPropagation()}>
<slot />
</div>
{#if $contextMenu === id}
{#key position}
{#key fixedPosition}
<div
class="fixed z-50 px-1 py-1 bg-zinc-800 bg-opacity-50 rounded-lg backdrop-blur-xl flex flex-col"
style="left: {position.x}px; top: {position.y}px;"
class={`${position} z-50 my-2 px-1 py-1 bg-zinc-800 bg-opacity-50 rounded-lg backdrop-blur-xl flex flex-col w-max`}
style={position === 'fixed'
? `left: ${fixedPosition.x}px; top: ${fixedPosition.y}px;`
: 'left: 0px;'}
bind:this={menu}
in:fly|global={{ y: 5, duration: 100, delay: 100 }}
in:fly|global={{ y: 5, duration: 100, delay: anchored ? 0 : 100 }}
out:fly|global={{ y: 5, duration: 100 }}
>
<slot name="title">
{#if heading}
<h2
class="text-xs text-zinc-200 opacity-60 tracking-wide font-semibold px-3 py-1 line-clamp-1"
class="text-xs text-zinc-200 opacity-60 tracking-wide font-semibold px-3 py-1 line-clamp-1 text-left"
>
{heading}
</h2>

View File

@@ -1,11 +1,11 @@
import { writable } from 'svelte/store';
function createContextMenu() {
const visibleItem = writable<Symbol | null>(null);
const visibleItem = writable<symbol | null>(null);
return {
subscribe: visibleItem.subscribe,
show: (item: Symbol) => {
show: (item: symbol) => {
visibleItem.set(item);
},
hide: () => {

View File

@@ -0,0 +1,25 @@
<script lang="ts">
import ContextMenu from './ContextMenu.svelte';
import { contextMenu } from '../ContextMenu/ContextMenu';
import Button from '../Button.svelte';
import { DotsVertical } from 'radix-icons-svelte';
export let heading = '';
export let contextMenuId = Symbol();
function handleToggleVisibility() {
if ($contextMenu === contextMenuId) contextMenu.hide();
else contextMenu.show(contextMenuId);
}
</script>
<div class="relative">
<ContextMenu position="absolute" {heading} id={contextMenuId}>
<slot name="menu" slot="menu" />
<Button slim on:click={handleToggleVisibility}>
<DotsVertical size={24} />
</Button>
</ContextMenu>
</div>

View File

@@ -0,0 +1 @@
<div class="bg-zinc-200 bg-opacity-20 h-[1.5px] mx-3 my-1 rounded-full" />

View File

@@ -7,9 +7,9 @@
<button
on:click
class={classNames(
'text-sm font-medium px-3 py-1 hover:bg-zinc-200 hover:bg-opacity-10 rounded-md text-left cursor-default',
'text-sm font-medium tracking-wide px-3 py-1 hover:bg-zinc-200 hover:bg-opacity-10 rounded-md text-left cursor-default',
{
'opacity-80 pointer-events-none': disabled
'opacity-75 pointer-events-none': disabled
}
)}
>

View File

@@ -0,0 +1,82 @@
<script lang="ts">
import {
PUBLIC_JELLYFIN_URL,
PUBLIC_RADARR_BASE_URL,
PUBLIC_SONARR_BASE_URL
} from '$env/static/public';
import { setJellyfinItemUnwatched, setJellyfinItemWatched } from '$lib/apis/jellyfin/jellyfinApi';
import { library, type LibraryItemStore } from '$lib/stores/library.store';
import type { TitleType } from '$lib/types';
import ContextMenuDivider from './ContextMenuDivider.svelte';
import ContextMenuItem from './ContextMenuItem.svelte';
export let itemStore: LibraryItemStore;
export let type: TitleType;
export let tmdbId: number;
let watched = false;
itemStore.subscribe((i) => {
if (i.item?.jellyfinItem) {
watched =
i.item.jellyfinItem.UserData?.Played !== undefined
? i.item.jellyfinItem.UserData?.Played
: watched;
}
});
function handleSetWatched() {
if ($itemStore.item?.jellyfinId) {
watched = true;
setJellyfinItemWatched($itemStore.item?.jellyfinId).finally(() => library.refreshIn(3000));
}
}
function handleSetUnwatched() {
if ($itemStore.item?.jellyfinId) {
watched = false;
setJellyfinItemUnwatched($itemStore.item?.jellyfinId).finally(() => library.refreshIn(3000));
}
}
function handleOpenInJellyfin() {
window.open(
PUBLIC_JELLYFIN_URL + '/web/index.html#!/details?id=' + $itemStore.item?.jellyfinItem?.Id
);
}
</script>
{#if $itemStore.item}
<ContextMenuItem on:click={handleSetWatched} disabled={!$itemStore.item?.jellyfinId || watched}>
Mark as watched
</ContextMenuItem>
<ContextMenuItem
on:click={handleSetUnwatched}
disabled={!$itemStore.item?.jellyfinId || !watched}
>
Mark as unwatched
</ContextMenuItem>
<ContextMenuDivider />
<ContextMenuItem disabled={!$itemStore.item.jellyfinItem} on:click={handleOpenInJellyfin}>
Open in Jellyfin
</ContextMenuItem>
{#if $itemStore.item.type === 'movie'}
<ContextMenuItem
disabled={!$itemStore.item.radarrMovie}
on:click={() =>
window.open(PUBLIC_RADARR_BASE_URL + '/movie/' + $itemStore.item?.radarrMovie?.tmdbId)}
>
Open in Radarr
</ContextMenuItem>
{:else}
<ContextMenuItem
disabled={!$itemStore.item.sonarrSeries}
on:click={() =>
window.open(PUBLIC_SONARR_BASE_URL + '/series/' + $itemStore.item?.sonarrSeries?.titleSlug)}
>
Open in Sonarr
</ContextMenuItem>
{/if}
{/if}
<ContextMenuItem on:click={() => window.open(`https://www.themoviedb.org/${type}/${tmdbId}`)}>
Open in TMDB
</ContextMenuItem>