Merge pull request #136 from Flow-Works/dev
[🪢] Merge `dev` into `master`
This commit is contained in:
commit
3a292d68bc
14 changed files with 136 additions and 67 deletions
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
|
|
@ -1,9 +1,9 @@
|
|||
name: build
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
branches: [master, dev]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
branches: [master, dev]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
|
|
|||
4
.github/workflows/ts-standard.yml
vendored
4
.github/workflows/ts-standard.yml
vendored
|
|
@ -1,9 +1,9 @@
|
|||
name: ts-standard
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
branches: [master, dev]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
branches: [master, dev]
|
||||
jobs:
|
||||
ts-standard:
|
||||
runs-on: ubuntu-latest
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
import './assets/style.less'
|
||||
import { version } from '../package.json'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
import { Permission } from './system/lib/VirtualFS'
|
||||
import ProcessLib from './structures/ProcessLib'
|
||||
import ProcLib from './structures/ProcLib'
|
||||
import { Executable, Process, Package, ProcessInfo, KernelConfig } from './types'
|
||||
import { Executable, Process, Package, ProcessInfo, KernelConfig, Permission } from './types'
|
||||
import semver from 'semver'
|
||||
|
||||
declare global {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import semver from 'semver'
|
||||
import Kernel from '../kernel'
|
||||
import { Permission } from '../system/lib/VirtualFS'
|
||||
import { Process, Executable, LibraryData, Package, Library } from '../types'
|
||||
import { Process, Executable, Package, Library, Permission, LoadedLibrary, LibraryPath } from '../types'
|
||||
import FlowWindow from './FlowWindow'
|
||||
import LibraryLib from './LibraryLib'
|
||||
import ProcLib from './ProcLib'
|
||||
|
|
@ -58,7 +57,7 @@ export default class ProcessLib {
|
|||
})
|
||||
}
|
||||
|
||||
async loadLibrary (url: string): Promise<LibraryData> {
|
||||
async loadLibrary <T extends LibraryPath>(url: T): Promise<LoadedLibrary<T>> {
|
||||
let executable: Executable
|
||||
try {
|
||||
const comps = import.meta.glob('../system/**/*.ts')
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import HTML from '../HTML'
|
||||
import { AppClosedEvent, AppOpenedEvent, Process } from '../types'
|
||||
import { AppClosedEvent, AppOpenedEvent, Directory, FileSystemObject, Process } from '../types'
|
||||
import { getTime } from '../utils'
|
||||
import { db, defaultFS, initializeDatabase, read, setFileSystem, write } from './lib/VirtualFS'
|
||||
import nullIcon from '../assets/icons/application-default-icon.svg'
|
||||
|
|
@ -32,10 +32,19 @@ const BootLoader: Process = {
|
|||
} else {
|
||||
console.warn('Persistent storage is not supported.')
|
||||
}
|
||||
const fileSystem = await read()
|
||||
const fileSystem = await read() as FileSystemObject
|
||||
if (fileSystem === undefined) {
|
||||
await write(defaultFS)
|
||||
} else {
|
||||
const appsDirectory = ((fileSystem.root.children.home as Directory).children.Applications as Directory).children
|
||||
const defaultAppsDirectory = ((defaultFS.root.children.home as Directory).children.Applications as Directory).children
|
||||
for (const file in defaultAppsDirectory) {
|
||||
if (appsDirectory[file] === undefined && defaultAppsDirectory[file] !== undefined) {
|
||||
console.log(file)
|
||||
appsDirectory[file] = defaultAppsDirectory[file]
|
||||
}
|
||||
}
|
||||
await write(fileSystem)
|
||||
await setFileSystem(fileSystem)
|
||||
}
|
||||
|
||||
|
|
@ -74,7 +83,7 @@ const BootLoader: Process = {
|
|||
const files = await fs.readdir('/home/Applications/')
|
||||
files
|
||||
.filter((x: string) => x.endsWith('.app') && ((input.elm as HTMLInputElement) !== null ? x.toLowerCase().includes((input.elm as HTMLInputElement).value.toLowerCase()) : true))
|
||||
.forEach(async (file: string) => {
|
||||
.forEach((file: string) => {
|
||||
fs.readFile(`/home/Applications/${file}`).then(async (data: Uint8Array) => {
|
||||
const path = Buffer.from(data).toString()
|
||||
const executable = await process.kernel.getExecutable(path) as Process
|
||||
|
|
@ -88,7 +97,7 @@ const BootLoader: Process = {
|
|||
alt: `${executable.config.name} icon`
|
||||
}).appendTo(appElement)
|
||||
new HTML('div').text(executable.config.name).appendTo(appElement)
|
||||
})
|
||||
}).catch((e: any) => console.error(e))
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -120,11 +129,11 @@ const BootLoader: Process = {
|
|||
|
||||
setInterval((): any => {
|
||||
getTime().then((time) => {
|
||||
statusBar.element.qs('div[data-toolbar-id="calendar"]').text(time)
|
||||
statusBar.element.qs('div[data-toolbar-id="calendar"]')?.text(time)
|
||||
}).catch(e => console.error)
|
||||
}, 1000)
|
||||
|
||||
statusBar.element.qs('div[data-toolbar-id="start"]').on('click', () => {
|
||||
statusBar.element.qs('div[data-toolbar-id="start"]')?.on('click', () => {
|
||||
launcher.toggle()
|
||||
})
|
||||
|
||||
|
|
@ -171,11 +180,11 @@ const BootLoader: Process = {
|
|||
e.detail.win.focus()
|
||||
e.detail.win.toggleMin()
|
||||
})
|
||||
).appendTo(statusBar.element.qs('div[data-toolbar-id="apps"]'))
|
||||
).appendTo(statusBar.element.qs('div[data-toolbar-id="apps"]')?.elm as HTMLElement)
|
||||
})
|
||||
|
||||
document.addEventListener('app_closed', (e: AppClosedEvent): void => {
|
||||
statusBar.element.qs('div[data-toolbar-id="apps"]').qs(`img[data-id="${e.detail.token}"]`).elm.parentElement.remove()
|
||||
statusBar.element.qs('div[data-toolbar-id="apps"]')?.qs(`img[data-id="${e.detail.token}"]`)?.elm.parentElement?.remove()
|
||||
})
|
||||
|
||||
document.body.style.flexDirection = 'column-reverse'
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ const UserAccessControl: Process = {
|
|||
const target = process.data.executable
|
||||
return await new Promise((resolve) => {
|
||||
process.loadLibrary('lib/WindowManager').then(async wm => {
|
||||
let message
|
||||
let message = 'Unknown action'
|
||||
switch (process.data.type) {
|
||||
case 'launch': {
|
||||
message = `${initiator.config.name as string} wants to launch ${target.config.name as string}`
|
||||
|
|
@ -42,7 +42,7 @@ const UserAccessControl: Process = {
|
|||
} else {
|
||||
resolve(false)
|
||||
}
|
||||
})
|
||||
}).catch((e) => console.error(e))
|
||||
}).catch((e) => console.error(e))
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ const BrowserApp: Process = {
|
|||
targetVer: '1.0.0-indev.0'
|
||||
},
|
||||
run: async (process) => {
|
||||
const win = await process.loadLibrary('lib/WindowManager').then((wm: any) => {
|
||||
const win = await process.loadLibrary('lib/WindowManager').then(wm => {
|
||||
return wm.createWindow({
|
||||
title: 'Browser',
|
||||
icon,
|
||||
|
|
@ -75,7 +75,7 @@ const BrowserApp: Process = {
|
|||
iframe: HTMLIFrameElement = document.createElement('iframe')
|
||||
|
||||
constructor (url: string) {
|
||||
this.iframe.src = `/service/${xor.encode(url) as string}`
|
||||
this.iframe.src = `/service/${xor.encode(url)}`
|
||||
this.iframe.style.display = 'none'
|
||||
|
||||
this.header.innerHTML = `
|
||||
|
|
@ -97,7 +97,7 @@ const BrowserApp: Process = {
|
|||
if (this === tabManager.activeTab) {
|
||||
(win.content.querySelector('.toggle') as HTMLElement).innerHTML = 'toggle_on'
|
||||
}
|
||||
this.iframe.src = `/service/${xor.encode(win.content.querySelector('input').value) as string}`
|
||||
this.iframe.src = `/service/${xor.encode(win.content.querySelector('input')?.value ?? '')}`
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -167,7 +167,7 @@ const BrowserApp: Process = {
|
|||
|
||||
win.content.querySelector('.inp')?.addEventListener('keydown', (event: KeyboardEvent) => {
|
||||
if (event.key === 'Enter') {
|
||||
tabManager.activeTab.iframe.src = tabManager.activeTab.proxy ? `/service/${xor.encode((win.content.querySelector('.inp') as HTMLInputElement).value) as string}` : (win.content.querySelector('.inp') as HTMLInputElement).value
|
||||
tabManager.activeTab.iframe.src = tabManager.activeTab.proxy ? `/service/${xor.encode((win.content.querySelector('.inp') as HTMLInputElement).value)}` : (win.content.querySelector('.inp') as HTMLInputElement).value
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ const Files: Process = {
|
|||
(win.content.querySelector('.folder') as HTMLElement).onclick = async () => {
|
||||
const title = prompt('Enter folder name')
|
||||
if (title != null) {
|
||||
await fs.mkdir(`${dir}/${title}`, '')
|
||||
await fs.mkdir(`${dir}/${title}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -69,7 +69,7 @@ const Files: Process = {
|
|||
const genIcon = (): string => {
|
||||
return `<span class="material-symbols-rounded">${(MIMETypes[file.split('.')[1]] === undefined ? 'draft' : MIMETypes[file.split('.')[1]].icon) as string}</span>`
|
||||
}
|
||||
const icon = fileStat.isDirectory() as boolean ? '<span class="material-symbols-rounded">folder</span>' : genIcon()
|
||||
const icon = fileStat.isDirectory() ? '<span class="material-symbols-rounded">folder</span>' : genIcon()
|
||||
|
||||
element.innerHTML += `${icon} <span style="flex:1;">${file}</span><span class="material-symbols-rounded delete">delete_forever</span><span class="material-symbols-rounded rename">edit</span>`;
|
||||
(element.querySelector('.rename') as HTMLElement).onclick = async () => {
|
||||
|
|
@ -79,7 +79,7 @@ const Files: Process = {
|
|||
}
|
||||
}
|
||||
(element.querySelector('.delete') as HTMLElement).onclick = async () => {
|
||||
if (fileStat.isDirectory() as boolean) {
|
||||
if (fileStat.isDirectory()) {
|
||||
await fs.rmdir(dir + seperator + file)
|
||||
} else {
|
||||
await fs.unlink(dir + seperator + file)
|
||||
|
|
@ -101,7 +101,7 @@ const Files: Process = {
|
|||
}
|
||||
|
||||
element.ondblclick = async () => {
|
||||
if (fileStat.isDirectory() as boolean) {
|
||||
if (fileStat.isDirectory()) {
|
||||
await setDir(dir + seperator + file)
|
||||
} else {
|
||||
await run(dir + seperator + file)
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ const Info: Process = {
|
|||
}).text(`v${String(process.sysInfo.version)}`),
|
||||
new HTML('br'),
|
||||
new HTML('a').attr({
|
||||
href: ''
|
||||
href: 'https://discord.gg/nj93ywpyRy'
|
||||
}).append(
|
||||
new HTML('img').attr({
|
||||
src: badge,
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ const Store: Process = {
|
|||
install(app.url)
|
||||
}
|
||||
}
|
||||
})
|
||||
}).catch((e: any) => console.error(e))
|
||||
}).catch((e: any) => console.error(e))
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Library } from '../../types'
|
||||
|
||||
const MIME: Library = {
|
||||
const MIMETypes: Library = {
|
||||
config: {
|
||||
name: 'MIMETypes',
|
||||
type: 'library',
|
||||
|
|
@ -83,4 +83,4 @@ const MIME: Library = {
|
|||
}
|
||||
}
|
||||
|
||||
export default MIME
|
||||
export default MIMETypes
|
||||
|
|
|
|||
|
|
@ -1,42 +1,11 @@
|
|||
import Kernel from '../../kernel'
|
||||
import ProcessLib from '../../structures/ProcessLib'
|
||||
import { Library } from '../../types'
|
||||
import { Directory, Errors, File, Library, Permission, Stats } from '../../types'
|
||||
|
||||
console.debug = (...args: any[]) => {
|
||||
console.log('[VirtualFS]', ...args)
|
||||
}
|
||||
|
||||
export enum Errors {
|
||||
ENOENT = 'ENOENT',
|
||||
EISDIR = 'EISDIR',
|
||||
EEXIST = 'EEXIST',
|
||||
EPERM = 'EPERM',
|
||||
ENOTDIR = 'ENOTDIR',
|
||||
EACCES = 'EACCES'
|
||||
}
|
||||
|
||||
export enum Permission {
|
||||
USER,
|
||||
ELEVATED,
|
||||
SYSTEM
|
||||
}
|
||||
|
||||
export interface Directory {
|
||||
type: 'directory'
|
||||
permission: Permission
|
||||
deleteable: boolean
|
||||
children: {
|
||||
[key: string]: Directory | File
|
||||
}
|
||||
}
|
||||
|
||||
export interface File {
|
||||
type: 'file'
|
||||
permission: Permission
|
||||
deleteable: boolean
|
||||
content: Buffer
|
||||
}
|
||||
|
||||
export const defaultFS: { root: Directory } = {
|
||||
root: {
|
||||
type: 'directory',
|
||||
|
|
@ -452,7 +421,7 @@ const VirtualFS: Library = {
|
|||
console.debug(`readdir ${path}`)
|
||||
return result
|
||||
},
|
||||
stat: async (path: string): Promise<{ isDirectory: () => boolean, isFile: () => boolean }> => {
|
||||
stat: async (path: string): Promise<Stats> => {
|
||||
const { current } = await navigatePath(path)
|
||||
|
||||
console.debug(`stat ${path}`)
|
||||
|
|
|
|||
|
|
@ -8,9 +8,6 @@ const XOR: Library = {
|
|||
},
|
||||
init: (l, k, p) => {},
|
||||
data: {
|
||||
randomMax: 100,
|
||||
randomMin: -100,
|
||||
|
||||
encode: (str: string): string => {
|
||||
return encodeURIComponent(
|
||||
str
|
||||
|
|
|
|||
96
src/types.ts
96
src/types.ts
|
|
@ -1,7 +1,10 @@
|
|||
import HTML from './HTML'
|
||||
import Kernel from './kernel'
|
||||
import FlowWindow from './structures/FlowWindow'
|
||||
import LibraryLib from './structures/LibraryLib'
|
||||
import ProcessLib from './structures/ProcessLib'
|
||||
import Components from './system/lib/Components'
|
||||
import MIMETypes from './system/lib/MIMETypes'
|
||||
|
||||
export interface AppClosedEvent extends CustomEvent {
|
||||
detail: {
|
||||
|
|
@ -9,6 +12,41 @@ export interface AppClosedEvent extends CustomEvent {
|
|||
}
|
||||
}
|
||||
|
||||
export enum Errors {
|
||||
ENOENT = 'ENOENT',
|
||||
EISDIR = 'EISDIR',
|
||||
EEXIST = 'EEXIST',
|
||||
EPERM = 'EPERM',
|
||||
ENOTDIR = 'ENOTDIR',
|
||||
EACCES = 'EACCES'
|
||||
}
|
||||
|
||||
export enum Permission {
|
||||
USER,
|
||||
ELEVATED,
|
||||
SYSTEM
|
||||
}
|
||||
|
||||
export interface Directory {
|
||||
type: 'directory'
|
||||
permission: Permission
|
||||
deleteable: boolean
|
||||
children: {
|
||||
[key: string]: Directory | File
|
||||
}
|
||||
}
|
||||
|
||||
export interface File {
|
||||
type: 'file'
|
||||
permission: Permission
|
||||
deleteable: boolean
|
||||
content: Buffer
|
||||
}
|
||||
|
||||
export interface FileSystemObject {
|
||||
root: Directory
|
||||
}
|
||||
|
||||
export interface AppOpenedEvent extends CustomEvent {
|
||||
detail: {
|
||||
proc: Process
|
||||
|
|
@ -89,3 +127,61 @@ export interface KernelConfig {
|
|||
SERVER: string
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export interface Stats {
|
||||
isDirectory: () => boolean
|
||||
isFile: () => boolean
|
||||
}
|
||||
|
||||
export interface FileSystem {
|
||||
unlink: (path: string) => Promise<void>
|
||||
readFile: (path: string) => Promise<Buffer>
|
||||
writeFile: (path: string, content: string | Buffer) => Promise<void>
|
||||
mkdir: (path: string) => Promise<void>
|
||||
rmdir: (path: string) => Promise<void>
|
||||
readdir: (path: string) => Promise<string[]>
|
||||
stat: (path: string) => Promise<Stats>
|
||||
rename: (oldPath: string, newPath: string) => Promise<void>
|
||||
exists: (path: string) => Promise<boolean>
|
||||
}
|
||||
|
||||
export interface ModalData {
|
||||
value: boolean
|
||||
win: FlowWindow
|
||||
}
|
||||
export interface WindowManager {
|
||||
windowArea: HTML
|
||||
windows: FlowWindow[]
|
||||
getHighestZIndex: () => number
|
||||
createWindow: (config: FlowWindowConfig, process: ProcessLib) => FlowWindow
|
||||
createModal: (title: string, text: string, process: ProcessLib) => Promise<ModalData>
|
||||
}
|
||||
|
||||
export interface Launcher {
|
||||
element: HTML
|
||||
toggle: () => void
|
||||
}
|
||||
|
||||
export interface XOR {
|
||||
encode: (str: string) => string
|
||||
decode: (str: string) => string
|
||||
}
|
||||
|
||||
export interface StatusBar {
|
||||
element: HTML
|
||||
updateBatteryIcon: (battery: any) => void
|
||||
updateIcon: (ms: number) => void
|
||||
}
|
||||
|
||||
export type LoadedLibrary<T> =
|
||||
T extends 'lib/VirtualFS' ? FileSystem :
|
||||
T extends 'lib/WindowManager' ? WindowManager :
|
||||
T extends 'lib/HTML' ? typeof HTML :
|
||||
T extends 'lib/Launcher' ? Launcher :
|
||||
T extends 'lib/XOR' ? XOR :
|
||||
T extends 'lib/StatusBar' ? StatusBar :
|
||||
T extends 'lib/MIMETypes' ? typeof MIMETypes.data :
|
||||
T extends 'lib/Components' ? typeof Components.data :
|
||||
any
|
||||
|
||||
export type LibraryPath = 'lib/VirtualFS' | 'lib/WindowManager' | string
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue