Skip to content

paperlesspaper/paperlesspaper-apps

Repository files navigation

paperlesspaper apps

This repository contains paperlesspaper integrations rendered for eInk displays. It is a Next.js app optimized for fixed-size, low-color layouts and screenshot-based rendering.

Example image

Quick start

  1. Install dependencies.
  2. Start the dev server (defaults to http://localhost:3001).
npm install
npm run dev

Available integrations

Connect to your Google Calendar. Data is provided via window.postMessage.

Displays current weather and forecast for any location using OpenWeather data.

Shows the Wikipedia “Article of the day” or “On this day”. Available in English and German.

Displays any RSS feed.

Shows pharmacies on emergency duty in Germany using the public search endpoint of aponet.de. Exposes data via /api/apothekennotdienst.

Renders a random image from a public iCloud Shared Album.

Additional routes live in src/app.

Open Integration Example (plugin provider)

This repo ships an example provider for the Open Integration system used by memo-mono.

  • Manifest (install URL): /open-integration-example/config.json
  • Settings page (iframe): /open-integration-example/settings
  • Render page (Puppeteer target): /open-integration-example/render
  • Mock OAuth/redirect helper (optional): /open-integration-example/auth

Local install into memo-mono:

  1. Start this repo (defaults to http://localhost:3001).
  2. In memo-mono, choose the Integration Plugin application.
  3. Install by config URL:

http://localhost:3001/open-integration-example/config.json

The settings iframe supports both structured messages ({ source: "wirewire-app", type: "INIT" | "REDIRECT", ... }) and the legacy { cmd: "message" | "redirect", data: ... } format.

Features

  • Style selection
  • Optimized for 7-color AcEP eInk displays (Spectra 6 coming soon)
  • Layout fitting and auto-scaling
  • Horizontal and vertical layouts
  • Multilanguage support (see src/i18n)

Components

RescaleText

Fits text into its container by adjusting font size.

Properties:

checkHeight = false
maxFontSize = 100
id = "no-id"

Usage with paperlesspaper

No extra setup is required. Each integration appears inside paperlesspaper when creating a new image.

Usage with custom ePaper displays

You need to render a route to an image and transmit it to the display.

Typical steps:

  1. Render the route using Puppeteer.
  2. Compare against the previous image to avoid redundant uploads.
  3. Dither the image.
  4. Transmit the dithered image to the display.

How to build an integration

Puppeteer rendering contract

The backend detects loading states based on the presence of these DOM elements.

#website-has-loading-element

Indicates that the page has a custom loading state. If this element exists, the backend waits for loading to finish before taking a screenshot.

Add while loading:

<div id="website-has-loading-element" />

#website-has-loaded

Signals that the page is fully loaded and ready for rendering. The backend waits for this element to appear before proceeding.

Add when ready:

<div id="website-has-loaded" />

Fallback behavior

If #website-has-loading-element is not found, the backend waits a fixed timeout (8.5 seconds) before taking a screenshot.

Loading helper (React)

A context provider that tracks the loading status of registered operations. Wrap your app (or a subtree) with this provider to enable loading state management.

Props:

  • children: ReactNode
  • finishedLoading (optional): boolean — If set to true, marks all as loaded regardless of internal state.

Behavior:

  • Renders a <div id="website-has-loaded" /> when all registered operations are finished loading.
  • Renders a <div id="website-is-loading" /> when any operation is still loading.
  • Always renders a <div id="website-has-loading-element" /> for reference.

useLoading

Registers and updates a loading operation.

Arguments:

  • id: string — Unique identifier for the loading operation.

Returns:

  • setLoading: (loading: boolean) => void

Usage example:

import { useLoading } from "../helpers/Loading";

const MyComponent = () => {
  const setLoading = useLoading({ id: "my-component" });

  useEffect(() => {
    setLoading(true);
    fetchData().finally(() => setLoading(false));
  }, []);
};

Theme via URL param

The app supports switching the theme (CSS class on <body>) via a query param.

  • Param name: color
  • Supported values:
    • dark | light
    • red-dark | red-light
    • green-dark | green-light
    • blue-dark | blue-light
  • Default: dark

In these *-dark / *-light variants, the base (dark/light) controls background + foreground, and the color controls the highlight (--accent).

Examples:

  • /weather?color=light
  • /google-calendar?color=dark
  • /weather?color=green-dark
  • /weather?color=blue-light

Implementation detail: this is handled globally in src/app/layout.tsx via the ColorVariablesFromParam component.

Usage

In your css file you can use the theme variables like this:

color: var(--background);
background: var(--foreground);
border-top: var(--accent);

Development

Scripts:

  • npm run dev — Start Next.js on port 3001
  • npm run build — Build for production
  • npm run start — Run the production server
  • npm run lint — Lint the codebase
  • npm run deploy — Deploy via Vercel

About

Integrations used in the paperlesspaper E Ink display

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages