You've already forked AstralRinth
forked from didirus/AstralRinth
Start search page + Add i18n & generation base
This commit is contained in:
129
theseus_gui/src/components/ProjectCard.svelte
Normal file
129
theseus_gui/src/components/ProjectCard.svelte
Normal file
@@ -0,0 +1,129 @@
|
||||
<script lang="ts">
|
||||
import { t } from 'svelte-intl-precompile';
|
||||
import IconHeart from 'virtual:icons/lucide/heart';
|
||||
import IconDownload from 'virtual:icons/heroicons-outline/download';
|
||||
import IconCalendar from 'virtual:icons/lucide/calendar';
|
||||
import { ago, simplify } from 'omorphia/utils';
|
||||
import { Avatar, Button } from 'omorphia';
|
||||
import { tagIcons } from '$generated/tags.json';
|
||||
|
||||
export let project;
|
||||
|
||||
// @ts-ignore: Author is only available in the result
|
||||
let author = project.author ?? '';
|
||||
|
||||
// @ts-ignore: ID is in different locations in the result and project
|
||||
let id = project.id ?? project.project_id;
|
||||
|
||||
// @ts-ignore: Updated is in different locations in the result and project
|
||||
let updated = project.date_modified ?? project.updated;
|
||||
|
||||
const href = `/${project.project_type}/${project.slug || id}`;
|
||||
</script>
|
||||
|
||||
<div class="card project-card">
|
||||
<a {href} tabindex="-1">
|
||||
<Avatar src={project.icon_url} size="md" />
|
||||
</a>
|
||||
|
||||
<div class="project-card__info">
|
||||
<div class="project-card__info__top">
|
||||
<div class="project-card__info__top__text">
|
||||
<span
|
||||
><a class="project-card__info__top__text__title" {href}>{project.title}</a>
|
||||
{#if author}
|
||||
<a href="/user/{author}" class="project-card__info__top__text__author"
|
||||
>{@html $t('generic.byline', { values: { author } })}</a
|
||||
>
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
<p class="summary">
|
||||
{project.description}
|
||||
</p>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<Button color="primary"><IconDownload />Install</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tag-group">
|
||||
{#each project.categories as category}
|
||||
<div class="tag">
|
||||
{@html tagIcons[category] || '?'}
|
||||
{$t(`tags.${category}`)}
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
<span class="stat">
|
||||
<IconDownload />
|
||||
<b>{simplify(project.downloads)}</b>
|
||||
</span>
|
||||
<span class="tag">
|
||||
<IconCalendar />
|
||||
{@html $t('stats.updated', { values: { ago: ago(updated) } })}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if !$$slots.actions}{:else}
|
||||
<slot name="actions" />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
.project-card {
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
padding: 10px;
|
||||
grid-gap: 1rem;
|
||||
|
||||
&__info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
grid-gap: 0.25rem;
|
||||
line-height: 100%;
|
||||
margin-top: 0.2rem;
|
||||
width: 100%;
|
||||
|
||||
&__top {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
|
||||
&__text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
|
||||
&__title {
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
&__author {
|
||||
white-space: nowrap;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.summary {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
width: calc(100vw - 224px - 24px - 16px - 64px - 130px);
|
||||
}
|
||||
}
|
||||
|
||||
:global(.button-group) {
|
||||
margin-left: auto;
|
||||
height: 33px;
|
||||
}
|
||||
}
|
||||
|
||||
.tag-group {
|
||||
.stat {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
<div class="vertical-nav">
|
||||
{#each items as item (item.href)}
|
||||
<a class="nav-item" href="/{item.href}" class:active={path[level] === item.href}>
|
||||
<a class="nav-item" href="/{item.href}" class:active={path[level] === item.href} sveltekit:prefetch>
|
||||
<svelte:component this={item.icon} />
|
||||
{item.label}
|
||||
</a>
|
||||
|
||||
@@ -127,7 +127,7 @@
|
||||
grid-gap: 8px;
|
||||
|
||||
:global(button) {
|
||||
width: 34px;
|
||||
width: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,71 +1,91 @@
|
||||
<script lang="ts">
|
||||
import IconChevronLeft from 'virtual:icons/lucide/chevron-left';
|
||||
import IconChevronRight from 'virtual:icons/lucide/chevron-right';
|
||||
import IconCaretRight from 'virtual:icons/carbon/caret-right';
|
||||
import { page } from '$app/stores'
|
||||
import IconChevronLeft from 'virtual:icons/lucide/chevron-left';
|
||||
import IconChevronRight from 'virtual:icons/lucide/chevron-right';
|
||||
import IconCaretRight from 'virtual:icons/carbon/caret-right';
|
||||
import { page } from '$app/stores';
|
||||
|
||||
let path: string[];
|
||||
$: path = $page.url.pathname
|
||||
.substring(1)
|
||||
.split('/')
|
||||
let path: string[];
|
||||
$: path = $page.url.pathname.substring(1).split('/');
|
||||
</script>
|
||||
|
||||
<div class="status-bar">
|
||||
<div class="page-nav">
|
||||
<button title="Back" on:click={() => window.history.back()}>
|
||||
<IconChevronLeft />
|
||||
</button>
|
||||
<button title="Forward" on:click={() => window.history.forward()}>
|
||||
<IconChevronRight />
|
||||
</button>
|
||||
</div>
|
||||
<div class="page-nav">
|
||||
<button title="Back" on:click={() => window.history.back()}>
|
||||
<IconChevronLeft />
|
||||
</button>
|
||||
<button title="Forward" on:click={() => window.history.forward()}>
|
||||
<IconChevronRight />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="breadcrumbs">
|
||||
{#each path as crumb, index}
|
||||
{#if index !== 0}
|
||||
<IconCaretRight />
|
||||
{/if}
|
||||
<a class="breadcrumbs__crumb" href={crumb}>{crumb || 'home'}</a>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="breadcrumbs">
|
||||
{#each path as crumb, index}
|
||||
{#if index !== 0}
|
||||
<IconCaretRight />
|
||||
{/if}
|
||||
<a class="breadcrumbs__crumb" href={crumb}>{crumb || 'home'}</a>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="statuses">
|
||||
<div>Updating 12 mods...</div>
|
||||
<div>236 mods installed</div>
|
||||
</div>
|
||||
<div class="statuses">
|
||||
<div>Updating 12 mods...</div>
|
||||
<div>236 mods installed</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
.status-bar {
|
||||
.status-bar {
|
||||
display: flex;
|
||||
padding: 0.75rem;
|
||||
grid-gap: 0.75rem;
|
||||
background-color: var(--status-bg);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
box-shadow: var(--shadow-raised);
|
||||
|
||||
.page-nav {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
|
||||
button {
|
||||
display: flex;
|
||||
padding: 0.75rem;
|
||||
grid-gap: 0.75rem;
|
||||
background-color: var(--status-bg);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
box-shadow: var(--shadow-raised);
|
||||
}
|
||||
}
|
||||
|
||||
.breadcrumbs {
|
||||
display: flex;
|
||||
grid-gap: 0.25rem;
|
||||
text-transform: capitalize;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
grid-gap: 0.25rem;
|
||||
text-transform: capitalize;
|
||||
align-items: center;
|
||||
overflow-x: auto;
|
||||
|
||||
&__crumb:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
/* Hide scrollbar */
|
||||
&::-webkit-scrollbar {
|
||||
display: none; /* Chrome, Safari and Opera */
|
||||
}
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
|
||||
:global(.icon) {
|
||||
color: var(--color-text-lightest)
|
||||
}
|
||||
&__crumb:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
:global(.icon) {
|
||||
color: var(--color-text-lightest);
|
||||
}
|
||||
}
|
||||
|
||||
.statuses {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
grid-gap: 1rem;
|
||||
color: var(--color-text-lightest);
|
||||
margin-left: auto;
|
||||
align-items: flex-end;
|
||||
justify-content: flex-end;
|
||||
display: flex;
|
||||
grid-gap: 1rem;
|
||||
color: var(--color-text-lightest);
|
||||
font-size: 13px;
|
||||
line-height: 1.2;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
<script context="module" lang="ts">
|
||||
import { init, waitLocale } from 'svelte-intl-precompile'
|
||||
import { registerAll } from '$locales'
|
||||
|
||||
registerAll()
|
||||
|
||||
/** @type {import('@sveltejs/kit').Load} */
|
||||
export async function load({fetch, session, stuff}) {
|
||||
init({
|
||||
fallbackLocale: 'en',
|
||||
initialLocale: session.acceptedLanguage,
|
||||
})
|
||||
await waitLocale()
|
||||
|
||||
return {}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import '@fontsource/inter'
|
||||
import 'omorphia/styles.postcss'
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
</script>
|
||||
|
||||
<div class="section">
|
||||
<TitledSection title="Override global settings" toggleable=true>
|
||||
<TitledSection title="Override global settings" toggleable={true}>
|
||||
<GlobalSettings />
|
||||
</TitledSection>
|
||||
</div>
|
||||
|
||||
12
theseus_gui/src/routes/search/__layout.svelte
Normal file
12
theseus_gui/src/routes/search/__layout.svelte
Normal file
@@ -0,0 +1,12 @@
|
||||
<div class="layout-search">
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
.layout-search {
|
||||
display: flex;
|
||||
padding: 16px;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
</style>
|
||||
90
theseus_gui/src/routes/search/index.svelte
Normal file
90
theseus_gui/src/routes/search/index.svelte
Normal file
@@ -0,0 +1,90 @@
|
||||
<script lang="ts" context="module">
|
||||
/** @type {import('./index').Load} */
|
||||
export async function load({ fetch }) {
|
||||
const response = await fetch(`https://api.modrinth.com/v2/search?query=&limit=10&offset=0&index=relevance`);
|
||||
|
||||
return {
|
||||
props: {
|
||||
projects: response.ok && (await response.json()).hits
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import IconSearch from 'virtual:icons/heroicons-outline/search';
|
||||
import IconSortDescending from 'virtual:icons/heroicons-outline/sort-descending';
|
||||
import IconBox from 'virtual:icons/lucide/box';
|
||||
import IconGlobe from 'virtual:icons/lucide/globe';
|
||||
import IconCpu from 'virtual:icons/lucide/cpu';
|
||||
import IconTruck from 'virtual:icons/lucide/truck';
|
||||
import IconFileText from 'virtual:icons/lucide/file-text';
|
||||
|
||||
import { TextInput, Button } from 'omorphia';
|
||||
import ProjectCard from '$components/ProjectCard.svelte';
|
||||
|
||||
export let projects;
|
||||
</script>
|
||||
|
||||
<div class="controls">
|
||||
<div class="controls__row">
|
||||
<TextInput placeholder="Search..." icon={IconSearch} />
|
||||
<Button color="tertiary"><IconSortDescending />Sort by relevance</Button>
|
||||
</div>
|
||||
<div class="controls__row controls__row--overflow">
|
||||
<Button color="secondary"><IconBox />Minecraft versions</Button>
|
||||
<Button color="secondary"><IconGlobe />Categories</Button>
|
||||
<Button color="secondary"><IconCpu />Environment</Button>
|
||||
<Button color="secondary"><IconTruck />Mod loaders</Button>
|
||||
<Button color="secondary"><IconFileText />License</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="results">
|
||||
{#each projects as project}
|
||||
<ProjectCard {project} />
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
.controls {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
gap: 10px;
|
||||
|
||||
&__row {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
|
||||
&--overflow {
|
||||
overflow-x: auto;
|
||||
margin: 0px -4px;
|
||||
padding: 0 6px;
|
||||
width: calc(100% + 3px);
|
||||
mask-image: linear-gradient(to right, transparent, hsla(0, 0%, 0%, 1) 1% 99%, transparent);
|
||||
|
||||
/* Hide scrollbar */
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
:global(.text-input) {
|
||||
flex: 1 1;
|
||||
}
|
||||
|
||||
:global(.text-input > input) {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.results {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,13 @@
|
||||
@import 'components.postcss';
|
||||
|
||||
.base {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.avatar--size-md {
|
||||
--size: 64px !important;
|
||||
}
|
||||
|
||||
.theme-dark {
|
||||
--status-bg: hsl(216, 5%, 29%);
|
||||
--sidebar-bg: hsl(216, 10%, 3%);
|
||||
|
||||
Reference in New Issue
Block a user