{
+ const win = window.wm.createWindow({
+ title: this.meta.name,
+ icon: this.meta.icon,
+ width: 500,
+ height: 700
+ })
+
+ win.content.style.background = 'var(--base)'
+
+ const config = await window.config()
+
+ fetch(config.SERVER_URL + '/apps/list/')
+ .then(async (res) => await res.json())
+ .then(handle)
+ .catch(e => console.error(e))
+
+ function handle (repos: RepoData[]): void {
+ win.content.innerHTML = `
+
+ `
+
+ repos.forEach((repo) => {
+ (win.content.querySelector('.repos') as HTMLElement).innerHTML += `
+
+
+
${sanitize(repo.name)}
+ ${sanitize(repo.id)}
+
+
+
+
+ `
+
+ repo.apps.forEach((app) => {
+ (win.content.querySelector(`div[data-repo-id="${sanitize(repo.id)}"] > .apps`) as HTMLElement).innerHTML += `
+
+
})
+
+
${sanitize(app.name)}
+
+ ${sanitize(app.pkg)}
+ download
+
+
+
+ `
+
+ window.fs.exists(`/Applications/${app.url.split('/').at(-1) as string}`, (exists) => {
+ if (exists) {
+ (win.content.querySelector(`div[data-pkg="${sanitize(app.pkg)}"] div > .material-symbols-rounded`) as HTMLElement).innerHTML = 'delete';
+
+ (win.content.querySelector(`div[data-pkg="${sanitize(app.pkg)}"] div > .material-symbols-rounded`) as HTMLElement).onclick = () => {
+ window.fs.unlink(`/Applications/${app.url.split('/').at(-1) as string}`, () => {
+ window.location.reload()
+ })
+ }
+ } else {
+ (win.content.querySelector(`div[data-pkg="${sanitize(app.pkg)}"] div > .material-symbols-rounded`) as HTMLElement).onclick = () => {
+ install(app.url)
+ }
+ }
+ });
+
+ (win.content.querySelector(`div[data-pkg="${sanitize(app.pkg)}"] div > .material-symbols-rounded`) as HTMLElement).onclick = () => {
+ install(app.url)
+ }
+ })
+ })
+ }
+
+ function install (url: string): void {
+ fetch(url).then(async (res) => await res.text())
+ .then((data) => {
+ window.fs.exists('/Applications', (exists) => {
+ if (!exists) window.fs.promises.mkdir('/Applications').catch(console.error)
+
+ window.fs.promises.writeFile(`/Applications/${url.split('/').at(-1) as string}`, data).then(() => window.location.reload()).catch(console.error)
+ })
+ }).catch(console.error)
+ }
+
+ return win
+ }
+}
diff --git a/src/instances/Flow.ts b/src/instances/Flow.ts
index 957ac8c..322e003 100644
--- a/src/instances/Flow.ts
+++ b/src/instances/Flow.ts
@@ -1,15 +1,17 @@
import { LoadedApp, LoadedPlugin } from '../types'
+import nullIcon from '../assets/icons/application-default-icon.svg'
class Flow {
apps: LoadedApp[] = []
appList: string[] = [
- 'settings',
- 'music',
- 'files',
- 'editor',
- 'info',
- 'manager',
- 'browser'
+ '../builtin/apps/settings.ts',
+ '../builtin/apps/music.ts',
+ '../builtin/apps/files.ts',
+ '../builtin/apps/editor.ts',
+ '../builtin/apps/info.ts',
+ '../builtin/apps/manager.ts',
+ '../builtin/apps/browser.ts',
+ '../builtin/apps/store.ts'
]
plugins: LoadedPlugin[] = []
@@ -22,15 +24,22 @@ class Flow {
window.preloader.setPending('apps')
window.preloader.setStatus('importing default apps...')
+ await (await window.fs.promises.readdir('/Applications')).forEach((file) => {
+ window.fs.promises.readFile('/Applications/' + file).then(content => {
+ this.appList.push(`data:text/javascript;base64,${btoa(content.toString())}`)
+ }).catch(console.error)
+ })
+
for (const appPath of this.appList) {
window.preloader.setStatus(`importing default apps\n${appPath}`)
- const { default: ImportedApp } = await import(`../builtin/apps/${appPath}.ts`).catch(async (e: Error) => {
+ const { default: ImportedApp } = await import(`${appPath}`).catch(async (e: Error) => {
console.error(e)
await window.preloader.setError('apps')
window.preloader.setStatus(`unable to import ${appPath}\n${e.name}: ${e.message}`)
})
const app = new ImportedApp()
app.builtin = true
+ app.meta.icon = app.meta.icon ?? nullIcon
this.addApp(app)
}
diff --git a/src/structures/FlowWindow.ts b/src/structures/FlowWindow.ts
index 7124116..97fb99d 100644
--- a/src/structures/FlowWindow.ts
+++ b/src/structures/FlowWindow.ts
@@ -2,6 +2,7 @@ import { v4 as uuid } from 'uuid'
import WindowManager from '../instances/WindowManager'
import { FlowWindowConfig } from '../types'
import { sanitize } from '../utils'
+import nullIcon from '../assets/icons/application-default-icon.svg'
/**
* Makes an element draggable.
@@ -109,9 +110,9 @@ class FlowWindow {
this.element.style.height = `${config.height ?? 200}px`
this.header = document.createElement('window-header')
- this.header.innerHTML = `
${sanitize(config.title)}
minimizeclose`
+ this.header.innerHTML = `
${sanitize(config.title)}
minimizeclose`
if (config.canResize) {
- this.header.innerHTML = `
${sanitize(config.title)}
minimizesquareclose`
+ this.header.innerHTML = `
${sanitize(config.title)}
minimizesquareclose`
}
(this.header.querySelector('#close') as HTMLElement).onclick = () => {
diff --git a/src/types.ts b/src/types.ts
index 03317df..d85901c 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -30,6 +30,11 @@ export interface PluginMeta extends BaseMeta {
icon?: string
}
+export interface RepoAppMeta extends BaseMeta {
+ icon?: string
+ url: string
+}
+
export interface Apps {
[key: string]: App
}
@@ -79,3 +84,9 @@ export interface FlowConfig {
USERNAME: string
'24HOUR_CLOCK': boolean
}
+
+export interface RepoData {
+ name: string
+ id: string
+ apps: RepoAppMeta[]
+}
diff --git a/vite.config.js b/vite.config.js
index ed22f22..ff70d3f 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -1,10 +1,8 @@
import { defineConfig } from 'vite'
import { nodePolyfills } from 'vite-plugin-node-polyfills'
-import dynamicImport from 'vite-plugin-dynamic-import'
export default defineConfig({
plugins: [
- nodePolyfills(),
- dynamicImport()
+ nodePolyfills()
]
})