More app fixes 0.9.0 (#3054)

* initial set of fixes (toggle sidebar, profile pagination)

* more fixes, bump version

* fix lint:

* fix quick switcher ordering
This commit is contained in:
Geometrically
2024-12-22 20:03:58 -07:00
committed by GitHub
parent ef08d8e538
commit cae6f12ea0
52 changed files with 502 additions and 1501 deletions

View File

@@ -218,14 +218,14 @@ const filteredResults = computed(() => {
})
</script>
<template>
<div class="iconified-input">
<SearchIcon />
<input v-model="search" type="text" class="h-12" placeholder="Search" />
<Button class="r-btn" @click="() => (search = '')">
<XIcon />
</Button>
</div>
<div class="flex gap-2">
<div class="iconified-input flex-1">
<SearchIcon />
<input v-model="search" type="text" placeholder="Search" />
<Button class="r-btn" @click="() => (search = '')">
<XIcon />
</Button>
</div>
<DropdownSelect
v-slot="{ selected }"
v-model="sortBy"
@@ -363,7 +363,7 @@ const filteredResults = computed(() => {
.instances {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
width: 100%;
gap: 0.75rem;
margin-right: auto;

View File

@@ -207,13 +207,18 @@ const calculateCardsPerRow = () => {
}
}
const rowContainer = ref(null)
const resizeObserver = ref(null)
onMounted(() => {
calculateCardsPerRow()
resizeObserver.value = new ResizeObserver(calculateCardsPerRow)
resizeObserver.value.observe(rowContainer.value)
window.addEventListener('resize', calculateCardsPerRow)
})
onUnmounted(() => {
window.removeEventListener('resize', calculateCardsPerRow)
resizeObserver.value.unobserve(rowContainer.value)
})
</script>
@@ -226,7 +231,7 @@ onUnmounted(() => {
proceed-label="Delete"
@proceed="deleteProfile"
/>
<div class="flex flex-col gap-4">
<div ref="rowContainer" class="flex flex-col gap-4">
<div v-for="(row, rowIndex) in actualInstances" ref="rows" :key="row.label" class="row">
<router-link
class="flex mb-3 leading-none items-center gap-1 text-primary text-lg font-bold hover:underline group"

View File

@@ -12,11 +12,9 @@ import { showProfileInFolder } from '@/helpers/utils.js'
import { handleSevereError } from '@/store/error.js'
import { trackEvent } from '@/helpers/analytics'
import dayjs from 'dayjs'
import duration from 'dayjs/plugin/duration'
import relativeTime from 'dayjs/plugin/relativeTime'
import { formatCategory } from '@modrinth/utils'
dayjs.extend(duration)
dayjs.extend(relativeTime)
const props = defineProps({
@@ -169,7 +167,7 @@ onUnmounted(() => unlisten())
>
<div class="relative flex items-center justify-center">
<Avatar
size="96px"
size="48px"
:src="instance.icon_path ? convertFileSrc(instance.icon_path) : null"
:tint-by="instance.path"
alt="Mod card"
@@ -205,7 +203,7 @@ onUnmounted(() => unlisten())
</div>
</div>
<div class="flex flex-col gap-1">
<p class="m-0 text-lg font-bold text-contrast leading-tight line-clamp-2">
<p class="m-0 text-md font-bold text-contrast leading-tight line-clamp-1">
{{ instance.name }}
</p>
<div class="flex items-center col-span-3 gap-1 text-secondary font-semibold mt-auto">
@@ -214,17 +212,6 @@ onUnmounted(() => unlisten())
{{ formatCategory(instance.loader) }} {{ instance.game_version }}
</span>
</div>
<div class="flex items-center col-span-3 gap-1 text-secondary font-semibold">
<TimerIcon class="shrink-0" />
<span class="text-sm line-clamp-1">
Played for
{{
dayjs
.duration(instance.recent_time_played + instance.submitted_time_played, 'seconds')
.humanize()
}}
</span>
</div>
</div>
</div>
</div>

View File

@@ -1,29 +1,24 @@
<template>
<div class="tooltip-parent flex items-center justify-center">
<RouterLink
v-if="typeof to === 'string'"
:to="to"
v-bind="$attrs"
:class="{
'router-link-active': isPrimary && isPrimary(route),
'subpage-active': isSubpage && isSubpage(route),
}"
class="w-12 h-12 rounded-full flex items-center justify-center text-2xl transition-all bg-transparent hover:bg-button-bg hover:text-contrast"
>
<slot />
</RouterLink>
<button
v-else
v-bind="$attrs"
class="button-animation border-none text-primary cursor-pointer w-12 h-12 rounded-full flex items-center justify-center text-2xl transition-all bg-transparent hover:bg-button-bg hover:text-contrast"
@click="to"
>
<slot />
</button>
<div class="tooltip-label">
<slot name="label" />
</div>
</div>
<RouterLink
v-if="typeof to === 'string'"
:to="to"
v-bind="$attrs"
:class="{
'router-link-active': isPrimary && isPrimary(route),
'subpage-active': isSubpage && isSubpage(route),
}"
class="w-12 h-12 rounded-full flex items-center justify-center text-2xl transition-all bg-transparent hover:bg-button-bg hover:text-contrast"
>
<slot />
</RouterLink>
<button
v-else
v-bind="$attrs"
class="button-animation border-none text-primary cursor-pointer w-12 h-12 rounded-full flex items-center justify-center text-2xl transition-all bg-transparent hover:bg-button-bg hover:text-contrast"
@click="to"
>
<slot />
</button>
</template>
<script setup lang="ts">
@@ -61,50 +56,4 @@ defineOptions({
.subpage-active {
@apply text-contrast bg-button-bg;
}
.tooltip-parent {
position: relative;
border-radius: var(--radius-max);
}
.tooltip-parent:hover .tooltip-label {
opacity: 1;
translate: 0 0;
scale: 1;
}
.tooltip-label:not(:empty) {
--_tooltip-bg: black;
--_tooltip-color: var(--dark-color-contrast);
position: absolute;
background-color: var(--_tooltip-bg);
color: var(--_tooltip-color);
text-wrap: nowrap;
padding: 0.5rem 0.5rem;
border-radius: var(--radius-sm);
left: calc(100% + 0.5rem);
font-size: 1rem;
line-height: 1;
font-weight: bold;
filter: drop-shadow(5px 5px 0.8rem rgba(0, 0, 0, 0.35));
pointer-events: none;
user-select: none;
opacity: 0;
translate: -0.5rem 0;
scale: 0.9;
transition: all ease-in-out 0.1s;
}
.tooltip-label:not(:empty)::after {
content: '';
position: absolute;
top: 50%;
right: 100%; /* To the left of the tooltip */
margin-top: -5px;
border-width: 5px;
border-style: solid;
border-color: transparent var(--_tooltip-bg) transparent transparent;
}
</style>

View File

@@ -25,7 +25,7 @@ onMounted(() => {
function updateAdPosition() {
if (adsWrapper.value) {
init_ads_window(true)
init_ads_window()
initDevicePixelRatioWatcher()
}
}

View File

@@ -15,8 +15,14 @@ const getInstances = async () => {
recentInstances.value = profiles
.sort((a, b) => {
const dateA = dayjs(a.created > a.last_played ? a.last_played : a.created)
const dateB = dayjs(b.created > b.last_played ? b.last_played : b.created)
const dateACreated = dayjs(a.created)
const dateAPlayed = dayjs(a.last_played)
const dateBCreated = dayjs(b.created)
const dateBPlayed = dayjs(b.last_played)
const dateA = dateACreated.isAfter(dateAPlayed) ? dateACreated : dateAPlayed
const dateB = dateBCreated.isAfter(dateBPlayed) ? dateBCreated : dateBPlayed
if (dateA.isSame(dateB)) {
return a.name.localeCompare(b.name)
@@ -42,6 +48,7 @@ onUnmounted(() => {
<NavButton
v-for="instance in recentInstances"
:key="instance.id"
v-tooltip.right="instance.name"
:to="`/instance/${encodeURIComponent(instance.path)}`"
>
<Avatar
@@ -56,8 +63,8 @@ onUnmounted(() => {
>
<SpinnerIcon class="animate-spin w-4 h-4" />
</div>
<template #label>{{ instance.name }}</template>
</NavButton>
<div v-if="recentInstances.length > 0" class="h-px w-6 mx-auto my-2 bg-button-bg"></div>
</template>
<style scoped lang="scss"></style>

View File

@@ -1,9 +1,5 @@
<template>
<div class="action-groups">
<a href="https://support.modrinth.com" class="link">
<ChatIcon />
<span> Get support </span>
</a>
<Button
v-if="currentLoadingBars.length > 0"
ref="infoButton"
@@ -123,7 +119,6 @@ import { useRouter } from 'vue-router'
import { progress_bars_list } from '@/helpers/state.js'
import ProgressBar from '@/components/ui/ProgressBar.vue'
import { handleError } from '@/store/notifications.js'
import { ChatIcon } from '@/assets/icons'
import { get_many } from '@/helpers/profile.js'
import { trackEvent } from '@/helpers/analytics'

View File

@@ -38,7 +38,7 @@
<div class="m-0 line-clamp-2">
{{ project.description }}
</div>
<div class="mt-auto flex items-center gap-1 no-wrap">
<div v-if="categories.length > 0" class="mt-auto flex items-center gap-1 no-wrap">
<TagsIcon class="h-4 w-4 shrink-0" />
<div
v-for="tag in categories"

View File

@@ -190,6 +190,7 @@ const messages = defineMessages({
description="If you proceed, all data for your instance will be permanently erased, including your worlds. You will not be able to recover it."
:has-to-type="false"
proceed-label="Delete"
:show-ad-on-close="false"
@proceed="removeProfile"
/>
<div class="block">

View File

@@ -457,6 +457,7 @@ const messages = defineMessages({
:proceed-icon="HammerIcon"
:proceed-label="formatMessage(messages.repairButton)"
:danger="false"
:show-ad-on-close="false"
@proceed="() => repairProfile(true)"
/>
<ModpackVersionModal
@@ -480,6 +481,7 @@ const messages = defineMessages({
:description="formatMessage(messages.unlinkInstanceConfirmDescription)"
:proceed-icon="UnlinkIcon"
:proceed-label="formatMessage(messages.unlinkInstanceButton)"
:show-ad-on-close="false"
@proceed="() => unpairProfile()"
/>
<ConfirmModalWrapper
@@ -488,6 +490,7 @@ const messages = defineMessages({
:description="formatMessage(messages.reinstallModpackConfirmDescription)"
:proceed-icon="DownloadIcon"
:proceed-label="formatMessage(messages.reinstallModpackButton)"
:show-ad-on-close="false"
@proceed="() => repairModpack()"
/>
<div>

View File

@@ -10,7 +10,7 @@ import {
CoffeeIcon,
} from '@modrinth/assets'
import { TabbedModal } from '@modrinth/ui'
import { computed, ref } from 'vue'
import { computed, ref, watch } from 'vue'
import { useVIntl, defineMessage } from '@vintl/vintl'
import AppearanceSettings from '@/components/ui/settings/AppearanceSettings.vue'
import JavaSettings from '@/components/ui/settings/JavaSettings.vue'
@@ -22,6 +22,7 @@ import { version as getOsVersion, platform as getOsPlatform } from '@tauri-apps/
import { useTheming } from '@/store/state'
import FeatureFlagSettings from '@/components/ui/settings/FeatureFlagSettings.vue'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import { get, set } from '@/helpers/settings'
const themeStore = useTheming()
@@ -99,6 +100,28 @@ defineExpose({ show, isOpen })
const version = await getVersion()
const osPlatform = getOsPlatform()
const osVersion = getOsVersion()
const settings = ref(await get())
watch(
settings,
async () => {
await set(settings.value)
},
{ deep: true },
)
function devModeCount() {
devModeCounter.value++
if (devModeCounter.value > 5) {
themeStore.devMode = !themeStore.devMode
settings.value.developer_mode = !!themeStore.devMode
devModeCounter.value = 0
if (!themeStore.devMode && tabs[modal.value.selectedTab].developerOnly) {
modal.value.setTab(0)
}
}
}
</script>
<template>
<ModalWrapper ref="modal">
@@ -118,19 +141,7 @@ const osVersion = getOsVersion()
<button
class="p-0 m-0 bg-transparent border-none cursor-pointer button-animation"
:class="{ 'text-brand': themeStore.devMode, 'text-secondary': !themeStore.devMode }"
@click="
() => {
devModeCounter++
if (devModeCounter > 5) {
themeStore.devMode = !themeStore.devMode
devModeCounter = 0
if (!themeStore.devMode && tabs[modal.selectedTab].developerOnly) {
modal.setTab(0)
}
}
}
"
@click="devModeCount"
>
<ModrinthIcon class="w-6 h-6" />
</button>

View File

@@ -6,7 +6,7 @@ import { useTheming } from '@/store/theme.js'
const themeStore = useTheming()
defineProps({
const props = defineProps({
confirmationText: {
type: String,
default: '',
@@ -37,6 +37,10 @@ defineProps({
type: Boolean,
default: true,
},
showAdOnClose: {
type: Boolean,
default: true,
},
})
const emit = defineEmits(['proceed'])
@@ -54,7 +58,9 @@ defineExpose({
})
function onModalHide() {
show_ads_window()
if (props.showAdOnClose) {
show_ads_window()
}
}
function proceed() {

View File

@@ -23,8 +23,13 @@ watch(
<p class="m-0 mt-1">Select your preferred color theme for Modrinth App.</p>
<ThemeSelector
:update-color-theme="themeStore.setThemeState"
:current-theme="themeStore.selectedTheme"
:update-color-theme="
(theme) => {
themeStore.setThemeState(theme)
settings.theme = theme
}
"
:current-theme="settings.theme"
:theme-options="themeStore.themeOptions"
system-theme-color="system"
/>
@@ -97,4 +102,22 @@ watch(
:options="['Home', 'Library']"
/>
</div>
<div class="mt-4 flex items-center justify-between">
<div>
<h2 class="m-0 text-lg font-extrabold text-contrast">Toggle sidebar</h2>
<p class="m-0 mt-1">Enables the ability to toggle the sidebar.</p>
</div>
<Toggle
id="toggle-sidebar"
:model-value="settings.toggle_sidebar"
:checked="settings.toggle_sidebar"
@update:model-value="
(e) => {
settings.toggle_sidebar = e
themeStore.toggleSidebar = settings.toggle_sidebar
}
"
/>
</div>
</template>

View File

@@ -1,34 +1,36 @@
<script setup lang="ts">
import { Toggle } from '@modrinth/ui'
import { useTheming } from '@/store/state'
import { computed } from 'vue'
import type { Ref } from 'vue'
import { ref, watch } from 'vue'
import { get, set } from '@/helpers/settings'
const themeStore = useTheming()
type ThemeStoreKeys = keyof typeof themeStore
const settings = ref(await get())
const options = ref(['project_background', 'page_path'])
const options: Ref<ThemeStoreKeys[]> = computed(() => {
return Object.keys(themeStore).filter((key) => key.startsWith('featureFlag_')) as ThemeStoreKeys[]
})
function getStoreValue<K extends ThemeStoreKeys>(key: K): (typeof themeStore)[K] {
return themeStore[key]
function getStoreValue(key: string) {
return themeStore.featureFlags[key] ?? false
}
function setStoreValue<K extends ThemeStoreKeys>(key: K, value: (typeof themeStore)[K]) {
themeStore[key] = value
function setStoreValue(key: string, value: boolean) {
themeStore.featureFlags[key] = value
settings.value.feature_flags[key] = value
}
function formatFlagName(name: string) {
return name.replace('featureFlag_', '')
}
watch(
settings,
async () => {
await set(settings.value)
},
{ deep: true },
)
</script>
<template>
<div v-for="option in options" :key="option" class="mt-4 flex items-center justify-between">
<div>
<h2 class="m-0 text-lg font-extrabold text-contrast capitalize">
{{ formatFlagName(option) }}
{{ option }}
</h2>
</div>
@@ -36,7 +38,7 @@ function formatFlagName(name: string) {
id="advanced-rendering"
:model-value="getStoreValue(option)"
:checked="getStoreValue(option)"
@update:model-value="() => setStoreValue(option, !themeStore[option])"
@update:model-value="() => setStoreValue(option, !themeStore.featureFlags[option])"
/>
</div>
</template>

View File

@@ -80,6 +80,7 @@ async function findLauncherDir() {
description="If you proceed, your entire cache will be purged. This may slow down the app temporarily."
:has-to-type="false"
proceed-label="Purge cache"
:show-ad-on-close="false"
@proceed="purgeCache"
/>