diff --git a/apps/frontend/src/globals.css b/apps/frontend/src/globals.css index fbc756a6..adb6ef8b 100644 --- a/apps/frontend/src/globals.css +++ b/apps/frontend/src/globals.css @@ -3,17 +3,24 @@ body { } /* start lynith's native experience (tm) */ -* { +* { -webkit-user-select: none; user-select: none; -webkit-user-drag: none; user-drag: none; - cursor: default !important; + cursor: default !important; animation-duration: 150ms !important; } -*:hover { - cursor: default !important; +*:hover { + cursor: default !important; +} + +img { + user-select: none; + -webkit-user-drag: none; + user-drag: none; + pointer-events: none; } /* end lynith's native experience (tm) */ diff --git a/apps/frontend/src/ui/components/SettingsRow.tsx b/apps/frontend/src/ui/components/SettingsRow.tsx index 1062d4ff..cbc4f58c 100644 --- a/apps/frontend/src/ui/components/SettingsRow.tsx +++ b/apps/frontend/src/ui/components/SettingsRow.tsx @@ -4,14 +4,17 @@ type SettingsRowProps = ParentProps & { title: JSX.Element; description: JSX.Element; icon: JSX.Element; - onClick?: () => any; + disabled?: boolean; }; function SettingsRow(props: SettingsRowProps) { return (
{props.icon} @@ -22,7 +25,7 @@ function SettingsRow(props: SettingsRowProps) {

{props.description}

-
+
{props.children}
diff --git a/apps/frontend/src/ui/components/base/Button.module.scss b/apps/frontend/src/ui/components/base/Button.module.scss index 7344d5da..7be37c94 100644 --- a/apps/frontend/src/ui/components/base/Button.module.scss +++ b/apps/frontend/src/ui/components/base/Button.module.scss @@ -1,26 +1,19 @@ .button { @apply py-1.5 px-3 rounded-lg flex flex-row justify-center items-center gap-x-2.5; - &:disabled { - @apply border-op-1! bg-opacity-10! text-op-10! pointer-events-none; - - svg { - @apply fill-op-10! stroke-op-10!; - } - } - &__primary { @apply text-onbrand hover:text-onbrand-hover active:text-onbrand-pressed disabled:text-onbrand-disabled; @apply bg-brand hover:bg-brand-hover active:bg-brand-pressed disabled:bg-brand-disabled; } &__secondary, &__iconSecondary { - @apply border border-gray-05; + @apply border box-border border-gray-05; @apply text-fg-primary hover:text-fg-primary-hover active:text-fg-primary-pressed disabled:text-fg-primary-disabled; @apply bg-component-bg hover:bg-component-bg-hover active:bg-component-bg-pressed disabled:bg-component-bg-disabled; } &__danger, &__iconDanger { + @apply text-onbrand hover:text-onbrand-hover active:text-onbrand-pressed disabled:text-onbrand-disabled; @apply bg-danger hover:bg-danger-hover active:bg-danger-pressed disabled:bg-danger-disabled; } @@ -62,6 +55,14 @@ @apply stroke-fg-primary-disabled; } } + + &:disabled { + @apply border-opacity-1! bg-opacity-10! text-opacity-10! pointer-events-none; + + svg { + @apply fill-opacity-10! stroke-opacity-10!; + } + } } .icon > * { diff --git a/apps/frontend/src/ui/pages/Home.tsx b/apps/frontend/src/ui/pages/Home.tsx index 2596bc06..35a1404d 100644 --- a/apps/frontend/src/ui/pages/Home.tsx +++ b/apps/frontend/src/ui/pages/Home.tsx @@ -1,18 +1,14 @@ import { ChevronRightIcon, - DotsVerticalIcon, - EyeIcon, PlayIcon, PlusIcon, SearchMdIcon, } from '@untitled-theme/icons-solid'; -import type { Accessor, Setter } from 'solid-js'; import { For, Show, createEffect, createSignal } from 'solid-js'; import { useNavigate } from '@solidjs/router'; import { mergeRefs } from '@solid-primitives/refs'; import image from '../../assets/images/header.png'; -import ContextMenu from '../components/overlay/ContextMenu'; import Button from '../components/base/Button'; import TextField from '../components/base/TextField'; import ClusterRoot from './cluster/ClusterRoot'; @@ -276,43 +272,8 @@ function Banner(props: BannerProps) { ); } -interface ClusterCardContextMenuProps { - pos: Accessor<{ x: number; y: number }>; - contextMenuVisible: Accessor; - setContextMenuVisible: Setter; - id: string; -} - -function ClusterCardContextMenu(props: ClusterCardContextMenuProps) { - const navigate = useNavigate(); - - return ( - - } - text="View" - onClick={() => ClusterRoot.open(navigate, props.id)} - /> - - - - } - text="Launch" - onClick={() => {}} - /> - - ); -} - function ClusterCard(props: Cluster) { const navigate = useNavigate(); - const [pos, setPos] = createSignal({ x: 0, y: 0 }); - const [contextMenuVisible, setContextMenuVisible] = createSignal(false); // const sortable = createSortable(props.uuid, props); let ref!: HTMLDivElement; @@ -320,22 +281,10 @@ function ClusterCard(props: Cluster) { navigate(`/clusters/?id=${props.uuid}`); } - function openContextMenu(e: MouseEvent) { - e.preventDefault(); - e.stopPropagation(); - setContextMenuVisible(!contextMenuVisible()); - } - - function onMouseDown(e: MouseEvent) { - setPos({ x: e.clientX, y: e.clientY }); - } - return ( <>
openClusterPage(e)} - onMouseDown={e => onMouseDown(e)} - onContextMenu={e => openContextMenu(e)} ref={mergeRefs( ref, // sortable @@ -353,9 +302,9 @@ function ClusterCard(props: Cluster) { />
-
-
-

{props.meta.name}

+
+
+

{props.meta.name}

{upperFirst(props.meta.loader)} {' '} @@ -364,18 +313,18 @@ function ClusterCard(props: Cluster) { {props.instance.mods && `• ${props.mods} mods`} */}

-
- - ); } diff --git a/apps/frontend/src/ui/pages/cluster/ClusterOverview.tsx b/apps/frontend/src/ui/pages/cluster/ClusterOverview.tsx index d8e1f4ba..af8efe0e 100644 --- a/apps/frontend/src/ui/pages/cluster/ClusterOverview.tsx +++ b/apps/frontend/src/ui/pages/cluster/ClusterOverview.tsx @@ -1,35 +1,184 @@ -import { Edit02Icon, ImagePlusIcon, PlayIcon, Save01Icon, Share07Icon, Trash01Icon } from '@untitled-theme/icons-solid'; -import { Show, createSignal, untrack } from 'solid-js'; +import { Edit02Icon, FolderIcon, ImagePlusIcon, LinkExternal01Icon, PlayIcon, Save01Icon, Share07Icon, Trash01Icon } from '@untitled-theme/icons-solid'; +import { type Accessor, type Setter, Show, createSignal, untrack } from 'solid-js'; import { useBeforeLeave, useNavigate } from '@solidjs/router'; import * as dialog from '@tauri-apps/plugin-dialog'; +import { open } from '@tauri-apps/plugin-shell'; import ClusterCover from '../../components/game/ClusterCover'; import LoaderIcon from '../../components/game/LoaderIcon'; import Button from '../../components/base/Button'; +import ClusterRoot from './ClusterRoot'; import ScrollableContainer from '~ui/components/ScrollableContainer'; import Sidebar from '~ui/components/Sidebar'; import type { Cluster } from '~bindings'; -import { upperFirst } from '~utils/primitives'; +import { secondsToWords, upperFirst } from '~utils/primitives'; import useClusterContext from '~ui/hooks/useCluster'; import Modal from '~ui/components/overlay/Modal'; import { bridge } from '~imports'; -import { tryResult } from '~ui/hooks/useCommand'; import TextField from '~ui/components/base/TextField'; +import SettingsRow from '~ui/components/SettingsRow'; +import useSettingsContext from '~ui/hooks/useSettings'; +import joinPath from '~utils/helpers'; function ClusterOverview() { + const settings = useSettingsContext(); const [cluster, { refetch }] = useClusterContext(); const [deleteVisible, setDeleteVisible] = createSignal(false); + const [saveVisible, setSaveVisible] = createSignal(false); + + const [editMode, setEditMode] = createSignal(false); + const [newName, setNewName] = createSignal(''); + const [newCover, setNewCover] = createSignal(''); + const navigate = useNavigate(); + useBeforeLeave((e) => { + if (editMode() && madeChanges()) { + e.preventDefault(); + setSaveVisible(true); + } + }); + + function getPath() { + const clusterPath = cluster()?.path; + const configDir = settings.config_dir; + + if (typeof clusterPath !== 'string' || typeof configDir !== 'string') + return ''; + + return joinPath(configDir, 'clusters', clusterPath); + } + + function openPath() { + open(getPath()); + } + async function deleteCluster() { await bridge.commands.removeCluster(cluster()!.uuid); navigate('/'); } + function madeChanges() { + const name = untrack(() => newName()); + const cover = untrack(() => newCover()); + const c = untrack(() => cluster()); + + if (!c) + return false; + + const changedName = (name.length > 0 && name !== c.meta.name); + const changedCover = (cover.length > 0 && (cover !== c.meta.icon_url || cover !== c.meta.icon)); + + return changedName || changedCover; + } + + function toggleEditMode() { + const next = !untrack(() => editMode()); + + if (next === false && madeChanges()) { + setSaveVisible(true); + return; + } + + setEditMode(next); + } + + async function pushEdits() { + const c = untrack(() => cluster()); + + if (!c) + return; + + await bridge.commands.editCluster( + c.uuid, + newName().length > 0 ? newName() : null, + newCover().length > 0 ? newCover() : c.meta.icon_url || c.meta.icon || null, + ); + + refetch(); + } + + function dontSave() { + setSaveVisible(false); + setEditMode(false); + } + + function save() { + pushEdits(); + setSaveVisible(false); + setEditMode(false); + } + return (

Overview

- setDeleteVisible(true)} /> + + + Folders and Files + } + disabled={editMode()} + children={( +
- setModalVisible(false)} - />, -