@owdproject/core
Version:
Nuxt Desktop module for browser desktop experiences (windows, shell, apps)
548 lines (428 loc) • 12.4 kB
text/typescript
import { useApplicationManager } from '../../composables/useApplicationManager'
import { useDesktopWindowStore } from '../../stores/storeDesktopWindow'
import { deepClone } from '../../utils/utilCommon'
import { markRaw, reactive } from '@vue/reactivity'
import { defineAsyncComponent } from 'vue'
export class WindowController implements IWindowController {
public readonly application: IApplicationController
public readonly instanced: boolean = true
public readonly model: string
public config: WindowConfig = {
title: '',
category: '',
component: undefined,
// position
position: {
x: 0,
y: 0,
z: 0,
},
// sizes
size: {
width: undefined,
height: undefined,
},
// minimize
minimizable: true,
// maximize
maximized: false,
maximizable: false,
// destroy
destroyable: true,
// draggable
draggable: true,
// resizable
resizable: true,
}
public override: WindowOverride = reactive({})
private storedState: WindowStoredState
constructor(
application: IApplicationController,
model: string,
windowConfig: WindowConfig,
windowStoredState: WindowStoredState,
) {
this.application = application
this.model = model
this.storedState = windowStoredState
this.setConfig(windowConfig)
this.restoreState()
}
private setConfig(config: WindowConfig) {
// component
if (config.component) {
this.config.component = markRaw(defineAsyncComponent(config.component))
}
// title
if (config.title) this.config.title = config.title
if (config.icon) this.config.icon = config.icon
// position
if (!this.config.position) this.config.position = { x: 0, y: 0, z: 0 }
if (config.position?.x) this.config.position.x = config.position.x
if (config.position?.y) this.config.position.y = config.position.y
if (config.position?.z) this.config.position.z = config.position.z
// sizes
if (config.size?.width) this.config.size.width = config.size.width
if (config.size?.height) this.config.size.height = config.size.height
if (config.size?.minWidth) this.config.size.minWidth = config.size.minWidth
if (config.size?.minHeight)
this.config.size.minHeight = config.size.minHeight
if (config.size?.maxWidth) this.config.size.maxWidth = config.size.maxWidth
if (config.size?.maxHeight)
this.config.size.maxHeight = config.size.maxHeight
// minimize
if (typeof config.minimizable !== 'undefined') {
this.config.minimizable = config.minimizable
}
if (typeof config.maximizable !== 'undefined') {
this.config.maximizable = config.maximizable
}
// maximize
if (typeof config.maximized !== 'undefined') {
this.config.maximized = config.maximized
}
// draggable
if (typeof config.draggable !== 'undefined') {
this.config.draggable = config.draggable
}
// resizable
if (typeof config.resizable !== 'undefined') {
this.config.resizable = config.resizable
}
// destroy
if (typeof config.destroyable !== 'undefined') {
this.config.destroyable = config.destroyable
}
const DEFAULT_OVERRIDABLE = {
draggable: true,
resizable: false,
position: false,
size: false,
maximized: false,
destroyable: false,
minimizable: false,
maximizable: false,
}
DEFAULT_OVERRIDABLE.position = DEFAULT_OVERRIDABLE.draggable
DEFAULT_OVERRIDABLE.size = DEFAULT_OVERRIDABLE.resizable
DEFAULT_OVERRIDABLE.maximized = DEFAULT_OVERRIDABLE.maximizable
this.config.overridable = {
...DEFAULT_OVERRIDABLE,
...(config.overridable || {}),
}
}
// state
get state() {
return this.storedState.state
}
private restoreState() {
const overridable = this.config.overridable || {}
for (const key in overridable) {
if (overridable[key as keyof typeof overridable]) {
const stateKey = key as keyof WindowState
if (typeof this.state[stateKey] === 'undefined') {
if (typeof this.config[stateKey] === 'object') {
this.state[stateKey] = deepClone(this.config[stateKey])
} else {
this.state[stateKey] = !!this.config[stateKey]
}
}
}
}
}
private setState(state: WindowState) {
this.storedState.state = state
}
// meta
get meta() {
return this.storedState.meta
}
// position
public setPosition(data: { x: number; y: number }) {
if (!this.state.position) {
this.state.position = { x: 0, y: 0 }
}
this.state.position.x = data.x
this.state.position.y = data.y
}
public setActive(value: boolean) {
this.state.active = value
}
public setFocus(value: boolean) {
this.state.focused = value
}
public focus() {
const applicationManager = useApplicationManager()
const desktopWindowStore = useDesktopWindowStore()
if (!this.state.position) {
this.state.position = { x: 0, y: 0, z: 0 }
}
for (const window of applicationManager.windowsOpened.value.values()) {
if (window !== this) {
window.setFocus(false)
}
}
this.setFocus(true)
this.state.position.z = desktopWindowStore.incrementPositionZ()
}
// common
get title(): string {
if (typeof this.override.title !== 'undefined') {
return this.override.title
}
if (this.config.title) {
return this.config.title
}
return this.application.config.title
}
get windowTitle(): string {
if (typeof this.override.windowTitle !== 'undefined') {
return this.override.windowTitle
}
return this.title
}
get icon() {
if (typeof this.override.icon !== 'undefined') {
return this.override.icon
}
if (this.config.icon) {
return this.config.icon
}
return this.application.config.icon
}
// position
get position() {
if (typeof this.state.position === 'undefined') {
return this.config.position
}
return this.state.position
}
// sizes
get size() {
const stateSize = this.state.size || {}
const configSize = this.config.size || {}
return {
width:
typeof stateSize.width !== 'undefined'
? stateSize.width
: configSize.width,
height:
typeof stateSize.height !== 'undefined'
? stateSize.height
: configSize.height,
minWidth:
typeof stateSize.minWidth !== 'undefined'
? stateSize.minWidth
: configSize.minWidth,
maxWidth:
typeof stateSize.maxWidth !== 'undefined'
? stateSize.maxWidth
: configSize.maxWidth,
minHeight:
typeof stateSize.minHeight !== 'undefined'
? stateSize.minHeight
: configSize.minHeight,
maxHeight:
typeof stateSize.maxHeight !== 'undefined'
? stateSize.maxHeight
: configSize.maxHeight,
}
}
public setSize(data: { width: number; height: number }) {
if (!this.state.size) {
this.state.size = { width: undefined, height: undefined }
}
this.state.size.width = data.width
this.state.size.height = data.height
}
// minimize
get isMinimizable() {
if (this.config.overridable?.minimizable) {
return !!this.state.minimizable
}
return !!this.config.minimizable
}
public minimize() {
if (!this.isMinimizable) {
return false
}
this.state.active = false
return true
}
public unminimize() {
this.state.active = true
return true
}
public toggleMinimize() {
this.state.active = !this.state.active
this.focus()
}
// maximize
get isMaximizable() {
if (typeof this.state.maximizable === 'undefined') {
return !!this.config.maximizable
}
return this.state.maximizable
}
get isMaximized() {
if (this.state.layout === 'maximize') {
return true
}
if (this.config.overridable?.maximized) {
return !!this.state.maximized
}
return !!this.config.maximized
}
private parseSizeValue(value: WindowSizeValue | undefined, fallback: number): number {
if (typeof value === 'number') {
return value
}
if (typeof value === 'string') {
const parsed = Number.parseFloat(value)
if (!Number.isNaN(parsed)) {
return parsed
}
}
return fallback
}
public saveBoundsBeforeLayout() {
if (this.state.layout && this.state.layout !== 'normal') {
return
}
const pos = this.position
const size = this.size
this.state.boundsBeforeLayout = {
x: pos?.x ?? 0,
y: pos?.y ?? 0,
width: this.parseSizeValue(size.width, 400),
height: this.parseSizeValue(size.height, 300),
}
}
public setLayout(layout: WindowLayout) {
if (layout === 'normal') {
return this.clearLayout()
}
if (!this.isMaximizable) {
return false
}
this.saveBoundsBeforeLayout()
this.state.layout = layout
this.state.maximized = layout === 'maximize'
return true
}
public clearLayout() {
const bounds = this.state.boundsBeforeLayout
if (bounds) {
this.setPosition({ x: bounds.x, y: bounds.y })
this.setSize({ width: bounds.width, height: bounds.height })
delete this.state.boundsBeforeLayout
}
this.state.layout = 'normal'
this.state.maximized = false
return true
}
public toggleMaximize() {
if (!this.isMaximizable) {
return false
}
if (this.isMaximized) {
return this.clearLayout()
}
return this.setLayout('maximize')
}
public maximize() {
if (!this.isMaximizable) {
return false
}
return this.setLayout('maximize')
}
public unmaximize() {
if (!this.isMaximizable) {
return false
}
return this.clearLayout()
}
// destroy
get isDestroyable() {
if (this.config.overridable?.destroyable) {
return !!this.state.destroyable
}
return !!this.config.destroyable
}
public destroy() {
if (!this.isDestroyable) {
return false
}
this.application.closeWindow(this.state.id)
return true
}
// draggable
get isDraggable() {
if (this.config.overridable?.draggable) {
return !!this.state.draggable
}
return !!this.config.draggable
}
// resizable
get isResizable() {
if (this.config.overridable?.resizable) {
return !!this.state.resizable
}
return !!this.config.resizable
}
// workspace
public setWorkspace(workspaceId: string) {
this.state.workspace = workspaceId
}
// override
public setTitleOverride(value: undefined | string) {
this.override.title = value
}
public resetTitleOverride() {
this.override.title = undefined
}
public setWindowTitleOverride(value: undefined | string) {
this.override.windowTitle = value
}
public resetWindowTitleOverride() {
this.override.windowTitle = undefined
}
// menu
public menu: any[] = []
public setMenu(menu: any[]) {
this.menu = menu
}
// deprecated ?
get actions() {
return {
// position
setActive: this.setActive.bind(this),
setFocus: this.setFocus.bind(this),
bringToFront: this.focus.bind(this),
focus: this.focus.bind(this),
setPosition: this.setPosition.bind(this),
// size
setSize: this.setSize.bind(this),
// minimize
minimize: this.minimize.bind(this),
toggleMinimize: this.toggleMinimize.bind(this),
// maximize
toggleMaximize: this.toggleMaximize.bind(this),
maximize: this.maximize.bind(this),
unmaximize: this.unmaximize.bind(this),
// layout
setLayout: this.setLayout.bind(this),
clearLayout: this.clearLayout.bind(this),
saveBoundsBeforeLayout: this.saveBoundsBeforeLayout.bind(this),
// destroy
destroy: this.destroy.bind(this),
// workspace
setWorkspace: this.setWorkspace.bind(this),
// override
setTitleOverride: this.setTitleOverride.bind(this),
resetTitleOverride: this.resetTitleOverride.bind(this),
setWindowTitleOverride: this.setWindowTitleOverride.bind(this),
resetWindowTitleOverride: this.resetWindowTitleOverride.bind(this),
}
}
}