[🔨] Use standard-ts codestyle

This commit is contained in:
ThinLiquid 2023-10-16 15:25:19 +01:00
parent 2271b5130e
commit f935718ece
19 changed files with 3318 additions and 436 deletions

2889
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -26,6 +26,7 @@
"node-polyfill-webpack-plugin": "^2.0.1", "node-polyfill-webpack-plugin": "^2.0.1",
"source-map-loader": "^4.0.1", "source-map-loader": "^4.0.1",
"style-loader": "^3.3.3", "style-loader": "^3.3.3",
"ts-standard": "^12.0.2",
"typescript": "^5.2.2", "typescript": "^5.2.2",
"webpack": "^5.88.2", "webpack": "^5.88.2",
"webpack-cli": "^5.1.4", "webpack-cli": "^5.1.4",

View file

@ -1,39 +1,38 @@
import icon from '../assets/icons/editor.png'; import icon from '../assets/icons/editor.png'
import { App } from "../types.ts"; import { App } from '../types.ts'
import { fullEditor } from "prism-code-editor/setups"; import { fullEditor } from 'prism-code-editor/setups'
import Prism from "prism-code-editor/prism-core"; import Prism from 'prism-code-editor/prism-core'
import "prismjs/components/prism-clike.js"; import 'prismjs/components/prism-clike.js'
import "prismjs/components/prism-markup.js"; import 'prismjs/components/prism-markup.js'
import "prismjs/components/prism-javascript.js"; import 'prismjs/components/prism-javascript.js'
import "prismjs/components/prism-typescript.js"; import 'prismjs/components/prism-typescript.js'
import "prismjs/components/prism-css.js"; import 'prismjs/components/prism-css.js'
import { FlowWindow } from '../wm.ts'
interface EditorConfig { interface EditorConfig {
path: string; path: string
} }
export default class EditorApp implements App { export default class EditorApp implements App {
name = 'Editor'; name = 'Editor'
pkg = 'flow.editor'; pkg = 'flow.editor'
icon = icon; icon = icon
version = '1.0.0'; version = '1.0.0'
constructor() {} async open (data?: EditorConfig): Promise<FlowWindow> {
const { default: fs } = await import('fs')
async open(data?: EditorConfig) { const win = (window as any).wm.createWindow({
const { default: fs } = await import('fs');
const win = window.wm.createWindow({
title: this.name, title: this.name,
icon: icon, icon,
width: 500, width: 500,
height: 400 height: 400
}); })
if (data) { if (data != null) {
win.setTitle('Editor - ' + data.path); win.setTitle('Editor - ' + data.path)
const value = (await fs.promises.readFile(data.path)).toString() const value = (await fs.promises.readFile(data.path)).toString()
const editor = fullEditor( const editor = fullEditor(
@ -41,11 +40,11 @@ export default class EditorApp implements App {
win.content, win.content,
{ {
language: data.path.split('.').at(-1), language: data.path.split('.').at(-1),
theme: "github-dark", theme: 'github-dark',
value value
}, }
); )
const style = document.createElement('style'); const style = document.createElement('style')
style.innerHTML = ` style.innerHTML = `
.prism-editor { .prism-editor {
caret-color: var(--text); caret-color: var(--text);
@ -79,7 +78,7 @@ export default class EditorApp implements App {
` `
editor.scrollContainer.appendChild(style) editor.scrollContainer.appendChild(style)
} }
return win; return win
} }
} }

View file

@ -1,133 +1,131 @@
import icon from '../assets/icons/files.png'; import icon from '../assets/icons/files.png'
import { App } from "../types.ts"; import { App } from '../types.ts'
import flow from '../flow.ts'; import flow from '../flow.ts'
import { FlowWindow } from '../wm.ts'
export default class FilesApp implements App { export default class FilesApp implements App {
name = 'Files'; name = 'Files'
pkg = 'flow.files'; pkg = 'flow.files'
icon = icon; icon = icon
version = '1.0.0'; version = '1.0.0'
constructor() {} async open (): Promise<FlowWindow> {
const { default: fs } = await import('fs')
async open() { const win = (window as any).wm.createWindow({
const { default: fs } = await import('fs');
const win = window.wm.createWindow({
title: this.name, title: this.name,
icon: icon, icon,
width: 500, width: 500,
height: 400 height: 400
}); })
try { try {
await fs.mkdir('/home', () => {}); await fs.mkdir('/home', () => {})
await fs.mkdir('/home/meow', () => {}); await fs.mkdir('/home/meow', () => {})
} catch (e) {} } catch (e) {}
try { try {
await fs.writeFile('/home/owo1.txt', 'sussy', () => {}); await fs.writeFile('/home/owo1.txt', 'sussy', () => {})
await fs.writeFile('/home/owo2.html', '<body></body>', () => {}); await fs.writeFile('/home/owo2.html', '<body></body>', () => {})
await fs.writeFile('/home/owo.js', 'alert(`hi`)', () => {}); await fs.writeFile('/home/owo.js', 'alert(`hi`)', () => {})
} catch (e) {} } catch (e) {}
win.content.style.display = 'flex'
win.content.style.flexDirection = 'column'
win.content.style.display = 'flex'; async function setDir (dir: string): Promise<void> {
win.content.style.flexDirection = 'column';
async function setDir(dir: string) {
await fs.readdir(dir, (e, files) => { await fs.readdir(dir, (e, files) => {
const back = dir === '/' ? `<i class='bx bx-arrow-to-left'></i>` : `<i class='back bx bx-left-arrow-alt'></i>`; const back = dir === '/' ? '<i class=\'bx bx-arrow-to-left\'></i>' : '<i class=\'back bx bx-left-arrow-alt\'></i>'
win.content.innerHTML = ` win.content.innerHTML = `
<div style="padding: 5px;display: flex;align-items: center;">${back}${dir}</div> <div style="padding: 5px;display: flex;align-items: center;">${back}${dir}</div>
<div class="files" style="background: var(--base);flex: 1;border-radius: 10px;display: flex;flex-direction: column;"></div> <div class="files" style="background: var(--base);flex: 1;border-radius: 10px;display: flex;flex-direction: column;"></div>
`; `
if (back !== `<i class='bx bx-arrow-to-left'></i>`) { if (back !== '<i class=\'bx bx-arrow-to-left\'></i>') {
(win.content.querySelector('.back') as HTMLElement).onclick = () => { (win.content.querySelector('.back') as HTMLElement).onclick = async () => {
if (dir.split('/')[1] === dir.replace('/', '')) { if (dir.split('/')[1] === dir.replace('/', '')) {
setDir('/' + dir.split('/')[0]) await setDir('/' + dir.split('/')[0])
} else { } else {
setDir('/' + dir.split('/')[1]) await setDir('/' + dir.split('/')[1])
} }
} }
} }
for (const file in files) { for (const file of files) {
const separator = dir === '/' ? '' : '/'; const separator = dir === '/' ? '' : '/'
fs.stat(dir + separator + files[file], (e, fileStat) => { fs.stat(dir + separator + file, (e, fileStat) => {
const element = document.createElement('div'); const element = document.createElement('div')
element.setAttribute('style', 'padding: 5px;border-bottom: 1px solid var(--text);display:flex;align-items:center;gap: 5px;'); element.setAttribute('style', 'padding: 5px;border-bottom: 1px solid var(--text);display:flex;align-items:center;gap: 5px;')
const genIcon = () => { const genIcon = (): string => {
switch (files[file].split('.').at(-1)) { switch (file.split('.').at(-1)) {
case 'js': case 'js':
case 'mjs': case 'mjs':
case 'cjs': { case 'cjs': {
return `<i class='bx bxs-file-js' ></i>` return '<i class=\'bx bxs-file-js\' ></i>'
} }
case 'html': case 'html':
case 'htm': { case 'htm': {
return `<i class='bx bxs-file-html' ></i>` return '<i class=\'bx bxs-file-html\' ></i>'
} }
case 'css': { case 'css': {
return `<i class='bx bxs-file-css' ></i>` return '<i class=\'bx bxs-file-css\' ></i>'
} }
case 'json': { case 'json': {
return `<i class='bx bxs-file-json' ></i>` return '<i class=\'bx bxs-file-json\' ></i>'
} }
case 'md': { case 'md': {
return `<i class='bx bxs-file-md' ></i>` return '<i class=\'bx bxs-file-md\' ></i>'
} }
case 'txt': case 'txt':
case 'text': { case 'text': {
return `<i class='bx bxs-file-txt' ></i>` return '<i class=\'bx bxs-file-txt\' ></i>'
} }
case 'png': case 'png':
case 'apng': { case 'apng': {
return `<i class='bx bxs-file-png' ></i>` return '<i class=\'bx bxs-file-png\' ></i>'
} }
case 'jpg': case 'jpg':
case 'jpeg': { case 'jpeg': {
return `<i class='bx bxs-file-jpg' ></i>` return '<i class=\'bx bxs-file-jpg\' ></i>'
} }
case 'gif': { case 'gif': {
return `<i class='bx bxs-file-gif' ></i>` return '<i class=\'bx bxs-file-gif\' ></i>'
} }
default: { default: {
return `<i class='bx bxs-file-blank' ></i>` return '<i class=\'bx bxs-file-blank\' ></i>'
} }
} }
} }
const icon = fileStat.isDirectory() ? `<i class='bx bx-folder'></i>` : genIcon() const icon = fileStat.isDirectory() ? '<i class=\'bx bx-folder\'></i>' : genIcon()
element.innerHTML += `${icon} ${files[file]}`; element.innerHTML += `${icon} ${file}`
element.onclick = () => { element.onclick = async () => {
if (fileStat.isDirectory() === true) { if (fileStat.isDirectory()) {
setDir(dir + separator + files[file]); await setDir(dir + separator + file)
} else { } else {
flow.openApp('flow.editor', { path: dir + separator + files[file] }) flow.openApp('flow.editor', { path: dir + separator + file })
} }
}; }
win.content.querySelector('.files').appendChild(element); win.content.querySelector('.files').appendChild(element)
}); })
} }
}); })
} }
setDir('/') await setDir('/')
return win; return win
} }
} }

View file

@ -1,22 +1,21 @@
import icon from '../assets/icons/music.png'; import icon from '../assets/icons/music.png'
import { App } from "../types.ts"; import { App } from '../types.ts'
import { FlowWindow } from '../wm.ts'
export default class MusicApp implements App { export default class MusicApp implements App {
name = 'Music'; name = 'Music'
pkg = 'flow.music'; pkg = 'flow.music'
icon = icon; icon = icon
version = '1.0.0'; version = '1.0.0'
constructor() {} async open (): Promise<FlowWindow> {
const win = (window as any).wm.createWindow({
async open() {
const win = window.wm.createWindow({
title: this.name, title: this.name,
icon: icon icon
}); })
win.content.innerHTML = 'hi'; win.content.innerHTML = 'hi'
return win; return win
} }
} }

View file

@ -1,28 +1,27 @@
import icon from '../assets/icons/settings.png'; import icon from '../assets/icons/settings.png'
import { App } from "../types.ts"; import { App } from '../types.ts'
import { FlowWindow } from '../wm.ts'
export default class SettingsApp implements App { export default class SettingsApp implements App {
name = 'Settings'; name = 'Settings'
pkg = 'flow.settings'; pkg = 'flow.settings'
icon = icon; icon = icon
version = '1.0.0'; version = '1.0.0'
constructor() {} async open (): Promise<FlowWindow> {
const win = (window as any).wm.createWindow({
async open() {
const win = window.wm.createWindow({
title: this.name, title: this.name,
icon: icon, icon,
width: 700, width: 700,
height: 300 height: 300
}); })
win.content.style.padding = '10px'; win.content.style.padding = '10px'
win.content.innerHTML = ` win.content.innerHTML = `
<h1>Settings</h1> <h1>Settings</h1>
<p>owo2</p> <p>owo2</p>
`; `
return win; return win
} }
} }

8
src/files.d.ts vendored
View file

@ -1,4 +1,4 @@
declare module "*.png"; declare module '*.png';
declare module "*.svg"; declare module '*.svg';
declare module "*.jpeg"; declare module '*.jpeg';
declare module "*.jpg"; declare module '*.jpg';

View file

@ -1,29 +1,29 @@
import { App } from './types.ts'; import { App } from './types.ts'
import SettingsApp from './apps/settings.ts'; import SettingsApp from './apps/settings.ts'
import FilesApp from './apps/files.ts'; import FilesApp from './apps/files.ts'
import MusicApp from './apps/music.ts'; import MusicApp from './apps/music.ts'
import EditorApp from './apps/editor.ts'; import EditorApp from './apps/editor.ts'
interface Flow { interface Flow {
apps: { apps: {
[key: string]: App [key: string]: App
}, }
openApp: Function; openApp: Function
} }
const flow: Flow = { const flow: Flow = {
apps: { apps: {
"flow.settings": new SettingsApp(), 'flow.settings': new SettingsApp(),
"flow.music": new MusicApp(), 'flow.music': new MusicApp(),
"flow.files": new FilesApp(), 'flow.files': new FilesApp(),
"flow.editor": new EditorApp(), 'flow.editor': new EditorApp()
}, },
openApp(pkg: string, data: any) { async openApp (pkg: string, data: any) {
const win = this.apps[pkg].open(data); const win = this.apps[pkg].open(data)
const event = new CustomEvent('app_opened', { detail: { app: this.apps[pkg], win } }); const event = new CustomEvent('app_opened', { detail: { app: this.apps[pkg], win: await win } })
window.dispatchEvent(event); window.dispatchEvent(event)
} }
} }
export default flow; export default flow

View file

@ -1,12 +1,7 @@
import './style.less'; import './style.less'
import StatusBar from './statusbar.ts'; import StatusBar from './statusbar.ts'
import WM from './wm.ts'; import WM from './wm.ts'
declare global { (window as any).statusBar = new StatusBar();
var wm: WM; (window as any).wm = new WM()
var statusBar: StatusBar;
}
window.statusBar = new StatusBar();
window.wm = new WM();

View file

@ -4,14 +4,14 @@ export const meta = {
id: 'appview' id: 'appview'
} }
export const run = (element: HTMLDivElement) => { export const run = (element: HTMLDivElement): void => {
element.style.display = 'flex'; element.style.display = 'flex'
element.style.alignItems = 'center'; element.style.alignItems = 'center'
element.style.justifyContent = 'center'; element.style.justifyContent = 'center'
element.style.aspectRatio = '1 / 1'; element.style.aspectRatio = '1 / 1'
element.innerHTML = `<i class='bx bx-rocket'></i>`; element.innerHTML = '<i class=\'bx bx-rocket\'></i>'
element.onclick = () => { element.onclick = () => {
window.wm.toggleLauncher(); (window as any).wm.toggleLauncher()
} }
} }

View file

@ -1,4 +1,4 @@
import { FlowWindow } from '../wm.ts'; import { AppOpenedEvent, AppClosedEvent } from '../types'
export const meta = { export const meta = {
name: 'Apps', name: 'Apps',
@ -6,30 +6,28 @@ export const meta = {
id: 'apps' id: 'apps'
} }
interface Status { export const run = (element: HTMLDivElement): void => {
win: FlowWindow, element.style.display = 'flex'
appElement: HTMLElement element.style.alignItems = 'center'
} element.style.gap = '10px'
element.style.paddingLeft = '15px'
element.style.paddingRight = '15px'
export const run = (element: HTMLDivElement) => { window.addEventListener('app_opened', (e: AppOpenedEvent): void => {
element.style.display = 'flex'; const appIcon = document.createElement('app')
element.style.alignItems = 'center'; const app = e.detail.app
element.style.gap = '10px'; const win = e.detail.win
element.style.paddingLeft = '15px'; appIcon.innerHTML = `<img data-id="${win.id}" src="${app.icon}"/>`
element.style.paddingRight = '15px'; appIcon.onclick = async () => {
const win = await e.detail.win
window.addEventListener('app_opened', async (e: CustomEvent) => { win.focus()
const app = document.createElement('app'); win.toggleMin()
app.innerHTML = `<img data-id="${(await e.detail.win).id}" src="${e.detail.app.icon}"/>`;
app.onclick = async () => {
const win = await e.detail.win;
await win.focus();
await win.toggleMin();
} }
element.appendChild(app); element.appendChild(appIcon)
}) })
window.addEventListener('app_closed', async (e: CustomEvent) => { window.addEventListener('app_closed', (e: AppClosedEvent): void => {
element.querySelector(`img[data-id="${(await e.detail.win).id}"]`).parentElement.remove(); const win = e.detail.win
element.querySelector(`img[data-id="${win.id}"]`)?.parentElement?.remove()
}) })
} }

View file

@ -4,8 +4,8 @@ export const meta = {
id: 'clock' id: 'clock'
} }
export const run = (element: HTMLDivElement) => { export const run = (element: HTMLDivElement): void => {
element.style.display = 'flex'; element.style.display = 'flex'
element.style.alignItems = 'center'; element.style.alignItems = 'center'
element.innerText = '9:41 AM\n10/14/2023'; element.innerText = '9:41 AM\n10/14/2023'
} }

View file

@ -4,11 +4,11 @@ export const meta = {
id: 'switcher' id: 'switcher'
} }
export const run = (element: HTMLDivElement) => { export const run = (element: HTMLDivElement): void => {
element.style.display = 'flex'; element.style.display = 'flex'
element.style.gap = '10px'; element.style.gap = '10px'
element.style.alignItems = 'center'; element.style.alignItems = 'center'
element.style.paddingLeft = '15px'; element.style.paddingLeft = '15px'
element.style.paddingRight = '15px'; element.style.paddingRight = '15px'
element.innerHTML = `<i class='bx bxs-dice-1'></i><i class='bx bx-dice-2'></i><i class='bx bx-dice-3'></i>`; element.innerHTML = '<i class=\'bx bxs-dice-1\'></i><i class=\'bx bx-dice-2\'></i><i class=\'bx bx-dice-3\'></i>'
} }

View file

@ -1,3 +1,3 @@
declare module "prism-code-editor"; declare module 'prism-code-editor';
declare module "prism-code-editor/setups"; declare module 'prism-code-editor/setups';
declare module "prism-code-editor/prism-core"; declare module 'prism-code-editor/prism-core';

View file

@ -1,38 +1,38 @@
import * as clock from './modules/clock.ts'; import * as clock from './modules/clock.ts'
import * as switcher from './modules/switcher.ts'; import * as switcher from './modules/switcher.ts'
import * as appView from './modules/appView.ts'; import * as appView from './modules/appView.ts'
import * as apps from './modules/apps.ts'; import * as apps from './modules/apps.ts'
import { StatusItem } from './types'; import { StatusItem } from './types'
class StatusBar { class StatusBar {
items: StatusItem[] = []; items: StatusItem[] = []
element: HTMLElement; element: HTMLElement
constructor() { constructor () {
this.element = document.createElement('toolbar'); this.element = document.createElement('toolbar')
document.body.appendChild(this.element); document.body.appendChild(this.element)
this.add(appView); this.add(appView)
this.add(apps); this.add(apps)
this.add(clock); this.add(clock)
this.add(switcher); this.add(switcher)
} }
add(item: StatusItem) { add (item: StatusItem): void {
if (this.items.some(x => x.meta.id === item.meta.id)) { if (this.items.some(x => x.meta.id === item.meta.id)) {
console.error(`Unable to register tool; ${item.meta.id} is already registered.`); console.error(`Unable to register tool; ${item.meta.id} is already registered.`)
} else { } else {
const element = document.createElement('div'); const element = document.createElement('div')
element.setAttribute('data-toolbar-id', item.meta.id); element.setAttribute('data-toolbar-id', item.meta.id)
this.items.push(item); this.items.push(item)
this.element.appendChild(element); this.element.appendChild(element)
item.run(element); item.run(element)
} }
} }
} }
export default StatusBar; export default StatusBar

View file

@ -1,21 +1,34 @@
import { FlowWindow } from "./wm"; import { FlowWindow } from './wm'
export interface StatusItem { export interface StatusItem {
meta: { meta: {
name: string; name: string
description: string; description: string
id: string; id: string
}, }
run: Function; run: Function
} }
export interface App { export interface App {
name: string; name: string
pkg: string; pkg: string
version: string; version: string
icon: string; icon: string
open(data: any): Promise<FlowWindow>; open: (data: any) => Promise<FlowWindow>
} }
export interface AppOpenedEvent extends CustomEvent {
detail: {
app: App
win: FlowWindow
}
}
export interface AppClosedEvent extends CustomEvent {
detail: {
win: FlowWindow
}
}

312
src/wm.ts
View file

@ -1,270 +1,262 @@
import flow from "./flow.ts"; import flow from './flow.ts'
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid'
interface FlowWindowConfig { interface FlowWindowConfig {
title: string; title: string
icon: string; icon: string
width?: number; width?: number
height?: number; height?: number
minWidth?: number; minWidth?: number
minHeight?: number; minHeight?: number
} }
let focus = true; function dragElement (element: HTMLElement, container: HTMLElement): void {
let posX = 0; let posY = 0
window.onfocus = () => focus = true; element.querySelector('window-header')?.addEventListener('mousedown', dragMouseDown)
window.onblur = () => focus = false;
function dragElement(element: HTMLElement, container: HTMLElement) { function dragMouseDown (e: MouseEvent): void {
var posX = 0, posY = 0; e.preventDefault()
closeAll()
element.querySelector('window-header').addEventListener('mousedown', dragMouseDown); posX = e.clientX
posY = e.clientY
function dragMouseDown(e: MouseEvent) { document.onmouseup = closeDragElement
e.preventDefault(); document.onmousemove = elementDrag
closeAll();
posX = e.clientX;
posY = e.clientY;
document.onmouseup = closeDragElement;
document.onmousemove = elementDrag;
} }
function elementDrag(e: MouseEvent) { function elementDrag (e: MouseEvent): void {
e.preventDefault(); e.preventDefault()
// Calculate the distance the mouse has moved const dx = e.clientX - posX
const dx = e.clientX - posX; const dy = e.clientY - posY
const dy = e.clientY - posY;
const newTop = element.offsetTop + dy
// Calculate the new position for the element const newLeft = element.offsetLeft + dx
const newTop = element.offsetTop + dy;
const newLeft = element.offsetLeft + dx; const containerWidth = container.offsetWidth
const containerHeight = container.offsetHeight
// Get the container's dimensions
const containerWidth = container.offsetWidth;
const containerHeight = container.offsetHeight;
// Ensure the element stays within the container boundaries
if (newTop >= 0 && newTop + element.offsetHeight <= containerHeight) { if (newTop >= 0 && newTop + element.offsetHeight <= containerHeight) {
element.style.top = newTop + "px"; element.style.top = `${newTop}px`
} }
if (newLeft >= 0 && newLeft + element.offsetWidth <= containerWidth) { if (newLeft >= 0 && newLeft + element.offsetWidth <= containerWidth) {
element.style.left = newLeft + "px"; element.style.left = `${newLeft}px`
} }
// Update the last mouse position posX = e.clientX
posX = e.clientX; posY = e.clientY
posY = e.clientY;
} }
function closeDragElement() { function closeDragElement (): void {
document.onmouseup = null; document.onmouseup = null
document.onmousemove = null; document.onmousemove = null
container.onmouseleave = null; container.onmouseleave = null
} }
function closeAll () { function closeAll (): void {
closeDragElement(); closeDragElement()
container.onmouseenter = null; container.onmouseenter = null
} }
} }
export class FlowWindow { export class FlowWindow {
element: HTMLElement; element: HTMLElement
private header: HTMLElement; private readonly header: HTMLElement
content: HTMLElement; content: HTMLElement
maximized: boolean; maximized: boolean
minimized: boolean; minimized: boolean
width: number; width: number
height: number; height: number
isMinimized = false; isMinimized = false
isMaximized = false; isMaximized = false
wm: WM; wm: WM
id = uuid(); id = uuid()
config: FlowWindowConfig; config: FlowWindowConfig
constructor(wm: WM, config: FlowWindowConfig) { constructor (wm: WM, config: FlowWindowConfig) {
this.wm = wm; this.wm = wm
this.config = config; this.config = config
this.element = document.createElement('window'); this.element = document.createElement('window')
this.element.style.zIndex = (wm.getHighestZIndex() + 1).toString(); this.element.style.zIndex = (wm.getHighestZIndex() + 1).toString()
this.element.style.position = 'absolute'; this.element.style.position = 'absolute'
this.focus(); this.focus()
this.element.onmousedown = () => { this.element.onmousedown = () => {
this.focus() this.focus()
}; }
this.element.style.width = `${config.width || 300}px`; this.element.style.width = `${config.width ?? 300}px`
this.element.style.height = `${config.height || 200}px`; this.element.style.height = `${config.height ?? 200}px`
this.header = document.createElement('window-header'); 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='bx bx-minus'></i><i id="max" class='bx bx-checkbox'></i><i id="close" class='bx bx-x'></i>`; this.header.innerHTML = `<img src="${config.icon}"></img> <div class="title">${config.title}</div><div style="flex:1;"></div><i id="min" class='bx bx-minus'></i><i id="max" class='bx bx-checkbox'></i><i id="close" class='bx bx-x'></i>`;
(this.header.querySelector('#close') as HTMLElement).onclick = () => { (this.header.querySelector('#close') as HTMLElement).onclick = () => {
this.close(); this.close()
} }
(this.header.querySelector('#min') as HTMLElement).onclick = () => this.toggleMin(); (this.header.querySelector('#min') as HTMLElement).onclick = () => this.toggleMin();
(this.header.querySelector('#max') as HTMLElement).onclick = () => this.toggleMax(); (this.header.querySelector('#max') as HTMLElement).onclick = () => this.toggleMax()
this.content = document.createElement('window-content'); this.content = document.createElement('window-content')
this.element.appendChild(this.header);
this.element.appendChild(this.content);
dragElement(this.element, document.querySelector('window-area')); this.element.appendChild(this.header)
this.element.appendChild(this.content)
dragElement(this.element, (document.querySelector('window-area') as HTMLElement))
} }
toggleMin() { toggleMin (): boolean {
if (this.isMinimized) { if (this.isMinimized) {
this.element.style.pointerEvents = null; this.element.style.pointerEvents = 'all'
this.element.style.opacity = '1'; this.element.style.opacity = '1'
} else { } else {
this.element.style.pointerEvents = 'none'; this.element.style.pointerEvents = 'none'
this.element.style.opacity = '0'; this.element.style.opacity = '0'
} }
this.isMinimized = !this.isMinimized; this.isMinimized = !this.isMinimized
return this.isMinimized
} }
private prevTop: string; private prevTop: string
private prevLeft: string; private prevLeft: string
private prevWidth: string; private prevWidth: string
private prevHeight: string; private prevHeight: string
toggleMax() { toggleMax (): boolean {
if (this.isMaximized) { if (this.isMaximized) {
this.element.style.width = this.prevWidth; this.element.style.width = this.prevWidth
this.element.style.height = this.prevHeight; this.element.style.height = this.prevHeight
this.element.style.top = this.prevTop; this.element.style.top = this.prevTop
this.element.style.left = this.prevLeft; this.element.style.left = this.prevLeft
} else { } else {
this.prevTop = this.element.style.top; this.prevTop = this.element.style.top
this.prevLeft = this.element.style.left; this.prevLeft = this.element.style.left
this.prevWidth = this.element.style.width; this.prevWidth = this.element.style.width
this.prevHeight = this.element.style.height; this.prevHeight = this.element.style.height
this.element.style.top = '0'; this.element.style.top = '0'
this.element.style.left = '0'; this.element.style.left = '0'
this.element.style.width = 'calc(100% - 2px)'; this.element.style.width = 'calc(100% - 2px)'
this.element.style.height = 'calc(100% - 3px)'; this.element.style.height = 'calc(100% - 3px)'
} }
this.isMaximized = !this.isMaximized; this.isMaximized = !this.isMaximized
return this.isMaximized
} }
focus() { focus (): void {
if (this.element.style.zIndex !== this.wm.getHighestZIndex().toString()) { if (this.element.style.zIndex !== this.wm.getHighestZIndex().toString()) {
this.element.style.zIndex = (this.wm.getHighestZIndex() + 1).toString(); this.element.style.zIndex = (this.wm.getHighestZIndex() + 1).toString()
} }
} }
close() { close (): void {
this.element.remove(); this.element.remove()
const event = new CustomEvent('app_closed', { detail: { win: this } }); const event = new CustomEvent('app_closed', { detail: { win: this } })
window.dispatchEvent(event); window.dispatchEvent(event)
} }
setTitle(title: string) { setTitle (title: string): void {
(this.header.querySelector('.title') as HTMLElement).innerText = title; (this.header.querySelector('.title') as HTMLElement).innerText = title
} }
} }
class WM { class WM {
launcherOpen = false; launcherOpen = false
area: HTMLElement; area: HTMLElement
launcher: HTMLElement; launcher: HTMLElement
windows: FlowWindow[] = []; windows: FlowWindow[] = []
constructor() { constructor () {
this.area = document.createElement('window-area'); this.area = document.createElement('window-area')
this.launcher = document.createElement('launcher'); this.launcher = document.createElement('launcher')
this.init(); this.init()
} }
getHighestZIndex() { getHighestZIndex (): number {
const indexes = this.windows.map((win: FlowWindow) => { const indexes = this.windows.map((win: FlowWindow) => {
if (win.element.style.zIndex === '') return; return parseInt(win.element.style.zIndex)
return parseInt(win.element.style.zIndex); })
}).filter(x => x !== undefined);
const max = Math.max(...indexes); const max = Math.max(...indexes)
if (max === -Infinity) { if (max === -Infinity) {
return 0; return 0
} else { } else {
return max; return max
} }
} }
createWindow(config: FlowWindowConfig): FlowWindow { createWindow (config: FlowWindowConfig): FlowWindow {
const win = new FlowWindow(this, config); const win = new FlowWindow(this, config)
this.windows.push(win); this.windows.push(win)
this.area.appendChild(win.element); this.area.appendChild(win.element)
return win; return win
} }
toggleLauncher() { toggleLauncher (): boolean {
if (this.launcherOpen === true) { if (this.launcherOpen) {
this.launcher.style.opacity = '0'; this.launcher.style.opacity = '0'
this.launcher.style.backdropFilter = 'blur(0px)'; this.launcher.style.backdropFilter = 'blur(0px)'
this.launcher.style.pointerEvents = 'none'; this.launcher.style.pointerEvents = 'none'
} else { } else {
this.launcher.style.opacity = '1'; this.launcher.style.opacity = '1'
this.launcher.style.backdropFilter = 'blur(20px)'; this.launcher.style.backdropFilter = 'blur(20px)'
this.launcher.style.pointerEvents = null; this.launcher.style.pointerEvents = 'all'
} }
this.launcherOpen = !this.launcherOpen; this.launcherOpen = !this.launcherOpen
return this.launcherOpen
} }
private async init() { private init (): void {
this.launcher.innerHTML = ` this.launcher.innerHTML = `
<input placeholder="Search"/> <input placeholder="Search"/>
<apps></apps> <apps></apps>
`; `
this.launcher.onclick = (e) => { this.launcher.onclick = (e) => {
if(e.target !== e.currentTarget) return; if (e.target !== e.currentTarget) return
this.toggleLauncher(); this.toggleLauncher()
} }
(this.launcher.querySelector('apps') as HTMLElement).onclick = (e) => { (this.launcher.querySelector('apps') as HTMLElement).onclick = (e) => {
if(e.target !== e.currentTarget) return; if (e.target !== e.currentTarget) return
this.toggleLauncher(); this.toggleLauncher()
} }
this.launcher.style.opacity = '0'; this.launcher.style.opacity = '0'
this.launcher.style.filter = 'blur(0px)'; this.launcher.style.filter = 'blur(0px)'
this.launcher.style.pointerEvents = 'none'; this.launcher.style.pointerEvents = 'none'
for (const pkg in flow.apps) { for (const pkg in flow.apps) {
const app = document.createElement('app'); const app = document.createElement('app')
app.onclick = () => { app.onclick = () => {
flow.openApp(pkg); flow.openApp(pkg)
this.toggleLauncher(); this.toggleLauncher()
} }
app.innerHTML = `<img src="${flow.apps[pkg].icon}"><div>${flow.apps[pkg].name}</div>` app.innerHTML = `<img src="${flow.apps[pkg].icon}"><div>${flow.apps[pkg].name}</div>`
this.launcher.querySelector('apps').appendChild(app); this.launcher.querySelector('apps')?.appendChild(app)
} }
document.body.appendChild(this.area); document.body.appendChild(this.area)
document.body.appendChild(this.launcher); document.body.appendChild(this.launcher)
} }
} }
export default WM; export default WM

View file

@ -13,6 +13,7 @@
"esModuleInterop": true, "esModuleInterop": true,
"declaration": true, "declaration": true,
"emitDeclarationOnly": true, "emitDeclarationOnly": true,
"allowImportingTsExtensions": true "allowImportingTsExtensions": true,
"strictNullChecks": true
} }
} }

View file

@ -1,13 +1,13 @@
const HtmlWebpackPlugin = require('html-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin')
const { FilerWebpackPlugin } = require('filer/webpack'); const { FilerWebpackPlugin } = require('filer/webpack')
const NodePolyfillPlugin = require('node-polyfill-webpack-plugin'); const NodePolyfillPlugin = require('node-polyfill-webpack-plugin')
const webpack = require('webpack'); const webpack = require('webpack')
const path = require('path'); const path = require('path')
module.exports = { module.exports = {
entry: { entry: {
flow: './src/index.ts', flow: './src/index.ts'
}, },
devtool: 'inline-source-map', devtool: 'inline-source-map',
mode: 'production', mode: 'production',
@ -16,24 +16,24 @@ module.exports = {
{ {
test: /\.less$/i, test: /\.less$/i,
use: [ use: [
"style-loader", 'style-loader',
"css-loader", 'css-loader',
"less-loader", 'less-loader'
], ]
}, },
{ {
test: /\.css$/i, test: /\.css$/i,
use: [ use: [
"style-loader", 'style-loader',
"css-loader", 'css-loader'
], ]
}, },
{ {
test: /\.(png|jpe?g|gif)$/i, test: /\.(png|jpe?g|gif)$/i,
loader: 'file-loader', loader: 'file-loader',
options: { options: {
outputPath: 'images', outputPath: 'images'
}, }
}, },
{ {
test: /\.ts$/, test: /\.ts$/,
@ -42,33 +42,33 @@ module.exports = {
}, },
{ {
test: /\.js$/, test: /\.js$/,
use: ["source-map-loader"], use: ['source-map-loader'],
enforce: "pre" enforce: 'pre'
}, }
], ]
}, },
resolve: { resolve: {
extensions: ['.tsx', '.ts', '.js'], extensions: ['.tsx', '.ts', '.js']
}, },
output: { output: {
filename: '[name].bundle.js', filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'), path: path.resolve(__dirname, 'dist'),
clean: true, clean: true
}, },
devServer: { devServer: {
static: { static: {
directory: path.join(__dirname, 'dist'), directory: path.join(__dirname, 'dist')
}, },
compress: true, compress: true,
port: 9000, port: 9000
}, },
plugins: [new HtmlWebpackPlugin(), new FilerWebpackPlugin(), plugins: [new HtmlWebpackPlugin(), new FilerWebpackPlugin(),
new NodePolyfillPlugin({ new NodePolyfillPlugin({
excludeAliases: ['console'] excludeAliases: ['console']
}), }),
new webpack.optimize.MinChunkSizePlugin({ new webpack.optimize.MinChunkSizePlugin({
minChunkSize: 50000, minChunkSize: 50000
}), }),
new webpack.optimize.SplitChunksPlugin({ new webpack.optimize.SplitChunksPlugin({
minSize: 45000, minSize: 45000,
@ -87,15 +87,15 @@ module.exports = {
cacheGroups: { cacheGroups: {
commons: { commons: {
test: /[\\/]node_modules[\\/]/, test: /[\\/]node_modules[\\/]/,
name: "vendor", name: 'vendor',
chunks: "initial", chunks: 'initial',
reuseExistingChunk: true, reuseExistingChunk: true
}, },
default: { default: {
minChunks: 2, minChunks: 2,
reuseExistingChunk: true, reuseExistingChunk: true
}, }
}, }
}, }
}, }
}; }