Skip to content

Commit

Permalink
Begin constructing command line
Browse files Browse the repository at this point in the history
  • Loading branch information
GreenAppers committed Nov 5, 2024
1 parent dd24bcb commit 569615a
Show file tree
Hide file tree
Showing 9 changed files with 533 additions and 91 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"electron-squirrel-startup": "^1.0.1",
"electron-store": "^8.2.0",
"framer-motion": "^11.5.6",
"p-settle": "^5.1.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"recharts": "^2.13.0",
Expand Down
205 changes: 160 additions & 45 deletions src/components/Launcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
FormLabel,
Heading,
IconButton,
Image,
Input,
List,
ListItem,
Expand All @@ -20,10 +21,12 @@ import {
Spinner,
Tooltip,
} from '@chakra-ui/react'
import { SettingsIcon } from '@chakra-ui/icons'
import { AddIcon } from '@chakra-ui/icons'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import axios from 'axios'
import React, { useEffect, useState } from 'react'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { v4 as uuidv4 } from 'uuid'

import {
GameInstall,
Expand Down Expand Up @@ -75,6 +78,23 @@ export const useCreateGameInstallMutation = (options?: {
})
}

export const useDeleteGameInstallMutation = (options?: {
callback?: (error?: unknown) => void
}) => {
const queryClient = useQueryClient()
return useMutation<boolean, unknown, GameInstall>({
mutationFn: (deleteGameInstall) =>
window.api.deleteGameInstall(deleteGameInstall),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.useGameInstalls] })
if (options?.callback) options.callback(undefined)
},
onError: (error) => {
if (options?.callback) options.callback(error)
},
})
}

