Add kumos.

This commit is contained in:
天クマ 2026-07-05 23:23:26 -03:00
commit 964bd2e808
18 changed files with 427 additions and 23 deletions

29
static/kumos/apps.mjs Normal file
View file

@ -0,0 +1,29 @@
import { Application, Window, promoteWindow, registerApplication, getScreenRoot, getApplications } from "./kernel.mjs";
const app = new Application('apps', (p) => {
const w = new Window();
w.setTitle("App list");
w.content.style.padding = "1rem";
const list = document.createElement("ul");
getApplications().forEach(app => {
const item = document.createElement("li");
const link = document.createElement("a");
link.innerText = app.name;
link.addEventListener('click', () => app.fn());
item.appendChild(link);
list.appendChild(item);
})
w.content.appendChild(list);
w.close.addEventListener('click', () => {
p.detach();
w.destroy();
});
getScreenRoot().appendChild(w.window);
promoteWindow(w);
})
registerApplication(app);

32
static/kumos/browser.mjs Normal file
View file

@ -0,0 +1,32 @@
import { Application, Window, promoteWindow, registerApplication, getScreenRoot } from "./kernel.mjs";
const app = new Application('browser', (p) => {
const w = new Window();
w.setTitle("Web browser");
const iframe = document.createElement("iframe");
iframe.src = "https://adrianvic.github.io";
iframe.style.height = "100%";
const searchBar = document.createElement("div");
searchBar.style.display = "flex";
const search = document.createElement("input");
search.style.width = "100%";
const searchButton = document.createElement("button");
searchBar.appendChild(search);
searchBar.appendChild(searchButton);
search.type = "text";
iframe.addEventListener('load', () => search.value = iframe.src);
searchButton.innerText = "go";
searchButton.addEventListener('click', () => iframe.src = search.value);
w.content.appendChild(searchBar);
w.content.appendChild(iframe);
getScreenRoot().appendChild(w.window);
promoteWindow(w);
w.close.addEventListener('click', () => {
p.detach();
w.destroy();
})
})
registerApplication(app);

View file

@ -0,0 +1,38 @@
import { Application, Window, promoteWindow, registerApplication, getScreenRoot, getProcList } from "./kernel.mjs";
import { notify } from "./shell.mjs";
function draw(p) {
const w = new Window();
w.setTitle("Guestbook");
w.content.innerHTML = `
<script src="https://iframe.chat/scripts/main.min.js"></script>
<iframe class="windowContent" src="https://iframe.chat/embed?chat=17048300" id="chattable"></iframe>
<script>chattable.initialize({ theme: "moderno" });</script>
`
getScreenRoot().appendChild(w.window);
promoteWindow(w);
w.close.addEventListener('click', () => {
p.detach();
w.destroy();
})
}
function main(p) {
let running = false;
getProcList().forEach(proc => {
if (proc.name == 'guestbook' && proc != p) {
notify("Guestbook", "Guestbook is already running!")
p.detach();
running = true;
}
})
if (!running) {
draw(p);
}
}
const app = new Application('guestbook', main);
registerApplication(app);

125
static/kumos/kernel.mjs Normal file
View file

@ -0,0 +1,125 @@
const language = "en";
const procList = [];
export class Process {
constructor(app) {
this.pid = procList.length == 0 ? 0 : procList[procList.length - 1].pid + 1;
this.name = app.name;
procList.push(this);
app.fn(this);
}
detach() {
procList.splice(procList.indexOf(this), 1);
}
}
export class Application {
constructor(name, fn) {
this.fn = fn;
this.name = name;
}
}
export class Window {
constructor() {
// Window
const w = document.createElement("div");
w.classList.add("window");
this.window = w;
// Header
const h = document.createElement("div");
h.classList.add("windowHeader");
this.header = h;
// Title
const title = document.createElement("p");
title.classList.add("windowHeaderTitle");
this.title = title;
// Close button
const close = document.createElement("button");
close.innerText = "close";
close.classList.add("windowHeaderButton");
close.classList.add("windowHeaderButtonClose");
this.close = close;
// Content
const c = document.createElement("div");
c.classList.add("windowContent");
this.content = c;
// Building
h.appendChild(title);
h.appendChild(close);
w.appendChild(h);
w.appendChild(c);
}
setTitle(title) {
this.title.innerText = title;
}
destroy() {
this.window.remove();
}
}
export function promoteWindow(window) {
const header = window.header;
const elmnt = window.window;
header.style.cursor = 'move';
header.onmousedown = (e) => {
e.preventDefault();
let pos3 = e.clientX;
let pos4 = e.clientY;
const startTop = elmnt.offsetTop;
const startLeft = elmnt.offsetLeft;
document.onmouseup = closeDragElement;
document.onmousemove = elementDrag;
function elementDrag(e) {
e.preventDefault();
const pos1 = pos3 - e.clientX;
const pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
}
function closeDragElement() {
document.onmouseup = null;
document.onmousemove = null;
}
};
}
export function getScreenRoot() {
return document.querySelector('body');
}
const applications = [];
export function getApplications() {
return applications;
}
export function registerApplication(application) {
applications.push(application);
}
export function getLanguage() {
return language;
}
export function getProcList() {
return procList;
}

