macos-web/src/system/index.ts
2026-07-20 14:30:44 +08:00

181 lines
6.8 KiB
TypeScript

/**
* system/index.ts — 系统启动入口
*
* 将 original main.js 中的桌面图标渲染和启动序列集成到模块化架构中。
*/
import { $, $$, el, esc, clamp, iconImg } from '../utils'
import { bus } from '../composables/useBus'
import { store } from '../composables/useStore'
import { fs } from '../composables/useFS'
import { wm } from '../composables/useWM'
import { ui } from '../composables/useUI'
import { Sys } from '../composables/useSys'
import { Notify } from '../composables/useNotify'
import { Spotlight } from '../composables/useSpotlight'
import { Apps } from '../composables/useApps'
import { WALLPAPERS } from '../composables/useSettings'
// 注册所有应用
import '../apps/index'
// ==================== 桌面图标渲染 ====================
Sys.renderDesktopIcons = function () {
const container = $('#desktop-icons')!
const items = fs.list(fs.HOME + '/Desktop')
const positions: Record<string, { x: number; y: number }> = store.get('desktop-positions', {})
container.innerHTML = ''
items.forEach((item: any, i: number) => {
const pos = positions[item.name] || { x: 20 + (i % 6) * 110, y: 20 + Math.floor(i / 6) * 110 }
const icon = el('div', {
class: 'desk-icon',
style: { left: pos.x + 'px', top: pos.y + 'px' },
dataset: { path: item.path, name: item.name }
},
iconImg(fs.iconFor(item.path), '', item.name),
el('span', { class: 'desk-label', text: item.name }))
// 单击选中
icon.addEventListener('click', (e: MouseEvent) => {
$$('.desk-icon.sel').forEach((n: HTMLElement) => n.classList.remove('sel'))
icon.classList.add('sel')
})
// 双击打开
icon.addEventListener('dblclick', (e: MouseEvent) => {
Apps.openPath(item.path)
})
// 拖动
let dragging = false, startX = 0, startY = 0, origX = 0, origY = 0
icon.addEventListener('pointerdown', (e: PointerEvent) => {
if (e.button !== 0) return
dragging = true; startX = e.clientX; startY = e.clientY
origX = parseInt(icon.style.left) || 0; origY = parseInt(icon.style.top) || 0
icon.setPointerCapture(e.pointerId)
})
icon.addEventListener('pointermove', (e: PointerEvent) => {
if (!dragging) return
const dx = e.clientX - startX, dy = e.clientY - startY
icon.style.left = (origX + dx) + 'px'
icon.style.top = (origY + dy) + 'px'
})
icon.addEventListener('pointerup', () => {
if (dragging) {
dragging = false
positions[item.name] = {
x: parseInt(icon.style.left) || 0,
y: parseInt(icon.style.top) || 0
}
store.set('desktop-positions', positions)
}
})
// 右键菜单
icon.addEventListener('contextmenu', (e: MouseEvent) => {
e.preventDefault(); e.stopPropagation()
ui.contextMenu([
{ label: '打开', action: () => Apps.openPath(item.path) },
{ label: '重命名…', action: async () => {
const newName = await ui.prompt('重命名', '输入新名称:', item.name)
if (newName && newName !== item.name) {
try { fs.rename(item.path, newName); Sys.renderDesktopIcons() } catch (e: any) { ui.alert('错误', e.message) }
}
}},
{ sep: true },
{ label: '移到废纸篓', action: () => { fs.trash(item.path); Sys.renderDesktopIcons() } },
], e)
})
container.append(icon)
})
}
// ==================== 桌面空白区域事件 ====================
function initDesktop() {
const desktop = $('#desktop')!
desktop.addEventListener('click', (e: MouseEvent) => {
if ((e.target as Element).closest('.desk-icon')) return
$$('.desk-icon.sel').forEach((n: HTMLElement) => n.classList.remove('sel'))
})
desktop.addEventListener('contextmenu', (e: MouseEvent) => {
if ((e.target as Element).closest('.desk-icon')) return
e.preventDefault()
ui.contextMenu([
{ label: '新建文件夹', action: () => {
try { fs.mkdir(fs.HOME + '/Desktop/未命名文件夹'); Sys.renderDesktopIcons() } catch (e: any) { ui.alert('错误', e.message) }
}},
{ sep: true },
{ label: '整理', action: () => { store.remove('desktop-positions'); Sys.renderDesktopIcons() } },
{ label: '排序方式', submenu: [
{ label: '名称', checked: Sys.settings.finderSort === 'name', action: () => { Sys.settings.finderSort = 'name'; Sys.save(); Sys.renderDesktopIcons() } },
{ label: '修改日期', checked: Sys.settings.finderSort === 'date', action: () => { Sys.settings.finderSort = 'date'; Sys.save(); Sys.renderDesktopIcons() } },
]},
{ sep: true },
{ label: '更换墙纸…', action: () => Apps.open('settings', { pane: 'wallpaper' }) },
], e)
})
// 刷新桌面图标的文件变更监听
bus.on('fs:changed', (payload: any) => {
if (!payload || !payload.paths) return
const desktopPath = fs.HOME + '/Desktop'
if (payload.paths.some((p: string) => p === desktopPath || p.startsWith(desktopPath + '/')))
Sys.renderDesktopIcons()
})
}
// ==================== 窗口聚焦 → 菜单栏切换 ====================
bus.on('wm:focus', (win: any) => {
Sys.setActiveApp(win ? win.appId : 'finder')
})
bus.on('wm:changed', () => {
if (!wm.windows.length) Sys.setActiveApp('finder')
})
// ==================== 全局 Escape 关闭浮层 ====================
document.addEventListener('keydown', (e: KeyboardEvent) => {
if (e.key !== 'Escape' || !Sys.unlocked) return
if (!$('#spotlight')!.classList.contains('hidden')) { Spotlight.close(); return }
if (!$('#control-center')!.classList.contains('hidden')) { $('#control-center')!.classList.add('hidden'); return }
if (!$('#notification-center')!.classList.contains('hidden')) { Notify.hideCenter(); return }
ui.closeAllMenus()
})
// ==================== 通知中心外部点击关闭 ====================
document.addEventListener('pointerdown', (e: PointerEvent) => {
const nc = $('#notification-center')!
if (!nc.classList.contains('hidden') && !(e.target as Element).closest('#notification-center') && !(e.target as Element).closest('#mb-clock')) {
Notify.hideCenter()
}
}, true)
// ==================== 启动序列 ====================
export function initSystem() {
fs.init()
Sys.init()
Notify.init()
Sys.renderDock()
initDesktop()
bus.emit('apps:ready')
Sys.boot()
}
// Expose globals for compatibility
;(window as any).Sys = Sys
;(window as any).Notify = Notify
;(window as any).Spotlight = Spotlight
;(window as any).WM = wm
;(window as any).UI = ui
;(window as any).FS = fs
;(window as any).Apps = Apps
;(window as any).Bus = bus
;(window as any).Store = store
;(window as any).WALLPAPERS = WALLPAPERS
;(window as any).$ = $
;(window as any).$$ = $$
;(window as any).el = el
;(window as any).esc = esc
;(window as any).clamp = clamp
;(window as any).iconImg = iconImg