244 lines
6.6 KiB
TypeScript
244 lines
6.6 KiB
TypeScript
import { v4 as uuid } from 'uuid'
|
|
import WindowManager from '../instances/WindowManager'
|
|
import { FlowWindowConfig } from '../types'
|
|
|
|
/**
|
|
* Makes an element draggable.
|
|
*
|
|
* @param element The element to become draggable.
|
|
* @param container The draggable element container.
|
|
*/
|
|
function dragElement (element: HTMLElement, container: HTMLElement): void {
|
|
let posX = 0; let posY = 0
|
|
|
|
element.querySelector('window-header')?.addEventListener('mousedown', dragMouseDown)
|
|
|
|
function dragMouseDown (e: MouseEvent): void {
|
|
e.preventDefault()
|
|
closeAll()
|
|
|
|
posX = e.clientX
|
|
posY = e.clientY
|
|
|
|
document.onmouseup = closeDragElement
|
|
document.onmousemove = elementDrag
|
|
}
|
|
|
|
function elementDrag (e: MouseEvent): void {
|
|
e.preventDefault()
|
|
|
|
const dx = e.clientX - posX
|
|
const dy = e.clientY - posY
|
|
|
|
const newTop = element.offsetTop + dy
|
|
const newLeft = element.offsetLeft + dx
|
|
|
|
const containerWidth = container.offsetWidth
|
|
const containerHeight = container.offsetHeight
|
|
|
|
if (newTop >= 0 && newTop + element.offsetHeight <= containerHeight) {
|
|
element.style.top = `${newTop}px`
|
|
}
|
|
|
|
if (newLeft >= 0 && newLeft + element.offsetWidth <= containerWidth) {
|
|
element.style.left = `${newLeft}px`
|
|
}
|
|
|
|
posX = e.clientX
|
|
posY = e.clientY
|
|
}
|
|
|
|
function closeDragElement (): void {
|
|
document.onmouseup = null
|
|
document.onmousemove = null
|
|
container.onmouseleave = null
|
|
}
|
|
|
|
function closeAll (): void {
|
|
closeDragElement()
|
|
container.onmouseenter = null
|
|
}
|
|
}
|
|
|
|
class FlowWindow {
|
|
element: HTMLElement
|
|
|
|
private readonly header: HTMLElement
|
|
private readonly realContent: HTMLElement
|
|
content: HTMLElement
|
|
|
|
maximized: boolean
|
|
minimized: boolean
|
|
|
|
width: number
|
|
height: number
|
|
|
|
isMinimized = false
|
|
isMaximized = false
|
|
|
|
wm: WindowManager
|
|
|
|
id = uuid()
|
|
|
|
config: FlowWindowConfig
|
|
|
|
/**
|
|
* Creates a window session.
|
|
*
|
|
* @param wm The current window manager session.
|
|
* @param config The window's pre-set config.
|
|
*/
|
|
constructor (wm: WindowManager, config: FlowWindowConfig) {
|
|
this.wm = wm
|
|
this.config = config
|
|
|
|
this.element = document.createElement('window')
|
|
|
|
this.element.style.zIndex = (window.wm.getHighestZIndex() + 1).toString()
|
|
this.element.style.position = 'absolute'
|
|
this.focus()
|
|
|
|
this.element.onmousedown = () => {
|
|
this.focus()
|
|
}
|
|
|
|
if (config.canResize === undefined || config.canResize === null) config.canResize = true
|
|
|
|
this.element.style.width = `${config.width ?? 300}px`
|
|
this.element.style.height = `${config.height ?? 200}px`
|
|
|
|
this.header = document.createElement('window-header')
|
|
this.header.innerHTML = `<img src="${config.icon}"></img> <div class="title">${config.title}</div><div style="flex:1;"></div><i id="min" class='material-symbols-rounded' style="margin-bottom: 5px;">minimize</i><i id="close" class='material-symbols-rounded'>close</i>`
|
|
if (config.canResize) {
|
|
this.header.innerHTML = `<img src="${config.icon}"></img> <div class="title">${config.title}</div><div style="flex:1;"></div><i id="min" class='material-symbols-rounded' style="margin-bottom: 5px;">minimize</i><i id="max" class='material-symbols-rounded' style="font-size: 20px;">square</i><i id="close" class='material-symbols-rounded'>close</i>`
|
|
}
|
|
|
|
(this.header.querySelector('#close') as HTMLElement).onclick = () => {
|
|
this.close()
|
|
}
|
|
|
|
(this.header.querySelector('#min') as HTMLElement).onclick = () => this.toggleMin()
|
|
|
|
if (config.canResize) {
|
|
(this.header.querySelector('#max') as HTMLElement).onclick = () => this.toggleMax()
|
|
}
|
|
|
|
this.realContent = document.createElement('window-content')
|
|
const shadow = this.realContent.attachShadow({ mode: 'open' })
|
|
shadow.innerHTML = `
|
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" />
|
|
<style>
|
|
.material-symbols-rounded {
|
|
font-variation-settings:
|
|
'FILL' 0,
|
|
'wght' 400,
|
|
'GRAD' 200,
|
|
'opsz' 48
|
|
}
|
|
</style>
|
|
<style>
|
|
* {
|
|
-ms-overflow-style: none;
|
|
scrollbar-width: none;
|
|
|
|
font-family: "Satoshi", sans-serif;
|
|
font-weight: 600;
|
|
|
|
color: var(--text);
|
|
}
|
|
</style>
|
|
`
|
|
const shadowBody = document.createElement('body')
|
|
shadowBody.style.margin = '0px'
|
|
shadowBody.style.height = '100%'
|
|
|
|
shadow.appendChild(shadowBody)
|
|
this.content = shadowBody
|
|
|
|
console.log(this.content)
|
|
|
|
this.element.appendChild(this.header)
|
|
this.element.appendChild(this.realContent)
|
|
|
|
dragElement(this.element, (document.querySelector('window-area') as HTMLElement))
|
|
}
|
|
|
|
/**
|
|
* Toggles the window's minimization.
|
|
*
|
|
* @returns The window's current minimization state.
|
|
*/
|
|
toggleMin (): boolean {
|
|
if (this.isMinimized) {
|
|
this.element.style.pointerEvents = 'all'
|
|
this.element.style.opacity = '1'
|
|
} else {
|
|
this.element.style.pointerEvents = 'none'
|
|
this.element.style.opacity = '0'
|
|
}
|
|
|
|
this.isMinimized = !this.isMinimized
|
|
return this.isMinimized
|
|
}
|
|
|
|
private prevTop: string
|
|
private prevLeft: string
|
|
private prevWidth: string
|
|
private prevHeight: string
|
|
|
|
/**
|
|
* Toggles the window's maximization.
|
|
*
|
|
* @returns The window's current maximization state.
|
|
*/
|
|
toggleMax (): boolean {
|
|
if (this.isMaximized) {
|
|
this.element.style.width = this.prevWidth
|
|
this.element.style.height = this.prevHeight
|
|
this.element.style.top = this.prevTop
|
|
this.element.style.left = this.prevLeft
|
|
} else {
|
|
this.prevTop = this.element.style.top
|
|
this.prevLeft = this.element.style.left
|
|
this.prevWidth = this.element.style.width
|
|
this.prevHeight = this.element.style.height
|
|
|
|
this.element.style.top = '0'
|
|
this.element.style.left = '0'
|
|
this.element.style.width = 'calc(100% - 2px)'
|
|
this.element.style.height = 'calc(100% - 3px)'
|
|
}
|
|
|
|
this.isMaximized = !this.isMaximized
|
|
return this.isMaximized
|
|
}
|
|
|
|
/**
|
|
* Focuses the window.
|
|
*/
|
|
focus (): void {
|
|
if (this.element.style.zIndex !== this.wm.getHighestZIndex().toString()) {
|
|
this.element.style.zIndex = (this.wm.getHighestZIndex() + 1).toString()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Closes the window.
|
|
*/
|
|
close (): void {
|
|
this.element.remove()
|
|
const event = new CustomEvent('app_closed', { detail: { win: this } })
|
|
window.dispatchEvent(event)
|
|
}
|
|
|
|
/**
|
|
* Sets the title of the window.
|
|
*
|
|
* @param title - The desired window title.
|
|
*/
|
|
setTitle (title: string): void {
|
|
(this.header.querySelector('.title') as HTMLElement).innerText = title
|
|
}
|
|
}
|
|
|
|
export default FlowWindow
|