ENH - update SMS branch from dev

This commit is contained in:
Eichenholz
2026-08-06 19:42:54 +02:00
219 changed files with 29676 additions and 1309 deletions
+74 -8
View File
@@ -1,16 +1,20 @@
import { describe, expect, it } from 'vitest'
import { PHONE_APPS } from './apps'
import { isPhoneAppId, PHONE_APPS } from './apps'
describe('app registry', () => {
it('has unique ids and routes with the reference dock order', () => {
expect(new Set(PHONE_APPS.map((app) => app.id)).size).toBe(
PHONE_APPS.length,
)
expect(PHONE_APPS.every((app) => app.route === `/apps/${app.id}`)).toBe(
true,
)
expect(PHONE_APPS.every((app) => app.iconImage.endsWith('.webp'))).toBe(
true,
)
expect(
PHONE_APPS.every(
(app) => app.route === null || app.route === `/apps/${app.id}`,
),
).toBe(true)
expect(
PHONE_APPS.every(
(app) => typeof app.iconImage === 'string' && app.iconImage.length > 0,
),
).toBe(true)
expect(PHONE_APPS.find((app) => app.id === 'phone')).toMatchObject({
dockOrder: 0,
labelKey: 'Apps.phone.name',
@@ -26,10 +30,72 @@ describe('app registry', () => {
labelKey: 'Apps.weather.name',
route: '/apps/weather',
})
expect(PHONE_APPS.find((app) => app.id === 'calendar')).toMatchObject({
gridOrder: 21,
labelKey: 'Apps.calendar.name',
route: '/apps/calendar',
})
expect(PHONE_APPS.find((app) => app.id === 'snake')).toMatchObject({
dockOrder: null,
gridOrder: 12,
labelKey: 'Apps.snake.name',
route: '/apps/snake',
})
expect(PHONE_APPS.find((app) => app.id === 'memory')).toMatchObject({
dockOrder: null,
gridOrder: 13,
labelKey: 'Apps.memory.name',
route: '/apps/memory',
})
expect(PHONE_APPS.find((app) => app.id === 'number-merge')).toMatchObject({
dockOrder: null,
gridOrder: 14,
labelKey: 'Apps.numberMerge.name',
route: '/apps/number-merge',
})
expect(PHONE_APPS.find((app) => app.id === 'minesweeper')).toMatchObject({
dockOrder: null,
gridOrder: 15,
labelKey: 'Apps.minesweeper.name',
route: '/apps/minesweeper',
})
expect(PHONE_APPS.find((app) => app.id === 'tower-stack')).toMatchObject({
dockOrder: null,
gridOrder: 16,
labelKey: 'Apps.towerStack.name',
route: '/apps/tower-stack',
})
expect(PHONE_APPS.find((app) => app.id === 'sky-flappy')).toMatchObject({
dockOrder: null,
gridOrder: 17,
labelKey: 'Apps.skyFlappy.name',
route: '/apps/sky-flappy',
})
expect(PHONE_APPS.find((app) => app.id === 'neon-drop')).toMatchObject({
dockOrder: null,
gridOrder: 18,
labelKey: 'Apps.neonDrop.name',
route: '/apps/neon-drop',
})
expect(PHONE_APPS.find((app) => app.id === 'citymarkt')).toMatchObject({
dockOrder: null,
gridOrder: 19,
labelKey: 'Apps.citymarkt.name',
route: '/apps/citymarkt',
})
expect(PHONE_APPS.find((app) => app.id === 'camera')).toMatchObject({
route: '/apps/camera',
})
expect(PHONE_APPS.find((app) => app.id === 'photos')).toMatchObject({
route: '/apps/photos',
})
expect(isPhoneAppId('camera')).toBe(true)
expect(isPhoneAppId('photos')).toBe(true)
expect(isPhoneAppId('clock')).toBe(true)
expect(
PHONE_APPS.filter((app) => app.dockOrder !== null)
.sort((a, b) => (a.dockOrder ?? 0) - (b.dockOrder ?? 0))
.map((app) => app.id),
).toEqual(['phone', 'messages', 'calculator'])
).toEqual(['phone', 'messages', 'camera', 'clock'])
})
})
+182 -6
View File
@@ -1,7 +1,15 @@
import {
Calculator,
Bomb,
Blocks,
Camera,
CalendarDays,
Clock3,
Gamepad2,
Grid2X2,
Brain,
Images,
Layers3,
Mail,
MapPinned,
MessageCircle,
@@ -10,12 +18,17 @@ import {
Settings,
ShoppingBag,
CloudSun,
Wind,
Tag,
MapPinHouse,
} from 'lucide-vue-next'
import { defineAsyncComponent, markRaw } from 'vue'
import appStoreIcon from '@/assets/img/app-icons/apps.webp'
import calculatorIcon from '@/assets/img/app-icons/calculator.webp'
import cameraIcon from '@/assets/img/app-icons/camera.webp'
import clockIcon from '@/assets/img/app-icons/clock.webp'
import calendarIcon from '@/assets/img/app-icons/calendar.svg'
import mailIcon from '@/assets/img/app-icons/mail.webp'
import mapIcon from '@/assets/img/app-icons/map.webp'
import messagesIcon from '@/assets/img/app-icons/sms.webp'
@@ -23,10 +36,49 @@ import notesIcon from '@/assets/img/app-icons/notes.webp'
import photosIcon from '@/assets/img/app-icons/gallery.webp'
import phoneIcon from '@/assets/img/app-icons/phone.webp'
import settingsIcon from '@/assets/img/app-icons/settings.webp'
import snakeIcon from '@/assets/img/app-icons/snake.webp'
import memoryIcon from '@/assets/img/app-icons/memory.webp'
import numberMergeIcon from '@/assets/img/app-icons/number-merge.webp'
import minesweeperIcon from '@/assets/img/app-icons/minesweeper.webp'
import towerStackIcon from '@/assets/img/app-icons/tower-stack.webp'
import skyFlappyIcon from '@/assets/img/app-icons/sky-flappy.webp'
import neonDropIcon from '@/assets/img/app-icons/neon-drop.webp'
import weatherIcon from '@/assets/img/app-icons/weather.webp'
import type { PhoneAppDefinition, PhoneAppId } from '@/types/apps'
import citymarktIcon from '@/assets/img/app-icons/citymarkt.webp'
import localPagesIcon from '@/assets/img/app-icons/local-pages.webp'
import type {
LaunchablePhoneAppDefinition,
LaunchablePhoneAppId,
PhoneAppDefinition,
} from '@/types/apps'
export const PHONE_APPS: PhoneAppDefinition[] = [
{
component: markRaw(
defineAsyncComponent(() => import('@/views/apps/CalendarApp.vue')),
),
dockOrder: null,
gridOrder: 21,
icon: markRaw(CalendarDays),
iconClass: 'app-icon--calendar',
iconImage: calendarIcon,
id: 'calendar',
labelKey: 'Apps.calendar.name',
route: '/apps/calendar',
},
{
component: markRaw(
defineAsyncComponent(() => import('@/views/apps/LocalPagesApp.vue')),
),
dockOrder: null,
gridOrder: 20,
icon: markRaw(MapPinHouse),
iconClass: 'app-icon--local-pages',
iconImage: localPagesIcon,
id: 'local-pages',
labelKey: 'Apps.localPages.name',
route: '/apps/local-pages',
},
{
component: markRaw(
defineAsyncComponent(() => import('@/views/apps/PhoneApp.vue')),
@@ -96,7 +148,7 @@ export const PHONE_APPS: PhoneAppDefinition[] = [
component: markRaw(
defineAsyncComponent(() => import('@/views/apps/CalculatorApp.vue')),
),
dockOrder: 2,
dockOrder: null,
gridOrder: 2,
icon: markRaw(Calculator),
iconClass: 'app-icon--calculator',
@@ -105,11 +157,24 @@ export const PHONE_APPS: PhoneAppDefinition[] = [
labelKey: 'Apps.calculator.name',
route: '/apps/calculator',
},
{
component: markRaw(
defineAsyncComponent(() => import('@/views/apps/CameraApp.vue')),
),
dockOrder: 2,
gridOrder: 3,
icon: markRaw(Camera),
iconClass: 'app-icon--camera',
iconImage: cameraIcon,
id: 'camera',
labelKey: 'Apps.camera.name',
route: '/apps/camera',
},
{
component: markRaw(
defineAsyncComponent(() => import('@/views/apps/ClockApp.vue')),
),
dockOrder: null,
dockOrder: 3,
gridOrder: 4,
icon: markRaw(Clock3),
iconClass: 'app-icon--clock',
@@ -133,7 +198,7 @@ export const PHONE_APPS: PhoneAppDefinition[] = [
},
{
component: markRaw(
defineAsyncComponent(() => import('@/views/apps/PhotosApp.vue')),
defineAsyncComponent(() => import('@/views/apps/GalleryApp.vue')),
),
dockOrder: null,
gridOrder: 8,
@@ -170,6 +235,110 @@ export const PHONE_APPS: PhoneAppDefinition[] = [
labelKey: 'Apps.settings.name',
route: '/apps/settings',
},
{
component: markRaw(
defineAsyncComponent(() => import('@/views/apps/SnakeApp.vue')),
),
dockOrder: null,
gridOrder: 12,
icon: markRaw(Gamepad2),
iconClass: 'app-icon--snake',
iconImage: snakeIcon,
id: 'snake',
labelKey: 'Apps.snake.name',
route: '/apps/snake',
},
{
component: markRaw(
defineAsyncComponent(() => import('@/views/apps/MemoryApp.vue')),
),
dockOrder: null,
gridOrder: 13,
icon: markRaw(Brain),
iconClass: 'app-icon--memory',
iconImage: memoryIcon,
id: 'memory',
labelKey: 'Apps.memory.name',
route: '/apps/memory',
},
{
component: markRaw(
defineAsyncComponent(() => import('@/views/apps/NumberMergeApp.vue')),
),
dockOrder: null,
gridOrder: 14,
icon: markRaw(Grid2X2),
iconClass: 'app-icon--number-merge',
iconImage: numberMergeIcon,
id: 'number-merge',
labelKey: 'Apps.numberMerge.name',
route: '/apps/number-merge',
},
{
component: markRaw(
defineAsyncComponent(() => import('@/views/apps/MinesweeperApp.vue')),
),
dockOrder: null,
gridOrder: 15,
icon: markRaw(Bomb),
iconClass: 'app-icon--minesweeper',
iconImage: minesweeperIcon,
id: 'minesweeper',
labelKey: 'Apps.minesweeper.name',
route: '/apps/minesweeper',
},
{
component: markRaw(
defineAsyncComponent(() => import('@/views/apps/TowerStackApp.vue')),
),
dockOrder: null,
gridOrder: 16,
icon: markRaw(Layers3),
iconClass: 'app-icon--tower-stack',
iconImage: towerStackIcon,
id: 'tower-stack',
labelKey: 'Apps.towerStack.name',
route: '/apps/tower-stack',
},
{
component: markRaw(
defineAsyncComponent(() => import('@/views/apps/SkyFlappyApp.vue')),
),
dockOrder: null,
gridOrder: 17,
icon: markRaw(Wind),
iconClass: 'app-icon--sky-flappy',
iconImage: skyFlappyIcon,
id: 'sky-flappy',
labelKey: 'Apps.skyFlappy.name',
route: '/apps/sky-flappy',
},
{
component: markRaw(
defineAsyncComponent(() => import('@/views/apps/CityMarktApp.vue')),
),
dockOrder: null,
gridOrder: 19,
icon: markRaw(Tag),
iconClass: 'app-icon--citymarkt',
iconImage: citymarktIcon,
id: 'citymarkt',
labelKey: 'Apps.citymarkt.name',
route: '/apps/citymarkt',
},
{
component: markRaw(
defineAsyncComponent(() => import('@/views/apps/NeonDropApp.vue')),
),
dockOrder: null,
gridOrder: 18,
icon: markRaw(Blocks),
iconClass: 'app-icon--neon-drop',
iconImage: neonDropIcon,
id: 'neon-drop',
labelKey: 'Apps.neonDrop.name',
route: '/apps/neon-drop',
},
]
export const PHONE_APP_IDS = PHONE_APPS.map((app) => app.id)
@@ -181,6 +350,13 @@ export function getPhoneApp(
return PHONE_APPS.find((app) => app.id === appId)
}
export function isPhoneAppId(value: string): value is PhoneAppId {
return PHONE_APP_IDS.includes(value as PhoneAppId)
export function isPhoneAppId(value: string): value is LaunchablePhoneAppId {
const app = getPhoneApp(value)
return !!app && isLaunchablePhoneApp(app)
}
export function isLaunchablePhoneApp(
app: PhoneAppDefinition,
): app is LaunchablePhoneAppDefinition {
return app.component !== null && app.route !== null
}