Skip to content

Commit

Permalink
refactor: Reduce memroy footprint by using virtual list for modpack u…
Browse files Browse the repository at this point in the history
…pstream display
  • Loading branch information
ci010 committed Nov 24, 2024
1 parent 9d5ad54 commit b962cc2
Show file tree
Hide file tree
Showing 13 changed files with 315 additions and 187 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -163,9 +163,9 @@

<script lang="ts" setup>
import { useMarkdown } from '@/composables/markdown'
import { useVuetifyColor } from '@/composables/vuetify'
import { useVirtualizer, VirtualItem, VirtualizerOptions } from '@tanstack/vue-virtual'
import { VueInstance } from '@vueuse/core'
import { getEl } from '@/util/el'
import StoreProjectInstallVersionDialogVersion from './StoreProjectInstallVersionDialogVersion.vue'
export interface StoreProjectVersion {
Expand Down Expand Up @@ -197,7 +197,6 @@ const props = defineProps<{
const { t } = useI18n()
const emit = defineEmits(['install', 'input'])
const { getColorCode } = useVuetifyColor()
const gameVersions = computed(() => {
const result = [] as string[]
Expand Down Expand Up @@ -231,12 +230,6 @@ const gameVersion = ref('' as string)
const loader = ref('' as string)
const versionType = ref('' as string)
function getKey(i: number) {
const v = all.value[i]
if (!v) return i
return typeof v === 'string' ? v : v.id
}
const all = computed(() => {
const filtered = [] as StoreProjectVersion[]
for (const v of props.versions) {
Expand Down Expand Up @@ -280,8 +273,32 @@ const all = computed(() => {
return [...result, 'divider', ...originals.filter(v => !result.includes(v))]
})
const offsetTop = ref(0)
// Select versions
const loading = ref(false)
const selectedDetail = ref<StoreProjectVersionDetail | undefined>(undefined)
async function onVersionClicked(version: StoreProjectVersion) {
try {
loading.value = true
const detail = await props.getVersionDetail(version)
selectedDetail.value = detail
} finally {
loading.value = false
}
}
function asAny(v: unknown): any {
return v as any
}
watch(() => props.value, (newVal) => {
if (!newVal) {
selectedDetail.value = undefined
}
})
const { render } = useMarkdown()
// virtual scroll
const offsetTop = ref(0)
const containerRef = ref<HTMLElement | VueInstance | null>(null)
const scrollElement = ref<VueInstance | HTMLElement | null>(null)
Expand All @@ -291,7 +308,11 @@ watch(containerRef, container => {
}
})
const selectedDetail = ref<StoreProjectVersionDetail | undefined>(undefined)
function getKey(i: number) {
const v = all.value[i]
if (!v) return i
return typeof v === 'string' ? v : v.id
}
const virtualizerOptions = computed(() => ({
count: selectedDetail.value ? selectedDetail.value.dependencies.length : all.value.length,
Expand All @@ -313,44 +334,14 @@ const measureElement = (el: any) => {
virtualizer.value.measureElement(el)
}
const loading = ref(false)
async function onVersionClicked(version: StoreProjectVersion) {
try {
loading.value = true
const detail = await props.getVersionDetail(version)
selectedDetail.value = detail
} finally {
loading.value = false
}
}
function getEl(e: any) {
if (!e) return undefined
if ('$el' in e) return e.$el as HTMLElement
return e as HTMLElement
}
function asAny(v: unknown): any {
return v as any
}
watch(selectedDetail, () => {
nextTick().then(() => {
const el = getEl(containerRef.value)
if (el) {
virtualizer.value.scrollToIndex(0)
const top = el.offsetTop || 0
offsetTop.value = 0
}
})
})
watch(() => props.value, (newVal) => {
if (!newVal) {
selectedDetail.value = undefined
}
})
const { render } = useMarkdown()
</script>
2 changes: 1 addition & 1 deletion xmcl-keystone-ui/src/directives/sharedTooltip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { SharedTooltipData, useSharedTooltipData } from '@/composables/sharedToo
import { FunctionDirective } from 'vue'

export type VSharedTooltipParam = {
text: string
text?: string
items?: Array<{ icon: string; text: string }>
color?: string
list?: Array<string>
Expand Down
5 changes: 5 additions & 0 deletions xmcl-keystone-ui/src/util/el.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export function getEl(e: any) {
if (!e) return undefined
if ('$el' in e) return e.$el as HTMLElement
return e as HTMLElement
}
33 changes: 33 additions & 0 deletions xmcl-keystone-ui/src/views/AppSideBarContentFocus.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,23 @@

<v-list-item-title v-text="'Text'" />
</v-list-item>

<v-list-item
v-if="upstreamBtn"
v-shared-tooltip.right="_ => (upstreamBtn || [])[1]"
link
push
to="/?upstream=true"
class="non-moveable"
>
<v-list-item-icon>
<v-icon>
{{ upstreamBtn[0] }}
</v-icon>
</v-list-item-icon>
<v-list-item-title v-text="'Text'" />
</v-list-item>

<v-list-item
v-shared-tooltip.right="_ => t('mod.name', 2)"
link
Expand Down Expand Up @@ -78,8 +95,10 @@
</template>
<script lang="ts" setup>
import { useDialog } from '@/composables/dialog'
import { kInstance } from '@/composables/instance'
import { AddInstanceDialogKey } from '@/composables/instanceTemplates'
import { vSharedTooltip } from '@/directives/sharedTooltip'
import { injection } from '@/util/inject'
const router = useRouter()
const expanding = ref(false)
Expand All @@ -101,5 +120,19 @@ router.afterEach((to) => {
})
const { show: showAddInstance } = useDialog(AddInstanceDialogKey)
const { instance } = injection(kInstance)
const upstreamBtn = computed(() => {
const up = instance.value.upstream
if (!up) return undefined
if (up.type === 'curseforge-modpack') {
return ['$vuetify.icons.curseforge', 'Curseforge']
} else if (up.type === 'modrinth-modpack') {
return ['$vuetify.icons.modrinth', 'Modrinth']
} else if (up.type === 'ftb-modpack') {
return ['$vuetify.icons.ftb', 'FTB']
}
return undefined
})
const { t } = useI18n()
</script>
5 changes: 4 additions & 1 deletion xmcl-keystone-ui/src/views/HomeDefault.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<template>
<div
ref="scrollElement"
class="relative mx-3 select-none"
>
<HomeDatabaseError />
Expand Down Expand Up @@ -273,6 +274,9 @@ const saveRowCount = computed(() => getRowCount(containerWidths[CardType.Save]))
// newsLayout.value = layout
// })
const scrollElement = ref(null as HTMLElement | null)
provide('scrollElement', scrollElement)
const { t } = useI18n()
useTutorial(computed(() => {
const steps: DriveStep[] = [
Expand All @@ -283,7 +287,6 @@ useTutorial(computed(() => {
]
return steps
}))
</script>

<style scoped>
Expand Down
78 changes: 23 additions & 55 deletions xmcl-keystone-ui/src/views/HomeFocus.vue
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
<template>
<div
ref="content"
class="h-full overflow-auto"
@wheel="onWheel"
class="relative h-full"
>
<Transition name="slide-y-transition">
<div
v-if="!inView"
key="empty"
class="h-full"
>
<HomeDatabaseError />
</div>
<div
v-if="!upstreamQuery"
class="h-full"
>
<HomeDatabaseError />
<HomeFocusFooter
class="absolute bottom-0 left-0 pb-[26px]"
/>
</div>
<template v-else>
<HomeUpstreamCurseforge
v-else-if="instance.upstream && instance.upstream.type === 'curseforge-modpack'"
v-if="instance.upstream && instance.upstream.type === 'curseforge-modpack'"
:id="instance.upstream.modId"
key="curseforge"
class="p-2"
Expand All @@ -24,23 +24,27 @@
key="modrinth"
class="p-2"
/>
</Transition>

<HomeFocusFooter
class="absolute bottom-0 left-0 flex gap-6 px-8 pb-[26px]"
/>
<HomeUpstreamFeedTheBeast
v-else-if="instance.upstream && instance.upstream.type === 'ftb-modpack'"
:id="instance.upstream.id"
key="ftb"
class="p-2"
/>
</template>
</div>
</template>
<script setup lang="ts">
import { kInstance } from '@/composables/instance'
import { useTutorial } from '@/composables/tutorial'
import { injection } from '@/util/inject'
import { DriveStep } from 'driver.js'
import debounce from 'lodash.debounce'
import HomeFocusFooter from './HomeFocusFooter.vue'
import HomeUpstreamCurseforge from './HomeUpstreamCurseforge.vue'
import HomeUpstreamModrinth from './HomeUpstreamModrinth.vue'
import HomeDatabaseError from './HomeDatabaseError.vue'
import HomeUpstreamFeedTheBeast from './HomeUpstreamFeedTheBeast.vue'
import { useScroll } from '@vueuse/core'
import { useQuery } from '@/composables/query'
const { instance } = injection(kInstance)
const { t } = useI18n()
Expand All @@ -55,41 +59,5 @@ useTutorial(computed(() => {
return steps
}))
const content = ref<HTMLElement | null>(null)
let counter = 0
const inView = ref(false)
const scrollIn = debounce(() => {
if (counter > 3 && instance.value.upstream) {
inView.value = true
}
counter = 0
}, 300)
const scrollOut = debounce(() => {
if (counter > 3 && instance.value.upstream) {
inView.value = false
}
counter = 0
}, 300)
function onWheel(e: WheelEvent) {
const v = content.value
if (!v) return
if (e.deltaY > 0) {
if (!inView.value) {
counter++
scrollIn()
}
}
if (e.deltaY < 0) {
if (inView.value) {
const el = content.value
if (!el) return
if (el.scrollTop === 0) {
counter++
scrollOut()
}
}
}
}
const upstreamQuery = useQuery('upstream')
</script>
Loading

0 comments on commit b962cc2

Please sign in to comment.