diff --git a/frontend/src/assets/main.css b/frontend/src/assets/main.css index 573cb99..daf9f7c 100644 --- a/frontend/src/assets/main.css +++ b/frontend/src/assets/main.css @@ -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; } diff --git a/frontend/src/stores/phone.ts b/frontend/src/stores/phone.ts index a2589a7..996d4f1 100644 --- a/frontend/src/stores/phone.ts +++ b/frontend/src/stores/phone.ts @@ -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 diff --git a/frontend/src/utils/pages.test.ts b/frontend/src/utils/pages.test.ts index fc7f7c2..c52b5ed 100644 --- a/frontend/src/utils/pages.test.ts +++ b/frontend/src/utils/pages.test.ts @@ -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], + ]) + }) }) diff --git a/frontend/src/utils/pages.ts b/frontend/src/utils/pages.ts index 820dde2..d8e3583 100644 --- a/frontend/src/utils/pages.ts +++ b/frontend/src/utils/pages.ts @@ -1,5 +1,14 @@ export const SPRINGBOARD_PAGE_COUNT = 3 +export function paginateItems(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, diff --git a/frontend/src/views/SpringboardView.vue b/frontend/src/views/SpringboardView.vue index 739dd4d..b2d413f 100644 --- a/frontend/src/views/SpringboardView.vue +++ b/frontend/src/views/SpringboardView.vue @@ -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 { >
- +
-