feat: Dropdown + Text Field modifications. Started work on Browser
This commit is contained in:
parent
88709c6218
commit
a17bcbb471
12 changed files with 251 additions and 50 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
19
desktop/src/ui/components/base/Dropdown.module.scss
Normal file
19
desktop/src/ui/components/base/Dropdown.module.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
80
desktop/src/ui/components/base/Dropdown.tsx
Normal file
80
desktop/src/ui/components/base/Dropdown.tsx
Normal 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;
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
65
desktop/src/ui/pages/browser/BrowserMain.tsx
Normal file
65
desktop/src/ui/pages/browser/BrowserMain.tsx
Normal 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;
|
||||
19
desktop/src/ui/pages/browser/BrowserRoot.tsx
Normal file
19
desktop/src/ui/pages/browser/BrowserRoot.tsx
Normal 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;
|
||||
|
|
@ -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,23 +25,18 @@ function ClusterRoot(props: ParentProps) {
|
|||
const [searchParams] = useSearchParams();
|
||||
const cluster = useCluster(searchParams.id);
|
||||
|
||||
if (!cluster) {
|
||||
location.href = '/';
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<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 }}
|
||||
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),
|
||||
(cluster!()!.meta.loader === 'vanilla' ? [<PackagePlusIcon />, 'Mods', '/mods'] : undefined),
|
||||
[<Image03Icon />, 'Screenshots', '/screenshots'],
|
||||
[<Globe04Icon />, 'Worlds', '/worlds'],
|
||||
[<File06Icon />, 'Logs', '/logs'],
|
||||
|
|
@ -62,6 +57,7 @@ function ClusterRoot(props: ParentProps) {
|
|||
</AnimatedRoutes>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
26
desktop/src/utils/sorting.ts
Normal file
26
desktop/src/utils/sorting.ts
Normal 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;
|
||||
Loading…
Add table
Reference in a new issue