diff --git a/assets/apps.svg b/assets/apps.svg
new file mode 100644
index 0000000..b07ee19
--- /dev/null
+++ b/assets/apps.svg
@@ -0,0 +1,9 @@
+
diff --git a/assets/chrome.png b/assets/chrome.png
deleted file mode 100644
index 193aa20..0000000
Binary files a/assets/chrome.png and /dev/null differ
diff --git a/assets/file-image.png b/assets/file-image.png
deleted file mode 100644
index 8b20c8d..0000000
Binary files a/assets/file-image.png and /dev/null differ
diff --git a/assets/home.png b/assets/home.png
deleted file mode 100644
index 6d4e49c..0000000
Binary files a/assets/home.png and /dev/null differ
diff --git a/assets/homebank.svg b/assets/homebank.svg
new file mode 100644
index 0000000..73984a2
--- /dev/null
+++ b/assets/homebank.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/image.svg b/assets/image.svg
new file mode 100644
index 0000000..a03eacf
--- /dev/null
+++ b/assets/image.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/trash.svg b/assets/trash.svg
new file mode 100644
index 0000000..ac74e61
--- /dev/null
+++ b/assets/trash.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/wallpaper3.png b/assets/wallpaper3.png
new file mode 100644
index 0000000..6169ea5
Binary files /dev/null and b/assets/wallpaper3.png differ
diff --git a/assets/web.svg b/assets/web.svg
new file mode 100644
index 0000000..aebdf80
--- /dev/null
+++ b/assets/web.svg
@@ -0,0 +1,4 @@
+
diff --git a/bun.lockb b/bun.lockb
index 25c74d4..66add3c 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/index.css b/index.css
index 506e9f7..c5fc2f9 100644
--- a/index.css
+++ b/index.css
@@ -1,3 +1,7 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
* {
box-sizing: border-box;
}
@@ -9,7 +13,7 @@ html {
}
body {
- background-image: url("./assets/wallpaper2.jpg");
+ background-image: url("./assets/wallpaper3.png");
background-size: cover;
background-repeat: no-repeat;
width: 100vw;
diff --git a/package.json b/package.json
index 7b181a7..2b2565c 100644
--- a/package.json
+++ b/package.json
@@ -9,7 +9,13 @@
"preview": "vite preview"
},
"devDependencies": {
+ "autoprefixer": "^10.4.19",
+ "postcss": "^8.4.38",
+ "tailwindcss": "^3.4.4",
"typescript": "^5.2.2",
"vite": "^5.2.0"
+ },
+ "dependencies": {
+ "date-fns": "^3.6.0"
}
}
diff --git a/postcss.config.js b/postcss.config.js
new file mode 100644
index 0000000..2e7af2b
--- /dev/null
+++ b/postcss.config.js
@@ -0,0 +1,6 @@
+export default {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+}
diff --git a/src/os/applications.ts b/src/os/applications.ts
index 71c8f88..e4c7f29 100644
--- a/src/os/applications.ts
+++ b/src/os/applications.ts
@@ -1,14 +1,29 @@
+import Application from "./applications/application";
+import FileManager from "./applications/fileManager";
+
export default class Applications {
- applications: unknown[];
+ /** currently openedd applications */
+ applications: Application[];
+
constructor() {
this.applications = [];
}
- addApplication(application: unknown) {
+ addApplication(application: Application) {
this.applications.push(application);
}
getApplications() {
return this.applications;
}
+
+ openFileManager(): FileManager {
+ if (this.applications.find(app => app instanceof FileManager)) {
+ console.log("skipping file manager");
+ return
+ }
+ const app = new FileManager()
+ this.applications.push(app);
+ return app
+ }
}
diff --git a/src/os/applications/application.ts b/src/os/applications/application.ts
new file mode 100644
index 0000000..c210bc4
--- /dev/null
+++ b/src/os/applications/application.ts
@@ -0,0 +1,22 @@
+export default class Application {
+ name: string;
+ constructor(name: string) {
+ this.name = name;
+ }
+
+ getName() {
+ return this.name;
+ }
+
+ getDescription() {
+ return "This is a base class for all applications";
+ }
+
+ getIcon() {
+ return "💻";
+ }
+
+ run() {
+ console.log(`Running ${this.getName()}`);
+ }
+}
diff --git a/src/os/applications/fileManager.ts b/src/os/applications/fileManager.ts
new file mode 100644
index 0000000..bb68d25
--- /dev/null
+++ b/src/os/applications/fileManager.ts
@@ -0,0 +1,24 @@
+ import Application from "./application";
+ import Window from "../../ui/window";
+
+
+ export default class FileManager extends Application {
+ /** the ui element for this application */
+ ui_element: Window;
+ icon: string = "📁";
+
+ constructor() {
+ super("File Manager");
+ this.ui_element = new Window(this);
+ }
+
+ getDescription() {
+ return "This is a file manager";
+ }
+
+ /** returns the ui for this application */
+ run(){
+ const el = this.ui_element.start()
+ return el
+ }
+ }
diff --git a/src/os/cursor.ts b/src/os/cursor.ts
index db625ea..e749526 100644
--- a/src/os/cursor.ts
+++ b/src/os/cursor.ts
@@ -4,6 +4,6 @@ export default class Cursor {
}
start(e: MouseEvent) {
- console.log(e);
+ //console.log(e);
}
}
diff --git a/src/os/index.ts b/src/os/index.ts
index d1eafc8..9a761ef 100644
--- a/src/os/index.ts
+++ b/src/os/index.ts
@@ -23,4 +23,13 @@ export default class OS {
console.log("start os");
return this;
}
+
+ open_application(id: string) {
+ console.log("open application", id);
+ if (id == 'file_manager') {
+ this.applications.openFileManager();
+ }
+ this.ui.updateDesktop();
+ return this;
+ }
}
diff --git a/src/ui/component.ts b/src/ui/component.ts
index 9ec9942..94fefad 100644
--- a/src/ui/component.ts
+++ b/src/ui/component.ts
@@ -1,6 +1,10 @@
export default class Component {
element: HTMLDivElement = document.createElement("div");
+ constructor(id: string) {
+ this.element.id = id
+ }
+
addChild(child: HTMLDivElement) {
this.element.appendChild(child);
}
diff --git a/src/ui/desktop.ts b/src/ui/desktop.ts
index aad5cb7..11710ac 100644
--- a/src/ui/desktop.ts
+++ b/src/ui/desktop.ts
@@ -4,51 +4,59 @@ import Window from "./window";
export default class Desktop extends Component {
shortcuts: DesktopShortcut[] = [];
- windows: Window[] = [];
+ shortcuts_container: HTMLDivElement;
+ windows_container: HTMLDivElement;
+ os: OS;
- constructor() {
+ constructor(os: OS) {
super();
+ this.os = os
+ this.element.classList.add("grid","grid-flow-col", "grid-cols-1");
+ this.element.classList.add("w-full", "h-full", "gap-8", "p-2");
this.element.id = "desktop";
- this.element.style.height = "100%";
- this.element.style.width = "100%";
- this.element.style.display = "grid";
- this.element.style.gap = "10px";
- this.element.style.padding = "10px";
- this.element.style.display = "grid";
- this.element.style.gridAutoFlow = "column";
this.element.style.gridTemplateColumns =
"repeat(auto-fill, minmax(60px, 1fr))";
this.element.style.gridTemplateRows =
"repeat(auto-fill, minmax(60px, 1fr))";
+
+ this.shortcuts_container = new Component('shortcuts_container');
+ this.windows_container = new Component('windows_container');
}
updateDesktop() {
- this.element.replaceChildren(...this.shortcuts.map((e) => e.element));
+ const windows = this.os.applications.getApplications()
+ console.log(windows)
+ this.shortcuts_container.element.replaceChildren(...this.shortcuts.map(s => s.start()));
+ this.windows_container.element.replaceChildren(...windows.map(w => w.ui_element.start()));
}
addApplicationShortcut(shortcut: DesktopShortcut) {
this.shortcuts.push(shortcut);
- this.updateDesktop();
+ this.updateDesktop()
}
addOpenedWindow(w: Window) {
this.windows.push(w);
- this.element.appendChild(w.start());
+ this.updateDesktop()
}
initDesktopShortcuts() {
this.addApplicationShortcut(
- new DesktopShortcut("../../assets/home.png", "Home"),
+ new DesktopShortcut("../../assets/homebank.svg", "Home", this.os, 'file_manager'),
);
this.addApplicationShortcut(
- new DesktopShortcut("../../assets/file-image.png", "img_12131995.png"),
+ new DesktopShortcut("../../assets/image.svg", "fun.png", this.os, 'image_viewer'),
+ );
+ this.addApplicationShortcut(
+ new DesktopShortcut("../../assets/trash.svg", "trash", this.os, 'file_manager'),
);
- this.addOpenedWindow(new Window());
}
start() {
+ this.addChild(this.shortcuts_container.element)
+ this.addChild(this.windows_container.element)
this.initDesktopShortcuts();
return this.element;
}
diff --git a/src/ui/desktop_shortcut.ts b/src/ui/desktop_shortcut.ts
index 91b165a..da2688e 100644
--- a/src/ui/desktop_shortcut.ts
+++ b/src/ui/desktop_shortcut.ts
@@ -1,44 +1,48 @@
+import OS from "../os";
import Component from "./component";
export default class DesktopShortcut extends Component {
icon: string;
title: string;
+ os: OS
- constructor(icon: string, title: string) {
+ constructor(icon: string, title: string, os: OS, id: string) {
super();
this.icon = icon;
this.title = title;
+ this.id = id;
+ this.os = os;
- this.element.style.borderRadius = "10px";
- this.element.style.cursor = "pointer";
- this.element.style.color = "white";
- this.element.style.justifyContent = "center";
- this.element.style.display = "flex";
- this.element.style.flexDirection = "column";
- this.element.style.alignItems = "center";
- this.element.style.padding = "6px";
-
- this.element.addEventListener("mouseover", () => {
- this.element.style.border = "1px solid #0000005f";
- });
-
- this.element.addEventListener("mouseout", () => {
- this.element.style.border = "1px solid #00000000";
- });
-
+ this.element.classList.add("text-white", "cursor-pointer", "flex", "flex-col", "justify-center", "items-center", "p-2");
+
const _icon = document.createElement("img");
const _title = document.createElement("p");
_icon.src = this.icon;
- _icon.style.width = "30px";
+ _icon.style.width = "60px";
_title.textContent = this.title;
- _title.style.margin = "0";
- _title.style.fontSize = "12px";
- _title.style.marginTop = "4px";
- _title.style.wordWrap = "anywhere";
+ _title.classList.add( "font-bold", "text-md", "mt-1", "break-normal");
this.addChild(_icon);
this.addChild(_title);
+
+ this.element.addEventListener("click", () => this.open_application());
+ }
+
+ getId() {
+ return this.id;
+ }
+
+ getTitle() {
+ return this.title;
+ }
+
+ getIcon() {
+ return this.icon;
+ }
+
+ open_application() {
+ this.os.open_application(this.id);
}
}
diff --git a/src/ui/index.ts b/src/ui/index.ts
index 634963a..cff7822 100644
--- a/src/ui/index.ts
+++ b/src/ui/index.ts
@@ -22,7 +22,6 @@ export default class UI extends Component {
}
start() {
- console.log("start ui");
this.element.id = "ui";
document.body.appendChild(this.element);
@@ -30,7 +29,7 @@ export default class UI extends Component {
this.addChild(this.topbar.start());
this.navbar = new NavBar();
- this.desktop = new Desktop();
+ this.desktop = new Desktop(this.os);
const desktop_container = document.createElement("div");
desktop_container.id = "desktop_container";
@@ -43,4 +42,8 @@ export default class UI extends Component {
return this.element;
}
+
+ updateDesktop() {
+ this.desktop.updateDesktop();
+ }
}
diff --git a/src/ui/nav.ts b/src/ui/nav.ts
index abb5fe3..ac4f52b 100644
--- a/src/ui/nav.ts
+++ b/src/ui/nav.ts
@@ -1,22 +1,47 @@
import Component from "./component"
export default class NavBar extends Component {
+ applications: HTMLDivElement[] = [];
constructor() {
super()
- this.element.style.display = 'flex'
- this.element.style.flexDirection = 'column'
- this.element.style.backgroundColor = '#000000af';
- this.element.style.backdropFilter = 'blur(10px)';
- this.element.style.height = '100%'
- this.element.style.width = '60px'
+ this.element.classList.add('flex', 'flex-column', 'w-20', 'h-full', 'bg-[#000a]', 'justify-center','py-2', 'z-[12]')
+ }
+ init(){
+ this.applications.push(new Application("../../assets/apps.svg").start())
+ this.applications.forEach(app => this.element.appendChild(app))
}
start() {
this.element.id = 'navbar'
+ this.init()
return this.element
}
}
+
+class Application extends Component {
+ icon: string;
+ constructor(icon: string) {
+ super();
+ this.icon = icon;
+
+ this.element.classList.add('cursor-pointer')
+ }
+
+ setup_click_handler(){
+ this.element.addEventListener('click', () => {
+ console.log('clicked')
+ })
+ }
+
+ start() {
+ this.setup_click_handler()
+ this.element.innerHTML = `
+
+ `
+ return this.element
+ }
+}
diff --git a/src/ui/topbar.ts b/src/ui/topbar.ts
index 24bddc2..8d862ef 100644
--- a/src/ui/topbar.ts
+++ b/src/ui/topbar.ts
@@ -1,14 +1,25 @@
import Component from "./component"
+import { format } from "date-fns"
export default class TopBar extends Component {
+ time: HTMLDivElement;
+
constructor() {
super()
- this.element.style.height = "20px"
- this.element.style.width = "100%"
- this.element.style.backgroundColor = "black"
+ this.element.classList.add("w-full", "bg-black", "py-1", 'flex', 'justify-center', 'z-[11]', 'relative')
+ }
+
+ add_time(){
+ const date = new Date()
+ this.time = new Component()
+ this.time.element.classList.add( "text-white", "text-center", "cursor-default")
+ this.time.element.innerText = format(date, "MMM dd H:mm aa")
+
+ this.element.appendChild(this.time.start())
}
start() {
+ this.add_time()
this.element.id = 'topbar'
return this.element
}
diff --git a/src/ui/window.ts b/src/ui/window.ts
index ba64988..1cc5f72 100644
--- a/src/ui/window.ts
+++ b/src/ui/window.ts
@@ -1,18 +1,135 @@
+import Component from "./component";
+
export default class Window {
element: HTMLDivElement = document.createElement("div");
initial_size = { w: 500, h: 500 } as const;
+ dragging = false;
+ header: HTMLDivElement;
+ /** the os application that this window is for */
+ application: Application;
- constructor() {
- this.element.style.backgroundColor = "#ccc";
+ constructor(application: Application) {
+ this.element.classList.add("bg-[#ccc]", "absolute", "rounded-lg", "shadow-xl");
this.element.style.width = `${this.initial_size.w}px`;
this.element.style.height = `${this.initial_size.h}px`;
- this.element.style.position = "absolute";
- this.element.style.zIndex = "10";
- this.element.style.borderRadius = "10px";
- this.element.style.boxShadow =
- "0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)";
+ this.application = application;
+
+ this._add_header("Hello World");
+ this._add_content("This is a test");
+ this._add_resize_handles();
+ this._handle_drag()
}
+ private _add_header(title: string): HTMLDivElement {
+ this.header = new Component()
+ this.header.element.id = "header";
+ this.header.element.classList.add(
+ "bg-[#333]", "p-2", "text-sm", "text-[#eee]", "rounded-t", "text-center", "cursor-move"
+ );
+ this.header.element.innerText = title;
+ this.element.appendChild(this.header.start());
+ }
+
+ private _handle_drag(e: MouseEvent): void {
+ let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
+ const dragMouseDown = (e) => {
+ e.preventDefault();
+ // get the mouse cursor position at startup:
+ pos3 = e.clientX;
+ pos4 = e.clientY;
+ document.onmouseup = closeDragElement;
+ // call a function whenever the cursor moves:
+ document.onmousemove = elementDrag;
+ }
+
+ if (this.header) {
+ // if present, the header is where you move the DIV from:
+ this.header.element.onmousedown = dragMouseDown;
+ }
+
+ const elementDrag = (e) => {
+ e.preventDefault();
+ // calculate the new cursor position:
+ pos1 = pos3 - e.clientX;
+ pos2 = pos4 - e.clientY;
+ pos3 = e.clientX;
+ pos4 = e.clientY;
+ // set the element's new position:
+ this.element.style.top = (this.element.offsetTop - pos2) + "px";
+ this.element.style.left = (this.element.offsetLeft - pos1) + "px";
+ }
+
+ const closeDragElement = () =>{
+ // stop moving when mouse button is released:
+ document.onmouseup = null;
+ document.onmousemove = null;
+ }
+ }
+
+ private _add_content(content: string): HTMLDivElement {
+ const content_element = document.createElement("div");
+ content_element.classList.add("bg-[#eee]", "p-2", "text-sm", "text-[#333]");
+ content_element.innerText = content;
+ this.element.appendChild(content_element);
+ return content_element;
+ }
+
+ private _add_resize_handles(): void{
+ const color_classes = ["hover:bg-[#0af]", "transition-colors", "duration-500", "rounded-lg"]
+ // --- resize handle left ---
+ const resize_handle_left = document.createElement("div");
+ resize_handle_left.classList.add(
+ "absolute", `w-1`, "h-full", "cursor-w-resize", "top-0", "left-0",
+ ...color_classes
+ );
+ resize_handle_left.addEventListener("mousedown", (e) => {
+ this._resize_start(e, 'l');
+ });
+ this.element.appendChild(resize_handle_left);
+
+ // --- resize handle right ---
+ const resize_handle_right = document.createElement("div");
+ resize_handle_right.classList.add(
+ "absolute", `w-1`, "h-full", "cursor-e-resize", "top-0", "right-0",
+ ...color_classes
+ );
+ resize_handle_right.addEventListener("mousedown", (e) => {
+ this._resize_start(e, 'r');
+ });
+ this.element.appendChild(resize_handle_right);
+
+ // --- resize handle bottom ---
+ const resize_handle_bottom = document.createElement("div");
+ resize_handle_bottom.classList.add(
+ "absolute", `w-full`, `h-1`, "cursor-s-resize", "bottom-0", "left-0",
+ ...color_classes
+ );
+ resize_handle_bottom.addEventListener("mousedown", (e) => {
+ this._resize_start(e, 'b');
+ });
+ this.element.appendChild(resize_handle_bottom);
+
+ // --- resize handle top ---
+ const resize_handle_top = document.createElement("div");
+ resize_handle_top.classList.add(
+ "absolute", `w-full`, `h-1`, "cursor-n-resize", "top-0", "left-0",
+ ...color_classes
+ );
+ resize_handle_top.addEventListener("mousedown", (e) => {
+ this._resize_start(e, 't');
+ });
+ this.element.appendChild(resize_handle_top);
+ }
+
+
+
+ private _resize_start(e: MouseEvent, dir: 'l' | 'r' | 't' | 'b') {
+ // TODO finish
+ console.log({e, dir})
+ }
+
+
+
start(): HTMLDivElement {
return this.element;
}
diff --git a/tailwind.config.js b/tailwind.config.js
new file mode 100644
index 0000000..57daf84
--- /dev/null
+++ b/tailwind.config.js
@@ -0,0 +1,8 @@
+/** @type {import('tailwindcss').Config} */
+module.exports = {
+ content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
+ theme: {
+ extend: {},
+ },
+ plugins: [],
+}