|
|
|
@ -1,7 +1,8 @@
|
|
|
|
|
<script lang="ts">
|
|
|
|
|
import FlattenedFileList from './FlattenedFileList.svelte';
|
|
|
|
|
import FileTree from './FileTree.svelte';
|
|
|
|
|
|
|
|
|
|
import type { Folder } from '$lib/types';
|
|
|
|
|
import type { File, Folder } from '$lib/types';
|
|
|
|
|
import {
|
|
|
|
|
Tab,
|
|
|
|
|
TabGroup,
|
|
|
|
@ -13,7 +14,14 @@
|
|
|
|
|
DialogTitle,
|
|
|
|
|
DialogDescription
|
|
|
|
|
} from '@rgossiaux/svelte-headlessui';
|
|
|
|
|
import { PlusIcon } from 'svelte-feather-icons';
|
|
|
|
|
import { FolderIcon, PlusIcon } from 'svelte-feather-icons';
|
|
|
|
|
import {
|
|
|
|
|
searchProjects,
|
|
|
|
|
type searchProjectsHit,
|
|
|
|
|
type searchProjectsResponse
|
|
|
|
|
} from '$lib/modrinth';
|
|
|
|
|
import type { z } from 'zod';
|
|
|
|
|
import toml from '@ltd/j-toml';
|
|
|
|
|
let fileTree: Folder = {
|
|
|
|
|
type: 'folder',
|
|
|
|
|
children: {
|
|
|
|
@ -70,49 +78,229 @@ version = "2sIhirkG"
|
|
|
|
|
};
|
|
|
|
|
let blobs: Blob[] = [];
|
|
|
|
|
let isOpen = false;
|
|
|
|
|
let modalStatus:
|
|
|
|
|
| 'none'
|
|
|
|
|
| 'search:mods'
|
|
|
|
|
| 'search:resourcepacks'
|
|
|
|
|
| 'search:shaderpacks'
|
|
|
|
|
| 'edit:simple'
|
|
|
|
|
| 'edit:advanced'
|
|
|
|
|
| 'select:modversion' = 'none';
|
|
|
|
|
|
|
|
|
|
let searchTerm = '';
|
|
|
|
|
let searchTermSaved = '';
|
|
|
|
|
let searchResult: z.infer<typeof searchProjectsResponse>;
|
|
|
|
|
let searchLoading = false;
|
|
|
|
|
let searchLoadingMore = false;
|
|
|
|
|
async function search() {
|
|
|
|
|
// const result = await modrinthClient.searchProjects({
|
|
|
|
|
// te
|
|
|
|
|
// })
|
|
|
|
|
// console.log(result)
|
|
|
|
|
searchLoading = true;
|
|
|
|
|
searchTermSaved = searchTerm;
|
|
|
|
|
let facet: string = '';
|
|
|
|
|
switch (modalStatus) {
|
|
|
|
|
case 'search:mods':
|
|
|
|
|
facet = 'project_type:mod';
|
|
|
|
|
break;
|
|
|
|
|
case 'search:resourcepacks':
|
|
|
|
|
facet = 'project_type:resourcepack';
|
|
|
|
|
break;
|
|
|
|
|
case 'search:shaderpacks':
|
|
|
|
|
facet = 'project_type:shader';
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
const result = await searchProjects({
|
|
|
|
|
query: searchTerm,
|
|
|
|
|
facets: [[facet]]
|
|
|
|
|
});
|
|
|
|
|
console.log(result);
|
|
|
|
|
searchResult = result;
|
|
|
|
|
searchLoading = false;
|
|
|
|
|
}
|
|
|
|
|
async function searchMore() {
|
|
|
|
|
searchLoadingMore = true;
|
|
|
|
|
const result = await searchProjects({
|
|
|
|
|
query: searchTermSaved,
|
|
|
|
|
facets: [['project_type:mod']],
|
|
|
|
|
offset: searchResult.hits.length
|
|
|
|
|
});
|
|
|
|
|
console.log(result);
|
|
|
|
|
searchResult.hits = searchResult.hits.concat(result.hits);
|
|
|
|
|
searchLoadingMore = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function addMod(mod: z.infer<typeof searchProjectsHit>) {}
|
|
|
|
|
|
|
|
|
|
let editPath: string[] = [];
|
|
|
|
|
function getFileFromPath(path: string[], tree: Folder): File | undefined {
|
|
|
|
|
const segment = editPath.shift();
|
|
|
|
|
if (typeof segment == 'undefined') {
|
|
|
|
|
return undefined
|
|
|
|
|
}
|
|
|
|
|
const next = tree.children[segment];
|
|
|
|
|
if (!next) {
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
if (next.type == 'file' && editPath.length == 0) {
|
|
|
|
|
return next;
|
|
|
|
|
} else if (next.type == 'file') {
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
return getFileFromPath(editPath, next);
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<TabGroup>
|
|
|
|
|
<div>
|
|
|
|
|
<TabGroup>
|
|
|
|
|
<TabList>
|
|
|
|
|
<Tab>Simple</Tab>
|
|
|
|
|
<Tab>Advanced</Tab>
|
|
|
|
|
</TabList>
|
|
|
|
|
<TabPanels>
|
|
|
|
|
<TabPanel>
|
|
|
|
|
<h1>Mods <button on:click={() => isOpen = true}><PlusIcon /></button></h1>
|
|
|
|
|
<FlattenedFileList tree={fileTree.children.mods?.type == 'folder'
|
|
|
|
|
<TabPanel class="flex flex-col gap-2">
|
|
|
|
|
<div>
|
|
|
|
|
<div class="flex flex-row gap-2">
|
|
|
|
|
<h1 class="text-xl">Mods</h1>
|
|
|
|
|
<button
|
|
|
|
|
class="rounded w-6 h-6 bg-green outline-lavender flex justify-center items-center"
|
|
|
|
|
on:click={() => (modalStatus = 'search:mods')}
|
|
|
|
|
>
|
|
|
|
|
<PlusIcon class="w-4 h-4 text-black" />
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
<FlattenedFileList
|
|
|
|
|
editFile={(path) => {
|
|
|
|
|
modalStatus = 'edit:simple';
|
|
|
|
|
editPath = ['mods'].concat(path);
|
|
|
|
|
}}
|
|
|
|
|
tree={fileTree.children.mods?.type == 'folder'
|
|
|
|
|
? fileTree.children.mods
|
|
|
|
|
: { type: 'folder', children: {} }} />
|
|
|
|
|
<h1>Resource Packs <button><PlusIcon /></button></h1>
|
|
|
|
|
: { type: 'folder', children: {} }}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<div class="flex flex-row gap-2">
|
|
|
|
|
<h1 class="text-xl">Resource Packs</h1>
|
|
|
|
|
<button
|
|
|
|
|
class="rounded w-6 h-6 bg-green outline-lavender flex justify-center items-center"
|
|
|
|
|
on:click={() => (modalStatus = 'search:resourcepacks')}
|
|
|
|
|
>
|
|
|
|
|
<PlusIcon class="w-4 h-4 text-black" />
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
<FlattenedFileList
|
|
|
|
|
editFile={(path) => {
|
|
|
|
|
modalStatus = 'edit:simple';
|
|
|
|
|
editPath = ['resourcepacks'].concat(path);
|
|
|
|
|
}}
|
|
|
|
|
tree={fileTree.children.resourcepacks?.type == 'folder'
|
|
|
|
|
? fileTree.children.resourcepacks
|
|
|
|
|
: { type: 'folder', children: {} }}
|
|
|
|
|
/>
|
|
|
|
|
<h1>Shader Packs <button><PlusIcon /></button></h1>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<div class="flex flex-row gap-2">
|
|
|
|
|
<h1 class="text-xl">Shader Packs</h1>
|
|
|
|
|
<button
|
|
|
|
|
class="rounded w-6 h-6 bg-green outline-lavender flex justify-center items-center"
|
|
|
|
|
on:click={() => (modalStatus = 'search:shaderpacks')}
|
|
|
|
|
>
|
|
|
|
|
<PlusIcon class="w-4 h-4 text-black" />
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
<FlattenedFileList
|
|
|
|
|
editFile={(path) => {
|
|
|
|
|
modalStatus = 'edit:simple';
|
|
|
|
|
editPath = ['shaderpacks'].concat(path);
|
|
|
|
|
}}
|
|
|
|
|
tree={fileTree.children.shaderpacks?.type == 'folder'
|
|
|
|
|
? fileTree.children.shaderpacks
|
|
|
|
|
: { type: 'folder', children: {} }}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</TabPanel>
|
|
|
|
|
<TabPanel>
|
|
|
|
|
<FolderIcon class="inline" /> /
|
|
|
|
|
<FileTree tree={fileTree} />
|
|
|
|
|
</TabPanel>
|
|
|
|
|
<TabPanel>{JSON.stringify(fileTree)}</TabPanel>
|
|
|
|
|
</TabPanels>
|
|
|
|
|
</TabGroup>
|
|
|
|
|
</TabGroup>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<Dialog open={isOpen} on:close={() => (isOpen = false)} class="fixed top-0 left-0 w-screen h-screen flex justify-center items-center">
|
|
|
|
|
<DialogOverlay class="fixed top-0 left-0 bg-base opacity-50 w-screen h-screen" />
|
|
|
|
|
<div class="z-10 bg-surface0 rounded-xl shadow-xl p-5">
|
|
|
|
|
<DialogTitle>Add Mod</DialogTitle>
|
|
|
|
|
<Dialog
|
|
|
|
|
open={modalStatus != 'none'}
|
|
|
|
|
on:close={() => (modalStatus = 'none')}
|
|
|
|
|
class="fixed top-0 left-0 w-screen h-screen flex overflow-scroll"
|
|
|
|
|
>
|
|
|
|
|
<DialogOverlay class="fixed top-0 left-0 bg-overlay0 opacity-50 w-screen h-screen" />
|
|
|
|
|
<div class="m-auto z-10">
|
|
|
|
|
<div class="bg-base rounded-xl shadow-xl p-5 m-2 flex flex-col gap-1">
|
|
|
|
|
{#if modalStatus == 'search:mods' || modalStatus == 'search:resourcepacks' || modalStatus == 'search:shaderpacks'}
|
|
|
|
|
{#if modalStatus == 'search:mods'}
|
|
|
|
|
<DialogTitle class="text-xl">Add Mod</DialogTitle>
|
|
|
|
|
<DialogDescription>Add a mod to your modpack</DialogDescription>
|
|
|
|
|
<input type="text" placeholder="Search" bind:value={searchTerm}><button on:click={search}>Search</button>
|
|
|
|
|
{:else if modalStatus == 'search:resourcepacks'}
|
|
|
|
|
<DialogTitle class="text-xl">Add Resource Pack</DialogTitle>
|
|
|
|
|
<DialogDescription>Add a resource pack to your modpack</DialogDescription>
|
|
|
|
|
{:else if modalStatus == 'search:shaderpacks'}
|
|
|
|
|
<DialogTitle class="text-xl">Add Shader</DialogTitle>
|
|
|
|
|
<DialogDescription>Add a shader to your modpack</DialogDescription>
|
|
|
|
|
{/if}
|
|
|
|
|
<div class="flex gap-1">
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
placeholder="Search"
|
|
|
|
|
bind:value={searchTerm}
|
|
|
|
|
on:change={search}
|
|
|
|
|
class="bg-surface0 rounded grow border-1 border-overlay0 outline-lavender focus:ring-lavender focus:border-lavender"
|
|
|
|
|
/>
|
|
|
|
|
<button
|
|
|
|
|
on:click={search}
|
|
|
|
|
class="rounded p-3 bg-surface0 hover:bg-surface1 outline-lavender">Search</button
|
|
|
|
|
>
|
|
|
|
|
</div>
|
|
|
|
|
{#if searchLoading}
|
|
|
|
|
Searching...
|
|
|
|
|
{:else if searchResult?.hits.length > 0}
|
|
|
|
|
<ul class="flex flex-col gap-2 mt-2">
|
|
|
|
|
{#each searchResult?.hits ?? [] as hit}
|
|
|
|
|
<li class="flex flex-row rounded bg-surface0 gap-2 p-1 w-md max-w-md min-w-md shadow">
|
|
|
|
|
<img
|
|
|
|
|
src={hit.icon_url}
|
|
|
|
|
alt="{hit.title} icon"
|
|
|
|
|
class="bg-surface1 h-16 w-16 rounded-lg"
|
|
|
|
|
/>
|
|
|
|
|
<div class="grow">
|
|
|
|
|
<h1 class="text-lg">{hit.title}</h1>
|
|
|
|
|
<p>{hit.description}</p>
|
|
|
|
|
<button
|
|
|
|
|
on:click={() => addMod(hit)}
|
|
|
|
|
class="float-right bg-green text-crust p-1 rounded m-1"
|
|
|
|
|
>
|
|
|
|
|
<PlusIcon class="inline" />
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</li>
|
|
|
|
|
{/each}
|
|
|
|
|
{#if searchResult?.hits.length < searchResult?.total_hits}
|
|
|
|
|
<li>
|
|
|
|
|
<button
|
|
|
|
|
on:click={searchMore}
|
|
|
|
|
class="rounded w-full p-3 bg-surface0 hover:bg-surface1 outline-lavender"
|
|
|
|
|
>
|
|
|
|
|
Load More
|
|
|
|
|
</button>
|
|
|
|
|
</li>
|
|
|
|
|
{:else if searchLoadingMore}
|
|
|
|
|
Loading...
|
|
|
|
|
{/if}
|
|
|
|
|
</ul>
|
|
|
|
|
{/if}
|
|
|
|
|
{:else if modalStatus == 'edit:simple'}
|
|
|
|
|
{@const file = getFileFromPath(editPath, fileTree)}
|
|
|
|
|
{#if file && file.inline && file.metafile}
|
|
|
|
|
{@const parsed = toml.parse(file.content)}
|
|
|
|
|
{JSON.stringify(parsed)}
|
|
|
|
|
{/if}
|
|
|
|
|
{/if}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</Dialog>
|
|
|
|
|