[💥] Major system rewrite

This commit is contained in:
ThinLiquid 2024-01-16 04:35:28 +00:00
parent 2aaf0475a0
commit 8de338f899
No known key found for this signature in database
GPG key ID: 17538DC3DF6A7387
6 changed files with 463 additions and 37 deletions

View file

@ -45,7 +45,7 @@ npm run serve
## Made with
FlowOS is made with the following software:
* [Filer](https://github.com/filerjs/filer)
* [Kat21's HTML Library](https://github.com/datkat21/html)
* [Prism Code Editor](https://github.com/FIameCaster/prism-code-editor)
* [Vite](https://vitejs.dev)
* [Ultraviolet](https://github.com/titaniumnetwork-dev/ultraviolet)

View file

@ -1,8 +1,9 @@
import icon from '../../assets/icons/userinfo.svg'
import badge from '../../assets/badge.png'
import { App, PackageJSON } from '../../types'
import { App } from '../../types'
import FlowWindow from '../../structures/FlowWindow'
import HTML from '../../lib'
export default class InfoApp implements App {
meta = {
@ -14,7 +15,6 @@ export default class InfoApp implements App {
}
async open (): Promise<FlowWindow> {
const packageJSON: PackageJSON = await import('../../../package.json')
const win = window.wm.createWindow({
title: this.meta.name,
icon: this.meta.icon,
@ -30,18 +30,29 @@ export default class InfoApp implements App {
win.content.style.justifyContent = 'center'
win.content.style.alignItems = 'center'
win.content.style.background = 'var(--base)'
win.content.innerHTML = `
<div>
<h1 style="margin:0;">FlowOS</h1>
<p style="margin:0;">v${packageJSON.version}</p>
<br/>
<p>Created by ThinLiquid, 1nspird_, proudparot2, systemless_</p>
<img src="${badge}" height="50"><br/>
<a class="discord" href="https://discord.gg/flowos">Discord</a>
-
<a class="github" href="https://github.com/Flow-Works/FlowOS-2.0">Github</a>
</div>
`
const div = new HTML('div').appendTo(win.content)
new HTML('h1').style({
margin: '0'
}).text(`FlowOS ${window.flowDetails.codename}`).appendTo(div)
new HTML('p').style({
margin: '0'
}).text(`v${window.flowDetails.version}`).appendTo(div)
new HTML('br').appendTo(div)
new HTML('img').attr({
src: badge,
height: '50'
}).appendTo(div)
new HTML('br').appendTo(div)
new HTML('a').text('Discord').attr({
href: 'https://discord.gg/86F8dK9vfn',
class: 'discord'
}).appendTo(div)
new HTML('span').text(' - ').appendTo(div)
new HTML('a').text('Github').attr({
href: 'https://github.com/Flow-Works/FlowOS',
class: 'github'
}).appendTo(div)
return win
}

View file

@ -8,14 +8,15 @@ export enum Errors {
}
export enum Permission {
ROOT,
ADMIN,
USER
USER,
ELEVATED,
SYSTEM
}
export interface Directory {
type: 'directory'
permission: Permission
deleteable: boolean
children: {
[key: string]: Directory | File
}
@ -24,34 +25,41 @@ export interface Directory {
export interface File {
type: 'file'
permission: Permission
deleteable: boolean
content: Buffer
}
const defaultFS: { root: Directory } = {
root: {
type: 'directory',
permission: Permission.ROOT,
deleteable: false,
permission: Permission.SYSTEM,
children: {
home: {
type: 'directory',
permission: Permission.ROOT,
deleteable: false,
permission: Permission.SYSTEM,
children: {
Downloads: {
type: 'directory',
permission: Permission.ADMIN,
deleteable: false,
permission: Permission.USER,
children: {}
},
Applications: {
type: 'directory',
permission: Permission.ADMIN,
deleteable: false,
permission: Permission.USER,
children: {}
},
Desktop: {
type: 'directory',
permission: Permission.ADMIN,
deleteable: false,
permission: Permission.USER,
children: {
'README.md': {
type: 'file',
deleteable: true,
permission: Permission.USER,
content: Buffer.from('# Welcome to FlowOS!')
}
@ -59,39 +67,53 @@ const defaultFS: { root: Directory } = {
},
Pictures: {
type: 'directory',
permission: Permission.ADMIN,
deleteable: false,
permission: Permission.USER,
children: {}
},
Videos: {
type: 'directory',
permission: Permission.ADMIN,
deleteable: false,
permission: Permission.USER,
children: {}
},
Documents: {
type: 'directory',
permission: Permission.ADMIN,
deleteable: false,
permission: Permission.USER,
children: {}
},
Music: {
type: 'directory',
permission: Permission.ADMIN,
deleteable: false,
permission: Permission.USER,
children: {}
}
}
},
var: {
type: 'directory',
permission: Permission.ROOT,
deleteable: false,
permission: Permission.SYSTEM,
children: {}
},
etc: {
type: 'directory',
permission: Permission.ROOT,
children: {}
deleteable: false,
permission: Permission.SYSTEM,
children: {
hostname: {
type: 'file',
deleteable: false,
permission: Permission.ELEVATED,
content: Buffer.from('flow')
}
}
},
boot: {
type: 'directory',
permission: Permission.ROOT,
deleteable: false,
permission: Permission.SYSTEM,
children: {}
}
}
@ -203,10 +225,28 @@ export class VirtualFS {
* sufficient permissions to access a certain path.
*/
private async handlePermissions (path: string, permission: Permission): Promise<void> {
const { current } = await this.navigatePath(path)
let current
if (current.permission === Permission.ADMIN && current.permission <= permission) throw new Error(Errors.EACCES)
if (current.permission === Permission.ROOT && current.permission <= permission) throw new Error(Errors.EPERM)
current = (await this.navigatePath(path)).current
console.log(current)
if (current === undefined) current = (await this.navigatePathParent(path)).current
console.log(current)
console.log(current.permission, permission)
if (current.permission === Permission.USER && current.permission < permission) {
const uac = await window.wm.createModal('Elevated Permissions Required', 'You need elevated permissions to perform this operation.')
if (!uac) {
throw new Error(Errors.EACCES)
}
}
if (current.permission === Permission.ELEVATED && current.permission < permission) {
const uac = await window.wm.createModal('Elevated Permissions Required', 'You need elevated permissions to perform this operation.')
if (!uac) {
throw new Error(Errors.EACCES)
}
}
if (current.permission === Permission.SYSTEM && current.permission < permission) throw new Error(Errors.EPERM)
}
/**
@ -254,6 +294,7 @@ export class VirtualFS {
async unlink (path: string, permission = Permission.USER): Promise<void> {
const { current, filename } = await this.navigatePathParent(path)
if (!current.children[filename].deleteable) throw new Error(Errors.EPERM)
await this.handlePermissions(path, permission)
Reflect.deleteProperty(current.children, filename)
@ -296,7 +337,8 @@ export class VirtualFS {
current.children[filename] = {
type: 'file',
permission: Permission.USER,
deleteable: true,
permission,
content: Buffer.from(content)
}
await this.save()
@ -318,7 +360,8 @@ export class VirtualFS {
current.children[filename] = {
type: 'directory',
permission: Permission.USER,
deleteable: true,
permission,
children: {}
}
await this.save()
@ -336,6 +379,7 @@ export class VirtualFS {
async rmdir (path: string, permission = Permission.USER): Promise<void> {
const { current, filename } = await this.navigatePathParent(path)
if (!current.deleteable) throw new Error(Errors.EPERM)
await this.handlePermissions(path, permission)
if (current.children[filename].type !== 'directory') throw new Error(Errors.ENOTDIR)
@ -391,6 +435,9 @@ export class VirtualFS {
const { current: oldCurrent, filename: oldFilename } = await this.navigatePathParent(oldPath)
const { current: newCurrent, filename: newFilename } = await this.navigatePathParent(newPath)
if (!oldCurrent.deleteable) throw new Error(Errors.EPERM)
if (!newCurrent.deleteable) throw new Error(Errors.EPERM)
await this.handlePermissions(oldPath, permission)
await this.handlePermissions(newPath, permission)

View file

@ -14,7 +14,6 @@ const flowDetails = {
version,
codename: 'Mochi'
}
declare global {
interface Window {
flowDetails: typeof flowDetails
@ -38,6 +37,7 @@ if (params.get('debug') !== null && params.get('debug') !== undefined) {
enableDebug().catch(e => console.error(e))
}
window.flowDetails = flowDetails
window.preloader = new Preloader()
window.flow = new Flow()
window.statusBar = new StatusBar()

View file

@ -1,4 +1,5 @@
import HTML from '../lib'
import FlowWindow from '../structures/FlowWindow'
import { FlowWindowConfig } from '../types'
@ -43,6 +44,40 @@ class WindowManager {
return win
}
/**
* Creates a modal window.
*
* @param {string} title - A string representing the title of the modal window.
* @param {string} text - The `text` parameter is a string that represents the content or message to
* be displayed in the modal window.
* @returns The function `createModal` is returning a `FlowWindow` object.
*/
async createModal (title: string, text: string): Promise<boolean> {
const win = new FlowWindow(this, {
title,
icon: '',
width: 300,
height: 200,
canResize: false
})
return await new Promise((resolve) => {
new HTML('h3').text(text).appendTo(win.content)
new HTML('p').text(text).appendTo(win.content)
new HTML('button').text('Allow').appendTo(win.content).on('click', () => {
resolve(true)
win.close()
})
new HTML('button').text('Allow').appendTo(win.content).on('click', () => {
resolve(false)
win.close()
})
this.windows.push(win)
this.windowArea.appendChild(win.element)
})
}
/**
* Toggles the app launcher.
*/

333
src/lib.ts Normal file
View file

@ -0,0 +1,333 @@
export default class HTML {
/** The HTML element referenced in this instance. Change using `.swapRef()`, or remove using `.cleanup()`. */
elm: HTMLInputElement | HTMLElement
/**
* Create a new instance of the HTML class.
* @param elm The HTML element to be created or classified from.
*/
constructor (elm: string | HTMLElement) {
if (elm instanceof HTMLElement) {
this.elm = elm
} else {
this.elm = document.createElement(elm === '' ? 'div' : elm)
}
}
/**
* Sets the text of the current element.
* @param val The text to set to.
* @returns HTML
*/
text (val: string): HTML {
this.elm.innerText = val
return this
}
/**
* Sets the text of the current element.
* @param val The text to set to.
* @returns HTML
*/
html (val: string): HTML {
this.elm.innerHTML = val
return this
}
/**
* Safely remove the element. Can be used in combination with a `.swapRef()` to achieve a "delete & swap" result.
* @returns HTML
*/
cleanup (): HTML {
this.elm.remove()
return this
}
/**
* querySelector something.
* @param selector The query selector.
* @returns The HTML element (not as HTML)
*/
query (selector: string): HTMLElement | null {
return this.elm.querySelector(selector)
}
/**
* An easier querySelector method.
* @param query The string to query
* @returns a new HTML
*/
qs (query: string): HTML | null {
if (this.elm.querySelector(query) != null) {
return HTML.from(this.elm.querySelector(query) as HTMLElement)
} else {
return null
}
}
/**
* An easier querySelectorAll method.
* @param query The string to query
* @returns a new HTML
*/
qsa (query: string): Array<HTML | null> | null {
if (this.elm.querySelector(query) != null) {
return Array.from(this.elm.querySelectorAll(query)).map((e) =>
HTML.from(e as HTMLElement)
)
} else {
return null
}
}
/**
* Sets the ID of the element.
* @param val The ID to set.
* @returns HTML
*/
id (val: string): HTML {
this.elm.id = val
return this
}
/**
* Toggle on/off a class.
* @param val The class to toggle.
* @returns HTML
*/
class (...val: string[]): HTML {
for (let i = 0; i < val.length; i++) {
this.elm.classList.toggle(val[i])
}
return this
}
/**
* Toggles ON a class.
* @param val The class to enable.
* @returns HTML
*/
classOn (...val: string[]): HTML {
for (let i = 0; i < val.length; i++) {
this.elm.classList.add(val[i])
}
return this
}
/**
* Toggles OFF a class.
* @param val The class to disable.
* @returns HTML
*/
classOff (...val: string[]): HTML {
for (let i = 0; i < val.length; i++) {
this.elm.classList.remove(val[i])
}
return this
}
/**
* Apply CSS styles (dashed method.) Keys use CSS syntax, e.g. `background-color`.
* @param obj The styles to apply (as an object of `key: value;`.)
* @returns HTML
*/
style (obj: { [x: string]: string | null }): HTML {
for (const key of Object.keys(obj)) {
this.elm.style.setProperty(key, obj[key])
}
return this
}
/**
* Apply CSS styles (JS method.) Keys use JS syntax, e.g. `backgroundColor`.
* @param obj The styles to apply (as an object of `key: value;`)
* @returns HTML
*/
styleJs (obj: { [key: string]: string | null }): HTML {
for (const key of Object.keys(obj)) {
// @ts-expect-error No other workaround I could find.
this.elm.style[key] = obj[key]
}
return this
}
/**
* Apply an event listener.
* @param ev The event listener type to add.
* @param cb The event listener callback to add.
* @returns HTML
*/
on (ev: string, cb: EventListenerOrEventListenerObject): HTML {
this.elm.addEventListener(ev, cb)
return this
}
/**
* Remove an event listener.
* @param ev The event listener type to remove.
* @param cb The event listener callback to remove.
* @returns HTML
*/
un (ev: string, cb: EventListenerOrEventListenerObject): HTML {
this.elm.removeEventListener(ev, cb)
return this
}
/**
* Append this element to another element. Uses `appendChild()` on the parent.
* @param parent Element to append to. HTMLElement, HTML, and string (as querySelector) are supported.
* @returns HTML
*/
appendTo (parent: HTMLElement | HTML | string): HTML {
if (parent instanceof HTMLElement) {
parent.appendChild(this.elm)
} else if (parent instanceof HTML) {
parent.elm.appendChild(this.elm)
} else if (typeof parent === 'string') {
document.querySelector(parent)?.appendChild(this.elm)
}
return this
}
/**
* Append an element. Typically used as a `.append(new HTML(...))` call.
* @param elem The element to append.
* @returns HTML
*/
append (elem: string | HTMLElement | HTML): HTML {
if (elem instanceof HTMLElement) {
this.elm.appendChild(elem)
} else if (elem instanceof HTML) {
this.elm.appendChild(elem.elm)
} else if (typeof elem === 'string') {
const newElem = document.createElement(elem)
this.elm.appendChild(newElem)
return new HTML(newElem.tagName)
}
return this
}
/**
* Append multiple elements. Typically used as a `.appendMany(new HTML(...), new HTML(...)` call.
* @param elements The elements to append.
* @returns HTML
*/
appendMany (...elements: any[]): HTML {
for (const elem of elements) {
this.append(elem)
}
return this
}
/**
* Clear the innerHTML of the element.
* @returns HTML
*/
clear (): HTML {
this.elm.innerHTML = ''
return this
}
/**
* Set attributes (object method.)
* @param obj The attributes to set (as an object of `key: value;`)
* @returns HTML
*/
attr (obj: { [x: string]: any }): HTML {
for (const key in obj) {
if (obj[key] !== null && obj[key] !== undefined) {
this.elm.setAttribute(key, obj[key])
} else {
this.elm.removeAttribute(key)
}
}
return this
}
/**
* Set the text value of the element. Only works if element is `input` or `textarea`.
* @param str The value to set.
* @returns HTML
*/
val (str: any): HTML {
const x = this.elm as HTMLInputElement
x.value = str
return this
}
/**
* Retrieve text content from the element. (as innerText, not trimmed)
* @returns string
*/
getText (): string {
return (this.elm as HTMLInputElement).innerText
}
/**
* Retrieve HTML content from the element.
* @returns string
*/
getHTML (): string {
return (this.elm as HTMLInputElement).innerHTML
}
/**
* Retrieve the value of the element. Only applicable if it is an `input` or `textarea`.
* @returns string
*/
getValue (): string {
return (this.elm as HTMLInputElement).value
}
/**
* Swap the local `elm` with a new HTMLElement.
* @param elm The element to swap with.
* @returns HTML
*/
swapRef (elm: HTMLElement): HTML {
this.elm = elm
return this
}
/**
* An alternative method to create an HTML instance.
* @param elm Element to create from.
* @returns HTML
*/
static from (elm: HTMLElement | string): HTML | null {
if (typeof elm === 'string') {
const element = HTML.qs(elm)
if (element === null) return null
else return element
} else {
return new HTML(elm)
}
}
/**
* An easier querySelector method.
* @param query The string to query
* @returns a new HTML
*/
static qs (query: string): HTML | null {
if (document.querySelector(query) != null) {
return HTML.from(document.querySelector(query) as HTMLElement)
} else {
return null
}
}
/**
* An easier querySelectorAll method.
* @param query The string to query
* @returns a new HTML
*/
static qsa (query: string): Array<HTML | null> | null {
if (document.querySelector(query) != null) {
return Array.from(document.querySelectorAll(query)).map((e) =>
HTML.from(e as HTMLElement)
)
} else {
return null
}
}
}