Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ This is a "bug bounty hackathon" project that ironically has more bugs than it f
- 🧱 **Tailwind Detox**: The entire interface now leans on React95 and styled-components—no utility classes in sight
- 🪟 **Pixel-Perfect Window Controls**: Title bars now rock the classic `_`, `□`, and `X` glyphs straight out of Windows 95
- 🖱️ **Authentic Desktop Shell**: Draggable, resizable windows with minimize/maximize controls, a living taskbar, and a Start menu that launches every feature
- 🧊 **Smart Window Auto-Sizing**: Newly opened apps now expand to fit their content so you're not hunting for UI that slipped outside the frame
- 🗃️ **Zustand State Management**: Because Redux wasn't complicated enough, we needed something "simpler"
- ⚡ **Blazing Fast Vite**: The only thing fast about this project
- 🎭 **TypeScript**: Added types everywhere except where they're actually needed
Expand Down
166 changes: 137 additions & 29 deletions src/components/DesktopWindow.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Suspense } from 'react'
import { Suspense, useCallback, useEffect, useRef } from 'react'
import { Rnd } from 'react-rnd'
import { styled } from 'styled-components'
import { Button, Window, WindowContent, WindowHeader } from 'react95'
Expand Down Expand Up @@ -46,7 +46,7 @@ const ControlGroup = styled.div`
const Content = styled(WindowContent)`
flex: 1;
width: 100%;
overflow: hidden;
overflow: auto;
`

const LoadingFallback = styled.div`
Expand All @@ -68,85 +68,193 @@ export default function DesktopWindow({ windowState, containerSize }: Props) {
setWindowTitle,
} = useWindowManager()

const {
id,
position: windowPosition,
size,
minimized,
maximized,
zIndex,
context,
} = windowState

const windowShellRef = useRef<HTMLDivElement | null>(null)
const contentRef = useRef<HTMLDivElement | null>(null)
const autoSizeEnabledRef = useRef(true)

const definition = WINDOW_APPS[windowState.appId]

const adjustToContentSize = useCallback(() => {
if (maximized || minimized || !autoSizeEnabledRef.current) {
return
}

if (containerSize.width <= 0 || containerSize.height <= 0) {
return
}

const shellEl = windowShellRef.current
const contentEl = contentRef.current

if (!shellEl || !contentEl) {
return
}

const widthExtra = shellEl.offsetWidth - contentEl.offsetWidth
const heightExtra = shellEl.offsetHeight - contentEl.offsetHeight

const requiredWidth = Math.ceil(
contentEl.scrollWidth + Math.max(0, widthExtra)
)
const requiredHeight = Math.ceil(
contentEl.scrollHeight + Math.max(0, heightExtra)
)

const nextWidth = Math.min(
Math.max(size.width, requiredWidth),
containerSize.width
)
const nextHeight = Math.min(
Math.max(size.height, requiredHeight),
containerSize.height
)

let nextX = windowPosition.x
let nextY = windowPosition.y

if (nextX + nextWidth > containerSize.width) {
nextX = Math.max(0, containerSize.width - nextWidth)
}

if (nextY + nextHeight > containerSize.height) {
nextY = Math.max(0, containerSize.height - nextHeight)
}

const sizeChanged = nextWidth > size.width || nextHeight > size.height
const positionChanged =
nextX !== windowPosition.x || nextY !== windowPosition.y

if (sizeChanged || positionChanged) {
const maybePosition = positionChanged ? { x: nextX, y: nextY } : undefined
setWindowSize(id, { width: nextWidth, height: nextHeight }, maybePosition)
}
}, [
containerSize.height,
containerSize.width,
id,
maximized,
minimized,
setWindowSize,
size.height,
size.width,
windowPosition.x,
windowPosition.y,
])

useEffect(() => {
if (typeof window === 'undefined' || maximized || minimized) {
return
}

const contentEl = contentRef.current
if (!contentEl) {
return
}

adjustToContentSize()

if (typeof ResizeObserver === 'undefined') {
const rafId = window.requestAnimationFrame(() => adjustToContentSize())
return () => window.cancelAnimationFrame(rafId)
}

const observer = new ResizeObserver(() => {
adjustToContentSize()
})

observer.observe(contentEl)
return () => observer.disconnect()
}, [adjustToContentSize, maximized, minimized])

if (!definition) {
return null
}

const { Component } = definition

const handleMaximize = () => {
toggleMaximize(windowState.id, containerSize)
autoSizeEnabledRef.current = false
toggleMaximize(id, containerSize)
}

return (
<Rnd
bounds="parent"
size={{ width: windowState.size.width, height: windowState.size.height }}
position={{ x: windowState.position.x, y: windowState.position.y }}
onDragStart={() => focusWindow(windowState.id)}
onDragStop={(_, data) =>
setWindowPosition(windowState.id, { x: data.x, y: data.y })
}
onResizeStart={() => focusWindow(windowState.id)}
size={{ width: size.width, height: size.height }}
position={{ x: windowPosition.x, y: windowPosition.y }}
onDragStart={() => {
autoSizeEnabledRef.current = false
focusWindow(id)
}}
onDragStop={(_, data) => setWindowPosition(id, { x: data.x, y: data.y })}
onResizeStart={() => {
autoSizeEnabledRef.current = false
focusWindow(id)
}}
onResize={(_, __, ref, ___, position) =>
setWindowSize(
windowState.id,
id,
{ width: ref.offsetWidth, height: ref.offsetHeight },
position
)
}
onMouseDown={() => focusWindow(windowState.id)}
onMouseDown={() => focusWindow(id)}
dragHandleClassName="win95-title-bar"
disableDragging={windowState.maximized}
enableResizing={!windowState.maximized}
disableDragging={maximized}
enableResizing={!maximized}
minWidth={360}
minHeight={240}
style={{
display: windowState.minimized ? 'none' : undefined,
zIndex: windowState.zIndex,
display: minimized ? 'none' : undefined,
zIndex,
}}
>
<WindowShell shadow>
<WindowShell ref={windowShellRef} shadow>
<TitleBar onDoubleClick={handleMaximize}>
<TitleText>{windowState.title}</TitleText>
<ControlGroup>
<Button
square
size="sm"
aria-label="Minimize"
onClick={() => toggleMinimize(windowState.id)}
onClick={() => toggleMinimize(id)}
>
<Win95MinimizeIcon />
</Button>
<Button
square
size="sm"
aria-label={windowState.maximized ? 'Restore' : 'Maximize'}
aria-label={maximized ? 'Restore' : 'Maximize'}
onClick={handleMaximize}
>
{windowState.maximized ? (
<Win95RestoreIcon />
) : (
<Win95MaximizeIcon />
)}
{maximized ? <Win95RestoreIcon /> : <Win95MaximizeIcon />}
</Button>
<Button
square
size="sm"
aria-label="Close"
onClick={() => closeWindow(windowState.id)}
onClick={() => closeWindow(id)}
>
<Win95CloseIcon />
</Button>
</ControlGroup>
</TitleBar>
<Content>
<Content ref={contentRef}>
<Suspense fallback={<LoadingFallback>Loading...</LoadingFallback>}>
<Component
windowId={windowState.id}
context={windowState.context}
setTitle={title => setWindowTitle(windowState.id, title)}
windowId={id}
context={context}
setTitle={title => setWindowTitle(id, title)}
/>
</Suspense>
</Content>
Expand Down