@annotation-studio/components

annotation-components React component

Stats

stars 🌟issues ⚠️updated 🛠created 🐣size 🏋️‍♀️
@annotation-studio/components
Minified + gzip package size for @annotation-studio/components in KB

Readme

Annotation Studio Components

Components

  • Composites
    • Canvas
    • Confirmation Form
    • Selector
    • Viewer (@todo)
    • Router
  • Domain
    • Validation
  • Inputs
    • DatePicker
    • Form
    • TextArea
    • TextBox
  • Navigation
    • Navigation Resource
  • 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); // False since 31st Feb is invalid.

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:

  • .box-selector
    • .box-selector__button

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, // unused
};

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 // this is unique to this component.
}

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, // this is the IIIF Canvas, fully loaded, not an ID.
  maxHeight: PropTypes.number // Defaults to height*scale
}

Minimum example usage

<OpenSeadragonViewer
    resource="http://.../portrait.jpg" // this is required in case a fallback is required
    height={400}
    width={200}
    scale={0.5}
    canvas={{ '@id': '...', '@type': 'sc:Canvas', /*...*/ }}
  />

If you find any bugs or have a feature request, please open an issue on github!

The npm package download data comes from npm's download counts api and package details come from npms.io.