Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: frame v2 types #3532

Merged
merged 29 commits into from
Dec 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
3ec265a
chore: types
guanbinrui Dec 20, 2024
e2b3a6a
chore: code maintenance [bot]
github-actions[bot] Dec 20, 2024
345f1d0
refactor: types
guanbinrui Dec 20, 2024
50a8389
refactor: config
guanbinrui Dec 20, 2024
78ffaee
refactor: rm types
guanbinrui Dec 20, 2024
547da0b
chore: add host sdk
guanbinrui Dec 20, 2024
fb1b9a0
chore: provider
guanbinrui Dec 20, 2024
7c64242
chore: code maintenance [bot]
github-actions[bot] Dec 20, 2024
fd88db4
refactor: frame host
guanbinrui Dec 20, 2024
be76112
refactor: primary button
guanbinrui Dec 20, 2024
5a8b62d
chore: code maintenance [bot]
github-actions[bot] Dec 20, 2024
05ad696
chore: post
guanbinrui Dec 20, 2024
4b2bda3
chore: code maintenance [bot]
github-actions[bot] Dec 21, 2024
53f4a79
chore: impl FrameHost
guanbinrui Dec 21, 2024
cbbc894
chore: code maintenance [bot]
github-actions[bot] Dec 21, 2024
c01a850
chore: new Host
guanbinrui Dec 23, 2024
ab0ade1
chore: code maintenance [bot]
github-actions[bot] Dec 23, 2024
945ecbf
refactor: frame v2
guanbinrui Dec 23, 2024
bdc0354
refactor: login required
guanbinrui Dec 23, 2024
8a7e206
chore: loading indicator
guanbinrui Dec 23, 2024
1449a80
fix: the bg color
guanbinrui Dec 23, 2024
890d088
chore: update dep
guanbinrui Dec 23, 2024
79b9c81
chore: debug
guanbinrui Dec 23, 2024
6debc09
chore: debug
guanbinrui Dec 23, 2024
e269fba
chore: throw error if no profile found
guanbinrui Dec 23, 2024
03f8e5f
chore: impl reload
guanbinrui Dec 23, 2024
6ac0d48
chore: tooltip
guanbinrui Dec 23, 2024
d1b8f4f
Revert "chore: tooltip"
guanbinrui Dec 23, 2024
cdd177b
chore: code maintenance [bot]
github-actions[bot] Dec 23, 2024
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"@dialectlabs/blinks": "^0.15.1",
"@dimensiondev/holoflows-kit": "0.9.0-20240322092738-f9180f3",
"@farcaster/core": "^0.15.6",
"@farcaster/frame-host": "^0.0.19",
"@giphy/js-fetch-api": "^5.6.0",
"@giphy/react-components": "^9.6.0",
"@headlessui/react": "2.1.10",
Expand Down
51 changes: 47 additions & 4 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

51 changes: 45 additions & 6 deletions src/components/Frame/V2/Card.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,51 @@
import { memo } from 'react';
import type { SetPrimaryButton } from '@farcaster/frame-host';
import { memo, useState } from 'react';

import { ClickableButton } from '@/components/ClickableButton.js';
import { Image } from '@/components/Image.js';
import { FrameViewerModalRef } from '@/modals/controls.js';
import { Source } from '@/constants/enum.js';
import { getCurrentProfile } from '@/helpers/getCurrentProfile.js';
import { FrameViewerModalRef, LoginModalRef } from '@/modals/controls.js';
import { FarcasterFrameHost } from '@/providers/frame/Host.js';
import type { Post } from '@/providers/types/SocialMedia.js';
import type { FrameV2 } from '@/types/frame.js';

interface CardProps {
post: Post;
frame: FrameV2;
}

