diff --git a/src/HTML.ts b/src/HTML.ts
index 5c766cc..90f6880 100644
--- a/src/HTML.ts
+++ b/src/HTML.ts
@@ -196,6 +196,24 @@ export default class HTML {
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.
* @param elements The elements to append.
@@ -208,6 +226,18 @@ export default class HTML {
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.
* @returns HTML
diff --git a/src/system/apps/Store.ts b/src/system/apps/Store.ts
index 7dd8176..8d1d64a 100644
--- a/src/system/apps/Store.ts
+++ b/src/system/apps/Store.ts
@@ -1,7 +1,5 @@
import { Process, RepoData } from '../../types'
import icon from '../../assets/icons/softwarecenter.svg'
-
-import { sanitize } from '../../utils'
import nullIcon from '../../assets/icons/application-default-icon.svg'
const Store: Process = {
@@ -22,68 +20,176 @@ const Store: Process = {
})
const fs = await process.loadLibrary('lib/VirtualFS')
-
- win.content.style.background = 'var(--base)'
+ const HTML = await process.loadLibrary('lib/HTML')
+ const { Button, Icon } = await process.loadLibrary('lib/Components')
fetch(`${process.kernel.config.SERVER as string}/apps/list/`)
.then(async (res) => await res.json())
.then(handle)
.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 {
- win.content.innerHTML = `
-
- `
+ async function updateList (): Promise {
+ const res = fetch(`${process.kernel.config.SERVER as string}/apps/list/`)
+ 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) => {
- (win.content.querySelector('.repos') as HTMLElement).innerHTML += `
-
-
-
${sanitize(repo.name)}
- ${sanitize(repo.id)}
-
-
-
-
- `
+ 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))
- repo.apps.forEach((app) => {
- (win.content.querySelector(`div[data-repo-id="${sanitize(repo.id)}"] > .apps`) as HTMLElement).innerHTML += `
-
-
})
-
-
${sanitize(app.name)}
-
- ${sanitize(app.targetVer)}
- download
-
-
-
- `
+ 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))
+ }
- 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) {
- (win.content.querySelector(`div[data-pkg="${sanitize(app.name)}"] div > .material-symbols-rounded`) as HTMLElement).innerHTML = 'delete';
-
- (win.content.querySelector(`div[data-pkg="${sanitize(app.name)}"] div > .material-symbols-rounded`) as HTMLElement).onclick = async () => {
- await fs.unlink(`/home/Applications/${app.url.split('/').at(-1)?.replace('.js', '.app') as string}`)
- await fs.unlink(`/opt/apps/${app.url.split('/').at(-1) as string}`)
- }
- } else {
- (win.content.querySelector(`div[data-pkg="${sanitize(app.name)}"] div > .material-symbols-rounded`) as HTMLElement).onclick = () => {
- install(app.url)
- }
- }
- }).catch((e: any) => console.error(e))
- }).catch((e: any) => console.error(e))
- })
+ 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) => {
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 updateList()
}).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))
+ }
}
}
diff --git a/src/system/lib/Components.ts b/src/system/lib/Components.ts
index 0a5a430..3647015 100644
--- a/src/system/lib/Components.ts
+++ b/src/system/lib/Components.ts
@@ -31,12 +31,23 @@ const Components: Library = {
const button = new HTML('button')
button.style({
'border-radius': '5px',
- padding: '2.5px',
- background: 'transparent',
- border: '1px solid var(--surface-0)'
+ padding: '2.5px 5px',
+ background: 'var(--base)',
+ border: '1px solid var(--surface-1)'
})
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
+ })
+ }
}
}
}