@farcaster/snap 2.8.0 → 2.10.0
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/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/react/catalog-renderer.d.ts +5 -5
- package/dist/react/catalog-renderer.js +16 -4
- package/dist/react/components/action-button.js +23 -5
- package/dist/react/index.d.ts +2 -13
- package/dist/react/snap-view-core.js +90 -45
- package/dist/react/v1/snap-view.js +1 -1
- package/dist/react/v2/snap-view.js +1 -1
- package/dist/react-native/components/snap-action-button.js +6 -1
- package/dist/react-native/snap-view-core.js +77 -44
- package/dist/react-native/types.d.ts +2 -13
- package/dist/render-state.d.ts +9 -0
- package/dist/render-state.js +27 -0
- package/dist/schemas.d.ts +123 -3
- package/dist/schemas.js +53 -2
- package/dist/server/parseRequest.js +19 -3
- package/dist/ui/button.d.ts +1 -0
- package/dist/ui/button.js +1 -0
- package/dist/ui/catalog.d.ts +13 -14
- package/dist/ui/catalog.js +15 -22
- package/package.json +1 -1
- package/src/index.ts +7 -0
- package/src/react/catalog-renderer.tsx +57 -3
- package/src/react/components/action-button.tsx +32 -3
- package/src/react/index.tsx +4 -14
- package/src/react/snap-view-core.tsx +144 -48
- package/src/react/v1/snap-view.tsx +1 -0
- package/src/react/v2/snap-view.tsx +1 -0
- package/src/react-native/components/snap-action-button.tsx +6 -1
- package/src/react-native/snap-view-core.tsx +114 -48
- package/src/react-native/types.ts +4 -14
- package/src/render-state.ts +46 -0
- package/src/schemas.ts +73 -2
- package/src/server/parseRequest.ts +37 -6
- package/src/ui/button.ts +1 -0
- package/src/ui/catalog.ts +16 -25
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export type { Spec as SnapSpec, UIElement as SnapUIElement, } from "@json-render/core";
|
|
2
2
|
export { SPEC_VERSION, SPEC_VERSION_1, SPEC_VERSION_2, SUPPORTED_SPEC_VERSIONS, type SpecVersion, SNAP_PAYLOAD_HEADER, MEDIA_TYPE, EFFECT_VALUES, POST_GRID_TAP_KEY, MAX_ELEMENTS, MAX_ROOT_CHILDREN, MAX_CHILDREN, MAX_DEPTH, } from "./constants.js";
|
|
3
3
|
export { DEFAULT_THEME_ACCENT, PALETTE_COLOR, PALETTE_COLOR_ACCENT, PALETTE_COLOR_VALUES, PALETTE_LIGHT_HEX, PALETTE_DARK_HEX, isSnapHexColorString, readableTextOnHex, resolveSnapColorHex, type PaletteColor, } from "./colors.js";
|
|
4
|
-
export { ACTION_TYPE_GET, ACTION_TYPE_POST, snapResponseSchema, payloadSchema, getPayloadSchema, type SnapAction, type SnapGetAction, type SnapContext, type SnapResponse, type SnapHandlerResult, type SnapElementInput, type SnapSpecInput, type SnapFunction, type SnapPayload, type SnapGetPayload, } from "./schemas.js";
|
|
4
|
+
export { ACTION_TYPE_GET, ACTION_TYPE_POST, ACTION_TYPE_TRANSACTION_RESULT, snapResponseSchema, payloadSchema, getPayloadSchema, transactionResultPayloadSchema, snapTransactionResultSchema, type SnapAction, type SnapGetAction, type SnapTransactionResultAction, type SnapContext, type SnapResponse, type SnapHandlerResult, type SnapElementInput, type SnapSpecInput, type SnapFunction, type SnapPayload, type SnapGetPayload, type SnapSendTransactionParams, type SnapTransactionResult, type SnapTransactionResultPayload, } from "./schemas.js";
|
|
5
5
|
export { validateSnapResponse, type ValidationResult } from "./validator.js";
|
|
6
6
|
export type { SnapRenderState } from "./render-state.js";
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { SPEC_VERSION, SPEC_VERSION_1, SPEC_VERSION_2, SUPPORTED_SPEC_VERSIONS, SNAP_PAYLOAD_HEADER, MEDIA_TYPE, EFFECT_VALUES, POST_GRID_TAP_KEY, MAX_ELEMENTS, MAX_ROOT_CHILDREN, MAX_CHILDREN, MAX_DEPTH, } from "./constants.js";
|
|
2
2
|
export { DEFAULT_THEME_ACCENT, PALETTE_COLOR, PALETTE_COLOR_ACCENT, PALETTE_COLOR_VALUES, PALETTE_LIGHT_HEX, PALETTE_DARK_HEX, isSnapHexColorString, readableTextOnHex, resolveSnapColorHex, } from "./colors.js";
|
|
3
|
-
export { ACTION_TYPE_GET, ACTION_TYPE_POST, snapResponseSchema, payloadSchema, getPayloadSchema, } from "./schemas.js";
|
|
3
|
+
export { ACTION_TYPE_GET, ACTION_TYPE_POST, ACTION_TYPE_TRANSACTION_RESULT, snapResponseSchema, payloadSchema, getPayloadSchema, transactionResultPayloadSchema, snapTransactionResultSchema, } from "./schemas.js";
|
|
4
4
|
export { validateSnapResponse } from "./validator.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import { type CreateRendererProps } from "@json-render/react";
|
|
2
|
+
import { type ReactNode } from "react";
|
|
3
|
+
export declare function SnapCatalogView({ spec, store, state, onAction, onStateChange, functions, loading, fallback, children, }: CreateRendererProps & {
|
|
4
|
+
children?: ReactNode;
|
|
5
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { JSONUIProvider, Renderer, } from "@json-render/react";
|
|
4
|
+
import { useMemo } from "react";
|
|
4
5
|
import { SnapActionButton } from "./components/action-button.js";
|
|
5
6
|
import { SnapBadge } from "./components/badge.js";
|
|
6
7
|
import { SnapIcon } from "./components/icon.js";
|
|
@@ -22,7 +23,7 @@ import { SnapCellGrid } from "./components/cell-grid.js";
|
|
|
22
23
|
* Maps snap json-render catalog types to React components.
|
|
23
24
|
* Keys match the snap wire-format `type` strings exactly.
|
|
24
25
|
*/
|
|
25
|
-
|
|
26
|
+
const snapCatalogRegistry = {
|
|
26
27
|
badge: SnapBadge,
|
|
27
28
|
button: SnapActionButton,
|
|
28
29
|
icon: SnapIcon,
|
|
@@ -40,4 +41,15 @@ export const SnapCatalogView = createRenderer(snapJsonRenderCatalog, {
|
|
|
40
41
|
toggle_group: SnapToggleGroup,
|
|
41
42
|
bar_chart: SnapBarChart,
|
|
42
43
|
cell_grid: SnapCellGrid,
|
|
43
|
-
}
|
|
44
|
+
};
|
|
45
|
+
export function SnapCatalogView({ spec, store, state, onAction, onStateChange, functions, loading, fallback, children, }) {
|
|
46
|
+
const actionHandlers = useMemo(() => onAction
|
|
47
|
+
? new Proxy({}, {
|
|
48
|
+
get: (_target, prop) => {
|
|
49
|
+
return (params) => onAction(String(prop), params);
|
|
50
|
+
},
|
|
51
|
+
has: () => true,
|
|
52
|
+
})
|
|
53
|
+
: undefined, [onAction]);
|
|
54
|
+
return (_jsxs(JSONUIProvider, { registry: snapCatalogRegistry, store: store, initialState: state, handlers: actionHandlers, functions: functions, onStateChange: onStateChange, children: [_jsx(Renderer, { spec: spec, registry: snapCatalogRegistry, loading: loading, fallback: fallback }), children] }));
|
|
55
|
+
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useState } from "react";
|
|
4
|
-
import { useStateStore } from "@json-render/react";
|
|
3
|
+
import { useMemo, useState } from "react";
|
|
4
|
+
import { useStateStore, useStateValue } from "@json-render/react";
|
|
5
5
|
import { ExternalLink } from "lucide-react";
|
|
6
6
|
import { Button } from "@neynar/ui/button";
|
|
7
7
|
import { cn } from "@neynar/ui/utils";
|
|
8
8
|
import { useSnapColors } from "../hooks/use-snap-colors.js";
|
|
9
9
|
import { getPaginatorAction, runPaginatorAction, } from "../../ui/paginator-state.js";
|
|
10
|
+
import { buildActionActivityStateChanges } from "../../render-state.js";
|
|
10
11
|
import { useSnapStackDirection } from "../stack-direction-context.js";
|
|
11
12
|
import { ICON_MAP } from "./icon.js";
|
|
12
13
|
function isExternalLinkAction(on) {
|
|
@@ -17,21 +18,36 @@ function isExternalLinkAction(on) {
|
|
|
17
18
|
return false;
|
|
18
19
|
return press.action === "open_url";
|
|
19
20
|
}
|
|
21
|
+
function getActionPendingPath(on) {
|
|
22
|
+
const press = on?.press;
|
|
23
|
+
if (!press?.action)
|
|
24
|
+
return "/__snap/action/pending";
|
|
25
|
+
return (buildActionActivityStateChanges({
|
|
26
|
+
actionName: press.action,
|
|
27
|
+
params: press.params ?? {},
|
|
28
|
+
pending: true,
|
|
29
|
+
}).find((change) => change.path.endsWith("/pending"))?.path ??
|
|
30
|
+
"/__snap/action/pending");
|
|
31
|
+
}
|
|
20
32
|
export function SnapActionButton({ element, emit, }) {
|
|
21
33
|
const { props } = element;
|
|
22
34
|
const label = String(props.label ?? "Action");
|
|
23
35
|
const variant = String(props.variant ?? "secondary");
|
|
24
36
|
const isPrimary = variant === "primary";
|
|
37
|
+
const disabled = props.disabled === true;
|
|
25
38
|
const iconName = props.icon ? String(props.icon) : undefined;
|
|
26
39
|
const colors = useSnapColors();
|
|
27
40
|
const [hovered, setHovered] = useState(false);
|
|
28
41
|
const stateStore = useStateStore();
|
|
29
42
|
const paginatorAction = getPaginatorAction(element.on);
|
|
43
|
+
const actionPendingPath = useMemo(() => getActionPendingPath(element.on), [element.on]);
|
|
44
|
+
const actionPending = useStateValue(actionPendingPath) === true;
|
|
30
45
|
const Icon = iconName ? ICON_MAP[iconName] : undefined;
|
|
31
46
|
const showExternalIcon = isExternalLinkAction(element.on);
|
|
32
47
|
const inHorizontalStack = useSnapStackDirection() === "horizontal";
|
|
33
48
|
const style = {
|
|
34
|
-
cursor: "pointer",
|
|
49
|
+
cursor: disabled ? "not-allowed" : "pointer",
|
|
50
|
+
opacity: disabled ? 0.62 : 1,
|
|
35
51
|
...(isPrimary
|
|
36
52
|
? {
|
|
37
53
|
backgroundColor: hovered ? colors.accentHover : colors.accent,
|
|
@@ -54,9 +70,11 @@ export function SnapActionButton({ element, emit, }) {
|
|
|
54
70
|
*/
|
|
55
71
|
_jsx("div", { className: inHorizontalStack
|
|
56
72
|
? "min-w-0 flex-auto"
|
|
57
|
-
: "w-full min-w-0", style: inHorizontalStack ? { flex: "1 1 auto" } : undefined, children: _jsxs(Button, { type: "button", variant: isPrimary ? "default" : "secondary", className: cn("h-8 w-full gap-2 px-3 text-sm"), style: style, onClick: () => {
|
|
73
|
+
: "w-full min-w-0", style: inHorizontalStack ? { flex: "1 1 auto" } : undefined, children: _jsxs(Button, { type: "button", variant: isPrimary ? "default" : "secondary", className: cn("h-8 w-full gap-2 px-3 text-sm"), disabled: disabled, style: style, onClick: () => {
|
|
74
|
+
if (disabled)
|
|
75
|
+
return;
|
|
58
76
|
if (!runPaginatorAction(stateStore, paginatorAction)) {
|
|
59
77
|
emit("press");
|
|
60
78
|
}
|
|
61
|
-
}, onPointerEnter: () => setHovered(true), onPointerLeave: () => setHovered(false), children: [Icon && _jsx(Icon, { size: 16 }), label, showExternalIcon && (_jsx(ExternalLink, { size: 14, style: { opacity: 0.6 } }))] }) }));
|
|
79
|
+
}, onPointerEnter: () => setHovered(true), onPointerLeave: () => setHovered(false), children: [Icon && _jsx(Icon, { size: 16 }), label, actionPending && (_jsx("span", { "data-snap-action-pending-active": "true", hidden: true })), showExternalIcon && (_jsx(ExternalLink, { size: 14, style: { opacity: 0.6 } }))] }) }));
|
|
62
80
|
}
|
package/dist/react/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { Spec } from "@json-render/core";
|
|
|
2
2
|
import type { ReactNode } from "react";
|
|
3
3
|
import type { ValidationResult } from "../validator.js";
|
|
4
4
|
import type { SnapRenderState } from "../render-state.js";
|
|
5
|
+
import type { SnapTransactionResult } from "../schemas.js";
|
|
5
6
|
export type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
6
7
|
[key: string]: JsonValue;
|
|
7
8
|
};
|
|
@@ -23,17 +24,6 @@ export type SnapSendTransactionParams = {
|
|
|
23
24
|
maxFeePerGas?: string;
|
|
24
25
|
maxPriorityFeePerGas?: string;
|
|
25
26
|
};
|
|
26
|
-
export type SnapSendCallsParams = {
|
|
27
|
-
version?: "1.0";
|
|
28
|
-
chainId: string;
|
|
29
|
-
atomicRequired?: boolean;
|
|
30
|
-
id?: string;
|
|
31
|
-
calls: Array<{
|
|
32
|
-
to?: string;
|
|
33
|
-
data?: string;
|
|
34
|
-
value?: string;
|
|
35
|
-
}>;
|
|
36
|
-
};
|
|
37
27
|
export type SnapActionHandlers = {
|
|
38
28
|
submit: (target: string, inputs: Record<string, JsonValue>) => void;
|
|
39
29
|
open_url: (target: string) => void;
|
|
@@ -66,8 +56,7 @@ export type SnapActionHandlers = {
|
|
|
66
56
|
sellToken?: string;
|
|
67
57
|
buyToken?: string;
|
|
68
58
|
}) => void;
|
|
69
|
-
send_transaction?: (params: SnapSendTransactionParams) => void
|
|
70
|
-
send_calls?: (params: SnapSendCallsParams) => void;
|
|
59
|
+
send_transaction?: (params: SnapSendTransactionParams) => void | Promise<void | SnapTransactionResult>;
|
|
71
60
|
};
|
|
72
61
|
export type { SnapRenderState };
|
|
73
62
|
export declare function SnapCard({ snap, handlers, loading, appearance, maxWidth, showOverflowWarning, onValidationError, validationErrorFallback, actionError, plain, loadingOverlay, initialRenderState, onRenderStateChange, }: {
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { createStateStore } from "@json-render/react";
|
|
3
4
|
import { snapJsonRenderCatalog } from "../ui/index.js";
|
|
4
5
|
import { SnapCatalogView } from "./catalog-renderer.js";
|
|
5
6
|
import { SnapPreviewAccentProvider } from "./accent-context.js";
|
|
6
7
|
import { SnapVersionProvider } from "./snap-version-context.js";
|
|
7
8
|
import { resolveSnapPaletteHex } from "./lib/resolve-palette-hex.js";
|
|
8
9
|
import { snapPreviewPrimaryCssProperties } from "./lib/preview-primary-css.js";
|
|
9
|
-
import {
|
|
10
|
+
import { buildActionActivityStateChanges, buildInitialRenderState, cloneSnapRenderState, getUnpresentedSnapEffects, hasPendingSnapAction, markSnapEffectsPresented, } from "../render-state.js";
|
|
10
11
|
import { useCallback, useEffect, useMemo, useRef, useState, } from "react";
|
|
11
12
|
function asRecord(value) {
|
|
12
13
|
return value && typeof value === "object"
|
|
@@ -16,6 +17,11 @@ function asRecord(value) {
|
|
|
16
17
|
function optionalString(value) {
|
|
17
18
|
return value ? String(value) : undefined;
|
|
18
19
|
}
|
|
20
|
+
function recordValue(value) {
|
|
21
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
22
|
+
? value
|
|
23
|
+
: undefined;
|
|
24
|
+
}
|
|
19
25
|
function withDefaultElementProps(spec) {
|
|
20
26
|
if (!spec || typeof spec !== "object" || !("elements" in spec))
|
|
21
27
|
return spec;
|
|
@@ -165,7 +171,7 @@ export function SnapLoadingOverlay({ appearance, accentHex, active, }) {
|
|
|
165
171
|
const trackColor = isDark
|
|
166
172
|
? "rgba(255, 255, 255, 0.12)"
|
|
167
173
|
: "rgba(15, 23, 42, 0.1)";
|
|
168
|
-
return (_jsxs("div", { style: {
|
|
174
|
+
return (_jsxs("div", { "data-snap-loading-overlay": true, "data-snap-loading-active": active ? "true" : "false", style: {
|
|
169
175
|
position: "absolute",
|
|
170
176
|
inset: 0,
|
|
171
177
|
display: "flex",
|
|
@@ -188,6 +194,20 @@ export function SnapLoadingOverlay({ appearance, accentHex, active, }) {
|
|
|
188
194
|
animation: "snapViewSpin 0.75s linear infinite",
|
|
189
195
|
flexShrink: 0,
|
|
190
196
|
} }), _jsx("style", { children: `
|
|
197
|
+
[data-snap-view-root]:has([data-snap-action-pending-active="true"])
|
|
198
|
+
[data-snap-loading-overlay] {
|
|
199
|
+
opacity: 1 !important;
|
|
200
|
+
pointer-events: auto !important;
|
|
201
|
+
backdrop-filter: blur(10px) saturate(1.05) !important;
|
|
202
|
+
-webkit-backdrop-filter: blur(10px) saturate(1.05) !important;
|
|
203
|
+
}
|
|
204
|
+
[data-snap-card-surface]:has([data-snap-action-pending-active="true"])
|
|
205
|
+
> [data-snap-loading-overlay] {
|
|
206
|
+
opacity: 1 !important;
|
|
207
|
+
pointer-events: auto !important;
|
|
208
|
+
backdrop-filter: blur(10px) saturate(1.05) !important;
|
|
209
|
+
-webkit-backdrop-filter: blur(10px) saturate(1.05) !important;
|
|
210
|
+
}
|
|
191
211
|
@keyframes snapViewSpin {
|
|
192
212
|
to { transform: rotate(360deg); }
|
|
193
213
|
}
|
|
@@ -200,6 +220,9 @@ export function SnapLoadingOverlay({ appearance, accentHex, active, }) {
|
|
|
200
220
|
}
|
|
201
221
|
` })] }));
|
|
202
222
|
}
|
|
223
|
+
function SnapPendingActionOverlay({ appearance, accentHex, }) {
|
|
224
|
+
return (_jsx(SnapLoadingOverlay, { appearance: appearance, accentHex: accentHex, active: false }));
|
|
225
|
+
}
|
|
203
226
|
const PALETTE = [
|
|
204
227
|
"gray",
|
|
205
228
|
"blue",
|
|
@@ -219,10 +242,23 @@ export function SnapViewCore({ snap, handlers, loading = false, appearance = "da
|
|
|
219
242
|
initialRenderState,
|
|
220
243
|
themeAccent: snap.theme?.accent,
|
|
221
244
|
}), [initialRenderState, spec.state, snap.theme?.accent]);
|
|
245
|
+
const stateStore = useMemo(() => createStateStore(initialState), [
|
|
246
|
+
initialState,
|
|
247
|
+
]);
|
|
222
248
|
const stateRef = useRef(initialState);
|
|
249
|
+
const onRenderStateChangeRef = useRef(onRenderStateChange);
|
|
250
|
+
const pendingActionCountRef = useRef(0);
|
|
223
251
|
useEffect(() => {
|
|
224
252
|
stateRef.current = cloneSnapRenderState(initialState);
|
|
225
253
|
}, [initialState]);
|
|
254
|
+
useEffect(() => {
|
|
255
|
+
onRenderStateChangeRef.current = onRenderStateChange;
|
|
256
|
+
}, [onRenderStateChange]);
|
|
257
|
+
useEffect(() => stateStore.subscribe(() => {
|
|
258
|
+
const snapshot = cloneSnapRenderState(stateStore.getSnapshot());
|
|
259
|
+
stateRef.current = snapshot;
|
|
260
|
+
onRenderStateChangeRef.current?.(snapshot);
|
|
261
|
+
}), [stateStore]);
|
|
226
262
|
useEffect(() => {
|
|
227
263
|
const catalogResult = snapJsonRenderCatalog.validate(spec);
|
|
228
264
|
if (!catalogResult.success) {
|
|
@@ -242,10 +278,6 @@ export function SnapViewCore({ snap, handlers, loading = false, appearance = "da
|
|
|
242
278
|
confetti: 0,
|
|
243
279
|
fireworks: 0,
|
|
244
280
|
});
|
|
245
|
-
const onRenderStateChangeRef = useRef(onRenderStateChange);
|
|
246
|
-
useEffect(() => {
|
|
247
|
-
onRenderStateChangeRef.current = onRenderStateChange;
|
|
248
|
-
}, [onRenderStateChange]);
|
|
249
281
|
useEffect(() => {
|
|
250
282
|
const effectsToPresent = getUnpresentedSnapEffects(stateRef.current, snapEffects);
|
|
251
283
|
if (effectsToPresent.length === 0) {
|
|
@@ -262,7 +294,10 @@ export function SnapViewCore({ snap, handlers, loading = false, appearance = "da
|
|
|
262
294
|
return;
|
|
263
295
|
}
|
|
264
296
|
if (markSnapEffectsPresented(stateRef.current, effectsToPresent)) {
|
|
265
|
-
|
|
297
|
+
const meta = recordValue(stateRef.current.__snapRender);
|
|
298
|
+
stateStore.update({
|
|
299
|
+
"/__snapRender/presentedEffects": meta?.presentedEffects ?? [],
|
|
300
|
+
});
|
|
266
301
|
}
|
|
267
302
|
setEffectRunKeys((current) => ({
|
|
268
303
|
confetti: effectsToPresent.includes("confetti")
|
|
@@ -276,7 +311,7 @@ export function SnapViewCore({ snap, handlers, loading = false, appearance = "da
|
|
|
276
311
|
? current.fireworks
|
|
277
312
|
: 0,
|
|
278
313
|
}));
|
|
279
|
-
}, [initialState, showConfetti, showFireworks, snapEffects]);
|
|
314
|
+
}, [initialState, showConfetti, showFireworks, snapEffects, stateStore]);
|
|
280
315
|
const accentName = snap.theme?.accent ?? "purple";
|
|
281
316
|
const accentHex = useMemo(() => resolveSnapPaletteHex(accentName, appearance), [accentName, appearance]);
|
|
282
317
|
const previewSurfaceStyle = useMemo(() => {
|
|
@@ -288,33 +323,52 @@ export function SnapViewCore({ snap, handlers, loading = false, appearance = "da
|
|
|
288
323
|
...vars,
|
|
289
324
|
};
|
|
290
325
|
}, [accentName, appearance]);
|
|
326
|
+
const applyActionActivityState = useCallback((name, params, pending) => {
|
|
327
|
+
stateStore.update(Object.fromEntries(buildActionActivityStateChanges({
|
|
328
|
+
actionName: name,
|
|
329
|
+
params,
|
|
330
|
+
pending,
|
|
331
|
+
}).map(({ path, value }) => [path, value])));
|
|
332
|
+
}, [stateStore]);
|
|
333
|
+
const setActionPending = useCallback((name, params) => {
|
|
334
|
+
pendingActionCountRef.current += 1;
|
|
335
|
+
applyActionActivityState(name, params, true);
|
|
336
|
+
}, [applyActionActivityState]);
|
|
337
|
+
const setActionSettled = useCallback((name, params) => {
|
|
338
|
+
pendingActionCountRef.current = Math.max(0, pendingActionCountRef.current - 1);
|
|
339
|
+
applyActionActivityState(name, params, false);
|
|
340
|
+
}, [applyActionActivityState]);
|
|
291
341
|
const handleAction = useCallback((name, params) => {
|
|
292
342
|
const inputs = (stateRef.current.inputs ?? {});
|
|
293
343
|
const p = (params ?? {});
|
|
344
|
+
let result;
|
|
345
|
+
setActionPending(name, p);
|
|
294
346
|
switch (name) {
|
|
295
347
|
case "submit":
|
|
296
|
-
handlers.submit(String(p.target ?? ""), inputs);
|
|
348
|
+
result = handlers.submit(String(p.target ?? ""), inputs);
|
|
297
349
|
break;
|
|
298
350
|
case "open_url":
|
|
299
|
-
handlers.open_url(String(p.target ?? ""));
|
|
351
|
+
result = handlers.open_url(String(p.target ?? ""));
|
|
300
352
|
break;
|
|
301
353
|
case "open_snap":
|
|
302
|
-
handlers.open_snap(String(p.target ?? ""));
|
|
354
|
+
result = handlers.open_snap(String(p.target ?? ""));
|
|
303
355
|
break;
|
|
304
356
|
case "open_mini_app":
|
|
305
|
-
handlers.open_mini_app(String(p.target ?? ""));
|
|
357
|
+
result = handlers.open_mini_app(String(p.target ?? ""));
|
|
306
358
|
break;
|
|
307
359
|
case "view_cast":
|
|
308
|
-
handlers.view_cast({ hash: String(p.hash ?? "") });
|
|
360
|
+
result = handlers.view_cast({ hash: String(p.hash ?? "") });
|
|
309
361
|
break;
|
|
310
362
|
case "view_profile":
|
|
311
|
-
handlers.view_profile({ fid: Number(p.fid ?? 0) });
|
|
363
|
+
result = handlers.view_profile({ fid: Number(p.fid ?? 0) });
|
|
312
364
|
break;
|
|
313
365
|
case "view_channel":
|
|
314
|
-
handlers.view_channel({
|
|
366
|
+
result = handlers.view_channel({
|
|
367
|
+
channelKey: String(p.channelKey ?? ""),
|
|
368
|
+
});
|
|
315
369
|
break;
|
|
316
370
|
case "compose_cast":
|
|
317
|
-
handlers.compose_cast({
|
|
371
|
+
result = handlers.compose_cast({
|
|
318
372
|
text: p.text ? String(p.text) : undefined,
|
|
319
373
|
channelKey: p.channelKey ? String(p.channelKey) : undefined,
|
|
320
374
|
embeds: Array.isArray(p.embeds)
|
|
@@ -323,10 +377,10 @@ export function SnapViewCore({ snap, handlers, loading = false, appearance = "da
|
|
|
323
377
|
});
|
|
324
378
|
break;
|
|
325
379
|
case "view_token":
|
|
326
|
-
handlers.view_token({ token: String(p.token ?? "") });
|
|
380
|
+
result = handlers.view_token({ token: String(p.token ?? "") });
|
|
327
381
|
break;
|
|
328
382
|
case "send_token":
|
|
329
|
-
handlers.send_token({
|
|
383
|
+
result = handlers.send_token({
|
|
330
384
|
token: String(p.token ?? ""),
|
|
331
385
|
amount: p.amount ? String(p.amount) : undefined,
|
|
332
386
|
recipientFid: p.recipientFid ? Number(p.recipientFid) : undefined,
|
|
@@ -336,13 +390,13 @@ export function SnapViewCore({ snap, handlers, loading = false, appearance = "da
|
|
|
336
390
|
});
|
|
337
391
|
break;
|
|
338
392
|
case "swap_token":
|
|
339
|
-
handlers.swap_token({
|
|
393
|
+
result = handlers.swap_token({
|
|
340
394
|
sellToken: p.sellToken ? String(p.sellToken) : undefined,
|
|
341
395
|
buyToken: p.buyToken ? String(p.buyToken) : undefined,
|
|
342
396
|
});
|
|
343
397
|
break;
|
|
344
398
|
case "send_transaction":
|
|
345
|
-
handlers.send_transaction?.({
|
|
399
|
+
result = handlers.send_transaction?.({
|
|
346
400
|
chainId: String(p.chainId ?? ""),
|
|
347
401
|
to: String(p.to ?? ""),
|
|
348
402
|
data: optionalString(p.data),
|
|
@@ -353,32 +407,23 @@ export function SnapViewCore({ snap, handlers, loading = false, appearance = "da
|
|
|
353
407
|
maxPriorityFeePerGas: optionalString(p.maxPriorityFeePerGas),
|
|
354
408
|
});
|
|
355
409
|
break;
|
|
356
|
-
case "send_calls":
|
|
357
|
-
handlers.send_calls?.({
|
|
358
|
-
version: p.version === "1.0" ? "1.0" : undefined,
|
|
359
|
-
chainId: String(p.chainId ?? ""),
|
|
360
|
-
atomicRequired: typeof p.atomicRequired === "boolean"
|
|
361
|
-
? p.atomicRequired
|
|
362
|
-
: undefined,
|
|
363
|
-
id: optionalString(p.id),
|
|
364
|
-
calls: Array.isArray(p.calls)
|
|
365
|
-
? p.calls.map((call) => {
|
|
366
|
-
const c = asRecord(call);
|
|
367
|
-
return {
|
|
368
|
-
to: optionalString(c.to),
|
|
369
|
-
data: optionalString(c.data),
|
|
370
|
-
value: optionalString(c.value),
|
|
371
|
-
};
|
|
372
|
-
})
|
|
373
|
-
: [],
|
|
374
|
-
});
|
|
375
|
-
break;
|
|
376
410
|
default:
|
|
377
411
|
break;
|
|
378
412
|
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
413
|
+
if (result instanceof Promise) {
|
|
414
|
+
void result.finally(() => {
|
|
415
|
+
setActionSettled(name, p);
|
|
416
|
+
}).catch(() => { });
|
|
417
|
+
}
|
|
418
|
+
else {
|
|
419
|
+
setActionSettled(name, p);
|
|
420
|
+
}
|
|
421
|
+
return result;
|
|
422
|
+
}, [handlers, setActionPending, setActionSettled]);
|
|
423
|
+
return (_jsxs("div", { "data-snap-view-root": true, style: { position: "relative", width: "100%" }, onClickCapture: (event) => {
|
|
424
|
+
if (!hasPendingSnapAction(stateRef.current))
|
|
425
|
+
return;
|
|
426
|
+
event.preventDefault();
|
|
427
|
+
event.stopPropagation();
|
|
428
|
+
}, children: [showConfetti && effectRunKeys.confetti > 0 && (_jsx(ConfettiOverlay, {}, effectRunKeys.confetti)), showFireworks && effectRunKeys.fireworks > 0 && (_jsx(FireworksOverlay, {}, effectRunKeys.fireworks)), loadingOverlay === undefined ? (_jsx(SnapLoadingOverlay, { appearance: appearance, accentHex: accentHex, active: loading })) : loading ? (_jsx(_Fragment, { children: loadingOverlay })) : null, _jsx("div", { style: previewSurfaceStyle, children: _jsx(SnapPreviewAccentProvider, { pageAccent: snap.theme?.accent, appearance: appearance, children: _jsx(SnapVersionProvider, { value: snap.version === "2.0" ? "2.0" : "1.0", children: _jsx(SnapCatalogView, { spec: spec, store: stateStore, loading: false, onAction: handleAction, children: loadingOverlay === undefined ? (_jsx(SnapPendingActionOverlay, { appearance: appearance, accentHex: accentHex })) : null }, pageKey) }) }) })] }));
|
|
384
429
|
}
|
|
@@ -49,7 +49,7 @@ export function SnapCardV1({ snap, handlers, loading = false, appearance = "dark
|
|
|
49
49
|
position: "relative",
|
|
50
50
|
width: "100%",
|
|
51
51
|
maxWidth,
|
|
52
|
-
}, children: [_jsxs("div", { style: {
|
|
52
|
+
}, children: [_jsxs("div", { "data-snap-card-surface": true, style: {
|
|
53
53
|
position: "relative",
|
|
54
54
|
overflow: "hidden",
|
|
55
55
|
...(plain ? {} : {
|
|
@@ -90,7 +90,7 @@ export function SnapCardV2({ snap, handlers, loading = false, appearance = "dark
|
|
|
90
90
|
position: "relative",
|
|
91
91
|
width: "100%",
|
|
92
92
|
maxWidth,
|
|
93
|
-
}, children: [_jsxs("div", { style: {
|
|
93
|
+
}, children: [_jsxs("div", { "data-snap-card-surface": true, style: {
|
|
94
94
|
position: "relative",
|
|
95
95
|
maxHeight: containerMaxHeight,
|
|
96
96
|
overflow: "hidden",
|
|
@@ -22,6 +22,7 @@ export function SnapActionButton({ element, emit, }) {
|
|
|
22
22
|
const label = String(props.label ?? "Action");
|
|
23
23
|
const variant = String(props.variant ?? "secondary");
|
|
24
24
|
const isPrimary = variant === "primary";
|
|
25
|
+
const disabled = props.disabled === true;
|
|
25
26
|
const iconName = props.icon ? String(props.icon) : undefined;
|
|
26
27
|
const textColor = isPrimary ? "#fff" : colors.text;
|
|
27
28
|
const iconColor = isPrimary ? "#fff" : colors.text;
|
|
@@ -37,7 +38,10 @@ export function SnapActionButton({ element, emit, }) {
|
|
|
37
38
|
? { backgroundColor: pressed ? accentHex + "DD" : accentHex }
|
|
38
39
|
: { backgroundColor: pressed ? colors.mutedHover : colors.muted },
|
|
39
40
|
pressed && styles.pressed,
|
|
40
|
-
|
|
41
|
+
disabled && styles.disabled,
|
|
42
|
+
], disabled: disabled, onPress: () => {
|
|
43
|
+
if (disabled)
|
|
44
|
+
return;
|
|
41
45
|
if (runPaginatorAction(stateStore, paginatorAction))
|
|
42
46
|
return;
|
|
43
47
|
void (async () => {
|
|
@@ -85,4 +89,5 @@ const styles = StyleSheet.create({
|
|
|
85
89
|
paddingVertical: 6,
|
|
86
90
|
},
|
|
87
91
|
pressed: { opacity: 0.88 },
|
|
92
|
+
disabled: { opacity: 0.62 },
|
|
88
93
|
});
|