mcvm 0.25.0

A fast, extensible, and powerful Minecraft launcher
Documentation
import { For, Show, createEffect, createSignal, onCleanup } from "solid-js";
import "./LaunchFooter.css";
import { UnlistenFn, listen, Event } from "@tauri-apps/api/event";
import { invoke } from "@tauri-apps/api";
import { PasswordPrompt } from "../input/PasswordPrompt";
import { Play, Properties } from "../../icons";
import IconTextButton from "../input/IconTextButton";
import IconButton from "../input/IconButton";
import { AuthDisplayEvent, RunningInstanceInfo } from "../../types";
import MicrosoftAuthInfo from "../input/MicrosoftAuthInfo";
import { getIconSrc } from "../../utils";

export default function LaunchFooter(props: LaunchFooterProps) {
	// Basic state
	const [runningInstances, setRunningInstances] = createSignal<
		RunningInstanceInfo[]
	>([]);

	// Prompts
	const [showPasswordPrompt, setShowPasswordPrompt] = createSignal(false);
	const [authInfo, setAuthInfo] = createSignal<AuthDisplayEvent | null>(null);
	const [passwordPromptMessage, setPasswordPromptMessage] = createSignal("");
	// Unlisteners for tauri events
	const [unlistens, setUnlistens] = createSignal<UnlistenFn[]>([]);

	async function updateRunningInstances() {
		setRunningInstances(await invoke("get_running_instances"));
	}

	// Setup and clean up event listeners for updating state
	createEffect(async () => {
		updateRunningInstances();

		for (let unlisten of unlistens()) {
			unlisten();
		}

		let updateStatePromise = listen("update_run_state", () => {
			console.log("Updating run state");
			updateRunningInstances();
		});

		let authInfoPromise = listen(
			"mcvm_display_auth_info",
			(event: Event<AuthDisplayEvent>) => {
				setAuthInfo(event.payload);
			}
		);

		let authInfoClosePromise = listen("mcvm_close_auth_info", () => {
			setAuthInfo(null);
		});

		let passwordPromise = listen(
			"mcvm_display_password_prompt",
			(event: Event<string>) => {
				setShowPasswordPrompt(true);
				setPasswordPromptMessage(event.payload);
			}
		);

		let stoppedPromise = listen("game_finished", (event: Event<string>) => {
			console.log("Stopped instance " + event.payload);
			stopGame(event.payload);
		});

		let eventUnlistens = await Promise.all([
			updateStatePromise,
			authInfoPromise,
			authInfoClosePromise,
			passwordPromise,
			stoppedPromise,
		]);

		setUnlistens(eventUnlistens);
	}, []);

	onCleanup(() => {
		for (const unlisten of unlistens()) {
			unlisten();
		}
	});

	async function launch() {
		if (props.selectedInstance === null) {
			return;
		}

		// Prevent launching until the current authentication screens are finished
		if (showPasswordPrompt() || authInfo() !== null) {
			return;
		}

		let launchPromise = invoke("launch_game", {
			instanceId: props.selectedInstance,
			offline: false,
		});

		await Promise.all([launchPromise]);

		updateRunningInstances();
	}

	async function stopGame(instance: string) {
		setAuthInfo(null);
		setShowPasswordPrompt(false);
		await invoke("stop_game", { instance: instance });
		updateRunningInstances();
	}

	return (
		<div class="launch-footer border">
			<div class="launch-footer-section launch-footer-left"></div>
			<div class="launch-footer-section launch-footer-center">
				<div class="launch-footer-center-inner">
					<div class="launch-button-container">
						<div class="launch-footer-config">
							<IconButton
								icon={Properties}
								size="28px"
								color="var(--bg2)"
								selectedColor="var(--accent)"
								onClick={() => {
									if (props.selectedInstance != null) {
										window.location.href = `/instance_config/${props.selectedInstance}`;
									}
								}}
								selected={false}
							/>
						</div>
						<div class="launch-button">
							<IconTextButton
								icon={Play}
								text="Launch"
								size="22px"
								color="var(--bg2)"
								selectedColor="var(--accent)"
								onClick={() => {
									launch();
								}}
								selected={props.selectedInstance !== null}
							/>
						</div>
					</div>
				</div>
			</div>
			<div class="launch-footer-section launch-footer-right">
				<RunningInstanceList instances={runningInstances()} onStop={stopGame} />
			</div>

			<Show when={authInfo() !== null}>
				<MicrosoftAuthInfo
					event={authInfo() as AuthDisplayEvent}
					onCancel={() => {
						setAuthInfo(null);
						if (props.selectedInstance !== null) {
							stopGame(props.selectedInstance);
						}
					}}
				/>
			</Show>
			<Show when={showPasswordPrompt()}>
				<PasswordPrompt
					onSubmit={() => setShowPasswordPrompt(false)}
					message={passwordPromptMessage()}
				/>
			</Show>
		</div>
	);
}

export interface LaunchFooterProps {
	selectedInstance: string | null;
}

// Displays a list of instance icons that can be interacted with
function RunningInstanceList(props: RunningInstanceListProps) {
	return (
		<div class="running-instance-list">
			<For each={props.instances}>
				{(instance) => (
					<img
						src={getIconSrc(instance.info.icon)}
						class="running-instance-list-icon border"
						title={
							instance.info.name != null ? instance.info.name : instance.info.id
						}
					/>
				)}
			</For>
		</div>
	);
}

interface RunningInstanceListProps {
	instances: RunningInstanceInfo[];
	onStop: (instance: string) => void;
}