feat: Dropdown + Text Field modifications. Started work on Browser

This commit is contained in:
LynithDev 2024-06-16 12:47:51 +02:00
parent 88709c6218
commit a17bcbb471
12 changed files with 251 additions and 50 deletions

View file

@ -6,23 +6,19 @@ import './imports';
import { Route, Router } from '@solidjs/router';
import App from './ui/App';
import HomePage from './ui/pages/Home';
import BrowserPage from './ui/pages/Browser';
import UpdatesPage from './ui/pages/Updates';
import ClusterRoot from '~ui/pages/cluster/ClusterRoot';
import SettingsRoot from '~ui/pages/settings/SettingsRoot';
import BrowserRoot from '~ui/pages/browser/BrowserRoot';
export * as bridge from './bindings';
render(() => (
<Router root={App}>
<Route path="/" component={HomePage} />
<Route path="/browser" component={BrowserPage} />
<Route path="/updates" component={UpdatesPage} />
<Route path="/clusters" component={ClusterRoot}>
<ClusterRoot.Routes />
</Route>
<Route path="/settings" component={SettingsRoot}>
<SettingsRoot.Routes />
</Route>
<Route path="/clusters" component={ClusterRoot} children={<ClusterRoot.Routes />} />
<Route path="/settings" component={SettingsRoot} children={<SettingsRoot.Routes />} />
<Route path="/browser" component={BrowserRoot} children={<BrowserRoot.Routes />} />
</Router>
), document.getElementById('root') as HTMLElement);

View file

@ -8,10 +8,11 @@ import NotificationOverlay from './components/overlay/notifications/Notification
import { bridge } from '~index';
function App(props: ParentProps) {
if (!bridge.commands.isDev())
if (!bridge.commands.isDev()) {
document.addEventListener('contextmenu', (event) => {
event.preventDefault();
});
}
return (
<main class="flex flex-col bg-primary w-full min-h-screen overflow-hidden h-screen max-h-screen text-fg-primary">

View file

@ -0,0 +1,19 @@
.dropdown {
@apply relative;
.row {
@apply flex flex-row justify-start items-center gap-1.5 h-4;
> svg {
@apply w-4 h-4;
}
}
.selected {
@apply w-1 h-1 bg-white rounded-full opacity-0;
&.visible {
@apply opacity-100;
}
}
}

View file

@ -0,0 +1,80 @@
import { Index, type JSX, type ParentProps, children, createSignal } from 'solid-js';
import { ChevronDownIcon } from '@untitled-theme/icons-solid';
import Popup from '../overlay/Popup';
import Button from './Button';
import styles from './Dropdown.module.scss';
export type DropdownProps = ParentProps;
function Dropdown(props: DropdownProps) {
const [visible, setVisible] = createSignal(false);
const [selected, setSelected] = createSignal<number>(0);
let ref!: HTMLDivElement;
const items = () => children(() => props.children).toArray();
function select(index: number) {
setSelected(index);
setVisible(false);
}
return (
<div ref={ref} class={styles.dropdown}>
<Button
class="h-full"
buttonStyle="secondary"
onClick={() => setVisible(true)}
iconRight={<ChevronDownIcon />}
>
<div
class="w-full h-full overflow-hidden mt-px"
>
<div
class="flex flex-col justify-start relative gap-4"
style={{
transform: `translateY(-${(selected() * 16) * 2}px)`,
}}
>
{items()}
</div>
</div>
</Button>
<Popup
mount={ref}
visible={visible}
setVisible={setVisible}
ref={ref => ref.classList.add('mt-1', 'left-0', 'right-0')}
>
<div class="bg-secondary rounded-lg border border-gray-05 p-1 shadow-md shadow-black/30">
<div class="flex flex-col gap-2">
<Index each={items()}>
{(item, index) => (
<div onClick={() => select(index)}>
<div class="hover:bg-gray-05 p-2 rounded-lg flex flex-row gap-2 justify-between items-center">
{item()}
<div class={styles.selected! + (selected() === index ? ` ${styles.visible}` : '')} />
</div>
</div>
)}
</Index>
</div>
</div>
</Popup>
</div>
);
}
interface DropdownRowProps {
iconLeft?: JSX.Element;
};
Dropdown.Row = function (props: ParentProps) {
return (
<div class={styles.row}>
{props.children}
</div>
);
};
export default Dropdown;

View file

@ -1,9 +1,8 @@
.textfield {
@apply bg-component-bg [&:not(:focus-within)]:hover:bg-component-bg-hover focus-within:bg-component-bg-pressed disabled:bg-component-bg-disabled;
@apply border border-gray-05 text-fg-primary fill-fg-primary;
@apply rounded-lg px-2.5 py-1.5;
@apply flex flex-row gap-x-1.5;
@apply rounded-lg px-2 py-1 max-h-8;
@apply flex flex-row items-center justify-start gap-x-1.5;
& input {
&::selection {
@ -15,7 +14,7 @@
& span {
& * {
@apply w-4;
@apply w-4 h-4;
}
}
}

View file

@ -0,0 +1,65 @@
import { HeartIcon, SearchMdIcon } from '@untitled-theme/icons-solid';
import { createSignal } from 'solid-js';
import Dropdown from '~ui/components/base/Dropdown';
import TextField from '~ui/components/base/TextField';
import createSortable from '~utils/sorting';
interface CardProps {
id: string;
name: string;
date_added: number;
date_updated: number;
provider: Provider;
};
enum Provider {
Curseforge,
Modrinth,
Polyfrost,
Skyclient,
}
interface BrowserFilters {
provider: Provider[]; // TODO: Change with Provider types from Rust
}
function BrowserMain() {
const [filters, setFilters] = createSignal<BrowserFilters>({
provider: [Provider.Curseforge],
});
const [list, key, setList, setKey] = createSortable<CardProps>([], {
'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
});
return (
<div class="flex flex-col flex-grow-1">
<div class="flex flex-row gap-2 h-8">
<TextField iconLeft={<SearchMdIcon />} />
<Dropdown>
<Dropdown.Row>
<HeartIcon />
<p>Test</p>
</Dropdown.Row>
<Dropdown.Row>
<HeartIcon />
<p>Test 2</p>
</Dropdown.Row>
<Dropdown.Row>
<p>Test 333333333</p>
</Dropdown.Row>
<Dropdown.Row>
<HeartIcon />
<p>Lorem Ipsum dolor</p>
</Dropdown.Row>
</Dropdown>
</div>
</div>
);
}
export default BrowserMain;

View file

@ -0,0 +1,19 @@
import { Route } from '@solidjs/router';
import type { ParentProps } from 'solid-js';
import BrowserMain from './BrowserMain';
function BrowserRoutes() {
return (
<>
<Route path="/" component={BrowserMain} />
</>
);
}
function BrowserRoot(props: ParentProps) {
return <>{props.children}</>;
}
BrowserRoot.Routes = BrowserRoutes;
export default BrowserRoot;

View file

@ -1,5 +1,5 @@
import { Route, useSearchParams } from '@solidjs/router';
import type { ParentProps } from 'solid-js';
import { type ParentProps, Show } from 'solid-js';
import { EyeIcon, File06Icon, Globe04Icon, Image03Icon, PackagePlusIcon, Settings04Icon, Tool02Icon } from '@untitled-theme/icons-solid';
import Sidebar from '../../components/Sidebar';
import AnimatedRoutes from '../../components/AnimatedRoutes';
@ -25,43 +25,39 @@ function ClusterRoot(props: ParentProps) {
const [searchParams] = useSearchParams();
const cluster = useCluster(searchParams.id);
if (!cluster) {
location.href = '/';
return;
}
return (
<div class="flex flex-row flex-1 h-full gap-x-7">
<div class="mt-8">
<Sidebar
base="/clusters"
state={{ id: cluster()!.uuid }}
links={{
Cluster: [
[<EyeIcon />, 'Overview', '/'],
// [<User01Icon />, 'Accounts', '/users'],
// TODO: Better way of checking mods
(cluster()?.meta.loader !== 'vanilla' ? [<PackagePlusIcon />, 'Mods', '/mods'] : undefined),
[<Image03Icon />, 'Screenshots', '/screenshots'],
[<Globe04Icon />, 'Worlds', '/worlds'],
[<File06Icon />, 'Logs', '/logs'],
],
Settings: [
[<Tool02Icon />, 'Java', '/java'],
[<Settings04Icon />, 'Miscellaneous', '/misc'],
],
}}
/>
</div>
<Show when={cluster !== null && cluster() !== undefined}>
<div class="flex flex-row flex-1 h-full gap-x-7">
<div class="mt-8">
<Sidebar
base="/clusters"
state={{ id: cluster!()!.uuid }}
links={{
Cluster: [
[<EyeIcon />, 'Overview', '/'],
// TODO: Better way of checking mods
(cluster!()!.meta.loader === 'vanilla' ? [<PackagePlusIcon />, 'Mods', '/mods'] : undefined),
[<Image03Icon />, 'Screenshots', '/screenshots'],
[<Globe04Icon />, 'Worlds', '/worlds'],
[<File06Icon />, 'Logs', '/logs'],
],
Settings: [
[<Tool02Icon />, 'Java', '/java'],
[<Settings04Icon />, 'Miscellaneous', '/misc'],
],
}}
/>
</div>
<div class="flex flex-col w-full h-full">
<AnimatedRoutes>
<ErrorBoundary>
{props.children}
</ErrorBoundary>
</AnimatedRoutes>
<div class="flex flex-col w-full h-full">
<AnimatedRoutes>
<ErrorBoundary>
{props.children}
</ErrorBoundary>
</AnimatedRoutes>
</div>
</div>
</div>
</Show>
);
}

View file

@ -1,5 +1,5 @@
import { ColorsIcon, PaintPourIcon, Speedometer04Icon } from '@untitled-theme/icons-solid';
import SettingsRow from '../components';
import SettingsRow from '../../../components/SettingsRow';
import ScrollableContainer from '~ui/components/ScrollableContainer';
import Button from '~ui/components/base/Button';
import Toggle from '~ui/components/base/Toggle';

View file

@ -1,5 +1,5 @@
import { XIcon } from '@untitled-theme/icons-solid';
import SettingsRow from '../components';
import SettingsRow from '../../../components/SettingsRow';
import ScrollableContainer from '~ui/components/ScrollableContainer';
import Toggle from '~ui/components/base/Toggle';
import appSettings from '~ui/state/appSettings';

View file

@ -0,0 +1,26 @@
import { type Accessor, type Setter, createSignal } from 'solid-js';
export type SortingFunction<T> = (a: T, b: T) => number;
export interface Sortables<T> { [key: string]: SortingFunction<T> }
type CreateSortable<T, S extends Sortables<T> = Sortables<T>> = [
list: Accessor<T[]>,
key: Accessor<keyof S>,
setList: Setter<T[]>,
setKey: Setter<keyof S>,
];
export function createSortable<T, S extends Sortables<T> = Sortables<T>>(initList: T[], initSortables: S): CreateSortable<T, S> {
const sortables = initSortables;
const [list, setList] = createSignal<T[]>(initList);
const [key, setKey] = createSignal<keyof S>(Object.keys(sortables)[0]!);
return [
list,
key,
setList,
setKey,
];
}
export default createSortable;