36
static/kumos/shell.mjs Normal file
View file

@ -0,0 +1,36 @@
import { getScreenRoot, getApplications, Process } from "./kernel.mjs";
import { showNotification } from "../scripts/notification.js";
export function notify(title, text, time) {
showNotification(title, text, time);
}
function runApp(name) {
let launched = false;
getApplications().forEach(app => {
if (app.name == name) {
new Process(app);
launched = true;
}
})
if (!launched) alert("Not found.");
}
function createShell() {
const bar = document.createElement('div');
bar.classList.add("kumosBar");
const run = document.createElement('input');
const runButton = document.createElement('button');
run.type = "text";
runButton.innerText = "Run";
runButton.id = "kumosBarRunButton"
runButton.addEventListener('click', () => runApp(run.value));
bar.appendChild(run);
bar.appendChild(runButton);
getScreenRoot().appendChild(bar);
}
createShell();
showNotification("KumOS Shell", "Welcome to KumOS, everything is work-in-progress! Try running apps to see available packages.", 10000);

View file

@ -0,0 +1,34 @@
import { Application, Window, promoteWindow, registerApplication, getScreenRoot, getProcList } from "./kernel.mjs";
const app = new Application('task', (p) => {
function update() {
w.content.innerHTML = ``;
const list = document.createElement("ul");
getProcList().forEach(proc => {
const item = document.createElement("li");
const link = document.createElement("a");
link.innerText = `${proc.pid} -> ${proc.name}`;
item.appendChild(link);
list.appendChild(item);
})
w.content.appendChild(list);
}
const w = new Window();
w.setTitle("Task Manager");
w.content.style.padding = "1rem";
const task = setInterval(update, 100);
w.close.addEventListener('click', () => {
clearInterval(task);
p.detach();
w.destroy();
});
getScreenRoot().appendChild(w.window);
promoteWindow(w);
})
registerApplication(app);

View file

@ -1169,10 +1169,68 @@ header div:first-child {
.iframe {
padding: 1rem;
font-size: medium;
}
.iframe p {
font-size: medium;
/* KumOS */
.window {
position: absolute;
z-index: 9;
/* background-color: #f1f1f1; */
width: 300px;
height: 300px;
display: flex;
flex-direction: column;
resize: both;
overflow: hidden;
}
.windowHeader {
padding: 10px;
cursor: move;
z-index: 10;
background-color: rgba(0, 0, 0, 0.6);
color: #fff;
flex: 0 0 auto;
}
.windowContent {
width: 100%;
height: 100%;
flex: 1 1 auto;
border: 0;
}
.windowContent {
overflow: hidden;
display: flex;
flex-direction: column;
background-color: black;
}
.windowHeader {
display: flex;
}
.windowHeaderTitle {
margin: 0px;
margin-right: auto;
}
.kumosBar {
position: fixed;
left: 0;
bottom: 0;
width: 100%;
background-color: var(--theme-color);
padding: .4em;
display: flex;
gap: 1em;
}
.kumosBar * {
height: 100%;
}
/* Animations */

View file

@ -3,7 +3,7 @@ import { registerElementHint } from "./tips.js";
const notificationBox = document.createElement('div');
notificationBox.classList.add('notificationBox');
export async function showNotification(title, subtitle, time, hint) {
export async function showNotification(title, subtitle, time = 5000, hint) {
if (!hint) {
hint = headeri18n.notificationDefaultHint;
}