npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@milaboratories/structure-viewer

v0.2.0

Published

Vue component wrapping Mol* (molstar) for 3D macromolecular structure viewing.

Readme

@milaboratories/structure-viewer

Vue component wrapping Mol* for 3D macromolecular structure viewing. Renders a single PDB file from a Platforma blob handle with controls for representation type and color theme, a hover-synced sequence strip, click-to-focus camera, and PDB export.

Usage

<script setup lang="ts">
import { PlStructureViewer } from "@milaboratories/structure-viewer";
import type { LocalBlobHandle, RemoteBlobHandle } from "@platforma-sdk/model";

const handle: LocalBlobHandle | RemoteBlobHandle = /* ... */;
</script>

<template>
  <PlStructureViewer :handle="handle" file-name="structure.pdb" />
</template>

Props:

| Prop | Type | Default | Notes | | ---------- | ------------------------------------- | ----------------- | ------------------------------------ | | handle | LocalBlobHandle \| RemoteBlobHandle | required | Platforma blob handle for a PDB file | | fileName | string | "structure.pdb" | Suggested name for the export dialog |

The component fills its parent — give it a flex parent or sized container.

File map

src/
├── PlStructureViewer.vue   Root component. Composes Toolbar + SequenceStrip
│                           + canvas + hover-label overlay.
├── Toolbar.vue             Representation / coloring dropdowns + Export.
├── SequenceStrip.vue       One polymer chain's residue strip; emits
│                           hover/leave/click; receives `isHighlighted` callback.
├── useStructureViewer.ts   Composable: plugin lifecycle, load pipeline,
│                           representation updates, hover sync, sequence-strip
│                           construction, export.
├── types.ts                Pure type module.
├── utils.ts                Generic helpers: `run` (serialize-and-supersede
│                           async wrapper) and `rafThrottle`.
└── index.ts                Barrel.

Architecture

Plugin spec is hand-rolled

The Mol* PluginContext is constructed with only the behaviors the viewer actually exercises:

  • Representation.HighlightLoci
  • Representation.DefaultLociLabelProvider
  • Camera.FocusLoci
  • Camera.CameraControls
  • Camera.CameraAxisHelper
  • CustomProps.SecondaryStructure

Everything else from DefaultPluginSpec (SelectLoci, StructureFocusRepresentation, SnapshotControls, the other CustomProps, all actions, all animations) is omitted.

Load pipeline

useStructureViewer.load() matches Viewer.loadStructureFromData in mol-plugin-ui/apps/viewer/app.ts:229:

blobDriver.getContent(handle)
  → plugin.builders.data.rawData({ data: text })
  → plugin.builders.structure.parseTrajectory(data, "pdb")
  → plugin.builders.structure.hierarchy.applyPreset(trajectory, "default")

The default preset splits the structure into polymer / ligand / water / branched / ion / lipid / coarse components, each tagged static-<kind>.

Representation updates are component-kind aware

User-facing representation type (cartoon, ball-and-stick, gaussian-surface, putty) only makes sense for polymer atoms. Applying e.g. cartoon to a ligand component would blank it out.

applyRepresentation therefore branches:

  • Polymer components (tags?.includes("structure-component-static-polymer")) get their full representation params replaced (type + color) via createStructureRepresentationParams.
  • All other components keep the preset's default type and only have their color theme refreshed via createStructureColorThemeParams.

Both branches batch into one state.data.build() commit.

Concurrent loads supersede

Each call to load() swaps an internal token. The previous in-flight body's next step(promise) throws StaleOpError when its token goes stale, and the next call awaits the previous body's settlement before running so plugin mutations don't interleave. load.abort() swaps the token without enqueuing — used by dispose() to drop in-flight work on unmount.

applyRepresentation doesn't go through run(). Its invariant is "every mutation comes from a single state.data.build() whose commit serializes through Mol*'s own commit queue." Concurrent watcher firings produce two back-to-back commits; the second wins. Data-state mounting is fenced by load calling applyRepresentation() before flipping hasContent, so the user-facing watcher only sees fully-mounted hierarchies.

Bidirectional sequence-strip hover sync

Mol*'s wrapper objects keep an internal mutable markerArray (one byte per residue: bit 0 = highlighted, bit 1 = selected). Upstream's React Sequence component imperatively walks the DOM in updateMarker() to bypass React's reactivity — fine for them, not directly portable.

