Metadata-Version: 2.4
Name: splent_feature_media
Version: 0.12.0
License-Expression: LGPL-3.0-or-later
Requires-Python: >=3.13
Description-Content-Type: text/markdown
License-File: COPYING
License-File: COPYING.LESSER
Requires-Dist: Pillow>=10
Requires-Dist: splent_framework>=1.20.0
Dynamic: license-file

# splent_feature_media

The `media` feature of a [SPLENT](https://splent.io) product line: a media
library to upload and manage files, the analogue of WordPress's Media
Library, with a public gallery the product may show or keep to itself.

A SPLENT feature is a Python package that a product composes with others:
its models, routes, services, templates and hooks are registered when the
product starts. What this feature provides and requires is listed in the
`[tool.splent.contract]` section of `pyproject.toml`, which
`splent feature:contract --write` keeps in sync with the source. What it
reads from the environment, and its defaults, is `[tool.splent.config]`.

## Use it in a product

```bash
splent feature:add splent_feature_media
```

## Configuration

Declared in `[tool.splent.config]`, with their types in
`[tool.splent.config_schema]`. The `runtime` ones are also fields of the
settings panel (`register_settings("media", "Media", ...)`), stored as
`media_<field>` (`media_public_gallery` for `MEDIA_PUBLIC_GALLERY`), and the
panel wins; `param('media', 'MEDIA_PUBLIC_GALLERY')` reads whichever is in
force.

| Key | Type | Bind | Default | What it does |
|---|---|---|---|---|
| `MEDIA_PUBLIC_GALLERY` | bool | runtime | `true` | The gallery page at `/media`, browsable by anyone. Off answers 404, so visitors cannot enumerate the library |
| `MEDIA_NAV_LABEL` | text | derivation | empty | The main-nav label of the gallery. Empty keeps it out of the nav. Not in the panel: the nav entry is registered at start, so a panel value would change nothing until a restart |
| `MEDIA_HOME_COUNT` | int, at least `0` | runtime | `0` | The latest public photos the home page shows, in its `home.section` slot (order 40). Zero hides the section |
| `MEDIA_GALLERY_VIEW` | enum: `grid`, `mosaic` | runtime | `grid` | How the gallery opens. A visitor may switch, and the browser remembers it |
| `MEDIA_GALLERY_PAGE_SIZE` | int, at least `1` | runtime | `48` | Photos loaded at once as the visitor scrolls |
| `MEDIA_SLIDESHOW_SECONDS` | int, at least `1` | runtime | `4` | Seconds per photo in the slideshow |
| `MEDIA_UPLOAD_IN_GALLERY` | bool | runtime | `true` | Whether the upload form ticks "show in the public gallery" |
| `MEDIA_MAX_UPLOAD_SIZE` | size | deploy | `100M` | The largest file the library takes, as `100M`, `1G` or a number of bytes. `0` means no limit |
| `PROTECTED_UPLOADS_DIR` | str | deploy | empty | Where files that are not public live. Empty is the framework's default; in production, the mount target of a named volume |
| `PROTECTED_FILES_ACCEL_PREFIX` | str, `/.+` | deploy | empty | The internal nginx prefix a protected file is handed over with (`X-Accel-Redirect`). Empty streams it through Python, which is right without a proxy in front |

A library takes files bigger than a form (slides, recordings, scanned
course material), so media raises the framework's request cap,
`MAX_CONTENT_LENGTH`, to `MEDIA_MAX_UPLOAD_SIZE`, and never lowers it. It
does so in `init_feature`, from the resolved `app.config`, not in
`inject_config` from the environment: the product's value beats a
`docker/.env` merged before the product changed it, and a cap raised from
the environment could let through files far bigger than the limit the
product set.

The gallery opens its photos in the theme's lightbox, and appends the next
page as a slideshow reaches the end (`splent:lightbox:end`). A product
without media can still show a few static images on its home with the
theme's `SITE_GALLERY`.

## Frontend

The library's image editor, on an image's detail page
(`media/admin_detail.html`), is Cropper.js: crop, rotate and reset over the
preview, and the crop saved as a new media item. Its script is
`assets/js/cropper.js`, which imports Cropper.js and its stylesheet;
`cropperjs` 1.6.2 is an npm dependency in `frontend/package.json`, locked in
`frontend/package-lock.json`, and nothing of it is vendored. The SPLENT
toolchain builds the entry `cropper` into `assets/dist/cropper.bundle.js`
and `assets/dist/cropper.css`, which the page loads, and only that page:
Cropper's `.cropper-*` classes are global. The message the editor shows when
a crop cannot be saved reaches the script in a `data-*` attribute, in the
page's language. The markup uses the shell's Bootstrap CSS, declared in
`splent.shell`, so `product:validate` checks the product provides it.

The public gallery's own script and stylesheet (`assets/js/gallery.js`,
`assets/css/gallery.css`) and the picker's (`assets/css/picker.css`) are the
feature's own code, without a build, served as they are and registered with
`register_asset`.

```bash
splent feature:compile media            # build it once, in development mode
splent feature:compile media --watch    # again on every change
splent feature:compile media --verify   # the files match assets/meta/manifest.json
splent feature:npm media outdated       # a newer Cropper.js?
```

## Develop

```bash
splent feature:test splent_feature_media
splent feature:contract splent_feature_media
```

## Release

```bash
splent feature:release splent_feature_media --dry-run --bump minor
splent feature:release splent_feature_media
```

The first command rehearses the release, its frontend gates included (the
production build, the licences of what the bundle ships and `npm audit`),
and changes nothing. The second publishes it to the git forge and to PyPI,
and a PyPI version is permanent.
