Skip to main content
PictoChat is a chat application inspired by the Nintendo DS feature, embedded from an external service hosted at chat.aduncan.dev.

Features

  • Real-time Chat: Live messaging with other users
  • Drawing Support: Send drawings and doodles
  • Multi-user: Connect with multiple people
  • External Service: Hosted on chat.aduncan.dev
  • Focus Management: Overlay when window not focused

Component Structure

Location: src/WinXP/apps/PictoChat/index.jsx
function PictoChat({ isFocus }) {
  const gameUrl = 'https://chat.aduncan.dev';
  
  return (
    <AppContainer>
      <StyledIframe src={gameUrl} frameBorder="0" title="PictoChat" />
      {!isFocus && <Overlay />}
    </AppContainer>
  );
}

Configuration

From apps/index.jsx:
PictoChat: {
  name: 'PictoChat',
  header: { icon: pictoChatIcon, title: 'PictoChat' },
  component: PictoChat,
  defaultSize: { width: 400, height: 600 },
  defaultOffset: getCenter(400, 600),
  resizable: true,
  minimized: false,
  maximized: shouldMaximize(400, 600, true),
  multiInstance: false,
}

Focus Overlay

Like other iframe-based apps, PictoChat uses an overlay to manage focus:
const Overlay = styled.div`
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 2;
  background-color: transparent;
`;

{!isFocus && <Overlay />}
Why the overlay is needed:
  • Prevents iframe from capturing all mouse events when window is unfocused
  • Allows users to drag and resize the window
  • Clicking the overlay brings focus to the window
  • Overlay is removed when window has focus, allowing full interaction

Styling

const AppContainer = styled.div`
  width: 100%;
  height: 100%;
  position: relative;
  background-color: #309f6a;
  overflow: hidden;
`;

const StyledIframe = styled.iframe`
  display: block;
  width: 100%;
  height: 100%;
  border: none;
`;
The green background color (#309f6a) provides a fallback while the iframe loads.

External Service

PictoChat loads from https://chat.aduncan.dev, which is an external service. This means:

Advantages

  • No hosting required on your end
  • Real-time chat server managed externally
  • Automatic updates from service provider
  • Shared chat rooms with other users of the service

Limitations

  • Requires internet connection
  • Dependent on external service availability
  • Limited customization options
  • Cannot access chat data or history
  • Service may have its own terms of service

Alternative: Self-Hosted Chat

To host your own PictoChat service:

1. Set up a chat server

// Example using Socket.IO
const express = require('express');
const app = express();
const http = require('http').Server(app);
const io = require('socket.io')(http);

app.use(express.static('public'));

io.on('connection', (socket) => {
  console.log('User connected');
  
  socket.on('chat message', (msg) => {
    io.emit('chat message', msg);
  });
  
  socket.on('drawing', (data) => {
    socket.broadcast.emit('drawing', data);
  });
  
  socket.on('disconnect', () => {
    console.log('User disconnected');
  });
});

http.listen(3000);

2. Create client interface

<!-- public/index.html -->
<!DOCTYPE html>
<html>
<head>
  <title>PictoChat</title>
  <style>
    body { font-family: 'Tahoma', sans-serif; }
    #messages { list-style: none; padding: 0; }
    #canvas { border: 1px solid #ccc; cursor: crosshair; }
  </style>
</head>
<body>
  <ul id="messages"></ul>
  <canvas id="canvas" width="400" height="200"></canvas>
  <form id="form">
    <input id="input" autocomplete="off" />
    <button>Send</button>
  </form>
  
  <script src="/socket.io/socket.io.js"></script>
  <script src="client.js"></script>
</body>
</html>

3. Update component

function PictoChat({ isFocus }) {
  const gameUrl = 'http://localhost:3000'; // or your hosted URL
  
  return (
    <AppContainer>
      <StyledIframe src={gameUrl} frameBorder="0" title="PictoChat" />
      {!isFocus && <Overlay />}
    </AppContainer>
  );
}

Features of chat.aduncan.dev

The external service typically provides:
  • Text Chat: Send and receive text messages
  • Drawing Canvas: Draw and send images
  • User Presence: See who’s online
  • Room System: Multiple chat rooms
  • Message History: Recent message display
  • Emoji Support: Send emoji in messages

Usage Example

import { PictoChat } from 'src/WinXP/apps';

function Desktop() {
  const [focusedWindow, setFocusedWindow] = useState(null);
  
  return (
    <Window 
      title="PictoChat"
      onFocus={() => setFocusedWindow('pictochat')}
      onBlur={() => setFocusedWindow(null)}
    >
      <PictoChat isFocus={focusedWindow === 'pictochat'} />
    </Window>
  );
}

Integration Possibilities

If you control the chat service, you could add PostMessage communication:

In PictoChat Component

function PictoChat({ isFocus, username }) {
  const iframeRef = useRef(null);
  
  useEffect(() => {
    if (iframeRef.current && username) {
      iframeRef.current.contentWindow.postMessage(
        { type: 'SET_USERNAME', username },
        'https://chat.aduncan.dev'
      );
    }
  }, [username]);
  
  return (
    <AppContainer>
      <StyledIframe 
        ref={iframeRef}
        src="https://chat.aduncan.dev" 
        title="PictoChat" 
      />
      {!isFocus && <Overlay />}
    </AppContainer>
  );
}

In Chat Service

window.addEventListener('message', (event) => {
  if (event.origin !== 'https://your-webxp-domain.com') return;
  
  if (event.data.type === 'SET_USERNAME') {
    setUsername(event.data.username);
  }
});

Privacy Considerations

  • Chat messages may be visible to other users
  • Service may log conversations
  • No end-to-end encryption guarantee
  • Review service privacy policy before use
  • Consider implementing your own service for sensitive communications

Network Requirements

  • Stable internet connection
  • WebSocket support (for real-time features)
  • Reasonable latency for smooth chat experience
  • Service must allow iframe embedding (no X-Frame-Options: DENY)

Error Handling

Handle cases where the service is unavailable:
function PictoChat({ isFocus }) {
  const [loadError, setLoadError] = useState(false);
  const iframeRef = useRef(null);
  
  useEffect(() => {
    const timeout = setTimeout(() => {
      setLoadError(true);
    }, 10000);
    
    return () => clearTimeout(timeout);
  }, []);
  
  return (
    <AppContainer>
      <StyledIframe 
        ref={iframeRef}
        src="https://chat.aduncan.dev"
        onLoad={() => setLoadError(false)}
        onError={() => setLoadError(true)}
        title="PictoChat" 
      />
      {loadError && (
        <ErrorOverlay>
          <p>Unable to connect to chat service</p>
          <button onClick={() => window.location.reload()}>
            Retry
          </button>
        </ErrorOverlay>
      )}
      {!isFocus && !loadError && <Overlay />}
    </AppContainer>
  );
}

Resizable Window

Unlike some other iframe apps, PictoChat is resizable:
resizable: true,
multiInstance: false,
Users can:
  • Resize the window to their preferred size
  • Maximize for full-screen chat
  • Minimize to taskbar
  • Only one PictoChat instance can be open

Default Size

The default size (400x600) is optimized for:
  • Comfortable text reading
  • Adequate drawing canvas space
  • Chat history visibility
  • Vertical orientation (portrait-like)

Performance

  • Iframe isolation prevents chat from blocking main thread
  • External hosting reduces bandwidth on your server
  • WebSocket connections managed by chat service
  • Independent rendering context for smooth animations

Accessibility

  • Keyboard navigation within chat interface
  • Screen reader support (depends on chat service)
  • Text-based communication for users who can’t draw
  • Resizable interface for visibility preferences

Build docs developers (and LLMs) love