From 964bd2e80822c509240d20de5e56ea2c7ba0aefe Mon Sep 17 00:00:00 2001 From: Adrian Victor Date: Sun, 5 Jul 2026 23:23:26 -0300 Subject: [PATCH] Add kumos. --- .cache/mastodon.json | 2 +- .eleventy.js | 4 +- package.json | 1 + site/_data/i18n.cjs | 6 +- site/_includes/widgets/chattable.html | 2 +- site/guestbook.njk | 14 --- site/iframe/guestbook.njk | 28 ++++++ site/misc/kumaos/en.json | 3 + site/misc/kumaos/index.njk | 29 ++++++ site/misc/kumaos/pt.json | 3 + static/kumos/apps.mjs | 29 ++++++ static/kumos/browser.mjs | 32 +++++++ static/kumos/guestbook.mjs | 38 ++++++++ static/kumos/kernel.mjs | 125 ++++++++++++++++++++++++++ static/kumos/shell.mjs | 36 ++++++++ static/kumos/taskmanager.mjs | 34 +++++++ static/main.css | 62 ++++++++++++- static/scripts/notification.js | 2 +- 18 files changed, 427 insertions(+), 23 deletions(-) delete mode 100644 site/guestbook.njk create mode 100644 site/iframe/guestbook.njk create mode 100644 site/misc/kumaos/en.json create mode 100644 site/misc/kumaos/index.njk create mode 100644 site/misc/kumaos/pt.json create mode 100644 static/kumos/apps.mjs create mode 100644 static/kumos/browser.mjs create mode 100644 static/kumos/guestbook.mjs create mode 100644 static/kumos/kernel.mjs create mode 100644 static/kumos/shell.mjs create mode 100644 static/kumos/taskmanager.mjs diff --git a/.cache/mastodon.json b/.cache/mastodon.json index 05c076b..768bb3e 100644 --- a/.cache/mastodon.json +++ b/.cache/mastodon.json @@ -1,5 +1,5 @@ { - "lastFetched": "2026-07-05T21:48:17.927Z", + "lastFetched": "2026-07-05T22:01:02.267Z", "posts": [ { "date": "2026-07-03T01:51:37.604Z", diff --git a/.eleventy.js b/.eleventy.js index f4864e0..e272860 100644 --- a/.eleventy.js +++ b/.eleventy.js @@ -16,8 +16,8 @@ import readingTime from 'eleventy-plugin-reading-time'; // import i18n from "./_data/i18n.js"; -const production = (process.env.GITHUB_ACTIONS || process.env.FORGEJO_ACTIONS); -console.log("Production: ", production ? "true" : "false"); +const production = (process.env.GITHUB_ACTIONS || process.env.FORGEJO_ACTIONS || process.env.IS_PRODUCTION); +console.log("Production:", production ? "true" : "false"); export default function (eleventyConfig) { eleventyConfig.setInputDirectory("site"); diff --git a/package.json b/package.json index 746e5a3..5d966b9 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "scripts": { "build": "npx @11ty/eleventy", "serve": "npx @11ty/eleventy --serve", + "production": "IS_PRODUCTION=true npx @11ty/eleventy --serve", "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], diff --git a/site/_data/i18n.cjs b/site/_data/i18n.cjs index 893a2c5..f7c8f48 100644 --- a/site/_data/i18n.cjs +++ b/site/_data/i18n.cjs @@ -113,7 +113,8 @@ module.exports = { sfxCheckboxAlt: "Sound effects", videoArtworkAlt: "Video artwork", songSelectAlt: "Song selector", - guestbookWarning: "Load guestbook widget? You're subject to Chattable's terms of service." + guestbookWarning: "Load guestbook widget? You're subject to Chattable's terms of service.", + dontAskAgain: "Don't ask again" }, pt: { language: "português", @@ -202,6 +203,7 @@ module.exports = { sfxCheckboxAlt: "Efeitos sonoros", videoArtworkAlt: "Capa do vídeo", songSelectAlt: "Seletor de música", - guestbookWarning: "Carregar o guestbook? Você estará sujeito aos termos de uso do Chattable." + guestbookWarning: "Carregar o guestbook? Você estará sujeito aos termos de uso do Chattable.", + dontAskAgain: "Não pergunte novamente" } }; \ No newline at end of file diff --git a/site/_includes/widgets/chattable.html b/site/_includes/widgets/chattable.html index 32ceee7..c778445 100644 --- a/site/_includes/widgets/chattable.html +++ b/site/_includes/widgets/chattable.html @@ -1,5 +1,5 @@ - + +
+

{{ i18n[langKey].guestbookWarning | safe }}

+ +
+
\ No newline at end of file diff --git a/site/misc/kumaos/en.json b/site/misc/kumaos/en.json new file mode 100644 index 0000000..5405e32 --- /dev/null +++ b/site/misc/kumaos/en.json @@ -0,0 +1,3 @@ +{ + "pageDescription": "Operating system written in JS." +} \ No newline at end of file diff --git a/site/misc/kumaos/index.njk b/site/misc/kumaos/index.njk new file mode 100644 index 0000000..287a635 --- /dev/null +++ b/site/misc/kumaos/index.njk @@ -0,0 +1,29 @@ +--- +layout: text.njk +pagination: + data: languages + size: 1 + alias: langKey +pageTitle: KumaOS +--- +{% from "macros.njk" import i88x31 with context %} + + + + + + + +
+ + +
\ No newline at end of file diff --git a/site/misc/kumaos/pt.json b/site/misc/kumaos/pt.json new file mode 100644 index 0000000..0f23f42 --- /dev/null +++ b/site/misc/kumaos/pt.json @@ -0,0 +1,3 @@ +{ + "pageDescription": "Sistema operacional em JS." +} \ No newline at end of file diff --git a/static/kumos/apps.mjs b/static/kumos/apps.mjs new file mode 100644 index 0000000..c8fedd7 --- /dev/null +++ b/static/kumos/apps.mjs @@ -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); \ No newline at end of file diff --git a/static/kumos/browser.mjs b/static/kumos/browser.mjs new file mode 100644 index 0000000..976cd40 --- /dev/null +++ b/static/kumos/browser.mjs @@ -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); \ No newline at end of file diff --git a/static/kumos/guestbook.mjs b/static/kumos/guestbook.mjs new file mode 100644 index 0000000..a18adf3 --- /dev/null +++ b/static/kumos/guestbook.mjs @@ -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 = ` + + + + ` + + 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); \ No newline at end of file diff --git a/static/kumos/kernel.mjs b/static/kumos/kernel.mjs new file mode 100644 index 0000000..6f2a831 --- /dev/null +++ b/static/kumos/kernel.mjs @@ -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; +} \ No newline at end of file diff --git a/static/kumos/shell.mjs b/static/kumos/shell.mjs new file mode 100644 index 0000000..4058469 --- /dev/null +++ b/static/kumos/shell.mjs @@ -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); diff --git a/static/kumos/taskmanager.mjs b/static/kumos/taskmanager.mjs new file mode 100644 index 0000000..1d93b3c --- /dev/null +++ b/static/kumos/taskmanager.mjs @@ -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); \ No newline at end of file diff --git a/static/main.css b/static/main.css index a32f9fa..9de1c52 100644 --- a/static/main.css +++ b/static/main.css @@ -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 */ diff --git a/static/scripts/notification.js b/static/scripts/notification.js index a05b793..e91ccc4 100644 --- a/static/scripts/notification.js +++ b/static/scripts/notification.js @@ -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; }