feat: Experiment with search - should be refactored later on to provide a unified search across the entire app
This commit is contained in:
parent
9f9be695ad
commit
429fc1fc53
11 changed files with 191 additions and 98 deletions
|
|
@ -17,6 +17,7 @@
|
|||
"dependencies": {
|
||||
"@fontsource/poppins": "^5.0.14",
|
||||
"@iconify-json/logos": "^1.1.42",
|
||||
"@leeoniya/ufuzzy": "^1.0.14",
|
||||
"@onelauncher/client": "workspace:*",
|
||||
"@rspc/client": "^0.2.0",
|
||||
"@rspc/solid": "^0.2.0",
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ function Navbar() {
|
|||
let notificationButton!: HTMLButtonElement;
|
||||
|
||||
return (
|
||||
<div class="flex flex-row *:flex-1 items-center min-h-[60px] h-15 mx-2">
|
||||
<div class="flex flex-row *:flex-1 items-center min-h-[60px] h-15">
|
||||
<div>
|
||||
<div onClick={() => open(WEBSITE)} class="flex items-center justify-center active:scale-90 transition-transform w-min">
|
||||
<PolyfrostFull />
|
||||
|
|
|
|||
|
|
@ -1,21 +1,14 @@
|
|||
import { OverlayScrollbarsComponent } from 'overlayscrollbars-solid';
|
||||
import type { ParentProps } from 'solid-js';
|
||||
|
||||
type ScrollableContainerProps = ParentProps & {
|
||||
title: string;
|
||||
};
|
||||
|
||||
function ScrollableContainer(props: ScrollableContainerProps) {
|
||||
function ScrollableContainer(props: ParentProps) {
|
||||
return (
|
||||
<div class="flex flex-col flex-1">
|
||||
<h1>{props.title}</h1>
|
||||
<div class="flex flex-col flex-1 rounded-lg overflow-hidden w-[calc(100%+14px)]">
|
||||
<OverlayScrollbarsComponent class="os-hide-horizontal-scrollbar relative flex-1">
|
||||
<div class="flex flex-col absolute w-[calc(100%-14px)] gap-2">
|
||||
{props.children}
|
||||
</div>
|
||||
</OverlayScrollbarsComponent>
|
||||
</div>
|
||||
<div class="flex flex-col flex-1 overflow-hidden w-[calc(100%+14px)]">
|
||||
<OverlayScrollbarsComponent class="os-hide-horizontal-scrollbar relative flex-1">
|
||||
<div class="flex flex-col absolute w-[calc(100%-14px)] gap-1">
|
||||
{props.children}
|
||||
</div>
|
||||
</OverlayScrollbarsComponent>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useLocation, useNavigate } from '@solidjs/router';
|
||||
import type { JSX, ParentProps } from 'solid-js';
|
||||
import { For, createEffect } from 'solid-js';
|
||||
import { For, createEffect, splitProps } from 'solid-js';
|
||||
|
||||
type SidebarProps = ParentProps & {
|
||||
state: {
|
||||
|
|
@ -67,4 +67,13 @@ function Sidebar(props: SidebarProps) {
|
|||
);
|
||||
}
|
||||
|
||||
Sidebar.Page = function (props: JSX.HTMLAttributes<HTMLDivElement>) {
|
||||
const [split, rest] = splitProps(props, ['class', 'children']);
|
||||
return (
|
||||
<div class={`flex flex-col flex-1 gap-y-1 ${split.class || ''}`} {...rest}>
|
||||
{split.children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Sidebar;
|
||||
|
|
|
|||
|
|
@ -1,29 +1,103 @@
|
|||
import { open } from '@tauri-apps/plugin-shell';
|
||||
import { ArrowRightIcon, Edit02Icon, LinkExternal01Icon, Trash03Icon } from '@untitled-theme/icons-solid';
|
||||
import { For } from 'solid-js';
|
||||
import { ArrowRightIcon, ChevronDownIcon, ChevronUpIcon, Edit02Icon, SearchMdIcon, Trash03Icon } from '@untitled-theme/icons-solid';
|
||||
import { For, Index, Match, Switch, createEffect, createSignal } from 'solid-js';
|
||||
import * as uuid from 'uuid';
|
||||
import uFuzzy from '@leeoniya/ufuzzy';
|
||||
import Button from '~ui/components/base/Button';
|
||||
import TextField from '~ui/components/base/TextField';
|
||||
import ScrollableContainer from '~ui/components/ScrollableContainer';
|
||||
import Sidebar from '~ui/components/Sidebar';
|
||||
|
||||
const mods = Array<ModEntryProps>(15).fill({
|
||||
id: uuid.v4(),
|
||||
name: 'Mod Name',
|
||||
author: 'Author Name',
|
||||
version: '1.0.0',
|
||||
description: 'This is a mod description',
|
||||
provider: 'curseforge',
|
||||
thumbnail: 'https://cdn.modrinth.com/data/AANobbMI/icon.png',
|
||||
});
|
||||
const randomModNames = [
|
||||
'Sodium',
|
||||
'Nvidium',
|
||||
'Iris Shaders',
|
||||
'Create',
|
||||
'Fabric API',
|
||||
'Create: Estrogen',
|
||||
'OneConfig',
|
||||
'PolySprint',
|
||||
'PolyEffects',
|
||||
];
|
||||
|
||||
const mods: ModEntryProps[] = [];
|
||||
|
||||
for (let i = 0; i < randomModNames.length; i++) {
|
||||
mods.push({
|
||||
id: uuid.v4(),
|
||||
name: randomModNames[i] || 'Unknown',
|
||||
author: 'Author Name',
|
||||
version: '1.0.0',
|
||||
description: 'This is a mod description',
|
||||
provider: 'curseforge',
|
||||
thumbnail: 'https://cdn.modrinth.com/data/AANobbMI/icon.png',
|
||||
});
|
||||
}
|
||||
|
||||
function ClusterMods() {
|
||||
// Search state:
|
||||
// `null` - No search query, show all mods
|
||||
// `[]` - Search query, but no results, show "no results" message
|
||||
// `[...]` - Search query, with results, show results
|
||||
const [indexes, setIndexes] = createSignal<number[] | null>(null);
|
||||
|
||||
// State:
|
||||
// true - A to Z
|
||||
// false - Z to A
|
||||
const [sortingName, setSortingName] = createSignal<boolean>(true);
|
||||
|
||||
const uf = new uFuzzy();
|
||||
const modsSearchable = mods
|
||||
.map(mod => mod.name);
|
||||
|
||||
function search(value: string) {
|
||||
if (value === '' || value === undefined) {
|
||||
setIndexes(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const result = uf.search(modsSearchable, value);
|
||||
setIndexes(result[0] ?? []);
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollableContainer title="Mods">
|
||||
<For each={mods}>
|
||||
{mod => (
|
||||
<ModEntry {...mod} />
|
||||
)}
|
||||
</For>
|
||||
</ScrollableContainer>
|
||||
<Sidebar.Page>
|
||||
<div class="flex flex-row justify-between w-full">
|
||||
<h1>Mods</h1>
|
||||
<div class="flex flex-row justify-end items-center gap-x-2">
|
||||
<Button
|
||||
buttonStyle="secondary"
|
||||
iconLeft={sortingName() ? <ChevronUpIcon /> : <ChevronDownIcon />}
|
||||
onClick={() => setSortingName(!sortingName())}
|
||||
children="Name"
|
||||
/>
|
||||
|
||||
<TextField iconLeft={<SearchMdIcon />} placeholder="Search..." onInput={e => search(e.target.value)} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ScrollableContainer>
|
||||
<Switch>
|
||||
<Match when={indexes() === null}>
|
||||
<For each={mods}>
|
||||
{mod => (
|
||||
<ModEntry {...mod} />
|
||||
)}
|
||||
</For>
|
||||
</Match>
|
||||
<Match when={indexes()!.length > 0}>
|
||||
<For each={indexes()}>
|
||||
{(index) => {
|
||||
const mod = mods[index];
|
||||
return !mod ? <></> : <ModEntry {...mod} />;
|
||||
}}
|
||||
</For>
|
||||
</Match>
|
||||
<Match when={indexes()!.length === 0}>
|
||||
<p class="text-2lg text-center my-4">No mods were found</p>
|
||||
</Match>
|
||||
</Switch>
|
||||
</ScrollableContainer>
|
||||
</Sidebar.Page>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -49,9 +123,9 @@ function ModEntry(props: ModEntryProps) {
|
|||
<div class="flex flex-col flex-1">
|
||||
<div class="flex flex-row justify-between items-center">
|
||||
<div class="flex flex-col items-start justify-center">
|
||||
<div class="flex flex-row items-center">
|
||||
<div class="flex flex-col items-start justify-center gap-y-2">
|
||||
<h4>{props.name}</h4>
|
||||
<span class="flex flex-row items-center text-xs font-600 text-fg-secondary/50 ml-2">
|
||||
<span class="h-2 flex flex-row items-center justify-start text-xs font-600 text-fg-secondary/50">
|
||||
{props.version}
|
||||
{/* TODO: Add version checker */}
|
||||
{/* <Show when={props.version.includes()}> */}
|
||||
|
|
@ -60,15 +134,6 @@ function ModEntry(props: ModEntryProps) {
|
|||
{/* </Show> */}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<span class="flex flex-row text-xs -mb-1 gap-1 items-center text-fg-secondary opacity-50">
|
||||
by
|
||||
{/* TODO: Implement prompt */}
|
||||
<a onClick={() => open('https://google.com/')} class="text-fg-primary hover:opacity-70 flex flex-row gap-1 items-center">
|
||||
{props.author}
|
||||
<LinkExternal01Icon class="w-3" />
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row gap-2 items-end justify-center">
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import LoaderIcon from '../../../components/game/LoaderIcon';
|
|||
import Button from '../../../components/base/Button';
|
||||
import { launchCluster } from '../../../../bridge/game';
|
||||
import ScrollableContainer from '~ui/components/ScrollableContainer';
|
||||
import Sidebar from '~ui/components/Sidebar';
|
||||
|
||||
function ClusterOverview() {
|
||||
const [params] = useSearchParams();
|
||||
|
|
@ -15,11 +16,14 @@ function ClusterOverview() {
|
|||
throw new Error('Cluster doesn\'t exist');
|
||||
|
||||
return (
|
||||
<ScrollableContainer title="Overview">
|
||||
<Show when={!cluster.loading}>
|
||||
<Banner {...cluster()!} />
|
||||
</Show>
|
||||
</ScrollableContainer>
|
||||
<Sidebar.Page>
|
||||
<h1>Overview</h1>
|
||||
<ScrollableContainer>
|
||||
<Show when={!cluster.loading}>
|
||||
<Banner {...cluster()!} />
|
||||
</Show>
|
||||
</ScrollableContainer>
|
||||
</Sidebar.Page>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { For } from 'solid-js';
|
||||
import ScrollableContainer from '~ui/components/ScrollableContainer';
|
||||
import Sidebar from '~ui/components/Sidebar';
|
||||
|
||||
const list = Array<Screenshot>(19).fill({
|
||||
date_taken: Math.floor(Date.now() / 1000),
|
||||
|
|
@ -14,15 +15,18 @@ interface Screenshot {
|
|||
|
||||
function ClusterScreenshots() {
|
||||
return (
|
||||
<ScrollableContainer title="Screenshots">
|
||||
<div class="grid grid-cols-[repeat(auto-fill,minmax(350px,1fr))] w-full gap-2">
|
||||
<For each={list}>
|
||||
{screenshot => (
|
||||
<ScreenshotEntry {...screenshot} />
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</ScrollableContainer>
|
||||
<Sidebar.Page>
|
||||
<h1>Screenshots</h1>
|
||||
<ScrollableContainer>
|
||||
<div class="grid grid-cols-[repeat(auto-fill,minmax(350px,1fr))] w-full gap-2">
|
||||
<For each={list}>
|
||||
{screenshot => (
|
||||
<ScreenshotEntry {...screenshot} />
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</ScrollableContainer>
|
||||
</Sidebar.Page>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,37 +4,41 @@ import ScrollableContainer from '~ui/components/ScrollableContainer';
|
|||
import Button from '~ui/components/base/Button';
|
||||
import Toggle from '~ui/components/base/Toggle';
|
||||
import appSettings from '~ui/state/appSettings';
|
||||
import Sidebar from '~ui/components/Sidebar';
|
||||
|
||||
function SettingsAppearance() {
|
||||
const { settings, setSettings } = appSettings;
|
||||
|
||||
return (
|
||||
<ScrollableContainer title="Appearance">
|
||||
<div class="flex flex-row items-center flex-1">
|
||||
<p>theme placeholder</p>
|
||||
</div>
|
||||
<Sidebar.Page>
|
||||
<h1>Appearance</h1>
|
||||
<ScrollableContainer>
|
||||
<div class="flex flex-row items-center flex-1">
|
||||
<p>theme placeholder</p>
|
||||
</div>
|
||||
|
||||
<SettingsRow
|
||||
title="Accent Color"
|
||||
description="The main color used across the launcher. This doesn't edit your theme."
|
||||
icon={<PaintPourIcon />}
|
||||
>
|
||||
<Button iconLeft={<ColorsIcon />}>#ff0000</Button>
|
||||
</SettingsRow>
|
||||
<SettingsRow
|
||||
title="Accent Color"
|
||||
description="The main color used across the launcher. This doesn't edit your theme."
|
||||
icon={<PaintPourIcon />}
|
||||
>
|
||||
<Button iconLeft={<ColorsIcon />}>#ff0000</Button>
|
||||
</SettingsRow>
|
||||
|
||||
<SettingsRow
|
||||
title="Animations"
|
||||
description="Toggle all animations in the launcher."
|
||||
icon={<Speedometer04Icon />}
|
||||
>
|
||||
<Toggle
|
||||
defaultChecked={settings.reducedMotion}
|
||||
onChecked={(checked) => {
|
||||
setSettings('reducedMotion', checked);
|
||||
}}
|
||||
/>
|
||||
</SettingsRow>
|
||||
</ScrollableContainer>
|
||||
<SettingsRow
|
||||
title="Animations"
|
||||
description="Toggle all animations in the launcher."
|
||||
icon={<Speedometer04Icon />}
|
||||
>
|
||||
<Toggle
|
||||
defaultChecked={settings.reducedMotion}
|
||||
onChecked={(checked) => {
|
||||
setSettings('reducedMotion', checked);
|
||||
}}
|
||||
/>
|
||||
</SettingsRow>
|
||||
</ScrollableContainer>
|
||||
</Sidebar.Page>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,23 +3,27 @@ import SettingsRow from '../components';
|
|||
import ScrollableContainer from '~ui/components/ScrollableContainer';
|
||||
import Toggle from '~ui/components/base/Toggle';
|
||||
import appSettings from '~ui/state/appSettings';
|
||||
import Sidebar from '~ui/components/Sidebar';
|
||||
|
||||
function SettingsGeneral() {
|
||||
return (
|
||||
<ScrollableContainer title="General">
|
||||
<SettingsRow
|
||||
title='Show Close Dialog'
|
||||
description='Show a confirmation dialog when closing the launcher.'
|
||||
icon={<XIcon />}
|
||||
>
|
||||
<Toggle
|
||||
defaultChecked={appSettings.settings.closeDialog}
|
||||
onChecked={(checked) => {
|
||||
appSettings.setSettings('closeDialog', checked);
|
||||
}}
|
||||
/>
|
||||
</SettingsRow>
|
||||
</ScrollableContainer>
|
||||
<Sidebar.Page>
|
||||
<h1>General</h1>
|
||||
<ScrollableContainer>
|
||||
<SettingsRow
|
||||
title="Show Close Dialog"
|
||||
description="Show a confirmation dialog when closing the launcher."
|
||||
icon={<XIcon />}
|
||||
>
|
||||
<Toggle
|
||||
defaultChecked={appSettings.settings.closeDialog}
|
||||
onChecked={(checked) => {
|
||||
appSettings.setSettings('closeDialog', checked);
|
||||
}}
|
||||
/>
|
||||
</SettingsRow>
|
||||
</ScrollableContainer>
|
||||
</Sidebar.Page>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ export default petal({
|
|||
rules: {
|
||||
'petal/consistent-list-newline': 'off',
|
||||
'no-console': 'off',
|
||||
'new-cap': 'off',
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
|
|
|||
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
|
|
@ -80,6 +80,9 @@ importers:
|
|||
'@iconify-json/logos':
|
||||
specifier: ^1.1.42
|
||||
version: 1.1.42
|
||||
'@leeoniya/ufuzzy':
|
||||
specifier: ^1.0.14
|
||||
version: 1.0.14
|
||||
'@onelauncher/client':
|
||||
specifier: workspace:*
|
||||
version: link:../client
|
||||
|
|
@ -684,6 +687,9 @@ packages:
|
|||
resolution: {integrity: sha512-f5DRIOZf7wxogefH03RjMPMdBF7ADTWUMoOs9kaJo06EfwF+aFhMZMDZxHg/Xe12hptN9xoZjGso2fdjapBRIA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
'@leeoniya/ufuzzy@1.0.14':
|
||||
resolution: {integrity: sha512-/xF4baYuCQMo+L/fMSUrZnibcu0BquEGnbxfVPiZhs/NbJeKj4c/UmFpQzW9Us0w45ui/yYW3vyaqawhNYsTzA==}
|
||||
|
||||
'@nodelib/fs.scandir@2.1.5':
|
||||
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
||||
engines: {node: '>= 8'}
|
||||
|
|
@ -3929,6 +3935,8 @@ snapshots:
|
|||
string-argv: 0.3.2
|
||||
type-detect: 4.0.8
|
||||
|
||||
'@leeoniya/ufuzzy@1.0.14': {}
|
||||
|
||||
'@nodelib/fs.scandir@2.1.5':
|
||||
dependencies:
|
||||
'@nodelib/fs.stat': 2.0.5
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue