refactor: ClusterCreationModal (still awaits refactoring, Contexts shouldn't be used like this)

This commit is contained in:
LynithDev 2024-08-04 15:57:58 +02:00
parent 91aa2e03a7
commit 676c16f14a
2 changed files with 136 additions and 146 deletions

View file

@ -1,7 +1,7 @@
import { type Accessor, type Context, Match, type ParentProps, type Setter, Show, Switch, createContext, createEffect, createSignal, untrack, useContext } from 'solid-js';
import { type Accessor, type Context, Match, type ParentProps, type Setter, Switch, createContext, createEffect, createSignal, onCleanup, untrack, useContext } from 'solid-js';
import { ArrowRightIcon, Server01Icon } from '@untitled-theme/icons-solid';
import HeaderImage from '../../../../assets/images/header.png';
import FullscreenOverlay from '../FullscreenOverlay';
import { createModal } from '../Modal';
import ClusterStepOne from './ClusterStepOne';
import { ClusterStepTwo } from './ClusterStepTwo';
import Button from '~ui/components/base/Button';
@ -22,8 +22,6 @@ interface ClusterModalContextFunc {
updatePartialCluster: PartialClusterUpdateFunc;
start: () => Promise<void>; // Returns true if its finished (created) successfully
finish: () => void;
visible: Accessor<boolean>;
setVisible: Setter<boolean>;
}
const ClusterModalContext = createContext<ClusterModalContextFunc>() as Context<ClusterModalContextFunc>;
@ -37,7 +35,6 @@ type ClusterModalStagesLength = UnionToArray<keyof typeof ClusterModalStages>['l
export function ClusterModalController(props: ParentProps) {
const [step, setStep] = createSignal<number>(ClusterModalStages.STAGE_1_PROVIDER);
const [visible, setVisible] = createSignal(false);
const [partialCluster, setPartialCluster] = createSignal<PartialCluster>({});
const [inProgress, setInProgress] = createSignal(false);
const [error, setError] = createSignal<string | undefined>(undefined);
@ -51,91 +48,92 @@ export function ClusterModalController(props: ParentProps) {
});
};
const start = () => {
setPartialCluster({});
setStep(ClusterModalStages.STAGE_1_PROVIDER);
setError(undefined);
setInProgress(true);
setVisible(true);
const modal = createModal(() => (
// eslint-disable-next-line ts/no-use-before-define -- circular dependency should be refactored later
<ClusterCreationModal controller={context} />
));
return new Promise<void>((resolve, reject) => {
createEffect(() => {
const result = error();
if (inProgress() === true)
return;
if (result === undefined || result.length === 0)
resolve();
else
reject(result);
});
});
};
const finish = async () => {
const untracked = untrack(partialCluster);
if (untracked.name === undefined || untracked.mc_version === undefined || untracked.mod_loader === undefined)
throw new Error('Cluster is missing required fields');
setVisible(false);
await bridge.commands.createCluster({
name: untracked.name!,
mod_loader: untracked.mod_loader!,
mc_version: untracked.mc_version!,
// TODO: Implement the rest of the fields
icon: null,
icon_url: null,
loader_version: null,
package_data: null,
skip: null,
skip_watch: null,
});
setInProgress(false);
};
const stepper: ClusterModalContextFunc = {
const context: ClusterModalContextFunc = {
step,
setStep,
partialCluster,
setPartialCluster,
updatePartialCluster,
start,
finish,
visible,
setVisible,
start() {
setPartialCluster({});
setStep(ClusterModalStages.STAGE_1_PROVIDER);
setError(undefined);
setInProgress(true);
modal.show();
return new Promise<void>((resolve, reject) => {
createEffect(() => {
const result = error();
if (inProgress() === true)
return;
if (result === undefined || result.length === 0)
resolve();
else
reject(result);
});
});
},
async finish() {
const untracked = untrack(partialCluster);
if (untracked.name === undefined || untracked.mc_version === undefined || untracked.mod_loader === undefined)
throw new Error('Cluster is missing required fields');
modal.hide();
await bridge.commands.createCluster({
name: untracked.name!,
mod_loader: untracked.mod_loader!,
mc_version: untracked.mc_version!,
// TODO: Implement the rest of the fields
icon: null,
icon_url: null,
loader_version: null,
package_data: null,
skip: null,
skip_watch: null,
});
setInProgress(false);
},
};
createEffect(() => {
if (visible() === false)
setStep(ClusterModalStages.STAGE_1_PROVIDER);
onCleanup(() => {
setStep(ClusterModalStages.STAGE_1_PROVIDER);
});
return (
<ClusterModalContext.Provider value={stepper}>
<ClusterModalContext.Provider value={context}>
{props.children}
{/* Makes sure theres a new instance of the modal */}
<Show when={visible()}>
<ClusterCreationModal />
</Show>
</ClusterModalContext.Provider>
);
}
export function useClusterModalController() {
return useContext(ClusterModalContext);
const ctx = useContext(ClusterModalContext);
if (!ctx)
throw new Error('useClusterModalController must be used within a ClusterModalController');
return ctx;
}
export interface ClusterStepProps {
isVisible: Accessor<boolean>;
setCanGoForward: Setter<boolean>;
controller: Accessor<ClusterModalContextFunc>;
};
function ClusterCreationModal() {
const controller = useClusterModalController();
const buttonIsNext = () => controller.step() !== ((Object.keys(ClusterModalStages).length / 2) - 1);
function ClusterCreationModal(props: { controller: ClusterModalContextFunc }) {
const buttonIsNext = () => props.controller.step() !== ((Object.keys(ClusterModalStages).length / 2) - 1);
const [canGoForward, setCanGoForward] = createSignal(false);
// Indexed by the current step
@ -150,97 +148,91 @@ function ClusterCreationModal() {
].map((Component, index) => (
Component({
setCanGoForward,
isVisible: () => controller.visible() && controller.step() === index,
isVisible: () => props.controller.step() === index,
controller: () => props.controller,
})
));
const Step = (props: { step: number }) => <>{stepComponents[props.step]}</>;
function cancel() {
controller.setVisible(false);
// controller.setVisible(false);
}
function prev() {
controller.setStep(prev => prev - 1);
props.controller.setStep(prev => prev - 1);
}
function next() {
controller.setStep(prev => prev + 1);
props.controller.setStep(prev => prev + 1);
}
function finish() {
controller.finish();
props.controller.finish();
}
return (
<FullscreenOverlay
visible={controller.visible}
setVisible={controller.setVisible}
>
<div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
<div class="bg-primary rounded-lg text-center flex flex-col min-w-sm">
<div class="flex relative h-25">
<div class="absolute top-0 left-0 w-full h-full">
<img class="h-full w-full rounded-t-lg" src={HeaderImage} alt="Header Image" />
</div>
<div
class="absolute top-0 left-0 px-10 h-full flex flex-row justify-start items-center gap-x-4 bg-[radial-gradient(at_center,#00000077,transparent)]"
>
<Server01Icon class="w-8 h-8" />
<div class="flex flex-col items-start justify-center">
{/** weird positioning?? */}
<h1 class="h-10 -mt-2">New Cluster</h1>
<span>{messages[controller.step()]}</span>
</div>
</div>
</div>
<div class="flex flex-col border border-white/5 rounded-b-lg">
<div class="p-3">
<Step step={controller.step()} />
</div>
<div class="flex flex-row gap-x-2 justify-end pt-0 p-3">
<Switch>
<Match when={controller.step() === 0}>
<Button
children="Cancel"
buttonStyle="ghost"
onClick={cancel}
/>
</Match>
<Match when={controller.step() >= 1}>
<Button
children="Previous"
buttonStyle="ghost"
onClick={prev}
/>
</Match>
</Switch>
<Switch>
<Match when={buttonIsNext() === true}>
<Button
children="Next"
buttonStyle="primary"
disabled={!canGoForward()}
iconRight={<ArrowRightIcon />}
onClick={next}
/>
</Match>
<Match when={buttonIsNext() === false}>
<Button
children="Create"
buttonStyle="primary"
disabled={!canGoForward()}
iconRight={<ArrowRightIcon />}
onClick={finish}
/>
</Match>
</Switch>
</div>
<div class="bg-primary rounded-lg text-center flex flex-col min-w-sm">
<div class="flex relative h-25">
<div class="absolute top-0 left-0 w-full h-full">
<img class="h-full w-full rounded-t-lg" src={HeaderImage} alt="Header Image" />
</div>
<div
class="absolute top-0 left-0 px-10 h-full flex flex-row justify-start items-center gap-x-4 bg-[radial-gradient(at_center,#00000077,transparent)]"
>
<Server01Icon class="w-8 h-8" />
<div class="flex flex-col items-start justify-center">
{/** weird positioning?? */}
<h1 class="h-10 -mt-2">New Cluster</h1>
<span>{messages[props.controller.step()]}</span>
</div>
</div>
</div>
</FullscreenOverlay>
<div class="flex flex-col border border-white/5 rounded-b-lg">
<div class="p-3">
<Step step={props.controller.step()} />
</div>
<div class="flex flex-row gap-x-2 justify-end pt-0 p-3">
<Switch>
<Match when={props.controller.step() === 0}>
<Button
children="Cancel"
buttonStyle="ghost"
onClick={cancel}
/>
</Match>
<Match when={props.controller.step() >= 1}>
<Button
children="Previous"
buttonStyle="ghost"
onClick={prev}
/>
</Match>
</Switch>
<Switch>
<Match when={buttonIsNext() === true}>
<Button
children="Next"
buttonStyle="primary"
disabled={!canGoForward()}
iconRight={<ArrowRightIcon />}
onClick={next}
/>
</Match>
<Match when={buttonIsNext() === false}>
<Button
children="Create"
buttonStyle="primary"
disabled={!canGoForward()}
iconRight={<ArrowRightIcon />}
onClick={finish}
/>
</Match>
</Switch>
</div>
</div>
</div>
);
}

View file

@ -1,6 +1,6 @@
import { TextInputIcon } from '@untitled-theme/icons-solid';
import { For, Index, type JSX, Show, createEffect, createSignal, on, onMount, splitProps, untrack } from 'solid-js';
import { type ClusterStepProps, useClusterModalController } from './ClusterCreationModal';
import type { ClusterStepProps } from './ClusterCreationModal';
import Dropdown from '~ui/components/base/Dropdown';
import TextField from '~ui/components/base/TextField';
import VanillaImage from '~assets/logos/vanilla.png';
@ -37,12 +37,10 @@ const loaders: {
];
export function ClusterStepTwo(props: ClusterStepProps) {
const { partialCluster, updatePartialCluster } = useClusterModalController();
const check = () => {
const hasName = (partialCluster().name?.length ?? 0) > 0;
const hasVersion = (partialCluster().mc_version?.length ?? 0) > 0;
const hasLoader = (partialCluster().mod_loader?.length ?? 0) > 0;
const hasName = (props.controller().partialCluster().name?.length ?? 0) > 0;
const hasVersion = (props.controller().partialCluster().mc_version?.length ?? 0) > 0;
const hasLoader = (props.controller().partialCluster().mod_loader?.length ?? 0) > 0;
props.setCanGoForward(hasName && hasVersion && hasLoader);
};
@ -53,9 +51,9 @@ export function ClusterStepTwo(props: ClusterStepProps) {
check();
}));
const setName = (name: string) => updatePartialCluster('name', name);
const setVersion = (version: string) => updatePartialCluster('mc_version', version);
const setLoader = (loader: Loader | string) => updatePartialCluster('mod_loader', loader.toLowerCase() as Loader);
const setName = (name: string) => props.controller().updatePartialCluster('name', name);
const setVersion = (version: string) => props.controller().updatePartialCluster('mc_version', version);
const setLoader = (loader: Loader | string) => props.controller().updatePartialCluster('mod_loader', loader.toLowerCase() as Loader);
onMount(() => {
setLoader('vanilla');