# The Hitchhiker's Guide to AnuraOS App Development See [this document](./templates/template.app/README.md) for instructions on how to set up a basic iFrame app. ## AnuraOS Apps AnuraOS apps are simple creatures. They live inside folders with the suffix `.app` and the resources specific to each app are contained within that folder. ### Manifest Each app contains a `manifest.json`, which defines the functionality of the app. See [`manifest.json.example`](./manifest.json.example). - `name`: `String` - Program name. Required. - `type`: `String` - Program type. "auto" or "manual". Required. * "manual": Evaluates at top-window level. Highly discouraged. * "auto": Evaluates within a contained iframe. - `package`: `String` - Package name (structured class-like, `organization.programname`). Required. - `index`: `String` - Path (from app directory) to the index HTML file. Required if `type` is `"auto"` - the iframe source will be set to this. - `icon`: `String` - Path (from app directory) to the application's icon. Optional but highly recommended. Anura will display this icon throughout the DE. - `background`: Background color of iframe while it is loading. Optional. - `handler`: `String` - Path (from app directory) to a file containing JavaScript to execute at the top-level document. Required if `type` is `"manual"`, ignored otherwise - the top-level document will execute this file as JavaScript. - `useIdbWrapper`: `Boolean` - Use the IndexedDB wrapper, which prevents the app from making accidental modifications to other app's indexeddb stores or anura's own stores. Defaults to `false`. Optional. - `wininfo`: `Object {title, width, height, resizable}` - Required if `type` is `"auto"`. - `wininfo.title`: `String` - The title of the program. Defaults to "". Optional. - `wininfo.width`: `String` - The default width, in pixels, of the program. Defaults to "1000px". Optional. - `wininfo.height`: `String` - The default height, in pixels, of the program. Defaults to "500px". Optional. - `wininfo.resizable`: `Boolean` - Allow users to resize the window for your application. Defaults to `true`. Optional. ### Tips and Tricks - In iframed apps, Anura still gives you full access to the APIs through the `anura` object and also gives you access to your app instance and the Window in the Window Manager. You can access the WMWindow using `instanceWindow` and access your anura app instance using `instance` in your javascript. This could be used to manipulate the window or invoke actions on your app instance. An example is shown below where a back arrow is drawn on the window decorations in the marketplace app. ```js const back = html` `; instanceWindow.content.style.position = "absolute"; instanceWindow.content.style.height = "100%"; const titlebar = Array.from(instanceWindow.element.children).filter((e) => e.classList.contains("title"), )[0]; titlebar.style.backgroundColor = "rgba(0, 0, 0, 0)"; titlebar.insertBefore(back, titlebar.children[1]); ``` - All Anura windows allow you to hook into their events. This can be achieved via a callback or a event listener. You can see this in action in the window managment api and in multiple system apps. ```js let win = anura.wm.create(instance, { title: "Example Window", width: "1280px", height: "720px", }); win.addEventListener("focus", (event) => {}); win.addEventListener("resize", (event) => { console.log(event.data.height); console.log(event.data.width); }); win.addEventListener("close", (event) => {}); win.addEventListener("maximize", () => {}); win.addEventListener("unmaximize", () => {}); win.addEventListener("snap", (event) => { console.log(event.data.snappedDirection); }); // Same things but callbacks instead win.onfocus: () => void; win.onresize: (width, height) => void; win.onclose: () => void; win.onmaximize: () => void; win.onsnap: (snapDirection) => void; win.onunmaximize: () => void; ``` - When an app is installed from the Marketplace or `libstore`, the current version and the repo that the app was downloaded from is injected into the manifest of your app. This could be used for update logic in your apps. An example is shown below of basic app updating logic, where it checks to see if libstore downloaded the app. If it did, then it checks if the version is the same on the repo it was downloaded from and updates if not. ```js if (instance.manifest.marketplace) { let libstore = await anura.import("anura.libstore@2.0.0"); marketplace = new libstore.Store(anura.net, { onError: (appName, error) => { anura.notifications.add({ title: "Example Application", description: `Example Application encountered an error while updating.`, timeout: 5000, }); }, onDownloadStart: (appName) => { anura.notifications.add({ title: "Example Application", description: `Example Application started downloading an update.`, timeout: 5000, }); }, onDepInstallStart: (appName, libName) => { anura.notifications.add({ title: "Example Application", description: `Example Application started updating dependency ${libName}.`, timeout: 5000, }); }, onComplete: (appName) => { anura.notifications.add({ title: "Example Application", description: `Example Application finished updating.`, timeout: 5000, }); }, }); const marketplaceRepo = await marketplace.getRepo( "Update Repo", instance.manifest.marketplace.repo, ); let repoApp; if (repo.version === "legacy") { repoApp = marketplaceRepo.getApp(instance.name); } else { repoApp = marketplaceRepo.getApp(instance.package); } if (instance.manifest.version !== repoApp.version) { repo.installApp(instance.package); } } ``` ## Including Dreamland dreamland.js is a reactive JSX-inspired rendering library with no virtual dom and no build step. You can find the source code [here](https://github.com/MercuryWorkshop/dreamlandjs) and the documentation [here](https://dreamland.js.org/). AnuraOS itself uses dreamland for the desktop environment and core system apps, and you can use it in your apps as well. To include dreamland in your app without a bundler, you can add the following to the `head` section of your `index.html` file: ```html ``` While dreamland itself includes a `$store` function for preserving state between page reloads, this function is not available by default. Instead, you can use the [`anura.persistence`](#libpersist) library to build a `$store` function bound to your app instance. ```jsx const { buildLoader } = await anura.import("anura.persistence"); const loader = buildLoader(anura); await loader.locate(); const persistence = await loader.build(instance); const $store = persistence.createStoreFn($state, instanceWindow); let persistentState = await $store( { count: 0, }, "state", ); let externalState = $state({ count: 0, }); function App() { return (