diff --git a/desktop/src/ui/components/base/Dropdown.tsx b/desktop/src/ui/components/base/Dropdown.tsx index b24d0eaa..95502f77 100644 --- a/desktop/src/ui/components/base/Dropdown.tsx +++ b/desktop/src/ui/components/base/Dropdown.tsx @@ -4,12 +4,18 @@ import Popup from '../overlay/Popup'; import Button from './Button'; import styles from './Dropdown.module.scss'; -export type DropdownProps = JSX.HTMLAttributes; +export type DropdownProps = Omit, 'onChange'> & { + onChange?: (selected: number) => any; + text?: string; + selected?: number; +}; function Dropdown(props: DropdownProps) { - const [split, rest] = splitProps(props, ['children', 'class']); + const [split, rest] = splitProps(props, ['children', 'class', 'text', 'onChange', 'selected']); const [visible, setVisible] = createSignal(false); - const [selected, setSelected] = createSignal(0); + + // eslint-disable-next-line solid/reactivity + const [selected, setSelected] = createSignal(split.selected || 0); let ref!: HTMLDivElement; @@ -18,6 +24,9 @@ function Dropdown(props: DropdownProps) { function select(index: number) { setSelected(index); setVisible(false); + + if (split.onChange) + split.onChange(selected()); } return ( @@ -29,8 +38,9 @@ function Dropdown(props: DropdownProps) { iconRight={} >
+ {split.text} {items()[selected()]}
diff --git a/desktop/src/ui/components/base/Tooltip.module.scss b/desktop/src/ui/components/base/Tooltip.module.scss new file mode 100644 index 00000000..0838be33 --- /dev/null +++ b/desktop/src/ui/components/base/Tooltip.module.scss @@ -0,0 +1,16 @@ +.tooltip { + @apply relative; + + &:hover::after { + @apply opacity-100; + } + + &::after { + content: attr(data-text); + @apply opacity-0 transition-opacity; + @apply absolute left-1/2 -translate-x-1/2 bottom-6; + @apply min-w-44 w-max max-w-52; + @apply backdrop-blur-lg rounded-md bg-black/60 p-2 text-center text-sm text-wrap text-white shadow shadow-black/30; + } + +} \ No newline at end of file diff --git a/desktop/src/ui/components/base/Tooltip.tsx b/desktop/src/ui/components/base/Tooltip.tsx new file mode 100644 index 00000000..c354081f --- /dev/null +++ b/desktop/src/ui/components/base/Tooltip.tsx @@ -0,0 +1,16 @@ +import { type JSX, splitProps } from 'solid-js'; +import styles from './Tooltip.module.scss'; + +export type TooltipProps = JSX.HTMLAttributes & { + text: string; +}; + +function Tooltip(props: TooltipProps) { + const [split, rest] = splitProps(props, ['class']); + + return ( +
+ ); +} + +export default Tooltip; diff --git a/desktop/src/ui/components/game/PlayerHead.tsx b/desktop/src/ui/components/game/PlayerHead.tsx index bdd27e76..0d395ee5 100644 --- a/desktop/src/ui/components/game/PlayerHead.tsx +++ b/desktop/src/ui/components/game/PlayerHead.tsx @@ -1,19 +1,38 @@ -import { createResource } from 'solid-js'; +import { Show, createSignal, splitProps } from 'solid-js'; import type { JSX } from 'solid-js'; import steveSrc from '../../../assets/images/steve.png'; -type PlayerHeadProps = JSX.IntrinsicElements['img'] & { uuid: string }; +type PlayerHeadProps = JSX.IntrinsicElements['img'] & { + uuid: string; + onError?: () => any; +}; -async function fetchHeadSrc(uuid: string) { - return `https://crafatar.com/avatars/${uuid}?size=32`; +function headSrc(uuid: string) { + return `https://crafatar.com/avatars/${uuid}?size=48`; } function PlayerHead(props: PlayerHeadProps) { - const [headSrc] = createResource(() => props.uuid, fetchHeadSrc, { - initialValue: steveSrc, - }); + const [split, rest] = splitProps(props, ['uuid', 'class']); + const [isLoading, setLoading] = createSignal(true); - return ; + return ( + <> + + + + setLoading(false)} + onError={() => props.onError && props.onError()} + class={`image-render-pixel ${isLoading() ? 'hidden' : ''} ${split.class}`} + {...rest} + /> + + ); } export default PlayerHead; diff --git a/desktop/src/ui/components/overlay/AccountPopup.tsx b/desktop/src/ui/components/overlay/AccountPopup.tsx index 5ae80d37..7815a015 100644 --- a/desktop/src/ui/components/overlay/AccountPopup.tsx +++ b/desktop/src/ui/components/overlay/AccountPopup.tsx @@ -1,4 +1,5 @@ import { LogOut01Icon, PlusIcon, Settings01Icon } from '@untitled-theme/icons-solid'; +import { useNavigate } from '@solidjs/router'; import Button from '../base/Button'; import PlayerHead from '../game/PlayerHead'; import useAccount from '../../hooks/useAccount'; @@ -33,6 +34,7 @@ function AccountComponent(props: AccountComponentProps) { function AccountPopup(props: Popup.PopupProps) { const loggedInAccount = useAccount(); + const navigate = useNavigate(); return ( @@ -53,7 +55,14 @@ function AccountPopup(props: Popup.PopupProps) {
-
diff --git a/desktop/src/ui/hooks/useAccount.ts b/desktop/src/ui/hooks/useAccount.ts index 28190f57..5b59dde0 100644 --- a/desktop/src/ui/hooks/useAccount.ts +++ b/desktop/src/ui/hooks/useAccount.ts @@ -1,4 +1,4 @@ -function useAccount(): Core.Account { +function useAccount() { // TODO return the currently logged in account return { diff --git a/desktop/src/ui/hooks/useCommand.ts b/desktop/src/ui/hooks/useCommand.ts index 44ff2484..7b533c29 100644 --- a/desktop/src/ui/hooks/useCommand.ts +++ b/desktop/src/ui/hooks/useCommand.ts @@ -1,4 +1,4 @@ -import { type Resource, type ResourceReturn, createResource } from 'solid-js'; +import { type ResourceReturn, createResource } from 'solid-js'; import type { Result } from '~bindings'; /** diff --git a/desktop/src/ui/pages/Home.tsx b/desktop/src/ui/pages/Home.tsx index cf856f40..917094e8 100644 --- a/desktop/src/ui/pages/Home.tsx +++ b/desktop/src/ui/pages/Home.tsx @@ -18,6 +18,7 @@ import ClusterCover from '~ui/components/game/ClusterCover'; import type { Cluster } from '~bindings'; import useCommand from '~ui/hooks/useCommand'; import { bridge } from '~index'; +import { upperFirst } from '~utils/string'; // TODO: Replace this into it's own component // possibly with the core ui lirbary with autogenerated and optimized svgs pls @@ -142,9 +143,9 @@ function ClusterCard(props: Cluster) {
-

{props.meta.name}

+

{props.meta.name}

- {props.meta.loader} + {upperFirst(props.meta.loader)} {' '} {props.meta.mc_version} {/* {' '} diff --git a/desktop/src/ui/pages/browser/BrowserMain.tsx b/desktop/src/ui/pages/browser/BrowserMain.tsx index 6be041d3..991f1775 100644 --- a/desktop/src/ui/pages/browser/BrowserMain.tsx +++ b/desktop/src/ui/pages/browser/BrowserMain.tsx @@ -1,5 +1,5 @@ -import { HeartIcon, SearchMdIcon } from '@untitled-theme/icons-solid'; -import { createSignal } from 'solid-js'; +import { SearchMdIcon } from '@untitled-theme/icons-solid'; +import { For, createSignal } from 'solid-js'; import Dropdown from '~ui/components/base/Dropdown'; import TextField from '~ui/components/base/TextField'; import createSortable from '~utils/sorting'; @@ -28,34 +28,27 @@ function BrowserMain() { provider: [Provider.Curseforge], }); - const [list, key, setList, setKey] = createSortable([], { + const sortable = createSortable([], { 'A-Z': (a, b) => a.name.localeCompare(b.name), 'Z-A': (a, b) => b.name.localeCompare(a.name), 'Last Updated': (a, b) => a.date_updated - b.date_updated, 'New': (a, b) => b.date_added - a.date_added, - // 'Provider': (a, b) => a.provider }); + function onSortChange(selected: number) { + sortable.setKey(selected); + } + return (

} /> - - - -

Test

-
- - -

Test 2

-
- -

Test 333333333

-
- - -

Lorem Ipsum dolor

-
+ + + {sortable => ( + {sortable} + )} +
diff --git a/desktop/src/ui/pages/settings/SettingsRoot.tsx b/desktop/src/ui/pages/settings/SettingsRoot.tsx index 389833ad..22190b98 100644 --- a/desktop/src/ui/pages/settings/SettingsRoot.tsx +++ b/desktop/src/ui/pages/settings/SettingsRoot.tsx @@ -4,6 +4,7 @@ import { Route } from '@solidjs/router'; import Sidebar from '../../components/Sidebar'; import SettingsGeneral from './launcher/SettingsGeneral'; import SettingsAppearance from './launcher/SettingsAppearance'; +import SettingsAccounts from './game/SettingsAccounts'; import AnimatedRoutes from '~ui/components/AnimatedRoutes'; import ErrorBoundary from '~ui/components/ErrorBoundary'; @@ -12,6 +13,7 @@ function SettingsRoutes() { <> + ); } diff --git a/desktop/src/ui/pages/settings/game/SettingsAccounts.tsx b/desktop/src/ui/pages/settings/game/SettingsAccounts.tsx new file mode 100644 index 00000000..c8082a2d --- /dev/null +++ b/desktop/src/ui/pages/settings/game/SettingsAccounts.tsx @@ -0,0 +1,108 @@ +import { ChevronRightIcon, InfoCircleIcon } from '@untitled-theme/icons-solid'; +import { For, Show, createSignal, mergeProps } from 'solid-js'; +import Button from '~ui/components/base/Button'; +import Tooltip from '~ui/components/base/Tooltip'; +import PlayerHead from '~ui/components/game/PlayerHead'; +import ScrollableContainer from '~ui/components/ScrollableContainer'; +import SettingsRow from '~ui/components/SettingsRow'; +import Sidebar from '~ui/components/Sidebar'; + +const Accounts: { + username: string; + uuid: string; +}[] = [ + { + username: 'Lynith', + uuid: 'f247be7c-5b82-41c6-9148-793ded77e71f', + }, + { + username: 'Wyvest', + uuid: 'a5331404-0e77-440e-8bef-24c071dac1ae', + }, + { + username: 'nextday', + uuid: '000646f6-b779-419c-b5d6-2c5ba52419bf', + }, + { + username: 'xXfake_playerXx', + uuid: '000646f6-b779-419c-b5d6-aaa', + }, +]; + +function SettingsAccounts() { + const [current, setCurrent] = createSignal(); + + return ( + +

Accounts

+ + + + {account => ( + + )} + + + +
+ ); +} + +interface AccountRowProps { + username: string; + uuid: string; + current?: boolean; + onClick: (uuid: string) => any; +}; + +function AccountRow(props: AccountRowProps) { + const defaultProps = mergeProps({ current: false }, props); + const [errored, setErrored] = createSignal(false); + + return ( +
props.onClick(props.uuid)} + class={`flex flex-row bg-component-bg hover:bg-component-bg-hover active:bg-component-bg-pressed rounded-xl gap-3.5 p-4 items-center box-border border ${defaultProps.current ? 'border-brand' : 'border-transparent'}`} + > +
+ setErrored(true)} + /> +
+ +
+
+

{props.username}

+ + + + + +
+

{props.uuid}

+
+ +
+
+
+ ); +} + +export default SettingsAccounts; diff --git a/desktop/src/utils/sorting.ts b/desktop/src/utils/sorting.ts index 04a268d1..0899385f 100644 --- a/desktop/src/utils/sorting.ts +++ b/desktop/src/utils/sorting.ts @@ -1,26 +1,39 @@ import { type Accessor, type Setter, createSignal } from 'solid-js'; export type SortingFunction = (a: T, b: T) => number; -export interface Sortables { [key: string]: SortingFunction } +export type Sortables = Record>; -type CreateSortable = Sortables> = [ - list: Accessor, - key: Accessor, - setList: Setter, - setKey: Setter, -]; +interface CreateSortable = Sortables> { + list: Accessor; + key: Accessor; + setList: Setter; + setKey: (val: keyof S | number) => any; + sortables: S; +} export function createSortable = Sortables>(initList: T[], initSortables: S): CreateSortable { const sortables = initSortables; const [list, setList] = createSignal(initList); const [key, setKey] = createSignal(Object.keys(sortables)[0]!); - return [ + return { list, key, setList, - setKey, - ]; + setKey: (val: keyof S | number) => { + if (typeof val === 'string') { + setKey(val); + } + else if (typeof val === 'number') { + const key = Object.keys(sortables).at(val); + if (key === undefined) + throw new Error('Key cannot be undefined!'); + + setKey(key); + } + }, + sortables, + }; } export default createSortable; diff --git a/desktop/src/utils/string.ts b/desktop/src/utils/string.ts index a873310f..7eba83a5 100644 --- a/desktop/src/utils/string.ts +++ b/desktop/src/utils/string.ts @@ -1,4 +1,5 @@ -export function upperFirst(str: string): string { +export function upperFirst(object: any): string { + const str = object.toString(); return str.charAt(0).toUpperCase() + str.slice(1); }