diff --git a/package-lock.json b/package-lock.json index 6f80f0c..c70befc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "ts-standard": "^12.0.2", "typescript": "^5.2.2", "vite": "^4.4.11", + "vite-plugin-dynamic-import": "^1.5.0", "vite-plugin-node-polyfills": "^0.15.0" } }, @@ -1934,6 +1935,12 @@ "safe-array-concat": "^1.0.1" } }, + "node_modules/es-module-lexer": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.1.tgz", + "integrity": "sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q==", + "dev": true + }, "node_modules/es-set-tostringtag": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", @@ -5937,6 +5944,18 @@ } } }, + "node_modules/vite-plugin-dynamic-import": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/vite-plugin-dynamic-import/-/vite-plugin-dynamic-import-1.5.0.tgz", + "integrity": "sha512-Qp85c+AVJmLa8MLni74U4BDiWpUeFNx7NJqbGZyR2XJOU7mgW0cb7nwlAMucFyM4arEd92Nfxp4j44xPi6Fu7g==", + "dev": true, + "dependencies": { + "acorn": "^8.8.2", + "es-module-lexer": "^1.2.1", + "fast-glob": "^3.2.12", + "magic-string": "^0.30.1" + } + }, "node_modules/vite-plugin-node-polyfills": { "version": "0.15.0", "resolved": "https://registry.npmjs.org/vite-plugin-node-polyfills/-/vite-plugin-node-polyfills-0.15.0.tgz", diff --git a/package.json b/package.json index d095350..afab852 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "test": "ts-standard", "dev": "vite", "build": "vite build", - "preview": "vite preview" + "preview": "vite preview", + "serve": "vite dev" }, "keywords": [], "author": "", @@ -18,6 +19,7 @@ "ts-standard": "^12.0.2", "typescript": "^5.2.2", "vite": "^4.4.11", + "vite-plugin-dynamic-import": "^1.5.0", "vite-plugin-node-polyfills": "^0.15.0" }, "dependencies": { diff --git a/src/apps/editor.ts b/src/apps/editor.ts index f3643f4..eacf3bc 100644 --- a/src/apps/editor.ts +++ b/src/apps/editor.ts @@ -30,7 +30,8 @@ export default class EditorApp implements App { title: this.name, icon, width: 500, - height: 400 + height: 400, + canResize: true }) if (data != null) { diff --git a/src/apps/files.ts b/src/apps/files.ts index 91d5b4e..5c2d5d5 100644 --- a/src/apps/files.ts +++ b/src/apps/files.ts @@ -17,7 +17,8 @@ export default class FilesApp implements App { title: this.name, icon, width: 500, - height: 400 + height: 400, + canResize: true }) win.content.style.display = 'flex' diff --git a/src/apps/info.ts b/src/apps/info.ts new file mode 100644 index 0000000..2e69b75 --- /dev/null +++ b/src/apps/info.ts @@ -0,0 +1,42 @@ +import icon from '../assets/icons/info.png' +import { App, PackageJSON } from '../types.ts' +import { FlowWindow } from '../wm.ts' + +export default class SettingsApp implements App { + name = 'Info' + pkg = 'flow.info' + icon = icon + version = '1.0.0' + canResize = true + + async open (): Promise { + const packageJSON: PackageJSON = await import('../../package.json') + const win = window.wm.createWindow({ + title: this.name, + icon, + width: 300, + height: 400, + canResize: false + }) + + win.content.style.padding = '10px' + win.content.style.textAlign = 'center' + win.content.style.display = 'flex' + win.content.style.flexDirection = 'column' + win.content.style.justifyContent = 'center' + win.content.style.alignItems = 'center' + win.content.innerHTML = ` +
+

FlowOS

+

v${packageJSON.version}

+
+

Created by ThinLiquid, 1nspird_, proudparot2, systemless_

