@farcaster/snap-hono 1.1.5 → 1.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/renderSnapPage.js +8 -16
- package/package.json +2 -2
- package/src/renderSnapPage.ts +59 -64
package/dist/renderSnapPage.js
CHANGED
|
@@ -32,8 +32,6 @@ function renderElement(el, accent) {
|
|
|
32
32
|
return renderText(el, accent);
|
|
33
33
|
case "image":
|
|
34
34
|
return renderImage(el);
|
|
35
|
-
case "video":
|
|
36
|
-
return renderVideo(el);
|
|
37
35
|
case "grid":
|
|
38
36
|
return renderGrid(el);
|
|
39
37
|
case "progress":
|
|
@@ -85,13 +83,6 @@ function renderImage(el) {
|
|
|
85
83
|
const ratio = w && h ? `${w}/${h}` : "16/9";
|
|
86
84
|
return `<div style="aspect-ratio:${ratio};border-radius:8px;overflow:hidden;background:#F3F4F6"><img src="${url}" alt="${esc(el.alt ?? "")}" style="width:100%;height:100%;object-fit:cover"></div>`;
|
|
87
85
|
}
|
|
88
|
-
function renderVideo(el) {
|
|
89
|
-
const url = esc(el.url);
|
|
90
|
-
const aspect = el.aspect ?? "16:9";
|
|
91
|
-
const [w, h] = aspect.split(":").map(Number);
|
|
92
|
-
const ratio = w && h ? `${w}/${h}` : "16/9";
|
|
93
|
-
return `<div style="aspect-ratio:${ratio};border-radius:8px;overflow:hidden;background:#000"><video src="${url}" autoplay muted loop playsinline style="width:100%;height:100%;object-fit:cover"></video></div>`;
|
|
94
|
-
}
|
|
95
86
|
function renderGrid(el) {
|
|
96
87
|
const cols = el.cols;
|
|
97
88
|
const rows = el.rows;
|
|
@@ -131,12 +122,11 @@ function renderProgress(el, accent) {
|
|
|
131
122
|
}
|
|
132
123
|
function renderBarChart(el, accent) {
|
|
133
124
|
const bars = el.bars;
|
|
134
|
-
const max = el.max ??
|
|
135
|
-
Math.max(...bars.map((b) => b.value), 1);
|
|
125
|
+
const max = el.max ?? Math.max(...bars.map((b) => b.value), 1);
|
|
136
126
|
const defaultColor = colorHex(el.color, accent);
|
|
137
127
|
let html = `<div style="display:flex;align-items:flex-end;gap:12px;height:120px">`;
|
|
138
128
|
for (const bar of bars) {
|
|
139
|
-
const color = bar.color ?
|
|
129
|
+
const color = bar.color ? PALETTE[bar.color] ?? defaultColor : defaultColor;
|
|
140
130
|
const pct = max > 0 ? (bar.value / max) * 100 : 0;
|
|
141
131
|
html += `<div style="flex:1;display:flex;flex-direction:column;align-items:center;height:100%;justify-content:flex-end">`;
|
|
142
132
|
html += `<div style="font-size:11px;color:#6B7280;margin-bottom:4px">${bar.value}</div>`;
|
|
@@ -226,7 +216,11 @@ function renderButtons(buttons, layout, accent) {
|
|
|
226
216
|
: layout === "grid"
|
|
227
217
|
? "display:grid;grid-template-columns:1fr 1fr"
|
|
228
218
|
: "flex-direction:column";
|
|
229
|
-
const wrap = layout === "row"
|
|
219
|
+
const wrap = layout === "row"
|
|
220
|
+
? "display:flex;"
|
|
221
|
+
: layout === "grid"
|
|
222
|
+
? ""
|
|
223
|
+
: "display:flex;";
|
|
230
224
|
let html = `<div style="${wrap}${dir};gap:8px;margin-top:12px">`;
|
|
231
225
|
for (let i = 0; i < buttons.length; i++) {
|
|
232
226
|
const btn = buttons[i];
|
|
@@ -246,9 +240,7 @@ export function renderSnapPage(snap, snapOrigin) {
|
|
|
246
240
|
const accent = accentHex(page.theme?.accent);
|
|
247
241
|
// Extract title for <title> tag
|
|
248
242
|
const titleEl = page.elements.children.find((el) => el.type === "text" && el.style === "title");
|
|
249
|
-
const pageTitle = titleEl
|
|
250
|
-
? esc(titleEl.content)
|
|
251
|
-
: "Farcaster Snap";
|
|
243
|
+
const pageTitle = titleEl ? esc(titleEl.content) : "Farcaster Snap";
|
|
252
244
|
const snapUrl = encodeURIComponent(snapOrigin + "/");
|
|
253
245
|
// Render elements
|
|
254
246
|
let elementsHtml = "";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@farcaster/snap-hono",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.6",
|
|
4
4
|
"description": "Hono integration for Farcaster Snap servers",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
},
|
|
27
27
|
"license": "MIT",
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@farcaster/snap": "1.3.
|
|
29
|
+
"@farcaster/snap": "1.3.1"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
32
|
"hono": ">=4.0.0"
|
package/src/renderSnapPage.ts
CHANGED
|
@@ -39,8 +39,6 @@ function renderElement(el: Record<string, unknown>, accent: string): string {
|
|
|
39
39
|
return renderText(el, accent);
|
|
40
40
|
case "image":
|
|
41
41
|
return renderImage(el);
|
|
42
|
-
case "video":
|
|
43
|
-
return renderVideo(el);
|
|
44
42
|
case "grid":
|
|
45
43
|
return renderGrid(el);
|
|
46
44
|
case "progress":
|
|
@@ -67,17 +65,16 @@ function renderElement(el: Record<string, unknown>, accent: string): string {
|
|
|
67
65
|
medium: "16px",
|
|
68
66
|
large: "24px",
|
|
69
67
|
};
|
|
70
|
-
return `<div style="height:${
|
|
68
|
+
return `<div style="height:${
|
|
69
|
+
sizes[(el.size as string) ?? "medium"] ?? "16px"
|
|
70
|
+
}"></div>`;
|
|
71
71
|
}
|
|
72
72
|
default:
|
|
73
73
|
return "";
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
function renderText(
|
|
78
|
-
el: Record<string, unknown>,
|
|
79
|
-
_accent: string,
|
|
80
|
-
): string {
|
|
77
|
+
function renderText(el: Record<string, unknown>, _accent: string): string {
|
|
81
78
|
const style = el.style as string;
|
|
82
79
|
const content = esc(el.content as string);
|
|
83
80
|
const align = (el.align as string) ?? "left";
|
|
@@ -85,9 +82,12 @@ function renderText(
|
|
|
85
82
|
title: "font-size:20px;font-weight:700;color:#111",
|
|
86
83
|
body: "font-size:15px;line-height:1.5;color:#374151",
|
|
87
84
|
caption: "font-size:13px;color:#9CA3AF",
|
|
88
|
-
label:
|
|
85
|
+
label:
|
|
86
|
+
"font-size:13px;font-weight:600;color:#6B7280;text-transform:uppercase;letter-spacing:0.5px",
|
|
89
87
|
};
|
|
90
|
-
return `<div style="${
|
|
88
|
+
return `<div style="${
|
|
89
|
+
styles[style] ?? styles.body
|
|
90
|
+
};text-align:${align}">${content}</div>`;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
function renderImage(el: Record<string, unknown>): string {
|
|
@@ -95,15 +95,9 @@ function renderImage(el: Record<string, unknown>): string {
|
|
|
95
95
|
const aspect = (el.aspect as string) ?? "16:9";
|
|
96
96
|
const [w, h] = aspect.split(":").map(Number);
|
|
97
97
|
const ratio = w && h ? `${w}/${h}` : "16/9";
|
|
98
|
-
return `<div style="aspect-ratio:${ratio};border-radius:8px;overflow:hidden;background:#F3F4F6"><img src="${url}" alt="${esc(
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
function renderVideo(el: Record<string, unknown>): string {
|
|
102
|
-
const url = esc(el.url as string);
|
|
103
|
-
const aspect = (el.aspect as string) ?? "16:9";
|
|
104
|
-
const [w, h] = aspect.split(":").map(Number);
|
|
105
|
-
const ratio = w && h ? `${w}/${h}` : "16/9";
|
|
106
|
-
return `<div style="aspect-ratio:${ratio};border-radius:8px;overflow:hidden;background:#000"><video src="${url}" autoplay muted loop playsinline style="width:100%;height:100%;object-fit:cover"></video></div>`;
|
|
98
|
+
return `<div style="aspect-ratio:${ratio};border-radius:8px;overflow:hidden;background:#F3F4F6"><img src="${url}" alt="${esc(
|
|
99
|
+
(el.alt as string) ?? "",
|
|
100
|
+
)}" style="width:100%;height:100%;object-fit:cover"></div>`;
|
|
107
101
|
}
|
|
108
102
|
|
|
109
103
|
function renderGrid(el: Record<string, unknown>): string {
|
|
@@ -136,46 +130,45 @@ function renderGrid(el: Record<string, unknown>): string {
|
|
|
136
130
|
}
|
|
137
131
|
}
|
|
138
132
|
|
|
139
|
-
return `<div style="display:grid;grid-template-columns:repeat(${cols},1fr);gap:${
|
|
133
|
+
return `<div style="display:grid;grid-template-columns:repeat(${cols},1fr);gap:${
|
|
134
|
+
gapPx[gap] ?? "2px"
|
|
135
|
+
}">${cellsHtml}</div>`;
|
|
140
136
|
}
|
|
141
137
|
|
|
142
|
-
function renderProgress(
|
|
143
|
-
el: Record<string, unknown>,
|
|
144
|
-
accent: string,
|
|
145
|
-
): string {
|
|
138
|
+
function renderProgress(el: Record<string, unknown>, accent: string): string {
|
|
146
139
|
const value = el.value as number;
|
|
147
140
|
const max = el.max as number;
|
|
148
141
|
const label = el.label as string | undefined;
|
|
149
142
|
const color = colorHex(el.color as string | undefined, accent);
|
|
150
143
|
const pct = max > 0 ? Math.min(100, (value / max) * 100) : 0;
|
|
151
144
|
const labelHtml = label
|
|
152
|
-
? `<div style="font-size:13px;color:#6B7280;margin-bottom:4px">${esc(
|
|
145
|
+
? `<div style="font-size:13px;color:#6B7280;margin-bottom:4px">${esc(
|
|
146
|
+
label,
|
|
147
|
+
)}</div>`
|
|
153
148
|
: "";
|
|
154
149
|
return `<div>${labelHtml}<div style="height:8px;background:#E5E7EB;border-radius:4px;overflow:hidden"><div style="height:100%;width:${pct}%;background:${color};border-radius:4px"></div></div></div>`;
|
|
155
150
|
}
|
|
156
151
|
|
|
157
|
-
function renderBarChart(
|
|
158
|
-
el: Record<string, unknown>,
|
|
159
|
-
accent: string,
|
|
160
|
-
): string {
|
|
152
|
+
function renderBarChart(el: Record<string, unknown>, accent: string): string {
|
|
161
153
|
const bars = el.bars as Array<{
|
|
162
154
|
label: string;
|
|
163
155
|
value: number;
|
|
164
156
|
color?: string;
|
|
165
157
|
}>;
|
|
166
158
|
const max =
|
|
167
|
-
(el.max as number | undefined) ??
|
|
168
|
-
Math.max(...bars.map((b) => b.value), 1);
|
|
159
|
+
(el.max as number | undefined) ?? Math.max(...bars.map((b) => b.value), 1);
|
|
169
160
|
const defaultColor = colorHex(el.color as string | undefined, accent);
|
|
170
161
|
|
|
171
162
|
let html = `<div style="display:flex;align-items:flex-end;gap:12px;height:120px">`;
|
|
172
163
|
for (const bar of bars) {
|
|
173
|
-
const color = bar.color ?
|
|
164
|
+
const color = bar.color ? PALETTE[bar.color] ?? defaultColor : defaultColor;
|
|
174
165
|
const pct = max > 0 ? (bar.value / max) * 100 : 0;
|
|
175
166
|
html += `<div style="flex:1;display:flex;flex-direction:column;align-items:center;height:100%;justify-content:flex-end">`;
|
|
176
167
|
html += `<div style="font-size:11px;color:#6B7280;margin-bottom:4px">${bar.value}</div>`;
|
|
177
168
|
html += `<div style="width:100%;height:${pct}%;background:${color};border-radius:4px 4px 0 0;min-height:4px"></div>`;
|
|
178
|
-
html += `<div style="font-size:11px;color:#9CA3AF;margin-top:4px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%">${esc(
|
|
169
|
+
html += `<div style="font-size:11px;color:#9CA3AF;margin-top:4px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%">${esc(
|
|
170
|
+
bar.label,
|
|
171
|
+
)}</div>`;
|
|
179
172
|
html += `</div>`;
|
|
180
173
|
}
|
|
181
174
|
html += `</div>`;
|
|
@@ -196,12 +189,16 @@ function renderList(el: Record<string, unknown>): string {
|
|
|
196
189
|
style === "ordered"
|
|
197
190
|
? `<span style="color:#9CA3AF;min-width:20px">${i + 1}.</span>`
|
|
198
191
|
: style === "unordered"
|
|
199
|
-
|
|
200
|
-
|
|
192
|
+
? `<span style="color:#9CA3AF;min-width:20px">•</span>`
|
|
193
|
+
: "";
|
|
201
194
|
const trailing = item.trailing
|
|
202
|
-
? `<span style="color:#9CA3AF;font-size:13px;white-space:nowrap">${esc(
|
|
195
|
+
? `<span style="color:#9CA3AF;font-size:13px;white-space:nowrap">${esc(
|
|
196
|
+
item.trailing,
|
|
197
|
+
)}</span>`
|
|
203
198
|
: "";
|
|
204
|
-
html += `<div style="display:flex;align-items:center;gap:8px;padding:6px 0">${prefix}<span style="flex:1;font-size:14px;color:#374151">${esc(
|
|
199
|
+
html += `<div style="display:flex;align-items:center;gap:8px;padding:6px 0">${prefix}<span style="flex:1;font-size:14px;color:#374151">${esc(
|
|
200
|
+
item.content,
|
|
201
|
+
)}</span>${trailing}</div>`;
|
|
205
202
|
}
|
|
206
203
|
return `<div>${html}</div>`;
|
|
207
204
|
}
|
|
@@ -215,16 +212,15 @@ function renderButtonGroup(
|
|
|
215
212
|
const dir = layout === "stack" ? "column" : "row";
|
|
216
213
|
let html = `<div style="display:flex;flex-direction:${dir};gap:8px">`;
|
|
217
214
|
for (const opt of options) {
|
|
218
|
-
html += `<button onclick="showModal()" style="flex:1;padding:10px 12px;border-radius:8px;border:1px solid #E5E7EB;background:#fff;font-size:14px;color:#374151;cursor:pointer;font-family:inherit">${esc(
|
|
215
|
+
html += `<button onclick="showModal()" style="flex:1;padding:10px 12px;border-radius:8px;border:1px solid #E5E7EB;background:#fff;font-size:14px;color:#374151;cursor:pointer;font-family:inherit">${esc(
|
|
216
|
+
opt,
|
|
217
|
+
)}</button>`;
|
|
219
218
|
}
|
|
220
219
|
html += `</div>`;
|
|
221
220
|
return html;
|
|
222
221
|
}
|
|
223
222
|
|
|
224
|
-
function renderSlider(
|
|
225
|
-
el: Record<string, unknown>,
|
|
226
|
-
accent: string,
|
|
227
|
-
): string {
|
|
223
|
+
function renderSlider(el: Record<string, unknown>, accent: string): string {
|
|
228
224
|
const label = el.label as string | undefined;
|
|
229
225
|
const min = el.min as number;
|
|
230
226
|
const max = el.max as number;
|
|
@@ -233,7 +229,9 @@ function renderSlider(
|
|
|
233
229
|
const maxLabel = el.maxLabel as string | undefined;
|
|
234
230
|
|
|
235
231
|
const labelHtml = label
|
|
236
|
-
? `<div style="font-size:13px;color:#6B7280;margin-bottom:4px">${esc(
|
|
232
|
+
? `<div style="font-size:13px;color:#6B7280;margin-bottom:4px">${esc(
|
|
233
|
+
label,
|
|
234
|
+
)}</div>`
|
|
237
235
|
: "";
|
|
238
236
|
const minL = minLabel
|
|
239
237
|
? `<span style="font-size:11px;color:#9CA3AF">${esc(minLabel)}</span>`
|
|
@@ -250,10 +248,7 @@ function renderTextInput(el: Record<string, unknown>): string {
|
|
|
250
248
|
return `<input type="text" placeholder="${placeholder}" disabled style="width:100%;padding:10px 12px;border-radius:8px;border:1px solid #E5E7EB;background:#F9FAFB;font-size:14px;color:#9CA3AF;font-family:inherit;box-sizing:border-box">`;
|
|
251
249
|
}
|
|
252
250
|
|
|
253
|
-
function renderToggle(
|
|
254
|
-
el: Record<string, unknown>,
|
|
255
|
-
accent: string,
|
|
256
|
-
): string {
|
|
251
|
+
function renderToggle(el: Record<string, unknown>, accent: string): string {
|
|
257
252
|
const label = esc(el.label as string);
|
|
258
253
|
const value = el.value as boolean;
|
|
259
254
|
const bg = value ? accent : "#D1D5DB";
|
|
@@ -264,10 +259,7 @@ function renderToggle(
|
|
|
264
259
|
</div>`;
|
|
265
260
|
}
|
|
266
261
|
|
|
267
|
-
function renderGroup(
|
|
268
|
-
el: Record<string, unknown>,
|
|
269
|
-
accent: string,
|
|
270
|
-
): string {
|
|
262
|
+
function renderGroup(el: Record<string, unknown>, accent: string): string {
|
|
271
263
|
const children = el.children as Array<Record<string, unknown>>;
|
|
272
264
|
let html = `<div style="display:flex;gap:12px">`;
|
|
273
265
|
for (const child of children) {
|
|
@@ -290,9 +282,14 @@ function renderButtons(
|
|
|
290
282
|
layout === "row"
|
|
291
283
|
? "flex-direction:row"
|
|
292
284
|
: layout === "grid"
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
const wrap =
|
|
285
|
+
? "display:grid;grid-template-columns:1fr 1fr"
|
|
286
|
+
: "flex-direction:column";
|
|
287
|
+
const wrap =
|
|
288
|
+
layout === "row"
|
|
289
|
+
? "display:flex;"
|
|
290
|
+
: layout === "grid"
|
|
291
|
+
? ""
|
|
292
|
+
: "display:flex;";
|
|
296
293
|
|
|
297
294
|
let html = `<div style="${wrap}${dir};gap:8px;margin-top:12px">`;
|
|
298
295
|
for (let i = 0; i < buttons.length; i++) {
|
|
@@ -301,8 +298,7 @@ function renderButtons(
|
|
|
301
298
|
const style = (btn.style as string) ?? (i === 0 ? "primary" : "secondary");
|
|
302
299
|
const bg = style === "primary" ? accent : "transparent";
|
|
303
300
|
const color = style === "primary" ? "#fff" : accent;
|
|
304
|
-
const border =
|
|
305
|
-
style === "primary" ? "none" : `2px solid ${accent}`;
|
|
301
|
+
const border = style === "primary" ? "none" : `2px solid ${accent}`;
|
|
306
302
|
html += `<button onclick="showModal()" style="flex:1;padding:10px 16px;border-radius:10px;background:${bg};color:${color};border:${border};font-size:14px;font-weight:600;cursor:pointer;font-family:inherit">${label}</button>`;
|
|
307
303
|
}
|
|
308
304
|
html += `</div>`;
|
|
@@ -311,27 +307,26 @@ function renderButtons(
|
|
|
311
307
|
|
|
312
308
|
// ─── Main renderer ──────────────────────────────────────
|
|
313
309
|
|
|
314
|
-
export function renderSnapPage(
|
|
315
|
-
snap: SnapResponse,
|
|
316
|
-
snapOrigin: string,
|
|
317
|
-
): string {
|
|
310
|
+
export function renderSnapPage(snap: SnapResponse, snapOrigin: string): string {
|
|
318
311
|
const page = snap.page;
|
|
319
312
|
const accent = accentHex(page.theme?.accent);
|
|
320
313
|
|
|
321
314
|
// Extract title for <title> tag
|
|
322
315
|
const titleEl = page.elements.children.find(
|
|
323
|
-
(el) =>
|
|
316
|
+
(el) =>
|
|
317
|
+
el.type === "text" && (el as Record<string, unknown>).style === "title",
|
|
324
318
|
) as Record<string, unknown> | undefined;
|
|
325
|
-
const pageTitle = titleEl
|
|
326
|
-
? esc(titleEl.content as string)
|
|
327
|
-
: "Farcaster Snap";
|
|
319
|
+
const pageTitle = titleEl ? esc(titleEl.content as string) : "Farcaster Snap";
|
|
328
320
|
|
|
329
321
|
const snapUrl = encodeURIComponent(snapOrigin + "/");
|
|
330
322
|
|
|
331
323
|
// Render elements
|
|
332
324
|
let elementsHtml = "";
|
|
333
325
|
for (const el of page.elements.children) {
|
|
334
|
-
elementsHtml += `<div style="margin-bottom:12px">${renderElement(
|
|
326
|
+
elementsHtml += `<div style="margin-bottom:12px">${renderElement(
|
|
327
|
+
el as Record<string, unknown>,
|
|
328
|
+
accent,
|
|
329
|
+
)}</div>`;
|
|
335
330
|
}
|
|
336
331
|
|
|
337
332
|
// Render buttons
|