Merge pull request #147 from Flow-Works/feat/use-new-apps-list

[] Support for `/apps/list` added
This commit is contained in:
ThinLiquid 2024-01-18 11:41:35 +00:00 committed by GitHub
commit e0c3253490
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 214 additions and 57 deletions

View file

@ -196,6 +196,24 @@ export default class HTML {
return this return this
} }
/**
* Prepend an element. Typically used as a `.prepend(new HTML(...))` call.
* @param elem The element to prepend.
* @returns HTML
*/
prepend (elem: string | HTMLElement | HTML): HTML {
if (elem instanceof HTMLElement) {
this.elm.prepend(elem)
} else if (elem instanceof HTML) {
this.elm.prepend(elem.elm)
} else if (typeof elem === 'string') {
const newElem = document.createElement(elem)
this.elm.prepend(newElem)
return new HTML(newElem.tagName)
}
return this
}
/** /**
* Append multiple elements. Typically used as a `.appendMany(new HTML(...), new HTML(...)` call. * Append multiple elements. Typically used as a `.appendMany(new HTML(...), new HTML(...)` call.
* @param elements The elements to append. * @param elements The elements to append.
@ -208,6 +226,18 @@ export default class HTML {
return this return this
} }
/**
* Prepend multiple elements. Typically used as a `.prependMany(new HTML(...), new HTML(...)` call.
* @param elements The elements to prepend.
* @returns HTML
*/
prependMany (...elements: any[]): HTML {
for (const elem of elements) {
this.prepend(elem)
}
return this
}
/** /**
* Clear the innerHTML of the element. * Clear the innerHTML of the element.
* @returns HTML * @returns HTML

View file

@ -1,7 +1,5 @@
import { Process, RepoData } from '../../types' import { Process, RepoData } from '../../types'
import icon from '../../assets/icons/softwarecenter.svg' import icon from '../../assets/icons/softwarecenter.svg'
import { sanitize } from '../../utils'
import nullIcon from '../../assets/icons/application-default-icon.svg' import nullIcon from '../../assets/icons/application-default-icon.svg'
const Store: Process = { const Store: Process = {
@ -22,68 +20,176 @@ const Store: Process = {
}) })
const fs = await process.loadLibrary('lib/VirtualFS') const fs = await process.loadLibrary('lib/VirtualFS')
const HTML = await process.loadLibrary('lib/HTML')
win.content.style.background = 'var(--base)' const { Button, Icon } = await process.loadLibrary('lib/Components')
fetch(`${process.kernel.config.SERVER as string}/apps/list/`) fetch(`${process.kernel.config.SERVER as string}/apps/list/`)
.then(async (res) => await res.json()) .then(async (res) => await res.json())
.then(handle) .then(handle)
.catch(e => console.error(e)) .catch(e => console.error(e))
document.addEventListener('fs_update', () => {
fetch(`${process.kernel.config.SERVER as string}/apps/list/`)
.then(async (res) => await res.json())
.then(handle)
.catch(e => console.error(e))
})
function handle (repos: RepoData[]): void { async function updateList (): Promise<void> {
win.content.innerHTML = ` const res = fetch(`${process.kernel.config.SERVER as string}/apps/list/`)
<div class="repos" style="display: flex;flex-direction: column;gap: 10px;"></div> const repos = await (await res).json()
` const div = new HTML(win.content).qs('div')
repos.forEach(async (repo: string, index: number) => {
const repoDiv = div?.qsa('div')?.[index]
repoDiv?.html('')
fetch(`${process.kernel.config.SERVER as string}/cors/?url=${repo}`)
.then(async res => await res.json())
.then((repo: RepoData) => {
repo.apps.forEach((app) => {
fs.exists(`/home/Applications/${app.url.split('/').at(-1)?.replace('.js', '.app') as string}`)
.then((exists: boolean) => {
const button = Button.new().style({
display: 'flex',
gap: '5px',
'align-items': 'center'
}).text('Uninstall')
.prepend(Icon.new('delete'))
.on('click', () => uninstall(app.url))
if (exists) {
fetch(`${process.kernel.config.SERVER as string}/cors?url=${app.url}`)
.then(async (res) => await res.text())
.then(async (data) => {
const local = Buffer.from(await fs.readFile(`/opt/apps/${app.url.split('/').at(-1) as string}`)).toString()
if (local !== data) {
button.text('Update')
.prepend(Icon.new('update'))
.on('click', () => install(app.url))
}
}).catch(e => console.error(e))
} else {
button.text('Install')
.prepend(Icon.new('download'))
.on('click', () => install(app.url))
}
new HTML('div')
.style({
display: 'flex',
'flex-direction': 'row',
gap: '10px',
padding: '10px',
background: 'var(--base)',
'border-radius': '10px'
})
.appendMany(
new HTML('img').attr({
src: app.icon ?? nullIcon
}).style({
'aspect-ratio': '1 / 1',
width: '60px',
height: '60px'
}),
new HTML('div').appendMany(
new HTML('h3').style({
margin: '0'
}).text(app.name),
button
)
)
// @ts-expect-error
.appendTo(repoDiv)
}).catch(e => console.error(e))
})
})
.catch(e => console.error(e))
})
}
function handle (repos: string[]): void {
win.content.innerHTML = ''
const div = new HTML('div').appendTo(win.content)
repos.forEach((repo) => { repos.forEach((repo) => {
(win.content.querySelector('.repos') as HTMLElement).innerHTML += ` fetch(`${process.kernel.config.SERVER as string}/cors/?url=${repo}`)
<div data-repo-id="${sanitize(repo.id)}" style="display: flex;flex-direction: column;gap: 10px;background: var(--surface-0);padding: 20px;margin: 10px;border-radius: 10px;"> .then(async res => await res.json())
<div style="flex: 1;"> .then((repo: RepoData) => {
<h2 style="margin: 0;margin-bottom: 10px;">${sanitize(repo.name)}</h2> const icon = Icon.new('arrow_drop_up')
<code style="font-family: monospace;">${sanitize(repo.id)}</code> new HTML('h2').text(repo.name).style({
</div> margin: '0',
<br/> padding: '10px',
<div class="apps"></div> display: 'flex',
</div> gap: '5px',
` 'align-items': 'center'
})
.prepend(icon)
.appendTo(div)
.on('click', () => {
repoDiv.style({
height: repoDiv.elm.style.height === '0px' ? 'max-content' : '0'
})
icon.text(`arrow_drop_${repoDiv.elm.style.height === '0px' ? 'up' : 'down'}`)
})
const repoDiv = new HTML('div').appendTo(div).style({
height: '0',
display: 'flex',
'flex-direction': 'column',
gap: '10px',
overflow: 'hidden',
padding: '0 10px'
})
repo.apps.forEach((app) => {
fs.exists(`/home/Applications/${app.url.split('/').at(-1)?.replace('.js', '.app') as string}`)
.then((exists: boolean) => {
const button = Button.new().style({
display: 'flex',
gap: '5px',
'align-items': 'center'
}).text('Uninstall')
.prepend(Icon.new('delete'))
.on('click', () => uninstall(app.url))
repo.apps.forEach((app) => { if (exists) {
(win.content.querySelector(`div[data-repo-id="${sanitize(repo.id)}"] > .apps`) as HTMLElement).innerHTML += ` fetch(`${process.kernel.config.SERVER as string}/cors?url=${app.url}`)
<div data-pkg="${sanitize(app.name)}" style="display: flex;gap: 20px;"> .then(async (res) => await res.text())
<img src="${sanitize(app.icon ?? nullIcon)}" height="59.5px" style="border-radius: var(--app-radius);"> .then(async (data) => {
<div> const local = Buffer.from(await fs.readFile(`/opt/apps/${app.url.split('/').at(-1) as string}`)).toString()
<h3 style="margin: 0;margin-bottom: 10px;">${sanitize(app.name)}</h3> if (local !== data) {
<div style="display: flex;gap:5px;align-items: center;"> button.text('Update')
<code style="font-family: monospace;">${sanitize(app.targetVer)}</code> .prepend(Icon.new('update'))
<span class="material-symbols-rounded">download</span> .un('click', () => uninstall(app.url))
</div> .on('click', () => install(app.url))
</div> }
</div> }).catch(e => console.error(e))
` } else {
button.text('Install')
.prepend(Icon.new('download'))
.un('click', () => uninstall(app.url))
.on('click', () => install(app.url))
}
fs.exists(`/opt/apps/${app.url.split('/').at(-1) as string}`).then((exists: boolean) => { new HTML('div')
fs.exists(`/home/Applications/${app.url.split('/').at(-1)?.replace('.js', '.app') as string}`).then((exists2: boolean) => { .style({
if (exists) { display: 'flex',
(win.content.querySelector(`div[data-pkg="${sanitize(app.name)}"] div > .material-symbols-rounded`) as HTMLElement).innerHTML = 'delete'; 'flex-direction': 'row',
gap: '10px',
(win.content.querySelector(`div[data-pkg="${sanitize(app.name)}"] div > .material-symbols-rounded`) as HTMLElement).onclick = async () => { padding: '10px',
await fs.unlink(`/home/Applications/${app.url.split('/').at(-1)?.replace('.js', '.app') as string}`) background: 'var(--base)',
await fs.unlink(`/opt/apps/${app.url.split('/').at(-1) as string}`) 'border-radius': '10px'
} })
} else { .appendMany(
(win.content.querySelector(`div[data-pkg="${sanitize(app.name)}"] div > .material-symbols-rounded`) as HTMLElement).onclick = () => { new HTML('img').attr({
install(app.url) src: app.icon ?? nullIcon
} }).style({
} 'aspect-ratio': '1 / 1',
}).catch((e: any) => console.error(e)) width: '60px',
}).catch((e: any) => console.error(e)) height: '60px'
}) }),
new HTML('div').appendMany(
new HTML('h3').style({
margin: '0'
}).text(app.name),
button
)
)
.appendTo(repoDiv)
}).catch(e => console.error(e))
})
})
.catch(e => console.error(e))
}) })
} }
@ -92,8 +198,18 @@ const Store: Process = {
.then(async (data) => { .then(async (data) => {
await fs.writeFile(`/home/Applications/${url.split('/').at(-1)?.replace('.js', '.app') as string}`, `apps/${url.split('/').at(-1)?.split('.')[0] as string}`) await fs.writeFile(`/home/Applications/${url.split('/').at(-1)?.replace('.js', '.app') as string}`, `apps/${url.split('/').at(-1)?.split('.')[0] as string}`)
await fs.writeFile(`/opt/apps/${url.split('/').at(-1) as string}`, data) await fs.writeFile(`/opt/apps/${url.split('/').at(-1) as string}`, data)
await updateList()
}).catch(e => console.error(e)) }).catch(e => console.error(e))
} }
function uninstall (url: string): void {
fs.unlink(`/home/Applications/${url.split('/').at(-1)?.replace('.js', '.app') as string}`)
.then(async () => {
await fs.unlink(`/opt/apps/${url.split('/').at(-1) as string}`)
await updateList()
})
.catch(e => console.error(e))
}
} }
} }

View file

@ -31,12 +31,23 @@ const Components: Library = {
const button = new HTML('button') const button = new HTML('button')
button.style({ button.style({
'border-radius': '5px', 'border-radius': '5px',
padding: '2.5px', padding: '2.5px 5px',
background: 'transparent', background: 'var(--base)',
border: '1px solid var(--surface-0)' border: '1px solid var(--surface-1)'
}) })
return button return button
} }
},
Icon: {
new: (icon: string, size = 'inherit') => {
const { HTML } = library
return new HTML('i')
.class('material-symbols-rounded')
.text(icon)
.style({
'font-size': size
})
}
} }
} }
} }