Skip to content

Commit

Permalink
UI work
Browse files Browse the repository at this point in the history
  • Loading branch information
Prospector committed Jan 16, 2025
1 parent b5a1509 commit 2f1e9fc
Show file tree
Hide file tree
Showing 16 changed files with 539 additions and 567 deletions.
82 changes: 68 additions & 14 deletions apps/app-frontend/src/App.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<script setup>
<script async setup>
import { computed, ref, onMounted, watch, onUnmounted } from 'vue'
import { RouterView, useRouter, useRoute } from 'vue-router'
import {
Expand All @@ -17,8 +17,16 @@ import {
LogOutIcon,
RightArrowIcon,
LeftArrowIcon,
SpinnerIcon,
} from '@modrinth/assets'
import { Avatar, Button, ButtonStyled, Notifications, OverflowMenu } from '@modrinth/ui'
import {
Avatar,
Button,
ButtonStyled,
commonMessages,
Notifications,
OverflowMenu
} from '@modrinth/ui'
import { useLoading, useTheming } from '@/store/state'
import ModrinthAppLogo from '@/assets/modrinth_app.svg?component'
import AccountsCard from '@/components/ui/AccountsCard.vue'
Expand Down Expand Up @@ -62,6 +70,9 @@ import { hide_ads_window, init_ads_window } from '@/helpers/ads.js'
import FriendsList from '@/components/ui/friends/FriendsList.vue'
import { openUrl } from '@tauri-apps/plugin-opener'
import QuickInstanceSwitcher from '@/components/ui/QuickInstanceSwitcher.vue'
import { defineMessages, useVIntl } from '@vintl/vintl'
const { formatMessage } = useVIntl()
const themeStore = useTheming()
Expand Down Expand Up @@ -357,6 +368,45 @@ function handleAuxClick(e) {
e.target.dispatchEvent(event)
}
}
const messages = defineMessages({
playingAs: {
id: 'app.sidebar.accounts.title',
defaultMessage: 'Playing as',
},
loadingAccounts: {
id: 'app.sidebar.accounts.loading-accounts',
defaultMessage: 'Loading accounts...',
},
news: {
id: 'app.sidebar.news.title',
defaultMessage: 'News',
},
home: {
id: 'app.nav.home.title',
defaultMessage: 'Home',
},
discoverContent: {
id: 'app.nav.discover-content.title',
defaultMessage: 'Discover content',
},
library: {
id: 'app.nav.library.title',
defaultMessage: 'Library',
},
settings: {
id: 'app.nav.settings.title',
defaultMessage: 'Settings',
},
createInstance: {
id: 'app.nav.create-instance.title',
defaultMessage: 'Create new instance',
},
installUpdate: {
id: 'app.nav.install-update.title',
defaultMessage: 'Install update',
},
})
</script>

