mirror of
https://github.com/sky-systems/sky_phone.git
synced 2026-08-31 10:18:57 +00:00
FIX - allow apps to move across the dock
This commit is contained in:
@@ -76,7 +76,8 @@ describe('app store', () => {
|
||||
const apps = useAppStoreStore()
|
||||
apps.hydrate(null)
|
||||
|
||||
apps.moveHomeApp('mail', 'grid', 'grid', 0)
|
||||
const mailIndex = apps.homeLayout.grid.indexOf('mail')
|
||||
apps.moveHomeApp('grid', mailIndex, 'grid', 0)
|
||||
expect(apps.homeLayout.grid[0]).toBe('mail')
|
||||
|
||||
apps.removeHomeApp('mail')
|
||||
|
||||
@@ -94,15 +94,15 @@ export const useAppStoreStore = defineStore('app-store', {
|
||||
this.persist()
|
||||
},
|
||||
moveHomeApp(
|
||||
appId: LaunchablePhoneAppId,
|
||||
from: HomeArea,
|
||||
sourceIndex: number,
|
||||
to: HomeArea,
|
||||
targetIndex: number,
|
||||
): void {
|
||||
this.homeLayout = moveHomeApp(
|
||||
this.homeLayout,
|
||||
appId,
|
||||
from,
|
||||
sourceIndex,
|
||||
to,
|
||||
targetIndex,
|
||||
)
|
||||
|
||||
@@ -76,8 +76,32 @@ describe('home layout', () => {
|
||||
expect(layout.dock).toEqual(['messages', null, 'clock', null])
|
||||
})
|
||||
|
||||
it('preserves independently positioned shortcuts for the same app', () => {
|
||||
const grid: HomeLayout['grid'] = Array.from(
|
||||
{ length: HOME_GRID_PAGE_SIZE },
|
||||
() => null,
|
||||
)
|
||||
grid[0] = 'phone'
|
||||
grid[5] = 'phone'
|
||||
|
||||
const layout = parseHomeLayout(
|
||||
{
|
||||
dock: ['phone', null, null, null],
|
||||
grid,
|
||||
hidden: [],
|
||||
version: 2,
|
||||
},
|
||||
defaults,
|
||||
[...installed],
|
||||
)
|
||||
|
||||
expect(layout.grid[0]).toBe('phone')
|
||||
expect(layout.grid[5]).toBe('phone')
|
||||
expect(layout.dock[0]).toBe('phone')
|
||||
})
|
||||
|
||||
it('moves to an exact empty slot without compacting other apps', () => {
|
||||
const moved = moveHomeApp(defaults, 'mail', 'grid', 'grid', 12)
|
||||
const moved = moveHomeApp(defaults, 'grid', 2, 'grid', 12)
|
||||
|
||||
expect(moved.grid[2]).toBeNull()
|
||||
expect(moved.grid[12]).toBe('mail')
|
||||
@@ -86,7 +110,7 @@ describe('home layout', () => {
|
||||
})
|
||||
|
||||
it('shifts occupied grid slots instead of replacing their apps', () => {
|
||||
const reordered = moveHomeApp(defaults, 'mail', 'grid', 'grid', 0)
|
||||
const reordered = moveHomeApp(defaults, 'grid', 2, 'grid', 0)
|
||||
expect(reordered.grid.slice(0, 5)).toEqual([
|
||||
'mail',
|
||||
'phone',
|
||||
@@ -97,7 +121,7 @@ describe('home layout', () => {
|
||||
})
|
||||
|
||||
it('shifts an occupied dock slot toward its gap', () => {
|
||||
const docked = moveHomeApp(defaults, 'mail', 'grid', 'dock', 2)
|
||||
const docked = moveHomeApp(defaults, 'grid', 2, 'dock', 2)
|
||||
|
||||
expect(docked.dock).toEqual(['phone', 'messages', 'mail', 'clock'])
|
||||
expect(docked.grid[2]).toBeNull()
|
||||
@@ -108,20 +132,27 @@ describe('home layout', () => {
|
||||
...defaults,
|
||||
dock: ['phone', 'messages', 'clock', 'notes'],
|
||||
}
|
||||
const docked = moveHomeApp(layout, 'mail', 'grid', 'dock', 1)
|
||||
const docked = moveHomeApp(layout, 'grid', 2, 'dock', 1)
|
||||
|
||||
expect(docked.dock).toEqual(['phone', 'mail', 'messages', 'clock'])
|
||||
expect(docked.grid[2]).toBe('notes')
|
||||
})
|
||||
|
||||
it('moves shortcuts between the dock and grid independently', () => {
|
||||
const movedToGrid = moveHomeApp(defaults, 'dock', 0, 'grid', 5)
|
||||
|
||||
expect(movedToGrid.dock[0]).toBeNull()
|
||||
expect(movedToGrid.grid[0]).toBe('phone')
|
||||
expect(movedToGrid.grid[5]).toBe('phone')
|
||||
|
||||
const movedToDock = moveHomeApp(movedToGrid, 'grid', 1, 'dock', 3)
|
||||
expect(movedToDock.grid[1]).toBeNull()
|
||||
expect(movedToDock.dock[1]).toBe('messages')
|
||||
expect(movedToDock.dock[3]).toBe('messages')
|
||||
})
|
||||
|
||||
it('removes shortcuts without closing gaps and restores the first gap', () => {
|
||||
const layout: HomeLayout = moveHomeApp(
|
||||
defaults,
|
||||
'phone',
|
||||
'grid',
|
||||
'grid',
|
||||
10,
|
||||
)
|
||||
const layout: HomeLayout = moveHomeApp(defaults, 'grid', 0, 'grid', 10)
|
||||
const removed = removeHomeApp(layout, 'phone')
|
||||
expect(removed.grid[0]).toBeNull()
|
||||
expect(removed.grid[10]).toBeNull()
|
||||
|
||||
@@ -52,16 +52,13 @@ function readSlots(
|
||||
const slots = createSlots(length)
|
||||
if (!Array.isArray(value)) return slots
|
||||
|
||||
const usedIds = new Set<LaunchablePhoneAppId>()
|
||||
for (let index = 0; index < Math.min(value.length, length); index += 1) {
|
||||
const valueId = value[index]
|
||||
if (
|
||||
typeof valueId === 'string' &&
|
||||
availableIds.has(valueId as LaunchablePhoneAppId) &&
|
||||
!usedIds.has(valueId as LaunchablePhoneAppId)
|
||||
availableIds.has(valueId as LaunchablePhoneAppId)
|
||||
) {
|
||||
slots[index] = valueId as LaunchablePhoneAppId
|
||||
usedIds.add(valueId as LaunchablePhoneAppId)
|
||||
}
|
||||
}
|
||||
return slots
|
||||
@@ -226,21 +223,27 @@ export function restoreHomeApp(
|
||||
|
||||
export function moveHomeApp(
|
||||
layout: HomeLayout,
|
||||
appId: LaunchablePhoneAppId,
|
||||
from: HomeArea,
|
||||
sourceIndex: number,
|
||||
to: HomeArea,
|
||||
targetIndex: number,
|
||||
): HomeLayout {
|
||||
const next: HomeLayout = {
|
||||
dock: [...layout.dock],
|
||||
grid: [...layout.grid],
|
||||
hidden: layout.hidden.filter((id) => id !== appId),
|
||||
hidden: [...layout.hidden],
|
||||
version: 2,
|
||||
}
|
||||
const source = next[from]
|
||||
const sourceIndex = source.indexOf(appId)
|
||||
const target = next[to]
|
||||
if (sourceIndex === -1 || targetIndex < 0 || targetIndex >= target.length) {
|
||||
const appId = source[sourceIndex]
|
||||
if (
|
||||
!appId ||
|
||||
sourceIndex < 0 ||
|
||||
sourceIndex >= source.length ||
|
||||
targetIndex < 0 ||
|
||||
targetIndex >= target.length
|
||||
) {
|
||||
return layout
|
||||
}
|
||||
|
||||
@@ -251,11 +254,6 @@ export function moveHomeApp(
|
||||
return next
|
||||
}
|
||||
|
||||
const duplicateIndex = target.indexOf(appId)
|
||||
if (duplicateIndex !== -1) {
|
||||
target[duplicateIndex] = null
|
||||
}
|
||||
|
||||
source[sourceIndex] = insertIntoSlot(target, targetIndex, appId)
|
||||
return next
|
||||
}
|
||||
|
||||
@@ -29,8 +29,8 @@ const editMode = ref(false)
|
||||
const dragOffset = ref(0)
|
||||
const dragging = ref(false)
|
||||
const draggingHomeApp = ref<{
|
||||
appId: LaunchablePhoneAppId
|
||||
area: HomeArea
|
||||
index: number
|
||||
} | null>(null)
|
||||
let pointerStart = 0
|
||||
let pointerStartedAt = 0
|
||||
@@ -150,8 +150,8 @@ function enterEditMode(): void {
|
||||
dragOffset.value = 0
|
||||
}
|
||||
|
||||
function startHomeDrag(appId: LaunchablePhoneAppId, area: HomeArea): void {
|
||||
draggingHomeApp.value = { appId, area }
|
||||
function startHomeDrag(area: HomeArea, index: number): void {
|
||||
draggingHomeApp.value = { area, index }
|
||||
}
|
||||
|
||||
function finishHomeDrag(event: PointerEvent): void {
|
||||
@@ -160,20 +160,32 @@ function finishHomeDrag(event: PointerEvent): void {
|
||||
const target = document
|
||||
.elementsFromPoint(event.clientX, event.clientY)
|
||||
.find((element) => !element.closest('.app-icon-item--dragging'))
|
||||
const targetItem = target?.closest<HTMLElement>('[data-home-index]')
|
||||
const targetArea = target?.closest<HTMLElement>('[data-home-area]')
|
||||
let targetItem = target?.closest<HTMLElement>('[data-home-index]')
|
||||
if (!targetItem && targetArea) {
|
||||
const slotItems = Array.from(
|
||||
targetArea.querySelectorAll<HTMLElement>('[data-home-index]'),
|
||||
)
|
||||
targetItem = slotItems.reduce<HTMLElement | undefined>((closest, slot) => {
|
||||
if (!closest) return slot
|
||||
const slotBounds = slot.getBoundingClientRect()
|
||||
const closestBounds = closest.getBoundingClientRect()
|
||||
const slotDistance = Math.hypot(
|
||||
event.clientX - (slotBounds.left + slotBounds.width / 2),
|
||||
event.clientY - (slotBounds.top + slotBounds.height / 2),
|
||||
)
|
||||
const closestDistance = Math.hypot(
|
||||
event.clientX - (closestBounds.left + closestBounds.width / 2),
|
||||
event.clientY - (closestBounds.top + closestBounds.height / 2),
|
||||
)
|
||||
return slotDistance < closestDistance ? slot : closest
|
||||
}, undefined)
|
||||
}
|
||||
const area = (targetItem?.dataset.homeArea ??
|
||||
targetArea?.dataset.homeArea) as HomeArea | undefined
|
||||
if (area === 'grid' || area === 'dock') {
|
||||
const fallbackIndex =
|
||||
area === 'dock'
|
||||
? appStore.homeLayout.dock.length
|
||||
: appStore.homeLayout.grid.length
|
||||
const targetIndex = Number.parseInt(
|
||||
targetItem?.dataset.homeIndex ?? `${fallbackIndex}`,
|
||||
10,
|
||||
)
|
||||
appStore.moveHomeApp(dragged.appId, dragged.area, area, targetIndex)
|
||||
if ((area === 'grid' || area === 'dock') && targetItem) {
|
||||
const targetIndex = Number.parseInt(targetItem.dataset.homeIndex ?? '', 10)
|
||||
appStore.moveHomeApp(dragged.area, dragged.index, area, targetIndex)
|
||||
}
|
||||
draggingHomeApp.value = null
|
||||
}
|
||||
@@ -252,7 +264,12 @@ watch(isAppPage, (visible) => {
|
||||
:edit-mode="editMode"
|
||||
@dragcancel="stopHomeDrag"
|
||||
@dragend="finishHomeDrag"
|
||||
@dragstart="startHomeDrag(app.id, 'grid')"
|
||||
@dragstart="
|
||||
startHomeDrag(
|
||||
'grid',
|
||||
pageIndex * HOME_GRID_PAGE_SIZE + appIndex,
|
||||
)
|
||||
"
|
||||
@edit="enterEditMode"
|
||||
@remove="removeHomeApp(app.id)"
|
||||
/>
|
||||
@@ -392,7 +409,7 @@ watch(isAppPage, (visible) => {
|
||||
:show-label="false"
|
||||
@dragcancel="stopHomeDrag"
|
||||
@dragend="finishHomeDrag"
|
||||
@dragstart="startHomeDrag(app.id, 'dock')"
|
||||
@dragstart="startHomeDrag('dock', appIndex)"
|
||||
@edit="enterEditMode"
|
||||
@remove="removeHomeApp(app.id)"
|
||||
/>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Sky Phone</title>
|
||||
<script type="module" crossorigin src="./assets/sky-index-DqwmUbSx.js"></script>
|
||||
<script type="module" crossorigin src="./assets/sky-index-ByzUhUmM.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="./assets/sky-index-DXf7PPOf.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
Reference in New Issue
Block a user