Skip to content

Commit

Permalink
updated mod sources
Browse files Browse the repository at this point in the history
  • Loading branch information
MasterGordon committed Mar 18, 2024
1 parent 638b89b commit 801cbf9
Show file tree
Hide file tree
Showing 17 changed files with 461 additions and 35 deletions.
1 change: 1 addition & 0 deletions apps/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@slime-launcher/downloader": "workspace:*",
"@slime-launcher/mod-sources": "workspace:*",
"@slime-launcher/piston": "workspace:*",
"@supercharge/promise-pool": "^2.3.2",
"@tanstack/react-query": "^4.2.3",
Expand Down
4 changes: 4 additions & 0 deletions apps/frontend/packages/main/src/api/mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import {
deleteInstance,
openInstance,
fetchInstanceMods,
enableMod,
disableMod,
} from "@slime-launcher/piston";
import { inspect } from "util";

Expand All @@ -31,6 +33,8 @@ export const mutate = {
updateInstance,
openInstance,
fetchInstanceMods,
enableMod,
disableMod,
};

type Event = {
Expand Down
4 changes: 3 additions & 1 deletion apps/frontend/packages/main/src/mainWindow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import {
registerMutationIpc,
registerListeners,
} from "./api";
import { generateSettings } from "@slime-launcher/piston";
import { generateSettings, modSourceManager } from "@slime-launcher/piston";
import { modrinthSource } from "@slime-launcher/mod-sources";

async function createWindow() {
const browserWindow = new BrowserWindow({
Expand Down Expand Up @@ -47,6 +48,7 @@ async function createWindow() {
registerQueryIpc();
registerListeners();
void generateSettings();
modSourceManager.registerModSource(modrinthSource);

return browserWindow;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const InstanceSettingsMenu: React.FC = () => {
onChange={handleTabsChange}
background="gray.700"
index={tabIndex}
borderBottomLeftRadius="md"
>
<TabList
flexDirection="column"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,38 +1,102 @@
import { Table, TableContainer, Tbody, Th, Thead, Tr } from "@chakra-ui/react";
import { useEffect } from "react";
import type { BoxProps } from "@chakra-ui/react";
import {
Button,
Checkbox,
Flex,
Input,
Table,
TableContainer,
Tbody,
Td as ChakraTd,
Th as ChakraTh,
Thead,
Tooltip,
} from "@chakra-ui/react";
import { Fragment, useEffect, useRef, useState } from "react";
import { filter } from "../../../../utils/filter";
import { useMainMutation } from "../../../../hooks/main";
import { useInstance } from "../../InstanceProvider";

const Td: React.FC<BoxProps> = (props) => <ChakraTd as="div" {...props} />;
const Th: React.FC<BoxProps> = (props) => <ChakraTh as="div" {...props} />;

const Mods: React.FC = () => {
const instance = useInstance();
const fetchMods = useMainMutation("fetchInstanceMods");
const enableMod = useMainMutation("enableMod");
const disableMod = useMainMutation("disableMod");
const [keyword, setKeyword] = useState("");
const [filteredMods, setFilteredMods] = useState(instance.mods);
const timer = useRef<NodeJS.Timeout>();
useEffect(() => {
if (timer.current) clearTimeout(timer.current);
timer.current = setTimeout(() => {
setFilteredMods(filter(instance.mods, keyword, "title", "fileName"));
}, 200);
}, [instance.mods, keyword]);

useEffect(() => {
fetchMods.mutate(instance.path);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [instance.path]);

return (
<TableContainer>
<Table>
<Thead>
<Tr>
<>
<Flex pos="sticky" top="0" p="4" background="gray.700" zIndex="1" gap="4">
<Input
placeholder="Search"
value={keyword}
onChange={(e) => setKeyword(e.target.value)}
/>
<Button paddingX="8">Check Updates</Button>
<Button paddingX="8">Add Mods</Button>
</Flex>
<TableContainer whiteSpace="normal">
<Table as="div">
<Thead display="grid" gridTemplateColumns="5fr 3fr 120px" as="div">
<Th>Mod</Th>
<Th>Version</Th>
<Th>Actions</Th>
</Tr>
</Thead>
<Tbody>
{instance.mods.map((mod) => (
<Tr key={mod.fileName}>
<Th>{mod.title || mod.fileName}</Th>
<Th>{mod.version}</Th>
<Th></Th>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
</Thead>
<Tbody display="grid" gridTemplateColumns="5fr 3fr 120px" as="div">
{filteredMods.map((mod) => (
<Fragment key={mod.fileName}>
<Td display="flex" alignItems="start">
<Tooltip
label={mod.fileName}
shouldWrapChildren={false}
isOpen={mod.title ? undefined : false}
>
<>
<Checkbox
defaultChecked={mod.fileName.endsWith(".jar")}
marginRight="2"
onChange={(e) => {
if (e.target.checked) {
enableMod.mutate({
instancePath: instance.path,
fileName: mod.fileName,
});
} else {
disableMod.mutate({
instancePath: instance.path,
fileName: mod.fileName,
});
}
}}
></Checkbox>
{mod.title ?? mod.fileName}
</>
</Tooltip>
</Td>
<Td wordBreak="break-all">{mod.version}</Td>
<Td></Td>
</Fragment>
))}
</Tbody>
</Table>
</TableContainer>
</>
);
};

Expand Down
41 changes: 41 additions & 0 deletions apps/frontend/packages/renderer/src/utils/filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
export const filter = <T extends object>(
array: T[],
keyword: string,
key: keyof T,
alternativeKey?: keyof T,
) => {
console.time("filter");
const fuzzyRegex = new RegExp(keyword.split("").join(".*"), "i");
const startingRegex = new RegExp(`^${keyword}`, "i");
const containsRegex = new RegExp(`${keyword}`, "i");
const word = (item: T): string => {
if (!alternativeKey) return item[key] as unknown as string;
return (item[key] ?? item[alternativeKey]) as unknown as string;
};
const weight = (item: T) => {
const itemString = word(item);
if (startingRegex.test(itemString)) {
return 1;
}
if (containsRegex.test(itemString)) {
return 2;
}
if (fuzzyRegex.test(itemString)) {
return 3;
}
return 4;
};
const filteredArray = array
.filter((item) => fuzzyRegex.test(word(item)))
// sort by weight and alphabetical order
.sort((a, b) => {
const weightA = weight(a);
const weightB = weight(b);
if (weightA === weightB) {
return word(a).localeCompare(word(b));
}
return weightA - weightB;
});
console.timeEnd("filter");
return filteredArray;
};
3 changes: 2 additions & 1 deletion apps/frontend/packages/renderer/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"#preload": ["../preload/src/index"]
},
"lib": ["ESNext", "dom", "dom.iterable"],
"allowSyntheticDefaultImports": true
"allowSyntheticDefaultImports": true,
"verbatimModuleSyntax": true
},
"include": [
"src/**/*.ts",
Expand Down
4 changes: 1 addition & 3 deletions packages/mod-sources/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
import { modrinthSource } from "./modrinth-source";

export const modSources = [modrinthSource];
export { modrinthSource } from "./modrinth-source";
2 changes: 1 addition & 1 deletion packages/mod-sources/src/modrinth-source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export const modrinthSource: ModSource = {
title: project.title,
modId: project.id,
fileId: version.id,
source: sourceId,
source: [sourceId],
fileName: path.basename(filePath),
sha512: await getHash(filePath, "sha512"),
};
Expand Down
3 changes: 3 additions & 0 deletions packages/piston/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"devDependencies": {
"@slime-launcher/eslint-config-custom": "workspace:*",
"@slime-launcher/tsconfig": "workspace:*",
"@types/better-sqlite3": "^7.6.5",
"@types/fs-extra": "^9.0.13",
"@types/node": "^18.7.13",
"eslint": "^7.32.0",
Expand All @@ -23,6 +24,8 @@
"@slime-launcher/unpack": "workspace:*",
"@xmcl/mod-parser": "^3.3.3",
"axios": "^0.27.2",
"better-sqlite3": "^8.6.0",
"drizzle-orm": "^0.28.6",
"fast-deep-equal": "^3.1.3",
"fs-extra": "^10.1.0",
"glob": "10.3.3",
Expand Down
2 changes: 2 additions & 0 deletions packages/piston/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,7 @@ export * from "./instances/kill-instance";
export * from "./instances/delete-instance";
export * from "./instances/update-instance";
export * from "./instances/fetch-instance-mods";
export * from "./instances/toggle-mod";
export * from "./utils/open-instance";
export * from "./interfaces/ModSource";
export * from "./mod-source-manager";
2 changes: 1 addition & 1 deletion packages/piston/src/instances/Instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export interface InstanceMod {
modId?: number | string;
fileId?: number | string;
title?: string;
source?: string | "unknown";
source?: string[];
fileName: string;
version?: string;
sha512?: string;
Expand Down
37 changes: 37 additions & 0 deletions packages/piston/src/instances/instance-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,43 @@ const instanceManager = {
instanceProcesses.delete(instancePath);
}
},
enableMod: async (instancePath: string, fileName: string) => {
if (!fileName.endsWith(".jar.disabled")) return;
const instance = instanceManager.getInstance(instancePath);
if (!instance) throw new Error(`Instance ${instancePath} not found`);
await fs.rename(
`${instancesPath}/${instancePath}/mods/${fileName}`,
`${instancesPath}/${instancePath}/mods/${fileName.replace(
".disabled",
"",
)}`,
);
await instanceManager.updateInstance(instancePath, {
mods: instance.mods.map((mod) => {
if (mod.fileName === fileName) {
return { ...mod, fileName: mod.fileName.replace(".disabled", "") };
}
return mod;
}),
});
},
disableMod: async (instancePath: string, fileName: string) => {
if (fileName.endsWith(".jar.disabled")) return;
const instance = instanceManager.getInstance(instancePath);
if (!instance) throw new Error(`Instance ${instancePath} not found`);
await fs.rename(
`${instancesPath}/${instancePath}/mods/${fileName}`,
`${instancesPath}/${instancePath}/mods/${fileName}.disabled`,
);
await instanceManager.updateInstance(instancePath, {
mods: instance.mods.map((mod) => {
if (mod.fileName === fileName) {
return { ...mod, fileName: `${mod.fileName}.disabled` };
}
return mod;
}),
});
},
};
instanceManager.loadInstances();

Expand Down
21 changes: 21 additions & 0 deletions packages/piston/src/instances/toggle-mod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { instanceManager } from "./instance-manager";

export const enableMod = async ({
instancePath,
fileName,
}: {
instancePath: string;
fileName: string;
}) => {
instanceManager.enableMod(instancePath, fileName);
};

export const disableMod = async ({
instancePath,
fileName,
}: {
instancePath: string;
fileName: string;
}) => {
instanceManager.disableMod(instancePath, fileName);
};
19 changes: 19 additions & 0 deletions packages/piston/src/mod-source-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { ModSource } from "./interfaces/ModSource";

class ModSourceManager {
private modSources: ModSource[] = [];

public registerModSource(modSource: ModSource): void {
this.modSources.push(modSource);
}

public getModSources(): ModSource[] {
return this.modSources;
}

public getModSource(id: string): ModSource | undefined {
return this.modSources.find((modSource) => modSource.id === id);
}
}

export const modSourceManager = new ModSourceManager();
11 changes: 11 additions & 0 deletions packages/piston/src/utils/cache-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { drizzle } from "drizzle-orm/better-sqlite3";
import Database from "better-sqlite3";
import getAppData from "./get-app-data";
import path from "path";

const sqlite = new Database(path.join(getAppData(), "cache.db"), {
verbose: console.log,
});
const db = drizzle(sqlite);

export const cacheManager = {};
Loading

0 comments on commit 801cbf9

Please sign in to comment.