Skip to content

Commit 3ec5546

Browse files
committed
Enhances embed functionality for channels/playlists
Extends the embed component to support homepage, channels, playlists, and markdown content. For channel embeds, it now displays a grid of the latest uploads. Playlist embeds showcase the cover and a playable list. Adds support for rendering markdown content within embeds, fetching the content and displaying it. Also configures the homepage embed to point Mini App action to the homepage embed. Simplify unsaved settings warning logic remove comment Removed embed menu item to maintain focus on current changes. Improves embed and channel page handling Addresses issues related to embed path detection and homepage update loops. - Ensures correct embed path detection by checking for a valid pathname. - Prevents repeated 'isHome' updates on the homepage by using a bounded set, avoiding potential loops. - Extracts and memoizes the channel page wrapper component to prevent unnecessary re-renders. - Improves playlist ID detection for collection handling in embed wrappers. Improves embed and upload page handling Addresses several issues and improves the user experience. - Ensures the active uploads warning only appears when not on the upload page by checking the pathname more reliably. - Corrects collection redirection logic by adding a code block. - Removes duplicate URI building logic in embed wrappers. - Enhances Farcaster embed functionality by including the SDK and ready script only on embed pages to avoid unnecessary overhead on other pages. Improves Farcaster embed handling and OG image. Enhances Farcaster integration by adding an iframe check to the embed script, ensuring it only runs when not in a top-level browsing context. Updates the Open Graph image URL used for Farcaster frames to use the thumbnail card CDN URL if available, falling back to other defined URLs, which delivers a richer visual experience. Allows iframing for homepage Permits iframing of the homepage to support embedding in Farcaster miniapps, in addition to existing embed pages and API. This change enhances integration possibilities with external platforms. Adds debug logging for action URLs Adds console logs to output the generated fcActionUrl to aid in debugging mini app integrations on the homepage. This helps to verify the correct URL is being used, especially during development with different origins. Adds debug logging to build OG metadata Adds a console log statement to output the override options passed into the buildBasicOgMetadata function. This will aid in debugging issues related to Open Graph metadata generation during development. Improves embed player UI and URI handling Adds a dedicated CSS class for the reactions overlay in the embedded video player, improving styling and maintainability. Refactors URI handling in the embed wrapper to prioritize incoming URI for live/latest content, ensuring correct content resolution. Uses resolved URL in embed mode with live path In embed mode when viewing a live/latest stream, uses the resolved URL from lbry.tv instead of the channel URL. This ensures the correct stream is displayed when the user navigates from a live stream to its archived version. Enables playlist support in embed mode Adds support for playlist URLs in embed mode by redirecting to the first item in the playlist with a collection ID parameter. This allows users to view playlists in an embedded player, providing a better user experience. It also handles the initial loading state for playlist URLs. Additionally, the middleware is updated to allow iframing for playlist pages. Fixes channel page navigation in embed mode Ensures correct URL construction for channel pages when viewed in embed mode. It checks if the current path is within the embed context and prepends `/$/embed` to the base URL. This adjustment ensures proper navigation and link generation within the embedded player.
1 parent 3d6f53c commit 3ec5546

File tree

37 files changed

+1000
-343
lines changed

37 files changed

+1000
-343
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,5 @@ package-lock.json
4141
.env.lbrytv
4242
analyzeResults*.html
4343
ecosystem.config.js
44+
45+
.claude/settings.local.json

config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ const config = {
115115
// FROM COMMAND LINE
116116
COMMIT_ID: process.env.COMMIT_ID,
117117
NODE_ENV: process.env.NODE_ENV,
118+
// Dev/staged feature gating
119+
DYNAMIC_ROUTES_FIRST: process.env.DYNAMIC_ROUTES_FIRST === 'true',
118120
};
119121

