[🎨] Major codebase overhaul

This commit is contained in:
ThinLiquid 2023-11-23 23:16:21 +00:00 committed by GitHub
parent c994212bbc
commit faa5c87b3b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 257 additions and 151 deletions

4
package-lock.json generated
View file

@ -1,11 +1,11 @@
{
"name": "flow-os",
"name": "flowos",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "flow-os",
"name": "flowos",
"version": "1.0.0",
"license": "MIT",
"dependencies": {

View file

@ -1,7 +1,7 @@
{
"name": "flow-os",
"name": "flowos",
"version": "1.0.0",
"description": "",
"description": "The future of FlowOS.",
"main": "src/index.ts",
"scripts": {
"test": "ts-standard",

View file

@ -1,7 +1,7 @@
import icon from '../assets/icons/null.png'
import { App } from '../types.ts'
import icon from '../../assets/icons/null.png'
import { App } from '../../types'
import { FlowWindow } from '../wm.ts'
import FlowWindow from '../../structures/FlowWindow'
export default class BrowserApp implements App {
meta = {

View file

@ -1,5 +1,5 @@
import icon from '../assets/icons/editor.png'
import { App } from '../types.ts'
import icon from '../../assets/icons/editor.png'
import { App } from '../../types'
import { fullEditor } from 'prism-code-editor/setups'
// this will also import markup, clike, javascript, typescript and jsx
@ -8,7 +8,7 @@ import 'prism-code-editor/grammars/css-extras'
import 'prism-code-editor/grammars/markdown'
import 'prism-code-editor/grammars/python'
import { FlowWindow } from '../wm.ts'
import FlowWindow from '../../structures/FlowWindow'
interface EditorConfig {
path: string

View file

@ -1,7 +1,7 @@
import icon from '../assets/icons/files.png'
import { App } from '../types.ts'
import icon from '../../assets/icons/files.png'
import { App } from '../../types'
import { FlowWindow } from '../wm.ts'
import FlowWindow from '../../structures/FlowWindow'
import { Stats } from 'fs'

View file

@ -1,8 +1,8 @@
import icon from '../assets/icons/info.png'
import badge from '../assets/badge.png'
import { App, PackageJSON } from '../types.ts'
import icon from '../../assets/icons/info.png'
import badge from '../../assets/badge.png'
import { App, PackageJSON } from '../../types'
import { FlowWindow } from '../wm.ts'
import FlowWindow from '../../structures/FlowWindow'
export default class InfoApp implements App {
meta = {
@ -14,7 +14,7 @@ export default class InfoApp implements App {
}
async open (): Promise<FlowWindow> {
const packageJSON: PackageJSON = await import('../../package.json')
const packageJSON: PackageJSON = await import('../../../package.json')
const win = window.wm.createWindow({
title: this.meta.name,
icon: this.meta.icon,

View file

@ -1,7 +1,7 @@
import icon from '../assets/icons/manager.png'
import { App, LoadedApp, LoadedPlugin } from '../types.ts'
import { FlowWindow } from '../wm.ts'
import nullIcon from '../assets/icons/null.png'
import icon from '../../assets/icons/manager.png'
import { App, LoadedApp, LoadedPlugin } from '../../types'
import FlowWindow from '../../structures/FlowWindow'
import nullIcon from '../../assets/icons/null.png'
export default class ManagerApp implements App {
meta = {

View file

@ -1,6 +1,6 @@
import icon from '../assets/icons/music.png'
import { App } from '../types.ts'
import { FlowWindow } from '../wm.ts'
import icon from '../../assets/icons/music.png'
import { App } from '../../types'
import FlowWindow from '../../structures/FlowWindow'
export default class MusicApp implements App {
meta = {

View file

@ -1,6 +1,6 @@
import icon from '../assets/icons/settings.png'
import { App } from '../types.ts'
import { FlowWindow } from '../wm.ts'
import icon from '../../assets/icons/settings.png'
import { App } from '../../types'
import FlowWindow from '../../structures/FlowWindow'
export default class SettingsApp implements App {
meta = {

View file

@ -1,4 +1,4 @@
import { AppOpenedEvent, AppClosedEvent } from '../types'
import { AppOpenedEvent, AppClosedEvent } from '../../types'
export const meta = {
name: 'Apps',

View file

@ -1,9 +1,9 @@
import './style.less'
import './assets/style.less'
import Preloader from './preloader'
import StatusBar from './statusbar'
import WM from './wm'
import Flow from './flow'
import Preloader from './instances/Preloader'
import StatusBar from './instances/StatusBar'
import WindowManager from './instances/WindowManager'
import Flow from './instances/Flow'
import * as fs from 'fs'
@ -13,7 +13,7 @@ declare global {
flow: Flow
fs: typeof fs
statusBar: StatusBar
wm: WM
wm: WindowManager
}
}
@ -32,7 +32,7 @@ if (params.get('debug') !== null && params.get('debug') !== undefined) {
window.preloader = new Preloader()
window.flow = new Flow()
window.statusBar = new StatusBar()
window.wm = new WM();
window.wm = new WindowManager();
(async function () {
window.preloader.setPending('filesystem')

View file

@ -1,4 +1,4 @@
import { LoadedApp, LoadedPlugin } from './types.ts'
import { LoadedApp, LoadedPlugin } from '../types'
class Flow {
apps: LoadedApp[] = []
@ -22,13 +22,16 @@ class Flow {
'battery'
]
/**
* Initiates applications.
*/
private async initApps (): Promise<void> {
window.preloader.setPending('apps')
window.preloader.setStatus('importing default apps...')
for (const appPath of this.appList) {
window.preloader.setStatus(`importing default apps\n${appPath}`)
const { default: ImportedApp } = await import(`./apps/${appPath}.ts`).catch((e: Error) => {
const { default: ImportedApp } = await import(`../builtin/apps/${appPath}.ts`).catch((e: Error) => {
console.error(e)
window.preloader.setStatus(`unable to import ${appPath}\n${e.name}: ${e.message}`)
})
@ -61,13 +64,16 @@ class Flow {
await window.preloader.setDone('apps')
}
/**
* Initiates plugins.
*/
private async initPlugins (): Promise<void> {
window.preloader.setPending('plugins')
window.preloader.setStatus('importing default plugins...')
for (const pluginPath of this.pluginList) {
window.preloader.setStatus(`importing default plugins\n${pluginPath}`)
const plugin = await import(`./plugins/${pluginPath}.ts`).catch((e: Error) => {
const plugin = await import(`../builtin/plugins/${pluginPath}.ts`).catch((e: Error) => {
console.error(e)
window.preloader.setStatus(`unable to import ${pluginPath}\n${e.name}: ${e.message}`)
})
@ -79,11 +85,19 @@ class Flow {
}
}
/**
* Initiates the Flow session.
*/
async init (): Promise<void> {
await this.initApps()
await this.initPlugins()
}
/**
* Registers an app.
*
* @param app The app to be registered.
*/
addApp (app: LoadedApp): void {
if (this.apps.some(x => x.meta.pkg === app.meta.pkg)) {
console.error(`Unable to register app; ${app.meta.pkg} is already registered.`)
@ -93,6 +107,11 @@ class Flow {
this.apps.push(app)
}
/**
* Registers a plugin.
*
* @param plugin The plugin to be registered.
*/
addPlugin (plugin: LoadedPlugin): void {
if (window.flow.plugins.some(x => x.meta.pkg === plugin.meta.pkg)) {
console.error(`Unable to register tool; ${plugin.meta.pkg} is already registered.`)
@ -101,6 +120,12 @@ class Flow {
this.plugins.push(plugin)
}
/**
* Opens a registered application.
*
* @param pkg The PKG ID of the application.
* @param data Payload info for app to recieve.
*/
async openApp (pkg: string, data?: any): Promise<void> {
const app = this.apps.find(x => x.meta.pkg === pkg)
const win = app?.open(data)

View file

@ -1,8 +1,11 @@
import flowIcon from './assets/flow.png'
import flowIcon from '../assets/flow.png'
class Preloader {
element: HTMLElement
/**
* Creates the preloader.
*/
constructor () {
this.element = document.createElement('preloader')
@ -15,14 +18,29 @@ class Preloader {
document.body.appendChild(this.element)
}
/**
* Sets the status text of the preloader.
*
* @param value The preloader status text.
*/
setStatus (value: string): void {
(this.element.querySelector('.status') as HTMLElement).innerText = value
}
/**
* Sets loading state of an instance to pending.
*
* @param value The name of an instance.
*/
setPending (value: string): void {
(this.element.querySelector('.done') as HTMLElement).innerHTML += `<div class="${value.split(' ').join('-')}"><i class='icon bx bx-minus' ></i>${value}</div>`
}
/**
* Sets loading state of an instance to done.
*
* @param value The name of an instance.
*/
async setDone (value: string): Promise<void> {
const icon = this.element.querySelector('.done')?.querySelector(`.${value.split(' ').join('-')}`)?.querySelector('.icon')
icon?.classList.remove('bx-minus')
@ -30,6 +48,9 @@ class Preloader {
await new Promise(resolve => setTimeout(resolve, 300))
}
/**
* Removes the preloader.
*/
finish (): void {
this.element.style.opacity = '0'
this.element.style.pointerEvents = 'none'

View file

@ -1,15 +1,23 @@
import { Plugin } from './types'
import { Plugin } from '../types'
class StatusBar {
element: HTMLElement
/**
* Creates the status bar.
*/
constructor () {
this.element = document.createElement('toolbar')
document.body.appendChild(this.element)
}
/**
* Adds a plugin to the status bar.
*
* @param item The plugin to be added to the status bar.
*/
async add (item: Plugin): Promise<void> {
const element = document.createElement('div')
element.setAttribute('data-toolbar-id', item.meta.pkg)
@ -19,6 +27,9 @@ class StatusBar {
await item.run(element)
}
/**
* Initiates the status bar.
*/
async init (): Promise<void> {
window.preloader.setStatus('adding plugins to statusbar...')

View file

@ -0,0 +1,117 @@
import FlowWindow from '../structures/FlowWindow'
import { FlowWindowConfig } from '../types'
class WindowManager {
private isLauncherOpen = false
windowArea: HTMLElement
launcher: HTMLElement
windows: FlowWindow[] = []
/**
* Creates the window container.
*/
constructor () {
this.windowArea = document.createElement('window-area')
}
/**
* Gets the highest window's z-index.
*
* @returns The heighest window's z-index.
*/
getHighestZIndex (): number {
const indexes = this.windows.map((win: FlowWindow) => {
return parseInt(win.element.style.zIndex)
})
const max = Math.max(...indexes)
return max === -Infinity ? 0 : max
}
/**
* Creates a window.
*
* @param config The config for the window to follow.
* @returns The created window.
*/
createWindow (config: FlowWindowConfig): FlowWindow {
const win = new FlowWindow(this, config)
this.windows.push(win)
this.windowArea.appendChild(win.element)
return win
}
/**
* Toggles the app launcher.
*/
toggleLauncher (): boolean {
if (this.isLauncherOpen) {
this.launcher.style.opacity = '0'
this.launcher.style.backdropFilter = 'blur(0px)'
this.launcher.style.pointerEvents = 'none'
} else {
this.launcher.style.opacity = '1'
this.launcher.style.backdropFilter = 'blur(20px)'
this.launcher.style.pointerEvents = 'all'
}
this.isLauncherOpen = !this.isLauncherOpen
return this.isLauncherOpen
}
/**
* Initiates the window manager.
*/
async init (): Promise<void> {
window.preloader.setPending('window manager')
window.preloader.setStatus('creating app launcher...')
this.launcher = document.createElement('launcher')
this.launcher.innerHTML = `
<input placeholder="Search"/>
<apps></apps>
`;
(this.launcher.querySelector('input') as HTMLInputElement).onkeyup = () => {
(this.launcher.querySelector('apps') as HTMLElement).innerHTML = ''
if ((this.launcher.querySelector('input') as HTMLInputElement).value !== '') {
window.flow.apps.filter(x => x.meta.name.toLowerCase().includes((this.launcher.querySelector('input') as HTMLInputElement).value.toLowerCase())).forEach((app) => {
const appElement = document.createElement('app')
appElement.onclick = async () => {
await window.flow.openApp(app.meta.pkg)
this.toggleLauncher()
}
appElement.innerHTML = `<img src="${app.meta.icon}"><div>${app.meta.name}</div>`
this.launcher.querySelector('apps')?.appendChild(appElement)
})
} else {
window.flow.apps.forEach((app) => {
window.preloader.setStatus(`adding apps to app launcher\n${app.meta.name}`)
const appElement = document.createElement('app')
appElement.onclick = async () => {
await window.flow.openApp(app.meta.pkg)
window.wm.toggleLauncher()
}
appElement.innerHTML = `<img src="${app.meta.icon}"><div>${app.meta.name}</div>`
window.wm.launcher.querySelector('apps')?.appendChild(appElement)
})
}
}
this.launcher.onclick = (e) => {
if (e.target !== e.currentTarget) return
this.toggleLauncher()
}
(this.launcher.querySelector('apps') as HTMLElement).onclick = (e) => {
if (e.target !== e.currentTarget) return
this.toggleLauncher()
}
await window.preloader.setDone('window manager')
}
}
export default WindowManager

View file

@ -1,6 +1,13 @@
import { v4 as uuid } from 'uuid'
import { FlowWindowConfig } from './types.ts'
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
@ -53,7 +60,7 @@ function dragElement (element: HTMLElement, container: HTMLElement): void {
}
}
export class FlowWindow {
class FlowWindow {
element: HTMLElement
private readonly header: HTMLElement
@ -69,19 +76,25 @@ export class FlowWindow {
isMinimized = false
isMaximized = false
wm: WM
wm: WindowManager
id = uuid()
config: FlowWindowConfig
constructor (wm: WM, 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 = (wm.getHighestZIndex() + 1).toString()
this.element.style.zIndex = (window.wm.getHighestZIndex() + 1).toString()
this.element.style.position = 'absolute'
this.focus()
@ -141,6 +154,11 @@ export class FlowWindow {
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'
@ -158,6 +176,12 @@ export class FlowWindow {
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
@ -180,113 +204,32 @@ export class FlowWindow {
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
}
}
class WM {
private isLauncherOpen = false
windowArea: HTMLElement
launcher: HTMLElement
windows: FlowWindow[] = []
constructor () {
this.windowArea = document.createElement('window-area')
}
getHighestZIndex (): number {
const indexes = this.windows.map((win: FlowWindow) => {
return parseInt(win.element.style.zIndex)
})
const max = Math.max(...indexes)
return max === -Infinity ? 0 : max
}
createWindow (config: FlowWindowConfig): FlowWindow {
const win = new FlowWindow(this, config)
this.windows.push(win)
this.windowArea.appendChild(win.element)
return win
}
toggleLauncher (): boolean {
if (this.isLauncherOpen) {
this.launcher.style.opacity = '0'
this.launcher.style.backdropFilter = 'blur(0px)'
this.launcher.style.pointerEvents = 'none'
} else {
this.launcher.style.opacity = '1'
this.launcher.style.backdropFilter = 'blur(20px)'
this.launcher.style.pointerEvents = 'all'
}
this.isLauncherOpen = !this.isLauncherOpen
return this.isLauncherOpen
}
async init (): Promise<void> {
window.preloader.setPending('window manager')
window.preloader.setStatus('creating app launcher...')
this.launcher = document.createElement('launcher')
this.launcher.innerHTML = `
<input placeholder="Search"/>
<apps></apps>
`;
(this.launcher.querySelector('input') as HTMLInputElement).onkeyup = () => {
(this.launcher.querySelector('apps') as HTMLElement).innerHTML = ''
if ((this.launcher.querySelector('input') as HTMLInputElement).value !== '') {
window.flow.apps.filter(x => x.meta.name.toLowerCase().includes((this.launcher.querySelector('input') as HTMLInputElement).value.toLowerCase())).forEach((app) => {
const appElement = document.createElement('app')
appElement.onclick = async () => {
await window.flow.openApp(app.meta.pkg)
this.toggleLauncher()
}
appElement.innerHTML = `<img src="${app.meta.icon}"><div>${app.meta.name}</div>`
this.launcher.querySelector('apps')?.appendChild(appElement)
})
} else {
window.flow.apps.forEach((app) => {
window.preloader.setStatus(`adding apps to app launcher\n${app.meta.name}`)
const appElement = document.createElement('app')
appElement.onclick = async () => {
await window.flow.openApp(app.meta.pkg)
window.wm.toggleLauncher()
}
appElement.innerHTML = `<img src="${app.meta.icon}"><div>${app.meta.name}</div>`
window.wm.launcher.querySelector('apps')?.appendChild(appElement)
})
}
}
this.launcher.onclick = (e) => {
if (e.target !== e.currentTarget) return
this.toggleLauncher()
}
(this.launcher.querySelector('apps') as HTMLElement).onclick = (e) => {
if (e.target !== e.currentTarget) return
this.toggleLauncher()
}
await window.preloader.setDone('window manager')
}
}
export default WM
export default FlowWindow

View file

@ -1,10 +1,8 @@
import { FlowWindow } from './wm'
import FlowWindow from './structures/FlowWindow'
export type AppOpenFunction = (data: any) => Promise<FlowWindow>
export type PluginRunFunction = (element: HTMLDivElement) => void | Promise<void>
/* EVENTS */
export interface AppClosedEvent extends CustomEvent {
detail: {
win: FlowWindow
@ -17,9 +15,6 @@ export interface AppOpenedEvent extends CustomEvent {
win: FlowWindow
}
}
/* METADATA */
export interface BaseMeta {
name: string
description: string
@ -35,8 +30,6 @@ export interface PluginMeta extends BaseMeta {
icon?: string
}
/* OBJECTS */
export interface Apps {
[key: string]: App
}
@ -45,8 +38,6 @@ export interface Plugins {
[key: string]: Plugin
}
/* MAIN INTERFACES */
export interface App {
meta: AppMeta
open: AppOpenFunction
@ -57,8 +48,6 @@ export interface Plugin {
run: PluginRunFunction
}
/* MISC */
export interface FlowWindowConfig {
title: string
icon: string