forked from didirus/AstralRinth
Fixes on small frontend bugs (#4719)
* Account list is not scrollable Fixes #4688 * Selecting Glitch in the log Screen Fixes #4687 by explicitly defining the buffer * When sorting or grouping your instance, the option you choose does not get saved Fixes #4647 * use label prop to specify specific local storage for grid display state * Implement persistent filters on mods page Fixes #4517 * fix lint errors * update schemastore links --------- Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
This commit is contained in:
@@ -12,6 +12,7 @@ import {
|
|||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import { Button, DropdownSelect, injectNotificationManager } from '@modrinth/ui'
|
import { Button, DropdownSelect, injectNotificationManager } from '@modrinth/ui'
|
||||||
import { formatCategoryHeader } from '@modrinth/utils'
|
import { formatCategoryHeader } from '@modrinth/utils'
|
||||||
|
import { useStorage } from '@vueuse/core'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
@@ -121,40 +122,50 @@ const handleOptionsClick = async (args) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const state = useStorage(
|
||||||
|
`${props.label}-grid-display-state`,
|
||||||
|
{
|
||||||
|
group: 'Group',
|
||||||
|
sortBy: 'Name',
|
||||||
|
},
|
||||||
|
localStorage,
|
||||||
|
{ mergeDefaults: true },
|
||||||
|
)
|
||||||
|
|
||||||
const search = ref('')
|
const search = ref('')
|
||||||
const group = ref('Group')
|
|
||||||
const sortBy = ref('Name')
|
|
||||||
|
|
||||||
const filteredResults = computed(() => {
|
const filteredResults = computed(() => {
|
||||||
|
const { group = 'Group', sortBy = 'Name' } = state.value
|
||||||
|
|
||||||
const instances = props.instances.filter((instance) => {
|
const instances = props.instances.filter((instance) => {
|
||||||
return instance.name.toLowerCase().includes(search.value.toLowerCase())
|
return instance.name.toLowerCase().includes(search.value.toLowerCase())
|
||||||
})
|
})
|
||||||
|
|
||||||
if (sortBy.value === 'Name') {
|
if (sortBy === 'Name') {
|
||||||
instances.sort((a, b) => {
|
instances.sort((a, b) => {
|
||||||
return a.name.localeCompare(b.name)
|
return a.name.localeCompare(b.name)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sortBy.value === 'Game version') {
|
if (sortBy === 'Game version') {
|
||||||
instances.sort((a, b) => {
|
instances.sort((a, b) => {
|
||||||
return a.game_version.localeCompare(b.game_version, undefined, { numeric: true })
|
return a.game_version.localeCompare(b.game_version, undefined, { numeric: true })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sortBy.value === 'Last played') {
|
if (sortBy === 'Last played') {
|
||||||
instances.sort((a, b) => {
|
instances.sort((a, b) => {
|
||||||
return dayjs(b.last_played ?? 0).diff(dayjs(a.last_played ?? 0))
|
return dayjs(b.last_played ?? 0).diff(dayjs(a.last_played ?? 0))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sortBy.value === 'Date created') {
|
if (sortBy === 'Date created') {
|
||||||
instances.sort((a, b) => {
|
instances.sort((a, b) => {
|
||||||
return dayjs(b.date_created).diff(dayjs(a.date_created))
|
return dayjs(b.date_created).diff(dayjs(a.date_created))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sortBy.value === 'Date modified') {
|
if (sortBy === 'Date modified') {
|
||||||
instances.sort((a, b) => {
|
instances.sort((a, b) => {
|
||||||
return dayjs(b.date_modified).diff(dayjs(a.date_modified))
|
return dayjs(b.date_modified).diff(dayjs(a.date_modified))
|
||||||
})
|
})
|
||||||
@@ -162,7 +173,7 @@ const filteredResults = computed(() => {
|
|||||||
|
|
||||||
const instanceMap = new Map()
|
const instanceMap = new Map()
|
||||||
|
|
||||||
if (group.value === 'Loader') {
|
if (group === 'Loader') {
|
||||||
instances.forEach((instance) => {
|
instances.forEach((instance) => {
|
||||||
const loader = formatCategoryHeader(instance.loader)
|
const loader = formatCategoryHeader(instance.loader)
|
||||||
if (!instanceMap.has(loader)) {
|
if (!instanceMap.has(loader)) {
|
||||||
@@ -171,7 +182,7 @@ const filteredResults = computed(() => {
|
|||||||
|
|
||||||
instanceMap.get(loader).push(instance)
|
instanceMap.get(loader).push(instance)
|
||||||
})
|
})
|
||||||
} else if (group.value === 'Game version') {
|
} else if (group === 'Game version') {
|
||||||
instances.forEach((instance) => {
|
instances.forEach((instance) => {
|
||||||
if (!instanceMap.has(instance.game_version)) {
|
if (!instanceMap.has(instance.game_version)) {
|
||||||
instanceMap.set(instance.game_version, [])
|
instanceMap.set(instance.game_version, [])
|
||||||
@@ -179,7 +190,7 @@ const filteredResults = computed(() => {
|
|||||||
|
|
||||||
instanceMap.get(instance.game_version).push(instance)
|
instanceMap.get(instance.game_version).push(instance)
|
||||||
})
|
})
|
||||||
} else if (group.value === 'Group') {
|
} else if (group === 'Group') {
|
||||||
instances.forEach((instance) => {
|
instances.forEach((instance) => {
|
||||||
if (instance.groups.length === 0) {
|
if (instance.groups.length === 0) {
|
||||||
instance.groups.push('None')
|
instance.groups.push('None')
|
||||||
@@ -199,7 +210,7 @@ const filteredResults = computed(() => {
|
|||||||
|
|
||||||
// For 'name', we intuitively expect the sorting to apply to the name of the group first, not just the name of the instance
|
// For 'name', we intuitively expect the sorting to apply to the name of the group first, not just the name of the instance
|
||||||
// ie: Category A should come before B, even if the first instance in B comes before the first instance in A
|
// ie: Category A should come before B, even if the first instance in B comes before the first instance in A
|
||||||
if (sortBy.value === 'Name') {
|
if (sortBy === 'Name') {
|
||||||
const sortedEntries = [...instanceMap.entries()].sort((a, b) => {
|
const sortedEntries = [...instanceMap.entries()].sort((a, b) => {
|
||||||
// None should always be first
|
// None should always be first
|
||||||
if (a[0] === 'None' && b[0] !== 'None') {
|
if (a[0] === 'None' && b[0] !== 'None') {
|
||||||
@@ -217,7 +228,7 @@ const filteredResults = computed(() => {
|
|||||||
}
|
}
|
||||||
// default sorting would do 1.20.4 < 1.8.9 because 2 < 8
|
// default sorting would do 1.20.4 < 1.8.9 because 2 < 8
|
||||||
// localeCompare with numeric=true puts 1.8.9 < 1.20.4 because 8 < 20
|
// localeCompare with numeric=true puts 1.8.9 < 1.20.4 because 8 < 20
|
||||||
if (group.value === 'Game version') {
|
if (group === 'Game version') {
|
||||||
const sortedEntries = [...instanceMap.entries()].sort((a, b) => {
|
const sortedEntries = [...instanceMap.entries()].sort((a, b) => {
|
||||||
return a[0].localeCompare(b[0], undefined, { numeric: true })
|
return a[0].localeCompare(b[0], undefined, { numeric: true })
|
||||||
})
|
})
|
||||||
@@ -241,7 +252,7 @@ const filteredResults = computed(() => {
|
|||||||
</div>
|
</div>
|
||||||
<DropdownSelect
|
<DropdownSelect
|
||||||
v-slot="{ selected }"
|
v-slot="{ selected }"
|
||||||
v-model="sortBy"
|
v-model="state.sortBy"
|
||||||
name="Sort Dropdown"
|
name="Sort Dropdown"
|
||||||
class="max-w-[16rem]"
|
class="max-w-[16rem]"
|
||||||
:options="['Name', 'Last played', 'Date created', 'Date modified', 'Game version']"
|
:options="['Name', 'Last played', 'Date created', 'Date modified', 'Game version']"
|
||||||
@@ -252,7 +263,7 @@ const filteredResults = computed(() => {
|
|||||||
</DropdownSelect>
|
</DropdownSelect>
|
||||||
<DropdownSelect
|
<DropdownSelect
|
||||||
v-slot="{ selected }"
|
v-slot="{ selected }"
|
||||||
v-model="group"
|
v-model="state.group"
|
||||||
class="max-w-[16rem]"
|
class="max-w-[16rem]"
|
||||||
name="Group Dropdown"
|
name="Group Dropdown"
|
||||||
:options="['Group', 'Loader', 'Game version', 'None']"
|
:options="['Group', 'Loader', 'Game version', 'None']"
|
||||||
|
|||||||
@@ -289,7 +289,7 @@ onUnmounted(() => {
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
-ms-user-select: none;
|
-ms-user-select: none;
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
max-height: 98vh;
|
max-height: calc(100vh - 300px);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
&::-webkit-scrollbar-track {
|
&::-webkit-scrollbar-track {
|
||||||
|
|||||||
@@ -67,6 +67,7 @@
|
|||||||
direction="vertical"
|
direction="vertical"
|
||||||
:item-size="20"
|
:item-size="20"
|
||||||
key-field="id"
|
key-field="id"
|
||||||
|
buffer="200"
|
||||||
>
|
>
|
||||||
<div class="user no-wrap">
|
<div class="user no-wrap">
|
||||||
<span :style="{ color: item.prefixColor, 'font-weight': item.weight }">{{
|
<span :style="{ color: item.prefixColor, 'font-weight': item.weight }">{{
|
||||||
@@ -508,7 +509,7 @@ onUnmounted(() => {
|
|||||||
background-color: var(--color-accent-contrast);
|
background-color: var(--color-accent-contrast);
|
||||||
color: var(--color-contrast);
|
color: var(--color-contrast);
|
||||||
border-radius: var(--radius-lg);
|
border-radius: var(--radius-lg);
|
||||||
padding: 1.5rem;
|
padding-top: 1.5rem;
|
||||||
overflow-x: auto; /* Enables horizontal scrolling */
|
overflow-x: auto; /* Enables horizontal scrolling */
|
||||||
overflow-y: hidden; /* Disables vertical scrolling on this wrapper */
|
overflow-y: hidden; /* Disables vertical scrolling on this wrapper */
|
||||||
white-space: nowrap; /* Keeps content on a single line */
|
white-space: nowrap; /* Keeps content on a single line */
|
||||||
@@ -557,9 +558,10 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
.user {
|
.user {
|
||||||
height: 32%;
|
height: 32%;
|
||||||
padding: 0 12px;
|
padding: 0 1.5rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
user-select: text;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -285,6 +285,7 @@ import type { Organization, Project, TeamMember, Version } from '@modrinth/utils
|
|||||||
import { formatProjectType } from '@modrinth/utils'
|
import { formatProjectType } from '@modrinth/utils'
|
||||||
import { getCurrentWebview } from '@tauri-apps/api/webview'
|
import { getCurrentWebview } from '@tauri-apps/api/webview'
|
||||||
import { defineMessages, useVIntl } from '@vintl/vintl'
|
import { defineMessages, useVIntl } from '@vintl/vintl'
|
||||||
|
import { useStorage } from '@vueuse/core'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import type { ComputedRef } from 'vue'
|
import type { ComputedRef } from 'vue'
|
||||||
import { computed, onUnmounted, ref, watch } from 'vue'
|
import { computed, onUnmounted, ref, watch } from 'vue'
|
||||||
@@ -531,7 +532,13 @@ const filterOptions: ComputedRef<FilterOption[]> = computed(() => {
|
|||||||
return options
|
return options
|
||||||
})
|
})
|
||||||
|
|
||||||
const selectedFilters = ref<string[]>([])
|
const selectedFilters = useStorage<string[]>(
|
||||||
|
`${props.instance.name}-mod-selected-filters`,
|
||||||
|
[],
|
||||||
|
sessionStorage,
|
||||||
|
{ mergeDefaults: true },
|
||||||
|
)
|
||||||
|
|
||||||
const filteredProjects = computed(() => {
|
const filteredProjects = computed(() => {
|
||||||
const updatesFilter = selectedFilters.value.includes('updates')
|
const updatesFilter = selectedFilters.value.includes('updates')
|
||||||
const disabledFilter = selectedFilters.value.includes('disabled')
|
const disabledFilter = selectedFilters.value.includes('disabled')
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/tsconfig",
|
"$schema": "https://www.schemastore.org/tsconfig",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2020",
|
"target": "ES2020",
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/tsconfig",
|
"$schema": "https://www.schemastore.org/tsconfig",
|
||||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"lib": ["ESNext", "DOM"],
|
"lib": ["ESNext", "DOM"],
|
||||||
|
|||||||
Reference in New Issue
Block a user