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: [], +}