WindowFormElement
This commit is contained in:
57
src/core/events/WindowFormEventManager.ts
Normal file
57
src/core/events/WindowFormEventManager.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import { EventBuilderImpl } from '@/core/events/impl/EventBuilderImpl.ts'
|
||||||
|
import type { IEventMap } from '@/core/events/IEventBuilder.ts'
|
||||||
|
import type { TWindowFormState } from '@/core/window/types/WindowFormTypes.ts'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 窗口的事件
|
||||||
|
*/
|
||||||
|
export interface WindowFormEvent extends IEventMap {
|
||||||
|
/**
|
||||||
|
* 窗口最小化
|
||||||
|
* @param id 窗口id
|
||||||
|
*/
|
||||||
|
windowFormMinimize: (id: string) => void;
|
||||||
|
/**
|
||||||
|
* 窗口最大化
|
||||||
|
* @param id 窗口id
|
||||||
|
*/
|
||||||
|
windowFormMaximize: (id: string) => void;
|
||||||
|
/**
|
||||||
|
* 窗口还原
|
||||||
|
* @param id 窗口id
|
||||||
|
*/
|
||||||
|
windowFormRestore: (id: string) => void;
|
||||||
|
/**
|
||||||
|
* 窗口关闭
|
||||||
|
* @param id 窗口id
|
||||||
|
*/
|
||||||
|
windowFormClose: (id: string) => void;
|
||||||
|
/**
|
||||||
|
* 窗口聚焦
|
||||||
|
* @param id 窗口id
|
||||||
|
*/
|
||||||
|
windowFormFocus: (id: string) => void;
|
||||||
|
/**
|
||||||
|
* 窗口数据更新
|
||||||
|
* @param data 窗口数据
|
||||||
|
*/
|
||||||
|
windowFormDataUpdate: (data: IWindowFormDataUpdateParams) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IWindowFormDataUpdateParams {
|
||||||
|
/** 窗口id */
|
||||||
|
id: string;
|
||||||
|
/** 窗口状态 */
|
||||||
|
state: TWindowFormState,
|
||||||
|
/** 窗口宽度 */
|
||||||
|
width: number,
|
||||||
|
/** 窗口高度 */
|
||||||
|
height: number,
|
||||||
|
/** 窗口x坐标(左上角) */
|
||||||
|
x: number,
|
||||||
|
/** 窗口y坐标(左上角) */
|
||||||
|
y: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 窗口事件管理器 */
|
||||||
|
export const wfem = new EventBuilderImpl<WindowFormEvent>()
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { IProcessInfo } from '@/core/process/IProcessInfo.ts'
|
import type { IProcessInfo } from '@/core/process/IProcessInfo.ts'
|
||||||
import type { IWindowForm } from '@/core/window/IWindowForm.ts'
|
import type { IWindowForm } from '@/core/window/IWindowForm.ts'
|
||||||
import type { IEventBuilder } from '@/core/events/IEventBuilder.ts'
|
import type { IEventBuilder } from '@/core/events/IEventBuilder.ts'
|
||||||
import type { IProcessEvent } from '@/core/process/types/ProcessEvent.ts'
|
import type { IProcessEvent } from '@/core/process/types/ProcessEventTypes.ts'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 进程接口
|
* 进程接口
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import type { IWindowForm } from '@/core/window/IWindowForm.ts'
|
|||||||
import { processManager } from '@/core/process/ProcessManager.ts'
|
import { processManager } from '@/core/process/ProcessManager.ts'
|
||||||
import { EventBuilderImpl } from '@/core/events/impl/EventBuilderImpl.ts'
|
import { EventBuilderImpl } from '@/core/events/impl/EventBuilderImpl.ts'
|
||||||
import type { IEventBuilder } from '@/core/events/IEventBuilder.ts'
|
import type { IEventBuilder } from '@/core/events/IEventBuilder.ts'
|
||||||
import type { IProcessEvent } from '@/core/process/types/ProcessEvent.ts'
|
import type { IProcessEvent } from '@/core/process/types/ProcessEventTypes.ts'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 进程
|
* 进程
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ export class ObservableImpl<T extends TNonFunctionProperties<T>> implements IObs
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 订阅整个状态变化 */
|
/** 订阅整个状态变化 */
|
||||||
subscribe(fn: TObservableListener<T>, options: { immediate?: boolean } = {}): () => void {
|
public subscribe(fn: TObservableListener<T>, options: { immediate?: boolean } = {}): () => void {
|
||||||
this.listeners.add(fn)
|
this.listeners.add(fn)
|
||||||
if (options.immediate) fn(this.state)
|
if (options.immediate) fn(this.state)
|
||||||
return () => {
|
return () => {
|
||||||
@@ -189,7 +189,7 @@ export class ObservableImpl<T extends TNonFunctionProperties<T>> implements IObs
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 订阅指定字段变化 */
|
/** 订阅指定字段变化 */
|
||||||
subscribeKey<K extends keyof T>(
|
public subscribeKey<K extends keyof T>(
|
||||||
keys: K | K[],
|
keys: K | K[],
|
||||||
fn: TObservableKeyListener<T, K>,
|
fn: TObservableKeyListener<T, K>,
|
||||||
options: { immediate?: boolean } = {}
|
options: { immediate?: boolean } = {}
|
||||||
@@ -214,7 +214,7 @@ export class ObservableImpl<T extends TNonFunctionProperties<T>> implements IObs
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 批量更新状态 */
|
/** 批量更新状态 */
|
||||||
patch(values: Partial<T>): void {
|
public patch(values: Partial<T>): void {
|
||||||
for (const key in values) {
|
for (const key in values) {
|
||||||
if (Object.prototype.hasOwnProperty.call(values, key)) {
|
if (Object.prototype.hasOwnProperty.call(values, key)) {
|
||||||
const typedKey = key as keyof T
|
const typedKey = key as keyof T
|
||||||
@@ -226,7 +226,7 @@ export class ObservableImpl<T extends TNonFunctionProperties<T>> implements IObs
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 销毁 Observable 实例 */
|
/** 销毁 Observable 实例 */
|
||||||
dispose(): void {
|
public dispose(): void {
|
||||||
this.disposed = true
|
this.disposed = true
|
||||||
this.listeners.clear()
|
this.listeners.clear()
|
||||||
this.keyListeners.clear()
|
this.keyListeners.clear()
|
||||||
@@ -234,7 +234,7 @@ export class ObservableImpl<T extends TNonFunctionProperties<T>> implements IObs
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 语法糖:返回一个可解构赋值的 Proxy */
|
/** 语法糖:返回一个可解构赋值的 Proxy */
|
||||||
toRefsProxy(): { [K in keyof T]: T[K] } {
|
public toRefsProxy(): { [K in keyof T]: T[K] } {
|
||||||
const self = this
|
const self = this
|
||||||
return new Proxy({} as T, {
|
return new Proxy({} as T, {
|
||||||
get(_, prop: string | symbol) {
|
get(_, prop: string | symbol) {
|
||||||
|
|||||||
@@ -9,91 +9,125 @@ import { DraggableResizableWindow } from '@/core/utils/DraggableResizableWindow.
|
|||||||
import '../css/window-form.scss'
|
import '../css/window-form.scss'
|
||||||
import { serviceManager } from '@/core/service/kernel/ServiceManager.ts'
|
import { serviceManager } from '@/core/service/kernel/ServiceManager.ts'
|
||||||
import '../ui/WindowFormElement.ts'
|
import '../ui/WindowFormElement.ts'
|
||||||
|
import { wfem } from '@/core/events/WindowFormEventManager.ts'
|
||||||
|
import type { IObservable } from '@/core/state/IObservable.ts'
|
||||||
|
import { ObservableImpl } from '@/core/state/impl/ObservableImpl.ts'
|
||||||
|
|
||||||
export default class WindowFormImpl implements IWindowForm {
|
export default class WindowFormImpl implements IWindowForm {
|
||||||
private readonly _id: string = uuidV4();
|
private readonly _id: string = uuidV4()
|
||||||
private readonly _procId: string;
|
private readonly _procId: string
|
||||||
private dom: HTMLElement;
|
private dom: HTMLElement
|
||||||
private drw: DraggableResizableWindow;
|
private drw: DraggableResizableWindow
|
||||||
private pos: WindowFormPos = { x: 0, y: 0 };
|
private pos: WindowFormPos = { x: 0, y: 0 }
|
||||||
private width: number;
|
private width: number
|
||||||
private height: number;
|
private height: number
|
||||||
|
private _state: IObservable<{ x: number, y: number }> = new ObservableImpl({
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
})
|
||||||
|
|
||||||
public get id() {
|
public get id() {
|
||||||
return this._id;
|
return this._id
|
||||||
}
|
}
|
||||||
public get proc() {
|
public get proc() {
|
||||||
return processManager.findProcessById(this._procId)
|
return processManager.findProcessById(this._procId)
|
||||||
}
|
}
|
||||||
private get desktopRootDom() {
|
private get desktopRootDom() {
|
||||||
return XSystem.instance.desktopRootDom;
|
return XSystem.instance.desktopRootDom
|
||||||
}
|
}
|
||||||
private get sm() {
|
private get sm() {
|
||||||
return serviceManager.getService('WindowForm')
|
return serviceManager.getService('WindowForm')
|
||||||
}
|
}
|
||||||
public get windowFormEle() {
|
public get windowFormEle() {
|
||||||
return this.dom;
|
return this.dom
|
||||||
}
|
}
|
||||||
public get windowFormState() {
|
public get windowFormState() {
|
||||||
return this.drw.windowFormState
|
return this.drw.windowFormState
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(proc: IProcess, config: IWindowFormConfig) {
|
constructor(proc: IProcess, config: IWindowFormConfig) {
|
||||||
this._procId = proc.id;
|
this._procId = proc.id
|
||||||
console.log('WindowForm')
|
console.log('WindowForm')
|
||||||
this.pos = {
|
this.pos = {
|
||||||
x: config.left ?? 0,
|
x: config.left ?? 0,
|
||||||
y: config.top ?? 0
|
y: config.top ?? 0,
|
||||||
}
|
}
|
||||||
this.width = config.width ?? 200;
|
this.width = config.width ?? 200
|
||||||
this.height = config.height ?? 100;
|
this.height = config.height ?? 100
|
||||||
|
|
||||||
this.createWindowFrom();
|
this.createWindowFrom()
|
||||||
|
this.initEvent()
|
||||||
|
}
|
||||||
|
|
||||||
|
private initEvent() {
|
||||||
|
wfem.addEventListener('windowFormClose', this.windowFormCloseFun)
|
||||||
|
}
|
||||||
|
|
||||||
|
private windowFormCloseFun = (id: string) => {
|
||||||
|
if (id === this.id) {
|
||||||
|
this.closeWindowForm()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private createWindowFrom() {
|
private createWindowFrom() {
|
||||||
this.dom = this.createWindowFormEle();
|
// this.dom = this.createWindowFormEle();
|
||||||
this.dom.style.position = 'absolute';
|
// this.dom.style.position = 'absolute';
|
||||||
this.dom.style.width = `${this.width}px`;
|
// this.dom.style.width = `${this.width}px`;
|
||||||
this.dom.style.height = `${this.height}px`;
|
// this.dom.style.height = `${this.height}px`;
|
||||||
this.dom.style.zIndex = '10';
|
// this.dom.style.zIndex = '10';
|
||||||
|
//
|
||||||
const header = this.dom.querySelector('.title-bar') as HTMLElement;
|
// const header = this.dom.querySelector('.title-bar') as HTMLElement;
|
||||||
const content = this.dom.querySelector('.window-content') as HTMLElement;
|
// const content = this.dom.querySelector('.window-content') as HTMLElement;
|
||||||
this.drw = new DraggableResizableWindow({
|
// this.drw = new DraggableResizableWindow({
|
||||||
target: this.dom,
|
// target: this.dom,
|
||||||
handle: header,
|
// handle: header,
|
||||||
snapAnimation: true,
|
// snapAnimation: true,
|
||||||
snapThreshold: 20,
|
// snapThreshold: 20,
|
||||||
boundaryElement: document.body,
|
// boundaryElement: document.body,
|
||||||
taskbarElementId: '#taskbar',
|
// taskbarElementId: '#taskbar',
|
||||||
onWindowStateChange: (state) => {
|
// onWindowStateChange: (state) => {
|
||||||
if (state === 'maximized') {
|
// if (state === 'maximized') {
|
||||||
this.dom.style.borderRadius = '0px';
|
// this.dom.style.borderRadius = '0px';
|
||||||
} else {
|
// } else {
|
||||||
this.dom.style.borderRadius = '5px';
|
// this.dom.style.borderRadius = '5px';
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
})
|
// })
|
||||||
|
|
||||||
// this.desktopRootDom.appendChild(this.dom);
|
// this.desktopRootDom.appendChild(this.dom);
|
||||||
const wf = document.createElement('window-form-element')
|
const wf = document.createElement('window-form-element')
|
||||||
|
wf.pos = this._state
|
||||||
|
wf.wid = this.id
|
||||||
wf.dragContainer = document.body
|
wf.dragContainer = document.body
|
||||||
wf.snapDistance = 20
|
wf.snapDistance = 20
|
||||||
wf.addEventListener('windowForm:dragStart', (e) => {
|
wf.taskbarElementId = '#taskbar'
|
||||||
console.log('dragstart', e)
|
wf.addManagedEventListener('windowForm:stateChange:minimize', (e) => {
|
||||||
|
console.log('windowForm:stateChange:minimize', e)
|
||||||
})
|
})
|
||||||
this.desktopRootDom.appendChild(wf)
|
wf.addManagedEventListener('windowForm:stateChange:maximize', (e) => {
|
||||||
|
console.log('windowForm:stateChange:maximize', e)
|
||||||
|
})
|
||||||
|
wf.addManagedEventListener('windowForm:stateChange:restore', (e) => {
|
||||||
|
console.log('windowForm:stateChange:restore', e)
|
||||||
|
})
|
||||||
|
wf.addManagedEventListener('windowForm:close', () => {
|
||||||
|
this.closeWindowForm()
|
||||||
|
})
|
||||||
|
this.dom = wf
|
||||||
|
this.desktopRootDom.appendChild(this.dom)
|
||||||
|
wfem.notifyEvent('windowFormFocus', this.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
public closeWindowForm() {
|
public closeWindowForm() {
|
||||||
this.drw.destroy();
|
// this.drw.destroy();
|
||||||
this.desktopRootDom.removeChild(this.dom);
|
this.desktopRootDom.removeChild(this.dom)
|
||||||
this.proc?.event.notifyEvent('onProcessWindowFormExit', this.id)
|
this.proc?.event.notifyEvent('onProcessWindowFormExit', this.id)
|
||||||
|
wfem.removeEventListener('windowFormClose', this.windowFormCloseFun)
|
||||||
|
// wfem.notifyEvent('windowFormClose', this.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
private createWindowFormEle() {
|
private createWindowFormEle() {
|
||||||
const template = document.createElement('template');
|
const template = document.createElement('template')
|
||||||
template.innerHTML = `
|
template.innerHTML = `
|
||||||
<div class="window">
|
<div class="window">
|
||||||
<div class="title-bar">
|
<div class="title-bar">
|
||||||
@@ -107,23 +141,24 @@ export default class WindowFormImpl implements IWindowForm {
|
|||||||
<div class="window-content"></div>
|
<div class="window-content"></div>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
const fragment = template.content.cloneNode(true) as DocumentFragment;
|
const fragment = template.content.cloneNode(true) as DocumentFragment
|
||||||
const windowElement = fragment.firstElementChild as HTMLElement
|
const windowElement = fragment.firstElementChild as HTMLElement
|
||||||
|
|
||||||
windowElement.querySelector('.btn.minimize')
|
windowElement
|
||||||
?.addEventListener('click', () => this.drw.minimize());
|
.querySelector('.btn.minimize')
|
||||||
|
?.addEventListener('click', () => this.drw.minimize())
|
||||||
|
|
||||||
windowElement.querySelector('.btn.maximize')
|
windowElement.querySelector('.btn.maximize')?.addEventListener('click', () => {
|
||||||
?.addEventListener('click', () => {
|
|
||||||
if (this.drw.windowFormState === 'maximized') {
|
if (this.drw.windowFormState === 'maximized') {
|
||||||
this.drw.restore()
|
this.drw.restore()
|
||||||
} else {
|
} else {
|
||||||
this.drw.maximize()
|
this.drw.maximize()
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
windowElement.querySelector('.btn.close')
|
windowElement
|
||||||
?.addEventListener('click', () => this.closeWindowForm());
|
.querySelector('.btn.close')
|
||||||
|
?.addEventListener('click', () => this.closeWindowForm())
|
||||||
|
|
||||||
return windowElement
|
return windowElement
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,21 @@
|
|||||||
--shadow: 0 10px 30px rgba(0,0,0,0.25);
|
--shadow: 0 10px 30px rgba(0,0,0,0.25);
|
||||||
font-family: system-ui, "Segoe UI", Roboto, "Helvetica Neue", Arial;
|
font-family: system-ui, "Segoe UI", Roboto, "Helvetica Neue", Arial;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:host([focused]) {
|
||||||
|
z-index: 11;
|
||||||
|
.window {
|
||||||
|
border-color: #8338ec;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([windowFormState='maximized']) {
|
||||||
|
.window {
|
||||||
|
border-radius: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.window {
|
.window {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
box-shadow: var(--shadow, 0 10px 30px rgba(0,0,0,0.25));
|
box-shadow: var(--shadow, 0 10px 30px rgba(0,0,0,0.25));
|
||||||
@@ -20,7 +35,12 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
|
&.focus {
|
||||||
|
border-color: #3a86ff;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.titlebar {
|
.titlebar {
|
||||||
height: var(--titlebar-height, 32px);
|
height: var(--titlebar-height, 32px);
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -30,6 +50,7 @@
|
|||||||
background: linear-gradient(#f2f2f2, #e9e9e9);
|
background: linear-gradient(#f2f2f2, #e9e9e9);
|
||||||
border-bottom: 1px solid rgba(0,0,0,0.06);
|
border-bottom: 1px solid rgba(0,0,0,0.06);
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
@@ -39,7 +60,9 @@
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
color: #111;
|
color: #111;
|
||||||
}
|
}
|
||||||
|
|
||||||
.controls { display: flex; gap: 6px; }
|
.controls { display: flex; gap: 6px; }
|
||||||
|
|
||||||
button.ctrl {
|
button.ctrl {
|
||||||
width: 34px;
|
width: 34px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
@@ -50,7 +73,9 @@ button.ctrl {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
button.ctrl:hover { background: rgba(0,0,0,0.06); }
|
button.ctrl:hover { background: rgba(0,0,0,0.06); }
|
||||||
|
|
||||||
.content { flex: 1; overflow: auto; padding: 12px; background: transparent; }
|
.content { flex: 1; overflow: auto; padding: 12px; background: transparent; }
|
||||||
|
|
||||||
.resizer { position: absolute; z-index: 20; }
|
.resizer { position: absolute; z-index: 20; }
|
||||||
|
|||||||
Reference in New Issue
Block a user