<template>
Expand All @@ -372,19 +422,19 @@ function handleAuxClick(e) {
<div
class="app-grid-navbar bg-bg-raised flex flex-col p-[0.5rem] pt-0 gap-[0.5rem] w-[--left-bar-width]"
>
<NavButton v-tooltip.right="'Home'" to="/">
<NavButton v-tooltip.right="formatMessage(messages.home)" to="/">
<HomeIcon />
</NavButton>
<NavButton
v-tooltip.right="'Discover content'"
v-tooltip.right="formatMessage(messages.discoverContent)"
to="/browse/modpack"
:is-primary="() => route.path.startsWith('/browse') && !route.query.i"
:is-subpage="(route) => route.path.startsWith('/project') && !route.query.i"
>
<CompassIcon />
</NavButton>
<NavButton
v-tooltip.right="'Library'"
v-tooltip.right="formatMessage(messages.library)"
to="/library"
:is-subpage="
() =>
Expand All @@ -400,17 +450,17 @@ function handleAuxClick(e) {
<QuickInstanceSwitcher />
</suspense>
<NavButton
v-tooltip.right="'Create new instance'"
v-tooltip.right="formatMessage(messages.createInstance)"
:to="() => $refs.installationModal.show()"
:disabled="offline"
>
<PlusIcon />
</NavButton>
<div class="flex flex-grow"></div>
<NavButton v-if="updateAvailable" v-tooltip.right="'Install update'" :to="() => restartApp()">
<NavButton v-if="updateAvailable" v-tooltip.right="formatMessage(messages.installUpdate)" :to="() => restartApp()">
<DownloadIcon />
</NavButton>
<NavButton v-tooltip.right="'Settings'" :to="() => $refs.settingsModal.show()">
<NavButton v-tooltip.right="formatMessage(messages.settings)" :to="() => $refs.settingsModal.show()">
<SettingsIcon />
</NavButton>
<ButtonStyled v-if="credentials" type="transparent" circular>
Expand All @@ -430,12 +480,11 @@ function handleAuxClick(e) {
size="32px"
circle
/>
<template #sign-out> <LogOutIcon /> Sign out </template>
<template #sign-out> <LogOutIcon /> {{ formatMessage(commonMessages.signOutButton) }} </template>
</OverflowMenu>
</ButtonStyled>
<NavButton v-else v-tooltip.right="'Sign in'" :to="() => signIn()">
<NavButton v-else v-tooltip.right="formatMessage(commonMessages.signInButton)" :to="() => signIn()">
<LogInIcon />
<template #label>Sign in</template>
</NavButton>
</div>
<div data-tauri-drag-region class="app-grid-statusbar bg-bg-raised h-[--top-bar-height] flex">
Expand Down Expand Up @@ -543,10 +592,15 @@ function handleAuxClick(e) {
<div id="sidebar-teleport-target" class="sidebar-teleport-content"></div>
<div class="sidebar-default-content" :class="{ 'sidebar-enabled': sidebarVisible }">
<div class="p-4 border-0 border-b-[1px] border-[--brand-gradient-border] border-solid">
<h3 class="text-lg m-0">Playing as</h3>
<suspense>
<h3 class="text-lg m-0">{{ formatMessage(messages.playingAs) }}</h3>
<Suspense>
<AccountsCard ref="accounts" />
</suspense>
<template #fallback>
<div class="flex items-center gap-2 pt-4 pb-1">
<SpinnerIcon class="animate-spin" /> {{ formatMessage(messages.loadingAccounts) }}
</div>
</template>
</Suspense>
</div>
<div class="p-4 border-0 border-b-[1px] border-[--brand-gradient-border] border-solid">
<suspense>
Expand Down
32 changes: 17 additions & 15 deletions apps/app-frontend/src/components/ui/AccountsCard.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@

<script setup lang="ts">
import { RadioButtonChecked, RadioButtonIcon, PlusIcon, TrashIcon, LogInIcon } from '@modrinth/assets'
import { Avatar, Button, Accordion, ButtonStyled } from '@modrinth/ui'
import { SkinManagerIcon } from '@/assets/icons/index.js'
import { ref, computed, onUnmounted } from 'vue'
import { type Ref, type ComputedRef, ref, computed, onUnmounted } from 'vue'
import {
users,
remove_user,
Expand All @@ -18,24 +17,25 @@ import { handleSevereError } from '@/store/error.js'
import {
cache_users_skins,
cache_new_user_skin,
account_heads,
account_heads as skinManagerAccountHeads,
loaded_skins,
get_heads,
get_filters,
selectedAccount as skinManagerAccount,
} from '@/helpers/skin_manager.js'
import { defineMessages, useVIntl } from '@vintl/vintl'
import type { MinecraftCredentials } from '@/helpers/types'
const { formatMessage } = useVIntl()
const emit = defineEmits(['change'])
const accounts = ref({})
const defaultUser = ref()
const accounts: Ref<MinecraftCredentials[]> = ref([])
const defaultUser: Ref<string | undefined> = ref()
async function refreshValues() {
defaultUser.value = await get_default_user().catch(handleError)
accounts.value = await users().catch(handleError)
accounts.value = await users().catch(handleError) ?? []
accounts.value.sort((a, b) => a.username.localeCompare(b.username))
Expand All @@ -46,13 +46,13 @@ defineExpose({
refreshValues,
})
const selectedAccount = computed(() => {
const account = accounts.value.find((account) => account.id === defaultUser.value)
skinManagerAccount.value = account
const selectedAccount: ComputedRef<MinecraftCredentials | undefined> = computed(() => {
const account = accounts.value.find((account) => account.id === defaultUser.value);
(skinManagerAccount as Ref<MinecraftCredentials | undefined>).value = account
return account
})
async function setAccount(account) {
async function setAccount(account: MinecraftCredentials) {
defaultUser.value = account.id
await set_default_user(account.id).catch(handleError)
emit('change')
Expand All @@ -63,15 +63,15 @@ async function login() {
if (loggedIn) {
await cache_new_user_skin(loggedIn).catch(handleError)
get_heads()
await get_heads()
await setAccount(loggedIn)
await refreshValues()
}
trackEvent('AccountLogIn')
}
async function logout(id) {
async function logout(id: string) {
await remove_user(id).catch(handleError)
await refreshValues()
if (!selectedAccount.value && accounts.value.length > 0) {
Expand All @@ -83,7 +83,7 @@ async function logout(id) {
trackEvent('AccountLogOut')
}
const unlisten = await process_listener(async (e) => {
const unlisten = await process_listener(async (e: { event: string }) => {
if (e.event === 'launched') {
await refreshValues()
}
Expand All @@ -96,6 +96,8 @@ async function refreshSkins() {
await get_filters()
}
const account_heads: Ref<Record<string, string>> = computed(() => skinManagerAccountHeads.value)
onUnmounted(() => {
unlisten()
})
Expand Down Expand Up @@ -155,10 +157,10 @@ await refreshValues()
<div v-if="accounts.length > 0" class="account-group">
<div v-for="account in accounts" :key="account.id" class="flex gap-1 items-center">
<button class="flex items-center flex-shrink flex-grow overflow-clip gap-2 p-2 border-0 bg-transparent cursor-pointer button-base" @click="setAccount(account)">
<RadioButtonChecked v-if="selectedAccount.id === account.id" class="w-5 h-5 text-brand" />
<RadioButtonChecked v-if="selectedAccount && selectedAccount.id === account.id" class="w-5 h-5 text-brand" />
<RadioButtonIcon v-else class="w-5 h-5 text-secondary" />
<Avatar :src="account_heads[account.id]" size="24px" />
<p class="m-0 truncate" :class="selectedAccount.id === account.id ? `text-contrast font-semibold` : `text-primary`">{{ account.username }}</p>
<p class="m-0 truncate" :class="selectedAccount && selectedAccount.id === account.id ? `text-contrast font-semibold` : `text-primary`">{{ account.username }}</p>
</button>
<ButtonStyled circular color="red" color-fill="none" hover-color-fill="background">
<button v-tooltip="formatMessage(messages.removeAccount)" class="mr-2" @click="logout(account.id)">
Expand Down
88 changes: 9 additions & 79 deletions apps/app-frontend/src/components/ui/SkinSave.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
<script setup>
import { Card } from '@modrinth/ui'
const props = defineProps({
data: {
type: Object,
Expand All @@ -14,81 +12,13 @@ const emit = defineEmits(['setSkin'])
</script>

<template>
<div class="instance">
<Card class="instance-card-item button-base" @click="emit('setSkin', props.data)">
<img
:src="props.data.model"
alt="Mod card"
class="mod-image"
draggable="false"
/>
<div class="project-info">
<p class="title">{{ props.data.name }}</p>
<p class="description">{{ props.data.arms }}, {{ props.data.cape }}</p>
</div>
</Card>
</div>
<button class="button-base cursor-pointer bg-bg-raised rounded-2xl hover:brightness-12 flex flex-col items-center justify-center 5 p-0 py-4 border-0 active:scale-[0.96] transition-all w-full h-full" @click="emit('setSkin', props.data)">
<img
:src="props.data.model"
alt="Mod card"
class="max-w-[144px]"
draggable="false"
/>
<span class="m-0">{{ props.data.name }}</span>
</button>
</template>

<style lang="scss" scoped>
.instance {
position: relative;
&:hover {
.cta {
opacity: 1;
bottom: calc(var(--gap-md) + 4.25rem);
}
}
}
.instance-card-item {
display: block;
flex: none;
align-items: center;
justify-content: center;
cursor: pointer;
padding: var(--gap-md);
transition: 0.1s ease-in-out all !important; /* overrides Omorphia defaults */
margin-bottom: 0;
.mod-image {
--size: 100%;
width: 144px;
height: 144px;
}
.project-info {
margin-top: 1rem;
width: 100%;
.title {
color: var(--color-contrast);
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
width: 144px;
margin: 0;
font-weight: 600;
font-size: 1rem;
line-height: 110%;
display: inline-block;
}
.description {
color: var(--color-base);
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
font-weight: 500;
font-size: 0.775rem;
line-height: 125%;
margin: 0.25rem 0 0;
text-transform: capitalize;
white-space: nowrap;
text-overflow: ellipsis;
}
}
}
</style>
Loading

0 comments on commit 2f1e9fc

Please sign in to comment.