Skip to content

Commit

Permalink
chore: refactor create-adaptor, add to demo and fix issues with Exter…
Browse files Browse the repository at this point in the history
…nalInput
  • Loading branch information
chrisvxd committed Jul 7, 2023
1 parent 2df119c commit 39b8264
Show file tree
Hide file tree
Showing 8 changed files with 100 additions and 50 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ A `Field` represents a user input field shown in the Puck interface.
- **label** (`text` [optional]): A label for the input. Will use the key if not provided.
- **arrayFields** (`object`): Object describing sub-fields for items in an `array` input
- **[fieldName]** (`Field`): The Field objects describing the input data for each item
- **getItemSummary** (`(object, number) => string` [optional]): Function to get the name of each item when using an `array` field type
- **getItemSummary** (`(object, number) => string` [optional]): Function to get the name of each item when using the `array` or `external` field types
- **defaultItemProps** (`object` [optional]): Default props to pass to each new item added, when using a `array` field type
- **options** (`object[]`): array of items to render for select-type inputs
- **label** (`string`)
Expand Down
24 changes: 24 additions & 0 deletions apps/demo/app/configs/custom/blocks/Hero/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import styles from "./styles.module.css";
import { getClassNameFactory } from "@measured/puck/lib";
import { Button } from "@measured/puck/components/Button";
import { Section } from "../../components/Section";
import createAdaptor from "@measured/puck-adaptor-fetch/index";

const getClassName = getClassNameFactory("Hero", styles);

