Skip to content

Commit

Permalink
Added creator documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
stephenwf committed Sep 17, 2024
1 parent ef19080 commit c1c2d26
Show file tree
Hide file tree
Showing 2 changed files with 162 additions and 0 deletions.
1 change: 1 addition & 0 deletions apps/docs/pages/developer/_meta.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export default {
"saving-manifests": { title: "Saving manifests" },
configuration: { title: "Configuration" },
previews: { title: "Previews" },
creators: { title: "Resource creators" },
};
161 changes: 161 additions & 0 deletions apps/docs/pages/developer/creators.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
# Resource creators

The primary functionality of the Manifest Editor is based around the ability to create, update and delete resources.

A resource creator is made up of a few different parts:

- A form to create the resource, or custom UI
- An asyncronous function to save the resource
- Metadata about what it creators, and which fields

A simple example of a resource creator is the `ImageUrlCreator`.

The form is a simple text input, with a URL to an image hosted somewhere - and a button to submit the form.

```tsx
import { FormEvent } from "react";
import { InputContainer, InputLabel, Input } from "@manifest-editor/editors";
import { CreatorContext } from "@manifest-editor/creator-api";

export function CreateImageUrlForm(props: CreatorContext) {
const onSubmit = (e: FormEvent) => {
e.preventDefault();
const data = new FormData(e.target as HTMLFormElement);
const formData = Object.fromEntries(data.entries()) as any;

if (formData.url) {
props.runCreate({ url: formData.url });
}
};

return (
<form onSubmit={onSubmit}>
<InputContainer $wide>
<InputLabel htmlFor="id">Link to Image</InputLabel>
<Input id="url" name="url" defaultValue="" />
</InputContainer>

<Button type="submit">Create</Button>
</form>
);
}
```

The props passed to this component are provided by the `CreatorContext`, which includes the
`props.runCreate()` which is also defined by the creator.

This is the creator function. This is used by the Form, but can also be used by _other creators_ so you
can compose them together to create more complex resources.

```ts
import { getImageDimensions, getFormat } from "@manifest-editor/shell";
import { CreatorFunctionContext } from "@manifest-editor/creator-api";

export interface CreateImageUrlPayload {
url: string;
format?: string;
height?: number;
width?: number;
}

export async function createImageUrl(
data: CreateImageUrlPayload,
ctx: CreatorFunctionContext
): Promise<CreatorResource> {
if (!data.height || !data.width) {
const dimensions = await getImageDimensions(data.url);
if (dimensions) {
data.height = dimensions.height;
data.width = dimensions.width;
}
}

return ctx.embed({
id: data.url,
type: "Image",
format: data.format || (await getFormat(data.url)),
height: data.height,
width: data.width,
});
}
```

Whatever you return from this function is what will be saved to the parent resource.

You can see what the parent resource is by looking at the `CreatorContext` along with some helpers
for correctly creating references and linking them together in the Vault:

```ts
interface CreatorFunctionContext {
options: {
targetType: string;
target?: Reference;
parent?: CreatorParent;
initialData?: any;
};
ref(idOrRef: string | Reference): ReferencedResource;
embed(data: any): CreatorResource;
create(definition: string, payload: any, options?: Partial<CreatorOptions>): Promise<CreatorResource>;
generateId(type: string, parent?: Reference | ReferencedResource): string;
getParent(): Reference | undefined;
getTarget(): SpecificResource | Reference | undefined;
getParentResource(): SpecificResource | undefined;
getPreviewVault(): Vault;
}
```

This is required because of how the Vault stores resources as flat resources. In most
cases you want to save embedded resources to the Vault and reference them by an ID and type.

There are some exceptions - such as `service` properties, in which case you can use the `embed()` helper.

There are a lot of examples in the GitHub repository of different ways to use these functions.

These functions are bound together in a `CreatorDefinition`:

```ts
import { CreatorDefinition } from "@manifest-editor/creator-api";

export const imageUrlCreator: CreatorDefinition = {
id: "@manifest-editor/image-url-creator",
create: createImageUrl,
label: "Image",
summary: "Image from a URL",
icon: <AddImageIcon />,
render(ctx) {
return <CreateImageUrlForm {...ctx} />;
},
resourceType: "ContentResource",
resourceFields: ["format"],
supports: {
parentFields: ["logo", "body", "thumbnail"],
},
staticFields: {
type: "Image",
},
};
```

This definition specifies some components and labels that will be displayed to users when they go
to use your creator from the UI.

You can add new creators to your `mapApp` function when creating the Editor.

```ts
import { mapApp } from "@manifest-editor/shell";
import * as manifestEditorPreset from "@manifest-editor/manifest-preset";
import { myCustomCreator } from "./my-custom-creator";

const app = mapApp(manifestEditorPreset, (app) => ({
...app,
creators: [
//
...app.creators,
myCustomCreator,
],
}));
```

Now in the Manifest editor, when the "create" icon is clicked in a valid context - based on the configuration
you provided - they will see your icon and be able to click on it to use your Form component
to create a new resource.

0 comments on commit c1c2d26

Please sign in to comment.