digirati-co-uk / iiif-manifest-editor

Create new IIIF Manifests. Modify existing manifests. Tell stories with IIIF.
https://manifest-editor.digirati.services/
MIT License
31 stars 2 forks source link

Custom editing plugins #149

Closed tomcrane closed 1 year ago

tomcrane commented 2 years ago

From Canadiana:

When editing a manifest they would like to be able to use our a plugin that we would develop to edit our custom metadata for their manifest. Is this something that could be done by us in your design?

Notes

We want the Manifest Editor to be highly pluggable, including places where your own UI appears - but in the Wiki, these extension points have so far been at the Load/Save interface - e.g., a GitHub publishing backend.

What would it mean to develop a custom metadata editor? What does it do that the current editor does not (or more likely what does it simplify)? It still needs to produce valid IIIF, for example.

I think this needs a sketch to understand what the requirement is.

tomcrane commented 2 years ago

This is a customisation of the ME at the level of an individual RHS tab.

This might even be able to pull in the MARC record and populate some of the fields automatically.

So question is - what's the published interface / API / config for replacing a UI component with one of your own? We need some documentation for this so that Canadiana can do it.

https://github.com/digirati-co-uk/iiif-manifest-editor/blob/main/manifest-editor/src/shell/README.md

tomcrane commented 2 years ago

This could also be done as an app - a simple centre panel app that can collect metadata from external sources. This implies that this app can disable the RHS metadata panel - make it read-only.

Doing this as an app seems simpler to implement and more flexible - could have more than one source of metadata even.

stephenwf commented 2 years ago

Pattern for creating apps: https://github.com/digirati-co-uk/iiif-manifest-editor/tree/feature/storybook-ish/manifest-editor-example

This is similar to how storybook works, you install an empty manifest editor and fill it with either pre-made "Apps" or compose existing Apps left, right and center panels into new apps.

The exploratory demo here is to show that custom apps/panels don't need to be exclusively React - here it is shown using React, Vue, lighterhtml, static HTML and document.createElement dom calls.

tomcrane commented 2 years ago

Canadiana comments - see issue #202 which applies to this issue too.

Digirati comments - we need some tutorials, and "hello world" style apps. Some of which are written in plain JavaScript and are as simple as possible.

Task - come up with a list of apps. Task - make sure the model for configuring apps is sound. Task - make sure the UI for switch apps is sound

Apps can occupy different slots in the ME UI. And some might only available for particular contexts.

Note - this suggests that an app might be passed multiple references as well as the vault itself. E.g., if you want to make a custom behaviour editor, when your app is invoked it needs a reference to the vault, a reference to the Manifest that is currently being edited, and a reference to the particular resource (e.g., a Canvas) that you want to set the behavior on.

stephenwf commented 2 years ago

when your app is invoked it needs a reference to the vault, a reference to the Manifest that is currently being edited, and a reference to the particular resource

Sort of, there are helpers that are vault-linked that contain this information.

From inside an extension you will be able to use the following to get the current identifiers:

const {
    canvas,
    annotationPage,
    annotation,
    range,
    manifest,
    collection,
  } = useResourceContext();

Additionally there is "App state", which currently holds the current canvas, and allows you to switch it. This is a special case since changing the canvas can change many panels at once:

const { state } = useAppState();
// state.canvasId 

The final way is to create an extension/panel that needs to be instantiated with a resource identifier:

{
  id: "canvas-media",
  label: "Edit media item",
  requiresState: true,
  render: (state: { annotation: string }, ctx, app) => (
    <AnnotationContext annotation={state.annotation}>
      <CanvasContext canvas={app.state.canvasId}>
        <CanvasMedia key={state.annotation} onAfterDelete={ctx.current.popStack} />
      </CanvasContext>
    </AnnotationContext>
  ),
},

This defines a component that requires an annotationId be provided when "opening" the panel. So you could open it like this from anywhere in the app:

actions.open("canvas-media", { annotation: 'https://example.org/annotation-1' });

In the JSX above you can see 3 components:

The configuration shown above is analogous to how a controller might work in MVC, where all data/parameters are mapped to the UI component and navigational actions are mapped. The components themselves don't need to know about the shell.

Vault access

There are various helpers that can be used as a result of the Contexts explained above. For example, if the contexts are set then you can grab a thumbnail anywhere in the tree under that component using:

const thumbnail = useThumbnail();

This allows you to create a thumbnail component that could work under many different contexts. Similarly there are direct access to the vault + selectors:

const vault = useVault();
const somethingFromVault = useVaultSelector(v => vault.get(someIdentifier), [someIdentifier]);

This works with Reacts model ensuring that if the resource changes, the component re-renders correctly.

brittnylapierre commented 2 years ago

Everything looks good! :smile: