make windows work properly

This commit is contained in:
Triston Armstrong 2024-06-20 13:54:10 -05:00
parent 8427aaf24d
commit 06d1a5a41a
26 changed files with 342 additions and 60 deletions

9
assets/apps.svg Normal file
View File

@ -0,0 +1,9 @@
<svg version="1.1" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<rect x="1" y="1" width="30" height="30" rx="8" ry="8" fill="#2db0ff"/>
<g fill="#ffffff" stroke-width="1.0001">
<rect x="5" y="5" width="10" height="10" rx="4" ry="4"/>
<path d="m9 17c-2.216 0-4 1.784-4 4v2c0 2.216 1.784 4 4 4h2c2.216 0 4-1.784 4-4v-2c0-2.216-1.784-4-4-4h-2zm0 1h2c1.662 0 3 1.338 3 3v2c0 1.662-1.338 3-3 3h-2c-1.662 0-3-1.338-3-3v-2c0-1.662 1.338-3 3-3z" opacity=".45"/>
<path d="m21 17c-2.216 0-4 1.784-4 4v2c0 2.216 1.784 4 4 4h2c2.216 0 4-1.784 4-4v-2c0-2.216-1.784-4-4-4zm0 1h2c1.662 0 3 1.338 3 3v2c0 1.662-1.338 3-3 3h-2c-1.662 0-3-1.338-3-3v-2c0-1.662 1.338-3 3-3z" opacity=".45"/>
<path d="m21 5c-2.216 0-4 1.784-4 4v2c0 2.216 1.784 4 4 4h2c2.216 0 4-1.784 4-4v-2c0-2.216-1.784-4-4-4zm0 1h2c1.662 0 3 1.338 3 3v2c0 1.662-1.338 3-3 3h-2c-1.662 0-3-1.338-3-3v-2c0-1.662 1.338-3 3-3z" opacity=".45"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 923 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 798 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 439 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 405 B

1
assets/homebank.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.4 KiB

1
assets/image.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.4 KiB

1
assets/trash.svg Normal file
View File

@ -0,0 +1 @@
<svg width="64" height="64" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><linearGradient id="a" x1="32.432" x2="32.432" y1="4.138" y2="23.904" gradientUnits="userSpaceOnUse"><stop stop-color="#d1d1d1" offset="0"/><stop stop-color="#a6a6a6" offset="1"/></linearGradient></defs><image x="7.5" y="45.241" width="49" height="21" image-rendering="optimizeQuality" preserveAspectRatio="none" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADEAAAAVCAYAAADvoQY8AAAACXBIWXMAAA7DAAAOwwHHb6hkAAAA GXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAVJJREFUWIXdltuOwyAMRAnp//9x YV8gmgxj7GxJW9USwrkAPli+pPQDsn14PUp9hxH87xbQLakBXT1LeTjflXHbROc1llQxK12tGWQG oYzNoPNIYlZiAfAoaQSSIAoCDckw5xQDiUJEAArpEoYh0KBMY59AeIZ7QBbEE0A2eH8CQQgG2GEg zH/jIAKCQKWdVxrMs30fPKI8gQAPglBGr0jTFfbpMB2gpPEM9MgBwbfbQTwPrBJrv9zm2uzB2Dig My2yAvZOABbOiF4SSQxhZQvO43eKl3qHOrLTBurWPQ+s8IyqBZhie3Bjyj1gEMLL7VZL8Kpn1M1j RuJxCuqUztkJPxR411OdCvIrRc4yngG42KEHBi9Yh1oVmyv3nW2HVbGl92eHqdjwKvZVCK9iRxrD kPvVLb+ri3Wbv+iB1r9WLFyBmOnqWcqKlmGV3F1/vlv+ANnpsi80YU39AAAAAElFTkSuQmCC"/><path d="M32 3C19.256 3 8.924 5.583 8.924 8.77l.04.33c.688 3.032 10.737 5.438 23.036 5.438 12.3 0 22.352-2.407 23.036-5.439l.04-.33C55.076 5.583 44.744 3 32 3zM14.539 54.516zm.266.748zm1.024 1.413zm32.39.001zm-31.226.973zm1.469.886zm1.744.784zm25.38-.784zm-1.745.784z" color="#000" fill="url(#a)" opacity=".6"/><path d="m8.924 8.768 5.525 44.978c0 4.16 7.869 7.533 17.575 7.533 9.707 0 17.576-3.372 17.576-7.533l5.476-44.978c-.001 3.186-10.332 5.769-23.076 5.769S8.925 11.954 8.924 8.768zm46.152 0z" color="#000" fill="#f5f5f5" opacity=".915"/></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
assets/wallpaper3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

