diff --git a/.github/workflows/podman-image.yml b/.github/workflows/podman-image.yml
index 3118fc87..dc1602a3 100644
--- a/.github/workflows/podman-image.yml
+++ b/.github/workflows/podman-image.yml
@@ -43,7 +43,7 @@ jobs:
${{ runner.os }}-18-pnpm-store-
- run: pnpm i --frozen-lockfile
- - run: pnpm run build
+ - run: VITE_DOCS=true pnpm run build:all
- run: pnpm run test
# Unchanged from the original.
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 00000000..66a265e7
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1,2 @@
+public-hoist-pattern[]=@babel/*
+public-hoist-pattern[]=@mdx-js/*
diff --git a/README.md b/README.md
index 427c776f..35281f93 100644
--- a/README.md
+++ b/README.md
@@ -2,4 +2,54 @@
Timeliner is a reimplementation of [Variations Audio Timeliner](http://variations.sourceforge.net/vat/index.html) as a web application, using the [IIIF Presentation API 3.0](https://iiif.io/api/presentation/3.0/).
-[Documentation](https://iiif-timeliner.netlify.com/docs) | [User stories](https://github.com/digirati-co-uk/timeliner/issues?q=is%3Aissue+is%3Aopen+label%3A"%3Abusts_in_silhouette%3A+user+story") ([Kanban](https://github.com/digirati-co-uk/timeliner)) | [UX Wireframe](https://preview.uxpin.com/874bd44d74fc6062565cd95dc2dfc9e694b6ed4f#/pages/92279172/simulate/no-panels?mode=i) | [Original timeliner](http://variations.indiana.edu/use/timelines.html) | [Demo site](https://iiif-timeliner.netlify.com) | [Changelog](https://github.com/digirati-co-uk/timeliner/issues?q=is%3Aissue+is%3Aclosed+milestone%3A"UI+Components+1.0")
+## Installation
+
+Node 18+ and `yarn` are required.
+
+```
+yarn install
+```
+
+## Development
+
+Start the main application at at [http://localhost:5173/](http://localhost:5173/) with hot-reload using,
+
+```
+yarn start
+```
+
+Start the documentation site in development mode at [http://localhost:3000/](http://localhost:3000/) using,
+
+```
+yarn docz:dev
+```
+
+## Building
+
+Build the main application,
+
+```
+yarn build
+```
+
+Build the main application and documentation together,
+
+```
+yarn build:all
+```
+
+Preview the production build (main app + docs) at [http://localhost:5173/](http://localhost:5173/) and docs at [http://localhost:5173/docs](http://localhost:5173/docs) using,
+
+```
+yarn preview
+```
+
+## Testing
+
+```
+yarn test
+```
+
+## Links
+
+[Documentation](https://timeliner.dlib.indiana.edu/docs) | [User stories](https://github.com/digirati-co-uk/timeliner/issues?q=is%3Aissue+is%3Aopen+label%3A"%3Abusts_in_silhouette%3A+user+story") | [UX Wireframe](https://preview.uxpin.com/874bd44d74fc6062565cd95dc2dfc9e694b6ed4f#/pages/92279172/simulate/no-panels?mode=i) | [Original timeliner](http://variations.indiana.edu/use/timelines.html) | [Demo site](https://timeliner.dlib.indiana.edu/) | [Changelog](https://github.com/digirati-co-uk/timeliner/issues?q=is%3Aissue+is%3Aclosed+milestone%3A"UI+Components+1.0")
diff --git a/doczrc.js b/doczrc.js
new file mode 100644
index 00000000..0b8481de
--- /dev/null
+++ b/doczrc.js
@@ -0,0 +1,65 @@
+const path = require('path');
+const webpack = require('webpack');
+
+export default {
+ title: 'Timeliner',
+ repository: "https://github.com/IUBLibTech/timeliner",
+ description: 'IIIF Timeliner Documentation',
+ base: process.env.DOCZ_BASE || '/docs',
+ dest: './dist/docs',
+ src: './src',
+ wrapper: 'src/components/docs/components/DocsWrapper/DocsWrapper',
+ files: '**/*.{md,markdown,mdx}',
+ menu: [
+ 'Getting started',
+ 'User stories',
+ 'Technical Documentation',
+ ],
+ onCreateWebpackChain(config) {
+ // Force all imports of 'docz' to resolve to the same copy so that the
+ // shared React context is a single instance.
+ config.resolve.alias.set(
+ 'docz',
+ path.resolve(__dirname, 'node_modules/docz')
+ );
+ // Add SCSS support
+ config.module
+ .rule('scss')
+ .test(/\.scss$/)
+ .use('style-loader')
+ .loader('style-loader')
+ .end()
+ .use('css-loader')
+ .loader('css-loader')
+ .options({ sourceMap: false })
+ .end()
+ .use('sass-loader')
+ .loader('sass-loader')
+ .options({
+ implementation: require('sass'),
+ sourceMap: false,
+ })
+ .end();
+
+ // Plain CSS files (e.g. from third-party imports)
+ config.module
+ .rule('css')
+ .test(/\.css$/)
+ .use('style-loader')
+ .loader('style-loader')
+ .end()
+ .use('css-loader')
+ .loader('css-loader')
+ .options({ sourceMap: false })
+ .end();
+
+ // Expose VITE_DOCS to the docz/webpack bundle for conditional rendering in DocsWrapper
+ config
+ .plugin('define')
+ .use(webpack.DefinePlugin, [{
+ 'process.env.VITE_DOCS': JSON.stringify(process.env.VITE_DOCS)
+ }]);
+
+ return config;
+ },
+};
diff --git a/index.html b/index.html
index e21d13ba..d49c1966 100644
--- a/index.html
+++ b/index.html
@@ -1,46 +1,46 @@
-
-
-
-
-
-
- Timeliner
-
-
-
-
-
IIIF Timeliner
-
-
- Timeliner is a reimplementation of Variations Audio Timeliner as a web
- application, using the IIIF Presentation API 3.0. As with the original
- version, developed as a part of the Variations Digital Music Library
- System, it is an audio annotation and analysis tool for creating and
- labeling bubble diagrams. These diagrams can be used to navigate music
- or other audio for detailed study.
-
-
- In addition to its use as a standalone application, the Timeliner is
- available as integrated feature within the successor to Variations,
- Avalon Media System. Avalon users can create new bubble diagrams
- directly from item pages within their Avalon instance and edit, share
- and copy timelines across all items in the repository.
-
-
- Please note that due to standard browser security, audio resources
- used with Timeliner must be set up for Cross-origin resource sharing
- (CORS). This is not necessarily the case for many resources on the
- web, and not all URLs for media files may work.
-
-
+
+
+
+
+
+
+
+ Timeliner
+
+
+
+
+
+
IIIF Timeliner
+
+
+ Timeliner is a reimplementation of Variations Audio Timeliner as a web
+ application, using the IIIF Presentation API 3.0. As with the original
+ version, developed as a part of the Variations Digital Music Library
+ System, it is an audio annotation and analysis tool for creating and
+ labeling bubble diagrams. These diagrams can be used to navigate music
+ or other audio for detailed study.
+
+
+ In addition to its use as a standalone application, the Timeliner is
+ available as integrated feature within the successor to Variations,
+ Avalon Media System. Avalon users can create new bubble diagrams
+ directly from item pages within their Avalon instance and edit, share
+ and copy timelines across all items in the repository.
+
+
+ Please note that due to standard browser security, audio resources
+ used with Timeliner must be set up for Cross-origin resource sharing
+ (CORS). This is not necessarily the case for many resources on the
+ web, and not all URLs for media files may work.
+
;
Table.Header = ({ children }) => (
{children}
);
+Table.Body = ({ children }) => (
+ {children}
+);
Table.Row = ({ children }) =>
{children}
;
Table.Cell = ({ children, ...props }) => (
diff --git a/src/components/docs/index.mdx b/src/components/docs/index.mdx
index 35e84814..1803b10d 100644
--- a/src/components/docs/index.mdx
+++ b/src/components/docs/index.mdx
@@ -2,18 +2,32 @@
name: Getting started
route: /
order: 1
+edit: false
---
# Timeliner documentation
## Installation
To install the development environment you will require `node@8.11.x` and `yarn` installed.
-To install run: `$ yarn`
+To install run:
+```
+yarn install
+```
-To start the main application run: `$ yarn start`
+To start the main application with hot-reload for code changes, run:
+```
+yarn start
+```
+and your server will be available at [http://localhost:5173/](http://localhost:5173/)
-and your server will be available at [http://localhost:5000/](http://localhost:5000/)
+To start this documentation site in development-mode, run:
+```
+yarn docz:dev
+```
+and the documentation will be available at [http://localhost:3000/](http://localhost:3000/)
-To start this documentation application, run: `$ yarn docz:dev`
-
-and the documentation will be available at [http://localhost:5001/](http://localhost:5001/)
\ No newline at end of file
+To start the main application with documentation site previewing the prodcuction site, run:
+```
+yarn preview
+```
+and the main site and documentation will be available at [http://localhost:5173/](http://localhost:5173/) and [http://localhost:5173/docs](http://localhost:5173/docs) respectively
diff --git a/src/components/docs/technical-documentation/iiif-output.mdx b/src/components/docs/technical-documentation/iiif-output.mdx
index 7f43affd..0cf7d9bf 100644
--- a/src/components/docs/technical-documentation/iiif-output.mdx
+++ b/src/components/docs/technical-documentation/iiif-output.mdx
@@ -1,6 +1,7 @@
---
-title: IIF Output
+title: IIIF Output
menu: Technical Documentation
+edit: false
---
# IIIF Output
diff --git a/src/components/docs/technical-documentation/state-management.mdx b/src/components/docs/technical-documentation/state-management.mdx
index 31308117..6711762f 100644
--- a/src/components/docs/technical-documentation/state-management.mdx
+++ b/src/components/docs/technical-documentation/state-management.mdx
@@ -1,6 +1,7 @@
---
title: Timeliner State Management
menu: Technical Documentation
+edit: false
---
# Timeliner State Management
@@ -22,6 +23,7 @@ import Table from '../components/Table/Table';
Selected (0, 1, n)
+ Bubblemany
@@ -52,6 +54,7 @@ import Table from '../components/Table/Table';
Canvas1
+
@@ -80,6 +83,7 @@ The project represents a Manifest presentation v3 object.
Details
+ bubblesStylestring
@@ -152,6 +156,7 @@ Maps to IIIF behaviour for the rounded or square bubbles setting
yesMaps to metadata
+
@@ -182,6 +187,7 @@ Maps to IIIF behaviour for the rounded or square bubbles setting
Details
+ urlstring
@@ -210,6 +216,7 @@ Maps to IIIF behaviour for the rounded or square bubbles setting
yesAudio file relative current time
+
@@ -234,6 +241,7 @@ Maps to IIIF behaviour for the rounded or square bubbles setting
Details
+ startTimenumber
@@ -276,6 +284,7 @@ Maps to IIIF behaviour for the rounded or square bubbles setting
yesIs the bubble selected.
+
@@ -300,6 +309,7 @@ The view state stores the editor UI's current state. The view state is not getti
Details
+ isPlayingbool
@@ -350,6 +360,7 @@ The view state stores the editor UI's current state. The view state is not getti
yesThe volume is global and the value is between 0..100.
+
@@ -367,8 +378,9 @@ Any behaviours that happen that are not mutations. These could be multiple value
Affected entities
+
- General behaviours
+ General behavioursOpen Import Modal
@@ -396,7 +408,7 @@ Any behaviours that happen that are not mutations. These could be multiple value
Viewport, Bubbles
- Playback related behaviours
+ Playback related behavioursPlay/pause
@@ -434,7 +446,7 @@ Any behaviours that happen that are not mutations. These could be multiple value
Viewport, Audio
- Editing related behaviours
+ Editing related behavioursUpdate Bubble constraints
@@ -466,6 +478,7 @@ Any behaviours that happen that are not mutations. These could be multiple value
Removes all bubbles, stops the playback, moves the played to zero, resets the button statesProject, Viewport, Bubble
+
@@ -483,6 +496,7 @@ A value that is derived from the models, but is not stored in state. Will probab
Affected entities
+ Formatted Current TimeFormatted version of the current playhead position in `mm:ss` and `hh:mm:ss` formats.
@@ -523,6 +537,7 @@ A value that is derived from the models, but is not stored in state. Will probab
With regards the current time, the 'current Bubbles', 'duration' and 'is playing' the transport bar buttons will be updated on the disabled/enabled state and the play/pause buttons icon.ViewState, Bubble
+
diff --git a/src/components/docs/user-stories/audio.mdx b/src/components/docs/user-stories/audio.mdx
index 119db69f..bc2fcf73 100644
--- a/src/components/docs/user-stories/audio.mdx
+++ b/src/components/docs/user-stories/audio.mdx
@@ -1,6 +1,7 @@
---
title: Audio
menu: User stories
+edit: false
---
# A/V User stories
diff --git a/src/components/docs/user-stories/bubble.mdx b/src/components/docs/user-stories/bubble.mdx
index 8a315ae1..c7d2c665 100644
--- a/src/components/docs/user-stories/bubble.mdx
+++ b/src/components/docs/user-stories/bubble.mdx
@@ -1,6 +1,7 @@
---
title: Bubble
menu: User stories
+edit: false
---
# Bubble user stories
@@ -250,4 +251,4 @@ const point = {
isSelected: false,
colour: '#ff0000',
};
-```
\ No newline at end of file
+```
diff --git a/src/components/docs/user-stories/metadata.mdx b/src/components/docs/user-stories/metadata.mdx
index 579c53de..6f3314d6 100644
--- a/src/components/docs/user-stories/metadata.mdx
+++ b/src/components/docs/user-stories/metadata.mdx
@@ -1,6 +1,7 @@
---
name: Metadata
menu: User stories
+edit: false
---
# Metadata user stories
diff --git a/src/containers/Audio/Audio.Demo.jsx b/src/containers/Audio/Audio.demo.jsx
similarity index 100%
rename from src/containers/Audio/Audio.Demo.jsx
rename to src/containers/Audio/Audio.demo.jsx
diff --git a/src/containers/Audio/Audio.jsx b/src/containers/Audio/Audio.jsx
index e798f51a..9de98644 100644
--- a/src/containers/Audio/Audio.jsx
+++ b/src/containers/Audio/Audio.jsx
@@ -1,124 +1,35 @@
-import React, { useLayoutEffect, useRef, useState } from 'react';
+import React, { useRef } from 'react';
+import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { mediaLoading, mediaLoaded, mediaError } from '../../actions/canvas';
-
import { setCurrentTime, finishedPlaying, seek } from '../../actions/viewState';
+import useMediaPlayer from '../../hooks/useMediaPlayer';
-// Media Element
-import 'mediaelement/standalone';
-import useEventListener from '../../hooks/useEventListener';
-import useInterval from '../../hooks/useInterval';
-import { ERROR_CODES } from '../../constants/canvas';
-
-const { MediaElement } = window;
-
-function Audio({ url, volume, currentTime, startTime, isPlaying, ...props }) {
+function AudioPlayer({ url, ...props }) {
const audio = useRef();
- const player = useRef();
- const [duration, setDuration] = useState();
- const [loaded, setLoaded] = useState();
- const sources = [{ src: url }];
- const lastTime = useRef(() => startTime - 1);
-
- // Bootstrap the element.
- useLayoutEffect(() => {
- const element = new MediaElement(
- audio.current,
- {
- startVolume: volume / 100,
- currentTime: currentTime / 1000,
- },
- sources
- );
- player.current = element;
- setLoaded(false);
- return () => {
- element.remove();
- };
- }, []);
-
- useEventListener(player, 'error', event => {
- if (event && event.type === 'error') {
- // This will need to be refined.
- props.mediaError('error', ERROR_CODES.MEDIA_ERR_NETWORK);
- }
- });
-
- // Loop timer for calculating current time.
- useInterval(
- () => {
- const position = player.current.getCurrentTime();
- const relPosition = position * 1000 - startTime;
- if (position * 1000 !== lastTime.current) {
- lastTime.current = position * 1000;
- props.setCurrentTime(relPosition);
- }
-
- if (player.current.readyState && loaded === false) {
- setDuration(props.runTime || player.current.duration * 1000);
- props.mediaLoading(
- 1,
- 1,
- props.runTime || player.current.duration * 1000
- );
- props.mediaLoaded(true);
- setLoaded(true);
- }
-
- if (relPosition >= duration && isPlaying) {
- props.finishedPlaying();
- }
- },
- 1000 / 5,
- [loaded]
- );
-
- // Handle play/pause
- useLayoutEffect(() => {
- if (player.current) {
- if (isPlaying) {
- player.current.play();
- } else {
- if (player.current.readyState) {
- player.current.pause();
- }
- }
- }
- }, [isPlaying, url]);
-
- // Handle volume change.
- useLayoutEffect(() => {
- if (player.current) {
- player.current.setVolume(volume / 100);
- }
- }, [volume, url]);
-
- // Handle user-changed current time.
- useLayoutEffect(() => {
- if (player.current && currentTime !== lastTime.current) {
- // Toggle isSeeked flag in the state
- props.seek(!props.isSeeked);
- lastTime.current = currentTime;
- player.current.setCurrentTime(currentTime / 1000);
- }
- }, [currentTime, url]);
-
- if (!url) {
- return null;
- }
+ useMediaPlayer(audio, { url, ...props });
return (