We bridge the bitmap into Vue reactivity with a single Symbol-valued shallowRef:

  • Composable installs an onLociMark provider on plugin.managers.interactivity.lociHighlights.
  • The provider calls wrapper.markResidue(loci, action) for every visible chain; if any wrapper reports a change, markerVersion.value = Symbol().
  • Parent's isResidueHighlighted(strip, idx) reads markerVersion.value (tracked) before consulting wrapper.isHighlighted(idx), so dependent bindings re-evaluate on every change.
  • SequenceStrip receives :is-highlighted="(idx) => isResidueHighlighted(strip, idx)" per-strip — child has zero awareness of the marker version (clean prop boundary).

Hover the strip → highlights propagate to the 3D canvas; hover the canvas → highlights propagate to the strip.

Hover events from the strip are coalesced via requestAnimationFrame (rafThrottle in utils.ts), so molstar updates align with the next paint instead of an off-axis wall-clock timer (upstream uses an rxjs throttleTime(50ms) instead).

Chain enumeration is in-scope-minimal

buildSequenceStrips does the minimal walk that covers ImmuneBuilder antibody PDBs: group atomic polymer units by chainGroupId, instantiate PolymerSequenceWrapper({ structure, units }) per group, derive the chain label from StructureProperties.chain.auth_asym_id. About 20 lines, no helpers imported from upstream.

The four upstream enumeration helpers (getModelEntityOptions, getChainOptions, getOperatorOptions, getSequenceWrapper in mol-plugin-ui/sequence.tsx) handle multi-model NMR ensembles, multi-entity complexes, and crystallographic operators (NCS / HKL / spgr op) — none of which the in-scope inputs require. They live in a .tsx file that imports React / react/jsx-runtime / PluginUIComponent / controls/icons at top level; with molstar's package.json declaring no sideEffects, naïve consumption drags ~100 KB minified of React UI into the consumer's bundle (measured: 8 React Component subclasses + JSX render sites). Doing the walk ourselves avoids that import path entirely.

Limitations

For inputs outside the antibody-PDB shape the viewer still loads and renders, but the sequence strip degrades:

  • Crystallographic operators (NCS, HKL, space-group): multiple poses of the same chain collapse into one strip instead of one per operator.
  • Multi-model NMR ensembles: chains from different models aren't differentiated; they merge by chainGroupId.
  • Very long polymer chains (>5000 residues): rendered as a giant strip; upstream falls back to ChainSequenceWrapper / ElementSequenceWrapper to keep the DOM small.
  • Ligand / hetero residue sequences: not displayed (upstream uses HeteroSequenceWrapper); ligands still render in 3D under their preset-default representation.
  • No selection state: only highlight (hover) is wired. No shift-drag range select, no Ctrl-click to add/remove selection.
  • No focus state: clicking a residue zooms the camera but doesn't paint a focus indicator on the strip.
  • Sequence-number period is hardcoded to 10; upstream picks 1/5/10 dynamically based on chain length.
  • Hover labels rendered with v-html: trusted molstar label provider output that may include <small> etc. Not sanitized.
  • Export uses window.showSaveFilePicker: relies on the File System Access API. Works in Chromium-based hosts (Electron, Chrome). Will reject the export silently on Firefox/Safari.

Revisit the chain-enumeration walk if the consumer ever needs to render generic PDBs.

Reference paths in upstream Mol*

Used during the original port; these are useful anchors when bumping molstar.

| Path | What | | ------------------------------------------------------ | ----------------------------------------------------- | | mol-plugin/spec.ts:121 | DefaultPluginSpec.behaviors (whitelist source) | | mol-plugin-ui/apps/viewer/app.ts:229 | loadStructureFromData shape | | mol-plugin-ui/state/update-transform.tsx:32 | State Tree update primitive | | mol-plugin-state/manager/structure/component.ts:295 | updateRepresentationsTheme (color-only update path) | | mol-plugin-ui/sequence/polymer.ts | PolymerSequenceWrapper class | | mol-plugin-ui/sequence/sequence.tsx:263 | getSequenceNumber (atomic / coarse branches) | | mol-plugin-ui/sequence/sequence.tsx:280 | padSeqNum (NBSP padding to width 5) | | mol-plugin-ui/skin/base/components/sequence.scss:105 | Sequence-number float CSS | | mol-plugin-state/manager/loci-label.ts:63 | LociLabelManager highlight observable |