refactor: Modal stacking system + transitions :D
This commit is contained in:
parent
d55e97957b
commit
91aa2e03a7
8 changed files with 277 additions and 188 deletions
|
|
@ -83,6 +83,21 @@ h4 { @apply text-lg font-medium; }
|
|||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.modal-animation-enter-active, .modal-animation-leave-active {
|
||||
transition: opacity 160ms, transform 175ms;
|
||||
transition-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
|
||||
}
|
||||
|
||||
.modal-animation-enter, .modal-animation-leave-to {
|
||||
opacity: 0;
|
||||
transform: scale(0);
|
||||
}
|
||||
|
||||
.modal-animation-enter-to, .modal-animation-leave {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
body.reduce-motion *, body.reduce-motion *::after, body.reduce-motion *::before {
|
||||
transition-duration: 0.00001ms !important;
|
||||
animation-duration: 0.00001ms !important;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { SettingsProvider } from './hooks/useSettings';
|
|||
import { ClusterModalController } from './components/overlay/cluster/ClusterCreationModal';
|
||||
import { MultiProvider } from './components/MultiProvider';
|
||||
import { AccountControllerProvider } from './components/overlay/account/AddAccountModal';
|
||||
import { ModalProvider } from './components/overlay/Modal';
|
||||
import { PROGRAM_INFO } from '~bindings';
|
||||
|
||||
function App(props: ParentProps) {
|
||||
|
|
@ -47,9 +48,10 @@ function GlobalContexts(props: ParentProps) {
|
|||
return (
|
||||
<MultiProvider
|
||||
values={[
|
||||
ModalProvider,
|
||||
SettingsProvider,
|
||||
ClusterModalController,
|
||||
AccountControllerProvider,
|
||||
ClusterModalController,
|
||||
]}
|
||||
>
|
||||
{props.children}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { Window } from '@tauri-apps/api/window';
|
||||
import { ChevronLeftIcon, Maximize02Icon, MinusIcon, XCloseIcon } from '@untitled-theme/icons-solid';
|
||||
import type { JSX } from 'solid-js';
|
||||
import { createSignal, onMount } from 'solid-js';
|
||||
import Modal from './overlay/Modal';
|
||||
import { onMount } from 'solid-js';
|
||||
import Modal, { createModal } from './overlay/Modal';
|
||||
import Button from './base/Button';
|
||||
import useSettingsContext from '~ui/hooks/useSettings';
|
||||
|
||||
|
|
@ -23,7 +23,6 @@ function TitlebarButton(props: TitlebarButtonProps) {
|
|||
}
|
||||
|
||||
function WindowFrame() {
|
||||
const [isModalVisible, setModalVisible] = createSignal(false);
|
||||
const settings = useSettingsContext();
|
||||
|
||||
const maximize = () => Window.getCurrent().toggleMaximize();
|
||||
|
|
@ -31,11 +30,22 @@ function WindowFrame() {
|
|||
const quit = () => Window.getCurrent().close();
|
||||
const kill = () => Window.getCurrent().destroy();
|
||||
|
||||
const modal = createModal(props => (
|
||||
<Modal.Simple
|
||||
{...props}
|
||||
title="Close OneLauncher?"
|
||||
buttons={[
|
||||
<Button buttonStyle="secondary" onClick={() => props.hide()}>No</Button>,
|
||||
<Button buttonStyle="danger" onClick={() => kill()}>Yes</Button>,
|
||||
]}
|
||||
/>
|
||||
));
|
||||
|
||||
onMount(() => {
|
||||
Window.getCurrent().onCloseRequested((event) => {
|
||||
if (settings.hide_close_prompt !== true) {
|
||||
event.preventDefault();
|
||||
setModalVisible(true);
|
||||
modal.show();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -51,17 +61,6 @@ function WindowFrame() {
|
|||
<TitlebarButton icon={Maximize02Icon} onClick={() => maximize()} />
|
||||
<TitlebarButton icon={XCloseIcon} onClick={() => quit()} danger />
|
||||
</div>
|
||||
|
||||
<Modal.Simple
|
||||
title="Close OneLauncher?"
|
||||
visible={isModalVisible}
|
||||
setVisible={setModalVisible}
|
||||
zIndex={999999}
|
||||
buttons={[
|
||||
<Button buttonStyle="secondary" onClick={() => setModalVisible(false)}>No</Button>,
|
||||
<Button buttonStyle="danger" onClick={() => kill()}>Yes</Button>,
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,42 +1,119 @@
|
|||
import type {
|
||||
Accessor,
|
||||
Context,
|
||||
JSX,
|
||||
ParentProps,
|
||||
Ref,
|
||||
Setter,
|
||||
} from 'solid-js';
|
||||
import { For, createEffect, createSignal, on } from 'solid-js';
|
||||
import { For, createContext, createSignal, onCleanup, onMount, splitProps, useContext } from 'solid-js';
|
||||
import { mergeRefs } from '@solid-primitives/refs';
|
||||
import { createStore } from 'solid-js/store';
|
||||
import { Transition } from 'solid-transition-group';
|
||||
import Button from '../base/Button';
|
||||
import FullscreenOverlay from './FullscreenOverlay';
|
||||
import type { MakeOptional } from '~types.ts';
|
||||
|
||||
export type ModalProps = {
|
||||
visible: Accessor<boolean>;
|
||||
setVisible: Setter<boolean>;
|
||||
zIndex?: number | undefined;
|
||||
mount?: Node | undefined;
|
||||
ref?: Ref<HTMLDivElement> | undefined;
|
||||
} & ParentProps;
|
||||
interface ModalContextType {
|
||||
displayModal: (modal: () => JSX.Element) => number;
|
||||
closeModal: (index?: number) => void;
|
||||
};
|
||||
|
||||
const ModalContext = createContext() as Context<ModalContextType>;
|
||||
type ModalsList = (() => JSX.Element)[];
|
||||
|
||||
export function ModalProvider(props: ParentProps) {
|
||||
const [modals, setModals] = createStore<ModalsList>([]);
|
||||
|
||||
const context: ModalContextType = {
|
||||
displayModal: (modal) => {
|
||||
setModals(prev => [...prev, modal]);
|
||||
return modals.length - 1;
|
||||
},
|
||||
|
||||
closeModal: (index?: number) => {
|
||||
if (modals.length === 0 || modals.length <= (index || 0))
|
||||
return;
|
||||
|
||||
if (index !== undefined) {
|
||||
setModals(prev => prev.filter((_, i) => i !== index));
|
||||
return;
|
||||
}
|
||||
|
||||
setModals(prev => prev.slice(0, -1));
|
||||
},
|
||||
};
|
||||
|
||||
// TODO: Implement some sort of stacking control?
|
||||
function Modal(props: ModalProps) {
|
||||
return (
|
||||
<FullscreenOverlay
|
||||
visible={props.visible}
|
||||
setVisible={props.setVisible}
|
||||
mount={props.mount}
|
||||
zIndex={props.zIndex || 1000}
|
||||
>
|
||||
{/* <div ref={mergeRefs(props.ref)} class="absolute top-0 bottom-0 left-1/2 flex items-center"> */}
|
||||
<div ref={mergeRefs(props.ref)} class="bg-primary border border-white/5 p-4 rounded-lg text-center flex flex-col gap-y-2 min-w-xs">
|
||||
{props.children}
|
||||
</div>
|
||||
{/* </div> */}
|
||||
</FullscreenOverlay>
|
||||
<ModalContext.Provider value={context}>
|
||||
{props.children}
|
||||
<FullscreenOverlay
|
||||
visible={() => modals.length > 0}
|
||||
setVisible={(value) => {
|
||||
if (value === false)
|
||||
context.closeModal();
|
||||
}}
|
||||
>
|
||||
<Transition
|
||||
mode="outin"
|
||||
enterClass="modal-animation-enter"
|
||||
enterActiveClass="modal-animation-enter-active"
|
||||
enterToClass="modal-animation-enter-to"
|
||||
exitClass="modal-animation-leave"
|
||||
exitActiveClass="modal-animation-leave-active"
|
||||
exitToClass="modal-animation-leave-to"
|
||||
>
|
||||
<>{modals.length > 0 && modals[modals.length - 1]!()}</>
|
||||
</Transition>
|
||||
</FullscreenOverlay>
|
||||
</ModalContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useModalController() {
|
||||
const context = useContext(ModalContext);
|
||||
|
||||
if (!context)
|
||||
throw new Error('useModal must be used within a ModalProvider');
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
interface CreateModal {
|
||||
show: () => void;
|
||||
hide: () => void;
|
||||
};
|
||||
|
||||
export function createModal(el: (props: CreateModal) => JSX.Element): CreateModal {
|
||||
const controller = useModalController();
|
||||
const [index, setIndex] = createSignal<number>();
|
||||
|
||||
const ctx: CreateModal = {
|
||||
show: () => {
|
||||
setIndex(controller.displayModal(() => el(ctx)));
|
||||
},
|
||||
hide: () => {
|
||||
controller.closeModal(index());
|
||||
},
|
||||
};
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
||||
export type ModalProps = {
|
||||
ref?: Ref<HTMLDivElement> | undefined;
|
||||
} & ParentProps & CreateModal;
|
||||
|
||||
function Modal(props: ModalProps) {
|
||||
return (
|
||||
<div ref={mergeRefs(props.ref)} class="bg-primary border border-white/5 p-4 rounded-lg text-center flex flex-col gap-y-2 min-w-xs">
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Modal.SplitProps = function <T extends CreateModal>(props: T) {
|
||||
return splitProps(props, ['hide', 'show']);
|
||||
};
|
||||
|
||||
type ModalButtonProps = string | JSX.Element;
|
||||
|
||||
export type ModalSimpleProps = {
|
||||
|
|
@ -45,20 +122,16 @@ export type ModalSimpleProps = {
|
|||
} & ModalProps;
|
||||
|
||||
Modal.Simple = function (props: ModalSimpleProps) {
|
||||
const [split, rest] = splitProps(props, ['title', 'buttons', 'children']);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
ref={props.ref}
|
||||
setVisible={props.setVisible}
|
||||
visible={props.visible}
|
||||
mount={props.mount}
|
||||
zIndex={props.zIndex}
|
||||
>
|
||||
<h2>{props.title}</h2>
|
||||
<Modal {...rest}>
|
||||
<h2>{split.title}</h2>
|
||||
<div class="flex flex-col">
|
||||
{props.children}
|
||||
{split.children}
|
||||
</div>
|
||||
<div class="flex flex-row gap-x-4 [&>*]:flex-1 mt-2">
|
||||
<For each={props.buttons}>
|
||||
<For each={split.buttons}>
|
||||
{button => (
|
||||
<ModalButton props={button} />
|
||||
)}
|
||||
|
|
@ -75,14 +148,17 @@ type ModalDeleteProps = MakeOptional<ModalSimpleProps, 'title'> & {
|
|||
};
|
||||
|
||||
Modal.Delete = function (props: ModalDeleteProps) {
|
||||
const [split, rest] = splitProps(props, ['title', 'buttons', 'onCancel', 'onDelete', 'timeLeft', 'children']);
|
||||
const [timeLeft, setTimeLeft] = createSignal(1);
|
||||
const [intervalId, setIntervalId] = createSignal<NodeJS.Timeout | undefined>(undefined);
|
||||
|
||||
createEffect(on(() => props.visible(), (visible) => {
|
||||
startInterval(visible);
|
||||
if (visible !== true)
|
||||
onCancel();
|
||||
}));
|
||||
onMount(() => {
|
||||
startInterval(true);
|
||||
});
|
||||
|
||||
onCleanup(() => {
|
||||
clearIntervalId();
|
||||
});
|
||||
|
||||
function clearIntervalId() {
|
||||
if (intervalId())
|
||||
|
|
@ -94,7 +170,7 @@ Modal.Delete = function (props: ModalDeleteProps) {
|
|||
return;
|
||||
|
||||
clearIntervalId();
|
||||
setTimeLeft(props.timeLeft || 3);
|
||||
setTimeLeft(split.timeLeft || 3);
|
||||
|
||||
const intervalId = setInterval(() => {
|
||||
setTimeLeft((prev) => {
|
||||
|
|
@ -111,30 +187,24 @@ Modal.Delete = function (props: ModalDeleteProps) {
|
|||
}
|
||||
|
||||
function onCancel() {
|
||||
if (props.onCancel)
|
||||
props.onCancel();
|
||||
if (split.onCancel)
|
||||
split.onCancel();
|
||||
|
||||
if (props.visible() === true)
|
||||
props.setVisible(false);
|
||||
props.hide();
|
||||
}
|
||||
|
||||
function onDelete() {
|
||||
if (props.onDelete)
|
||||
props.onDelete();
|
||||
if (split.onDelete)
|
||||
split.onDelete();
|
||||
|
||||
if (props.visible() === true)
|
||||
props.setVisible(false);
|
||||
props.hide();
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal.Simple
|
||||
title={props.title || 'Confirm Delete'}
|
||||
ref={props.ref}
|
||||
setVisible={props.setVisible}
|
||||
visible={props.visible}
|
||||
mount={props.mount}
|
||||
zIndex={props.zIndex}
|
||||
buttons={props.buttons || [
|
||||
{...rest}
|
||||
title={split.title || 'Confirm Delete'}
|
||||
buttons={split.buttons || [
|
||||
<Button
|
||||
buttonStyle="secondary"
|
||||
children="Cancel"
|
||||
|
|
@ -148,7 +218,7 @@ Modal.Delete = function (props: ModalDeleteProps) {
|
|||
/>,
|
||||
]}
|
||||
>
|
||||
{props.children || (
|
||||
{split.children || (
|
||||
<>
|
||||
<div class="flex flex-col justify-center items-center gap-y-3">
|
||||
<p>Are you sure you want to delete this item?</p>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { type Accessor, type Context, Match, type ParentProps, type Resource, type Setter, Switch, createContext, createSignal, useContext } from 'solid-js';
|
||||
import { type Context, Match, type ParentProps, type Resource, Switch, createContext, createSignal, useContext } from 'solid-js';
|
||||
import { LinkExternal01Icon } from '@untitled-theme/icons-solid';
|
||||
import Modal from '../Modal';
|
||||
import Modal, { type ModalProps, createModal } from '../Modal';
|
||||
import type { MinecraftCredentials } from '~bindings';
|
||||
import { bridge } from '~imports';
|
||||
import useCommand, { tryResult } from '~ui/hooks/useCommand';
|
||||
|
|
@ -20,10 +20,16 @@ const AccountControllerContext = createContext<AccountControllerContextFunc>() a
|
|||
export function AccountControllerProvider(props: ParentProps) {
|
||||
const [accounts, { refetch: refetchAccounts }] = useCommand(bridge.commands.getUsers);
|
||||
const [defaultAccount, { refetch: refetchDefaultAccount }] = useCommand(bridge.commands.getDefaultUser);
|
||||
const [visible, setVisible] = createSignal(false);
|
||||
|
||||
const [deleteModalUuid, setDeleteModalUuid] = createSignal<string>();
|
||||
const deleteModalVisible = () => deleteModalUuid() !== undefined;
|
||||
|
||||
const addAccountModal = createModal(props => (
|
||||
<AddAccountModal {...props} refetch={refetch} />
|
||||
));
|
||||
|
||||
const deleteAccountModal = createModal(props => (
|
||||
<Modal.Delete {...props} onDelete={() => _forceRemoveAccount(deleteModalUuid())} />
|
||||
));
|
||||
|
||||
function refetch() {
|
||||
refetchAccounts();
|
||||
|
|
@ -35,10 +41,11 @@ export function AccountControllerProvider(props: ParentProps) {
|
|||
}
|
||||
|
||||
async function removeAccount(uuid: string, force?: boolean) {
|
||||
if (force !== true)
|
||||
if (force !== true) {
|
||||
setDeleteModalUuid(uuid);
|
||||
else
|
||||
await _forceRemoveAccount(uuid);
|
||||
deleteAccountModal.show();
|
||||
}
|
||||
else { await _forceRemoveAccount(uuid); }
|
||||
}
|
||||
|
||||
async function _forceRemoveAccount(uuid: string | undefined) {
|
||||
|
|
@ -50,7 +57,7 @@ export function AccountControllerProvider(props: ParentProps) {
|
|||
}
|
||||
|
||||
const func: AccountControllerContextFunc = {
|
||||
displayAddAccount: () => setVisible(true),
|
||||
displayAddAccount: () => addAccountModal.show(),
|
||||
refetch,
|
||||
accounts,
|
||||
defaultAccount,
|
||||
|
|
@ -61,13 +68,6 @@ export function AccountControllerProvider(props: ParentProps) {
|
|||
return (
|
||||
<AccountControllerContext.Provider value={func}>
|
||||
{props.children}
|
||||
<AddAccountModal visible={visible} setVisible={setVisible} refetch={refetch} />
|
||||
|
||||
<Modal.Delete
|
||||
setVisible={value => setDeleteModalUuid(value ? deleteModalUuid() : undefined)}
|
||||
visible={deleteModalVisible}
|
||||
onDelete={() => _forceRemoveAccount(deleteModalUuid())}
|
||||
/>
|
||||
</AccountControllerContext.Provider>
|
||||
);
|
||||
}
|
||||
|
|
@ -84,13 +84,12 @@ enum ModalStage {
|
|||
LoggingIn,
|
||||
}
|
||||
|
||||
interface AddAccountModalProps {
|
||||
visible: Accessor<boolean>;
|
||||
setVisible: Setter<boolean>;
|
||||
interface AddAccountModalProps extends ModalProps {
|
||||
refetch: () => any;
|
||||
}
|
||||
|
||||
function AddAccountModal(props: AddAccountModalProps) {
|
||||
function AddAccountModal(p: AddAccountModalProps) {
|
||||
const [modalProps, props] = Modal.SplitProps(p);
|
||||
const [stage, setStage] = createSignal(ModalStage.Tasks);
|
||||
|
||||
function start() {
|
||||
|
|
@ -100,20 +99,19 @@ function AddAccountModal(props: AddAccountModalProps) {
|
|||
}
|
||||
|
||||
function finish() {
|
||||
props.setVisible(false);
|
||||
modalProps.hide();
|
||||
props.refetch();
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal.Simple
|
||||
{...modalProps}
|
||||
title="Add Account"
|
||||
visible={props.visible}
|
||||
setVisible={props.setVisible}
|
||||
buttons={[
|
||||
<Button
|
||||
buttonStyle="secondary"
|
||||
children="Cancel"
|
||||
onClick={() => props.setVisible(false)}
|
||||
onClick={() => modalProps.hide()}
|
||||
/>,
|
||||
<Button
|
||||
buttonStyle="primary"
|
||||
|
|
@ -141,4 +139,4 @@ function AddAccountModal(props: AddAccountModalProps) {
|
|||
</div>
|
||||
</Modal.Simple>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
import { type Params, useSearchParams } from '@solidjs/router';
|
||||
import { Download01Icon } from '@untitled-theme/icons-solid';
|
||||
import { For, createSignal, onMount } from 'solid-js';
|
||||
import { onMount } from 'solid-js';
|
||||
import { bridge } from '~imports';
|
||||
import Button from '~ui/components/base/Button';
|
||||
import Dropdown from '~ui/components/base/Dropdown';
|
||||
import ModCard from '~ui/components/content/ModCard';
|
||||
import Modal from '~ui/components/overlay/Modal';
|
||||
import useCommand, { tryResult } from '~ui/hooks/useCommand';
|
||||
import useCommand from '~ui/hooks/useCommand';
|
||||
|
||||
interface BrowserModParams extends Params {
|
||||
id: string;
|
||||
|
|
@ -16,31 +14,30 @@ interface BrowserModParams extends Params {
|
|||
// TODO: Refactor the entire browser page and subpages
|
||||
function BrowserMod() {
|
||||
const [params] = useSearchParams<BrowserModParams>();
|
||||
const _isInvalid = !params.id || !params.provider;
|
||||
const [visible, setVisible] = createSignal(false);
|
||||
const [cluster, setCluster] = createSignal<string>();
|
||||
const [clusters, setClusters] = createSignal<[string, string][]>();
|
||||
// const _isInvalid = !params.id || !params.provider;
|
||||
// const [cluster, setCluster] = createSignal<string>();
|
||||
// const [clusters, setClusters] = createSignal<[string, string][]>();
|
||||
const [pkg] = useCommand(bridge.commands.getMod, params.id!);
|
||||
|
||||
function installTo() {
|
||||
setVisible(true);
|
||||
// setVisible(true);
|
||||
}
|
||||
|
||||
function download() {
|
||||
setVisible(false);
|
||||
tryResult(bridge.commands.downloadMod, cluster()!, params.id!).then().catch(err => console.error(err));
|
||||
}
|
||||
// function download() {
|
||||
// setVisible(false);
|
||||
// tryResult(bridge.commands.downloadMod, cluster()!, params.id!).then().catch(err => console.error(err));
|
||||
// }
|
||||
|
||||
function onChange(selected: number) {
|
||||
setCluster(clusters()![selected]![1]);
|
||||
}
|
||||
// function onChange(selected: number) {
|
||||
// setCluster(clusters()![selected]![1]);
|
||||
// }
|
||||
|
||||
onMount(() => {
|
||||
tryResult(bridge.commands.getClusters).then((res) => {
|
||||
const list: [string, string][] = res.map(cluster => [cluster.meta.name, cluster.uuid]);
|
||||
setClusters(list);
|
||||
setCluster(clusters()![0]![1]);
|
||||
});
|
||||
// tryResult(bridge.commands.getClusters).then((res) => {
|
||||
// const list: [string, string][] = res.map(cluster => [cluster.meta.name, cluster.uuid]);
|
||||
// setClusters(list);
|
||||
// setCluster(clusters()![0]![1]);
|
||||
// });
|
||||
});
|
||||
|
||||
return (
|
||||
|
|
@ -60,37 +57,39 @@ function BrowserMod() {
|
|||
<p>TO DO</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Modal.Simple
|
||||
visible={visible}
|
||||
setVisible={setVisible}
|
||||
title="Install To..."
|
||||
buttons={[
|
||||
<Button
|
||||
buttonStyle="secondary"
|
||||
children="Cancel"
|
||||
onClick={() => setVisible(false)}
|
||||
/>,
|
||||
<Button
|
||||
buttonStyle="primary"
|
||||
children="Download"
|
||||
onClick={download}
|
||||
/>,
|
||||
]}
|
||||
>
|
||||
<Dropdown onChange={onChange}>
|
||||
<For each={clusters()}>
|
||||
{cluster => (
|
||||
<Dropdown.Row>{cluster[0]}</Dropdown.Row>
|
||||
)}
|
||||
</For>
|
||||
</Dropdown>
|
||||
</Modal.Simple>
|
||||
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// function InstallToCluster(props: CreateModal) {
|
||||
// return (
|
||||
// <Modal.Simple
|
||||
// {...props}
|
||||
// title="Install To..."
|
||||
// buttons={[
|
||||
// <Button
|
||||
// buttonStyle="secondary"
|
||||
// children="Cancel"
|
||||
// onClick={() => props.hide()}
|
||||
// />,
|
||||
// <Button
|
||||
// buttonStyle="primary"
|
||||
// children="Download"
|
||||
// onClick={download}
|
||||
// />,
|
||||
// ]}
|
||||
// >
|
||||
// <Dropdown onChange={onChange}>
|
||||
// <For each={clusters()}>
|
||||
// {cluster => (
|
||||
// <Dropdown.Row>{cluster[0]}</Dropdown.Row>
|
||||
// )}
|
||||
// </For>
|
||||
// </Dropdown>
|
||||
// </Modal.Simple>
|
||||
// );
|
||||
// }
|
||||
|
||||
BrowserMod.getUrl = function (params: BrowserModParams): string {
|
||||
return `/browser/mod?id=${params.id}&provider=${params.provider}`;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { type Accessor, type Setter, Show, createSignal, untrack } from 'solid-j
|
|||
import { useBeforeLeave, useNavigate } from '@solidjs/router';
|
||||
import * as dialog from '@tauri-apps/plugin-dialog';
|
||||
import { open } from '@tauri-apps/plugin-shell';
|
||||
import { join } from 'pathe';
|
||||
import ClusterCover from '../../components/game/ClusterCover';
|
||||
import LoaderIcon from '../../components/game/LoaderIcon';
|
||||
import Button from '../../components/base/Button';
|
||||
|
|
@ -12,18 +13,16 @@ import Sidebar from '~ui/components/Sidebar';
|
|||
import type { Cluster } from '~bindings';
|
||||
import { secondsToWords, upperFirst } from '~utils';
|
||||
import useClusterContext from '~ui/hooks/useCluster';
|
||||
import Modal from '~ui/components/overlay/Modal';
|
||||
import { bridge } from '~imports';
|
||||
import TextField from '~ui/components/base/TextField';
|
||||
import SettingsRow from '~ui/components/SettingsRow';
|
||||
import useSettingsContext from '~ui/hooks/useSettings';
|
||||
import { join } from 'pathe';
|
||||
import type { ModalProps } from '~ui/components/overlay/Modal';
|
||||
import Modal, { createModal } from '~ui/components/overlay/Modal';
|
||||
|
||||
function ClusterOverview() {
|
||||
const settings = useSettingsContext();
|
||||
const [cluster, { refetch }] = useClusterContext();
|
||||
const [deleteVisible, setDeleteVisible] = createSignal(false);
|
||||
const [saveVisible, setSaveVisible] = createSignal(false);
|
||||
|
||||
const [editMode, setEditMode] = createSignal(false);
|
||||
const [newName, setNewName] = createSignal('');
|
||||
|
|
@ -31,10 +30,18 @@ function ClusterOverview() {
|
|||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const saveModal = createModal(props => (
|
||||
<SaveModal {...props} save={save} dontSave={dontSave} />
|
||||
));
|
||||
|
||||
const deleteModal = createModal(props => (
|
||||
<Modal.Delete {...props} onDelete={deleteCluster} />
|
||||
));
|
||||
|
||||
useBeforeLeave((e) => {
|
||||
if (editMode() && madeChanges()) {
|
||||
e.preventDefault();
|
||||
setSaveVisible(true);
|
||||
saveModal.show();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -75,7 +82,7 @@ function ClusterOverview() {
|
|||
const next = !untrack(() => editMode());
|
||||
|
||||
if (next === false && madeChanges()) {
|
||||
setSaveVisible(true);
|
||||
saveModal.show();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -98,13 +105,13 @@ function ClusterOverview() {
|
|||
}
|
||||
|
||||
function dontSave() {
|
||||
setSaveVisible(false);
|
||||
saveModal.hide();
|
||||
setEditMode(false);
|
||||
}
|
||||
|
||||
function save() {
|
||||
pushEdits();
|
||||
setSaveVisible(false);
|
||||
saveModal.hide();
|
||||
setEditMode(false);
|
||||
}
|
||||
|
||||
|
|
@ -116,9 +123,6 @@ function ClusterOverview() {
|
|||
cluster={cluster()!}
|
||||
refetch={refetch}
|
||||
|
||||
saveVisible={saveVisible}
|
||||
setSaveVisible={setSaveVisible}
|
||||
|
||||
editMode={editMode}
|
||||
newName={newName}
|
||||
setNewName={setNewName}
|
||||
|
|
@ -174,50 +178,52 @@ function ClusterOverview() {
|
|||
buttonStyle="danger"
|
||||
children="Delete"
|
||||
iconLeft={<Trash01Icon />}
|
||||
onClick={() => setDeleteVisible(true)}
|
||||
onClick={() => deleteModal.show()}
|
||||
disabled={editMode()}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</ScrollableContainer>
|
||||
|
||||
<Modal.Delete
|
||||
visible={deleteVisible}
|
||||
setVisible={setDeleteVisible}
|
||||
onDelete={deleteCluster}
|
||||
/>
|
||||
|
||||
<Modal.Simple
|
||||
title="Save Changes?"
|
||||
visible={saveVisible}
|
||||
setVisible={setSaveVisible}
|
||||
children="Do you want to save your changes?"
|
||||
buttons={[
|
||||
<Button
|
||||
buttonStyle="secondary"
|
||||
children="Cancel"
|
||||
onClick={() => setSaveVisible(false)}
|
||||
/>,
|
||||
<Button
|
||||
buttonStyle="danger"
|
||||
children="No"
|
||||
onClick={dontSave}
|
||||
/>,
|
||||
<Button
|
||||
buttonStyle="primary"
|
||||
children="Yes"
|
||||
onClick={save}
|
||||
/>,
|
||||
]}
|
||||
/>
|
||||
</Sidebar.Page>
|
||||
);
|
||||
}
|
||||
|
||||
interface SaveModalProps extends ModalProps {
|
||||
save: () => any;
|
||||
dontSave: () => any;
|
||||
}
|
||||
|
||||
function SaveModal(p: SaveModalProps) {
|
||||
const [modalProps, props] = Modal.SplitProps(p);
|
||||
|
||||
return (
|
||||
<Modal.Simple
|
||||
{...modalProps}
|
||||
title="Save Changes?"
|
||||
children="Do you want to save your changes?"
|
||||
buttons={[
|
||||
<Button
|
||||
buttonStyle="secondary"
|
||||
children="Cancel"
|
||||
onClick={() => modalProps.hide()}
|
||||
/>,
|
||||
<Button
|
||||
buttonStyle="danger"
|
||||
children="No"
|
||||
onClick={props.dontSave}
|
||||
/>,
|
||||
<Button
|
||||
buttonStyle="primary"
|
||||
children="Yes"
|
||||
onClick={props.save}
|
||||
/>,
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
interface BannerProps {
|
||||
cluster: Cluster;
|
||||
saveVisible: Accessor<boolean>;
|
||||
setSaveVisible: Setter<boolean>;
|
||||
editMode: Accessor<boolean>;
|
||||
newName: Accessor<string>;
|
||||
setNewName: Setter<string>;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue