Merge pull request #147 from Flow-Works/feat/use-new-apps-list
[➕] Support for `/apps/list` added
This commit is contained in:
commit
e0c3253490
3 changed files with 214 additions and 57 deletions
30
src/HTML.ts
30
src/HTML.ts
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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((repo) => {
|
|
||||||
(win.content.querySelector('.repos') as HTMLElement).innerHTML += `
|
|
||||||
<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;">
|
|
||||||
<div style="flex: 1;">
|
|
||||||
<h2 style="margin: 0;margin-bottom: 10px;">${sanitize(repo.name)}</h2>
|
|
||||||
<code style="font-family: monospace;">${sanitize(repo.id)}</code>
|
|
||||||
</div>
|
|
||||||
<br/>
|
|
||||||
<div class="apps"></div>
|
|
||||||
</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) => {
|
repo.apps.forEach((app) => {
|
||||||
(win.content.querySelector(`div[data-repo-id="${sanitize(repo.id)}"] > .apps`) as HTMLElement).innerHTML += `
|
fs.exists(`/home/Applications/${app.url.split('/').at(-1)?.replace('.js', '.app') as string}`)
|
||||||
<div data-pkg="${sanitize(app.name)}" style="display: flex;gap: 20px;">
|
.then((exists: boolean) => {
|
||||||
<img src="${sanitize(app.icon ?? nullIcon)}" height="59.5px" style="border-radius: var(--app-radius);">
|
const button = Button.new().style({
|
||||||
<div>
|
display: 'flex',
|
||||||
<h3 style="margin: 0;margin-bottom: 10px;">${sanitize(app.name)}</h3>
|
gap: '5px',
|
||||||
<div style="display: flex;gap:5px;align-items: center;">
|
'align-items': 'center'
|
||||||
<code style="font-family: monospace;">${sanitize(app.targetVer)}</code>
|
}).text('Uninstall')
|
||||||
<span class="material-symbols-rounded">download</span>
|
.prepend(Icon.new('delete'))
|
||||||
</div>
|
.on('click', () => uninstall(app.url))
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
|
|
||||||
fs.exists(`/opt/apps/${app.url.split('/').at(-1) as string}`).then((exists: boolean) => {
|
|
||||||
fs.exists(`/home/Applications/${app.url.split('/').at(-1)?.replace('.js', '.app') as string}`).then((exists2: boolean) => {
|
|
||||||
if (exists) {
|
if (exists) {
|
||||||
(win.content.querySelector(`div[data-pkg="${sanitize(app.name)}"] div > .material-symbols-rounded`) as HTMLElement).innerHTML = 'delete';
|
fetch(`${process.kernel.config.SERVER as string}/cors?url=${app.url}`)
|
||||||
|
.then(async (res) => await res.text())
|
||||||
(win.content.querySelector(`div[data-pkg="${sanitize(app.name)}"] div > .material-symbols-rounded`) as HTMLElement).onclick = async () => {
|
.then(async (data) => {
|
||||||
await fs.unlink(`/home/Applications/${app.url.split('/').at(-1)?.replace('.js', '.app') as string}`)
|
const local = Buffer.from(await fs.readFile(`/opt/apps/${app.url.split('/').at(-1) as string}`)).toString()
|
||||||
await fs.unlink(`/opt/apps/${app.url.split('/').at(-1) as string}`)
|
if (local !== data) {
|
||||||
|
button.text('Update')
|
||||||
|
.prepend(Icon.new('update'))
|
||||||
|
.on('click', () => install(app.url))
|
||||||
}
|
}
|
||||||
|
}).catch(e => console.error(e))
|
||||||
} else {
|
} else {
|
||||||
(win.content.querySelector(`div[data-pkg="${sanitize(app.name)}"] div > .material-symbols-rounded`) as HTMLElement).onclick = () => {
|
button.text('Install')
|
||||||
install(app.url)
|
.prepend(Icon.new('download'))
|
||||||
|
.on('click', () => install(app.url))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}).catch((e: any) => console.error(e))
|
new HTML('div')
|
||||||
}).catch((e: any) => console.error(e))
|
.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) => {
|
||||||
|
fetch(`${process.kernel.config.SERVER as string}/cors/?url=${repo}`)
|
||||||
|
.then(async res => await res.json())
|
||||||
|
.then((repo: RepoData) => {
|
||||||
|
const icon = Icon.new('arrow_drop_up')
|
||||||
|
new HTML('h2').text(repo.name).style({
|
||||||
|
margin: '0',
|
||||||
|
padding: '10px',
|
||||||
|
display: 'flex',
|
||||||
|
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))
|
||||||
|
|
||||||
|
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'))
|
||||||
|
.un('click', () => uninstall(app.url))
|
||||||
|
.on('click', () => install(app.url))
|
||||||
|
}
|
||||||
|
}).catch(e => console.error(e))
|
||||||
|
} else {
|
||||||
|
button.text('Install')
|
||||||
|
.prepend(Icon.new('download'))
|
||||||
|
.un('click', () => uninstall(app.url))
|
||||||
|
.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
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.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))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue