App fixes 0.9.0 (#3034)

* push fixes to test on windows

* Fix searching mods

* Fix search not saving, fix scrolling issues, etc
This commit is contained in:
Geometrically
2024-12-17 23:23:30 -07:00
committed by GitHub
parent 7e8ceadfd4
commit 6ceed4b226
19 changed files with 280 additions and 178 deletions

View File

@@ -127,7 +127,6 @@ const os = ref('')
getOS().then((x) => (os.value = x))
loading_listener(async (e) => {
console.log(e)
if (e.event.type === 'directory_move') {
loadingProgress.value = 100 * (e.fraction ?? 1)
message.value = 'Updating app directory...'

View File

@@ -49,17 +49,3 @@ export const releaseColor = (releaseType) => {
return ''
}
}
export function debounce(fn, wait) {
let timer
return function (...args) {
if (timer) {
clearTimeout(timer) // clear any pre-existing timer
}
const context = this // get the current context
timer = setTimeout(() => {
fn.apply(context, args) // call the function if time expires
}, wait)
}
}

View File

@@ -21,7 +21,6 @@ import { useRoute, useRouter } from 'vue-router'
import SearchCard from '@/components/ui/SearchCard.vue'
import { get as getInstance, get_projects as getInstanceProjects } from '@/helpers/profile.js'
import { get_search_results } from '@/helpers/cache.js'
import { debounce } from '@/helpers/utils.js'
import NavTabs from '@/components/ui/NavTabs.vue'
import type Instance from '@/components/ui/Instance.vue'
import InstanceIndicator from '@/components/ui/InstanceIndicator.vue'
@@ -190,6 +189,7 @@ const pageCount = computed(() =>
)
watch(requestParams, () => {
if (!route.params.projectType) return
refreshSearch()
})
@@ -214,45 +214,40 @@ async function refreshSearch() {
}
}
results.value = rawResults.result
const persistentParams: LocationQuery = {}
for (const [key, value] of Object.entries(route.query)) {
if (PERSISTENT_QUERY_PARAMS.includes(key)) {
persistentParams[key] = value
}
}
if (instanceHideInstalled.value) {
persistentParams.ai = 'true'
} else {
delete persistentParams.ai
}
const params = {
...persistentParams,
...createPageParams(),
}
breadcrumbs.setContext({
name: 'Discover content',
link: `/browse/${projectType.value}`,
query: params,
})
await router.replace({ path: route.path, query: params })
}
function setPage(newPageNumber: number) {
async function setPage(newPageNumber: number) {
currentPage.value = newPageNumber
updateSearchResults()
onSearchChangeToTop()
await onSearchChangeToTop()
}
async function updateSearchResults() {
await refreshSearch()
if (import.meta.client) {
const persistentParams: LocationQuery = {}
for (const [key, value] of Object.entries(route.query)) {
if (PERSISTENT_QUERY_PARAMS.includes(key)) {
persistentParams[key] = value
}
}
if (instanceHideInstalled.value) {
persistentParams.ai = 'true'
} else {
delete persistentParams.ai
}
const params = {
...persistentParams,
...createPageParams(),
}
await router.replace({ path: route.path, query: params })
breadcrumbs.setContext({ name: 'Discover content', link: route.path, query: params })
}
}
const debouncedSearchChange = debounce(() => updateSearchResults(1), 200)
const searchWrapper: Ref<HTMLElement | null> = ref(null)
async function onSearchChangeToTop() {
@@ -261,13 +256,10 @@ async function onSearchChangeToTop() {
window.scrollTo({ top: 0, behavior: 'smooth' })
}
async function clearSearch() {
function clearSearch() {
query.value = ''
await updateSearchResults()
}
async function clearFilters() {}
watch(
() => route.params.projectType,
async (newType) => {
@@ -275,14 +267,9 @@ watch(
if (!newType || newType === projectType.value) return
projectType.value = newType
breadcrumbs.setContext({ name: 'Discover content', link: `/browse/${projectType.value}` })
currentSortType.value = { display: 'Relevance', name: 'relevance' }
query.value = ''
loading.value = true
await clearFilters()
loading.value = false
},
)
@@ -418,7 +405,6 @@ await refreshSearch()
spellcheck="false"
type="text"
:placeholder="`Search ${projectType}s...`"
@input="debouncedSearchChange()"
/>
<Button v-if="query" class="r-btn" @click="() => clearSearch()">
<XIcon />
@@ -432,7 +418,6 @@ await refreshSearch()
name="Sort by"
:options="sortTypes as any"
:display-name="(option: SortType | undefined) => option?.display"
@change="updateSearchResults()"
>
<span class="font-semibold text-primary">Sort by: </span>
<span class="font-semibold text-secondary">{{ selected }}</span>
@@ -443,7 +428,6 @@ await refreshSearch()
name="Max results"
:options="[5, 10, 15, 20, 50, 100]"
class="max-w-[9rem]"
@change="updateSearchResults()"
>
<span class="font-semibold text-primary">View: </span>
<span class="font-semibold text-secondary">{{ selected }}</span>

View File

@@ -13,70 +13,77 @@
</template>
<template #summary> </template>
<template #stats>
<div class="flex items-center gap-2 font-semibold transform capitalize">
<div
class="flex items-center gap-2 font-semibold transform capitalize border-0 border-solid border-divider pr-4 md:border-r"
>
<GameIcon class="h-6 w-6 text-secondary" />
{{ instance.loader }} {{ instance.game_version }}
</div>
<div class="flex items-center gap-2 font-semibold">
<TimerIcon class="h-6 w-6 text-secondary" />
<template v-if="timePlayed > 0">
{{ timePlayedHumanized }}
</template>
<template v-else> Never played </template>
</div>
</template>
<template #actions>
<ButtonStyled v-if="instance.install_stage !== 'installed'" color="brand" size="large">
<button disabled>Installing...</button>
</ButtonStyled>
<template v-else>
<div class="flex gap-2">
<ButtonStyled v-if="playing === true" color="red" size="large">
<button @click="stopInstance('InstancePage')">
<StopCircleIcon />
Stop
</button>
</ButtonStyled>
<ButtonStyled
v-else-if="playing === false && loading === false"
color="brand"
size="large"
<div class="flex gap-2">
<ButtonStyled v-if="instance.install_stage !== 'installed'" color="brand" size="large">
<button disabled>Installing...</button>
</ButtonStyled>
<ButtonStyled v-else-if="playing === true" color="red" size="large">
<button @click="stopInstance('InstancePage')">
<StopCircleIcon />
Stop
</button>
</ButtonStyled>
<ButtonStyled
v-else-if="playing === false && loading === false"
color="brand"
size="large"
>
<button @click="startInstance('InstancePage')">
<PlayIcon />
Play
</button>
</ButtonStyled>
<ButtonStyled
v-else-if="loading === true && playing === false"
color="brand"
size="large"
>
<button disabled>Loading...</button>
</ButtonStyled>
<ButtonStyled size="large" circular>
<RouterLink
v-tooltip="'Instance settings'"
:to="`/instance/${encodeURIComponent(route.params.id)}/options`"
>
<button @click="startInstance('InstancePage')">
<PlayIcon />
Play
</button>
</ButtonStyled>
<ButtonStyled
v-else-if="loading === true && playing === false"
color="brand"
size="large"
<SettingsIcon />
</RouterLink>
</ButtonStyled>
<ButtonStyled size="large" type="transparent" circular>
<OverflowMenu
:options="[
{
id: 'open-folder',
action: () => showProfileInFolder(instance.path),
},
{
id: 'export-mrpack',
action: () => $refs.exportModal.show(),
},
]"
>
<button disabled>Loading...</button>
</ButtonStyled>
<ButtonStyled size="large" circular>
<RouterLink
v-tooltip="'Instance settings'"
:to="`/instance/${encodeURIComponent(route.params.id)}/options`"
>
<SettingsIcon />
</RouterLink>
</ButtonStyled>
<ButtonStyled size="large" type="transparent" circular>
<OverflowMenu
:options="[
{
id: 'open-folder',
action: () => showProfileInFolder(instance.path),
},
{
id: 'export-mrpack',
action: () => $refs.exportModal.show(),
},
]"
>
<MoreVerticalIcon />
<template #share-instance> <UserPlusIcon /> Share instance </template>
<template #host-a-server> <ServerIcon /> Create a server </template>
<template #open-folder> <FolderOpenIcon /> Open folder </template>
<template #export-mrpack> <PackageIcon /> Export modpack </template>
</OverflowMenu>
</ButtonStyled>
</div>
</template>
<MoreVerticalIcon />
<template #share-instance> <UserPlusIcon /> Share instance </template>
<template #host-a-server> <ServerIcon /> Create a server </template>
<template #open-folder> <FolderOpenIcon /> Open folder </template>
<template #export-mrpack> <PackageIcon /> Export modpack </template>
</OverflowMenu>
</ButtonStyled>
</div>
</template>
</ContentPageHeader>
</div>
@@ -106,15 +113,15 @@
<ContextMenu ref="options" @option-clicked="handleOptionsClick">
<template #play> <PlayIcon /> Play </template>
<template #stop> <StopCircleIcon /> Stop </template>
<template #add_content> <PlusIcon /> Add Content </template>
<template #add_content> <PlusIcon /> Add content </template>
<template #edit> <EditIcon /> Edit </template>
<template #copy_path> <ClipboardCopyIcon /> Copy Path </template>
<template #open_folder> <ClipboardCopyIcon /> Open Folder </template>
<template #copy_link> <ClipboardCopyIcon /> Copy Link </template>
<template #open_link> <ClipboardCopyIcon /> Open In Modrinth <ExternalIcon /> </template>
<template #copy_path> <ClipboardCopyIcon /> Copy path </template>
<template #open_folder> <ClipboardCopyIcon /> Open folder </template>
<template #copy_link> <ClipboardCopyIcon /> Copy link </template>
<template #open_link> <ClipboardCopyIcon /> Open in Modrinth <ExternalIcon /> </template>
<template #copy_names><EditIcon />Copy names</template>
<template #copy_slugs><HashIcon />Copy slugs</template>
<template #copy_links><GlobeIcon />Copy Links</template>
<template #copy_links><GlobeIcon />Copy links</template>
<template #toggle><EditIcon />Toggle selected</template>
<template #disable><XIcon />Disable selected</template>
<template #enable><CheckCircleIcon />Enable selected</template>
@@ -153,8 +160,9 @@ import {
UpdatedIcon,
MoreVerticalIcon,
GameIcon,
TimerIcon,
} from '@modrinth/assets'
import { get, kill, run } from '@/helpers/profile'
import { get, get_full_path, kill, run } from '@/helpers/profile'
import { get_by_profile_path } from '@/helpers/process'
import { process_listener, profile_listener } from '@/helpers/events'
import { useRoute, useRouter } from 'vue-router'
@@ -168,8 +176,13 @@ import { convertFileSrc } from '@tauri-apps/api/core'
import { handleSevereError } from '@/store/error.js'
import { get_project, get_version_many } from '@/helpers/cache.js'
import dayjs from 'dayjs'
import duration from 'dayjs/plugin/duration'
import relativeTime from 'dayjs/plugin/relativeTime'
import ExportModal from '@/components/ui/ExportModal.vue'
dayjs.extend(duration)
dayjs.extend(relativeTime)
const route = useRoute()
const router = useRouter()
@@ -321,9 +334,11 @@ const handleOptionsClick = async (args) => {
case 'open_folder':
await showProfileInFolder(instance.value.path)
break
case 'copy_path':
await navigator.clipboard.writeText(instance.value.path)
case 'copy_path': {
const fullPath = await get_full_path(instance.value.path)
await navigator.clipboard.writeText(fullPath)
break
}
}
}
@@ -347,6 +362,26 @@ const icon = computed(() =>
instance.value.icon_path ? convertFileSrc(instance.value.icon_path) : null,
)
const timePlayed = computed(() => {
return instance.value.recent_time_played + instance.value.submitted_time_played
})
const timePlayedHumanized = computed(() => {
const duration = dayjs.duration(timePlayed.value, 'seconds')
const hours = Math.floor(duration.asHours())
if (hours >= 1) {
return hours + ' hour' + (hours > 1 ? 's' : '')
}
const minutes = Math.floor(duration.asMinutes())
if (minutes >= 1) {
return minutes + ' minute' + (minutes > 1 ? 's' : '')
}
const seconds = Math.floor(duration.asSeconds())
return seconds + ' second' + (seconds > 1 ? 's' : '')
})
onUnmounted(() => {
unlistenProcesses()
unlistenProfiles()

View File

@@ -183,8 +183,8 @@
},
{
id: 'copy-link',
shown: item.project !== undefined,
action: () => toggleDisableMod(item.data),
shown: item.data !== undefined && item.data.slug !== undefined,
action: () => copyModLink(item),
},
{
divider: true,
@@ -674,6 +674,12 @@ const removeMod = async (mod) => {
})
}
const copyModLink = async (mod) => {
await navigator.clipboard.writeText(
`https://modrinth.com/${mod.data.project_type}/${mod.data.slug}`,
)
}
const deleteSelected = async () => {
for (const project of functionValues.value) {
await remove_project(props.instance.path, project.path).catch(handleError)

View File

@@ -149,9 +149,9 @@ export default new createRouter({
linkExactActiveClass: 'router-link-exact-active',
scrollBehavior() {
// Sometimes Vue's scroll behavior is not working as expected, so we need to manually scroll to top (especially on Linux)
document.querySelector('.router-view')?.scrollTo(0, 0)
document.querySelector('.app-viewport')?.scrollTo(0, 0)
return {
el: '.router-view',
el: '.app-viewport',
top: 0,
}
},

View File

@@ -16,7 +16,7 @@ theseus = { path = "../../packages/app-lib", features = ["tauri"] }
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
tauri = { version = "2.1.1", features = ["devtools", "macos-private-api", "protocol-asset", "unstable"] }
tauri = { git = "https://github.com/modrinth/tauri", rev = "9c36dd3", features = ["devtools", "macos-private-api", "protocol-asset", "unstable"] }
tauri-plugin-window-state = "2.2.0"
tauri-plugin-deep-link = "2.2.0"
tauri-plugin-os = "2.2.0"

View File

@@ -103,7 +103,7 @@ pub async fn init_ads_window<R: Runtime>(
AD_LINK.parse().unwrap(),
),
)
.initialization_script(LINK_SCRIPT)
.initialization_script_for_main_only(LINK_SCRIPT, false)
.user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36")
.zoom_hotkeys_enabled(false)
.transparent(true),

View File

@@ -42,9 +42,7 @@
Install content to server
</h1>
</template>
<ContentPageHeader v-else>
<template #title> Discover content </template>
</ContentPageHeader>
<ContentPageHeader v-else></ContentPageHeader>
<NavTabs v-if="!server" :links="selectableProjectTypes" class="hidden md:flex" />
</section>
<aside
@@ -342,7 +340,11 @@ const tags = useTags();
const flags = useFeatureFlags();
const auth = await useAuth();
const projectType = ref({ id: "mod", display: "mod", actual: "mod" });
const projectType = computed(() =>
tags.value.projectTypes.find(
(x) => x.id === route.path.replaceAll(/^\/|s\/?$/g, ""), // Removes prefix `/` and suffixes `s` and `s/`
),
);
const projectTypes = computed(() => [projectType.value.id]);
const server = ref();
@@ -506,10 +508,6 @@ async function serverInstall(project) {
project.installing = false;
}
projectType.value = tags.value.projectTypes.find(
(x) => x.id === route.path.replaceAll(/^\/|s\/?$/g, ""), // Removes prefix `/` and suffixes `s` and `s/`
);
const noLoad = ref(false);
const {
data: rawResults,