+ Discord + - + Github +
+ ` + + return win + } +} diff --git a/src/apps/music.ts b/src/apps/music.ts index a93abee..f31ad2b 100644 --- a/src/apps/music.ts +++ b/src/apps/music.ts @@ -11,7 +11,10 @@ export default class MusicApp implements App { async open (): Promise { const win = window.wm.createWindow({ title: this.name, - icon + icon, + width: 700, + height: 300, + canResize: true }) win.content.innerHTML = 'hi' diff --git a/src/apps/settings.ts b/src/apps/settings.ts index 04567db..3354c83 100644 --- a/src/apps/settings.ts +++ b/src/apps/settings.ts @@ -13,7 +13,8 @@ export default class SettingsApp implements App { title: this.name, icon, width: 700, - height: 300 + height: 300, + canResize: true }) win.content.style.padding = '10px' diff --git a/src/assets/icons/info.png b/src/assets/icons/info.png new file mode 100644 index 0000000..ddfc507 Binary files /dev/null and b/src/assets/icons/info.png differ diff --git a/src/files.d.ts b/src/files.d.ts index 31dca6b..5d25086 100644 --- a/src/files.d.ts +++ b/src/files.d.ts @@ -1 +1,2 @@ declare module '*.png' +declare module '*.json' diff --git a/src/flow.ts b/src/flow.ts index 5be8f64..bf53e0c 100644 --- a/src/flow.ts +++ b/src/flow.ts @@ -4,13 +4,15 @@ import SettingsApp from './apps/settings.ts' import FilesApp from './apps/files.ts' import MusicApp from './apps/music.ts' import EditorApp from './apps/editor.ts' +import InfoApp from './apps/info.ts' const flow: Flow = { apps: { 'flow.settings': new SettingsApp(), 'flow.music': new MusicApp(), 'flow.files': new FilesApp(), - 'flow.editor': new EditorApp() + 'flow.editor': new EditorApp(), + 'flow.info': new InfoApp() }, async openApp (pkg: string, data: any) { const win = this.apps[pkg].open(data) diff --git a/src/index.ts b/src/index.ts index 24f0676..312bb1c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,6 @@ import './style.less' +import Preloader from './preloader' import StatusBar from './statusbar' import WM from './wm' @@ -7,16 +8,13 @@ import * as fs from 'fs' declare global { interface Window { + preloader: Preloader statusBar: StatusBar wm: WM fs: typeof fs } } -window.statusBar = new StatusBar() -window.wm = new WM() -window.fs = new (window as any).Filer.FileSystem() - const params = new URLSearchParams(window.location.search) async function enableDebug (): Promise { @@ -28,3 +26,19 @@ async function enableDebug (): Promise { if (params.get('debug') !== null && params.get('debug') !== undefined) { enableDebug().catch(e => console.error(e)) } + +window.preloader = new Preloader() +window.statusBar = new StatusBar() +window.wm = new WM(); + +(async function () { + window.preloader.setPending('filesystem') + window.fs = new (window as any).Filer.FileSystem() + await window.preloader.setDone('filesystem') + + await window.statusBar.init() + await window.wm.init() + + window.preloader.setStatus('') + window.preloader.finish() +})().catch(e => console.error) diff --git a/src/preloader.ts b/src/preloader.ts new file mode 100644 index 0000000..200a246 --- /dev/null +++ b/src/preloader.ts @@ -0,0 +1,39 @@ +import flowIcon from './assets/flow.png' + +class Preloader { + element: HTMLElement + + constructor () { + this.element = document.createElement('preloader') + + this.element.innerHTML = ` + +
+
+ ` + + document.body.appendChild(this.element) + } + + setStatus (value: string): void { + (this.element.querySelector('.status') as HTMLElement).innerText = value + } + + setPending (value: string): void { + (this.element.querySelector('.done') as HTMLElement).innerHTML += `
${value}
` + } + + async setDone (value: string): Promise { + const icon = this.element.querySelector('.done')?.querySelector(`.${value.split(' ').join('-')}`)?.querySelector('.icon') + icon?.classList.remove('bx-minus') + icon?.classList.add('bx-check') + await new Promise(resolve => setTimeout(resolve, 300)) + } + + finish (): void { + this.element.style.opacity = '0' + this.element.style.pointerEvents = 'none' + } +} + +export default Preloader diff --git a/src/statusbar.ts b/src/statusbar.ts index 5111cfc..74a2146 100644 --- a/src/statusbar.ts +++ b/src/statusbar.ts @@ -1,31 +1,27 @@ -import * as clock from './modules/clock.ts' -import * as switcher from './modules/switcher.ts' -import * as appView from './modules/appLauncher.ts' -import * as apps from './modules/apps.ts' -import * as weather from './modules/weather.ts' -import * as battery from './modules/battery.ts' import { StatusItem } from './types' class StatusBar { - items: StatusItem[] = [] + pluginList: string[] = [ + 'appLauncher', + 'apps', + 'weather', + 'clock', + 'switcher', + 'battery' + ] + + plugins: StatusItem[] = [] element: HTMLElement constructor () { this.element = document.createElement('toolbar') document.body.appendChild(this.element) - - this.add(appView) - this.add(apps) - this.add(weather) - this.add(clock) - this.add(switcher) - this.add(battery) } add (item: StatusItem): void { - if (this.items.some(x => x.meta.id === item.meta.id)) { + if (this.plugins.some(x => x.meta.id === item.meta.id)) { console.error(`Unable to register tool; ${item.meta.id} is already registered.`) return } @@ -33,11 +29,25 @@ class StatusBar { const element = document.createElement('div') element.setAttribute('data-toolbar-id', item.meta.id) - this.items.push(item) + this.plugins.push(item) this.element.appendChild(element) item.run(element) } + + async init (): Promise { + window.preloader.setPending('plugins') + window.preloader.setStatus('importing default plugins...') + + for (const pluginPath of this.pluginList) { + const plugin = await import(`./modules/${pluginPath}.ts`) + + window.preloader.setStatus(`importing default plugins\n${pluginPath}`) + this.add(plugin) + } + + await window.preloader.setDone('plugins') + } } export default StatusBar diff --git a/src/style.less b/src/style.less index 9efce8c..ae5aa87 100644 --- a/src/style.less +++ b/src/style.less @@ -11,10 +11,6 @@ --crust: #11111b; } -.bx-category { - color: #181926; -} - body, html { background-color: var(--crust); @@ -55,10 +51,9 @@ toolbar { justify-content: center; div[data-toolbar-id="appview"] { - background: rgb(150, 181, 246); - background: linear-gradient(45deg, rgba(150, 181, 246, 1) 0%, rgba(150, 181, 246, 1) 12%, rgba(77, 129, 236, 1) 100%); - color: black; + background: linear-gradient(45deg, var(--crust), var(--surface-0)); } + & > div { background: var(--base); padding: 5px; @@ -185,3 +180,28 @@ launcher { } } } + +preloader { + position: absolute; + z-index: 999999999999999999; + top: 0; + left: 0; + background: var(--crust); + width: 100vw; + height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + transition: opacity 1s; + + .status, .done { + text-align: center; + } + + .done div { + display: flex; + align-items: center; + gap: 2px; + } +} \ No newline at end of file diff --git a/src/types.ts b/src/types.ts index d6748be..f978b3b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -9,6 +9,10 @@ export interface StatusItem { run: Function } +export interface PackageJSON { + version: string +} + export interface App { name: string pkg: string @@ -40,6 +44,8 @@ export interface FlowWindowConfig { width?: number height?: number + canResize: boolean + minWidth?: number minHeight?: number } diff --git a/src/wm.ts b/src/wm.ts index d7da6a4..82b6d40 100644 --- a/src/wm.ts +++ b/src/wm.ts @@ -93,13 +93,20 @@ export class FlowWindow { this.element.style.height = `${config.height ?? 200}px` this.header = document.createElement('window-header') - this.header.innerHTML = `
${config.title}
`; + this.header.innerHTML = `
${config.title}
` + if (config.canResize) { + this.header.innerHTML = `
${config.title}
` + } + (this.header.querySelector('#close') as HTMLElement).onclick = () => { this.close() } - (this.header.querySelector('#min') as HTMLElement).onclick = () => this.toggleMin(); - (this.header.querySelector('#max') as HTMLElement).onclick = () => this.toggleMax() + (this.header.querySelector('#min') as HTMLElement).onclick = () => this.toggleMin() + + if (config.canResize) { + (this.header.querySelector('#max') as HTMLElement).onclick = () => this.toggleMax() + } this.content = document.createElement('window-content') @@ -173,9 +180,6 @@ class WM { constructor () { this.windowArea = document.createElement('window-area') - this.launcher = document.createElement('launcher') - - this.init() } getHighestZIndex (): number { @@ -210,7 +214,11 @@ class WM { return this.isLauncherOpen } - private init (): void { + async init (): Promise { + window.preloader.setPending('window manager') + window.preloader.setStatus('creating app launcher...') + this.launcher = document.createElement('launcher') + this.launcher.innerHTML = ` @@ -230,7 +238,10 @@ class WM { this.launcher.style.filter = 'blur(0px)' this.launcher.style.pointerEvents = 'none' + window.preloader.setStatus('adding apps to app launcher...') + for (const pkg in flow.apps) { + window.preloader.setStatus(`adding apps to app launcher\n${flow.apps[pkg].name}`) const app = document.createElement('app') app.onclick = () => { flow.openApp(pkg) @@ -242,6 +253,8 @@ class WM { document.body.appendChild(this.windowArea) document.body.appendChild(this.launcher) + + await window.preloader.setDone('window manager') } } diff --git a/tsconfig.json b/tsconfig.json index 8b317be..a2d9131 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "./dist/", "sourceMap": true, "noImplicitAny": true, - "module": "CommonJS", + "module": "ES2022", "target": "ESNext", "jsx": "react", "jsxFactory": "h", diff --git a/vite.config.js b/vite.config.js index ff70d3f..ed22f22 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,8 +1,10 @@ import { defineConfig } from 'vite' import { nodePolyfills } from 'vite-plugin-node-polyfills' +import dynamicImport from 'vite-plugin-dynamic-import' export default defineConfig({ plugins: [ - nodePolyfills() + nodePolyfills(), + dynamicImport() ] })