[💥] Major system rewrite
This commit is contained in:
parent
2aaf0475a0
commit
8de338f899
6 changed files with 463 additions and 37 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
89
src/fs.ts
89
src/fs.ts
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
333
src/lib.ts
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue