epic context menu

This commit is contained in:
LynithDev 2024-03-21 20:02:32 +01:00
parent d300366018
commit ad61294642
5 changed files with 168 additions and 56 deletions

View file

@ -35,9 +35,15 @@ function WindowFrame() {
});
return (
<div data-tauri-drag-region class="flex flex-row justify-end items-center w-screen h-8 bg-secondary gap-0.5 pr-0.5">
<TitlebarButton icon={MinusIcon} onClick={() => minimize()} />
<TitlebarButton icon={XCloseIcon} onClick={() => setModalVisible(true)} danger />
<div data-tauri-drag-region class="flex flex-row justify-between items-center w-screen h-8 bg-secondary gap-0.5 pr-0.5">
<div class="flex flex-row items-center">
{/* Empty */}
</div>
<div class="flex flex-row items-center justify-end">
<TitlebarButton icon={MinusIcon} onClick={() => minimize()} />
<TitlebarButton icon={XCloseIcon} onClick={() => setModalVisible(true)} danger />
</div>
<Modal
title="Close OneLauncher?"

View file

@ -0,0 +1,67 @@
import { type Accessor, type JSX, createEffect, createSignal, onMount } from 'solid-js';
import Popup from './Popup';
type ContextMenuProps = {
pos: Accessor<{ x: number; y: number }>;
} & Popup.PopupProps;
function ContextMenu(props: ContextMenuProps) {
const [pos, setPos] = createSignal({ x: 0, y: 0 });
const [ref, setRef] = createSignal<HTMLDivElement>();
createEffect(() => {
if (ref() === undefined)
return;
const rect = ref()!.getBoundingClientRect();
let x = props.pos().x;
let y = props.pos().y;
if (x + rect.width > window.innerWidth)
x = window.innerWidth - rect.width - 10;
if (y + rect.height > window.innerHeight)
y = window.innerHeight - rect.height - 10;
setPos({ x, y });
});
return (
<Popup
style={{ top: `${pos().y}px`, left: `${pos().x}px` }}
visible={props.visible}
setVisible={props.setVisible}
>
<div ref={setRef} class="bg-secondary rounded-xl border border-gray-0.10 p-1 shadow-lg shadow-black/50">
<div class="flex flex-col gap-y-1 text-fg-primary">
{props.children}
</div>
</div>
</Popup>
);
}
ContextMenu.Seperator = function () {
return <div class="border-b mx-1 border-gray-0.05" />;
};
interface ContextMenuRowProps {
icon: JSX.Element;
text: string;
onClick?: (e: MouseEvent) => any;
}
ContextMenu.Row = function (props: ContextMenuRowProps) {
return (
<div
onClick={e => props.onClick?.(e)}
class="flex items-center gap-x-2 m-px px-1.5 py-0.5 rounded-mdlg hover:bg-gray-0.05 active:bg-gray-0.10 [&>svg]:w-[18px]"
>
{props.icon}
{props.text}
</div>
);
};
export default ContextMenu;

View file

@ -1,13 +1,12 @@
import type {
Accessor,
ParentProps,
Setter,
} from 'solid-js';
import {
Show,
createEffect,
createSignal,
} from 'solid-js';
createSignal } from 'solid-js';
import type {
Accessor,
JSX,
ParentProps,
Setter } from 'solid-js';
import { Portal } from 'solid-js/web';
// eslint-disable-next-line ts/no-namespace
@ -16,6 +15,7 @@ declare namespace Popup {
visible: Accessor<boolean>;
setVisible: Setter<boolean>;
class?: string;
style?: JSX.CSSProperties | string;
mount?: Node;
}
}
@ -54,7 +54,7 @@ function Popup(props: Popup.PopupProps) {
return (
<Show when={localVisible()}>
<Portal mount={props.mount || document.body}>
<div ref={popupRef} class={`absolute z-[1000] ${animate()} ${props.class || ''}`}>
<div ref={popupRef} style={props.style || ''} class={`absolute z-[1000] ${animate()} ${props.class || ''}`}>
{props.children}
</div>
</Portal>

View file

@ -2,11 +2,13 @@ import {
CheckIcon,
DotsVerticalIcon,
Download01Icon,
EyeIcon,
PlayIcon,
PlusIcon,
SearchMdIcon,
} from '@untitled-theme/icons-solid';
import { For, createSignal } from 'solid-js';
import type { Accessor, Setter } from 'solid-js';
import { For, createSignal, untrack } from 'solid-js';
import { DragDropProvider, DragDropSensors, SortableProvider, closestCenter, createSortable } from '@thisbeyond/solid-dnd';
import type { DragEventHandler, Id } from '@thisbeyond/solid-dnd';
import { useNavigate } from '@solidjs/router';
@ -17,6 +19,7 @@ import Tag from '../components/base/Tag';
import TextField from '../components/base/TextField';
import defaultCover from '../../assets/images/default_instance_cover.jpg';
import Popup from '../components/overlay/Popup';
import ContextMenu from '../components/overlay/ContextMenu';
// TODO: Replace this into it's own component
function OneConfigLogo() {
@ -81,6 +84,40 @@ declare module 'solid-js' {
}
}
interface InstanceCardContextMenuProps {
pos: Accessor<{ x: number; y: number }>;
contextMenuVisible: Accessor<boolean>;
setContextMenuVisible: Setter<boolean>;
id: string;
}
function InstanceCardContextMenu(props: InstanceCardContextMenuProps) {
const navigate = useNavigate();
return (
<ContextMenu
pos={props.pos}
visible={props.contextMenuVisible}
setVisible={props.setContextMenuVisible}
>
<ContextMenu.Row
icon={<EyeIcon />}
text="View"
onClick={() => {
navigate(`/instances/?id=${props.id}`);
}}
/>
<ContextMenu.Seperator />
<ContextMenu.Row
icon={<PlayIcon />}
text="Launch"
/>
</ContextMenu>
);
}
function InstanceCard(props: InstanceCardProps) {
const navigate = useNavigate();
const [pos, setPos] = createSignal({ x: 0, y: 0 });
@ -89,7 +126,6 @@ function InstanceCard(props: InstanceCardProps) {
const id = () => props.elementId as Id;
let ref!: HTMLDivElement;
// eslint-disable-next-line solid/reactivity
const sortable = createSortable(id());
function didDrag() {
@ -131,51 +167,50 @@ function InstanceCard(props: InstanceCardProps) {
}
return (
<div
use:sortable={sortable}
onClick={e => openInstancePage(e)}
onMouseDown={e => onMouseDown(e)}
onMouseUp={e => onMouseUp(e)}
onContextMenu={e => openContextMenu(e)}
ref={ref}
class="relative h-[152px] group flex flex-col rounded-xl bg-component-bg hover:bg-component-bg-hover active:bg-component-bg-pressed border border-gray-0.05"
>
<div class="flex-1 relative overflow-hidden rounded-t-xl">
<div
class="absolute h-full w-full group-hover:!scale-110 transition-transform"
style={{ '-webkit-transform': 'translateZ(0)' }} // WebKit starts flickering whenever we try scrolling. This is a 'fix'
>
<img
class={`object-cover h-full w-full ${props.cover ? '' : ' filter grayscale'}`}
src={props.cover || defaultCover}
/>
</div>
</div>
<div class="z-10 p-3 flex flex-row items-center justify-between">
<div class="flex flex-col gap-1.5">
<p class="h-4 font-semibold">{props.name}</p>
<p class="h-4 text-xs">
{props.clientType}
{' '}
{props.version}
{' '}
{props.mods && `• ${props.mods} mods`}
</p>
</div>
<Button onClick={e => openContextMenu(e)} styleType="icon" class="w-8 h-8">
<DotsVerticalIcon />
</Button>
</div>
<Popup visible={contextMenuVisible} setVisible={setContextMenuVisible}>
<div class="bg-secondary rounded-xl border border-gray-0.10 w-72 p-2 shadow-lg shadow-black/50">
<div class="flex flex-col gap-y-2 text-fg-primary">
<p>hello world</p>
<>
<div
use:sortable={sortable}
onClick={e => openInstancePage(e)}
onMouseDown={e => onMouseDown(e)}
onMouseUp={e => onMouseUp(e)}
onContextMenu={e => openContextMenu(e)}
ref={ref}
class="relative h-[152px] group flex flex-col rounded-xl bg-component-bg hover:bg-component-bg-hover active:bg-component-bg-pressed border border-gray-0.05"
>
<div class="flex-1 relative overflow-hidden rounded-t-xl">
<div
class="absolute h-full w-full group-hover:!scale-110 transition-transform"
style={{ '-webkit-transform': 'translateZ(0)' }}
>
<img
class={`object-cover h-full w-full ${props.cover ? '' : ' filter grayscale'}`}
src={props.cover || defaultCover}
/>
</div>
</div>
</Popup>
</div>
<div class="z-10 p-3 flex flex-row items-center justify-between">
<div class="flex flex-col gap-1.5">
<p class="h-4 font-semibold">{props.name}</p>
<p class="h-4 text-xs">
{props.clientType}
{' '}
{props.version}
{' '}
{props.mods && `• ${props.mods} mods`}
</p>
</div>
<Button onClick={e => openContextMenu(e)} styleType="icon" class="w-8 h-8">
<DotsVerticalIcon />
</Button>
</div>
</div>
<InstanceCardContextMenu
contextMenuVisible={contextMenuVisible}
setContextMenuVisible={setContextMenuVisible}
pos={pos}
id={props.id}
/>
</>
);
}

View file

@ -88,6 +88,10 @@ const config: Config = {
transparent: 'transparent',
},
borderRadius: {
mdlg: '0.438rem',
},
animation: {
'fade-in': 'fadeIn 150ms easeOutExpo forwards',
'fade-out': 'fadeOut 150ms easeOutExpo forwards',