Merge pull request #25 from Flow-Works/thin-dev-branch

Thin-dev-branch
This commit is contained in:
ThinLiquid 2023-10-18 22:35:22 +01:00 committed by GitHub
commit 5800180579
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 272 additions and 123 deletions

View file

@ -3,16 +3,15 @@ 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-markup.js' import 'prismjs/components/prism-markup.js'
import 'prismjs/components/prism-clike.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-jsx.js'
import 'prismjs/components/prism-json.js' import 'prismjs/components/prism-tsx.js'
import 'prismjs/components/prism-c.js' import 'prism-code-editor/languages'
import 'prismjs/components/prism-csharp.js' import 'prism-code-editor/prism-markdown'
import 'prismjs/components/prism-cpp.js'
import { FlowWindow } from '../wm.ts' import { FlowWindow } from '../wm.ts'
interface EditorConfig { interface EditorConfig {
@ -20,15 +19,18 @@ interface EditorConfig {
} }
export default class EditorApp implements App { export default class EditorApp implements App {
name = 'Editor' meta = {
pkg = 'flow.editor' name: 'Editor',
icon = icon description: 'A simple editor app.',
version = '1.0.0' pkg: 'flow.editor',
version: '1.0.0',
icon
}
async open (data?: EditorConfig): Promise<FlowWindow> { async open (data?: EditorConfig): Promise<FlowWindow> {
const win = window.wm.createWindow({ const win = window.wm.createWindow({
title: this.name, title: this.meta.name,
icon, icon: this.meta.icon,
width: 500, width: 500,
height: 400 height: 400
}) })
@ -125,12 +127,76 @@ export default class EditorApp implements App {
} }
}) })
let language
switch (data.path.split('.').at(-1)?.toLowerCase()) {
case 'c':
case 'cs':
case 'cpp':
case 'java': {
language = 'clike'
break
}
case 'ts': {
language = 'typescript'
break
}
case 'js':
case 'mjs':
case 'cjs': {
language = 'javascript'
break
}
case 'jsx': {
language = 'jsx'
break
}
case 'tsx': {
language = 'tsx'
break
}
case 'html': {
language = 'html'
break
}
case 'md': {
language = 'md'
break
}
case 'css': {
language = 'css'
break
}
case 'xml': {
language = 'xml'
break
}
case 'py': {
language = 'python'
break
}
default: {
language = 'text'
break
}
}
const value = (await window.fs.promises.readFile(data.path)).toString() const value = (await window.fs.promises.readFile(data.path)).toString()
const editor = fullEditor( const editor = fullEditor(
Prism, Prism,
win.content.querySelector('.editor'), win.content.querySelector('.editor'),
{ {
language: data.path.split('.').at(-1), language,
theme: 'github-dark', theme: 'github-dark',
value value
} }

View file

@ -6,15 +6,18 @@ import { FlowWindow } from '../wm.ts'
import { Stats } from 'fs' import { Stats } from 'fs'
export default class FilesApp implements App { export default class FilesApp implements App {
name = 'Files' meta = {
pkg = 'flow.files' name: 'Files',
icon = icon description: 'A simple files app.',
version = '1.0.0' pkg: 'flow.files',
version: '1.0.0',
icon
}
async open (): Promise<FlowWindow> { async open (): Promise<FlowWindow> {
const win = window.wm.createWindow({ const win = window.wm.createWindow({
title: this.name, title: this.meta.name,
icon, icon: this.meta.icon,
width: 500, width: 500,
height: 400 height: 400
}) })

View file

@ -3,17 +3,19 @@ import { App, PackageJSON } from '../types.ts'
import { FlowWindow } from '../wm.ts' import { FlowWindow } from '../wm.ts'
export default class SettingsApp implements App { export default class SettingsApp implements App {
name = 'Info' meta = {
pkg = 'flow.info' name: 'Info',
icon = icon description: 'FlowOS Information.',
version = '1.0.0' pkg: 'flow.info',
canResize = true version: '1.0.0',
icon
}
async open (): Promise<FlowWindow> { async open (): Promise<FlowWindow> {
const packageJSON: PackageJSON = await import('../../package.json') const packageJSON: PackageJSON = await import('../../package.json')
const win = window.wm.createWindow({ const win = window.wm.createWindow({
title: this.name, title: this.meta.name,
icon, icon: this.meta.icon,
width: 300, width: 300,
height: 400, height: 400,
canResize: false canResize: false

View file

@ -1,17 +1,21 @@
import icon from '../assets/icons/manager.png' import icon from '../assets/icons/manager.png'
import { App } from '../types.ts' import { App, LoadedApp, LoadedPlugin } from '../types.ts'
import { FlowWindow } from '../wm.ts' import { FlowWindow } from '../wm.ts'
import nullIcon from '../assets/icons/null.png'
export default class ManagerApp implements App { export default class ManagerApp implements App {
name = 'Manager' meta = {
pkg = 'flow.manager' name: 'Flow Manager',
icon = icon description: 'A FlowOS utility app.',
version = '1.0.0' pkg: 'flow.manager',
version: '1.0.0',
icon
}
async open (): Promise<FlowWindow> { async open (): Promise<FlowWindow> {
const win = window.wm.createWindow({ const win = window.wm.createWindow({
title: this.name, title: this.meta.name,
icon, icon: this.meta.icon,
width: 350, width: 350,
height: 500 height: 500
}) })
@ -22,13 +26,24 @@ export default class ManagerApp implements App {
win.content.style.padding = '10px' win.content.style.padding = '10px'
win.content.style.background = 'var(--base)' win.content.style.background = 'var(--base)'
win.content.innerHTML = ` win.content.innerHTML = `
${window.flow.apps.map(app => { ${window.flow.apps.map((app: LoadedApp) => {
return ` return `
<div style="display:flex;gap: 10px;padding: 10px;background: var(--surface-0);border-radius: 10px;"> <div style="display:flex;gap: 10px;padding: 10px;background: var(--surface-0);border-radius: 10px;">
<img src="${app.icon}" style="border-radius: 40%;aspect-ratio: 1 / 1;height: 50px;"/> <img src="${app.meta.icon}" style="border-radius: 40%;aspect-ratio: 1 / 1;height: 50px;"/>
<div> <div>
<h3 style="margin:0;">${app.name} ${(app.builtin ?? false) ? '<code style="font-size: 0.75em;">(builtin)</code>' : ''}</h3> <h3 style="margin:0;">${app.meta.name} ${(app.builtin ?? false) ? '<code style="font-size: 0.75em;">(builtin)</code>' : ''}</h3>
<p style="margin:0;">${app.pkg} (v${app.version})</p> <p style="margin:0;">${app.meta.pkg} (v${app.meta.version}) - App</p>
</div>
</div>
`
}).join('')}
${window.flow.plugins.map((plugin: LoadedPlugin) => {
return `
<div style="display:flex;gap: 10px;padding: 10px;background: var(--surface-0);border-radius: 10px;">
<img src="${plugin.meta.icon ?? nullIcon}" style="border-radius: 100%;aspect-ratio: 1 / 1;height: 50px;"/>
<div>
<h3 style="margin:0;">${plugin.meta.name} ${(plugin.builtin ?? false) ? '<code style="font-size: 0.75em;">(builtin)</code>' : ''}</h3>
<p style="margin:0;">${plugin.meta.pkg} (v${plugin.meta.version}) - Plugin</p>
</div> </div>
</div> </div>
` `

View file

@ -3,15 +3,18 @@ import { App } from '../types.ts'
import { FlowWindow } from '../wm.ts' import { FlowWindow } from '../wm.ts'
export default class MusicApp implements App { export default class MusicApp implements App {
name = 'Music' meta = {
pkg = 'flow.music' name: 'Music',
icon = icon description: 'A simple music app.',
version = '1.0.0' pkg: 'flow.music',
version: '1.0.0',
icon
}
async open (): Promise<FlowWindow> { async open (): Promise<FlowWindow> {
const win = window.wm.createWindow({ const win = window.wm.createWindow({
title: this.name, title: this.meta.name,
icon, icon: this.meta.icon,
width: 700, width: 700,
height: 300 height: 300
}) })

View file

@ -3,15 +3,18 @@ import { App } from '../types.ts'
import { FlowWindow } from '../wm.ts' import { FlowWindow } from '../wm.ts'
export default class SettingsApp implements App { export default class SettingsApp implements App {
name = 'Settings' meta = {
pkg = 'flow.settings' name: 'Settings',
icon = icon description: 'Modify/customize FlowOS.',
version = '1.0.0' pkg: 'flow.settings',
version: '1.0.0',
icon
}
async open (): Promise<FlowWindow> { async open (): Promise<FlowWindow> {
const win = window.wm.createWindow({ const win = window.wm.createWindow({
title: this.name, title: this.meta.name,
icon, icon: this.meta.icon,
width: 700, width: 700,
height: 300 height: 300
}) })

BIN
src/assets/icons/null.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

5
src/files.d.ts vendored
View file

@ -1,2 +1,5 @@
declare module '*.png' declare module '*.png' {
const image: string
export = image
}
declare module '*.json' declare module '*.json'

View file

@ -1,4 +1,4 @@
import { App, LoadedApp } from './types.ts' import { LoadedApp, LoadedPlugin } from './types.ts'
class Flow { class Flow {
apps: LoadedApp[] = [] apps: LoadedApp[] = []
@ -11,17 +11,30 @@ class Flow {
'manager' 'manager'
] ]
async init (): Promise<void> { plugins: LoadedPlugin[] = []
pluginList: string[] = [
'appLauncher',
'apps',
'weather',
'clock',
'switcher',
'battery'
]
private async initApps (): Promise<void> {
window.preloader.setPending('apps') window.preloader.setPending('apps')
window.preloader.setStatus('importing default apps...') window.preloader.setStatus('importing default apps...')
for (const appPath of this.appList) { for (const appPath of this.appList) {
const { default: ImportedApp } = await import(`./apps/${appPath}.ts`) window.preloader.setStatus(`importing default apps\n${appPath}`)
const { default: ImportedApp } = await import(`./apps/${appPath}.ts`).catch((e: Error) => {
console.error(e)
window.preloader.setStatus(`unable to import ${appPath}\n${e.name}: ${e.message}`)
})
const app = new ImportedApp() const app = new ImportedApp()
app.builtin = true app.builtin = true
window.preloader.setStatus(`importing default apps\n${appPath}`) this.addApp(app)
this.add(app)
} }
window.wm.launcher.style.opacity = '0' window.wm.launcher.style.opacity = '0'
@ -31,13 +44,13 @@ class Flow {
window.preloader.setStatus('adding apps to app launcher...') window.preloader.setStatus('adding apps to app launcher...')
this.apps.forEach((app) => { this.apps.forEach((app) => {
window.preloader.setStatus(`adding apps to app launcher\n${app.name}`) window.preloader.setStatus(`adding apps to app launcher\n${app.meta.name}`)
const appElement = document.createElement('app') const appElement = document.createElement('app')
appElement.onclick = async () => { appElement.onclick = async () => {
await window.flow.openApp(app.pkg) await window.flow.openApp(app.meta.pkg)
window.wm.toggleLauncher() window.wm.toggleLauncher()
} }
appElement.innerHTML = `<img src="${app.icon}"><div>${app.name}</div>` appElement.innerHTML = `<img src="${app.meta.icon}"><div>${app.meta.name}</div>`
window.wm.launcher.querySelector('apps')?.appendChild(appElement) window.wm.launcher.querySelector('apps')?.appendChild(appElement)
}) })
@ -47,17 +60,48 @@ class Flow {
await window.preloader.setDone('apps') await window.preloader.setDone('apps')
} }
add (app: App): void { private async initPlugins (): Promise<void> {
if (this.apps.some(x => x.pkg === app.pkg)) { window.preloader.setPending('plugins')
console.error(`Unable to register app; ${app.pkg} is already registered.`) 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) => {
console.error(e)
window.preloader.setStatus(`unable to import ${pluginPath}\n${e.name}: ${e.message}`)
})
const loadedPlugin = {
...plugin,
builtin: true
}
this.addPlugin(loadedPlugin)
}
}
async init (): Promise<void> {
await this.initApps()
await this.initPlugins()
}
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.`)
return return
} }
this.apps.push(app) this.apps.push(app)
} }
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.`)
return
}
this.plugins.push(plugin)
}
async openApp (pkg: string, data?: any): Promise<void> { async openApp (pkg: string, data?: any): Promise<void> {
const app = this.apps.find(x => x.pkg === pkg) const app = this.apps.find(x => x.meta.pkg === pkg)
const win = app?.open(data) const win = app?.open(data)
const event = new CustomEvent('app_opened', { detail: { app, win: await win } }) const event = new CustomEvent('app_opened', { detail: { app, win: await win } })
window.dispatchEvent(event) window.dispatchEvent(event)

View file

@ -39,10 +39,9 @@ window.wm = new WM();
window.fs = new (window as any).Filer.FileSystem() window.fs = new (window as any).Filer.FileSystem()
await window.preloader.setDone('filesystem') await window.preloader.setDone('filesystem')
await window.statusBar.init()
await window.wm.init() await window.wm.init()
await window.flow.init() await window.flow.init()
await window.statusBar.init()
window.preloader.setStatus('') window.preloader.setStatus('')
window.preloader.finish() window.preloader.finish()

View file

@ -1,7 +1,8 @@
export const meta = { export const meta = {
name: 'App Launcher', name: 'App Launcher',
description: 'Opens the app launcher.', description: 'Opens the app launcher.',
id: 'applauncher' pkg: 'flow.applauncher',
version: '1.0.0'
} }
export const run = (element: HTMLDivElement): void => { export const run = (element: HTMLDivElement): void => {

View file

@ -3,7 +3,8 @@ import { AppOpenedEvent, AppClosedEvent } from '../types'
export const meta = { export const meta = {
name: 'Apps', name: 'Apps',
description: 'Displays the current apps open.', description: 'Displays the current apps open.',
id: 'apps' pkg: 'flow.apps',
version: '1.0.0'
} }
export const run = (element: HTMLDivElement): void => { export const run = (element: HTMLDivElement): void => {
@ -19,7 +20,7 @@ export const run = (element: HTMLDivElement): void => {
appIcon.style.background = 'var(--surface-0)' appIcon.style.background = 'var(--surface-0)'
appIcon.style.padding = '5px 7.5px' appIcon.style.padding = '5px 7.5px'
appIcon.style.borderRadius = '5px' appIcon.style.borderRadius = '5px'
appIcon.innerHTML = `<img data-id="${win.id}" src="${app.icon}"/> ${app.name}` appIcon.innerHTML = `<img data-id="${win.id}" src="${app.meta.icon}"/> ${app.meta.name}`
appIcon.onclick = async () => { appIcon.onclick = async () => {
const win = await e.detail.win const win = await e.detail.win
win.focus() win.focus()

View file

@ -1,7 +1,8 @@
export const meta = { export const meta = {
name: 'Battery', name: 'Battery',
description: 'Tells you your device\'s battery.', description: 'Tells you your device\'s battery.',
id: 'battery' pkg: 'flow.battery',
version: '1.0.0'
} }
export const run = (element: HTMLDivElement): void => { export const run = (element: HTMLDivElement): void => {

View file

@ -1,7 +1,8 @@
export const meta = { export const meta = {
name: 'Clock', name: 'Clock',
description: 'Displays the date & time.', description: 'Displays the date & time.',
id: 'clock' pkg: 'flow.clock',
version: '1.0.0'
} }
export const run = (element: HTMLDivElement): void => { export const run = (element: HTMLDivElement): void => {

View file

@ -1,7 +1,8 @@
export const meta = { export const meta = {
name: 'Desktop Switcher', name: 'Desktop Switcher',
description: 'Allows you to switch between desktops.', description: 'Allows you to switch between desktops.',
id: 'switcher' pkg: 'flow.switcher',
version: '1.0.0'
} }
export const run = (element: HTMLDivElement): void => { export const run = (element: HTMLDivElement): void => {

View file

@ -1,7 +1,8 @@
export const meta = { export const meta = {
name: 'Weather', name: 'Weather',
description: 'Tells you the weather.', description: 'Tells you the weather.',
id: 'weather' pkg: 'flow.weather',
version: '1.0.0'
} }
export const run = (element: HTMLDivElement): void => { export const run = (element: HTMLDivElement): void => {

View file

@ -7,7 +7,7 @@ class Preloader {
this.element = document.createElement('preloader') this.element = document.createElement('preloader')
this.element.innerHTML = ` this.element.innerHTML = `
<img src="${flowIcon as string}" width="150px"> <img src="${flowIcon}" width="150px">
<div class="done"></div> <div class="done"></div>
<div class="status"></div> <div class="status"></div>
` `

View file

@ -1,17 +1,7 @@
import { StatusItem } from './types' import { Plugin } from './types'
class StatusBar { class StatusBar {
pluginList: string[] = [
'appLauncher',
'apps',
'weather',
'clock',
'switcher',
'battery'
]
plugins: StatusItem[] = []
element: HTMLElement element: HTMLElement
constructor () { constructor () {
@ -20,30 +10,21 @@ class StatusBar {
document.body.appendChild(this.element) document.body.appendChild(this.element)
} }
add (item: StatusItem): void { async add (item: Plugin): Promise<void> {
if (this.plugins.some(x => x.meta.id === item.meta.id)) {
console.error(`Unable to register tool; ${item.meta.id} is already registered.`)
return
}
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.pkg)
this.plugins.push(item)
this.element.appendChild(element) this.element.appendChild(element)
item.run(element) await item.run(element)
} }
async init (): Promise<void> { async init (): Promise<void> {
window.preloader.setPending('plugins') window.preloader.setStatus('adding plugins to statusbar...')
window.preloader.setStatus('importing default plugins...')
for (const pluginPath of this.pluginList) { for (const plugin of window.flow.plugins) {
const plugin = await import(`./modules/${pluginPath}.ts`) window.preloader.setStatus(`adding plugins to statusbar\n${plugin.meta.pkg}`)
await this.add(plugin)
window.preloader.setStatus(`importing default plugins\n${pluginPath}`)
this.add(plugin)
} }
await window.preloader.setDone('plugins') await window.preloader.setDone('plugins')

View file

@ -143,24 +143,36 @@ launcher {
max-height: 70vh; max-height: 70vh;
padding: 20px; padding: 20px;
margin: 40px; margin: 40px;
display: flex;
justify-content: center; justify-content: center;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(125px, 1fr));
grid-template-rows: repeat(auto-fit, minmax(200px, 1fr));
gap: 40px; gap: 40px;
flex-wrap: wrap;
app { app {
align-items: center;
justify-content: center; justify-content: center;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 10px; gap: 10px;
height: max-content; height: max-content;
text-align: center; text-align: center;
overflow: hidden;
img { img {
width: 75px; width: 100%;
border-radius: 40%; border-radius: 40%;
aspect-ratio: 1 / 1; aspect-ratio: 1 / 1;
} }
div {
overflow: hidden;
text-overflow: ellipsis;
width: fit-content;
height: 100%;
white-space: nowrap;
}
} }
} }

View file

@ -1,29 +1,9 @@
import { FlowWindow } from './wm' import { FlowWindow } from './wm'
export interface StatusItem {
meta: {
name: string
description: string
id: string
}
run: Function
}
export interface PackageJSON { export interface PackageJSON {
version: string version: string
} }
export interface App {
name: string
pkg: string
version: string
icon: string
open: (data: any) => Promise<FlowWindow>
}
export interface AppOpenedEvent extends CustomEvent { export interface AppOpenedEvent extends CustomEvent {
detail: { detail: {
app: App app: App
@ -50,10 +30,42 @@ export interface FlowWindowConfig {
minHeight?: number minHeight?: number
} }
export interface App {
meta: {
name: string
description: string
pkg: string
version: string
icon: string
}
open: (data: any) => Promise<FlowWindow>
}
export interface Plugin {
meta: {
name: string
description: string
pkg: string
version: string
icon?: string
}
run: (element: HTMLDivElement) => void | Promise<void>
}
export interface Apps { export interface Apps {
[key: string]: App [key: string]: App
} }
export interface Plugins {
[key: string]: Plugin
}
export interface LoadedApp extends App { export interface LoadedApp extends App {
builtin: boolean builtin: boolean
} }
export interface LoadedPlugin extends Plugin {
builtin: boolean
}