export const useMojangVersionManifestsQuery = () =>
useQuery<MojangVersionManifests>({
queryKey: [QUERY_KEYS.useMojangVersionManifests],
Expand Down Expand Up @@ -129,57 +149,99 @@ export function NewGameInstall(props: {
versionManifests.data,
])

if (versionManifests.isError)
return (
<>
<Box>Error loading version manifests</Box>
{versionManifests.error.message}
</>
)
return (
<Modal isOpen={props.isOpen} onClose={props.onClose}>
<ModalOverlay />
<ModalContent>
<ModalHeader>New Install</ModalHeader>
<ModalCloseButton />
{versionManifests.isError ? (
<ModalBody>
Error loading version manifests
{versionManifests.error.message}
</ModalBody>
) : !versionManifests.isSuccess ||
createGameInstallMutation.isPending ? (
<ModalBody>
<Spinner />
</ModalBody>
) : (
<ModalBody>
<FormControl>
<FormLabel>Version</FormLabel>
<Select
value={newInstall.versionManifest?.id}
onChange={(event) =>
updateNewInstallVersionManifest(event.target.value)
}
>
{versionManifests.data.versions.map((x) => (
<option key={x.id} value={x.id}>
{x.id}
</option>
))}
</Select>
</FormControl>
<FormControl>
<FormLabel>Name</FormLabel>
<Input
type="name"
value={newInstall.name}
onChange={(event) =>
setNewInstall((prev) => ({
...prev,
name: event.target.value,
}))
}
/>
</FormControl>
</ModalBody>
)}
<ModalFooter>
{versionManifests.isSuccess &&
!createGameInstallMutation.isPending && (
<Button
mr={3}
onClick={() => createGameInstallMutation.mutate(newInstall)}
>
Create
</Button>
)}
</ModalFooter>
</ModalContent>
</Modal>
)
}

if (!versionManifests.isSuccess || createGameInstallMutation.isPending)
return <Spinner />
export function LaunchGameInstall(props: {
isOpen: boolean
launchId: string
install: GameInstall
onClose: () => void
}) {
useEffect(() => {
if (!props.isOpen || !props.launchId) return
const handle = window.api.launchGameInstall(
props.launchId,
props.install,
(text) => {
console.log(props.launchId, text)
}
)
return () => window.api.removeListener(handle)
}, [props.isOpen, props.launchId])

return (
<Modal isOpen={props.isOpen} onClose={props.onClose}>
<ModalOverlay />
<ModalContent>
<ModalHeader>New Install</ModalHeader>
<ModalHeader>Launching {props.install.name}</ModalHeader>
<ModalCloseButton />
<ModalBody>
<FormControl>
<FormLabel>Version</FormLabel>
<Select
value={newInstall.versionManifest?.id}
onChange={(event) =>
updateNewInstallVersionManifest(event.target.value)
}
>
{versionManifests.data.versions.map((x) => (
<option key={x.id} value={x.id}>
{x.id}
</option>
))}
</Select>
</FormControl>
<FormControl>
<FormLabel>Name</FormLabel>
<Input
type="name"
value={newInstall.name}
onChange={(event) =>
setNewInstall((prev) => ({ ...prev, name: event.target.value }))
}
/>
</FormControl>
<Spinner />
</ModalBody>
<ModalFooter>
<Button
mr={3}
onClick={() => createGameInstallMutation.mutate(newInstall)}
>
Create
</Button>
<Button onClick={props.onClose}>Close</Button>
</ModalFooter>
</ModalContent>
</Modal>
Expand All @@ -189,6 +251,9 @@ export function NewGameInstall(props: {
export function Launcher() {
const gameInstalls = useGameInstallsQuery()
const [showNewInstall, setShowNewInstall] = useState(false)
const [launched, setLaunched] = useState<
Record<string, { install: GameInstall; show: boolean }>
>({})

return (
<>
Expand All @@ -214,10 +279,60 @@ export function Launcher() {
/>
)}

<List>
{Object.entries(launched).map(
([id, { install, show }]) =>
show && (
<LaunchGameInstall
key={id}
launchId={id}
install={install}
isOpen={show}
onClose={() =>
setLaunched((prev) => ({
...prev,
[id]: { ...prev[id], show: false },
}))
}
/>
)
)}

<List marginTop="1rem">
{gameInstalls.isSuccess &&
gameInstalls.data.map((x) => (
<ListItem key={x.name}>{x.name}</ListItem>
gameInstalls.data.map((x, i) => (
<ListItem key={x.name} height="5rem">
<Flex>
<IconButton
aria-label="gameInstall"
marginRight="1rem"
width="4rem"
icon={
<Image
src={
i % 2 === 0
? 'https://github.com/GreenAppers/EmpireUtils/blob/dd24bcbefda54039d8882b41e16cf455403d3aa9/images/icons/grassblock.png?raw=true'
: 'https://github.com/GreenAppers/EmpireUtils/blob/dd24bcbefda54039d8882b41e16cf455403d3aa9/images/icons/creeper.png?raw=true'
}
/>
}
onClick={() =>
setLaunched((prev) => ({
...prev,
[uuidv4()]: { install: x, show: true },
}))
}
/>
<Box>
<Heading as="h5" size="sm">
{x.name +
(x.versionManifest.id !== x.name
? `: ${x.versionManifest.id}`
: '')}
</Heading>
{x.uuid}
</Box>
</Flex>
</ListItem>
))}
</List>
</>
Expand Down
71 changes: 71 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,84 @@ export const mojangVersionManifests = z.object({
versions: z.array(mojangVersionManifest),
})

export const mojangRule = z.object({
action: z.string(),
features: z.optional(z.record(z.string(), z.boolean())),
os: z.optional(z.object({
arch: z.optional(z.string()),
name: z.optional(z.string()),
})),
})

export const mojangStringsTemplate = z.array(
z.union([
z.string(),
z.object({
rules: z.array(mojangRule),
value: z.union([z.string(), z.array(z.string())]),
}),
])
)

export const mojangHash = z.object({
sha1: z.string(),
size: z.number(),
url: z.string(),
})

export const mojangArtifact = mojangHash.extend({
path: z.string(),
})

export const mojangVersionDetails = z.object({
arguments: z.object({
game: mojangStringsTemplate,
jvm: mojangStringsTemplate,
}),
assetIndex: mojangHash.extend({
id: z.string(),
totalSize: z.number(),
}),
assets: z.string(),
complianceLevel: z.number(),
downloads: z.record(z.string(), mojangHash),
id: z.string(),
javaVersion: z.object({
component: z.string(),
majorVersion: z.number(),
}),
libraries: z.array(
z.object({
downloads: z.object({
artifact: mojangArtifact,
}),
name: z.string(),
rules: z.optional(z.array(mojangRule)),
})
),
logging: z.object({
client: z.object({
argument: z.string(),
file: mojangHash.extend({ id: z.string() }),
type: z.string(),
}),
}),
mainClass: z.string(),
minimumLauncherVersion: z.number(),
releaseTime: z.string(),
time: z.string(),
type: z.string(),
})

export const gameInstall = z.object({
name: z.string(),
path: z.string(),
uuid: z.string(),
versionManifest: mojangVersionManifest,
})

export type MojangRule = z.infer<typeof mojangRule>
export type MojangStringsTemplate = z.infer<typeof mojangStringsTemplate>
export type MojangVersionManifest = z.infer<typeof mojangVersionManifest>
export type MojangVersionManifests = z.infer<typeof mojangVersionManifests>
export type GameInstall = z.infer<typeof gameInstall>
Expand Down
4 changes: 2 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,9 @@ app.on('ready', () => {
)
ipcMain.handle(
CHANNELS.launchGameInstall,
async (_event, gameInstall: GameInstall, launchId: string) => {
async (_event, launchId: string, gameInstall: GameInstall) => {
const channel = LAUNCH_CHANNEL(launchId)
launchInstall(gameInstall, (update) => {
launchInstall(launchId, gameInstall, (update) => {
mainWindow?.webContents.send(channel, update)
}).catch((error) => log.error('launchGameInstall error', error))
return true
Expand Down
6 changes: 2 additions & 4 deletions src/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts

import { contextBridge, ipcRenderer } from 'electron/renderer'
import { v4 as uuidv4 } from 'uuid'

import { CHANNELS, GameInstall, LAUNCH_CHANNEL } from './constants'

Expand All @@ -23,12 +22,12 @@ export const api = {
deleteGameInstall: (gameInstall: GameInstall): Promise<boolean> =>
ipcRenderer.invoke(CHANNELS.deleteGameInstall, gameInstall),
launchGameInstall: (
launchId: string,
gameInstall: GameInstall,
callback: (text: string) => void
): ListenHandle<[string]> => {
const launchId = uuidv4()
ipcRenderer
.invoke(CHANNELS.launchGameInstall, gameInstall, launchId)
.invoke(CHANNELS.launchGameInstall, launchId, gameInstall)
.catch((error) =>
console.log(`${CHANNELS.launchGameInstall} error`, error)
)
Expand Down Expand Up @@ -89,4 +88,3 @@ export const api = {
process.once('loaded', () => {
contextBridge.exposeInMainWorld('api', api)
})

Loading

0 comments on commit 569615a

Please sign in to comment.