120122
config.SDK_API_PATH = `${config.LBRY_WEB_API}/api/v1`;

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
"dev": "yarn dev:electron",
2828
"dev:electron": "cross-env NODE_ENV=development node ./electron/devServer.js",
2929
"dev:web": "yarn copyenv && cd web && yarn dev",
30-
"dev:web-server": "cross-env NODE_ENV=development yarn compile:web && concurrently \"cross-env NODE_ENV=development yarn compile:web --watch\" \"cd web && yarn dev:server\"",
30+
"dev:web-server": "cross-env NODE_ENV=development DYNAMIC_ROUTES_FIRST=true yarn compile:web && cross-env NODE_ENV=development DYNAMIC_ROUTES_FIRST=true concurrently \"yarn compile:web --watch\" \"cd web && yarn dev:server\"",
3131
"dev:internal-apis": "LBRY_API_URL='http://localhost:8080' yarn dev:electron",
3232
"dev:iatv": "LBRY_API_URL='http://localhost:15400' SDK_API_URL='http://localhost:15100' yarn dev:web",
3333
"run:web-server": "cross-env NODE_ENV=production yarn compile:web && cd web && yarn dev:server",

ui/component/app/view.jsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,24 @@ function App(props: Props) {
225225

226226
// Only 1 nag is possible, so show the most important:
227227

228+
// Active uploads warning (show globally so users know why the browser prompts on leave)
229+
if (uploadCount > 0 && !embedPath) {
230+
const pathname = location && location.pathname;
231+
const onUploadPage =
232+
(pathname && pathname.startsWith(`/$/${PAGES.UPLOAD}`)) ||
233+
(pathname && pathname.startsWith(`/$/${PAGES.UPLOADS}`));
234+
if (!onUploadPage) {
235+
return (
236+
<Nag
237+
type="helpful"
238+
message={__('Upload in progress. Closing or reloading may interrupt your upload.')}
239+
actionText={__('View Uploads')}
240+
onClick={() => history.push(`/$/${PAGES.UPLOADS}`)}
241+
/>
242+
);
243+
}
244+
}
245+
228246
if (user === null && !embedPath) {
229247
return <NagNoUser />;
230248
}

ui/component/claimTilesDiscover/view.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,8 @@ function ClaimTilesDiscover(props: Props) {
116116
const uris = (prefixUris || []).concat(claimSearchUris);
117117
if (prefixUris && prefixUris.length) uris.splice(prefixUris.length * -1, prefixUris.length);
118118

119-
if (window.location.pathname === '/') {
119+
// Treat the embed homepage the same as the main homepage for pin injection.
120+
if (window.location.pathname === '/' || window.location.pathname === '/$/embed/home') {
120121
injectPinUrls(uris, pins, resolvedPinUris);
121122
}
122123

ui/component/embedTextArea/view.jsx

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ export default function EmbedTextArea(props: Props) {
2323
props;
2424

2525
const [embedAutoplay, setEmbedAutoplay] = React.useState(false);
26+
const isChannel = claim && claim.value_type === 'channel';
27+
const isCollection = claim && claim.value_type === 'collection';
28+
const showAutoplayToggle = !isChannel && !isCollection && !newestType;
2629

2730
const { canonical_url: canonicalUri } = claim;
2831
const input = useRef();
@@ -66,15 +69,17 @@ export default function EmbedTextArea(props: Props) {
6669
readOnly
6770
/>
6871

69-
<div className="margin-vertical-medium">
70-
<FormField
71-
name={'embed-autoplay' + (newestType ? ' ' + newestType : '')}
72-
type="checkbox"
73-
label={__('Enable Autoplay')}
74-
checked={embedAutoplay}
75-
onChange={() => setEmbedAutoplay((prev) => !prev)}
76-
/>
77-
</div>
72+
{showAutoplayToggle && (
73+
<div className="margin-vertical-medium">
74+
<FormField
75+
name={'embed-autoplay' + (newestType ? ' ' + newestType : '')}
76+
type="checkbox"
77+
label={__('Enable Autoplay')}
78+
checked={embedAutoplay}
79+
onChange={() => setEmbedAutoplay((prev) => !prev)}
80+
/>
81+
</div>
82+
)}
7883

7984
<div className="section__actions">
8085
<Button

ui/component/fileActions/view.jsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,13 @@ export default function FileActions(props: Props) {
9090
const channelName = signingChannel && signingChannel.name;
9191
const fileName = value && value.source && value.source.name;
9292
const claimType = isLivestreamClaim ? 'livestream' : isPostClaim ? 'post' : 'upload';
93-
94-
const webShareable = costInfo && costInfo.cost === 0 && RENDER_MODES.WEB_SHAREABLE_MODES.includes(renderMode);
93+
const isCollectionClaim = claim && claim.value_type === 'collection';
94+
const isChannel = claim && claim.value_type === 'channel';
95+
const webShareable =
96+
(costInfo && costInfo.cost === 0 && RENDER_MODES.WEB_SHAREABLE_MODES.includes(renderMode)) ||
97+
RENDER_MODES.TEXT_MODES.includes(renderMode) ||
98+
isCollectionClaim ||
99+
isChannel;
95100
const urlParams = new URLSearchParams(search);
96101
const collectionId = urlParams.get(COLLECTIONS_CONSTS.COLLECTION_ID);
97102

ui/component/header/view.jsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ type Props = {
5555
openChangelog: ({}) => void,
5656
setSidebarOpen: (boolean) => void,
5757
signOut: () => void,
58+
hideSidebarToggle?: boolean,
5859
};
5960

6061
const Header = (props: Props) => {
@@ -116,7 +117,9 @@ const Header = (props: Props) => {
116117

117118
// Sign out if they click the "x" when they are on the password prompt
118119
const authHeaderAction = syncError && { onClick: signOut };
119-
const homeButtonNavigationProps = (isVerifyPage && {}) || (authHeader && authHeaderAction) || { navigate: '/' };
120+
const isEmbedPath = pathname && pathname.startsWith('/$/embed');
121+
const homeButtonNavigationProps = (isVerifyPage && {}) ||
122+
(authHeader && authHeaderAction) || { navigate: isEmbedPath ? '/$/embed/home' : '/' };
120123
const sidebarLabel = sidebarOpen
121124
? __('Close sidebar - hide channels you are following.')
122125
: __('Expand sidebar - view channels you are following.');
@@ -244,7 +247,7 @@ const Header = (props: Props) => {
244247
<div className="header__menu--left">
245248
<SkipNavigationButton />
246249

247-
{!authHeader && (
250+
{!authHeader && !props.hideSidebarToggle && (
248251
<span style={{ position: 'relative' }}>
249252
<Button
250253
aria-label={sidebarLabel}
@@ -262,7 +265,7 @@ const Header = (props: Props) => {
262265
aria-label={__('Home')}
263266
className="header__navigationItem--logo"
264267
onClick={() => {
265-
if (pathname === '/') {
268+
if (pathname === '/' || pathname === '/$/embed/home') {
266269
window.scrollTo({ top: 0, left: 0, behavior: 'smooth' });
267270
doClearClaimSearch();
268271
}

ui/component/page/view.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ function Page(props: Props) {
101101
sidebarOpen={sidebarOpen}
102102
isAbsoluteSideNavHidden={isAbsoluteSideNavHidden}
103103
setSidebarOpen={openSidebar}
104+
hideSidebarToggle={noSideNavigation}
104105
/>
105106
)}
106107

ui/component/router/view.jsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ type Props = {
162162
length: number,
163163
location: { pathname: string },
164164
push: (string) => void,
165+
replace: (string) => void,
165166
state: {},
166167
replaceState: ({}, string, string) => void,
167168
listen: (any) => () => void,
@@ -356,7 +357,9 @@ function AppRouter(props: Props) {
356357
// in the browser causing a redirect loop
357358
const decodedUrl = decodeURIComponent(pathname) + search;
358359
if (decodedUrl !== pathname + search) {
359-
return <Redirect to={decodedUrl} />;
360+
// Use history.replace instead of <Redirect> to avoid adding extra entries.
361+
history.replace(decodedUrl);
362+
return null;
360363
}
361364

362365
// Try to support strange cases where url has html encoding
@@ -478,6 +481,7 @@ function AppRouter(props: Props) {
478481

479482
<Route path={`/$/${PAGES.POPOUT}/:channelName/:streamName`} component={PopoutChatPage} />
480483

484+
<Route path={`/$/${PAGES.EMBED}/home`} exact component={HomePage} />
481485
<Route path={`/$/${PAGES.EMBED}/:claimName`} exact component={EmbedWrapperPage} />
482486
<Route path={`/$/${PAGES.EMBED}/:claimName/:claimId`} exact component={EmbedWrapperPage} />
483487

0 commit comments

Comments
 (0)