diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index e770a7b9..e0e78c61 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -47,7 +47,7 @@ pub async fn run_app(setup: F) { .plugin(tauri_plugin_clipboard_manager::init()) .plugin(ext::updater::plugin()) .manage(ext::updater::State::default()) - .plugin(tauri_plugin_window_state::Builder::default().build()) + // .plugin(tauri_plugin_window_state::Builder::default().build()) .menu(tauri::menu::Menu::new) .setup(move |app| { setup(app); diff --git a/apps/desktop/src/assets/logos/curseforge.svg b/apps/desktop/src/assets/logos/curseforge.svg new file mode 100644 index 00000000..c00fb357 --- /dev/null +++ b/apps/desktop/src/assets/logos/curseforge.svg @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/apps/desktop/src/assets/logos/modrinth.svg b/apps/desktop/src/assets/logos/modrinth.svg new file mode 100644 index 00000000..e84db411 --- /dev/null +++ b/apps/desktop/src/assets/logos/modrinth.svg @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/apps/desktop/src/types/global.d.ts b/apps/desktop/src/types/global.d.ts index 04a2db47..dff6020d 100644 --- a/apps/desktop/src/types/global.d.ts +++ b/apps/desktop/src/types/global.d.ts @@ -1,6 +1,26 @@ declare namespace globalThis { export type WithIndex = T & { index: number } export type MakeOptional = Omit & Partial> + + type GrowToSize = A['length'] extends N ? A : GrowToSize; + export type FixedArray = GrowToSize; + + // Get size of enum as a type - https://stackoverflow.com/a/68317984/23592613 + type UnionToIntersection = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never; + type UnionToOvlds = UnionToIntersection void : never>; + type PopUnion = UnionToOvlds extends (a: infer A) => void ? A : never; + export type IsUnion = [T] extends [UnionToIntersection] ? false : true; + export type UnionToArray = IsUnion extends true ? UnionToArray>, [PopUnion, ...A]> : [T, ...A]; + + // enum Example { + // how = "how", + // to = "to", + // count = "count", + // enum = "enum", + // entries = "entries" + // } + + // type Result = UnionToArray['length'] // = 5 } // TODO remove in place for specta-generated types diff --git a/apps/desktop/src/ui/components/AnimatedRoutes.tsx b/apps/desktop/src/ui/components/AnimatedRoutes.tsx index 31733eb6..0b7142a1 100644 --- a/apps/desktop/src/ui/components/AnimatedRoutes.tsx +++ b/apps/desktop/src/ui/components/AnimatedRoutes.tsx @@ -1,18 +1,23 @@ -import type { ParentProps } from 'solid-js'; -import { Transition } from 'solid-transition-group'; +import { type ParentProps, mergeProps, splitProps } from 'solid-js'; +import { Transition, type TransitionProps } from 'solid-transition-group'; + +function AnimatedRoutes(props: TransitionProps & ParentProps) { + const defaultProps: TransitionProps = { + mode: 'outin', + enterClass: 'page-animation-enter', + enterActiveClass: 'page-animation-enter-active', + enterToClass: 'page-animation-enter-to', + exitClass: 'page-animation-leave', + exitActiveClass: 'page-animation-leave-active', + exitToClass: 'page-animation-leave-to', + }; + + const [split, rest] = splitProps(props, ['children']); + const merged = mergeProps(defaultProps, rest); -function AnimatedRoutes(props: ParentProps) { return ( - - {props.children} + + {split.children} ); } diff --git a/apps/desktop/src/ui/components/base/Dropdown.module.scss b/apps/desktop/src/ui/components/base/Dropdown.module.scss index ff52cc8c..0270c908 100644 --- a/apps/desktop/src/ui/components/base/Dropdown.module.scss +++ b/apps/desktop/src/ui/components/base/Dropdown.module.scss @@ -1,5 +1,11 @@ .dropdown { - @apply relative w-52; + @apply relative h-full; + + .list { + @apply bg-secondary rounded-lg border border-gray-05 p-1 shadow-md shadow-black/30; + @apply flex flex-col gap-2; + @apply max-h-46 overflow-auto; + } .row { @apply w-full flex flex-row justify-start items-center gap-1.5 h-4 text-nowrap overflow-hidden; diff --git a/apps/desktop/src/ui/components/base/Dropdown.tsx b/apps/desktop/src/ui/components/base/Dropdown.tsx index 1617f817..aef2bab0 100644 --- a/apps/desktop/src/ui/components/base/Dropdown.tsx +++ b/apps/desktop/src/ui/components/base/Dropdown.tsx @@ -1,5 +1,6 @@ import { type Accessor, Index, type JSX, Match, type ParentProps, type ResolvedJSXElement, type Setter, Switch, children, createSignal, splitProps } from 'solid-js'; import { ChevronDownIcon, ChevronUpIcon } from '@untitled-theme/icons-solid'; +import { OverlayScrollbarsComponent } from 'overlayscrollbars-solid'; import Popup from '../overlay/Popup'; import Button from './Button'; import styles from './Dropdown.module.scss'; @@ -71,20 +72,18 @@ function Dropdown(props: DropdownProps) { ref.classList.add(...split.dropdownClass.split(' ')); }} > -
-
- - {(item, index) => ( -
select(index)}> -
- {item()} -
-
+ + + {(item, index) => ( +
select(index)}> +
+ {item()} +
- )} - -
-
+
+ )} + +
); diff --git a/apps/desktop/src/ui/components/overlay/FullscreenOverlay.tsx b/apps/desktop/src/ui/components/overlay/FullscreenOverlay.tsx index 180c7604..8e0a1fbe 100644 --- a/apps/desktop/src/ui/components/overlay/FullscreenOverlay.tsx +++ b/apps/desktop/src/ui/components/overlay/FullscreenOverlay.tsx @@ -5,7 +5,7 @@ import type { } from 'solid-js'; import { Portal } from 'solid-js/web'; -type FullscreenOverlayProps = { +export type FullscreenOverlayProps = { visible: Accessor; setVisible: Setter; mount?: Node | undefined; diff --git a/apps/desktop/src/ui/components/overlay/cluster/ClusterCreationModal.tsx b/apps/desktop/src/ui/components/overlay/cluster/ClusterCreationModal.tsx index 136e86e9..f6aca072 100644 --- a/apps/desktop/src/ui/components/overlay/cluster/ClusterCreationModal.tsx +++ b/apps/desktop/src/ui/components/overlay/cluster/ClusterCreationModal.tsx @@ -1,7 +1,10 @@ -import { type Accessor, type Context, Match, type ParentProps, type Setter, Show, Switch, createContext, createSignal, useContext } from 'solid-js'; -import { Server01Icon } from '@untitled-theme/icons-solid'; -import Modal, { type ModalProps } from '../Modal'; +import { type Accessor, type Context, Match, type ParentProps, type Setter, Switch, createContext, createEffect, createSignal, useContext } from 'solid-js'; +import { ArrowRightIcon, Server01Icon } from '@untitled-theme/icons-solid'; import HeaderImage from '../../../../assets/images/header.png'; +import FullscreenOverlay, { type FullscreenOverlayProps } from '../FullscreenOverlay'; +import ClusterStepOne from './ClusterStepOne'; +import { ClusterStepTwo } from './ClusterStepTwo'; +import Button from '~ui/components/base/Button'; // Why the fuck do I need to use a context for all this??? // TODO: Rewrite for use with the new Modal stacking system @@ -13,8 +16,15 @@ type ClusterModalContextFunc = [ const ClusterModalContext = createContext() as Context; +export enum ClusterModalStages { + STAGE_1_PROVIDER = 0, + STAGE_2_GAME_SETUP = 1, +} + +type ClusterModalStagesLength = UnionToArray['length']; + export function ClusterModalController(props: ParentProps) { - const [step, setStep] = createSignal(1); + const [step, setStep] = createSignal(ClusterModalStages.STAGE_1_PROVIDER); const [visible, setVisible] = createSignal(false); const stepper: ClusterModalContextFunc = [ @@ -23,10 +33,28 @@ export function ClusterModalController(props: ParentProps) { setVisible, ]; + createEffect(() => { + if (visible() === false) + setStep(ClusterModalStages.STAGE_1_PROVIDER); + }); + + const stepComponents = [ + ClusterStepOne, + ClusterStepTwo, + ]; + return ( {props.children} - + + {(stepComponents[step()]!)()} + ); } @@ -35,33 +63,94 @@ export function useClusterModalController() { return useContext(ClusterModalContext); } -type ClusterCreationModalProps = ModalProps & { - step: number; +type ClusterCreationModalProps = FullscreenOverlayProps & { + step: Accessor; + setStep: Setter; + buttonIsNext: boolean; }; function ClusterCreationModal(props: ClusterCreationModalProps) { - // const StepComponent = () => ( - // - // - // - // - // - // ); + // Indexed by the current step + const messages: FixedArray = [ + 'Provider selection', + 'Game Setup', + ]; + + let content!: HTMLDivElement; + + // TODO: For animation + function updateHeight() { + // content. + } return ( - -
-
- Header Image -
-
- -
-

New Cluster

- agagwag + +
+
+
+
+ Header Image +
+
+ +
+ {/** weird positioning?? */} +

New Cluster

+ {messages[props.step()]} +
+
+
+
+
+ {props.children} +
+ +
+ + +
- +
); } diff --git a/apps/desktop/src/ui/components/overlay/cluster/ClusterStepOne.tsx b/apps/desktop/src/ui/components/overlay/cluster/ClusterStepOne.tsx index 8e16c7fa..316e13bc 100644 --- a/apps/desktop/src/ui/components/overlay/cluster/ClusterStepOne.tsx +++ b/apps/desktop/src/ui/components/overlay/cluster/ClusterStepOne.tsx @@ -1,22 +1,56 @@ -import type { JSX } from 'solid-js'; +import { User03Icon } from '@untitled-theme/icons-solid'; +import { For, type JSX } from 'solid-js'; +import ModrinthIcon from '~assets/logos/modrinth.svg?component-solid'; +import CurseforgeIcon from '~assets/logos/curseforge.svg?component-solid'; + +const providers: ProviderCardProps[] = [ + { + name: 'New', + icon: , + }, + { + name: 'Modrinth', + icon: , + }, + { + name: 'Curseforge', + icon: , + }, + { + name: 'New', + icon: , + }, + { + name: 'Modrinth', + icon: , + }, + { + name: 'Curseforge', + icon: , + }, +]; export default function ClusterStepOne() { return ( -
- helo +
+ + {provider => } +
); } interface ProviderCardProps { - icon: () => JSX.Element; + icon: JSX.Element; name: string; }; function ProviderCard(props: ProviderCardProps) { return ( -
- +
+
+ {props.icon} +
{props.name}
); diff --git a/apps/desktop/src/ui/components/overlay/cluster/ClusterStepTwo.tsx b/apps/desktop/src/ui/components/overlay/cluster/ClusterStepTwo.tsx new file mode 100644 index 00000000..f89c2d2c --- /dev/null +++ b/apps/desktop/src/ui/components/overlay/cluster/ClusterStepTwo.tsx @@ -0,0 +1,83 @@ +import { TextInputIcon } from '@untitled-theme/icons-solid'; +import { For, type JSX, splitProps } from 'solid-js'; +import Dropdown from '~ui/components/base/Dropdown'; +import TextField from '~ui/components/base/TextField'; +import VanillaImage from '~assets/logos/vanilla.png'; +import FabricImage from '~assets/logos/fabric.png'; +import ForgeImage from '~assets/logos/forge.png'; +import QuiltImage from '~assets/logos/quilt.png'; + +const loaders: { + name: string; + icon: () => JSX.Element; +}[] = [ + { + name: 'Vanilla', + icon: () => , + }, + { + name: 'Fabric', + icon: () => , + }, + { + name: 'Forge', + icon: () => , + }, + { + name: 'Quilt', + icon: () => , + }, +]; + +export function ClusterStepTwo() { + return ( +
+ + +
+ + +
+
+ ); +} + +type OptionProps = { + header: string; +} & JSX.HTMLAttributes; + +function Option(props: OptionProps) { + const [split, rest] = splitProps(props, ['header', 'class']); + + return ( +
+

{props.header}

+
+ {props.children} +
+
+ ); +} diff --git a/apps/desktop/src/ui/pages/browser/BrowserMain.tsx b/apps/desktop/src/ui/pages/browser/BrowserMain.tsx index c249edc2..dcbc29b3 100644 --- a/apps/desktop/src/ui/pages/browser/BrowserMain.tsx +++ b/apps/desktop/src/ui/pages/browser/BrowserMain.tsx @@ -1,3 +1,4 @@ +import { A } from '@solidjs/router'; import { Download01Icon, FileCode01Icon, FilterLinesIcon, SearchMdIcon } from '@untitled-theme/icons-solid'; import { For, createSignal } from 'solid-js'; import Button from '~ui/components/base/Button'; @@ -115,7 +116,7 @@ function ModsRow(props: ModsRowProps) {

{props.header}

- See all + See all