Annotation Studio Components
Components
- Composites
- Canvas
- Confirmation Form
- Selector
- Viewer (@todo)
- Router
- Domain
- Inputs
- DatePicker
- Form
- TextArea
- TextBox
- Navigation
- Selectors
- BoxSelector
- WholeCanvasSelector
- Viewers
- StaticImageViewer
- ScaledImageViewer
- OpenSeadragonViewer
Composites
These are React components that are composed of other react components. They are still mostly pure components, handing off their state management to Redux. Some offer APIs for choosing between different components (like selectors) with the goal in the future to be smart and delegate based on different contexts to choose the best component for the job. (Think A/V IIIF content)
Canvas
This is the abstract concept of space occupied so far by Selectors, Viewers and drawn Regions or annotations.
Prop types
propType = {
onUpdateSelector: PropTypes.func,
onSaveSelector: PropTypes.func,
selector: PropTypes.shape({
type: PropTypes.string.isRequired,
left: PropTypes.number,
top: PropTypes.number,
height: PropTypes.number,
width: PropTypes.number,
}),
image: PropTypes.shape({
src: PropTypes.string.isRequired,
height: PropTypes.number.isRequired,
width: PropTypes.number.isRequired,
}).isRequired,
displayWidth: PropTypes.number.isRequired,
}
Minimum example usage
<Canvas image={{ src: '...', height: 100, width: 100 }} displayWidth={50}/>
ConfirmationForm
The confirmation form is a composite of form elements and display values. It adds a "preview" to forms to allow a better and customisable representation of the values.
Prop types
propTypes = {
draft: PropTypes.shape({
id: PropTypes.string.isRequired,
isPreviewing: PropTypes.bool.isRequired,
input: PropTypes.object.isRequired,
}),
bem: PropTypes.object,
nextLabel: PropTypes.string,
editLabel: PropTypes.string,
saveLabel: PropTypes.string,
updateField: PropTypes.func.isRequired,
onEdit: PropTypes.func,
onSave: PropTypes.func,
previewDraft: PropTypes.func.isRequired,
};
Minimum example usage
<ConfirmationForm
draft={{ id: 1, isPreviewing: this.state.previewing, input: { title: 'testing' } }}
updateField={e => console.log(e)}
previewDraft={e => this.setState({ previewing: true })}
onEdit={e => this.setState({ previewing: false })}
/>
Selector
This is the composite selector that will be most used in apps to get any selector that you require without duplicating code elsewhere.
Prop Types
propTypes = {
type: PropTypes.string.isRequired,
onSave: PropTypes.func.isRequired,
onCancel: PropTypes.func,
initialPosition: PropTypes.shape({
left: PropTypes.number,
top: PropTypes.number,
height: PropTypes.number,
width: PropTypes.number,
}),
}
Minimum example usage
<Selector type="WholeCanvasSelector"/>
Router
The router is a very basic routing component that will route in a similar way to a switch/case or if/elseif. You can define boolean logic to create complex flows through components, while remaining pure and quick.
Prop types
propTypes = {
routes: PropTypes.arrayOf(
PropTypes.shape({
matches: PropTypes.func.isRequired,
render: PropTypes.func.isRequired,
}).isRequired
),
state: PropTypes.any,
renderNotFound: PropTypes.func,
};
Minimum example usage
<Router
routes={[
{
matches: (state) => state.isFirst,
render: () => <StepOne />
},
{
matches: (state) => !state.isFinished && !state.isFirst,
render: () => <StepTwo />
},
{
matches: (state) => state.isFinished,
render: () => <LastStep />
}
]}
state={{ isFirst: true }}
/>
Domain
Note: this is likely to be removed from this part of the library as it is not strictly a component.
Validation
This is a library that will try and validate a variety of JSON-LD fields and contexts.
Usage
import validation from '@annotation-studio/components/es/domain/validation';
const fields = [
{context: 'https://schema.org/jobTitle', value: 'Some job title'},
{context: 'https://schema.org/birthDate', value: '31st Feb 1990'},
];
function validate(field) {
if (validation[field.context]) {
return validation[field.context](field.value);
}
return true;
}
function validateAll(fields) {
return fields.reduce((acc, cur) => acc && validate(field))
}
const isValid = validateAll(fields);
Inputs
Inputs are combined by a unified input field, offering an easy to compose interface to render a variety of fields. In the future this will be expanded to be more smart when choosing which field to render. Currently we have 3 example fields.
Date Picker
The date picker is a wrapper around a 3rd-party React component that really was meant to show off how these interfaces can be driven and expanded without re-inventing various wheels.
Prop types
propTypes = {
id: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
update: PropTypes.func.isRequired,
metaData: PropTypes.shape({
title: PropTypes.string.isRequired,
description: PropTypes.string
}).isRequired,
preview: PropTypes.bool,
};
Minimum example usage
<DatePicker
id={123}
value={this.state.value}
update={(value) => this.setState({ value })}
metaData={{ title: 'Pick a date' }}
/>
BEM class customisation
List of classes:
.input-text-box
.input-text-box__label
.input-text-box__description
.input-text-box__preview
.input-text-box__input
.input-text-box__input--datepicker
Form
The form is a composition of form elements, with associated meta data to drive both styling and display of elements. This will also be expanded to be more introspective of fields and perform "best guess" when choosing fields. Currently its all driven by type.
Prop types
propTypes = {
draft: PropTypes.shape({
id: PropTypes.string.isRequired,
input: PropTypes.objectOf(PropTypes.string).isRequired,
isPreviewing: PropTypes.bool
}).isRequired,
fields: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.string.isRequired,
inputType: PropTypes.oneOf(['madoc:textarea', 'madoc:datepicker', 'madoc:textbox']),
metaData: PropTypes.shape({
title: PropTypes.string.isRequired,
description: PropTypes.string,
conformsTo: PropTypes.shape({
id: PropTypes.oneOf([
'http://xmlns.com/foaf/name',
'https://schema.org/birthDate',
'https://schema.org/jobTitle',
'http://dublincore.org/documents/dcmi-terms/#terms-description'
]),
label: PropTypes.string.isRequired,
}),
}).isRequired,
})).isRequired,
updateField: PropTypes.func.isRequired,
};
Minimum example usage
<Form
draft={{ id: 1, input: this.state.values || {name: '', familyName: ''} }}
fields={[
{ id: 'name', inputType: 'madoc:textbox', metaData: {
title: 'First name'
}},
{ id: 'familyName', inputType: 'madoc:textbox', metaData: {
title: 'Family name'
}},
]}
updateField={(field, value) => this.setState(s => s.values[field.id] = value)}
/>
BEM class customisation
List of classes:
.input-form
.input-form__error
.input-form__error--hidden
TextArea
The text area is one of the most basic components binding to the DOM.
Prop Types
propTypes = {
id: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
update: PropTypes.func.isRequired,
metaData: PropTypes.shape({
title: PropTypes.string.isRequired,
description: PropTypes.string
}).isRequired,
preview: PropTypes.bool,
};
Minimum example usage
<TextArea
id={123}
value={this.state.value}
update={(value) => this.setState({ value })}
metaData={{ title: 'Tell a story' }}
/>
BEM class customisation
List of classes:
.input-text-box
.input-text-box__label
.input-text-box__description
.input-text-box__preview
.input-text-box__input
.input-text-box__input--textarea
TextField
The text area is one of the most basic components binding to the DOM.
Prop Types
propTypes = {
id: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
update: PropTypes.func.isRequired,
metaData: PropTypes.shape({
title: PropTypes.string.isRequired,
description: PropTypes.string
}).isRequired,
preview: PropTypes.bool,
};
Minimum example usage
<TextArea
id={123}
value={this.state.value}
update={(value) => this.setState({ value })}
metaData={{ title: 'First name' }}
/>
BEM class customisation
List of classes:
.input-text-box
.input-text-box__label
.input-text-box__description
.input-text-box__preview
.input-text-box__input
Navigation
The navigation section is one of the latest additions.
ResourceNavigator
The resource navigator focuses on building a UI around navigating through state by providing a path similar to path1.path2.path3
in Object notation. It binds to a UI and logic, allowing for state to be changed and the user to navigate through and render various things based on the current "location" in the state.
Prop Types
propTypes = {
selections: PropTypes.arrayOf(PropTypes.any).isRequired,
bem: PropTypes.object,
renderSelection: PropTypes.func,
onSelect: PropTypes.func.isRequired,
};
Minimum example usage
<ResourceNavigator
selections={['apples', 'oranges']}
onSelect={selection => console.log(selection)}>
Choose a fruit
</ResourceNavigator>
BEM class customisation
List of classes:
.resource-navigator
.resource-navigator__selection
Selectors
Selectors are built around highlighting a section or region of an image using a variety of tools, and being able to generate W3C valid selectors. This includes the media frags selector and also the SVG selector specifications.
BoxSelector
The box selector is a draggable box that is initially placed on a canvas. It is dragged around and is responsible for relaying back the co-ordinates that it currently exists at on the canvas (relative to the view port, for OSD)
Prop Types
propTypes = {
onSave: PropTypes.func.isRequired,
onUpdate: PropTypes.func.isRequired,
onCancel: PropTypes.func,
initialPosition: PropTypes.shape({
left: PropTypes.number,
top: PropTypes.number,
height: PropTypes.number,
width: PropTypes.number,
}),
};
Minimum example usage
<BoxSelector
onSave={position => console.log(position)}
onUpdate={position => console.log(position)}
/>
Note: the box selector can be dragged around its parents bounds. Make sure you have an appropriate container for dragging the box around and that it matches the height and width of your content.
BEM class customisation
List of classes:
WholeCanvasSelector
The whole canvas selector is a unique component, as it doesn't require a UI to work. Its job is simply to conform to the interface of other selectors and provide a way for the onSave method to be called to let the application know that it needs to target the whole application.
There is support for making the user confirm their selection before hand. You could envision cancel buttons being added at an application level too, if that fits with the user experience.
Prop types
propTypes = {
skipConfirm: PropTypes.bool,
onSave: PropTypes.func.isRequired,
onCancel: PropTypes.func,
};
Minimum example usage
<WholeCanvasSelector onSave={() => console.log('called on render')} />
Viewers
Viewers are components that display various kinds of content that you might want to annotate or surface annotations on.
Static Image Viewer
This is the most basic viewer, it takes an image, and displays it full size.
propTypes = {
resource: PropTypes.string.isRequired,
height: PropTypes.number.isRequired,
width: PropTypes.number.isRequired,
regions: PropTypes.arrayOf(
PropTypes.shape({
onClick: PropTypes.func,
left: PropTypes.number.isRequired,
top: PropTypes.number.isRequired,
height: PropTypes.number.isRequired,
width: PropTypes.number.isRequired,
selected: PropTypes.bool.isRequired,
})
)
}
Minimum example usage
<StaticImageViewer
resource="http://.../portrait.jpg"
height={200}
width={100}
/>
Scaled Image Viewer
This was the next iteration from the static image viewer, this component internally uses the static image viewer, but simply scales it down. You must specify a scale for this one to work. Note: because of the way react works, you can change the height, width and scale interactively and still achieve accurate positions of regions, this aids with responsive design
Prop types
propTypes = {
resource: PropTypes.string.isRequired,
height: PropTypes.number.isRequired,
width: PropTypes.number.isRequired,
regions: PropTypes.arrayOf(
PropTypes.shape({
onClick: PropTypes.func,
left: PropTypes.number.isRequired,
top: PropTypes.number.isRequired,
height: PropTypes.number.isRequired,
width: PropTypes.number.isRequired,
selected: PropTypes.bool.isRequired,
})
),
scale: PropTypes.number.isRequired
}
Minimum example usage
<ScaledImageViewer
resource="http://.../portrait.jpg"
height={400}
width={200}
scale={0.5}
/>
This will display the image at half the size (i.e. 100x200). You can also scale up, if required.
OpenSeadragonViewer
This is the most common choice for viewer, it consumes IIIF resources and enables deep zoom functionality. Unfortunately, not all content is the same, so this viewer will fallback as often as it can if it cannot read the resource provided.
Prop types
propTypes = {
resource: PropTypes.string.isRequired,
height: PropTypes.number.isRequired,
width: PropTypes.number.isRequired,
regions: PropTypes.arrayOf(
PropTypes.shape({
onClick: PropTypes.func,
left: PropTypes.number.isRequired,
top: PropTypes.number.isRequired,
height: PropTypes.number.isRequired,
width: PropTypes.number.isRequired,
selected: PropTypes.bool.isRequired,
})
),
scale: PropTypes.number.isRequired,
canvas: PropTypes.object.isRequired,
maxHeight: PropTypes.number
}
Minimum example usage
<OpenSeadragonViewer
resource="http://.../portrait.jpg"
height={400}
width={200}
scale={0.5}
canvas={{ '@id': '...', '@type': 'sc:Canvas', }}
/>