4
assets/web.svg Normal file
View File

@ -0,0 +1,4 @@
<svg width="32" height="32" version="1.1" xmlns="http://www.w3.org/2000/svg">
<rect x="1" y="1" width="30" height="30" rx="8" ry="8" fill="#4386f5"/>
<path d="M8.421 12.259a11.621 11.621 0 0 0 8.515 3.653 11.342 11.342 0 0 0 7.014-2.436l-.502-.988a10.171 10.171 0 0 1-6.512 2.356 10.457 10.457 0 0 1-8.02-3.68zm-.944 5.038c4.588 3.918 11.153 4.65 16.447 1.88l-.026-1.21c-5.145 2.974-11.718 2.233-16.156-1.87Zm4.376 6.062 1.262.291a12.108 12.108 0 0 1-.935-4.465c-.247-4.385 1.641-8.567 5.012-11.33l-1.13-.44a14.7 14.7 0 0 0-4.95 11.788c.018 1.412.274 2.806.741 4.156zm5.815.873.997-.44a13.754 13.754 0 0 1-2.55-7.977c0-2.736.794-5.347 2.285-7.57l-1.04-.371a14.733 14.733 0 0 0-2.312 7.94c0 3.045.917 5.948 2.62 8.418zm5.197-3.282.715-.812a9.652 9.652 0 0 1-4.862-8.276 9.523 9.523 0 0 1 .644-3.591l-1.032-.292a10.542 10.542 0 0 0-.68 3.892 10.716 10.716 0 0 0 5.215 9.079zm-10.694-5.144c.75 0 1.368-.627 1.368-1.385a1.377 1.377 0 1 0-2.753 0c0 .758.617 1.385 1.385 1.385zm6.759.732a1.36 1.36 0 0 0 0-2.717 1.36 1.36 0 0 0-1.368 1.35c0 .758.609 1.367 1.368 1.367zm-2.63 5.1c.76 0 1.368-.609 1.368-1.376a1.37 1.37 0 0 0-1.368-1.377 1.38 1.38 0 0 0-1.385 1.377c0 .767.618 1.376 1.385 1.376zM16 25c4.924 0 9-4.085 9-9 0-4.924-4.085-9-9.008-9C11.077 7 7 11.076 7 16c0 4.915 4.086 9 9 9zm0-1.2c-4.261 0-7.8-3.538-7.8-7.8 0-4.262 3.53-7.8 7.792-7.8 4.261 0 7.808 3.538 7.808 7.8 0 4.262-3.538 7.8-7.8 7.8z" fill="#fff" stroke-width=".75294"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
bun.lockb

Binary file not shown.

View File

@ -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;

View File

@ -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"
}
}

6
postcss.config.js Normal file
View File

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View File

@ -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
}
}

View File

@ -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()}`);
}
}

View File

@ -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
}
}

View File

@ -4,6 +4,6 @@ export default class Cursor {
}
start(e: MouseEvent) {
console.log(e);
//console.log(e);
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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 = `
<img src="${this.icon}" style="width: 60px;">
`
return this.element
}
}

View File

@ -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
}

View File

@ -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;
}

8
tailwind.config.js Normal file
View File

@ -0,0 +1,8 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {},
},
plugins: [],
}