Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions examples/plugins/ecommerce/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ async function syncRecords(collection, rows, keep = false) {
}

/** Field keys a layer shows of a product, by what the layer or a component property is called. */
/** @type {[RegExp, string][]} */
const LINKS = [[/title|name/i, 'title'], [/price/i, 'price'], [/image|picture|photo|cover/i, 'image'], [/description|body/i, 'description']]

/**
Expand All @@ -139,20 +140,21 @@ const LINKS = [[/title|name/i, 'title'], [/price/i, 'price'], [/image|picture|ph
async function link(product) {
const col = await collectionFor('commerce.product')
if (!col) throw new Error('This design has no products collection: Make it a store first')
const ids = await popcraft.getSelection()
// getSelection gives nodes; setRecord and bindField take their ids.
const ids = (await popcraft.getSelection()).map(n => n.id)
if (!ids.length) throw new Error('Select the layers that should show this product first')
await syncRecords(col, [product], true)
await popcraft.setRecord({ ids, collection: col.id, record: product.handle })
let bound = 0
for (const id of ids) {
const node = await popcraft.getNode(id)
if (!node) continue
const has = node.boundExpressions ?? {}
const has = /** @type {Record<string, unknown>} */ (node.boundExpressions ?? {})
const field = name => LINKS.find(([re]) => re.test(name))?.[1]
if (node.type === 'IMAGE' && !has.imageUrl) { await popcraft.bindField({ ids: [id], property: 'picture', field: 'image' }); bound++ }
if (node.type === 'TEXT' && !has.characters && field(node.name)) { await popcraft.bindField({ ids: [id], property: 'text', field: field(node.name) }); bound++ }
if (node.type === 'INSTANCE') {
const main = await popcraft.getNode(node.mainComponentId)
const main = await popcraft.getNode(String(node.mainComponentId))
const set = main?.parentId ? await popcraft.getNode(main.parentId) : null
const defs = (set?.type === 'COMPONENT_SET' ? set : main)?.componentPropertyDefinitions ?? {}
for (const [name, def] of Object.entries(defs)) {
Expand Down
17 changes: 16 additions & 1 deletion src/runtime/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
import { createRpcClient, type RpcClient } from './rpc.js'
import type {
PopCraftEvents, PopCraftExportOptions, PopCraftFindQuery, PopCraftNode, PopCraftNodeType,
PopCraftPage, PopCraftPaint, PopCraftShowUIOptions, PopCraftVariables,
PopCraftPage, PopCraftPaint, PopCraftShowUIOptions, PopCraftUploadOptions, PopCraftUploadResult,
PopCraftVariables,
} from '../types.js'
import type {
PopCraftSerialisedWidget, PopCraftWidgetDefinition, PopCraftWidgetHandler,
Expand Down Expand Up @@ -265,6 +266,20 @@ export function createMainApi(rpc: RpcClient = createRpcClient()) {

exportNode: (id: string, options?: PopCraftExportOptions) => rpc.call<Uint8Array | string>('exportNode', { id, options: options || {} }),
openExternal: (url: string) => rpc.call<void>('openExternal', { url }),
/**
* Send a file to an https URL. PopCraft makes the request, not the sandbox, so it works for files of
* any size the server accepts, including straight to a presigned storage URL. Needs `network`.
*/
upload: (url: string, options: PopCraftUploadOptions) => {
const body = options.body instanceof ArrayBuffer ? new Uint8Array(options.body) : options.body
return rpc.call<PopCraftUploadResult>('upload', {
url,
body,
method: options.method,
contentType: options.contentType,
headers: options.headers,
})
},
notify: (message: string, options?: { error?: boolean }) => rpc.call<void>('notify', { message: String(message), error: !!(options && options.error) }),

showUI: (options?: PopCraftShowUIOptions) => rpc.call<void>('showUI', options || {}),
Expand Down
24 changes: 23 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ export const ALL_PERMISSIONS: readonly PopCraftPermission[] = ['read', 'write',

export type PopCraftNodeType =
| 'RECTANGLE' | 'ELLIPSE' | 'POLYGON' | 'STAR' | 'LINE' | 'ARROW'
| 'FRAME' | 'SECTION' | 'TEXT' | 'STICKY' | 'VECTOR' | 'WIDGET'
| 'FRAME' | 'SECTION' | 'SLICE' | 'GROUP' | 'TEXT' | 'STICKY' | 'VECTOR' | 'BOOLEAN' | 'WIDGET'
| 'IMAGE' | 'COMPONENT' | 'COMPONENT_SET' | 'INSTANCE' | 'PIXEL_GRID' | 'RASTER'

export interface PopCraftCommand {
/** Passed to the plugin as `popcraft.command`. */
Expand Down Expand Up @@ -150,6 +151,27 @@ export interface PopCraftExportOptions {
scale?: number
}

/** What `popcraft.upload` sends. */
export interface PopCraftUploadOptions {
/** The file: bytes (an `exportNode` result, say) or text. */
body: Uint8Array | ArrayBuffer | string
/** Defaults to `PUT`, the verb presigned storage URLs expect. */
method?: 'PUT' | 'POST'
/** Sent as the request's `Content-Type`. */
contentType?: string
/** Extra request headers, such as one a storage service asks for. */
headers?: Record<string, string>
}

/** The server's answer to `popcraft.upload`. */
export interface PopCraftUploadResult {
ok: boolean
status: number
headers: Record<string, string>
/** The response body as text (parse it yourself if it is JSON). */
body: string
}

export interface PopCraftShowUIOptions {
width?: number
height?: number
Expand Down
Loading