-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
162 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |