diff --git a/frontend/src/utils/homeLayout.test.ts b/frontend/src/utils/homeLayout.test.ts index f84453a..c2e9cbe 100644 --- a/frontend/src/utils/homeLayout.test.ts +++ b/frontend/src/utils/homeLayout.test.ts @@ -85,20 +85,33 @@ describe('home layout', () => { expect(moved.grid[4]).toBe('notes') }) - it('swaps occupied slots and avoids duplicate displaced shortcuts', () => { + it('shifts occupied grid slots instead of replacing their apps', () => { const reordered = moveHomeApp(defaults, 'mail', 'grid', 'grid', 0) expect(reordered.grid.slice(0, 5)).toEqual([ 'mail', - 'messages', 'phone', + 'messages', 'clock', 'notes', ]) + }) - const docked = moveHomeApp(reordered, 'mail', 'grid', 'dock', 2) - expect(docked.dock).toEqual(['phone', 'messages', 'mail', null]) - expect(docked.grid[0]).toBeNull() - expect(docked.grid.filter((id) => id === 'clock')).toHaveLength(1) + it('shifts an occupied dock slot toward its gap', () => { + const docked = moveHomeApp(defaults, 'mail', 'grid', 'dock', 2) + + expect(docked.dock).toEqual(['phone', 'messages', 'mail', 'clock']) + expect(docked.grid[2]).toBeNull() + }) + + it('moves an app displaced from a full dock into the source slot', () => { + const layout: HomeLayout = { + ...defaults, + dock: ['phone', 'messages', 'clock', 'notes'], + } + const docked = moveHomeApp(layout, 'mail', 'grid', 'dock', 1) + + expect(docked.dock).toEqual(['phone', 'mail', 'messages', 'clock']) + expect(docked.grid[2]).toBe('notes') }) it('removes shortcuts without closing gaps and restores the first gap', () => { diff --git a/frontend/src/utils/homeLayout.ts b/frontend/src/utils/homeLayout.ts index b42d6fa..06ae0e0 100644 --- a/frontend/src/utils/homeLayout.ts +++ b/frontend/src/utils/homeLayout.ts @@ -81,6 +81,42 @@ function placeInFirstEmptySlot( slots[slots.length - HOME_GRID_PAGE_SIZE] = appId } +function insertIntoSlot( + slots: HomeSlot[], + targetIndex: number, + appId: LaunchablePhoneAppId, +): HomeSlot { + if (slots[targetIndex] === null) { + slots[targetIndex] = appId + return null + } + + const emptyAfter = slots.indexOf(null, targetIndex + 1) + if (emptyAfter !== -1) { + for (let index = emptyAfter; index > targetIndex; index -= 1) { + slots[index] = slots[index - 1] + } + slots[targetIndex] = appId + return null + } + + const emptyBefore = slots.lastIndexOf(null, targetIndex - 1) + if (emptyBefore !== -1) { + for (let index = emptyBefore; index < targetIndex; index += 1) { + slots[index] = slots[index + 1] + } + slots[targetIndex] = appId + return null + } + + const displacedApp = slots.at(-1) ?? null + for (let index = slots.length - 1; index > targetIndex; index -= 1) { + slots[index] = slots[index - 1] + } + slots[targetIndex] = appId + return displacedApp +} + export function createDefaultHomeLayout( installedIds: LaunchablePhoneAppId[], defaultGridIds: LaunchablePhoneAppId[], @@ -209,9 +245,9 @@ export function moveHomeApp( } if (from === to) { - const displacedApp = source[targetIndex] - source[targetIndex] = appId - source[sourceIndex] = displacedApp + if (sourceIndex === targetIndex) return layout + source[sourceIndex] = null + insertIntoSlot(source, targetIndex, appId) return next } @@ -220,13 +256,6 @@ export function moveHomeApp( target[duplicateIndex] = null } - const displacedApp = target[targetIndex] - source[sourceIndex] = - displacedApp && - displacedApp !== appId && - !source.some((id, index) => id === displacedApp && index !== sourceIndex) - ? displacedApp - : null - target[targetIndex] = appId + source[sourceIndex] = insertIntoSlot(target, targetIndex, appId) return next } diff --git a/sky_phone/source/html/index.html b/sky_phone/source/html/index.html index 081aac1..b10f17b 100644 --- a/sky_phone/source/html/index.html +++ b/sky_phone/source/html/index.html @@ -4,7 +4,7 @@