export const Card = memo<CardProps>(function Card({ frame }) {
export const Card = memo<CardProps>(function Card({ post, frame }) {
const [primaryButton, setPrimaryButton] = useState<Parameters<SetPrimaryButton>[0] | null>(null);

const [frameHost] = useState(
() =>
new FarcasterFrameHost(frame, post, {
ready: (options) => {
FrameViewerModalRef.open({
ready: true,
frame,
frameHost,
});
},
close: () => FrameViewerModalRef.close(),
setPrimaryButton,
}),
);

const onClick = () => {
const profile = getCurrentProfile(Source.Farcaster);
if (!profile) {
LoginModalRef.open({
source: Source.Farcaster,
});
return;
}

FrameViewerModalRef.open({
ready: false,
frame,
frameHost,
});
};

Expand All @@ -26,9 +59,15 @@ export const Card = memo<CardProps>(function Card({ frame }) {
src={frame.imageUrl}
alt={frame.x_url}
/>
<ClickableButton className="bg-fireflyBrand px-1 py-3 font-bold text-white" onClick={onClick}>
{frame.button.action.name}
</ClickableButton>
{primaryButton?.hidden ? null : (
<ClickableButton
className="bg-fireflyBrand px-1 py-3 font-bold text-white"
disabled={primaryButton?.loading || primaryButton?.disabled}
onClick={onClick}
>
{primaryButton?.text ?? frame.button.action.name}
</ClickableButton>
)}
</div>
);
});
4 changes: 2 additions & 2 deletions src/components/Frame/V2/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ interface FrameLayoutProps {
children?: ReactNode;
}

export const FrameLayout = memo<FrameLayoutProps>(function FrameLayout({ frame }) {
export const FrameLayout = memo<FrameLayoutProps>(function FrameLayout({ post, frame }) {
return (
<div className="pt-2">
<Card frame={frame} />
<Card post={post} frame={frame} />
</div>
);
});
17 changes: 17 additions & 0 deletions src/helpers/createEIP1193Provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { noop } from 'lodash-es';
import { getClient } from 'wagmi/actions';

import { config } from '@/configs/wagmiClient.js';

export function createEIP1193Provider() {
return {
async request<T>(parameters: unknown): Promise<T> {
const client = await getClient(config);
if (!client) throw new Error('Client not found');

return client.request(parameters as Parameters<typeof client.request>[0]);
},
on: noop,
removeListener: noop,
};
}
4 changes: 3 additions & 1 deletion src/modals/FrameViewerModal/MoreActionMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,18 @@ import { MenuGroup } from '@/components/MenuGroup.js';
import { MoreActionMenu } from '@/components/MoreActionMenu.js';

interface MoreActionProps {
disabled?: boolean;
onReload?: () => void;
}

export const MoreAction = memo(function MoreAction({ onReload }: MoreActionProps) {
export const MoreAction = memo(function MoreAction({ disabled = false, onReload }: MoreActionProps) {
return (
<MoreActionMenu loginRequired={false} button={<MoreIcon width={24} height={24} className="text-main" />}>
<MenuGroup>
<MenuItem>
{({ close }) => (
<MenuButton
disabled={disabled}
onClick={() => {
close();
onReload?.();
Expand Down
56 changes: 49 additions & 7 deletions src/modals/FrameViewerModal/index.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
import { forwardRef, useState } from 'react';
import { exposeToIframe, type FrameHost } from '@farcaster/frame-host';
import { delay } from '@masknet/kit';
import { forwardRef, useEffect, useRef, useState } from 'react';
import { useAsyncFn } from 'react-use';

import FireflyLogo from '@/assets/firefly.logo.svg';
import { CloseButton } from '@/components/IconButton.js';
import { Modal } from '@/components/Modal.js';
import { NotImplementedError } from '@/constants/error.js';
import { IS_DEVELOPMENT } from '@/constants/index.js';
import { createEIP1193Provider } from '@/helpers/createEIP1193Provider.js';
import { parseUrl } from '@/helpers/parseUrl.js';
import { useSingletonModal } from '@/hooks/useSingletonModal.js';
import type { SingletonModalRefCreator } from '@/libs/SingletonModal.js';
import { MoreAction } from '@/modals/FrameViewerModal/MoreActionMenu.js';
import type { FrameV2 } from '@/types/frame.js';

export type FrameViewerModalOpenProps = {
ready: boolean;
frame: FrameV2;
frameHost: Omit<FrameHost, 'ethProviderRequestV2'>;
};
export type FrameViewerModalCloseProps = void;

export const FrameViewerModal = forwardRef<SingletonModalRefCreator<FrameViewerModalOpenProps>>(
function FrameViewerModal(_, ref) {
const frameRef = useRef<HTMLIFrameElement | null>(null);
const [props, setProps] = useState<FrameViewerModalOpenProps | null>(null);

const [open, dispatch] = useSingletonModal(ref, {
Expand All @@ -27,6 +35,38 @@ export const FrameViewerModal = forwardRef<SingletonModalRefCreator<FrameViewerM
},
});

useEffect(() => {
if (!frameRef.current) return;

// frame host is required
if (!props?.frameHost) return;

const result = exposeToIframe({
debug: IS_DEVELOPMENT,
iframe: frameRef.current,
sdk: props.frameHost,
ethProvider: createEIP1193Provider(),
frameOrigin: '*',
});

return () => {
result?.cleanup();
};
}, [props]);

const [{ loading }, onReload] = useAsyncFn(async () => {
if (!props) return;

const modalProps = props;

setProps(null);
await delay(1000);
setProps({
...modalProps,
ready: false,
});
}, [props]);

if (!open || !props) return null;

const { frame } = props;
Expand All @@ -44,22 +84,24 @@ export const FrameViewerModal = forwardRef<SingletonModalRefCreator<FrameViewerM
{u ? <div className="text-faint text-xs">{u.host}</div> : null}
</div>
<div>
<MoreAction
onReload={() => {
throw new NotImplementedError();
}}
/>
<MoreAction disabled={loading} onReload={onReload} />
</div>
</div>
<iframe
className="scrollbar-hide h-full w-full opacity-100"
ref={frameRef}
src={frame.button.action.url}
allow="clipboard-write 'src'"
sandbox="allow-forms allow-scripts allow-same-origin"
style={{
backgroundColor: frame.button.action.splashBackgroundColor,
}}
/>
{!props.ready ? (
<div className="absolute inset-0 top-[60px] flex items-center justify-center bg-white dark:bg-black">
<FireflyLogo width={80} height={80} />
</div>
) : null}
</div>
</Modal>
);
Expand Down
Loading
Loading