diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/app.tsx | 11 | ||||
| -rw-r--r-- | src/desktop/apps/buttons.tsx | 0 | ||||
| -rw-r--r-- | src/desktop/apps/edit.tsx | 13 | ||||
| -rw-r--r-- | src/desktop/apps/index.tsx | 5 | ||||
| -rw-r--r-- | src/desktop/apps/music.tsx | 43 | ||||
| -rw-r--r-- | src/desktop/index.tsx | 43 | ||||
| -rw-r--r-- | src/desktop/window.tsx | 81 | ||||
| -rw-r--r-- | src/main.tsx | 6 | ||||
| -rw-r--r-- | src/taskbar/index.tsx | 27 | ||||
| -rw-r--r-- | src/taskbar/start.tsx | 11 | ||||
| -rw-r--r-- | src/taskbar/time.tsx | 23 |
11 files changed, 263 insertions, 0 deletions
diff --git a/src/app.tsx b/src/app.tsx new file mode 100644 index 0000000..c602c8f --- /dev/null +++ b/src/app.tsx @@ -0,0 +1,11 @@ +import { Taskbar } from "./taskbar"; +import { Desktop } from "./desktop"; + +export function App() { + return <> + <div id="bg"><div></div></div> + <div id="bg" class="reverse"><div></div></div> + <Desktop /> + <Taskbar /> + </> +} diff --git a/src/desktop/apps/buttons.tsx b/src/desktop/apps/buttons.tsx new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/desktop/apps/buttons.tsx diff --git a/src/desktop/apps/edit.tsx b/src/desktop/apps/edit.tsx new file mode 100644 index 0000000..00aba45 --- /dev/null +++ b/src/desktop/apps/edit.tsx @@ -0,0 +1,13 @@ +import { ComponentChildren } from "preact" + +export function Edit(props: Edit.Props) { + return <textarea autocomplete="off" spellcheck={false}> + {props.children} + </textarea> +} + +namespace Edit { + export type Props = { + children?: ComponentChildren + } +} diff --git a/src/desktop/apps/index.tsx b/src/desktop/apps/index.tsx new file mode 100644 index 0000000..a2954b6 --- /dev/null +++ b/src/desktop/apps/index.tsx @@ -0,0 +1,5 @@ +import { FunctionalComponent } from "preact"; +import { Edit } from "./edit"; +import { Music } from "./music"; + +export const APP_REGISTRY: Map<string, FunctionalComponent> = new Map(); diff --git a/src/desktop/apps/music.tsx b/src/desktop/apps/music.tsx new file mode 100644 index 0000000..b5646bd --- /dev/null +++ b/src/desktop/apps/music.tsx @@ -0,0 +1,43 @@ +// type Song = { +// name: string, +// url: string, +// length: number, +// +// bc_exclusive: boolean +// } +// +// const SONGS: = [ +// [], +// [], +// [], +// [], +// [], +// [], +// [], +// [], +// [], +// [], +// [] +// ] +// +// function Song() { +// +// } + +import { useState } from "preact/hooks" + +export function Music() { + const [userApproves, setUserApproves] = useState(false); + + if (userApproves) { + return <iframe style="border: 0; width: 350px; height: 500px;" src="https://bandcamp.com/EmbeddedPlayer/album=3374731395/size=large/bgcol=000000/linkcol=ff0047/transparent=true/" seamless><a href="https://sunnexo.bandcamp.com/album/netpunk">Netpunk by Sunnexo</a></iframe> + } else { + return <div class="external_warning"> + <div>Click here to load player from <a href="https://bandcamp.com">https://bandcamp.com</a></div> + <button onClick={() => setUserApproves(true)}> + Load player + </button> + </div> + } +} + diff --git a/src/desktop/index.tsx b/src/desktop/index.tsx new file mode 100644 index 0000000..021d3a0 --- /dev/null +++ b/src/desktop/index.tsx @@ -0,0 +1,43 @@ +import { useRef } from "preact/hooks" +import { Edit } from "./apps/edit" +import { Window } from "./window" +import { Music } from "./apps/music"; + +export function Desktop() { + const ref = useRef(null); + return <div id="desktop" ref={ref}> + <div class="icon_grid"> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + </div> + <Window title="README.txt" width="30vw" height={300} x={40} y={40} desktop={ref}> + <Edit> + Fine release by Sunnexo 2026 + + Tracklist: + 1. Internet Exploder + 2. Netpunk (feat. Kasane Teto) + 3. Free Download + 4. I am not a Robot + 5. Content + 6. Keygen + 7. Base64 + 8. Readme (feat. Kasane Teto) + 9. Symlink + 10. Memory + + An album about borrowing software forever. + + A clumsy robot forgets about their girlfriend's anniversary, scrambling to find something to do for the date, eventually concluding they should watch a movie. Of course the movie is in high demand, so the tickets have sold out. Ultimately they resort to piracy, and things spiral out of control... + </Edit> + </Window> + <Window title="Stream Netpunk" desktop={ref}> + <Music /> + </Window> + + <img id="netpunk" src="/assets/netpunk.png" /> + </div> +} diff --git a/src/desktop/window.tsx b/src/desktop/window.tsx new file mode 100644 index 0000000..b2deaec --- /dev/null +++ b/src/desktop/window.tsx @@ -0,0 +1,81 @@ +import { ComponentChildren, Ref, RefObject } from "preact"; +import { useCallback, useRef, useState } from "preact/hooks"; + +export function Window(props: Window.Props) { + const windowRef: Ref<HTMLDivElement> = useRef(null); + const dragTimeout: Ref<ReturnType<typeof setTimeout>> = useRef(null); + + const [x, setX] = useState(props.x ?? 0); + const [y, setY] = useState(props.y ?? 0); + + const initialOffsetX = useRef(0); + const initialOffsetY = useRef(0); + + const updatePosition = useCallback((e: MouseEvent) => { + setX(e.clientX - initialOffsetX.current) + setY(e.clientY - initialOffsetY.current) + }, []) + + const startDrag = useCallback((e: MouseEvent) => { + if (e.button === 0) { + + const BORDER_WIDTH = 1; + console.log(e); + // Naturally, I have to add the border width here, because offset* uses padding-edge for the calculations + initialOffsetX.current = e.offsetX + BORDER_WIDTH + initialOffsetY.current = e.offsetY + BORDER_WIDTH + windowRef.current?.classList.add("dragging") + + if (dragTimeout.current !== null) { + clearTimeout(dragTimeout.current); + dragTimeout.current = null; + } else { + props.desktop.current?.addEventListener("mousemove", updatePosition) + props.desktop.current?.addEventListener("mouseleave", stopDrag) + } + } + }, []); + + const stopDrag = useCallback((e: MouseEvent) => { + if (e.button === 0) { + dragTimeout.current = setTimeout(() => { + dragTimeout.current = null; + windowRef.current?.classList.remove("dragging") + props.desktop.current?.removeEventListener("mousemove", updatePosition) + props.desktop.current?.removeEventListener("mouseleave", stopDrag) + }, 50) // intrdouce a small delay where the user can still drag the window without actually pressing down lmb to prevent accidental drops + } + }, []); + + return <div class="window" ref={windowRef} style={{ width: props.width, height: props.height, left: x, top: y}}> + <div class="titlebar" onPointerDown={startDrag} onPointerUp={stopDrag}> + <div class="title">{props.title}</div> + <div class="buttons" onPointerDown={e => e.stopPropagation()}> + <button> + <img src="/assets/minimize.png" /> + </button> + <button> + <img src="/assets/maximize.png" /> + </button> + <button> + <img src="/assets/close.png" /> + </button> + </div> + </div> + <div class="content"> + {props.children} + </div> + </div> +} + +namespace Window { + export type Props = { + title: string; + width?: string | number; + height?: string | number; + x?: string | number; + y?: string | number; + desktop: RefObject<HTMLDivElement>; + children: ComponentChildren + } +} diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..43494a2 --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,6 @@ +import { render } from "preact"; +import { App } from "./app"; + +import 'temporal-polyfill/global' + +render(<App/>, document.getElementById("root")!); diff --git a/src/taskbar/index.tsx b/src/taskbar/index.tsx new file mode 100644 index 0000000..34377c2 --- /dev/null +++ b/src/taskbar/index.tsx @@ -0,0 +1,27 @@ +import { StartButton } from "./start" +import { Time } from "./time" + +export function Taskbar() { + return <nav> + <StartButton /> + <ul> + <li> + <button> + README.txt + </button> + </li> + <li> + <button> + KEYGEn + </button> + </li> + <li> + <button> + Chat + </button> + </li> + </ul> + <Time /> + <div id="netpunk_descender"></div> + </nav> +} diff --git a/src/taskbar/start.tsx b/src/taskbar/start.tsx new file mode 100644 index 0000000..ddf6d47 --- /dev/null +++ b/src/taskbar/start.tsx @@ -0,0 +1,11 @@ +export function StartButton() { + return <div id="start"> + <button> + <img src="/assets/fumo.png" /> + Start + </button> + <div> + <div></div> + </div> + </div> +} diff --git a/src/taskbar/time.tsx b/src/taskbar/time.tsx new file mode 100644 index 0000000..053147b --- /dev/null +++ b/src/taskbar/time.tsx @@ -0,0 +1,23 @@ +import { useEffect, useState } from "preact/hooks"; + +const dateTimeFormat = new Intl.DateTimeFormat(); +const timezoneId = Temporal.Now.timeZoneId(); + +export function Time() { + const [time, setTime] = useState(Temporal.Now.instant().toZonedDateTimeISO(timezoneId)) + + useEffect(() => { + const id = setInterval(() => { + setTime(time => time.add({ seconds: 1 })); + }, 1000) + + return () => { + clearInterval(id); + } + }, []) + + return <div id="time"> + <div>{dateTimeFormat.format(time.toPlainTime())}</div> + <div>{dateTimeFormat.format(time.toPlainDate())}</div> + </div> +} |
