Pinpoint is an engine for displaying hierarchical keys that identify the Earth's species.
npm install @sfgrp/pinpoint<script setup>
import { VuePinpoint } from '@sfgrp/pinpoint'
import '@sfgrp/pinpoint/style.css'
</script>
<template>
<VuePinpoint
:lead-id="103"
base-url="https://sfg.taxonworks.org/api/v1"
project-token="YOUR_TOKEN"
/>
</template>| Prop | Type | Required | Description |
|---|---|---|---|
leadId |
number | string |
yes | Id of the lead the key starts on. |
baseUrl |
string |
yes | TaxonWorks API base url. |
projectToken |
string |
yes | TaxonWorks project token. |
fetcher |
(id: number) => Promise<KeyResponse> |
no | Load the key payload from somewhere other than a TaxonWorks server. |
allowHtml |
boolean |
no | Render lead text as HTML. TaxonWorks marks up taxon names, so this is on by default. Defaults to true. |
Nothing is requested until leadId, baseUrl and projectToken are all set,
so binding them to values that arrive asynchronously is safe.
| Event | Payload | Fired when |
|---|---|---|
start |
— | A load begins. |
loading |
— | A load begins. |
end |
KeyResponse |
A load resolves. |
error |
Error |
A load rejects. |
const pinpoint = ref()
pinpoint.value.getState() // reactive store state
pinpoint.value.setCurrentNode(9) // navigate to a node by id
pinpoint.value.isLoading // computed<boolean>Every slot is declared on <VuePinpoint>, no matter how deep it renders.
Three levels of control, from most to least of the built-in UI replaced.
<VuePinpoint v-bind="config">
<template #figures="{ figures, node, open }">
<MyGallery
:images="figures"
@select="(index) => open(index)"
/>
</template>
</VuePinpoint>| Slot prop | Type | Description |
|---|---|---|
figures |
Figure[] |
Figures attached to this lead. |
node |
Node | null |
The lead the figures belong to. |
open |
(index: number) => void |
Opens the viewer on the given figure. |
<template #figure="{ figure, index, open }">
<img
:src="figure.thumb"
:alt="figure.caption ?? ''"
@click="open"
/>
</template>| Slot prop | Type | Description |
|---|---|---|
figure |
Figure |
The figure to render. |
index |
number |
Its position in figures. |
figures |
Figure[] |
The full list, for context. |
node |
Node | null |
The lead the figure belongs to. |
open |
() => void |
Opens the viewer on this figure. |
This is usually what you want: pinpoint renders the thumbnails and tracks which figure is active, your component does the actual viewing.
<template #figure-viewer="{ figure, figures, index, close, next, previous }">
<MyLightbox
:items="figures"
:start-index="index"
@close="close"
/>
</template>| Slot prop | Type | Description |
|---|---|---|
figure |
Figure |
The active figure. |
figures |
Figure[] |
The full list, so you can offer navigation. |
index |
number |
Index of the active figure. |
node |
Node | null |
The lead the figures belong to. |
close |
() => void |
Closes the viewer. |
next |
() => void |
Advances to the next figure (wraps around). |
previous |
() => void |
Goes back one figure (wraps around). |
The slot only renders once a thumbnail has been activated, so there is no need to guard on visibility yourself.
| Slot | Slot props | Replaces |
|---|---|---|
title |
{ title, metadata } |
The key title. |
loading |
{ leadId } |
Shown while a key loads. See below. |
error |
{ error } |
The error message. |
text |
{ text, node } |
Lead text, wherever it renders. |
target |
{ label, id, node } |
The target label of a lead. |
couplet-title |
{ node, coupletNumber } |
"Couplet N". |
previous-couplets-title |
— | "Previous couplets". |
button-up-label |
{ node } |
"Up". |
button-go-label |
{ node } |
"Go". |
button-next-label |
{ node } |
"Next". |
figure-next-label |
— | The viewer's next arrow. |
figure-previous-label |
— | The viewer's previous arrow. |
Rendered while a key is in flight, and removed once it resolves or fails. Nothing at all is rendered when the slot is not provided, so this is opt-in.
<VuePinpoint v-bind="config">
<template #loading>
<MySpinner />
</template>
</VuePinpoint>The rest of the key stays mounted underneath while loading, so an overlay works
without the content flickering out. If you would rather drive the state from
outside, the loading/end/error events still fire, and isLoading is
available on the component instance.
type Figure = {
id: number | null
caption: string | null
label: string | null
position: number
contentType: string | null
thumb: string // small preview
image: string // medium size, used by the built-in thumbnails
original: string // full size, project token appended
source: unknown // the untouched API payload
}source is the escape hatch: anything the adapters do not normalize is still
reachable from your own viewer.
fetcher replaces the request for the key payload. It receives the lead id and
must resolve with the raw payload, in the same shape the TaxonWorks endpoint
returns.
<VuePinpoint
:lead-id="1"
base-url="https://sfg.taxonworks.org/api/v1"
project-token="YOUR_TOKEN"
:fetcher="(id) => fetch(`/keys/${id}.json`).then((r) => r.json())"
/>baseUrl and projectToken are still required with a fetcher: the API
returns figure paths relative to the server (/api/v1/images/…), and the
adapters need both to turn them into fetchable urls.
npm run dev # demo app
npm test # unit + component tests
npm run typecheck # vue-tsc
npm run build:lib # library bundle + type declarations