初始化
This commit is contained in:
63
src/core/XSystem.ts
Normal file
63
src/core/XSystem.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import ProcessManages from './process/ProcessManages.ts'
|
||||
import AppProcess from './process/AppProcess.ts'
|
||||
import type { AppProcessInfo } from '@/core/process/AppProcessInfo.ts'
|
||||
import { isUndefined } from 'lodash'
|
||||
import { BasicSystemProcess } from '@/core/system/BasicSystemProcess.ts'
|
||||
import { DesktopProcess } from '@/core/desktop/DesktopProcess.ts'
|
||||
import type { IAllEvent } from '@/core/events/EventTypes.ts'
|
||||
import type { IEventBuilder } from '@/core/events/IEventBuilder.ts'
|
||||
import { EventBuilderImpl } from '@/core/events/impl/EventBuilderImpl.ts'
|
||||
|
||||
export default class XSystem {
|
||||
private static _instance: XSystem = new XSystem()
|
||||
|
||||
private _processManages: ProcessManages = new ProcessManages()
|
||||
private _eventManages: IEventBuilder<IAllEvent> = new EventBuilderImpl()
|
||||
|
||||
constructor() {
|
||||
console.log('XSystem')
|
||||
}
|
||||
|
||||
public static get instance() {
|
||||
return this._instance
|
||||
}
|
||||
public get processManages() {
|
||||
return this._processManages
|
||||
}
|
||||
public get eventManages() {
|
||||
return this._eventManages
|
||||
}
|
||||
|
||||
public initialization(dom: HTMLDivElement) {
|
||||
this.run('basic-system', BasicSystemProcess).then(() => {
|
||||
this.run('desktop', DesktopProcess).then((proc) => {
|
||||
console.log(proc)
|
||||
proc.mount(dom)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 运行进程
|
||||
public async run<T extends AppProcess = AppProcess>(
|
||||
proc: string | AppProcessInfo,
|
||||
constructor?: new (info: AppProcessInfo) => T,
|
||||
): Promise<T> {
|
||||
let info = typeof proc === 'string' ? this._processManages.findProcessInfoByName(proc) : proc
|
||||
if (isUndefined(info)) {
|
||||
throw new Error(`未找到进程信息:${proc}`)
|
||||
}
|
||||
|
||||
// 是单例应用
|
||||
if (info.singleton) {
|
||||
let process = this._processManages.findProcessByName(info.name)
|
||||
if (process) {
|
||||
return process as T
|
||||
}
|
||||
}
|
||||
|
||||
// 创建进程
|
||||
let process = isUndefined(constructor) ? new AppProcess(info) : new constructor(info)
|
||||
|
||||
return process as T
|
||||
}
|
||||
}
|
||||
18
src/core/apps/setting/app.json
Normal file
18
src/core/apps/setting/app.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "setting",
|
||||
"title": "设置",
|
||||
"description": "设置",
|
||||
"icon": "iconfont icon-setting",
|
||||
"startName": "main",
|
||||
"singleton": true,
|
||||
"isJustProcess": false,
|
||||
"windowFormConfigs": [
|
||||
{
|
||||
"name": "main",
|
||||
"title": "设置",
|
||||
"icon": "iconfont icon-setting",
|
||||
"width": 800,
|
||||
"height": 600
|
||||
}
|
||||
]
|
||||
}
|
||||
9
src/core/apps/setting/main.vue
Normal file
9
src/core/apps/setting/main.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<div>
|
||||
设置APP页面
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<style scoped></style>
|
||||
10
src/core/common/naive-ui/components.ts
Normal file
10
src/core/common/naive-ui/components.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import {
|
||||
create,
|
||||
NButton,
|
||||
NCard,
|
||||
NConfigProvider,
|
||||
} from 'naive-ui'
|
||||
|
||||
export const naiveUi = create({
|
||||
components: [NButton, NCard, NConfigProvider]
|
||||
})
|
||||
21
src/core/common/naive-ui/discrete-api.ts
Normal file
21
src/core/common/naive-ui/discrete-api.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { createDiscreteApi } from 'naive-ui'
|
||||
import { configProviderProps } from './theme.ts'
|
||||
|
||||
const { message, notification, dialog, loadingBar, modal } = createDiscreteApi(
|
||||
['message', 'dialog', 'notification', 'loadingBar', 'modal'],
|
||||
{
|
||||
configProviderProps: configProviderProps,
|
||||
notificationProviderProps: {
|
||||
placement: 'bottom-right',
|
||||
max: 3
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
export const { messageApi, notificationApi, dialogApi, loadingBarApi, modalApi } = {
|
||||
messageApi: message,
|
||||
notificationApi: notification,
|
||||
dialogApi: dialog,
|
||||
loadingBarApi: loadingBar,
|
||||
modalApi: modal
|
||||
}
|
||||
15
src/core/common/naive-ui/theme.ts
Normal file
15
src/core/common/naive-ui/theme.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { type ConfigProviderProps, darkTheme, dateZhCN, type GlobalTheme, lightTheme, zhCN } from 'naive-ui'
|
||||
|
||||
const lTheme: GlobalTheme = {
|
||||
...lightTheme,
|
||||
common: {
|
||||
...lightTheme.common,
|
||||
primaryColor: '#0070f3'
|
||||
}
|
||||
}
|
||||
|
||||
export const configProviderProps: ConfigProviderProps = {
|
||||
theme: lTheme,
|
||||
dateLocale: dateZhCN,
|
||||
locale: zhCN,
|
||||
}
|
||||
29
src/core/common/types/Version.ts
Normal file
29
src/core/common/types/Version.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 版本信息
|
||||
*/
|
||||
export interface Version {
|
||||
/**
|
||||
* 公司名称
|
||||
*/
|
||||
company: string
|
||||
|
||||
/**
|
||||
* 版本号
|
||||
*/
|
||||
major: number
|
||||
|
||||
/**
|
||||
* 子版本号
|
||||
*/
|
||||
minor: number
|
||||
|
||||
/**
|
||||
* 修订号
|
||||
*/
|
||||
build: number
|
||||
|
||||
/**
|
||||
* 私有版本号
|
||||
*/
|
||||
private: number
|
||||
}
|
||||
97
src/core/desktop/DesktopProcess.ts
Normal file
97
src/core/desktop/DesktopProcess.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import AppProcess from '@/core/process/AppProcess.ts'
|
||||
import type { AppProcessInfo } from '@/core/process/AppProcessInfo.ts'
|
||||
import XSystem from '@/core/XSystem.ts'
|
||||
import { BasicSystemProcess } from '@/core/system/BasicSystemProcess.ts'
|
||||
import { createApp, h } from 'vue'
|
||||
import DesktopComponent from '@/core/desktop/ui/DesktopComponent.vue'
|
||||
import { naiveUi } from '@/core/common/naive-ui/components.ts'
|
||||
import { DesktopEventEnum } from '@/core/events/EventTypes.ts'
|
||||
import { debounce } from 'lodash'
|
||||
|
||||
export class DesktopProcess extends AppProcess {
|
||||
private _desktopRootDom: HTMLElement;
|
||||
private _isMounted: boolean = false;
|
||||
private _width: number = 0;
|
||||
private _height: number = 0;
|
||||
private _pendingResize: boolean = false;
|
||||
|
||||
public get desktopRootDom() {
|
||||
return this._desktopRootDom;
|
||||
}
|
||||
public get isMounted() {
|
||||
return this._isMounted;
|
||||
}
|
||||
public get basicSystemProcess() {
|
||||
return XSystem.instance.processManages.findProcessByName<BasicSystemProcess>('basic-system')
|
||||
}
|
||||
|
||||
public get width() {
|
||||
return this._width;
|
||||
}
|
||||
public set width(value: number) {
|
||||
if (this._height === value) return;
|
||||
if (!this._isMounted) return;
|
||||
this._width = value;
|
||||
this._desktopRootDom.style.width = value >= 0 ? `${value}px` : '100%';
|
||||
|
||||
this.scheduleResizeEvent()
|
||||
}
|
||||
public get height() {
|
||||
return this._height;
|
||||
}
|
||||
public set height(value: number) {
|
||||
if (this._height === value) return;
|
||||
if (!this._isMounted) return;
|
||||
this._height = value;
|
||||
this._desktopRootDom.style.height = value >= 0 ? `${value}px` : '100%';
|
||||
|
||||
this.scheduleResizeEvent()
|
||||
}
|
||||
|
||||
private scheduleResizeEvent() {
|
||||
if (this._pendingResize) return;
|
||||
|
||||
this._pendingResize = true;
|
||||
|
||||
Promise.resolve().then(() => {
|
||||
if (this._pendingResize) {
|
||||
this._pendingResize = false;
|
||||
console.log('onDesktopRootDomResize')
|
||||
this.eventManages.notifyEvent(DesktopEventEnum.onDesktopRootDomResize, this._width, this._height);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private get eventManages() {
|
||||
return XSystem.instance.eventManages;
|
||||
}
|
||||
|
||||
constructor(info: AppProcessInfo) {
|
||||
super(info)
|
||||
console.log('DesktopProcess')
|
||||
}
|
||||
|
||||
public mount(dom: HTMLDivElement) {
|
||||
if (this._isMounted) return;
|
||||
this._width = window.innerWidth
|
||||
this._height = window.innerHeight
|
||||
window.addEventListener(
|
||||
'resize',
|
||||
debounce(() => {
|
||||
this.width = window.innerWidth
|
||||
this.height = window.innerHeight
|
||||
}, 300),
|
||||
)
|
||||
|
||||
dom.style.zIndex = '0';
|
||||
dom.style.width = `${this._width}px`
|
||||
dom.style.height = `${this._height}px`
|
||||
this._desktopRootDom = dom;
|
||||
|
||||
const app = createApp(DesktopComponent, { process: this })
|
||||
app.use(naiveUi)
|
||||
app.mount(dom)
|
||||
|
||||
this._isMounted = true;
|
||||
}
|
||||
}
|
||||
15
src/core/desktop/DesktopProcessInfo.ts
Normal file
15
src/core/desktop/DesktopProcessInfo.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { AppProcessInfo } from '@/core/process/AppProcessInfo.ts'
|
||||
|
||||
export const DesktopProcessInfo = new AppProcessInfo({
|
||||
name: 'desktop',
|
||||
title: '桌面',
|
||||
version: {
|
||||
company: 'XZG',
|
||||
major: 1,
|
||||
minor: 0,
|
||||
build: 0,
|
||||
private: 0
|
||||
},
|
||||
singleton: true,
|
||||
isJustProcess: true
|
||||
})
|
||||
7
src/core/desktop/types/IconType.ts
Normal file
7
src/core/desktop/types/IconType.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export interface IconType {
|
||||
name: string;
|
||||
icon: string;
|
||||
path: string;
|
||||
col: number;
|
||||
row: number;
|
||||
}
|
||||
101
src/core/desktop/ui/DesktopComponent.vue
Normal file
101
src/core/desktop/ui/DesktopComponent.vue
Normal file
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<n-config-provider :config-provider-props="configProviderProps" class="w-full h-full pos-relative">
|
||||
<div ref="desktopRootDom" class="desktop-root">
|
||||
<div class="desktop-container">
|
||||
<div v-for="icon in iconArr" class="icon-container">{{ icon.icon }}</div>
|
||||
</div>
|
||||
<div class="task-bar"></div>
|
||||
</div>
|
||||
</n-config-provider>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { DesktopProcess } from '@/core/desktop/DesktopProcess.ts'
|
||||
import XSystem from '@/core/XSystem.ts'
|
||||
import { notificationApi } from '@/core/common/naive-ui/discrete-api.ts'
|
||||
import { configProviderProps } from '@/core/common/naive-ui/theme.ts'
|
||||
import { DesktopEventEnum } from '@/core/events/EventTypes.ts'
|
||||
import { useIconDrag } from '@/core/desktop/utils/useIconDrag.ts'
|
||||
import { onMounted } from 'vue'
|
||||
import type { IconType } from '@/core/desktop/types/IconType.ts'
|
||||
import { useDesktopInit } from '@/core/desktop/ui/useDesktopInit.ts'
|
||||
|
||||
const props = defineProps<{process: DesktopProcess}>()
|
||||
console.log(props.process)
|
||||
|
||||
const iconArr: IconType[] = [
|
||||
{
|
||||
name: '文件管理器',
|
||||
icon: '🗂',
|
||||
path: '/',
|
||||
col: 1,
|
||||
row: 1
|
||||
},
|
||||
{
|
||||
name: '浏览器',
|
||||
icon: '🌐',
|
||||
path: '/',
|
||||
col: 1,
|
||||
row: 2
|
||||
},
|
||||
{
|
||||
name: '记事本',
|
||||
icon: '📄',
|
||||
path: '/',
|
||||
col: 1,
|
||||
row: 3
|
||||
},
|
||||
{
|
||||
name: '音乐播放器',
|
||||
icon: '🎵',
|
||||
path: '/',
|
||||
col: 1,
|
||||
row: 4
|
||||
}
|
||||
]
|
||||
|
||||
XSystem.instance.eventManages.addEventListener(DesktopEventEnum.onDesktopRootDomResize, (width, height) => {
|
||||
console.log(width, height)
|
||||
notificationApi.create({ title: '桌面通知', content: `桌面尺寸变化${width}x${height}}`, duration: 2000 })
|
||||
})
|
||||
|
||||
const iconsInit = () => {
|
||||
const icons = document.querySelectorAll<HTMLDivElement>('div.icon-container')
|
||||
const container = document.querySelector<HTMLDivElement>('.desktop-container')!
|
||||
icons.forEach((icon: HTMLDivElement) => {
|
||||
useIconDrag(icon, container)
|
||||
})
|
||||
}
|
||||
onMounted(() => {
|
||||
const container = document.querySelector<HTMLDivElement>('.desktop-container')!
|
||||
console.log(container.getBoundingClientRect())
|
||||
// iconsInit()
|
||||
const { col, row } = useDesktopInit(container)
|
||||
console.log(col.value, row.value)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$icon-width: 80px;
|
||||
$icon-height: 110px;
|
||||
|
||||
.desktop-root {
|
||||
@apply w-full h-full flex flex-col;
|
||||
|
||||
.desktop-container {
|
||||
@apply w-full flex-1 grid gap-4 grid-auto-flow-col p-4 pos-relative;
|
||||
grid-template-columns: repeat(auto-fill, minmax($icon-width, 1fr));
|
||||
grid-template-rows: repeat(auto-fill, minmax($icon-height, 1fr));
|
||||
|
||||
.icon-container {
|
||||
width: $icon-width;
|
||||
height: $icon-height;
|
||||
@apply flex flex-col items-center justify-center rounded text-white bg-gray-200;
|
||||
}
|
||||
}
|
||||
|
||||
.task-bar {
|
||||
@apply w-full h-[40px] bg-gray-200;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
44
src/core/desktop/ui/useDesktopInit.ts
Normal file
44
src/core/desktop/ui/useDesktopInit.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import XSystem from '@/core/XSystem.ts'
|
||||
import type { IconType } from '@/core/desktop/types/IconType.ts'
|
||||
import { nextTick, onUnmounted, reactive, toRefs } from 'vue'
|
||||
import { DesktopEventEnum } from '@/core/events/EventTypes.ts'
|
||||
import { useDraggable } from '@vueuse/core'
|
||||
|
||||
export function useDesktopInit(container: HTMLElement) {
|
||||
const gridTemplate = reactive({
|
||||
cellWidth: 90,
|
||||
cellHeight: 110,
|
||||
gap: 10,
|
||||
col: 1,
|
||||
row: 1
|
||||
})
|
||||
|
||||
const ro = new ResizeObserver(entries => {
|
||||
const entry= entries[0]
|
||||
const containerRect = entry.contentRect
|
||||
gridTemplate.col = Math.floor(containerRect.width / gridTemplate.cellWidth);
|
||||
gridTemplate.row = Math.floor(containerRect.height / (gridTemplate.cellHeight));
|
||||
console.log(1111)
|
||||
})
|
||||
ro.observe(container)
|
||||
onUnmounted(() => {
|
||||
ro.unobserve(container)
|
||||
})
|
||||
|
||||
// 有桌面图标的app
|
||||
const apps = XSystem.instance.processManages.processInfos.filter(processInfo => !processInfo.isJustProcess)
|
||||
console.log(apps)
|
||||
const icons: IconType[] = apps.map(processInfo => {
|
||||
return {
|
||||
name: processInfo.name,
|
||||
icon: processInfo.icon,
|
||||
path: processInfo.startName,
|
||||
col: 0,
|
||||
row: 0
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
...toRefs(gridTemplate),
|
||||
}
|
||||
}
|
||||
64
src/core/desktop/utils/useIconDrag.ts
Normal file
64
src/core/desktop/utils/useIconDrag.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { useDraggable } from '@vueuse/core'
|
||||
import { ref } from 'vue'
|
||||
|
||||
export function useIconDrag(el: HTMLElement, container: HTMLElement) {
|
||||
let offsetX = 0
|
||||
let offsetY = 0
|
||||
let containerRect = container.getBoundingClientRect()
|
||||
|
||||
el.addEventListener('mousedown', (e) => {
|
||||
el.classList.add('dragging')
|
||||
|
||||
let rect = el.getBoundingClientRect()
|
||||
console.log(rect)
|
||||
offsetX = e.clientX - rect.left
|
||||
offsetY = e.clientY - rect.top
|
||||
|
||||
// 临时脱离 grid,用绝对定位移动
|
||||
el.style.position = "absolute";
|
||||
el.style.left = rect.left - containerRect.left + "px";
|
||||
el.style.top = rect.top - containerRect.top + "px";
|
||||
el.style.gridRow = "auto";
|
||||
el.style.gridColumn = "auto";
|
||||
|
||||
document.addEventListener("mousemove", onMouseMove);
|
||||
document.addEventListener("mouseup", onMouseUp);
|
||||
})
|
||||
|
||||
function onMouseMove(e: MouseEvent) {
|
||||
if (!el) return;
|
||||
el.style.left = e.clientX - containerRect.left - offsetX + "px";
|
||||
el.style.top = e.clientY - containerRect.top - offsetY + "px";
|
||||
}
|
||||
|
||||
function onMouseUp(e: MouseEvent) {
|
||||
if (!el) return;
|
||||
|
||||
const cellWidth = 90 + 16; // 图标宽度 + gap
|
||||
const cellHeight = 110 + 16;
|
||||
|
||||
// 计算所在行列
|
||||
let col = Math.round((e.clientX - containerRect.left) / cellWidth) + 1;
|
||||
let row = Math.round((e.clientY - containerRect.top) / cellHeight) + 1;
|
||||
|
||||
// 限制在 grid 内
|
||||
const maxCols = Math.floor(containerRect.width / cellWidth);
|
||||
const maxRows = Math.floor(containerRect.height / cellHeight);
|
||||
col = Math.max(1, Math.min(maxCols, col));
|
||||
row = Math.max(1, Math.min(maxRows, row));
|
||||
|
||||
console.log(col, row)
|
||||
|
||||
// 放回 grid
|
||||
el.style.position = "relative";
|
||||
el.style.left = "";
|
||||
el.style.top = "";
|
||||
el.style.gridRow = `${row}`;
|
||||
el.style.gridColumn = `${col}`;
|
||||
|
||||
el.classList.remove("dragging");
|
||||
|
||||
document.removeEventListener("mousemove", onMouseMove);
|
||||
document.removeEventListener("mouseup", onMouseUp);
|
||||
}
|
||||
}
|
||||
32
src/core/events/EventTypes.ts
Normal file
32
src/core/events/EventTypes.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import type { IEventMap } from '@/core/events/IEventBuilder.ts'
|
||||
|
||||
export enum DesktopEventEnum {
|
||||
onDesktopRootDomResize = 'onDesktopRootDomResize'
|
||||
}
|
||||
|
||||
export type DesktopEventParams = {
|
||||
[DesktopEventEnum.onDesktopRootDomResize]: (width: number, height: number) => void
|
||||
}
|
||||
|
||||
export enum BasicSystemEventEnum {
|
||||
onAuthChange = 'onAuthChange',
|
||||
onThemeChange = 'onThemeChange'
|
||||
}
|
||||
|
||||
export type BasicSystemEventParams = {
|
||||
[BasicSystemEventEnum.onAuthChange]: () => {},
|
||||
[BasicSystemEventEnum.onThemeChange]: (theme: string) => void
|
||||
}
|
||||
|
||||
export type AllEventParams = BasicSystemEventParams & DesktopEventParams
|
||||
|
||||
export interface IDesktopEvent extends IEventMap {
|
||||
[DesktopEventEnum.onDesktopRootDomResize]: (width: number, height: number) => void
|
||||
}
|
||||
|
||||
export interface IBasicSystemEvent extends IEventMap {
|
||||
[BasicSystemEventEnum.onAuthChange]: () => {},
|
||||
[BasicSystemEventEnum.onThemeChange]: (theme: string) => void
|
||||
}
|
||||
|
||||
export interface IAllEvent extends IDesktopEvent, IBasicSystemEvent {}
|
||||
45
src/core/events/IEventBuilder.ts
Normal file
45
src/core/events/IEventBuilder.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* 事件定义
|
||||
* @interface IEventMap 事件定义 键是事件名称,值是事件处理函数
|
||||
*/
|
||||
export interface IEventMap {
|
||||
[key: string]: (...args: any[]) => void
|
||||
}
|
||||
|
||||
/**
|
||||
* 事件管理器接口定义
|
||||
*/
|
||||
export interface IEventBuilder<Events extends IEventMap> {
|
||||
/**
|
||||
* 添加事件监听
|
||||
* @param eventName 事件名称
|
||||
* @param handler 事件处理函数
|
||||
* @param options 配置项 { immediate: 立即执行一次 immediateArgs: 立即执行的参数 once: 只监听一次 }
|
||||
* @returns void
|
||||
*/
|
||||
addEventListener<E extends keyof Events, F extends Events[E]>(
|
||||
eventName: E,
|
||||
handler: F,
|
||||
options?: {
|
||||
immediate?: boolean
|
||||
immediateArgs?: Parameters<F>
|
||||
once?: boolean
|
||||
},
|
||||
): void
|
||||
|
||||
/**
|
||||
* 移除事件监听
|
||||
* @param eventName 事件名称
|
||||
* @param handler 事件处理函数
|
||||
* @returns void
|
||||
*/
|
||||
removeEventListener<E extends keyof Events, F extends Events[E]>(eventName: E, handler: F): void
|
||||
|
||||
/**
|
||||
* 触发事件
|
||||
* @param eventName 事件名称
|
||||
* @param args 参数
|
||||
* @returns void
|
||||
*/
|
||||
notifyEvent<E extends keyof Events, F extends Events[E]>(eventName: E, ...args: Parameters<F>): void
|
||||
}
|
||||
94
src/core/events/impl/EventBuilderImpl.ts
Normal file
94
src/core/events/impl/EventBuilderImpl.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import type { IEventBuilder, IEventMap } from '@/core/events/IEventBuilder.ts'
|
||||
|
||||
interface HandlerWrapper<T extends (...args: any[]) => any> {
|
||||
fn: T
|
||||
once: boolean
|
||||
}
|
||||
|
||||
export class EventBuilderImpl<Events extends IEventMap>
|
||||
implements IEventBuilder<Events>
|
||||
{
|
||||
private _eventHandlers = new Map<keyof Events, Set<HandlerWrapper<Events[keyof Events]>>>()
|
||||
|
||||
/**
|
||||
* 添加事件监听器
|
||||
* @param eventName 事件名称
|
||||
* @param handler 监听器
|
||||
* @param options { immediate: 立即执行一次 immediateArgs: 立即执行的参数 once: 只监听一次 }
|
||||
* @example
|
||||
* eventBus.addEventListener('noArgs', () => {})
|
||||
* eventBus.addEventListener('greet', name => {}, { immediate: true, immediateArgs: ['abc'] })
|
||||
* eventBus.addEventListener('onResize', (w, h) => {}, { immediate: true, immediateArgs: [1, 2] })
|
||||
*/
|
||||
addEventListener<E extends keyof Events, F extends Events[E]>(
|
||||
eventName: E,
|
||||
handler: F,
|
||||
options?: {
|
||||
immediate?: boolean;
|
||||
immediateArgs?: Parameters<F>;
|
||||
once?: boolean;
|
||||
},
|
||||
) {
|
||||
if (!handler) return
|
||||
if (!this._eventHandlers.has(eventName)) {
|
||||
this._eventHandlers.set(eventName, new Set<HandlerWrapper<F>>())
|
||||
}
|
||||
|
||||
const set = this._eventHandlers.get(eventName)!
|
||||
if (![...set].some((wrapper) => wrapper.fn === handler)) {
|
||||
set.add({ fn: handler, once: options?.once ?? false })
|
||||
}
|
||||
|
||||
if (options?.immediate) {
|
||||
try {
|
||||
handler(...(options.immediateArgs ?? []))
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除事件监听器
|
||||
* @param eventName 事件名称
|
||||
* @param handler 监听器
|
||||
* @example
|
||||
* eventBus.removeEventListener('noArgs', () => {})
|
||||
*/
|
||||
removeEventListener<E extends keyof Events, F extends Events[E]>(eventName: E, handler: F) {
|
||||
const set = this._eventHandlers.get(eventName)
|
||||
if (!set) return
|
||||
|
||||
for (const wrapper of set) {
|
||||
if (wrapper.fn === handler) {
|
||||
set.delete(wrapper)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知事件
|
||||
* @param eventName 事件名称
|
||||
* @param args 参数
|
||||
* @example
|
||||
* eventBus.notifyEvent('noArgs')
|
||||
* eventBus.notifyEvent('greet', 'Alice')
|
||||
* eventBus.notifyEvent('onResize', 1, 2)
|
||||
*/
|
||||
notifyEvent<E extends keyof Events, F extends Events[E]>(eventName: E, ...args: Parameters<F>) {
|
||||
if (!this._eventHandlers.has(eventName)) return
|
||||
|
||||
const set = this._eventHandlers.get(eventName)!
|
||||
for (const wrapper of set) {
|
||||
try {
|
||||
wrapper.fn(...args)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
|
||||
if (wrapper.once) {
|
||||
set.delete(wrapper)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
42
src/core/process/AppProcess.ts
Normal file
42
src/core/process/AppProcess.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { v4 as uuidV4 } from 'uuid';
|
||||
import XSystem from '../XSystem.ts'
|
||||
import type { AppProcessInfo } from '../process/AppProcessInfo.ts'
|
||||
import WindowForm from '../window/WindowForm.ts'
|
||||
|
||||
/**
|
||||
* 进程
|
||||
*/
|
||||
export default class AppProcess {
|
||||
private readonly _id: string = uuidV4();
|
||||
private readonly _processInfo: AppProcessInfo;
|
||||
// 当前进程的窗体集合
|
||||
private _windowForms: Map<string, WindowForm> = new Map();
|
||||
|
||||
public get id() {
|
||||
return this._id;
|
||||
}
|
||||
public get processInfo() {
|
||||
return this._processInfo;
|
||||
}
|
||||
public get windowForms() {
|
||||
return this._windowForms;
|
||||
}
|
||||
|
||||
constructor(info: AppProcessInfo) {
|
||||
console.log(`AppProcess: ${info.name}`)
|
||||
this._processInfo = info;
|
||||
|
||||
const startName = info.startName;
|
||||
|
||||
XSystem.instance.processManages.addProcess(this);
|
||||
// 通过设置 isJustProcess 为 true,则不会创建窗体
|
||||
if (!info.isJustProcess) {
|
||||
this.openWindowForm(startName)
|
||||
}
|
||||
}
|
||||
|
||||
public openWindowForm(startName: string) {
|
||||
const window = new WindowForm(this, startName);
|
||||
this._windowForms.set(window.id, window);
|
||||
}
|
||||
}
|
||||
100
src/core/process/AppProcessInfo.ts
Normal file
100
src/core/process/AppProcessInfo.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import type { Version } from '../common/types/Version.ts'
|
||||
import type { AppProcessInfoParams } from '../process/types/AppProcessInfoParams.ts'
|
||||
import type { WindowFormConfig } from '../window/types/WindowFormConfig.ts'
|
||||
|
||||
export class AppProcessInfo {
|
||||
/**
|
||||
* 应用进程名称
|
||||
* @private
|
||||
*/
|
||||
private readonly _name: string;
|
||||
|
||||
/**
|
||||
* 应用进程标题
|
||||
* @private
|
||||
*/
|
||||
private readonly _title: string;
|
||||
|
||||
/**
|
||||
* 应用进程描述
|
||||
* @private
|
||||
*/
|
||||
private readonly _description: string;
|
||||
|
||||
/**
|
||||
* 应用进程图标
|
||||
* @private
|
||||
*/
|
||||
private readonly _icon: string;
|
||||
|
||||
/**
|
||||
* 应用进程启动入口
|
||||
* 对应windowFrom参数name
|
||||
* @private
|
||||
*/
|
||||
private readonly _startName: string;
|
||||
|
||||
/**
|
||||
* 应用版本信息
|
||||
* @private
|
||||
*/
|
||||
private readonly _version: Version;
|
||||
|
||||
/**
|
||||
* 应用是否只存在一个进程
|
||||
* @private
|
||||
*/
|
||||
private readonly _singleton: boolean;
|
||||
|
||||
/**
|
||||
* 是否只是一个进程
|
||||
* @private
|
||||
*/
|
||||
private readonly _isJustProcess: boolean;
|
||||
|
||||
/**
|
||||
* 进程所有的窗口配置信息
|
||||
* @private
|
||||
*/
|
||||
private readonly _windowFormConfigs: Array<WindowFormConfig>;
|
||||
|
||||
constructor(info: AppProcessInfoParams) {
|
||||
this._name = info.name;
|
||||
this._title = info.title || '';
|
||||
this._description = info.description || '';
|
||||
this._icon = <string> info.icon;
|
||||
this._startName = info.startName || '';
|
||||
this._version = info.version || { company: 'XZG', major: 1, minor: 0, build: 0, private: 0 };
|
||||
this._singleton = info.singleton;
|
||||
this._isJustProcess = info.isJustProcess;
|
||||
this._windowFormConfigs = info.windowFormConfigs || [];
|
||||
}
|
||||
|
||||
public get name() {
|
||||
return this._name;
|
||||
}
|
||||
public get title() {
|
||||
return this._title;
|
||||
}
|
||||
public get description() {
|
||||
return this._description;
|
||||
}
|
||||
public get icon() {
|
||||
return this._icon;
|
||||
}
|
||||
public get startName() {
|
||||
return this._startName;
|
||||
}
|
||||
public get version() {
|
||||
return this._version;
|
||||
}
|
||||
public get singleton() {
|
||||
return this._singleton;
|
||||
}
|
||||
public get isJustProcess() {
|
||||
return this._isJustProcess;
|
||||
}
|
||||
public get windowFormConfigs() {
|
||||
return this._windowFormConfigs;
|
||||
}
|
||||
}
|
||||
78
src/core/process/ProcessManages.ts
Normal file
78
src/core/process/ProcessManages.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import type AppProcess from './AppProcess.ts'
|
||||
import { AppProcessInfo } from '@/core/process/AppProcessInfo.ts'
|
||||
import { BasicSystemProcessInfo } from '@/core/system/BasicSystemProcessInfo.ts'
|
||||
import { DesktopProcessInfo } from '@/core/desktop/DesktopProcessInfo.ts'
|
||||
import type { AppProcessInfoParams } from '@/core/process/types/AppProcessInfoParams.ts'
|
||||
|
||||
/**
|
||||
* 进程管理
|
||||
*/
|
||||
export default class ProcessManages {
|
||||
private _processPool: Map<string, AppProcess> = new Map<string, AppProcess>();
|
||||
private _processInfos: AppProcessInfo[] = [];
|
||||
|
||||
public get processInfos() {
|
||||
return this._processInfos;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
console.log('ProcessManages')
|
||||
this.loadAppProcessInfos();
|
||||
}
|
||||
// TODO 加载所有进程信息
|
||||
public loadAppProcessInfos() {
|
||||
console.log('加载所有进程信息')
|
||||
// 添加内置进程
|
||||
const apps = import.meta.glob<AppProcessInfoParams>('../apps/**/*.json', { eager: true })
|
||||
const internalProcessInfos: AppProcessInfo[] = Object.values(apps).map(data => new AppProcessInfo(data))
|
||||
|
||||
this._processInfos.push(BasicSystemProcessInfo)
|
||||
this._processInfos.push(DesktopProcessInfo)
|
||||
|
||||
this._processInfos.push(...internalProcessInfos)
|
||||
}
|
||||
|
||||
// 添加进程
|
||||
public addProcess(process: AppProcess) {
|
||||
this._processPool.set(process.id, process);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过进程id查找进程
|
||||
* @param id 进程id
|
||||
*/
|
||||
public findProcessById(id: string) {
|
||||
return this._processPool.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过进程名称查找进程
|
||||
* @param name 进程名称
|
||||
*/
|
||||
public findProcessByName<T extends AppProcess = AppProcess>(name: string) {
|
||||
const pools = [...this._processPool.values()];
|
||||
return pools.find(proc => proc.processInfo.name === name) as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据进程id删除进程
|
||||
* @param id 进程id
|
||||
*/
|
||||
public removeProcess(id: string): void;
|
||||
/**
|
||||
* 根据进程删除进程
|
||||
* @param process 进程信息
|
||||
*/
|
||||
public removeProcess(process: AppProcess): void;
|
||||
public removeProcess(params: string | AppProcess) {
|
||||
const id = typeof params === 'string' ? params : params.id;
|
||||
this._processPool.delete(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过进程名称查找进程信息
|
||||
*/
|
||||
public findProcessInfoByName(name: string) {
|
||||
return this._processInfos.find(info => info.name === name);
|
||||
}
|
||||
}
|
||||
14
src/core/process/types/AppProcessEvent.ts
Normal file
14
src/core/process/types/AppProcessEvent.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 进程的事件
|
||||
* <p>onProcessExit - 进程退出</p>
|
||||
* <p>onProcessWindowFormOpen - 进程的窗体打开</p>
|
||||
* <p>onProcessWindowFormExit - 进程的窗体退出</p>
|
||||
* <p>onProcessWindowFormFocus - 进程的窗体获取焦点</p>
|
||||
*
|
||||
*/
|
||||
type AppProcessEvent =
|
||||
'onProcessExit' |
|
||||
'onProcessWindowFormOpen' |
|
||||
'onProcessWindowFormExit' |
|
||||
'onProcessWindowFormFocus' |
|
||||
'onProcessWindowFormBlur'
|
||||
26
src/core/process/types/AppProcessInfoParams.ts
Normal file
26
src/core/process/types/AppProcessInfoParams.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import type { Version } from '../../common/types/Version.ts'
|
||||
import type { WindowFormConfig } from '../../window/types/WindowFormConfig.ts'
|
||||
|
||||
/**
|
||||
* 应用进程入参信息
|
||||
*/
|
||||
export interface AppProcessInfoParams {
|
||||
/** 应用进程名称 */
|
||||
name: string;
|
||||
/** 应用进程标题 */
|
||||
title?: string;
|
||||
/** 应用进程描述 */
|
||||
description?: string;
|
||||
/** 应用进程图标 */
|
||||
icon?: string;
|
||||
/** 应用进程启动入口 */
|
||||
startName?: string;
|
||||
/** 应用版本信息 */
|
||||
version?: Version;
|
||||
/** 应用是否只存在一个进程 */
|
||||
singleton: boolean;
|
||||
/** 是否只是一个进程, 没有UI */
|
||||
isJustProcess: boolean;
|
||||
/** 进程所有的窗口配置信息 */
|
||||
windowFormConfigs?: WindowFormConfig[];
|
||||
}
|
||||
18
src/core/system/BasicSystemProcess.ts
Normal file
18
src/core/system/BasicSystemProcess.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import AppProcess from '../process/AppProcess.ts'
|
||||
import { AppProcessInfo } from '@/core/process/AppProcessInfo.ts'
|
||||
|
||||
/**
|
||||
* 基础系统进程
|
||||
*/
|
||||
export class BasicSystemProcess extends AppProcess{
|
||||
private _isMounted: boolean = false;
|
||||
|
||||
public get isMounted() {
|
||||
return this._isMounted;
|
||||
}
|
||||
|
||||
constructor(info: AppProcessInfo) {
|
||||
super(info)
|
||||
console.log('BasicSystemProcess')
|
||||
}
|
||||
}
|
||||
18
src/core/system/BasicSystemProcessInfo.ts
Normal file
18
src/core/system/BasicSystemProcessInfo.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { AppProcessInfo } from '@/core/process/AppProcessInfo.ts'
|
||||
|
||||
/**
|
||||
* 基础系统进程信息
|
||||
*/
|
||||
export const BasicSystemProcessInfo = new AppProcessInfo({
|
||||
name: 'basic-system',
|
||||
title: '基础系统进程',
|
||||
isJustProcess: true,
|
||||
version: {
|
||||
company: 'XZG',
|
||||
major: 1,
|
||||
minor: 0,
|
||||
build: 0,
|
||||
private: 0
|
||||
},
|
||||
singleton: true
|
||||
});
|
||||
17
src/core/utils/Singleton.ts
Normal file
17
src/core/utils/Singleton.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
/** 单例模式
|
||||
* 确保一个类只有一个实例,并提供一个全局访问点
|
||||
* @param constructor
|
||||
* @constructor
|
||||
*/
|
||||
export function Singleton<T extends { new (...args: any[]): any }>(constructor: T): T {
|
||||
let instance: any;
|
||||
|
||||
return new Proxy(constructor, {
|
||||
construct(target, argsList, newTarget) {
|
||||
if (!instance) {
|
||||
instance = Reflect.construct(target, argsList, newTarget);
|
||||
}
|
||||
return instance;
|
||||
},
|
||||
});
|
||||
}
|
||||
22
src/core/window/WindowForm.ts
Normal file
22
src/core/window/WindowForm.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { v4 as uuidV4 } from 'uuid';
|
||||
import type AppProcess from '../process/AppProcess.ts'
|
||||
import XSystem from '../XSystem.ts'
|
||||
import type { AppProcessInfo } from '../process/AppProcessInfo.ts'
|
||||
|
||||
export default class WindowForm {
|
||||
private readonly _id: string = uuidV4();
|
||||
private readonly _procId: string;
|
||||
|
||||
public get id() {
|
||||
return this._id;
|
||||
}
|
||||
public get proc() {
|
||||
return XSystem.instance.processManages.findProcessById(this._procId)
|
||||
}
|
||||
|
||||
constructor(proc: AppProcess, startName: string) {
|
||||
this._procId = proc.id;
|
||||
console.log('WindowForm')
|
||||
}
|
||||
|
||||
}
|
||||
60
src/core/window/types/WindowFormConfig.ts
Normal file
60
src/core/window/types/WindowFormConfig.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* 窗体配置
|
||||
*/
|
||||
export interface WindowFormConfig {
|
||||
/**
|
||||
* 窗体名称
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* 窗体标题
|
||||
*/
|
||||
title?: string;
|
||||
/**
|
||||
* 窗体图标
|
||||
*/
|
||||
icon?: string;
|
||||
top?: number;
|
||||
left?: number;
|
||||
/**
|
||||
* 窗体宽度
|
||||
*/
|
||||
width?: number;
|
||||
widthAuto?: boolean;
|
||||
/**
|
||||
* 窗体高度
|
||||
*/
|
||||
height?: number;
|
||||
heightAuto?: boolean;
|
||||
/**
|
||||
* 窗体最小宽度
|
||||
*/
|
||||
minWidth?: number;
|
||||
/**
|
||||
* 窗体最小高度
|
||||
*/
|
||||
minHeight?: number;
|
||||
/**
|
||||
* 窗体最大宽度
|
||||
*/
|
||||
maxWidth?: number;
|
||||
/**
|
||||
* 窗体最大高度
|
||||
*/
|
||||
maxHeight?: number;
|
||||
/**
|
||||
* 窗体透明度
|
||||
*/
|
||||
opacity?: number;
|
||||
windowStyle?: string;
|
||||
windowState?: number;
|
||||
resizeMode?: number;
|
||||
topMost?: boolean;
|
||||
/**
|
||||
* 是否显示在任务栏
|
||||
*/
|
||||
showInTaskbar?: boolean;
|
||||
showTitleBarIcon?: boolean;
|
||||
showTitleBarText?: boolean;
|
||||
hideTitleBar?: boolean;
|
||||
}
|
||||
Reference in New Issue
Block a user