export type HeroProps = {
_data?: object;
title: string;
description: string;
align?: string;
Expand All @@ -18,8 +20,30 @@ export type HeroProps = {
buttons: { label: string; href: string; variant?: "primary" | "secondary" }[];
};

const quotesAdaptor = createAdaptor(
"Quotes API",
"https://api.quotable.io/quotes",
(body) =>
body.results.map((item) => ({
...item,
title: item.author,
description: item.content,
}))
);

export const Hero: ComponentConfig<HeroProps> = {
fields: {
_data: {
type: "external",
adaptor: quotesAdaptor,
adaptorParams: {
resource: "movies",
url: "http://localhost:1337",
apiToken:
"1fb8c347243145a8824481bd1008b95367677654ebc1f06c5a0a766e57b8859bcfde77f9b21405011f20eb8a13abb7b0ea3ba2393167ffc4a2cdc3828586494cc9983d5f1db4bc195ff4afb885aa55ee28a88ca796e7b883b9e9b80c98b50eadf79f5a1639ce8e2ae4cf63c2e8b8659a8cbbfeaa8e8adcce222b827eec49f989",
},
getItemSummary: (item: any) => item.content,
},
title: { type: "text" },
description: { type: "textarea" },
buttons: {
Expand Down
15 changes: 11 additions & 4 deletions packages/adaptor-fetch/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,18 @@ Create an adaptor using the [fetch() API](https://developer.mozilla.org/en-US/do
```jsx
import createAdaptor from "@measured/puck-adaptor-fetch";

const movieAdaptor = createAdaptor("http://localhost:1337/api/movies", {
headers: {
Authorization: "Bearer abc123",
const movieAdaptor = createAdaptor(
"Movies API",
"http://localhost:1337/api/movies",
// Optional request data
{
headers: {
Authorization: "Bearer abc123",
},
},
});
// Optional mapping function
(body) => body.data
);
```

Configure your Puck instance. In this case, we add our `movieAdaptor` to the `movie` field on our "MovieBlock" component:
Expand Down
47 changes: 36 additions & 11 deletions packages/adaptor-fetch/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,40 @@
export const createAdaptor = (
import { Adaptor } from "@measured/puck";

function createAdaptor(
name: string,
input: RequestInfo | URL,
map?: (data: any) => Record<string, any>[]
): Adaptor;

function createAdaptor(
name: string,
input: RequestInfo | URL,
init?: RequestInit,
name: string = "API"
) => ({
name,
fetchList: async () => {
const res = await fetch(input, init);
const body: { data: Record<string, any>[] } = await res.json();

return body.data;
},
});
map?: (data: any) => Record<string, any>[]
): Adaptor;

function createAdaptor(
name: string,
input: RequestInfo | URL,
initOrMap?: unknown,
map?: (data: any) => Record<string, any>[]
): Adaptor {
return {
name,
fetchList: async () => {
if (typeof initOrMap === "function") {
const res = await fetch(input);
const body = await res.json();

return initOrMap(body);
}

const res = await fetch(input, initOrMap as RequestInit);
const body = await res.json();

return map ? map(body) : body;
},
};
}

export default createAdaptor;
42 changes: 17 additions & 25 deletions packages/adaptor-strapi/index.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,19 @@
import { Adaptor } from "@measured/puck/types/Config";
import createAdaptor from "@measured/puck-adaptor-fetch/index";

type AdaptorParams = { apiToken: string; url: string; resource: string };
const createStrapiAdaptor = (url: string, apiToken: string) =>
createAdaptor(
"Strapi.js",
url,
{
headers: {
Authorization: `bearer ${apiToken}`,
},
},
(body) =>
body.data.map(({ attributes, ...item }) => ({
...item,
...attributes,
}))
);

const strapiAdaptor: Adaptor<AdaptorParams> = {
name: "Strapi.js",
fetchList: async (adaptorParams) => {
if (!adaptorParams) {
return null;
}

const res = await fetch(
`${adaptorParams.url}/api/${adaptorParams.resource}`,
{
headers: {
Authorization: `bearer ${adaptorParams.apiToken}`,
},
}
);

const body: { data: Record<string, any>[] } = await res.json();

return body.data;
},
};

export default strapiAdaptor;
export default createStrapiAdaptor;
16 changes: 9 additions & 7 deletions packages/core/components/ExternalInput/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ export const ExternalInput = ({
>
{/* NB this is hardcoded to strapi for now */}
{selectedData
? selectedData.attributes.title
? field.getItemSummary
? field.getItemSummary(selectedData)
: `${field.adaptor.name} item`
: `Select from ${field.adaptor.name}`}
</button>
{selectedData && (
Expand All @@ -75,18 +77,18 @@ export const ExternalInput = ({
<table>
<thead>
<tr>
{Object.keys(data[0].attributes).map((key) => (
{Object.keys(data[0]).map((key) => (
<th key={key} style={{ textAlign: "left" }}>
{key}
</th>
))}
</tr>
</thead>
<tbody>
{data.map((item) => {
{data.map((item, i) => {
return (
<tr
key={item.id}
key={i}
style={{ whiteSpace: "nowrap" }}
onClick={(e) => {
onChange(item);
Expand All @@ -96,8 +98,8 @@ export const ExternalInput = ({
setSelectedData(item);
}}
>
{Object.keys(item.attributes).map((key) => (
<td key={key}>{item.attributes[key]}</td>
{Object.keys(item).map((key) => (
<td key={key}>{item[key]}</td>
))}
</tr>
);
Expand All @@ -106,7 +108,7 @@ export const ExternalInput = ({
</table>
</div>
) : (
<div>No content</div>
<div style={{ padding: 24 }}>No content</div>
)}
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion packages/core/components/Puck/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,7 @@ export function Puck({
} else {
const changedFields = filter(
// filter out anything not supported by this component
(value as any).attributes, // TODO type properly after getting proper state library
value,
Object.keys(fields)
);

Expand Down
2 changes: 1 addition & 1 deletion packages/core/types/Config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export type Field<
arrayFields?: {
[SubPropName in keyof Props]: Field<Props[SubPropName]>;
};
getItemSummary?: (item: Props, index: number) => string;
getItemSummary?: (item: Props, index?: number) => string;
defaultItemProps?: Props;
options?: {
label: string;
Expand Down

0 comments on commit 39b8264

Please sign in to comment.