181 lines
6.8 KiB
TypeScript
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
|