FIX - paginate home screen apps

This commit is contained in:
Eichenholz
2026-08-06 20:35:49 +02:00
parent 398ed8315c
commit 5caedc7143
5 changed files with 59 additions and 23 deletions
+1 -5
View File
@@ -583,13 +583,10 @@ button {
}
}
.springboard-track {
width: 300%;
height: 100%;
display: flex;
touch-action: pan-y;
transform: translateX(
calc(var(--springboard-page) * -33.333333% + var(--drag-offset))
);
transform: translateX(calc(var(--springboard-offset) + var(--drag-offset)));
transition: transform 0.46s cubic-bezier(0.22, 0.9, 0.24, 1);
}
.springboard--dragging .springboard-track {
@@ -597,7 +594,6 @@ button {
}
.springboard-page {
position: relative;
width: 33.333333%;
flex: none;
padding: 61px 22px 138px;
}
+2 -2
View File
@@ -1024,8 +1024,8 @@ export const usePhoneStore = defineStore('phone', {
})
namespaceQueues.set(namespace, tracked)
},
setCurrentPage(page: number): void {
this.currentPage = clampPage(page)
setCurrentPage(page: number, pageCount?: number): void {
this.currentPage = clampPage(page, pageCount)
},
setCameraLandscape(landscape: boolean): void {
this.cameraLandscape = landscape
+13 -1
View File
@@ -1,9 +1,21 @@
import { describe, expect, it } from 'vitest'
import { clampPage } from './pages'
import { clampPage, paginateItems } from './pages'
describe('page clamping', () => {
it('keeps pages in range', () => {
expect(clampPage(-4)).toBe(0)
expect(clampPage(1)).toBe(1)
expect(clampPage(9)).toBe(2)
})
it('splits overflowing app grids into additional pages', () => {
expect(
paginateItems(
Array.from({ length: 21 }, (_, index) => index),
16,
),
).toEqual([
Array.from({ length: 16 }, (_, index) => index),
[16, 17, 18, 19, 20],
])
})
})
+9
View File
@@ -1,5 +1,14 @@
export const SPRINGBOARD_PAGE_COUNT = 3
export function paginateItems<T>(items: readonly T[], pageSize: number): T[][] {
if (!Number.isInteger(pageSize) || pageSize <= 0) return []
const pages: T[][] = []
for (let index = 0; index < items.length; index += pageSize) {
pages.push(items.slice(index, index + pageSize))
}
return pages
}
export function clampPage(
page: number,
pageCount = SPRINGBOARD_PAGE_COUNT,
+34 -15
View File
@@ -7,8 +7,9 @@ import SpringboardWidgets from '@/components/SpringboardWidgets.vue'
import { PHONE_APPS } from '@/config/apps'
import { usePhoneStore } from '@/stores/phone'
import type { PhoneAppDefinition } from '@/types/apps'
import { clampPage, SPRINGBOARD_PAGE_COUNT } from '@/utils/pages'
import { paginateItems } from '@/utils/pages'
const APPS_PER_HOME_PAGE = 16
const phone = usePhoneStore()
const searchQuery = ref('')
const searchFocused = ref(false)
@@ -21,6 +22,14 @@ let pointerStartedAt = 0
const gridApps = computed(() =>
[...PHONE_APPS].sort((a, b) => a.gridOrder - b.gridOrder),
)
const appPages = computed(() =>
paginateItems(gridApps.value, APPS_PER_HOME_PAGE),
)
const pageCount = computed(() => appPages.value.length + 2)
const libraryPage = computed(() => pageCount.value - 1)
const isAppPage = computed(
() => phone.currentPage > 0 && phone.currentPage < libraryPage.value,
)
const dockApps = computed(() =>
PHONE_APPS.filter((app) => app.dockOrder !== null).sort(
(a, b) => (a.dockOrder ?? 0) - (b.dockOrder ?? 0),
@@ -57,8 +66,10 @@ const alphabeticalGroups = computed(() => {
})
const trackStyle = computed(() => ({
'--drag-offset': `${dragOffset.value}px`,
'--springboard-page': phone.currentPage,
'--springboard-offset': `${(-phone.currentPage * 100) / pageCount.value}%`,
width: `${pageCount.value * 100}%`,
}))
const pageStyle = computed(() => ({ width: `${100 / pageCount.value}%` }))
function onPointerDown(event: PointerEvent): void {
const target = event.target as HTMLElement
@@ -80,7 +91,10 @@ function finishPointer(event: PointerEvent): void {
const elapsed = Math.max(1, Date.now() - pointerStartedAt)
const velocity = Math.abs(distance) / elapsed
if (Math.abs(distance) > 48 || velocity > 0.45) {
phone.setCurrentPage(phone.currentPage + (distance < 0 ? 1 : -1))
phone.setCurrentPage(
phone.currentPage + (distance < 0 ? 1 : -1),
pageCount.value,
)
}
dragging.value = false
dragOffset.value = 0
@@ -116,22 +130,27 @@ function openAllApps(): void {
>
<section
class="springboard-page springboard-page--widgets"
:style="pageStyle"
:aria-label="phone.t('Home.widgets.label')"
>
<SpringboardWidgets />
</section>
<section
v-for="(apps, pageIndex) in appPages"
:key="`apps-${pageIndex}`"
class="springboard-page springboard-page--apps"
:style="pageStyle"
:aria-label="phone.t('Home.apps')"
>
<div class="app-grid">
<AppIcon v-for="app in gridApps" :key="app.id" :app="app" />
<AppIcon v-for="app in apps" :key="app.id" :app="app" />
</div>
</section>
<section
class="springboard-page springboard-page--library"
:style="pageStyle"
:aria-label="phone.t('Home.appLibrary')"
>
<div
@@ -223,11 +242,7 @@ function openAllApps(): void {
</div>
<Transition name="dock">
<nav
v-if="phone.currentPage === 1"
class="app-dock"
:aria-label="phone.t('Home.dock')"
>
<nav v-if="isAppPage" class="app-dock" :aria-label="phone.t('Home.dock')">
<AppIcon
v-for="app in dockApps"
:key="app.id"
@@ -237,14 +252,18 @@ function openAllApps(): void {
</nav>
</Transition>
<nav class="page-indicator" :aria-label="phone.t('Home.pages')">
<nav
v-if="isAppPage && appPages.length > 1"
class="page-indicator"
:aria-label="phone.t('Home.pages')"
>
<button
v-for="page in SPRINGBOARD_PAGE_COUNT"
:key="page"
v-for="(_, pageIndex) in appPages"
:key="pageIndex"
type="button"
:class="{ active: phone.currentPage === page - 1 }"
:aria-label="`${phone.t('Home.page')} ${page}`"
@click="phone.setCurrentPage(clampPage(page - 1))"
:class="{ active: phone.currentPage === pageIndex + 1 }"
:aria-label="`${phone.t('Home.page')} ${pageIndex + 1}`"
@click="phone.setCurrentPage(pageIndex + 1, pageCount)"
></button>
</nav>
</section>